Skip to content

Commit d2103b3

Browse files
Merge pull request #1088 from microsoft/rsh/contentLengthUploadFix
Content Length Missing Fix
2 parents 4fb8d13 + 826ad00 commit d2103b3

File tree

6 files changed

+125
-26
lines changed

6 files changed

+125
-26
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Changed
1313

14+
## [1.0.5] - 2023-02-28
15+
16+
### Changed
17+
18+
- Added contentLength property to RequestInformation to facilitate in setting the content length of the Okhttp3 RequestBody object within the OkhttpRequestAdapter.
19+
1420
## [1.0.4] - 2024-02-26
1521

1622
### Changed

components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import okio.BufferedSink;
3030
import okio.Okio;
3131

32+
import java.io.ByteArrayInputStream;
3233
import java.io.IOException;
3334
import java.io.InputStream;
3435
import java.math.BigDecimal;
@@ -53,6 +54,7 @@
5354
/** RequestAdapter implementation for OkHttp */
5455
public class OkHttpRequestAdapter implements com.microsoft.kiota.RequestAdapter {
5556
private static final String contentTypeHeaderKey = "Content-Type";
57+
private static final String contentLengthHeaderKey = "Content-Length";
5658
@Nonnull private final Call.Factory client;
5759
@Nonnull private final AuthenticationProvider authProvider;
5860
@Nonnull private final ObservabilityOptions obsOptions;
@@ -188,7 +190,7 @@ public void enableBackingStore(@Nullable final BackingStoreFactory backingStoreF
188190
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
189191
Objects.requireNonNull(factory, nullFactoryParameter);
190192

191-
final Span span = startSpan(requestInfo, "sendCollectionAsync");
193+
final Span span = startSpan(requestInfo, "sendCollection");
192194
try (final Scope scope = span.makeCurrent()) {
193195
Response response = this.getHttpResponseMessage(requestInfo, span, span, null);
194196
final ResponseHandler responseHandler = getResponseHandler(requestInfo);
@@ -269,7 +271,7 @@ private Span startSpan(
269271
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
270272
Objects.requireNonNull(factory, nullFactoryParameter);
271273

272-
final Span span = startSpan(requestInfo, "sendAsync");
274+
final Span span = startSpan(requestInfo, "send");
273275
try (final Scope scope = span.makeCurrent()) {
274276
Response response = this.getHttpResponseMessage(requestInfo, span, span, null);
275277
final ResponseHandler responseHandler = getResponseHandler(requestInfo);
@@ -331,7 +333,7 @@ private void closeResponse(boolean closeResponse, Response response) {
331333
@Nonnull final Class<ModelType> targetClass) {
332334
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
333335
Objects.requireNonNull(targetClass, "parameter targetClass cannot be null");
334-
final Span span = startSpan(requestInfo, "sendPrimitiveAsync");
336+
final Span span = startSpan(requestInfo, "sendPrimitive");
335337
try (final Scope scope = span.makeCurrent()) {
336338
Response response = this.getHttpResponseMessage(requestInfo, span, span, null);
337339
final ResponseHandler responseHandler = getResponseHandler(requestInfo);
@@ -425,7 +427,7 @@ private void closeResponse(boolean closeResponse, Response response) {
425427
@Nonnull final ValuedEnumParser<ModelType> enumParser) {
426428
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
427429
Objects.requireNonNull(enumParser, nullEnumParserParameter);
428-
final Span span = startSpan(requestInfo, "sendEnumAsync");
430+
final Span span = startSpan(requestInfo, "sendEnum");
429431
try (final Scope scope = span.makeCurrent()) {
430432
Response response = this.getHttpResponseMessage(requestInfo, span, span, null);
431433
final ResponseHandler responseHandler = getResponseHandler(requestInfo);
@@ -471,7 +473,7 @@ private void closeResponse(boolean closeResponse, Response response) {
471473
@Nonnull final ValuedEnumParser<ModelType> enumParser) {
472474
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
473475
Objects.requireNonNull(enumParser, nullEnumParserParameter);
474-
final Span span = startSpan(requestInfo, "sendEnumCollectionAsync");
476+
final Span span = startSpan(requestInfo, "sendEnumCollection");
475477
try (final Scope scope = span.makeCurrent()) {
476478
Response response = this.getHttpResponseMessage(requestInfo, span, span, null);
477479
final ResponseHandler responseHandler = getResponseHandler(requestInfo);
@@ -518,7 +520,7 @@ private void closeResponse(boolean closeResponse, Response response) {
518520
@Nonnull final Class<ModelType> targetClass) {
519521
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
520522

521-
final Span span = startSpan(requestInfo, "sendPrimitiveCollectionAsync");
523+
final Span span = startSpan(requestInfo, "sendPrimitiveCollection");
522524
try (final Scope scope = span.makeCurrent()) {
523525
Response response = getHttpResponseMessage(requestInfo, span, span, null);
524526
final ResponseHandler responseHandler = getResponseHandler(requestInfo);
@@ -835,7 +837,7 @@ private void setBaseUrlForRequestInformation(@Nonnull final RequestInformation r
835837
@SuppressWarnings("unchecked")
836838
@Nonnull public <T> T convertToNativeRequest(@Nonnull final RequestInformation requestInfo) {
837839
Objects.requireNonNull(requestInfo, nullRequestInfoParameter);
838-
final Span span = startSpan(requestInfo, "convertToNativeRequestAsync");
840+
final Span span = startSpan(requestInfo, "convertToNativeRequest");
839841
try (final Scope scope = span.makeCurrent()) {
840842
this.authProvider.authenticateRequest(requestInfo, null);
841843
return (T) getRequestFromRequestInformation(requestInfo, span, span);
@@ -885,9 +887,8 @@ private void setBaseUrlForRequestInformation(@Nonnull final RequestInformation r
885887
@Override
886888
public MediaType contentType() {
887889
final Set<String> contentTypes =
888-
requestInfo.headers.containsKey(contentTypeHeaderKey)
889-
? requestInfo.headers.get(contentTypeHeaderKey)
890-
: new HashSet<>();
890+
requestInfo.headers.getOrDefault(
891+
contentTypeHeaderKey, new HashSet<>());
891892
if (contentTypes.isEmpty()) {
892893
return null;
893894
} else {
@@ -899,6 +900,30 @@ public MediaType contentType() {
899900
}
900901
}
901902

903+
@Override
904+
public long contentLength() {
905+
long length;
906+
final Set<String> contentLength =
907+
requestInfo.headers.getOrDefault(
908+
contentLengthHeaderKey, new HashSet<>());
909+
if (contentLength.isEmpty()
910+
&& requestInfo.content
911+
instanceof ByteArrayInputStream) {
912+
final ByteArrayInputStream contentStream =
913+
(ByteArrayInputStream) requestInfo.content;
914+
length = contentStream.available();
915+
} else {
916+
length =
917+
Long.parseLong(
918+
contentLength.toArray(new String[] {})[0]);
919+
}
920+
if (length > 0) {
921+
spanForAttributes.setAttribute(
922+
SemanticAttributes.HTTP_REQUEST_BODY_SIZE, length);
923+
}
924+
return length;
925+
}
926+
902927
@Override
903928
public void writeTo(@Nonnull BufferedSink sink) throws IOException {
904929
sink.writeAll(Okio.source(requestInfo.content));
@@ -933,17 +958,7 @@ public void writeTo(@Nonnull BufferedSink sink) throws IOException {
933958
requestBuilder.tag(obsOptions.getType(), obsOptions);
934959
}
935960
requestBuilder.tag(Span.class, parentSpan);
936-
final Request request = requestBuilder.build();
937-
final List<String> contentLengthHeader = request.headers().values("Content-Length");
938-
if (contentLengthHeader != null && !contentLengthHeader.isEmpty()) {
939-
final String firstEntryValue = contentLengthHeader.get(0);
940-
if (firstEntryValue != null && !firstEntryValue.isEmpty()) {
941-
spanForAttributes.setAttribute(
942-
SemanticAttributes.HTTP_REQUEST_BODY_SIZE,
943-
Long.parseLong(firstEntryValue));
944-
}
945-
}
946-
return request;
961+
return requestBuilder.build();
947962
} finally {
948963
span.end();
949964
}

components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public UserAgentHandlerOption() {}
1313

1414
private boolean enabled = true;
1515
@Nonnull private String productName = "kiota-java";
16-
@Nonnull private String productVersion = "1.0.0";
16+
@Nonnull private String productVersion = "1.0.5";
1717

1818
/**
1919
* Gets the product name to be used in the user agent header

components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,15 @@
2828

2929
import okio.Okio;
3030

31+
import org.junit.jupiter.api.Test;
3132
import org.junit.jupiter.params.ParameterizedTest;
3233
import org.junit.jupiter.params.provider.Arguments;
3334
import org.junit.jupiter.params.provider.EnumSource;
3435
import org.junit.jupiter.params.provider.MethodSource;
3536
import org.junit.jupiter.params.provider.ValueSource;
3637
import org.mockito.stubbing.Answer;
3738

38-
import java.io.ByteArrayInputStream;
39-
import java.io.IOException;
40-
import java.io.InputStream;
39+
import java.io.*;
4140
import java.net.URI;
4241
import java.nio.charset.StandardCharsets;
4342
import java.util.Arrays;
@@ -293,6 +292,60 @@ void throwsAPIException(
293292
assertTrue(exception.getResponseHeaders().containsKey("request-id"));
294293
}
295294

295+
@Test
296+
void getRequestFromRequestInformationHasCorrectContentLength_JsonPayload() throws Exception {
297+
final var authenticationProviderMock = mock(AuthenticationProvider.class);
298+
final var requestInformation = new RequestInformation();
299+
requestInformation.setUri(new URI("https://localhost"));
300+
ByteArrayInputStream content =
301+
new ByteArrayInputStream(
302+
"{\"name\":\"value\",\"array\":[\"1\",\"2\",\"3\"]}"
303+
.getBytes(StandardCharsets.UTF_8));
304+
requestInformation.setStreamContent(content, "application/octet-stream");
305+
requestInformation.httpMethod = HttpMethod.PUT;
306+
requestInformation.headers.tryAdd("Content-Length", String.valueOf(content.available()));
307+
308+
final var adapter = new OkHttpRequestAdapter(authenticationProviderMock);
309+
final var request =
310+
adapter.getRequestFromRequestInformation(
311+
requestInformation, mock(Span.class), mock(Span.class));
312+
313+
assertEquals(
314+
String.valueOf(requestInformation.content.available()),
315+
request.headers().get("Content-Length"));
316+
assertEquals("application/octet-stream", request.headers().get("Content-Type"));
317+
assertNotNull(request.body());
318+
assertEquals(request.body().contentLength(), requestInformation.content.available());
319+
assertEquals(request.body().contentType(), MediaType.parse("application/octet-stream"));
320+
}
321+
322+
@Test
323+
void getRequestFromRequestInformationIncludesContentLength_FilePayload() throws Exception {
324+
final var authenticationProviderMock = mock(AuthenticationProvider.class);
325+
final var testFile = new File("./src/test/resources/helloWorld.txt");
326+
final var requestInformation = new RequestInformation();
327+
328+
requestInformation.setUri(new URI("https://localhost"));
329+
requestInformation.httpMethod = HttpMethod.PUT;
330+
requestInformation.headers.add("Content-Length", String.valueOf(testFile.length()));
331+
try (FileInputStream content = new FileInputStream(testFile)) {
332+
requestInformation.setStreamContent(content, "application/octet-stream");
333+
334+
final var adapter = new OkHttpRequestAdapter(authenticationProviderMock);
335+
final var request =
336+
adapter.getRequestFromRequestInformation(
337+
requestInformation, mock(Span.class), mock(Span.class));
338+
339+
assertEquals(
340+
String.valueOf(requestInformation.content.available()),
341+
request.headers().get("Content-Length"));
342+
assertEquals("application/octet-stream", request.headers().get("Content-Type"));
343+
assertNotNull(request.body());
344+
assertEquals(request.body().contentLength(), requestInformation.content.available());
345+
assertEquals(request.body().contentType(), MediaType.parse("application/octet-stream"));
346+
}
347+
}
348+
296349
public static OkHttpClient getMockClient(final Response response) throws IOException {
297350
final OkHttpClient mockClient = mock(OkHttpClient.class);
298351
final Call remoteCall = mock(Call.class);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
HelloWorld!
2+
HelloWorld!
3+
HelloWorld!
4+
HelloWorld!
5+
HelloWorld!
6+
HelloWorld!
7+
HelloWorld!
8+
HelloWorld!
9+
HelloWorld!
10+
HelloWorld!
11+
HelloWorld!
12+
HelloWorld!
13+
HelloWorld!
14+
HelloWorld!
15+
HelloWorld!
16+
HelloWorld!
17+
HelloWorld!
18+
HelloWorld!
19+
HelloWorld!
20+
HelloWorld!
21+
HelloWorld!
22+
HelloWorld!
23+
HelloWorld!
24+
HelloWorld!
25+
HelloWorld!

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ org.gradle.caching=true
2626
mavenGroupId = com.microsoft.kiota
2727
mavenMajorVersion = 1
2828
mavenMinorVersion = 0
29-
mavenPatchVersion = 4
29+
mavenPatchVersion = 5
3030
mavenArtifactSuffix =
3131

3232
#These values are used to run functional tests

0 commit comments

Comments
 (0)