Skip to content

Commit 63438cc

Browse files
committed
Feature: handle rate limiting of UAA server.
1 parent 2e8e80a commit 63438cc

File tree

21 files changed

+1505
-196
lines changed

21 files changed

+1505
-196
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ Beyond that, it is helpful to capture the following information:
253253
If you open a Github issue with a request for help, please include as much of the information above as possible and do not forget to sanitize any request/response data posted.
254254

255255
## Development
256-
The project depends on Java 8. To build from source and install to your local Maven cache, run the following:
256+
The project depends on Java 8 to 21. To build from source and install to your local Maven cache, run the following:
257257

258258
```shell
259259
$ git submodule update --init --recursive
@@ -297,6 +297,7 @@ Name | Description
297297
`TEST_PROXY_PORT` | _(Optional)_ The port of a proxy to route all requests through. Defaults to `8080`.
298298
`TEST_PROXY_USERNAME` | _(Optional)_ The username for a proxy to route all requests through
299299
`TEST_SKIPSSLVALIDATION` | _(Optional)_ Whether to skip SSL validation when connecting to the Cloud Foundry instance. Defaults to `false`.
300+
`UAA_API_REQUEST_LIMIT` | _(Optional)_ If your UAA server does rate limiting and returns 429 errors, set this variable to a value smaller than the limit. Defaults to `0` (no limit)
300301

301302
If you do not have access to a CloudFoundry instance with admin access, you can run one locally using [bosh-deployment](https://github.com/cloudfoundry/bosh-deployment) & [cf-deployment](https://github.com/cloudfoundry/cf-deployment/) and Virtualbox.
302303

cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/AbstractUaaOperations.java

Lines changed: 342 additions & 138 deletions
Large diffs are not rendered by default.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.cloudfoundry.reactor.uaa;
18+
19+
import java.util.Map;
20+
import org.cloudfoundry.reactor.ConnectionContext;
21+
import org.cloudfoundry.reactor.TokenProvider;
22+
import org.cloudfoundry.uaa.ratelimit.Ratelimit;
23+
import org.cloudfoundry.uaa.ratelimit.RatelimitRequest;
24+
import org.cloudfoundry.uaa.ratelimit.RatelimitResponse;
25+
import reactor.core.publisher.Mono;
26+
27+
public final class ReactorRatelimit extends AbstractUaaOperations implements Ratelimit {
28+
29+
/**
30+
* Creates an instance
31+
*
32+
* @param connectionContext the {@link ConnectionContext} to use when communicating with the server
33+
* @param root the root URI of the server. Typically something like {@code https://uaa.run.pivotal.io}.
34+
* @param tokenProvider the {@link TokenProvider} to use when communicating with the server
35+
* @param requestTags map with custom http headers which will be added to web request
36+
*/
37+
public ReactorRatelimit(
38+
ConnectionContext connectionContext,
39+
Mono<String> root,
40+
TokenProvider tokenProvider,
41+
Map<String, String> requestTags) {
42+
super(connectionContext, root, tokenProvider, requestTags);
43+
}
44+
45+
@Override
46+
public Mono<RatelimitResponse> getRatelimit(RatelimitRequest request) {
47+
return get(
48+
request,
49+
RatelimitResponse.class,
50+
builder -> builder.pathSegment("RateLimitingStatus"))
51+
.checkpoint();
52+
}
53+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.cloudfoundry.reactor.uaa;
18+
19+
import io.netty.handler.codec.http.HttpHeaders;
20+
import java.util.Objects;
21+
import java.util.function.Consumer;
22+
import java.util.function.Function;
23+
import org.cloudfoundry.reactor.uaa.UaaThrottler.Token;
24+
import org.cloudfoundry.reactor.util.ErrorPayloadMapper;
25+
import org.cloudfoundry.reactor.util.Operator;
26+
import org.cloudfoundry.reactor.util.OperatorContext;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import reactor.core.publisher.Mono;
30+
import reactor.netty.http.client.HttpClient;
31+
32+
public class UaaOperator extends Operator {
33+
34+
private Token token = null;
35+
private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test");
36+
37+
public UaaOperator(OperatorContext context, HttpClient httpClient, Token value, String caller) {
38+
super(context, httpClient);
39+
token = Objects.requireNonNull(value, "value must not be null");
40+
if (token != UaaThrottler.NON_UAA_TOKEN) {
41+
LOGGER.trace("UaaOperator creating instance for " + value.id() + " caller " + caller);
42+
}
43+
}
44+
45+
@Override
46+
public UaaOperator followRedirects() {
47+
return new UaaOperator(
48+
this.context, super.getHttpClient().followRedirect(true), this.token, "follow");
49+
}
50+
51+
@Override
52+
public UaaOperator headers(Consumer<HttpHeaders> headersTransformer) {
53+
return new UaaOperator(
54+
this.context,
55+
super.getHttpClient().headers(headersTransformer),
56+
this.token,
57+
"headers");
58+
}
59+
60+
@Override
61+
public UaaOperator headersWhen(
62+
Function<HttpHeaders, Mono<? extends HttpHeaders>> headersWhenTransformer) {
63+
return new UaaOperator(
64+
this.context,
65+
super.getHttpClient().headersWhen(headersWhenTransformer),
66+
this.token,
67+
"headersWhen");
68+
}
69+
70+
@Override
71+
public UaaOperator withErrorPayloadMapper(ErrorPayloadMapper errorPayloadMapper) {
72+
return new UaaOperator(
73+
this.context.withErrorPayloadMapper(errorPayloadMapper),
74+
super.getHttpClient(),
75+
this.token,
76+
"errorPayload");
77+
}
78+
79+
@Override
80+
protected HttpClient attachRequestLogger(HttpClient httpClient) {
81+
return super.attachRequestLogger(httpClient)
82+
.doAfterRequest((response, connection) -> token.activate());
83+
}
84+
}

0 commit comments

Comments
 (0)