-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Order is not enforced when making a multipart/form-data request #1382
Comments
Hi, thanks for reporting this. This is an unfortunate side-effect of the current HTTP API, which is in plans of being improved in the near future, so we'll make sure to address this as well. In the meantime, you should be able to workaround this by manually building the request payload as a string (something like this) and passing that to The only tricky thing would be serializing the binary file, which you'd need to base64 encode first. Unfortunately, the k6 Then you'll be able to do something like:
... and append that to the body. We realize this is unwieldy, but it's the best workaround until this is improved. EDIT: Correction: |
Thanks @imiric! This worked wonderfully! It's ugly but we only need it for s3 uploads so we can just hide it away in a utility for now. Was tearing my hair out looking for any workaround or solution 😊 |
Hey @imiric this approach did take us a step further, but still with no luck. The upload successfully goes through, and we can see the file in the S3 bucket, but it becomes corrupted. We've deduced the S3 requires either a Buffer or a UTF8 String, not base64. Neither which k6 has native support for. So we end up trying to upload a word doc, it shows up in the bucket as a word document filled with the base64 of the corrupted file. Any suggestions on a work around for this? |
Hi, this is likely because S3 is storing the raw base64 string which you'll need to decode when you download it, but the data shouldn't be corrupted. Alternatively, try uploading a string converted from the byte array with something like: let file = http.file(binFile, fileName, 'application/msword');
let fileStr = String.fromCharCode(...file.data); And set the |
@imiric Nope, that didn't do it either. For a little more context, here's what we have so far.
Where the s3Payload has actual values. When a doc file is uploaded using this, it actually is more "corrupt" than the other method LibreOffice tries to recover it and fails. Where before the word doc would open, but just be filled with base64. Replacing ``${String.fromCharCode(...s3file.data)}` with a random string, literally converts to a word document with that string in it. |
The only successful attempt I have completed is by reverting back to the original method using the JSON object, and file: http.file() originally in @kylewright-bluescape 's post. But the preserving ordering issue remains. So it only completes successfully, randomly based on if |
Unfortunately this isn't possible as this is part of a multi part system. When the system pulls the file from S3 it expects to be the exact file to be the format described without decoding being necessary (like it is when we do a regular form data post outside of k6). |
Hi, sorry, I'm not familiar with the S3 API at this level, and we currently don't have the resources to implement a working example that uses formdata. I've seen this discussion which also brings up the corruption issue and suggests uploading only the file portion, but that was 5 years ago, so not sure how valid it might be. Alternatively give this library for the V4 API a try: https://gist.github.com/MStoykov/38cc1293daa9080b11e26053589a6865 , which you should be able to use without the manual form building. Also consider posting on the community forum in case someone has already done this before. |
If you can provide fuller code example where I only need to put in my keys/files, maybe I could take 30 minutes looking into it, but currently not up to once again reading AWS documentation on signing requests :D |
Hey @mstoykov, thanks for offering. I don't blame you for not wanting to re-read those docs 😁 Bit of a tricky situation since there may be some confidential information in that payload. But maybe we could simplify this away from S3 for a second. One of two things would solve this problem.
When using form data. The file is read using:
The funny thing is, the word doc then contains the text from the doc unformatted along with some gibberish header and footer that is likely the contents of the file. I believe this is because the formdata request is literally looking for the contents of the file. When using the JSON payload, we can pass the Buffer/stream (however k6 handles that). If neither of those things are possible, that's just how it is! We'll check-out/post-on the community forums as well. |
@landon-martin Also, the JS VM, goja, that we use currently is getting a lot of work on it which will likely turn out to fix this, so, me trying to work a hack around it now might not be very productive. Although we probably won't start using that new version for some time more, especially given its experimental status :( |
@mstoykov here what we were trying:
For this part: we've tried opening the file both with and without 'b'. This is the api ref: |
@kylewright-bluescape and doing |
@mstoykov correct had that in there before. |
The best solution for us turned out to be hacking together something into k6 source that allows us to specify field order in the params obj and then building our own binary. That change lets us do this:
Since ES2015 outputs keys in order of insertion (if they are all strings) it makes it the cleanest way for us to do this for now. My golang is pretty rudimentary/bad but I can post a PR (or a patch file) if you like. Not sure how this would fit into your current plans for the http rework. -Kyle |
is there any update on this bug? we're facing this problem because we're using this library to manage https://github.com/jaydenseric/graphql-upload and follow their suggested pattern (https://github.com/jaydenseric/graphql-multipart-request-spec) we need to follow the order that they suggest to avoid errors. Thanks |
@rtrive We never got a response for a fix, ended up just applying a patch and building the binary ourselves. |
Fixing this properly in k6 is probably not going to happen with the current I don't have an estimate when the new HTTP API is going to land, I've even procrastinated writing the overarching issue for it for around a year now... 😞 Mostly because of the horrific #1007 delays and a bunch of other urgent priorities, but also because we've continued to gather more and more issues with the current API that need fixing. I'll make the issue soon (:tm: :sweat_smile:), but one piece of good news is that we'll likely develop the new HTTP API as an xk6 extension (more info). So, it will take shape gradually and be available for testing much earlier than otherwise, if we tried to make it directly in k6 core. For a workaround until then, we plan to soon (either in k6 v0.30.0 or k6 v0.31.0) add better support for something else the current k6 doesn't deal well with - binary data (#1020). Specifically, we would support passing an |
Hey guys, just a heads-up: multipart requests should be simpler to make now with the |
Thanks for updating the thread @imiric. I'm failing to see how this solves the problem we're encountering here, but maybe I'm missing something. It seems like the FormData library at https://jslib.k6.io/formdata/0.0.1/index.js is only addressing order of files, not payload keys of any other type. For example, we are wanting something like:
Where everything but 'file' is a string.
Since the order of the |
Hi @landon-martin , import http from 'k6/http';
import { FormData } from 'https://jslib.k6.io/formdata/0.0.1/index.js';
const fileData = open('file.dat', 'b');
export default function() {
const uploadData = {
key: "1234161",
fields:{
bucket: "mybucket",
'X-Amz-Algorithm': "something",
},
}
const fd = new FormData();
fd.append('key', {data:uploadData.key});
fd.append('bucket', {data:uploadData.fields.bucket});
fd.append('X-Amz-Algorithm', {data:uploadData.fields['X-Amz-Algorithm'], content_type: "my custome type"});
//fd.append('X-Amz-Credential', uploadData.fields['X-Amz-Credential']);
//fd.append('X-Amz-Date', uploadData.fields['X-Amz-Date']);
//fd.append('Policy', uploadData.fields.Policy);
//fd.append('X-Amz-Signature', uploadData.fields['X-Amz-Signature']);
fd.append('file', http.file(fileData, "something", 'application/mine'));
const params = {
headers: {'Content-Type': `multipart/form-data; boundary=${fd.boundary}`},
};
const res = http.post("https://httpbin.test.k6.io/post", fd.body(), params);
console.log(JSON.stringify(res, null, " "));
} Does work as you will expect. But I see how this isn't obvious from the docs as the only code that shows that is in the jslib tests and that still uses all the fields, so in practice, only the documentation inside the polyfill is mentioning they are optional. |
Hi @mstoykov, You're right, I was expecting it to act more like the web API class that this is mimicking, for example: https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects Thanks for the tip, quickly changed over to that and it worked as expected 👍 |
The FormData polyfill resolves this issue, and is the recommended way to submit multipart requests, so I'll close this issue. In the future, we might integrate FormData into k6 as part of the new HTTP API (initial design document), but we haven't decided on the details yet. |
As an intermediate step to testing an API flows I need to upload to s3 using their POST API:
https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html
The api docs dictate that file MUST be the last field in the form data.
I have setup my payload like this:
Some background:
API1 (not shown here) is run to get all the payload values and the s3Url.
We use that response to actually upload to s3 (http.post(s3Url, s3Payload))
When S3 is done we call some more internal APIs to handle the newly uploaded file
We want to do this process as part of a test but when when try to post to the S3 api we get the following error:
"<Error><Code>InvalidArgument</Code><Message>Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.</Message><ArgumentName>key</ArgumentName><ArgumentValue></ArgumentValue><RequestId>B235CB11F0A582FA</RequestId><HostId></HostId></Error>"
or similar errors complaining about other fields being missing.
I have tested this in postman by changing the field order and anything below the file prop is reported as missing. In postman once putting file as the last parameter using all the same values passed through the K6 test, it succeeds.
In Node.js as of ES2015 this doesn't seem to happen as objects retain their keyed order (numeric keys in order added, followed by string keys in order added, followed by Symbol keys in order added). I'm guessing this is not the case once things end up down at the golang level.
Environment
Expected Behavior
Form data send in the order specified in test
Actual Behavior
Form data sent out of order and api call fails.
The text was updated successfully, but these errors were encountered: