Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
  • Help Room /
avatar image
3
Question by austingraham · May 16, 2017 at 03:52 PM · serverwwwwebrequestwwwformform

UnityWebRequest.Post() has multipart/form-data strangely formatted when sending request

A note before beginning, I know that WWW and WWWForm work, but since Unity is trying to get people off of that system, I would like to know why their new system breaks in my case. I'm using Unity 5.6

Goal: To take a screenshot and send it to a server

Problem: UnityWebRequest sends the data in a large data chunk rather than the correct multipart data even when it has the correct content-type header and boundary.

The server code was tested using tools such as postman to check it was working (server code isn't mine but expects multipart form-data with a 'file' field and an image as it's data) and the Unity and Postman requests were compared to identify the issue

This will be a long post, please bear with me as I explain what all I tried and how it broke:

 //get screenshot and start coroutine to make request
 //using dataPath so it is in assets folder for now.
 Application.CaptureScreenshot(Application.dataPath + "/screenshot.png");
 StartCoroutine(sendImage());

and the coroutine: (note: this code resulted in a 500 error from the server)

 IEnumerator sendImage()
     {
         /*validation to find file*/
         //read in file to bytes
         byte[] img = File.ReadAllBytes(Application.dataPath + "/screenshot.png");
 
         //create multipart list
         List<IMultipartFormSection> requestData = new List<IMultipartFormSection>();
         requestData.Add( new MultipartFormFileSection("file", img, 
         "screenshot.png", "image/png"));

         //url3 is a string url defined earlier
         UnityWebRequest request = UnityWebRequest.Post(url3, requestData);
         request.SetRequestHeader("Content-Type", "multipart/form-data");
         yield return request.Send();
         print("request completed with code: " + request.responseCode);
  
         if(request.isError) {
             print("Error: " + request.error);
         }
         else {
             print("Request Response: " + request.downloadHandler.text);
         }
     }

So that was the format giving us problems. Here is a screenshot from wireshark of what that request looked like. alt text

For comparison, here is a good request made using Postman: alt text

as you can see, the valid request breaks up the multipart form into it's sections, where as the UnityWebRequest.Post() was not. Does anyone know why this is? The best guess a few others and myself could come up with was the fact that the .Post() forces the data to be URLEncoded via WWWTranscoder.URLEncode prior to transmission per the docs.

Can someone verify that this is indeed the case? I only saw 1 post that mentioned that after hours of searching and trying to fix this.

I will post an answer below with all of the methods we attempted/found to try and fix this so they are all in one place (seriously I had way too many tabs open with different posts trying to find solutions). We ended up using a hybrid solution using UnityWebRequest.Post() but using WWWForm data instead of the iMultipartFormSection class, but I'd love to hear if there is a better way.

working-postman.png (256.0 kB)
broken-unity.png (175.0 kB)
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

5 Replies

· Add your reply
  • Sort: 
avatar image
5

Answer by michaelneil · Nov 28, 2017 at 01:20 AM

Thank you for posting this information; it got me most of the way to making multipart uploads work with Unity Web Request. You saw that the termination boundary wasn't added but, in your example:

  1. I think you formatted the closing boundary incorrectly https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html.

  2. The quotes around the boundary are not in the spec.

I was able to get it working once I appended that boundary to my body byte[].

 // read a file and add it to the form
 List<IMultipartFormSection> form= new List<IMultipartFormSection>
 {
    new MultipartFormFileSection("file", IO.File.ReadAllBytes("somefile.gif"), "filename.gif", "image/gif")
 };
 // generate a boundary then convert the form to byte[]
 byte[] boundary = UnityWebRequest.GenerateBoundary();
 byte[] formSections = UnityWebRequest.SerializeFormSections(form, boundary);
 // my termination string consisting of CRLF--{boundary}--
 byte[] terminate = Encoding.UTF8.GetBytes(String.Concat("\r\n--", Encoding.UTF8.GetString(boundary), "--"));
 // Make my complete body from the two byte arrays
 byte[] body = new byte[formSections.Length + terminate.Length];
 Buffer.BlockCopy(formSections, 0, body, 0, formSections.Length);
 Buffer.BlockCopy(terminate, 0, body, formSections.Length, terminate.Length);
 // Set the content type - NO QUOTES around the boundary
 string contentType = String.Concat("multipart/form-data; boundary=", Encoding.UTF8.GetString(boundary));
 // Make my request object and add the raw body. Set anything else you need here
 UnityWebRequest wr = new UnityWebRequest();
 UploadHandler uploader = new UploadHandlerRaw(body);
 uploader.contentType = contentType;
 wr.uploadHandler = uploader;


Comment
Add comment · Show 8 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image QuincyC · Nov 30, 2018 at 10:25 PM 0
Share

This works great, however the json file I'm uploading ends up being quoted with escape characters in it, despite adding it as a byte array. Any idea why this would be the case?

avatar image jdnichollsc · Jan 21, 2019 at 05:31 AM 0
Share

@QuincyC did you find any solution?

avatar image QuincyC jdnichollsc · Jan 21, 2019 at 03:17 PM 1
Share

Yes, though it's not pretty. If you don't need WebGL support I'd recommend using HTTPClient (https://assetstore.unity.com/packages/tools/network/http-client-79343)

I had to build out the multipart request semi-manually to work around bugs in UnityWebRequest.

     private byte[] Get$$anonymous$$ultipartBody(byte[] bodyRaw, string fieldName, string filename, out string contentTypeString)
     {
         // generate a boundary then convert the form to byte[]
         byte[] boundary = UnityWebRequest.GenerateBoundary();
         contentTypeString = String.Concat("multipart/form-data; boundary=", Encoding.UTF8.GetString(boundary));
 
         // read a file and add it to the form
         List<I$$anonymous$$ultipartFormSection> form = new List<I$$anonymous$$ultipartFormSection>
         {
             new $$anonymous$$ultipartFormFileSection(fieldName, bodyRaw, filename, contentTypeString )
         };
                 
         byte[] formSections = UnityWebRequest.SerializeFormSections(form, boundary);
 
         Debug.Log("formSections="+Encoding.UTF8.GetString(formSections));
                 
 
         // my ter$$anonymous$$ation string consisting of CRLF--{boundary}--
         byte[] ter$$anonymous$$ate = Encoding.UTF8.GetBytes(String.Concat("\r\n--", Encoding.UTF8.GetString(boundary), "--"));
         // $$anonymous$$ake my complete body from the two byte arrays
         byte[] body = new byte[formSections.Length + ter$$anonymous$$ate.Length];
         Buffer.BlockCopy(formSections, 0, body, 0, formSections.Length);
         Buffer.BlockCopy(ter$$anonymous$$ate, 0, body, formSections.Length, ter$$anonymous$$ate.Length);
 
         return body;
     }

This should do the trick

avatar image QuincyC QuincyC · Jan 21, 2019 at 03:18 PM 1
Share

then to use the function (couldn't include both in one post for some reason)

     private enum ContentType { Json, $$anonymous$$ultipart };
     UnityWebRequest CreatePostRequest(string url, string json, ContentType contentType = ContentType.Json)
     {
         Debug.Log("CreatePostRequest("+url+", "+ json + ", "+contentType);
 
         byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
 
         UnityWebRequest www = new UnityWebRequest(url, "POST");
         www.uploadHandler = (UploadHandler)new UploadHandlerRaw(bodyRaw);
         www.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();        
 
         string contentTypeString = "application/json";
         switch (contentType)
         {
             case ContentType.Json:
                 contentTypeString = "application/json";
                 break;
             case ContentType.$$anonymous$$ultipart:
                 byte[] body = Get$$anonymous$$ultipartBody(bodyRaw, Const.Web.filename$$anonymous$$ainGraph, Const.Web.filename$$anonymous$$ainGraph, out contentTypeString);
                 
                 // $$anonymous$$ake my request object and add the raw body. Set anything else you need here
                 www.uploadHandler = new UploadHandlerRaw(body);
                 www.uploadHandler.contentType = contentTypeString;
                 break;
         }
 
         www.SetRequestHeader("Content-Type", contentTypeString);
         www.SetRequestHeader("Authorization", GetAuthorizationHeader());
         return www;
     }

Show more comments
Show more comments
avatar image LennyJoy · May 09, 2019 at 05:09 AM 0
Share

Is it working?I'm also confused about boundary.

avatar image LiefLayer · Apr 10, 2021 at 03:09 PM 0
Share

It worked great for google drive api too. Thanks a lot!

avatar image
3

Answer by austingraham · May 16, 2017 at 04:30 PM

So I found several methods in an attempt to fix this problem. Here are the top ones and why they didn't work when I tried them.

Use .Put() then change the method to POST: so this basically bypasses the encoding problem of .Post() which is especially useful if you are sending json strings as those get butchered through .Post() if using the constructor for string data. Here is how using a .Put() looks:

 //.Put() takes in string url and either byte[] or string data
 UnityWebRequest request = UnityWebRequest.Put(url3, requestData);
 request.method = "POST";
 request.Send()

So this works for JSON strings (we tested and confirmed) but when we tried sending a byte array of our fields we never actually could find/confirm the request coming out was a post coming out (no wireshark entry) and the server crashed with a 500. So for JSON this is a decent workaround but not for our problem.

Create request from scratch: So there was another suggestion to craft the request from scratch. So we tried that. The code looks like this:

 //generate a unique boundary
 byte[] boundary = UnityWebRequest.GenerateBoundary();
 //serialize form fields into byte[] => requires a bounday to put in between fields
 byte[] formSections = UnityWebRequest.SerializeFormSections(requestData, boundary);
     
 UnityWebRequest request = new UnityWebRequest(url3);
 request.uploadHandler = new UploadHandlerRaw(formSections);
 
 /*note: adding the boundary to the uploadHandler.contentType is essential! It won't have the boundary otherwise and won't know how to split up the fields; also it must be encoded to a string otherwise it just says prints the type, 'byte[]', which is not what you want*/
 
 request.uploadHandler.contentType = "multipart/form-data; boundary=\"" + System.Text.Encoding.UTF8.GetString(boundary) + "\"";
 request.downloadHandler = new DownloadHandlerBuffer();
 request.method = "POST";
 request.SetRequestHeader("Content-Type", "multipart/form-data");
 yield return request.Send();

So this was actually pretty promising and almost worked for us. However, there were still problems. One was that the Content-Disposition of each field was empty. Not sure if this actually negatively affected anything, but since there is no field in the request object where I can manually set it, I could not change it. Second, the serialized fields added the boundary before each field but didn't add a trailing boundary so there was no flag for where the field ended (in our use case of 1 field). I tried manually tacking one onto the end by manually creating a new byte[] and copying the boundary onto the end of that (like so):

 byte[] boundary = UnityWebRequest.GenerateBoundary();
 byte[] formSections = UnityWebRequest.SerializeFormSections(requestData, boundary);
 byte[] reqData = new byte[formSections.Length + boundary.Length];
 System.Buffer.BlockCopy(formSections, 0, reqData, 0, formSections.Length);
 System.Buffer.BlockCopy(boundary, 0, reqData, formSections.Length, boundary.Length);
 /*and use reqData and not formSections in the request*/

I actually got really close, but the end was not formatted like the valid response. The valid one had 2 '.' icons before the ending boundary in wireshark while this attempt did not have those marks. I probably could get this to work with more finagling, but by this point I was irritated and tired so I went with our hybrid approach.

Hybrid: This uses the WWWForm class but is a legacy method.

 WWWForm form = new WWWForm();
 form.AddBinaryData("file", img, "screenshot.png", "image/png");
 UnityWebRequest request = UnityWebRequest.Post(url3, form);
 yield return request.Send();
 print("request completed with code: " + request.responseCode);
 if(request.isError) {
     print("Error: " + request.error);
 }
 else {
     print("Request Response: " + request.downloadHandler.text);
  }

and that last one worked as intended. So basically I wrote this question and this answer for 2 reasons:

  • Was I correct in my diagnosis of the problem and is there a better solution that tries to use the old methods as little as possible

  • If anyone esle experienced the same issues, here is all of the stuff I found and tried so it might save other people the trouble I had (also the 5 browser windows with 20 tabs open each of unity forum questions and other similat sites)

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image developer2017 · Jul 11, 2017 at 07:32 AM 0
Share

Legacy method works. But not strea$$anonymous$$g capabilities in there. Extra " around charset "utf-8" ins$$anonymous$$d of utf-8 using legacy WWWForm. Seems like Unity lacks unit testing.

avatar image Alber_h · Sep 04, 2018 at 03:12 PM 0
Share

The Hybrid method worked as expected. Thank you very much!

avatar image LennyJoy · May 09, 2019 at 03:47 AM 0
Share

How should I make a post with a JSON data and an image using UnityWebRequest?There is a question that I get a raw data of an image,but I found that if I convert the raw data to string,I got some messy codes...And if I make a post with them using HttpWebRequest would be fine which I think it's weird...

avatar image
1

Answer by IXNet · Jul 06, 2017 at 01:05 AM

I found unity adds quotes around the boundary in the content-type header which stumps some non Apache setups.

In addition unity adds extra newlines before the first boundary which again can cause some problems in some rare setups.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by Sea-Dragon · Nov 12, 2017 at 04:43 AM

That's why I did my own asset EasyWeb :) to support streaming file to server with low memory footprint

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by MaxxRafen · Feb 14, 2018 at 04:44 PM

It's worth noting that even during errors, the response from the server can be read from request.downloadHandler.text and may contain useful data about the issue.


If the error is related to Content-Length, try setting:

request.chunkedTransfer=false;

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

116 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

I have some deprecated errors with WWW someone knows how to convert this lines and use UnityWebRequest instead of WWW 2 Answers

UnityWebRequest returns an empty string 1 Answer

UnityWebRequest.downloadProgress keep coming back 1 Answer

www request Error unity 2018 0 Answers

How can resolve URL with caracter like & and other ? 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges