Skip to content

Commit b131f86

Browse files
authored
Miscellaneous Improvement to azure-core (Azure#27638)
Miscellaneous Improvement to azure-core
1 parent a2c852f commit b131f86

20 files changed

+605
-157
lines changed

sdk/core/azure-core/src/main/java/com/azure/core/annotation/ReturnValueWireType.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package com.azure.core.annotation;
55

66
import com.azure.core.http.rest.Page;
7-
import com.azure.core.implementation.UnixTime;
87
import com.azure.core.util.Base64Url;
98
import com.azure.core.util.DateTimeRfc1123;
109

@@ -22,7 +21,6 @@
2221
* <ol>
2322
* <li>{@link Base64Url}</li>
2423
* <li>{@link DateTimeRfc1123}</li>
25-
* <li>{@link UnixTime}</li>
2624
* <li>{@link Page}</li>
2725
* <li>{@link List List&lt;T&gt;} where {@code T} can be one of the four values above.</li>
2826
* </ol>

sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipeline.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public final class HttpPipeline {
2828
* HttpPipeline#send(HttpPipelineCallContext)} and it's response.
2929
*
3030
* @param httpClient the http client to write request to wire and receive response from wire.
31-
* @param pipelinePolicies pipeline policies in the order they need to applied, a copy of this array will be made
31+
* @param pipelinePolicies pipeline policies in the order they need to be applied, a copy of this array will be made
3232
* hence changing the original array after the creation of pipeline will not mutate the pipeline
3333
*/
3434
HttpPipeline(HttpClient httpClient, List<HttpPipelinePolicy> pipelinePolicies) {
@@ -41,7 +41,7 @@ public final class HttpPipeline {
4141
/**
4242
* Get the policy at the passed index in the pipeline.
4343
*
44-
* @param index index of the the policy to retrieve.
44+
* @param index index of the policy to retrieve.
4545
* @return the policy stored at that index.
4646
*/
4747
public HttpPipelinePolicy getPolicy(final int index) {

sdk/core/azure-core/src/main/java/com/azure/core/http/policy/AddDatePolicy.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,32 @@
66
import com.azure.core.http.HttpPipelineCallContext;
77
import com.azure.core.http.HttpPipelineNextPolicy;
88
import com.azure.core.http.HttpResponse;
9+
import com.azure.core.util.DateTimeRfc1123;
910
import reactor.core.publisher.Mono;
1011

1112
import java.time.OffsetDateTime;
12-
import java.time.ZoneId;
13+
import java.time.ZoneOffset;
1314
import java.time.format.DateTimeFormatter;
1415
import java.util.Locale;
1516

1617
/**
1718
* The pipeline policy that adds a "Date" header in RFC 1123 format when sending an HTTP request.
1819
*/
1920
public class AddDatePolicy implements HttpPipelinePolicy {
20-
private final DateTimeFormatter format = DateTimeFormatter
21+
private static final DateTimeFormatter FORMATTER = DateTimeFormatter
2122
.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'")
22-
.withZone(ZoneId.of("UTC"))
23+
.withZone(ZoneOffset.UTC)
2324
.withLocale(Locale.US);
2425

2526
@Override
2627
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
2728
return Mono.defer(() -> {
28-
context.getHttpRequest().getHeaders().set("Date", format.format(OffsetDateTime.now()));
29+
OffsetDateTime now = OffsetDateTime.now();
30+
try {
31+
context.getHttpRequest().getHeaders().set("Date", DateTimeRfc1123.toRfc1123String(now));
32+
} catch (IllegalArgumentException ignored) {
33+
context.getHttpRequest().getHeaders().set("Date", FORMATTER.format(now));
34+
}
2935
return next.process();
3036
});
3137
}

sdk/core/azure-core/src/main/java/com/azure/core/http/policy/AzureKeyCredentialPolicy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* an exception will be thrown to prevent leaking the key.
2020
*/
2121
public final class AzureKeyCredentialPolicy implements HttpPipelinePolicy {
22-
// AzureKeyCredentailPolicy can be a commonly used policy, use a static logger.
22+
// AzureKeyCredentialPolicy can be a commonly used policy, use a static logger.
2323
private static final ClientLogger LOGGER = new ClientLogger(AzureKeyCredentialPolicy.class);
2424

2525
private final String name;

sdk/core/azure-core/src/main/java/com/azure/core/http/policy/FixedDelayOptions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class FixedDelayOptions {
1717
private final Duration delay;
1818

1919
/**
20-
* Creates an instance of {@link maxRetries}.
20+
* Creates an instance of {@link FixedDelayOptions}.
2121
*
2222
* @param maxRetries The max number of retry attempts that can be made.
2323
* @param delay The fixed delay duration between retry attempts.

sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java

Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,25 @@
1010
import com.azure.core.http.HttpPipelineNextPolicy;
1111
import com.azure.core.http.HttpRequest;
1212
import com.azure.core.http.HttpResponse;
13+
import com.azure.core.implementation.AccessibleByteArrayOutputStream;
14+
import com.azure.core.implementation.ImplUtils;
1315
import com.azure.core.implementation.http.HttpPipelineCallContextHelper;
1416
import com.azure.core.implementation.jackson.ObjectMapperShim;
1517
import com.azure.core.util.Context;
1618
import com.azure.core.util.CoreUtils;
19+
import com.azure.core.util.FluxUtil;
1720
import com.azure.core.util.UrlBuilder;
1821
import com.azure.core.util.logging.ClientLogger;
1922
import com.azure.core.util.logging.LogLevel;
23+
import reactor.core.publisher.Flux;
2024
import reactor.core.publisher.Mono;
2125

22-
import java.io.ByteArrayOutputStream;
2326
import java.io.IOException;
24-
import java.io.UnsupportedEncodingException;
27+
import java.io.UncheckedIOException;
2528
import java.net.URL;
2629
import java.nio.ByteBuffer;
27-
import java.nio.channels.Channels;
28-
import java.nio.channels.WritableByteChannel;
30+
import java.nio.charset.Charset;
31+
import java.nio.charset.StandardCharsets;
2932
import java.time.Duration;
3033
import java.util.Collections;
3134
import java.util.Locale;
@@ -122,14 +125,14 @@ public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineN
122125
private HttpRequestLoggingContext getRequestLoggingOptions(HttpPipelineCallContext callContext) {
123126
return new HttpRequestLoggingContext(callContext.getHttpRequest(),
124127
HttpPipelineCallContextHelper.getContext(callContext),
125-
getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext), LOGGER));
128+
getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext)));
126129
}
127130

128131
private HttpResponseLoggingContext getResponseLoggingOptions(HttpResponse httpResponse, long startNs,
129132
HttpPipelineCallContext callContext) {
130133
return new HttpResponseLoggingContext(httpResponse, Duration.ofNanos(System.nanoTime() - startNs),
131134
HttpPipelineCallContextHelper.getContext(callContext),
132-
getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext), LOGGER));
135+
getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext)));
133136
}
134137

135138
private final class DefaultHttpRequestLogger implements HttpRequestLogger {
@@ -164,7 +167,8 @@ public Mono<Void> logRequest(ClientLogger logger, HttpRequestLoggingContext logg
164167
}
165168

166169
if (!httpLogDetailLevel.shouldLogBody()) {
167-
return logAndReturn(logger, logLevel, requestLogMessage, null);
170+
logMessage(logger, logLevel, requestLogMessage);
171+
return Mono.empty();
168172
}
169173

170174
if (request.getBody() == null) {
@@ -174,35 +178,40 @@ public Mono<Void> logRequest(ClientLogger logger, HttpRequestLoggingContext logg
174178
.append(request.getHttpMethod())
175179
.append(System.lineSeparator());
176180

177-
return logAndReturn(logger, logLevel, requestLogMessage, null);
181+
logMessage(logger, logLevel, requestLogMessage);
182+
return Mono.empty();
178183
}
179184

180185
String contentType = request.getHeaders().getValue("Content-Type");
181186
long contentLength = getContentLength(logger, request.getHeaders());
182187

183188
if (shouldBodyBeLogged(contentType, contentLength)) {
184-
ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) contentLength);
185-
WritableByteChannel bodyContentChannel = Channels.newChannel(outputStream);
189+
AccessibleByteArrayOutputStream stream = new AccessibleByteArrayOutputStream((int) contentLength);
186190

187191
// Add non-mutating operators to the data stream.
188192
request.setBody(
189193
request.getBody()
190-
.flatMap(byteBuffer -> writeBufferToBodyStream(bodyContentChannel, byteBuffer))
194+
.doOnNext(byteBuffer -> {
195+
try {
196+
ImplUtils.writeByteBufferToStream(byteBuffer.duplicate(), stream);
197+
} catch (IOException ex) {
198+
throw LOGGER.logExceptionAsError(new UncheckedIOException(ex));
199+
}
200+
})
191201
.doFinally(ignored -> {
192202
requestLogMessage.append(contentLength)
193203
.append("-byte body:")
194204
.append(System.lineSeparator())
195205
.append(prettyPrintIfNeeded(logger, prettyPrintBody, contentType,
196-
convertStreamToString(outputStream, logger)))
206+
new String(stream.toByteArray(), 0, stream.count(), StandardCharsets.UTF_8)))
197207
.append(System.lineSeparator())
198208
.append("--> END ")
199209
.append(request.getHttpMethod())
200210
.append(System.lineSeparator());
201211

202-
logAndReturn(logger, logLevel, requestLogMessage, null);
212+
logMessage(logger, logLevel, requestLogMessage);
203213
}));
204214

205-
return Mono.empty();
206215
} else {
207216
requestLogMessage.append(contentLength)
208217
.append("-byte body: (content not logged)")
@@ -211,8 +220,10 @@ public Mono<Void> logRequest(ClientLogger logger, HttpRequestLoggingContext logg
211220
.append(request.getHttpMethod())
212221
.append(System.lineSeparator());
213222

214-
return logAndReturn(logger, logLevel, requestLogMessage, null);
223+
logMessage(logger, logLevel, requestLogMessage);
215224
}
225+
226+
return Mono.empty();
216227
}
217228
}
218229

@@ -251,40 +262,28 @@ public Mono<HttpResponse> logResponse(ClientLogger logger, HttpResponseLoggingCo
251262

252263
if (!httpLogDetailLevel.shouldLogBody()) {
253264
responseLogMessage.append("<-- END HTTP");
254-
return logAndReturn(logger, logLevel, responseLogMessage, response);
265+
logMessage(logger, logLevel, responseLogMessage);
266+
return Mono.justOrEmpty(response);
255267
}
256268

257269
String contentTypeHeader = response.getHeaderValue("Content-Type");
258270
long contentLength = getContentLength(logger, response.getHeaders());
259271

260272
if (shouldBodyBeLogged(contentTypeHeader, contentLength)) {
261-
HttpResponse bufferedResponse = response.buffer();
262-
ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) contentLength);
263-
WritableByteChannel bodyContentChannel = Channels.newChannel(outputStream);
264-
return bufferedResponse.getBody()
265-
.flatMap(byteBuffer -> writeBufferToBodyStream(bodyContentChannel, byteBuffer))
266-
.doFinally(ignored -> {
267-
responseLogMessage.append("Response body:")
268-
.append(System.lineSeparator())
269-
.append(prettyPrintIfNeeded(logger, prettyPrintBody, contentTypeHeader,
270-
convertStreamToString(outputStream, logger)))
271-
.append(System.lineSeparator())
272-
.append("<-- END HTTP");
273-
274-
logAndReturn(logger, logLevel, responseLogMessage, response);
275-
}).then(Mono.just(bufferedResponse));
273+
return Mono.just(new LoggingHttpResponse(response, responseLogMessage, logger, logLevel,
274+
(int) contentLength, contentTypeHeader, prettyPrintBody));
276275
} else {
277276
responseLogMessage.append("(body content not logged)")
278277
.append(System.lineSeparator())
279278
.append("<-- END HTTP");
280279

281-
return logAndReturn(logger, logLevel, responseLogMessage, response);
280+
logMessage(logger, logLevel, responseLogMessage);
281+
return Mono.just(response);
282282
}
283283
}
284284
}
285285

286-
private static <T> Mono<T> logAndReturn(ClientLogger logger, LogLevel logLevel, StringBuilder logMessageBuilder,
287-
T data) {
286+
private static void logMessage(ClientLogger logger, LogLevel logLevel, StringBuilder logMessageBuilder) {
288287
switch (logLevel) {
289288
case VERBOSE:
290289
logger.verbose(logMessageBuilder.toString());
@@ -305,8 +304,6 @@ private static <T> Mono<T> logAndReturn(ClientLogger logger, LogLevel logLevel,
305304
default:
306305
break;
307306
}
308-
309-
return Mono.justOrEmpty(data);
310307
}
311308

312309
/*
@@ -450,36 +447,13 @@ private static boolean shouldBodyBeLogged(String contentTypeHeader, long content
450447
&& contentLength < MAX_BODY_LOG_SIZE;
451448
}
452449

453-
/*
454-
* Helper function which converts a ByteArrayOutputStream to a String without duplicating the internal buffer.
455-
*/
456-
private static String convertStreamToString(ByteArrayOutputStream stream, ClientLogger logger) {
457-
try {
458-
return stream.toString("UTF-8");
459-
} catch (UnsupportedEncodingException ex) {
460-
throw logger.logExceptionAsError(new RuntimeException(ex));
461-
}
462-
}
463-
464-
/*
465-
* Helper function which writes body ByteBuffers into the body message channel.
466-
*/
467-
private static Mono<ByteBuffer> writeBufferToBodyStream(WritableByteChannel channel, ByteBuffer byteBuffer) {
468-
try {
469-
channel.write(byteBuffer.duplicate());
470-
return Mono.just(byteBuffer);
471-
} catch (IOException ex) {
472-
return Mono.error(ex);
473-
}
474-
}
475-
476450
/*
477451
* Gets the request retry count to include in logging.
478452
*
479453
* If there is no value set or it isn't a valid number null will be returned indicating that retry count won't be
480454
* logged.
481455
*/
482-
private static Integer getRequestRetryCount(Context context, ClientLogger logger) {
456+
private static Integer getRequestRetryCount(Context context) {
483457
Object rawRetryCount = context.getData(RETRY_COUNT_CONTEXT).orElse(null);
484458
if (rawRetryCount == null) {
485459
return null;
@@ -488,7 +462,7 @@ private static Integer getRequestRetryCount(Context context, ClientLogger logger
488462
try {
489463
return Integer.valueOf(rawRetryCount.toString());
490464
} catch (NumberFormatException ex) {
491-
logger.warning("Could not parse the request retry count: '{}'.", rawRetryCount);
465+
LOGGER.warning("Could not parse the request retry count: '{}'.", rawRetryCount);
492466
return null;
493467
}
494468
}
@@ -503,4 +477,81 @@ private static ClientLogger getOrCreateMethodLogger(String methodName) {
503477

504478
return CALLER_METHOD_LOGGER_CACHE.computeIfAbsent(methodName, ClientLogger::new);
505479
}
480+
481+
private static final class LoggingHttpResponse extends HttpResponse {
482+
private final HttpResponse actualResponse;
483+
private final StringBuilder responseLogMessage;
484+
private final int contentLength;
485+
private final ClientLogger logger;
486+
private final boolean prettyPrintBody;
487+
private final String contentTypeHeader;
488+
private final LogLevel logLevel;
489+
490+
private LoggingHttpResponse(HttpResponse actualResponse, StringBuilder responseLogMessage,
491+
ClientLogger logger, LogLevel logLevel, int contentLength, String contentTypeHeader,
492+
boolean prettyPrintBody) {
493+
super(actualResponse.getRequest());
494+
this.actualResponse = actualResponse;
495+
this.responseLogMessage = responseLogMessage;
496+
this.logger = logger;
497+
this.logLevel = logLevel;
498+
this.contentLength = contentLength;
499+
this.contentTypeHeader = contentTypeHeader;
500+
this.prettyPrintBody = prettyPrintBody;
501+
}
502+
503+
@Override
504+
public int getStatusCode() {
505+
return actualResponse.getStatusCode();
506+
}
507+
508+
@Override
509+
public String getHeaderValue(String name) {
510+
return actualResponse.getHeaderValue(name);
511+
}
512+
513+
@Override
514+
public HttpHeaders getHeaders() {
515+
return actualResponse.getHeaders();
516+
}
517+
518+
@Override
519+
public Flux<ByteBuffer> getBody() {
520+
AccessibleByteArrayOutputStream stream = new AccessibleByteArrayOutputStream(contentLength);
521+
522+
return actualResponse.getBody()
523+
.doOnNext(byteBuffer -> {
524+
try {
525+
ImplUtils.writeByteBufferToStream(byteBuffer.duplicate(), stream);
526+
} catch (IOException ex) {
527+
throw LOGGER.logExceptionAsError(new UncheckedIOException(ex));
528+
}
529+
})
530+
.doFinally(ignored -> {
531+
responseLogMessage.append("Response body:")
532+
.append(System.lineSeparator())
533+
.append(prettyPrintIfNeeded(logger, prettyPrintBody, contentTypeHeader,
534+
new String(stream.toByteArray(), 0, stream.count(), StandardCharsets.UTF_8)))
535+
.append(System.lineSeparator())
536+
.append("<-- END HTTP");
537+
538+
logMessage(logger, logLevel, responseLogMessage);
539+
});
540+
}
541+
542+
@Override
543+
public Mono<byte[]> getBodyAsByteArray() {
544+
return FluxUtil.collectBytesFromNetworkResponse(getBody(), actualResponse.getHeaders());
545+
}
546+
547+
@Override
548+
public Mono<String> getBodyAsString() {
549+
return getBodyAsByteArray().map(String::new);
550+
}
551+
552+
@Override
553+
public Mono<String> getBodyAsString(Charset charset) {
554+
return getBodyAsByteArray().map(bytes -> new String(bytes, charset));
555+
}
556+
}
506557
}

0 commit comments

Comments
 (0)