From ccd3170124ba0e6ae4529acb4f3a7d7d2bfa5c31 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 18 Mar 2024 17:37:50 +0300 Subject: [PATCH 1/7] Fix bug calculating content length of stream bodies --- .../kiota/http/OkHttpRequestAdapter.java | 24 ++---- .../kiota/http/OkHttpRequestAdapterTest.java | 86 +++++++++++++++++-- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index dc4c00402..7ee5f4fcb 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -907,30 +907,20 @@ public MediaType contentType() { @Override public long contentLength() throws IOException { - long length; final Set contentLength = requestInfo.headers.getOrDefault( contentLengthHeaderKey, new HashSet<>()); - if (contentLength.isEmpty() - && requestInfo.content + if (!contentLength.isEmpty()) { + return Long.parseLong( + contentLength.toArray(new String[] {})[0]); + } + if (requestInfo.content instanceof ByteArrayInputStream) { final ByteArrayInputStream contentStream = (ByteArrayInputStream) requestInfo.content; - length = contentStream.available(); - } else { - length = - Long.parseLong( - contentLength.toArray(new String[] {})[0]); - } - if (length <= 0) { - length = super.contentLength(); - } - if (length > 0) { - spanForAttributes.setAttribute( - HttpIncubatingAttributes.HTTP_REQUEST_BODY_SIZE, - length); + return contentStream.available(); } - return length; + return super.contentLength(); } @Override diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java index 5905e2bcf..40c7eb99f 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java @@ -301,9 +301,10 @@ void getRequestFromRequestInformationHasCorrectContentLength_JsonPayload() throw new ByteArrayInputStream( "{\"name\":\"value\",\"array\":[\"1\",\"2\",\"3\"]}" .getBytes(StandardCharsets.UTF_8)); - requestInformation.setStreamContent(content, "application/octet-stream"); + requestInformation.setStreamContent(content, "application/json"); requestInformation.httpMethod = HttpMethod.PUT; - requestInformation.headers.tryAdd("Content-Length", String.valueOf(content.available())); + final var contentLength = content.available(); + requestInformation.headers.tryAdd("Content-Length", String.valueOf(contentLength)); final var adapter = new OkHttpRequestAdapter(authenticationProviderMock); final var request = @@ -311,12 +312,12 @@ void getRequestFromRequestInformationHasCorrectContentLength_JsonPayload() throw requestInformation, mock(Span.class), mock(Span.class)); assertEquals( - String.valueOf(requestInformation.content.available()), + String.valueOf(contentLength), request.headers().get("Content-Length")); - assertEquals("application/octet-stream", request.headers().get("Content-Type")); + assertEquals("application/json", request.headers().get("Content-Type")); assertNotNull(request.body()); - assertEquals(request.body().contentLength(), requestInformation.content.available()); - assertEquals(request.body().contentType(), MediaType.parse("application/octet-stream")); + assertEquals(request.body().contentLength(), contentLength); + assertEquals(request.body().contentType(), MediaType.parse("application/json")); } @Test @@ -327,7 +328,8 @@ void getRequestFromRequestInformationIncludesContentLength_FilePayload() throws requestInformation.setUri(new URI("https://localhost")); requestInformation.httpMethod = HttpMethod.PUT; - requestInformation.headers.add("Content-Length", String.valueOf(testFile.length())); + final var contentLength = testFile.length(); + requestInformation.headers.add("Content-Length", String.valueOf(contentLength)); try (FileInputStream content = new FileInputStream(testFile)) { requestInformation.setStreamContent(content, "application/octet-stream"); @@ -337,15 +339,81 @@ void getRequestFromRequestInformationIncludesContentLength_FilePayload() throws requestInformation, mock(Span.class), mock(Span.class)); assertEquals( - String.valueOf(requestInformation.content.available()), + String.valueOf(contentLength), request.headers().get("Content-Length")); assertEquals("application/octet-stream", request.headers().get("Content-Type")); assertNotNull(request.body()); - assertEquals(request.body().contentLength(), requestInformation.content.available()); + assertEquals(request.body().contentLength(), contentLength); assertEquals(request.body().contentType(), MediaType.parse("application/octet-stream")); } } + @Test + void getRequestFromRequestInformationWithoutContentLengthOverrideForStreamBody() throws Exception { + final var authenticationProviderMock = mock(AuthenticationProvider.class); + final var testFile = new File("./src/test/resources/helloWorld.txt"); + final var requestInformation = new RequestInformation(); + + requestInformation.setUri(new URI("https://localhost")); + requestInformation.httpMethod = HttpMethod.PUT; + try (FileInputStream content = new FileInputStream(testFile)) { + requestInformation.setStreamContent(content, "application/octet-stream"); + + final var adapter = new OkHttpRequestAdapter(authenticationProviderMock); + final var request = + adapter.getRequestFromRequestInformation( + requestInformation, mock(Span.class), mock(Span.class)); + + assertEquals("application/octet-stream", request.headers().get("Content-Type")); + assertNotNull(request.body()); + assertEquals(-1L, request.body().contentLength()); + assertEquals(request.body().contentType(), MediaType.parse("application/octet-stream")); + } + } + + @Test + void getRequestFromRequestInformationWithoutContentLengthOverrideForJsonPayload() throws Exception { + final var authenticationProviderMock = mock(AuthenticationProvider.class); + final var requestInformation = new RequestInformation(); + requestInformation.setUri(new URI("https://localhost")); + ByteArrayInputStream content = + new ByteArrayInputStream( + "{\"name\":\"value\",\"array\":[\"1\",\"2\",\"3\"]}" + .getBytes(StandardCharsets.UTF_8)); + requestInformation.setStreamContent(content, "application/json"); + requestInformation.httpMethod = HttpMethod.PUT; + final var contentLength = content.available(); + + final var adapter = new OkHttpRequestAdapter(authenticationProviderMock); + final var request = + adapter.getRequestFromRequestInformation( + requestInformation, mock(Span.class), mock(Span.class)); + + assertEquals("application/json", request.headers().get("Content-Type")); + assertNotNull(request.body()); + assertEquals(contentLength, request.body().contentLength()); + assertEquals(MediaType.parse("application/json"), request.body().contentType()); + } + + @Test + void getRequestFromRequestInformationWithoutContentLengthOverrideWithEmptyPayload() throws Exception { + final var authenticationProviderMock = mock(AuthenticationProvider.class); + final var requestInformation = new RequestInformation(); + requestInformation.setUri(new URI("https://localhost")); + ByteArrayInputStream content = + new ByteArrayInputStream(new byte[0]); + requestInformation.httpMethod = HttpMethod.PUT; + requestInformation.content = content; + + final var adapter = new OkHttpRequestAdapter(authenticationProviderMock); + final var request = + adapter.getRequestFromRequestInformation( + requestInformation, mock(Span.class), mock(Span.class)); + + assertNull(request.headers().get("Content-Type")); + assertEquals(0, request.body().contentLength()); + } + public static OkHttpClient getMockClient(final Response response) throws IOException { final OkHttpClient mockClient = mock(OkHttpClient.class); final Call remoteCall = mock(Call.class); From 20297acb55b60c286483a2737d6ae477fcc0a3dd Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 18 Mar 2024 18:10:34 +0300 Subject: [PATCH 2/7] Add content length to span after building request --- .../microsoft/kiota/http/OkHttpRequestAdapter.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 7ee5f4fcb..a3406c547 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -846,7 +846,7 @@ private void setBaseUrlForRequestInformation(@Nonnull final RequestInformation r try (final Scope scope = span.makeCurrent()) { this.authProvider.authenticateRequest(requestInfo, null); return (T) getRequestFromRequestInformation(requestInfo, span, span); - } catch (URISyntaxException | MalformedURLException ex) { + } catch (URISyntaxException | IOException ex) { span.recordException(ex); throw new RuntimeException(ex); } finally { @@ -868,7 +868,7 @@ private void setBaseUrlForRequestInformation(@Nonnull final RequestInformation r @Nonnull final RequestInformation requestInfo, @Nonnull final Span parentSpan, @Nonnull final Span spanForAttributes) - throws URISyntaxException, MalformedURLException { + throws URISyntaxException, MalformedURLException, IOException { final Span span = GlobalOpenTelemetry.getTracer(obsOptions.getTracerInstrumentationName()) .spanBuilder("getRequestFromRequestInformation") @@ -957,7 +957,12 @@ public void writeTo(@Nonnull BufferedSink sink) throws IOException { requestBuilder.tag(obsOptions.getType(), obsOptions); } requestBuilder.tag(Span.class, parentSpan); - return requestBuilder.build(); + final Request request = requestBuilder.build(); + if (request.body() != null && request.body().contentLength() > 0) { + spanForAttributes.setAttribute( + SemanticAttributes.HTTP_REQUEST_BODY_SIZE, request.body().contentLength()); + } + return request; } finally { span.end(); } From 220367e8bf60998f091584667feb55c7760d9b58 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 18 Mar 2024 18:19:07 +0300 Subject: [PATCH 3/7] Update CHANGELOG --- .vscode/settings.json | 4 ++-- CHANGELOG.md | 28 +++++++++++++++++++--------- gradle.properties | 4 ++-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 92a5ea5d0..990454686 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,6 @@ "inheritdoc", "kiota" ], - "java.configuration.updateBuildConfiguration": "interactive", + "java.configuration.updateBuildConfiguration": "automatic", "java.compile.nullAnalysis.mode": "automatic" -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1459dd08e..6727aa121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +### Changed + +## [1.1.5] + +### Changed + +- Fixed exception thrown when setting content length on stream request bodies. + ## [1.1.4] - 2024-04-04 ### Added @@ -33,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Fixed a bug where not providing scopes to `AzureIdentityAccessTokenProvider` failed with `UnsupportedOperationException` when attempting to fetch the token. [microsoftgraph/msgraph-sdk-java#1882](https://github.com/microsoftgraph/msgraph-sdk-java/issues/1882) +- Fixed a bug where not providing scopes to `AzureIdentityAccessTokenProvider` failed with `UnsupportedOperationException` when attempting to fetch the token. [microsoftgraph/msgraph-sdk-java#1882](https://github.com/microsoftgraph/msgraph-sdk-java/issues/1882) ## [1.1.0] - 2024-02-14 @@ -43,13 +53,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.6] - 2023-03-04 -### Changed +### Changed - Fixed a regression with the content length request header from 1.0.5. ## [1.0.5] - 2023-02-28 -### Changed +### Changed - Added contentLength property to RequestInformation to facilitate in setting the content length of the Okhttp3 RequestBody object within the OkhttpRequestAdapter. @@ -210,7 +220,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Add PeriodAndDuration constructor to create new object from a PeriodAndDuration object. +- Add PeriodAndDuration constructor to create new object from a PeriodAndDuration object. ## [0.7.0] - 2023-08-18 @@ -222,7 +232,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Javax annotations replaced in favor of Jakarta annotations. +- Javax annotations replaced in favor of Jakarta annotations. ## [0.5.0] - 2023-07-26 @@ -269,8 +279,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Adds a NativeResponseHandler to abstractions. -- Adds setResponseHandler method to RequestInformation class in abstractions. +- Adds a NativeResponseHandler to abstractions. +- Adds setResponseHandler method to RequestInformation class in abstractions. ## [0.4.1] - 2023-03-29 @@ -373,11 +383,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added ResponseHandlerOption class. +- Added ResponseHandlerOption class. ### Changed -- Removed responseHandler parameter from RequestAdapter sendAsync methods. +- Removed responseHandler parameter from RequestAdapter sendAsync methods. - Compatibility for Android level 26. ## [0.0.5] - 2022-09-15 diff --git a/gradle.properties b/gradle.properties index 38409fd06..1750affd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,8 +26,8 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 1 mavenMinorVersion = 1 -mavenPatchVersion = 4 -mavenArtifactSuffix = +mavenPatchVersion = 5 +mavenArtifactSuffix = #These values are used to run functional tests #If you wish to run the functional tests, edit the gradle.properties From 49b5895438281ddde43c1a703825919797704530 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 19 Mar 2024 13:19:31 +0300 Subject: [PATCH 4/7] Fix failing CI checks --- .../kiota/http/OkHttpRequestAdapter.java | 17 ++++++++++------ .../kiota/http/OkHttpRequestAdapterTest.java | 20 +++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index a3406c547..5c689ff3f 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -912,10 +912,9 @@ public long contentLength() throws IOException { contentLengthHeaderKey, new HashSet<>()); if (!contentLength.isEmpty()) { return Long.parseLong( - contentLength.toArray(new String[] {})[0]); + contentLength.toArray(new String[] {})[0]); } - if (requestInfo.content - instanceof ByteArrayInputStream) { + if (requestInfo.content instanceof ByteArrayInputStream) { final ByteArrayInputStream contentStream = (ByteArrayInputStream) requestInfo.content; return contentStream.available(); @@ -958,9 +957,15 @@ public void writeTo(@Nonnull BufferedSink sink) throws IOException { } requestBuilder.tag(Span.class, parentSpan); final Request request = requestBuilder.build(); - if (request.body() != null && request.body().contentLength() > 0) { - spanForAttributes.setAttribute( - SemanticAttributes.HTTP_REQUEST_BODY_SIZE, request.body().contentLength()); + if (request != null) { + RequestBody requestBody = request.body(); + if (requestBody != null) { + long contentLength = requestBody.contentLength(); + if (contentLength >= 0) { + spanForAttributes.setAttribute( + SemanticAttributes.HTTP_REQUEST_BODY_SIZE, contentLength); + } + } } return request; } finally { diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java index 40c7eb99f..3a2185915 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java @@ -311,9 +311,7 @@ void getRequestFromRequestInformationHasCorrectContentLength_JsonPayload() throw adapter.getRequestFromRequestInformation( requestInformation, mock(Span.class), mock(Span.class)); - assertEquals( - String.valueOf(contentLength), - request.headers().get("Content-Length")); + assertEquals(String.valueOf(contentLength), request.headers().get("Content-Length")); assertEquals("application/json", request.headers().get("Content-Type")); assertNotNull(request.body()); assertEquals(request.body().contentLength(), contentLength); @@ -338,9 +336,7 @@ void getRequestFromRequestInformationIncludesContentLength_FilePayload() throws adapter.getRequestFromRequestInformation( requestInformation, mock(Span.class), mock(Span.class)); - assertEquals( - String.valueOf(contentLength), - request.headers().get("Content-Length")); + assertEquals(String.valueOf(contentLength), request.headers().get("Content-Length")); assertEquals("application/octet-stream", request.headers().get("Content-Type")); assertNotNull(request.body()); assertEquals(request.body().contentLength(), contentLength); @@ -349,7 +345,8 @@ void getRequestFromRequestInformationIncludesContentLength_FilePayload() throws } @Test - void getRequestFromRequestInformationWithoutContentLengthOverrideForStreamBody() throws Exception { + void getRequestFromRequestInformationWithoutContentLengthOverrideForStreamBody() + throws Exception { final var authenticationProviderMock = mock(AuthenticationProvider.class); final var testFile = new File("./src/test/resources/helloWorld.txt"); final var requestInformation = new RequestInformation(); @@ -372,7 +369,8 @@ void getRequestFromRequestInformationWithoutContentLengthOverrideForStreamBody() } @Test - void getRequestFromRequestInformationWithoutContentLengthOverrideForJsonPayload() throws Exception { + void getRequestFromRequestInformationWithoutContentLengthOverrideForJsonPayload() + throws Exception { final var authenticationProviderMock = mock(AuthenticationProvider.class); final var requestInformation = new RequestInformation(); requestInformation.setUri(new URI("https://localhost")); @@ -396,12 +394,12 @@ void getRequestFromRequestInformationWithoutContentLengthOverrideForJsonPayload( } @Test - void getRequestFromRequestInformationWithoutContentLengthOverrideWithEmptyPayload() throws Exception { + void getRequestFromRequestInformationWithoutContentLengthOverrideWithEmptyPayload() + throws Exception { final var authenticationProviderMock = mock(AuthenticationProvider.class); final var requestInformation = new RequestInformation(); requestInformation.setUri(new URI("https://localhost")); - ByteArrayInputStream content = - new ByteArrayInputStream(new byte[0]); + ByteArrayInputStream content = new ByteArrayInputStream(new byte[0]); requestInformation.httpMethod = HttpMethod.PUT; requestInformation.content = content; From b95a6e9f10649f82ec0b3ae001ac943108c384ec Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 12 Apr 2024 10:45:06 +0300 Subject: [PATCH 5/7] Fix SonarCloud issues --- .../com/microsoft/kiota/http/OkHttpRequestAdapter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 5c689ff3f..1cfe82629 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; -import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.time.LocalDate; @@ -862,13 +861,13 @@ private void setBaseUrlForRequestInformation(@Nonnull final RequestInformation r * @param spanForAttributes the span for the attributes. * @return the created request instance. * @throws URISyntaxException if the URI is invalid. - * @throws MalformedURLException if the URL is invalid. + * @throws IOException if the URL is invalid. */ protected @Nonnull Request getRequestFromRequestInformation( @Nonnull final RequestInformation requestInfo, @Nonnull final Span parentSpan, @Nonnull final Span spanForAttributes) - throws URISyntaxException, MalformedURLException, IOException { + throws URISyntaxException, IOException { final Span span = GlobalOpenTelemetry.getTracer(obsOptions.getTracerInstrumentationName()) .spanBuilder("getRequestFromRequestInformation") @@ -963,7 +962,7 @@ public void writeTo(@Nonnull BufferedSink sink) throws IOException { long contentLength = requestBody.contentLength(); if (contentLength >= 0) { spanForAttributes.setAttribute( - SemanticAttributes.HTTP_REQUEST_BODY_SIZE, contentLength); + HttpIncubatingAttributes.HTTP_REQUEST_BODY_SIZE, contentLength); } } } From d5740e7717b475c9dfe6920543a9d76de6182eb2 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 16 Apr 2024 09:53:57 +0300 Subject: [PATCH 6/7] Undo vscode settings change --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 990454686..78348c083 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,6 @@ "inheritdoc", "kiota" ], - "java.configuration.updateBuildConfiguration": "automatic", + "java.configuration.updateBuildConfiguration": "interactive", "java.compile.nullAnalysis.mode": "automatic" } From 4143dbc63bc92b7dfb32626f730a453f53058414 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Thu, 18 Apr 2024 18:06:57 +0300 Subject: [PATCH 7/7] Document contentLength calculation --- .../java/com/microsoft/kiota/http/OkHttpRequestAdapter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 1cfe82629..744f73c6c 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -913,9 +913,13 @@ public long contentLength() throws IOException { return Long.parseLong( contentLength.toArray(new String[] {})[0]); } + // super.contentLength() is not relied on since it defaults to + // -1L, causing wrong telemetry added to the attributes. if (requestInfo.content instanceof ByteArrayInputStream) { final ByteArrayInputStream contentStream = (ByteArrayInputStream) requestInfo.content; + // using available() on a byte-array backed input stream is + // reliable because array size is defined. return contentStream.available(); } return super.contentLength();