RealityForge.org: Upload a file via POST with Net::HTTP /articles/2006/03/02/upload-a-file-via-post-with-net-http en-us 40 A little short for a storm trooper Upload a file via POST with Net::HTTP <p>To upload a file to a website I needed to supply the data with a content type of &#8220;multipart/form-data&#8221;. The <a href="http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/classes/Net/HTTP.html">Net:HTTP</a> API does not offer any such functionality, it just accepts raw content data. So I needed to roll my own.</p> <p>The &#8220;multipart/form-data&#8221; content type consists of a number of secions separated by <code>--BOUNDARY\r\n</code> and terminated by <code>BOUNDARY--\r\n</code> where <code>BOUNDARY</code> is a string that does not appear in the content of any of the data transmitted to the server.</p> <p>Each section represents a form field and contains a number of headers, a <code>\r\n</code>, the content and finishes with a <code>\r\n</code>. Normal form fields look like</p> <div class="typocode"><pre><code class="typocode_default ">Content-Disposition: form-data; name=&quot;mykey&quot; mydata</code></pre></div> <p>while file fields must include a few more headers.</p> <div class="typocode"><pre><code class="typocode_default ">Content-Disposition: form-data; name=&quot;mykey&quot;; filename=&quot;filename&quot; Content-Transfer-Encoding: binary Content-Type: text/plain DATADATADATADATADATADATADATA...</code></pre></div> <p>To construct the parameters in ruby I use the following code;</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">def </span><span class="method">text_to_multipart</span><span class="punct">(</span><span class="ident">key</span><span class="punct">,</span><span class="ident">value</span><span class="punct">)</span> <span class="keyword">return</span> <span class="punct">&quot;</span><span class="string">Content-Disposition: form-data; name=<span class="escape">\&quot;</span><span class="expr">#{CGI::escape(key)}</span><span class="escape">\&quot;\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string"><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string"><span class="expr">#{value}</span><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="keyword">end</span> <span class="keyword">def </span><span class="method">file_to_multipart</span><span class="punct">(</span><span class="ident">key</span><span class="punct">,</span><span class="ident">filename</span><span class="punct">,</span><span class="ident">mime_type</span><span class="punct">,</span><span class="ident">content</span><span class="punct">)</span> <span class="keyword">return</span> <span class="punct">&quot;</span><span class="string">Content-Disposition: form-data; name=<span class="escape">\&quot;</span><span class="expr">#{CGI::escape(key)}</span><span class="escape">\&quot;</span>; filename=<span class="escape">\&quot;</span><span class="expr">#{filename}</span><span class="escape">\&quot;\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string">Content-Transfer-Encoding: binary<span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string">Content-Type: <span class="expr">#{mime_type}</span><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string"><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string"><span class="expr">#{content}</span><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="keyword">end</span></code></pre></div> <p>To put it all together you need to join the parameters with boundary separators between each section. This can be done via</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">boundary</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">349832898984244898448024464570528145</span><span class="punct">'</span> <span class="ident">query</span> <span class="punct">=</span> <span class="ident">params</span><span class="punct">.</span><span class="ident">collect</span> <span class="punct">{|</span><span class="ident">p</span><span class="punct">|</span> <span class="punct">'</span><span class="string">--</span><span class="punct">'</span> <span class="punct">+</span> <span class="ident">boundary</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string"><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="ident">p</span><span class="punct">}.</span><span class="ident">join</span><span class="punct">('</span><span class="string"></span><span class="punct">')</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string">--</span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="ident">boundary</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string">--<span class="escape">\r\n</span></span><span class="punct">&quot;</span></code></pre></div> <p>The last thing that needs to be done is to make sure that you set the <span class="caps">HTTP</span> Header <code>Content-type</code> to <code>multipart/form-data; boundary=BOUNDARY</code>.</p> <p>A complete example that I extracted from code that uploads a css file to the w3c validator service is as follows.</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">params</span> <span class="punct">=</span> <span class="punct">[</span> <span class="ident">file_to_multipart</span><span class="punct">('</span><span class="string">file</span><span class="punct">','</span><span class="string">file.css</span><span class="punct">','</span><span class="string">text/css</span><span class="punct">',</span><span class="ident">data</span><span class="punct">),</span> <span class="ident">text_to_multipart</span><span class="punct">('</span><span class="string">warning</span><span class="punct">','</span><span class="string">1</span><span class="punct">'),</span> <span class="ident">text_to_multipart</span><span class="punct">('</span><span class="string">profile</span><span class="punct">','</span><span class="string">css2</span><span class="punct">'),</span> <span class="ident">text_to_multipart</span><span class="punct">('</span><span class="string">usermedium</span><span class="punct">','</span><span class="string">all</span><span class="punct">')</span> <span class="punct">]</span> <span class="ident">boundary</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">349832898984244898448024464570528145</span><span class="punct">'</span> <span class="ident">query</span> <span class="punct">=</span> <span class="ident">params</span><span class="punct">.</span><span class="ident">collect</span> <span class="punct">{|</span><span class="ident">p</span><span class="punct">|</span> <span class="punct">'</span><span class="string">--</span><span class="punct">'</span> <span class="punct">+</span> <span class="ident">boundary</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string"><span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="ident">p</span><span class="punct">}.</span><span class="ident">join</span><span class="punct">('</span><span class="string"></span><span class="punct">')</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string">--</span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="ident">boundary</span> <span class="punct">+</span> <span class="punct">&quot;</span><span class="string">--<span class="escape">\r\n</span></span><span class="punct">&quot;</span> <span class="ident">response</span> <span class="punct">=</span> <span class="ident">http</span><span class="punct">.</span><span class="ident">start</span><span class="punct">('</span><span class="string">jigsaw.w3.org</span><span class="punct">').</span> <span class="ident">post2</span><span class="punct">(&quot;</span><span class="string">/css-validator/validator</span><span class="punct">&quot;,</span> <span class="ident">query</span><span class="punct">,</span> <span class="punct">&quot;</span><span class="string">Content-type</span><span class="punct">&quot;</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">multipart/form-data; boundary=</span><span class="punct">&quot;</span> <span class="punct">+</span> <span class="ident">boundary</span><span class="punct">)</span></code></pre></div> <p>It was a little bit painful to figure out &#8220;multipart/form-data&#8221; via <a href="http://www.ethereal.com/">Ethereal</a> but relatively easy to implement. Hope this helps!</p> <p><strong>Update 3rd of October, 2006:</strong></p> <p>Slight correction supplied by Andrew Willis so that last boundary is <code>'--' + boundary + '--'</code> rather than just <code>boundary + '--'</code>.</p> Thu, 02 Mar 2006 13:57:00 +1100 urn:uuid:e0287477-1342-4b82-af78-b57e938264c7 Peter Donald http://www.realityforge.org/articles/2006/03/02/upload-a-file-via-post-with-net-http Rails ruby rails http://www.realityforge.org/articles/trackback/29