'PUT' file with URL output stream

Hi all,

I’m trying to upload a file to AWS S3. S3 requires that the method used to upload the file be PUT, not POST. I don’t think the URL createOutputStream() does PUT.

Here’s the code I have so far:

auto task = URL(uploadUrl).createOutputStream();
auto fileStream = File(file).createInputStream();
while (!fileStream->isExhausted())
{
    task->writeByte(fileStream->readByte());
}
task->flush();

task just returns empty. Any ideas on how I can go about uploading the file?

I think you’ve come at this from the wrong conceptual angle.

createOutputStream is definitely not the right method as it’s only for certain URIs on Android.

You want a URL that you set up accordingly:

then use the extra parameters from createInputStream to set up using PUT as the HTTP verb used when making the request to AWS : https://docs.juce.com/master/classURL.html#a641aa0282e20f7811bd7ef04d8647a11

I always like to think of HTTP to always be “requests” (because they are!), even if you’re POSTing or PUTting data, you will almost always receive a response back from the server beyond a simple 200 OK response header, so the concept is that you need to open an InputStream from the server to get the response based on the request you made in which you sent a file to be uploaded.

1 Like

Thank you for the reply!

I didn’t realize that output stream was only for Android. The docs make is sound like it’s for any kind of “local files”, Android or not. But I’ve got it working now! For any wondering, here’s my final code:

MemoryBlock fileData;
File(file).loadFileAsData(fileData);
URL(uploadUrl).withPOSTData(fileData).createInputStream(false, nullptr, nullptr,"Content-Type: image/png\n", -1, nullptr, nullptr, 5, "PUT");

Now I’m need to use the OpenStreamProgressCallback, any idea how to do that?

You may be correct, the sentence could be taken either way.

Untested, but this should work:

MemoryBlock fileData;
File(file).loadFileAsData(fileData);
auto progressCallback = [](void* context, int bytesSent, int totalBytes) {
    // context is "this" parameter passed after the lambda to createInputStream

    // return false if you want to interrupt the upload
    return true;
};
URL(uploadUrl).withPOSTData(fileData).createInputStream(false, progressCallback, this, "Content-Type: image/png\n", -1, nullptr, nullptr, 5, "PUT");
1 Like

Nice, that works great! I was trying to use a lambda but I was doing [=] instead of [] and it wasn’t working… :roll_eyes:

Thank you for all your help @asmilion!

1 Like

Now I’m having another problem (same topic though? :thinking:). The request is being made, I’m getting status code 200 and I can see a file in S3. The problem is it’s 0 bytes; no data is being uploaded to S3.

As you can see in the code I posted, I have doPostLikeRequest set to false. I tried setting it to true, but that returns an “Access Denied” error from S3. I used Fiddler to see what was actually being sent, and it looks like all the parameters in the upload URL are being sent in the body, rather than the URL itself. What?! How can I prevent this?

(I also tried withDataToUpload, but the data is not a parameter, so that just didn’t work. I also tried adding a Content-Type header, which didn’t work either.)

There is an open bug about that since some months now that has been silently ignored by the juce team. See URL createInputStream with post data, wrongly put get parameters in the post body

1 Like

Great. I see that you created your own pull request that fixed it, nice job! I’ll probably implement those changes into my JUCE library. I won’t be updating it often so it shouldn’t be too much of an annoyance. :crossed_fingers:

I’ll be bumping that topic!