fix(gateway-policies): tweak rate limiting to ensure probes work
MODIFY
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/AbstractMappedDataPolicy.java
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/AbstractMappedDataPolicy.java
@@ -27,6 +27,7 @@ import io.apiman.gateway.engine.policy.IPolicyContext;
* @author eric.wittmann@redhat.com
* @param <C> Policy configuration type
*/
+// TODO(msavy): Change to be interface to avoid diamond inheritance problem for TransferQuotaPolicy et al
public abstract class AbstractMappedDataPolicy<C> extends AbstractMappedPolicy<C> implements IDataPolicy {
/**

MODIFY
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/QuotaPolicy.java
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/QuotaPolicy.java
*/
package io.apiman.gateway.engine.policies;
-import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.PolicyFailureType;
import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent;
import io.apiman.gateway.engine.policies.config.RateLimitingConfig;
import io.apiman.gateway.engine.policies.i18n.Messages;
+import io.apiman.gateway.engine.policies.limiting.BucketFactory.BucketIdBuilderContext;
import io.apiman.gateway.engine.policies.probe.RateLimitingProbeConfig;
import io.apiman.gateway.engine.policy.IPolicyProbe;
@@ -77,8 +77,8 @@ public class QuotaPolicy extends RateLimitingPolicy
return DEFAULT_RESET_HEADER;
}
- protected String createBucketId(ApiRequest request, RateLimitingConfig config) {
- return "QUOTA||" + bucketId(request, config); //$NON-NLS-1$
+ @Override
+ protected String bucketId(RateLimitingConfig config, BucketIdBuilderContext context) {
+ return "QUOTA||" + super.bucketId(config, context);
}
-
}

MODIFY
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/RateLimitingPolicy.java
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/RateLimitingPolicy.java
@@ -18,11 +18,8 @@ package io.apiman.gateway.engine.policies;
import io.apiman.gateway.engine.async.AsyncResultImpl;
import io.apiman.gateway.engine.async.IAsyncResult;
import io.apiman.gateway.engine.async.IAsyncResultHandler;
-import io.apiman.gateway.engine.beans.Api;
-import io.apiman.gateway.engine.beans.ApiContract;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.ApiResponse;
-import io.apiman.gateway.engine.beans.Client;
import io.apiman.gateway.engine.beans.IPolicyProbeResponse;
import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.PolicyFailureType;
@@ -30,9 +27,9 @@ import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent;
import io.apiman.gateway.engine.components.IRateLimiterComponent;
import io.apiman.gateway.engine.components.rate.RateLimitResponse;
import io.apiman.gateway.engine.policies.config.RateLimitingConfig;
-import io.apiman.gateway.engine.policies.config.rates.RateLimitingGranularity;
-import io.apiman.gateway.engine.policies.config.rates.RateLimitingPeriod;
import io.apiman.gateway.engine.policies.i18n.Messages;
+import io.apiman.gateway.engine.policies.limiting.BucketFactory;
+import io.apiman.gateway.engine.policies.limiting.BucketFactory.BucketIdBuilderContext;
import io.apiman.gateway.engine.policies.probe.RateLimitingProbeConfig;
import io.apiman.gateway.engine.policies.probe.RateLimitingProbeResponse;
import io.apiman.gateway.engine.policy.IPolicyChain;
@@ -43,7 +40,6 @@ import io.apiman.gateway.engine.rates.RateBucketPeriod;
import java.util.HashMap;
import java.util.Map;
-import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
@@ -55,13 +51,12 @@ import org.apache.commons.lang3.StringUtils;
public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
implements IPolicyProbe<RateLimitingConfig, RateLimitingProbeConfig> {
- protected static final String NO_USER_AVAILABLE = "";
- protected static final String NO_CLIENT_AVAILABLE = "";
-
private static final String DEFAULT_LIMIT_HEADER = "X-RateLimit-Limit"; //$NON-NLS-1$
private static final String DEFAULT_REMAINING_HEADER = "X-RateLimit-Remaining"; //$NON-NLS-1$
private static final String DEFAULT_RESET_HEADER = "X-RateLimit-Reset"; //$NON-NLS-1$
+ private final BucketFactory bucketFactory = new BucketFactory();
+
/**
* Constructor.
*/
@@ -80,15 +75,15 @@ public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
protected void doApply(final ApiRequest request, final IPolicyContext context, final RateLimitingConfig config,
final IPolicyChain<ApiRequest> chain) {
String bucketId = bucketId(request, config);
- final RateBucketPeriod period = getPeriod(config);
+ final RateBucketPeriod period = bucketFactory.getPeriod(config);
- if (bucketId == NO_USER_AVAILABLE) {
+ if (bucketId.equals(BucketFactory.NO_USER_AVAILABLE)) {
IPolicyFailureFactoryComponent failureFactory = context.getComponent(IPolicyFailureFactoryComponent.class);
PolicyFailure failure = failureFactory.createFailure(PolicyFailureType.Other, PolicyFailureCodes.NO_USER_FOR_RATE_LIMITING, Messages.i18n.format("RateLimitingPolicy.NoUser")); //$NON-NLS-1$
chain.doFailure(failure);
return;
}
- if (bucketId == NO_CLIENT_AVAILABLE) {
+ if (bucketId.equals(BucketFactory.NO_CLIENT_AVAILABLE)) {
IPolicyFailureFactoryComponent failureFactory = context.getComponent(IPolicyFailureFactoryComponent.class);
PolicyFailure failure = failureFactory.createFailure(PolicyFailureType.Other, PolicyFailureCodes.NO_APP_FOR_RATE_LIMITING, Messages.i18n.format("RateLimitingPolicy.NoApp")); //$NON-NLS-1$
chain.doFailure(failure);
@@ -121,32 +116,6 @@ public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
});
}
- protected static String bucketId(ApiRequest request, RateLimitingConfig config) {
- BucketIdBuilderContext bucketInfo = new BucketIdBuilderContext()
- .setRateLimitingConfig(config)
- .setApi(request.getApi())
- .setContract(request.getContract())
- .setUserSupplier(() -> {
- String header = config.getUserHeader();
- if (!request.getHeaders().containsKey(header)) {
- return NO_USER_AVAILABLE;
- }
- return request.getHeaders().get(header);
- })
- .setRemoteAddr(request.getRemoteAddr());
- return bucketId(config, bucketInfo);
- }
-
- protected static String bucketId(RateLimitingProbeConfig probeConfig, ProbeContext probeContext, RateLimitingConfig config) {
- BucketIdBuilderContext bucketInfo = new BucketIdBuilderContext()
- .setRateLimitingConfig(config)
- .setApi(probeContext.getApi())
- .setContract(probeContext.getContract())
- .setUserSupplier(probeConfig::getUser)
- .setRemoteAddr(probeConfig.getCallerIp());
- return bucketId(config, bucketInfo);
- }
-
/**
* @see io.apiman.gateway.engine.policies.AbstractMappedPolicy#doApply(io.apiman.gateway.engine.beans.ApiResponse, io.apiman.gateway.engine.policy.IPolicyContext, java.lang.Object, io.apiman.gateway.engine.policy.IPolicyChain)
*/
@@ -160,98 +129,6 @@ public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
super.doApply(response, context, config, chain);
}
- /**
- * Creates the ID of the rate bucket to use. The ID is composed differently
- * depending on the configuration of the policy.
- */
- protected static String bucketId(RateLimitingConfig config, BucketIdBuilderContext context) {
- Api api = context.getApi();
- StringBuilder builder = new StringBuilder();
- // Public API in this branch
- if (context.getContract() == null) {
- builder.append("PUBLIC||"); //$NON-NLS-1$
- builder.append("||"); //$NON-NLS-1$
- builder.append(api.getOrganizationId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(api.getApiId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(api.getVersion());
- if (config.getGranularity() == RateLimitingGranularity.User) {
- String user = context.getUserSupplier().get();
- builder.append("||"); //$NON-NLS-1$
- builder.append(user);
- } else if (config.getGranularity() == RateLimitingGranularity.Ip) {
- builder.append("||"); //$NON-NLS-1$
- builder.append(context.getRemoteAddr());
- } else if (config.getGranularity() == RateLimitingGranularity.Api) {
- } else {
- return NO_CLIENT_AVAILABLE;
- }
- } else {
- // Have a fully valid contract in this branch.
- ApiContract contract = context.getContract();
- Client client = contract.getClient();
- String apiKey = client.getApiKey();
- builder.append(apiKey);
- if (config.getGranularity() == RateLimitingGranularity.User) {
- String user = context.getUserSupplier().get();
- if (user == null) {
- return NO_USER_AVAILABLE;
- } else {
- builder.append("||USER||"); //$NON-NLS-1$
- builder.append(client.getOrganizationId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(client.getClientId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(user);
- }
- } else if (config.getGranularity() == RateLimitingGranularity.Client) {
- builder.append(apiKey);
- builder.append("||APP||"); //$NON-NLS-1$
- builder.append(client.getOrganizationId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(client.getClientId());
- } else if (config.getGranularity() == RateLimitingGranularity.Ip) {
- builder.append(apiKey);
- builder.append("||IP||"); //$NON-NLS-1$
- builder.append(client.getOrganizationId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(context.getRemoteAddr());
- } else {
- builder.append(apiKey);
- builder.append("||SERVICE||"); //$NON-NLS-1$
- builder.append(api.getOrganizationId());
- builder.append("||"); //$NON-NLS-1$
- builder.append(api.getApiId());
- }
- }
- return builder.toString();
- }
-
- /**
- * Gets the appropriate bucket period from the config.
- * @param config
- */
- protected static RateBucketPeriod getPeriod(RateLimitingConfig config) {
- RateLimitingPeriod period = config.getPeriod();
- switch (period) {
- case Second:
- return RateBucketPeriod.Second;
- case Day:
- return RateBucketPeriod.Day;
- case Hour:
- return RateBucketPeriod.Hour;
- case Minute:
- return RateBucketPeriod.Minute;
- case Month:
- return RateBucketPeriod.Month;
- case Year:
- return RateBucketPeriod.Year;
- default:
- return RateBucketPeriod.Month;
- }
- }
-
/**
* Set response headers
*/
@@ -310,6 +187,13 @@ public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
return DEFAULT_LIMIT_HEADER;
}
+ protected String bucketId(ApiRequest request, RateLimitingConfig config) {
+ return bucketFactory.bucketId(request, config);
+ }
+
+ protected String bucketId(RateLimitingConfig config, BucketIdBuilderContext context) {
+ return bucketFactory.bucketId(config, context);
+ }
@Override
public Class<RateLimitingProbeConfig> getProbeRequestClass() {
@@ -319,10 +203,10 @@ public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
@Override
public void probe(RateLimitingProbeConfig probeRequest, RateLimitingConfig policyConfig, ProbeContext probeContext,
IPolicyContext context, IAsyncResultHandler<IPolicyProbeResponse> resultHandler) {
- String bucketId = bucketId(probeRequest, probeContext, policyConfig);
+ String bucketId = bucketFactory.bucketId(probeRequest, probeContext, policyConfig);
IRateLimiterComponent rateLimiter = context.getComponent(IRateLimiterComponent.class);
// Ask for rate limit, but don't actually decrement the counter.
- rateLimiter.accept(bucketId, getPeriod(policyConfig), policyConfig.getLimit(), 0, rateLimResult -> {
+ rateLimiter.accept(bucketId, bucketFactory.getPeriod(policyConfig), policyConfig.getLimit(), 0, rateLimResult -> {
RateLimitResponse remaining = rateLimResult.getResult();
var probeResult = new RateLimitingProbeResponse()
.setStatus(remaining)
@@ -330,63 +214,4 @@ public class RateLimitingPolicy extends AbstractMappedPolicy<RateLimitingConfig>
resultHandler.handle(AsyncResultImpl.create(probeResult));
});
}
-
- // TODO: make me a record
- private static final class BucketIdBuilderContext {
- private RateLimitingConfig rateLimitingConfig;
- private Api api;
- private ApiContract contract; // Remember, with a public API we might not have a contract!
- private Supplier<String> userSupplier;
- private String remoteAddr;
-
- public BucketIdBuilderContext() {
- }
-
- public RateLimitingConfig getRateLimitingConfig() {
- return rateLimitingConfig;
- }
-
- public BucketIdBuilderContext setRateLimitingConfig(
- RateLimitingConfig rateLimitingConfig) {
- this.rateLimitingConfig = rateLimitingConfig;
- return this;
- }
-
- public ApiContract getContract() {
- return contract;
- }
-
- public BucketIdBuilderContext setContract(ApiContract contract) {
- this.contract = contract;
- return this;
- }
-
- public Supplier<String> getUserSupplier() {
- return userSupplier;
- }
-
- public BucketIdBuilderContext setUserSupplier(Supplier<String> userSupplier) {
- this.userSupplier = userSupplier;
- return this;
- }
-
- public String getRemoteAddr() {
- return remoteAddr;
- }
-
- public BucketIdBuilderContext setRemoteAddr(String remoteAddr) {
- this.remoteAddr = remoteAddr;
- return this;
- }
-
- public Api getApi() {
- return api;
- }
-
- public BucketIdBuilderContext setApi(Api api) {
- this.api = api;
- return this;
- }
- }
-
}

MODIFY
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/TransferQuotaPolicy.java
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/TransferQuotaPolicy.java
*/
package io.apiman.gateway.engine.policies;
+import io.apiman.gateway.engine.async.AsyncResultImpl;
import io.apiman.gateway.engine.async.IAsyncResult;
import io.apiman.gateway.engine.async.IAsyncResultHandler;
-import io.apiman.gateway.engine.beans.PolicyFailure;
-import io.apiman.gateway.engine.beans.PolicyFailureType;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.ApiResponse;
+import io.apiman.gateway.engine.beans.IPolicyProbeResponse;
+import io.apiman.gateway.engine.beans.PolicyFailure;
+import io.apiman.gateway.engine.beans.PolicyFailureType;
import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent;
import io.apiman.gateway.engine.components.IRateLimiterComponent;
import io.apiman.gateway.engine.components.rate.RateLimitResponse;
@@ -30,8 +32,13 @@ import io.apiman.gateway.engine.io.IReadWriteStream;
import io.apiman.gateway.engine.policies.config.TransferDirectionType;
import io.apiman.gateway.engine.policies.config.TransferQuotaConfig;
import io.apiman.gateway.engine.policies.i18n.Messages;
+import io.apiman.gateway.engine.policies.limiting.BucketFactory;
+import io.apiman.gateway.engine.policies.probe.RateLimitingProbeConfig;
+import io.apiman.gateway.engine.policies.probe.RateLimitingProbeResponse;
import io.apiman.gateway.engine.policy.IPolicyChain;
import io.apiman.gateway.engine.policy.IPolicyContext;
+import io.apiman.gateway.engine.policy.IPolicyProbe;
+import io.apiman.gateway.engine.policy.ProbeContext;
import io.apiman.gateway.engine.rates.RateBucketPeriod;
import java.util.Map;
@@ -41,7 +48,8 @@ import java.util.Map;
*
* @author eric.wittmann@redhat.com
*/
-public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaConfig> {
+public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaConfig>
+ implements IPolicyProbe<TransferQuotaConfig, RateLimitingProbeConfig> {
private static final String BUCKET_ID_ATTR = TransferQuotaPolicy.class.getName() + ".bucketId"; //$NON-NLS-1$
private static final String PERIOD_ATTR = TransferQuotaPolicy.class.getName() + ".period"; //$NON-NLS-1$
@@ -52,17 +60,47 @@ public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaC
private static final String DEFAULT_REMAINING_HEADER = "X-TransferQuota-Remaining"; //$NON-NLS-1$
private static final String DEFAULT_RESET_HEADER = "X-TransferQuota-Reset"; //$NON-NLS-1$
+ private final BucketFactory bucketFactory = new BucketFactory();
+
/**
* Constructor.
*/
public TransferQuotaPolicy() {
}
+ @Override
+ public Class<RateLimitingProbeConfig> getProbeRequestClass() {
+ return RateLimitingProbeConfig.class;
+ }
+
@Override
public Class<TransferQuotaConfig> getConfigurationClass() {
return TransferQuotaConfig.class;
}
+ protected String bucketId(ApiRequest request, TransferQuotaConfig config) {
+ return "XFERQUOTA||" + bucketFactory.bucketId(request, config);
+ }
+
+ protected String bucketId(RateLimitingProbeConfig request, ProbeContext probeContext, TransferQuotaConfig config) {
+ return "XFERQUOTA||" + bucketFactory.bucketId(request, probeContext, config);
+ }
+
+ @Override
+ public void probe(RateLimitingProbeConfig probeRequest, TransferQuotaConfig policyConfig, ProbeContext probeContext, IPolicyContext context,
+ IAsyncResultHandler<IPolicyProbeResponse> resultHandler) {
+ String bucketId = bucketId(probeRequest, probeContext, policyConfig);
+ IRateLimiterComponent rateLimiter = context.getComponent(IRateLimiterComponent.class);
+ // Ask for rate limit, but don't actually decrement the counter.
+ rateLimiter.accept(bucketId, bucketFactory.getPeriod(policyConfig), policyConfig.getLimit(), 0, rateLimResult -> {
+ RateLimitResponse remaining = rateLimResult.getResult();
+ var probeResult = new RateLimitingProbeResponse()
+ .setStatus(remaining)
+ .setConfig(policyConfig);
+ resultHandler.handle(AsyncResultImpl.create(probeResult));
+ });
+ }
+
@Override
protected void doApply(final ApiRequest request, final IPolicyContext context, final TransferQuotaConfig config,
final IPolicyChain<ApiRequest> chain) {
@@ -70,16 +108,16 @@ public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaC
// Step 1: check to see if we're already in violation of this
// policy. If so, fail fast.
// *************************************************************
- String bucketId = "XFERQUOTA||" + RateLimitingPolicy.bucketId(request, config); //$NON-NLS-1$
- final RateBucketPeriod period = RateLimitingPolicy.getPeriod(config);
+ String bucketId = bucketId(request, config); //$NON-NLS-1$
+ final RateBucketPeriod period = bucketFactory.getPeriod(config);
- if (bucketId == RateLimitingPolicy.NO_USER_AVAILABLE) {
+ if (bucketId.equals(BucketFactory.NO_USER_AVAILABLE)) {
IPolicyFailureFactoryComponent failureFactory = context.getComponent(IPolicyFailureFactoryComponent.class);
PolicyFailure failure = failureFactory.createFailure(PolicyFailureType.Other, PolicyFailureCodes.NO_USER_FOR_RATE_LIMITING, Messages.i18n.format("TransferQuotaPolicy.NoUser")); //$NON-NLS-1$
chain.doFailure(failure);
return;
}
- if (bucketId == RateLimitingPolicy.NO_CLIENT_AVAILABLE) {
+ if (bucketId.equals(BucketFactory.NO_CLIENT_AVAILABLE)) {
IPolicyFailureFactoryComponent failureFactory = context.getComponent(IPolicyFailureFactoryComponent.class);
PolicyFailure failure = failureFactory.createFailure(PolicyFailureType.Other, PolicyFailureCodes.NO_APP_FOR_RATE_LIMITING, Messages.i18n.format("TransferQuotaPolicy.NoApp")); //$NON-NLS-1$
chain.doFailure(failure);
@@ -223,9 +261,6 @@ public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaC
/**
* Called when everything is done (the last byte is written). This is used to
* record the # of bytes downloaded.
- * @param context
- * @param config
- * @param downloadedBytes
*/
protected void doFinalApply(IPolicyContext context, TransferQuotaConfig config, long downloadedBytes) {
if (config.getDirection() == TransferDirectionType.download || config.getDirection() == TransferDirectionType.both) {
@@ -245,10 +280,6 @@ public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaC
/**
* Called to send a 'quota exceeded' failure.
- * @param context
- * @param config
- * @param chain
- * @param rtr
*/
protected void doQuotaExceededFailure(final IPolicyContext context, final TransferQuotaConfig config,
final IPolicyChain<?> chain, RateLimitResponse rtr) {
@@ -261,10 +292,6 @@ public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaC
chain.doFailure(failure);
}
- /**
- * @param responseHeaders
- * @param failureFactory
- */
protected PolicyFailure limitExceededFailure(IPolicyFailureFactoryComponent failureFactory) {
PolicyFailure failure = failureFactory.createFailure(PolicyFailureType.Other,
PolicyFailureCodes.BYTE_QUOTA_EXCEEDED,
@@ -293,5 +320,4 @@ public class TransferQuotaPolicy extends AbstractMappedDataPolicy<TransferQuotaC
protected String defaultLimitHeader() {
return DEFAULT_LIMIT_HEADER;
}
-
}

ADD
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/limiting/BucketFactory.java
+package io.apiman.gateway.engine.policies.limiting;
+
+import io.apiman.gateway.engine.beans.Api;
+import io.apiman.gateway.engine.beans.ApiContract;
+import io.apiman.gateway.engine.beans.ApiRequest;
+import io.apiman.gateway.engine.beans.Client;
+import io.apiman.gateway.engine.policies.config.RateLimitingConfig;
+import io.apiman.gateway.engine.policies.config.rates.RateLimitingGranularity;
+import io.apiman.gateway.engine.policies.config.rates.RateLimitingPeriod;
+import io.apiman.gateway.engine.policies.probe.RateLimitingProbeConfig;
+import io.apiman.gateway.engine.policy.ProbeContext;
+import io.apiman.gateway.engine.rates.RateBucketPeriod;
+
+import java.util.function.Supplier;
+
+/**
+ *
+ *
+ * @author Marc Savy {@literal <marc@blackparrotlabs.io>}
+ */
+public class BucketFactory {
+
+ public static final String NO_USER_AVAILABLE = "NO_USER_AVAILABLE";
+ public static final String NO_CLIENT_AVAILABLE = "NO_CLIENT_AVAILABLE";
+
+ /**
+ * Creates the ID of the rate bucket to use. The ID is composed differently
+ * depending on the configuration of the policy.
+ */
+ public String bucketId(RateLimitingConfig config, BucketIdBuilderContext context) {
+ Api api = context.getApi();
+ StringBuilder builder = new StringBuilder();
+ // Public API in this branch
+ if (context.getContract() == null) {
+ builder.append("PUBLIC||"); //$NON-NLS-1$
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(api.getOrganizationId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(api.getApiId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(api.getVersion());
+ if (config.getGranularity() == RateLimitingGranularity.User) {
+ String user = context.getUserSupplier().get();
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(user);
+ } else if (config.getGranularity() == RateLimitingGranularity.Ip) {
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(context.getRemoteAddr());
+ } else if (config.getGranularity() == RateLimitingGranularity.Api) {
+ } else {
+ return NO_CLIENT_AVAILABLE;
+ }
+ } else {
+ // Have a fully valid contract in this branch.
+ ApiContract contract = context.getContract();
+ Client client = contract.getClient();
+ String apiKey = client.getApiKey();
+ builder.append(apiKey);
+ if (config.getGranularity() == RateLimitingGranularity.User) {
+ String user = context.getUserSupplier().get();
+ if (user == null) {
+ return NO_USER_AVAILABLE;
+ } else {
+ builder.append("||USER||"); //$NON-NLS-1$
+ builder.append(client.getOrganizationId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(client.getClientId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(user);
+ }
+ } else if (config.getGranularity() == RateLimitingGranularity.Client) {
+ builder.append(apiKey);
+ builder.append("||APP||"); //$NON-NLS-1$
+ builder.append(client.getOrganizationId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(client.getClientId());
+ } else if (config.getGranularity() == RateLimitingGranularity.Ip) {
+ builder.append(apiKey);
+ builder.append("||IP||"); //$NON-NLS-1$
+ builder.append(client.getOrganizationId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(context.getRemoteAddr());
+ } else {
+ builder.append(apiKey);
+ builder.append("||SERVICE||"); //$NON-NLS-1$
+ builder.append(api.getOrganizationId());
+ builder.append("||"); //$NON-NLS-1$
+ builder.append(api.getApiId());
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Gets the appropriate bucket period from the config.
+ */
+ public RateBucketPeriod getPeriod(RateLimitingConfig config) {
+ RateLimitingPeriod period = config.getPeriod();
+ switch (period) {
+ case Second:
+ return RateBucketPeriod.Second;
+ case Day:
+ return RateBucketPeriod.Day;
+ case Hour:
+ return RateBucketPeriod.Hour;
+ case Minute:
+ return RateBucketPeriod.Minute;
+ case Month:
+ return RateBucketPeriod.Month;
+ case Year:
+ return RateBucketPeriod.Year;
+ default:
+ return RateBucketPeriod.Month;
+ }
+ }
+
+ /**
+ * Generate rate limiting bucket ID for API Request
+ */
+ public String bucketId(ApiRequest request, RateLimitingConfig config) {
+ BucketIdBuilderContext bucketInfo = new BucketIdBuilderContext()
+ .setRateLimitingConfig(config)
+ .setApi(request.getApi())
+ .setContract(request.getContract())
+ .setUserSupplier(() -> {
+ String header = config.getUserHeader();
+ if (!request.getHeaders().containsKey(header)) {
+ return NO_USER_AVAILABLE;
+ }
+ return request.getHeaders().get(header);
+ })
+ .setRemoteAddr(request.getRemoteAddr());
+ return bucketId(config, bucketInfo);
+ }
+
+ /**
+ * Generate rate limiting bucket ID for a probe request
+ */
+ public String bucketId(RateLimitingProbeConfig probeConfig, ProbeContext probeContext, RateLimitingConfig config) {
+ BucketIdBuilderContext bucketInfo = new BucketIdBuilderContext()
+ .setRateLimitingConfig(config)
+ .setApi(probeContext.getApi())
+ .setContract(probeContext.getContract())
+ .setUserSupplier(probeConfig::getUser)
+ .setRemoteAddr(probeConfig.getCallerIp());
+ return bucketId(config, bucketInfo);
+ }
+
+ // TODO: make me a record
+ public static final class BucketIdBuilderContext {
+ private RateLimitingConfig rateLimitingConfig;
+ private Api api;
+ private ApiContract contract; // Remember, with a public API we might not have a contract!
+ private Supplier<String> userSupplier;
+ private String remoteAddr;
+
+ public BucketIdBuilderContext() {
+ }
+
+ public RateLimitingConfig getRateLimitingConfig() {
+ return rateLimitingConfig;
+ }
+
+ public BucketIdBuilderContext setRateLimitingConfig(
+ RateLimitingConfig rateLimitingConfig) {
+ this.rateLimitingConfig = rateLimitingConfig;
+ return this;
+ }
+
+ public ApiContract getContract() {
+ return contract;
+ }
+
+ public BucketIdBuilderContext setContract(ApiContract contract) {
+ this.contract = contract;
+ return this;
+ }
+
+ public Supplier<String> getUserSupplier() {
+ return userSupplier;
+ }
+
+ public BucketIdBuilderContext setUserSupplier(Supplier<String> userSupplier) {
+ this.userSupplier = userSupplier;
+ return this;
+ }
+
+ public String getRemoteAddr() {
+ return remoteAddr;
+ }
+
+ public BucketIdBuilderContext setRemoteAddr(String remoteAddr) {
+ this.remoteAddr = remoteAddr;
+ return this;
+ }
+
+ public Api getApi() {
+ return api;
+ }
+
+ public BucketIdBuilderContext setApi(Api api) {
+ this.api = api;
+ return this;
+ }
+ }
+}

fix(gateway-policies): tweak rate limiting to ensure probes work