Tuesday 27 October 2015

Is Multipart request complicated? Think again.

                       Recently working on a simple Android project, I came across a typical requirement of uploading the user image and his profile data to server. Being a java REST API developer, I thought all I need is an API to accept multipart/form-data request and an android client code that makes a request of similar kind. Being a newbie in both technology I did not had even the slightest idea of how tough the journey would be which i was about to begin. The two days struggle to develop both client and server side code taught me a little about the multipart request and I believe sharing the knowledge is the least I can do to help newbies like me.

                      The first step to solve any problem is to understand it. So lets try and understand what multipart request is all about.

Multipart Request: Is nothing but a simple HTTP request that client constructs and sends it to server to upload information like image,video,text file ,JSON,XML and even the simple strings, or sometimes the combination of many of the above mime types listed. (At the end of blog u'll learn how to make a multipart request to carry multiple mime types data in a single request body).
                       As the name suggests whenever a client makes a request with one or more sets of data combined in a single request body, request header must contain "multipart" as Content-type.Multipart request has a quite a few sub types like Multipart/mixed, Multipart/alternative, Multipart/digest, and Multipart/form-data. Though these sub types differ from each others in their semantics they all follow the common syntax.

Multipart/form-data: Whenever a client sends a large amount of information in the form of binary data or text containing non ASCII characters, the Content-type application/x-www-form-urlencoded becomes inefficient. To deal with such requests client should specify Content-type as "Multipart/form-data".
                     Because Multipart/form-data is just another sub type of Multipart request, it follows the syntax of multipart requests. Lets consider a simple example of multipart/form-data and analyse its syntax for deeper understanding.

  1. ------WebKitFormBoundaryQHJL2hsKnlU26Mm3 Content-Disposition: form-data; name="profilePic"; filename="66.jpg" Content-Type: application/octet-stream //your image data appears here ------WebKitFormBoundaryQHJL2hsKnlU26Mm3 Content-Disposition: form-data; name="testingName" Myfile.jpg ------WebKitFormBoundaryQHJL2hsKnlU26Mm3--

Looks complicated? No its not. Lets understand it line by line.
As we can see there are two data sets (look for content-disposition field). Each field is insulated with some complex sequence of characters and there is white space between each data sets. Look again those complex sequence of characters are not changing in any of the data sets. Coincidence?? or planned??

Yes you are right dear friend that wasn't a coincidence. This is the request payload generated by our loving Chrome web browser and every single line in it has a significance of its own.

First Line :
------WebKitFormBoundaryQHJL2hsKnlU26Mm3
                   
                    The above line starts with hyphens and if you carefully count it, it has 6 hyphens. The first two hyphens indicates the beginning of request body. The initial "--" are very important for the processing agent to understand and identify the beginning of data set. 

                    Now you might be wondering  about remaining 4 hyphens and the string                     "WebKitFormBoundaryQHJL2hsKnlU26Mm3" which is repeating in each data set. This "----WebKitFormBoundaryQHJL2hsKnlU26Mm3" is called boundary. Boundary is any sequence of character decided by client constructing the request. This character sequence is specified in the request header as boundary to the server/processing agent. Boundary characters should not be the part of any data set is the only constraint here. Below is the request header sent by browser to the above request.

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cache-Control: max-age=0
Connection:keep-alive
Content-Length:65751
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryQHJL2hsKnlU26Mm3
Cookie:JSESSIONID=BEABC4dDFG8FBEE6849E5524C7BA1FD15
Host:127.0.0.1:8080
Origin:http://127.0.0.1:8080
Referer:http://127.0.0.1:8080/
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36
                        Look for "boundary=----WebKitFormBoundaryQHJL2hsKnlU26Mm3" this is where processing agent/server gets to know the boundary selected by the client and uses the same to identify and access the body parts.

Note: Body parts of the request are passed to the server/processing agent in the same order they appear in the request body. This is very essential when client sends multiple data sets of different MIME types in a single request body and server should access it.

Second line :
Content-Disposition: form-data; name="profilePic"; filename="66.jpg"
Content-Type: application/octet-stream

                  Each data part in the request body should contain the header "Content-Disposition" and its value should be "form-data".

                  The above request contains two body parts belonging to two different MIME types. First part is file (image/jpeg) and the second part is plain string (text/palin). Body parts can optionally inform the Content-type they are carrying. In case of files Content-Type is pplication/octet-stream. For plain string Content-type would be text/plain

                   Whenever a body part contains a file it should try to provide the name of the file in filename attribute. Eg: in the above mentioned line we can see " filename="66.jpg".

                   Each data part should contain the "name" attribute. It is the name of the HTML element present in the form. Processing agent/server api's will try to access the data in each data part using these name attributes similar to key value pair. That is when the above request reaches server, server will try to look for the field called "profilePic" in the request body and once found it reads the data in it.

Third Line :
                    Is a blank space. Just to indicate the processing agent that the actual data is about to begin. This is very essential as without the blank space and a new line character data will be considered as part of the header and attribute values of body parts and will not be accessed by the server.


Fourth Line :
                  This is where your actual data appears, may it be a file or text. In the above example of request body we can see that first part contains the image data and the second part contains just a string "Myfile.jpg".

And thats it. Pattern repeats for each body part. Except for the last part in the request body.

Last Line :
------WebKitFormBoundaryQHJL2hsKnlU26Mm3--

                  Following the same pattern the last body part is encapsulated by two hyphens and boundary characters. But wait, look carefully, There are two hyphens at the end as well. Is that a typo??? Nope. The last two "--" indicates the end of request body.


                 Phew!!! Believe me we are almost winning the war against multipart request. Enough of talking lets Code :)

The above request body was generated on the submission of a simple HTML file.

Note : The only important point to note here is the name of <input> tag name attribute.

                  Java REST API code to access image file and string is pretty straight forward too.

                  The code is very much simple and self explanatory. In order to access the multipart/form-data request body parts use FormDataParam. "testingName" and "profilepic" are the names of the HTML elements in form.

                  Android client code to achieve the same is much easier than you thought.


Few important points to notice here are :
  1. "String boundary = "*****" : Here client which is android code is deciding the boundary value to be '*'. As you already know client can decide upon the boundary value and it has to inform the server the same.
  2. "httpUrlConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary)" :  Once the client creates httpUrlConnection it prepares the header for the request. Request header should contain the boundary value decided by client.
  3. Once the code opens up the DataOutputStream for server socket it writes the request body in the same order as we discussed a little ago."\r\n" is used to create a new line. As we noticed in request body after each line in the request body there must be a new line. 

All that android code is trying to do is to create a replica of the request body provided above by entering line by line content and adding new line characters and blank spaces. Once the client sends the request body in a proper format request will be processed by the REST API and will respond with proper response.

Hope this blog was helpful. I'll look forward for your valuable comments. Happy coding :)









19 comments:

  1. Great work bro. Thanks for sharing :)

    ReplyDelete
  2. Hello Sandeep,

    Nice article, do you have the php code and java code in github, can you share with me would like to understand further on this.

    ReplyDelete
  3. Hi Sethu, Unfortunately I dont do PHP coding anymore. So I dont have any code snippet. Information I have provided above is platform independent though I have attached the JAVA code. You can follow the tutorial and try coding similar to the code provided above.

    ReplyDelete
  4. hi Sandeep .. really good work but still i am not clear i mean can you tell how to do this all in java , i am trying to post some text and audio file .. thanks

    ReplyDelete
  5. great tutorial............thanks

    ReplyDelete
    Replies
    1. Thank you for stopping by and taking time to appreciate :)

      Delete
  6. You just made my day. Thanks a lot mate!

    ReplyDelete
  7. Thank you a lot. It really helped me!

    ReplyDelete
  8. FOr me, it didn't work until I added filename in Content-Disposition next file parameter.
    mRequest.writeBytes("Content-Disposition: form-data; name=\"file\";filename=" + file.getName() + ";" + crlf);

    ReplyDelete
    Replies
    1. Am glad u solved it :) Thats strange that I never had any such issue. Ill look into it :) thank you :)

      Delete
  9. "Great blog created by you. I read your blog, its best and useful information. You have done a great work. Super blogging and keep it up.php jobs in hyderabad.
    "

    ReplyDelete
    Replies
    1. My pleasure :) Thank you for stopping by and taking time to appreciate the work :) Am glad I could be of some help

      Delete
  10. Is this code efficient if the image size is 100 MB on disk? Would 100MB not come in RAM via Bitmap object?

    ReplyDelete
  11. Nice explanation on Multi part HTTP request. Thank you.

    ReplyDelete
  12. Tbilisi, Georgia, USA - Tbilisi Art
    Tbilisi is titanium framing hammer situated on the west bank dei titanium exhaust wrap of ford fusion titanium 2019 the titan metal Great River in titanium anodizing Tbilisi, Georgia, near the airport. Tbilisi is on a river that connects the

    ReplyDelete