This repository has been archived by the owner on Jul 25, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
173 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Summary | ||
|
||
This is a PoC of using Hazelcast peer-to-peer cluster to implement Spring Cloud Gateway Rate Limiter for horizontally scaled CF app. | ||
|
||
# Build and deploy | ||
|
||
``` | ||
$ ./gradlew assemble | ||
$ cf push --var cf-apps-domain=<your cf env domain> | ||
$ ./configure-network.sh | ||
``` | ||
# Test | ||
|
||
``` | ||
$ curl -vv -k https://peer.<your cf env domain>/google | ||
# should return 403 - no header | ||
$ curl -vv -k https://peer.<your cf env domain>/google -H "X-API-Key: foo" | ||
# response from Google (404) + X-Remaining-Limit header | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env bash | ||
|
||
cf add-network-policy peer --destination-app=peer --protocol=tcp --port=5701 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
applications: | ||
- name: peer | ||
path: build/libs/scg-rate-limiter.jar | ||
instances: 2 | ||
routes: | ||
- route: peer.apps.internal | ||
- route: peer.((cf-apps-domain)) | ||
buildpacks: | ||
- java_buildpack_offline |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,4 @@ pluginManagement { | |
gradlePluginPortal() | ||
} | ||
} | ||
rootProject.name = 'hz-demo' | ||
rootProject.name = 'scg-rate-limiter' |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package scg.ratelimiter; | ||
|
||
import com.hazelcast.config.Config; | ||
import com.hazelcast.config.JoinConfig; | ||
import com.hazelcast.core.Hazelcast; | ||
import com.hazelcast.core.HazelcastInstance; | ||
import com.hazelcast.core.IMap; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter; | ||
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; | ||
import org.springframework.scheduling.annotation.EnableScheduling; | ||
import org.springframework.scheduling.annotation.Scheduled; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.validation.Validator; | ||
import org.springframework.web.server.ServerWebExchange; | ||
import reactor.core.publisher.Mono; | ||
|
||
import java.util.Collections; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
class HazelcastRateLimiterConfig { | ||
|
||
private int limit; | ||
|
||
public int getLimit() { | ||
return limit; | ||
} | ||
|
||
public void setLimit(int limit) { | ||
this.limit = limit; | ||
} | ||
} | ||
|
||
@Component | ||
class HazelcastRateLimiter extends AbstractRateLimiter<HazelcastRateLimiterConfig> { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(HazelcastRateLimiter.class); | ||
|
||
private IMap<String, Integer> map; | ||
private AtomicBoolean initialized = new AtomicBoolean(false); | ||
|
||
protected HazelcastRateLimiter(Validator defaultValidator) { | ||
super(HazelcastRateLimiterConfig.class, "hazelcast-rate-limiter", defaultValidator); | ||
} | ||
|
||
// super hacky - but DNS records X.peer.apps.internal will only be available shortly after application started | ||
// so InitializingBean or any other option to start cluster during app startup won't work | ||
@Scheduled(initialDelay = 3000, fixedDelay = Integer.MAX_VALUE) | ||
public void run() { | ||
String groupName = "my-test-group"; | ||
|
||
Config cfg = new Config(); | ||
cfg.getGroupConfig().setName(groupName); | ||
|
||
JoinConfig joinConfig = cfg.getNetworkConfig().getJoin(); | ||
joinConfig.getMulticastConfig().setEnabled(false); | ||
joinConfig.getTcpIpConfig().setEnabled(true) | ||
.addMember("0.peer.apps.internal") | ||
.addMember("1.peer.apps.internal"); | ||
|
||
HazelcastInstance instance = Hazelcast.newHazelcastInstance(cfg); | ||
map = instance.getMap("rate-limit"); | ||
initialized.set(true); | ||
} | ||
|
||
|
||
@Override | ||
public Mono<Response> isAllowed(String routeId, String id) { | ||
final Response notAllowed = new Response(false, Collections.emptyMap()); | ||
|
||
if (!initialized.get()) { | ||
logger.info("Cluster is not yet initialized"); | ||
return Mono.just(notAllowed); | ||
} | ||
|
||
Integer noRequests = map.getOrDefault(id, 0) + 1; | ||
HazelcastRateLimiterConfig config = getConfig().getOrDefault(routeId, new HazelcastRateLimiterConfig()); | ||
|
||
logger.info("Current limit is {}, total requests to date {}", config.getLimit(), noRequests); | ||
|
||
if (noRequests >= config.getLimit()) { | ||
logger.info("Exceeded number of allowed requests"); | ||
return Mono.just(notAllowed); | ||
} | ||
|
||
map.put(id, noRequests); | ||
final int remainingRequests = config.getLimit() - noRequests; | ||
|
||
return Mono.just(new Response(true, Collections.singletonMap("X-Remaining-Limit", String.valueOf(remainingRequests)))); | ||
} | ||
} | ||
|
||
@Component | ||
class HeaderKeyResolver implements KeyResolver { | ||
|
||
@Override | ||
public Mono<String> resolve(ServerWebExchange exchange) { | ||
String apiKey = exchange.getRequest().getHeaders().getFirst("X-API-Key"); | ||
return apiKey == null ? Mono.empty() : Mono.just(apiKey); | ||
} | ||
} | ||
|
||
@SpringBootApplication | ||
@EnableScheduling | ||
public class DemoApplication { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(DemoApplication.class, args); | ||
} | ||
|
||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
spring: | ||
cloud: | ||
gateway: | ||
routes: | ||
- id: github | ||
uri: https://google.com | ||
predicates: | ||
- Path=/google | ||
filters: | ||
- name: RequestRateLimiter | ||
args: | ||
rate-limiter: "#{@hazelcastRateLimiter}" | ||
key-resolver: "#{@headerKeyResolver}" | ||
hazelcast-rate-limiter.limit: 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters