Skip to content

Commit edbf95c

Browse files
authored
Add RequestContent (Azure#21320)
Added RequestContent and Updated HttpRequest to Use It
1 parent 155e942 commit edbf95c

File tree

20 files changed

+1216
-123
lines changed

20 files changed

+1216
-123
lines changed

eng/code-quality-reports/src/main/java/com/azure/tools/checkstyle/checks/GoodLoggingCheck.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public class GoodLoggingCheck extends AbstractCheck {
3232
private static final String CLIENT_LOGGER = "ClientLogger";
3333
private static final String LOGGER = "logger";
3434
private static final String STATIC_LOGGER_ERROR = "Use a static ClientLogger instance in a static method.";
35+
private static final int[] REQUIRED_TOKENS = new int[]{
36+
TokenTypes.IMPORT,
37+
TokenTypes.INTERFACE_DEF,
38+
TokenTypes.CLASS_DEF,
39+
TokenTypes.LITERAL_NEW,
40+
TokenTypes.VARIABLE_DEF,
41+
TokenTypes.METHOD_CALL,
42+
TokenTypes.METHOD_DEF
43+
};
3544

3645
private static final String LOGGER_NAME_ERROR =
3746
"ClientLogger instance naming: use ''%s'' instead of ''%s'' for consistency.";
@@ -42,7 +51,7 @@ public class GoodLoggingCheck extends AbstractCheck {
4251
// Boolean indicator that indicates if the java class imports ClientLogger
4352
private boolean hasClientLoggerImported;
4453
// A LIFO queue stores the class names, pop top element if exist the class name AST node
45-
private Queue<String> classNameDeque = Collections.asLifoQueue(new ArrayDeque<>());
54+
private final Queue<String> classNameDeque = Collections.asLifoQueue(new ArrayDeque<>());
4655
// Collection of Invalid logging packages
4756
private static final Set<String> INVALID_LOGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
4857
"org.slf4j", "org.apache.logging.log4j", "java.util.logging"
@@ -60,14 +69,7 @@ public int[] getAcceptableTokens() {
6069

6170
@Override
6271
public int[] getRequiredTokens() {
63-
return new int[] {
64-
TokenTypes.IMPORT,
65-
TokenTypes.CLASS_DEF,
66-
TokenTypes.LITERAL_NEW,
67-
TokenTypes.VARIABLE_DEF,
68-
TokenTypes.METHOD_CALL,
69-
TokenTypes.METHOD_DEF
70-
};
72+
return REQUIRED_TOKENS;
7173
}
7274

7375
@Override
@@ -96,6 +98,7 @@ public void visitToken(DetailAST ast) {
9698
});
9799
break;
98100
case TokenTypes.CLASS_DEF:
101+
case TokenTypes.INTERFACE_DEF:
99102
classNameDeque.offer(ast.findFirstToken(TokenTypes.IDENT).getText());
100103
break;
101104
case TokenTypes.LITERAL_NEW:
@@ -194,21 +197,20 @@ private void checkForInvalidStaticLoggerUsage(DetailAST methodDefToken) {
194197
// if not a static method
195198
if (!(TokenUtil.findFirstTokenByPredicate(methodDefToken,
196199
node -> node.branchContains(TokenTypes.LITERAL_STATIC)).isPresent())) {
197-
200+
198201
// error if static `LOGGER` present, LOGGER.*
199202
if (methodDefToken.findFirstToken(TokenTypes.SLIST) != null) {
200-
TokenUtil
201-
.forEachChild(methodDefToken.findFirstToken(TokenTypes.SLIST), TokenTypes.EXPR, (exprToken) -> {
202-
if (exprToken != null) {
203-
DetailAST methodCallToken = exprToken.findFirstToken(TokenTypes.METHOD_CALL);
204-
if (methodCallToken != null && methodCallToken.findFirstToken(TokenTypes.DOT) != null) {
205-
if (methodCallToken.findFirstToken(TokenTypes.DOT)
206-
.findFirstToken(TokenTypes.IDENT).getText().equals(LOGGER.toUpperCase())) {
207-
log(methodDefToken, STATIC_LOGGER_ERROR);
208-
}
203+
TokenUtil.forEachChild(methodDefToken.findFirstToken(TokenTypes.SLIST), TokenTypes.EXPR, exprToken -> {
204+
if (exprToken != null) {
205+
DetailAST methodCallToken = exprToken.findFirstToken(TokenTypes.METHOD_CALL);
206+
if (methodCallToken != null && methodCallToken.findFirstToken(TokenTypes.DOT) != null) {
207+
if (methodCallToken.findFirstToken(TokenTypes.DOT)
208+
.findFirstToken(TokenTypes.IDENT).getText().equals(LOGGER.toUpperCase())) {
209+
log(methodDefToken, STATIC_LOGGER_ERROR);
209210
}
210211
}
211-
});
212+
}
213+
});
212214
}
213215
}
214216
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
/**
5+
* Package containing implementation details.
6+
*/
7+
package com.azure.core.http.netty.implementation;

sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncHttpClient.java

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.io.IOException;
2828
import java.nio.ByteBuffer;
2929
import java.util.Objects;
30-
import java.util.function.Function;
3130

3231
/**
3332
* HttpClient implementation for OkHttp.
@@ -64,9 +63,13 @@ public Mono<HttpResponse> send(HttpRequest request, Context context) {
6463
// but block on the thread backing flux. This ignore any subscribeOn applied to send(r)
6564
//
6665
toOkHttpRequest(request).subscribe(okHttpRequest -> {
67-
Call call = httpClient.newCall(okHttpRequest);
68-
call.enqueue(new OkHttpCallback(sink, request, eagerlyReadResponse));
69-
sink.onCancel(call::cancel);
66+
try {
67+
Call call = httpClient.newCall(okHttpRequest);
68+
call.enqueue(new OkHttpCallback(sink, request, eagerlyReadResponse));
69+
sink.onCancel(call::cancel);
70+
} catch (Exception ex) {
71+
sink.error(ex);
72+
}
7073
}, sink::error);
7174
}));
7275
}
@@ -78,29 +81,26 @@ public Mono<HttpResponse> send(HttpRequest request, Context context) {
7881
* @return the Mono emitting okhttp request
7982
*/
8083
private static Mono<okhttp3.Request> toOkHttpRequest(HttpRequest request) {
81-
return Mono.just(new okhttp3.Request.Builder())
82-
.map(rb -> {
83-
rb.url(request.getUrl());
84-
if (request.getHeaders() != null) {
85-
for (HttpHeader hdr : request.getHeaders()) {
86-
// OkHttp allows for headers with multiple values, but it treats them as separate headers,
87-
// therefore, we must call rb.addHeader for each value, using the same key for all of them
88-
hdr.getValuesList().forEach(value -> rb.addHeader(hdr.getName(), value));
89-
}
90-
}
91-
return rb;
92-
})
93-
.flatMap((Function<Request.Builder, Mono<Request.Builder>>) rb -> {
94-
if (request.getHttpMethod() == HttpMethod.GET) {
95-
return Mono.just(rb.get());
96-
} else if (request.getHttpMethod() == HttpMethod.HEAD) {
97-
return Mono.just(rb.head());
98-
} else {
99-
return toOkHttpRequestBody(request.getBody(), request.getHeaders())
100-
.map(requestBody -> rb.method(request.getHttpMethod().toString(), requestBody));
101-
}
102-
})
103-
.map(Request.Builder::build);
84+
Request.Builder requestBuilder = new Request.Builder()
85+
.url(request.getUrl());
86+
87+
if (request.getHeaders() != null) {
88+
for (HttpHeader hdr : request.getHeaders()) {
89+
// OkHttp allows for headers with multiple values, but it treats them as separate headers,
90+
// therefore, we must call rb.addHeader for each value, using the same key for all of them
91+
hdr.getValuesList().forEach(value -> requestBuilder.addHeader(hdr.getName(), value));
92+
}
93+
}
94+
95+
if (request.getHttpMethod() == HttpMethod.GET) {
96+
return Mono.just(requestBuilder.get().build());
97+
} else if (request.getHttpMethod() == HttpMethod.HEAD) {
98+
return Mono.just(requestBuilder.head().build());
99+
}
100+
101+
return toOkHttpRequestBody(request.getBody(), request.getHeaders())
102+
.map(okhttpRequestBody -> requestBuilder.method(request.getHttpMethod().toString(), okhttpRequestBody)
103+
.build());
104104
}
105105

106106
/**
@@ -117,11 +117,9 @@ private static Mono<RequestBody> toOkHttpRequestBody(Flux<ByteBuffer> bbFlux, Ht
117117

118118
return bsMono.map(bs -> {
119119
String contentType = headers.getValue("Content-Type");
120-
if (contentType == null) {
121-
return RequestBody.create(bs, null);
122-
} else {
123-
return RequestBody.create(bs, MediaType.parse(contentType));
124-
}
120+
MediaType mediaType = (contentType == null) ? null : MediaType.parse(contentType);
121+
122+
return RequestBody.create(bs, mediaType);
125123
});
126124
}
127125

@@ -146,9 +144,7 @@ private static Mono<ByteString> toByteString(Flux<ByteBuffer> bbFlux) {
146144
} catch (IOException ioe) {
147145
throw Exceptions.propagate(ioe);
148146
}
149-
})
150-
.map(b -> ByteString.of(b.readByteArray())),
151-
okio.Buffer::clear)
147+
}).map(b -> ByteString.of(b.readByteArray())), okio.Buffer::clear)
152148
.switchIfEmpty(EMPTY_BYTE_STRING_MONO);
153149
}
154150

@@ -163,11 +159,13 @@ private static class OkHttpCallback implements okhttp3.Callback {
163159
this.eagerlyReadResponse = eagerlyReadResponse;
164160
}
165161

162+
@SuppressWarnings("NullableProblems")
166163
@Override
167164
public void onFailure(okhttp3.Call call, IOException e) {
168165
sink.error(e);
169166
}
170167

168+
@SuppressWarnings("NullableProblems")
171169
@Override
172170
public void onResponse(okhttp3.Call call, okhttp3.Response response) {
173171
/*
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
/**
5+
* Package containing implementation details.
6+
*/
7+
package com.azure.core.http.okhttp.implementation;

sdk/core/azure-core-http-okhttp/src/test/java/com/azure/core/http/okhttp/OkHttpAsyncHttpClientTests.java

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.junit.jupiter.api.AfterAll;
1515
import org.junit.jupiter.api.Assertions;
1616
import org.junit.jupiter.api.BeforeAll;
17-
import org.junit.jupiter.api.Disabled;
1817
import org.junit.jupiter.api.Test;
1918
import reactor.core.publisher.Flux;
2019
import reactor.core.publisher.Mono;
@@ -40,6 +39,7 @@
4039
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
4140
import static com.github.tomakehurst.wiremock.client.WireMock.get;
4241
import static com.github.tomakehurst.wiremock.client.WireMock.post;
42+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4343
import static org.junit.jupiter.api.Assertions.assertEquals;
4444
import static org.junit.jupiter.api.Assertions.assertLinesMatch;
4545
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -66,6 +66,7 @@ public static void beforeClass() {
6666
server.stubFor(post("/shortPost").willReturn(aResponse().withBody(SHORT_BODY)));
6767
server.stubFor(get(RETURN_HEADERS_AS_IS_PATH).willReturn(aResponse()
6868
.withTransformers(OkHttpAsyncHttpClientResponseTransformer.NAME)));
69+
6970
server.start();
7071
}
7172

@@ -87,25 +88,33 @@ public void testFlowableResponseLongBodyAsByteArrayAsync() {
8788
}
8889

8990
@Test
90-
@Disabled("This tests behaviour of reactor netty's ByteBufFlux, not applicable for OkHttp")
9191
public void testMultipleSubscriptionsEmitsError() {
9292
HttpResponse response = getResponse("/short");
93+
9394
// Subscription:1
94-
response.getBodyAsByteArray().block();
95+
StepVerifier.create(response.getBodyAsByteArray())
96+
.assertNext(Assertions::assertNotNull)
97+
.expectComplete()
98+
.verify(Duration.ofSeconds(20));
99+
95100
// Subscription:2
101+
// Getting the bytes of an OkHttp response closes the stream on first read.
102+
// Subsequent reads will return an IllegalStateException due to the stream being closed.
96103
StepVerifier.create(response.getBodyAsByteArray())
97-
.expectNextCount(0) // TODO: Check with smaldini, what is the verifier operator equivalent to .awaitDone(20, TimeUnit.SECONDS)
98-
.verifyError(IllegalStateException.class);
104+
.expectNextCount(0)
105+
.expectError(IllegalStateException.class)
106+
.verify(Duration.ofSeconds(20));
99107

100108
}
101109

102110
@Test
103111
public void testFlowableWhenServerReturnsBodyAndNoErrorsWhenHttp500Returned() {
104112
HttpResponse response = getResponse("/error");
105-
StepVerifier.create(response.getBodyAsString())
106-
.expectNext("error") // TODO: .awaitDone(20, TimeUnit.SECONDS) [See previous todo]
107-
.verifyComplete();
108113
assertEquals(500, response.getStatusCode());
114+
StepVerifier.create(response.getBodyAsString())
115+
.expectNext("error")
116+
.expectComplete()
117+
.verify(Duration.ofSeconds(20));
109118
}
110119

111120
@Test
@@ -128,7 +137,7 @@ public void testFlowableBackpressure() {
128137

129138
@Test
130139
public void testRequestBodyIsErrorShouldPropagateToResponse() {
131-
HttpClient client = HttpClient.createDefault();
140+
HttpClient client = new OkHttpAsyncClientProvider().createInstance();
132141
HttpRequest request = new HttpRequest(HttpMethod.POST, url(server, "/shortPost"))
133142
.setHeader("Content-Length", "123")
134143
.setBody(Flux.error(new RuntimeException("boo")));
@@ -140,7 +149,7 @@ public void testRequestBodyIsErrorShouldPropagateToResponse() {
140149

141150
@Test
142151
public void testRequestBodyEndsInErrorShouldPropagateToResponse() {
143-
HttpClient client = HttpClient.createDefault();
152+
HttpClient client = new OkHttpAsyncClientProvider().createInstance();
144153
String contentChunk = "abcdefgh";
145154
int repetitions = 1000;
146155
HttpRequest request = new HttpRequest(HttpMethod.POST, url(server, "/shortPost"))
@@ -149,10 +158,14 @@ public void testRequestBodyEndsInErrorShouldPropagateToResponse() {
149158
.repeat(repetitions)
150159
.map(s -> ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)))
151160
.concatWith(Flux.error(new RuntimeException("boo"))));
152-
StepVerifier.create(client.send(request))
153-
// .awaitDone(10, TimeUnit.SECONDS)
154-
.expectErrorMessage("boo")
155-
.verify();
161+
162+
try {
163+
StepVerifier.create(client.send(request))
164+
.expectErrorMessage("boo")
165+
.verify(Duration.ofSeconds(10));
166+
} catch (Exception ex) {
167+
assertEquals("boo", ex.getMessage());
168+
}
156169
}
157170

158171
@Test
@@ -200,42 +213,30 @@ public void testServerShutsDownSocketShouldPushErrorToContentFlowable() {
200213
});
201214
}
202215

203-
@Disabled("This flakey test fails often on MacOS. https://github.com/Azure/azure-sdk-for-java/issues/4357.")
204216
@Test
205217
public void testConcurrentRequests() throws NoSuchAlgorithmException {
206218
int numRequests = 100; // 100 = 1GB of data read
207-
HttpClient client = HttpClient.createDefault();
219+
HttpClient client = new OkHttpAsyncClientProvider().createInstance();
208220
byte[] expectedDigest = digest(LONG_BODY);
221+
long expectedByteCount = (long) numRequests * LONG_BODY.getBytes(StandardCharsets.UTF_8).length;
209222

210223
Mono<Long> numBytesMono = Flux.range(1, numRequests)
211224
.parallel(10)
212225
.runOn(Schedulers.boundedElastic())
213226
.flatMap(n -> Mono.fromCallable(() -> getResponse(client, "/long")).flatMapMany(response -> {
214227
MessageDigest md = md5Digest();
215228
return response.getBody()
216-
.doOnNext(md::update)
217-
.map(bb -> new NumberedByteBuffer(n, bb))
218-
// .doOnComplete(() -> System.out.println("completed " + n))
219-
.doOnComplete(() -> Assertions.assertArrayEquals(expectedDigest,
220-
md.digest(), "wrong digest!"));
229+
.doOnNext(buffer -> md.update(buffer.duplicate()))
230+
.doOnComplete(() -> assertArrayEquals(expectedDigest, md.digest(), "wrong digest!"));
221231
}))
222232
.sequential()
223-
// enable the doOnNext call to see request numbers and thread names
224-
// .doOnNext(g -> System.out.println(g.n + " " +
225-
// Thread.currentThread().getName()))
226-
.map(nbb -> (long) nbb.bb.limit())
227-
.reduce(Long::sum)
228-
.subscribeOn(Schedulers.boundedElastic());
233+
.map(buffer -> (long) buffer.remaining())
234+
.reduce(Long::sum);
229235

230236
StepVerifier.create(numBytesMono)
231-
// .awaitDone(timeoutSeconds, TimeUnit.SECONDS)
232-
.expectNext((long) (numRequests * LONG_BODY.getBytes(StandardCharsets.UTF_8).length))
233-
.verifyComplete();
234-
//
235-
// long numBytes = numBytesMono.block();
236-
// t = System.currentTimeMillis() - t;
237-
// System.out.println("totalBytesRead=" + numBytes / 1024 / 1024 + "MB in " + t / 1000.0 + "s");
238-
// assertEquals(numRequests * LONG_BODY.getBytes(StandardCharsets.UTF_8).length, numBytes);
237+
.expectNext(expectedByteCount)
238+
.expectComplete()
239+
.verify(Duration.ofSeconds(60));
239240
}
240241

241242
@Test
@@ -284,16 +285,6 @@ private static byte[] digest(String s) throws NoSuchAlgorithmException {
284285
return md.digest();
285286
}
286287

287-
private static final class NumberedByteBuffer {
288-
final long n;
289-
final ByteBuffer bb;
290-
291-
NumberedByteBuffer(long n, ByteBuffer bb) {
292-
this.n = n;
293-
this.bb = bb;
294-
}
295-
}
296-
297288
private static HttpResponse getResponse(String path) {
298289
HttpClient client = new OkHttpAsyncHttpClientBuilder().build();
299290
return getResponse(client, path);

sdk/core/azure-core/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@
265265
--add-opens com.azure.core/com.azure.core.implementation.models.jsonflatten=com.fasterxml.jackson.databind
266266
--add-opens com.azure.core/com.azure.core.implementation.models.jsonflatten=ALL-UNNAMED
267267
--add-opens com.azure.core/com.azure.core.implementation.serializer=ALL-UNNAMED
268+
--add-opens com.azure.core/com.azure.core.implementation.util=ALL-UNNAMED
268269
--add-opens com.azure.core/com.azure.core.models=ALL-UNNAMED
269270
--add-opens com.azure.core/com.azure.core.util=ALL-UNNAMED
270271
--add-opens com.azure.core/com.azure.core.util.jsonpatch=ALL-UNNAMED

0 commit comments

Comments
 (0)