-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Content Length Missing Fix #1088
Conversation
File uploading was failing due to contentLength property not being set on RequestBody object, add method to override. Make the value available as part of RequestInformation object.
273c0eb
to
0c608ee
Compare
components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not concerned about breaking the API in this case, as almost anything we are going to do on the InputStream
is gonna throw an IOException
.
I believe that relying on available
is not the correct resolution for this bug.
Having a test that stresses the use case would be great.
components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java
Outdated
Show resolved
Hide resolved
components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java
Outdated
Show resolved
Hide resolved
components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java
Outdated
Show resolved
Hide resolved
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
I have refactored the code to utilize Okhttp's method RequestBody.create(Byte[], MediaType) |
With this solution, is it no longer necessary to know the length of the InputStream in advance? The background is that in my program I first write the InputStream into a ByteArray to determine the final length, which I do not know, because I get the data from different data sources. |
components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java
Outdated
Show resolved
Hide resolved
@araneolus, I would advise caution if you don't control the source giving you the unknown length streams. The reason is that a misbehaving server can send you an infinite stream, which would fill up your RAM and cause a DoS. A potential change is to read from the stream into a fixed length buffer, then write the buffer contents into a file (up to a limit). You can get the size of the stream by either calculating it or from the file's metadata. You could then upload the file after. |
components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Get close! I'd like unit tests to be implemented for both scenarios:
- the stream is coming from local file system
- the stream is a known JSON payload
To make sure the headers are what we expect
components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java
Outdated
Show resolved
Hide resolved
…OkHttpRequestAdapter.java Co-authored-by: Vincent Biret <vibiret@microsoft.com>
This comment was marked as outdated.
This comment was marked as outdated.
Conflicts have been resolved. A maintainer will take a look shortly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the changes!
@ramsessanchez there are still a few failing unit test if you could look into it please? |
Quality Gate failedFailed conditions |
Hooray, it works :-) |
Unfortunately I have to take back my Hooray :-( Can't write 'testfolder/jdk1.6.0_45.tgz' Range [5242880, 5242880 + 5242880) out of bounds for length 5242880 I am setting the size of the File at driveItemUploadableProperties |
instanceof ByteArrayInputStream) { | ||
final ByteArrayInputStream contentStream = | ||
(ByteArrayInputStream) requestInfo.content; | ||
length = contentStream.available(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm so sorry I have not followed up on this subject, but, as noticed here: #1088 (comment)
available
is NOT the API you need to use in this context since it has very very different semantics from the expected behavior.
Have you explored different solutions?
Do we have a reproducer of the original issue that triggered those changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, due to the support and semantics, I'm using workarounds in the Apicurio Registry testsuite to properly perform file uploads.
But I'm looking forward to a real resolution of the issue.
If you need me to chip in I would love a minimal repoducer, maybe @araneolus would be able to provide one 🙏
Ok, This is my Test code.
public void put(final OFSFile<?> ofsFile, final String destination, final InputStream fis) throws IOException {
try {
connect();
} catch (InterruptedException e) {
throw new IOException("Interrupted");
}
Drive drive = this.getDrive();
if (drive == null) {
throw new IOException("Drive is empty");
}
Path tempFilePath = null;
long streamSize = 0;
byte[] tempBuffer = null;
if (this.isTempFileUse()) {
tempFilePath = Files.createTempFile("vec", ".tmp");
try {
Files.copy(fis, tempFilePath);
streamSize = Files.size(tempFilePath);
} catch (Exception e) {
log.error("[put] " + this.getSessionId() + " Can't create tempfile '" + tempFilePath.getFileName()
+ "' " + e.getMessage());
throw new IOException("Can't create tempfile '" + tempFilePath.getFileName() + "' " + e.getMessage());
} finally {
fis.close();
}
streamSize = Files.size(tempFilePath);
} else {
tempBuffer = IOUtils.toByteArray(fis);
streamSize = tempBuffer.length;
}
InputStream inputstream = null;
try {
if (this.isTempFileUse()) {
inputstream = Files.newInputStream(tempFilePath, java.nio.file.StandardOpenOption.READ);
} else {
inputstream = new ByteArrayInputStream(tempBuffer);
}
IProgressCallback progressCallback = new IProgressCallback() {
@Override
public void report(final long current, final long max) {
log.debug(String.format("Uploaded %d bytes of %d total bytes", current, max));
}
};
if (streamSize < 4194304) {
RequestInformation requestInformation = graphClient.drives().byDriveId(drive.getId()).items()
.byDriveItemId("root:/" + destination + ":").content().toPutRequestInformation(inputstream);
URI uriIncludesConflictBehavior = new URI(
requestInformation.getUri().toString() + "?@microsoft.graph.conflictBehavior=replace");
requestInformation.setUri(uriIncludesConflictBehavior);
graphClient.getRequestAdapter().sendPrimitive(requestInformation, null, InputStream.class);
} else {
DriveItemUploadableProperties driveItemUploadableProperties = new DriveItemUploadableProperties();
driveItemUploadableProperties.setName(FilenameUtils.getName(destination));
driveItemUploadableProperties.setFileSize(streamSize);
Map<String, Object> additionalData = new HashMap<>();
additionalData.put("@microsoft.graph.conflictBehavior", "replace");
driveItemUploadableProperties.setAdditionalData(additionalData);
Map<String, Object> commitData = new HashMap<>();
commitData.put("deferCommit", false);
CreateUploadSessionPostRequestBody uploadSessionPostRequestBody = new CreateUploadSessionPostRequestBody();
//uploadSessionPostRequestBody.setAdditionalData(commitData);
uploadSessionPostRequestBody.setItem(driveItemUploadableProperties);
UploadSession uploadSession = null;
try {
RequestInformation test = graphClient.drives().byDriveId(drive.getId()).items()
.byDriveItemId("root:/" + destination + ":").createUploadSession()
.toPostRequestInformation(uploadSessionPostRequestBody);
String xx = IOUtils.toString(test.content, StandardCharsets.UTF_8);
uploadSession = graphClient.drives().byDriveId(drive.getId()).items()
.byDriveItemId("root:/" + destination + ":").createUploadSession()
.post(uploadSessionPostRequestBody);
} catch (ApiException gse) {
if (gse.getResponseStatusCode() == 404) {
}
}
LargeFileUploadTask<DriveItemUploadableProperties> uploadTask = new LargeFileUploadTask<>(
graphClient.getRequestAdapter(), uploadSession, inputstream, streamSize,
DriveItemUploadableProperties::createFromDiscriminatorValue);
try {
UploadResult<DriveItemUploadableProperties> uploadResult = uploadTask
.upload(this.getRetry() <= 0 ? 5 : this.getRetry(),progressCallback);
if (uploadResult.isUploadSuccessful()) {
log.info("[put] " + this.getSessionId() + "finished. File '" + destination
+ "' succcessfully written");
}
} catch (ApiException | InterruptedException exception) {
log.error("[put] " + this.getSessionId() + "Exception :" + exception.getMessage());
exception.printStackTrace();
}
}
} catch (Exception e) {
log.error("[put] " + this.getSessionId() + " Can't write '" + destination + "' " + e.getMessage());
throw new IOException("Can't write '" + destination + "' " + e.getMessage());
} finally {
if (tempFilePath != null) {
Files.delete(tempFilePath);
}
if (inputstream != null) {
inputstream.close();
}
tempBuffer = null;
}
return;
} |
There have been several issues raised concering the failure of FileUpload. The issue stems from the fact that we did not include an
Override
on the contentLength method on the RequestBody custom constructor when translating a RequestInformation object to an Okhttp Request object. I have also added a property on RequestInformation that is set whenever thecontent
property is set on the RequestInformation object. While we did include acontent-length
header it seems to also be a requirement that this property is set on the RequestBody otherwise we will receive an error stating that the 'content length is unknown'.This will lead to version 1.0.3