HTTP PUT with multipart/form-data using pycurl

Let’s suppose you have a REST interface to talk to, and there’s a PUT request you want to make, sending data over using the multipart/form-data encoding (as opposed to application/x-www-form-urlencoded). If you’re using Python and pycurl, you’ll find out that if you try to combine setopt(pycurl.PUT, 1) with setopt(pycurl.HTTPPOST, [ (key1, val1), … ]), it doesn’t work. You could try to use setopt(pycurl.POSTFIELDS, “…”), but you’d have to handle encoding to multipart/form-data by hand, or use a third party library such as poster. But in any case it looks like more hassle than it should. The pycurl.HTTPPOST option can already do what’s needed, it’s just that it implies the POST method, while you want to use PUT.

A solution came to me when reading a thread on the curl-with-python mailing list. I knew I could already do what I needed using the command line utility, like this:

curl -X PUT -F 'fieldname=@filename.json' http://localhost:8000/

If you add an option like --libcurl foo.c to such call, you’ll get a C program which does what your command line invocation would do. This revealed, that “-X PUT” did not translate into setopt(pycurl.PUT, 1), but into setopt(pycurl.CUSTOMREQUEST, “PUT”). It might look like a subtle difference, but the latter does what I wanted, while the former doesn’t. A minimal working example would look like this:

import pycurl

c = pycurl.Curl()
c.setopt(pycurl.URL, "http://localhost:8000")
c.setopt(pycurl.HTTPPOST, [('foo', 'bar')])
c.setopt(pycurl.CUSTOMREQUEST, "PUT")

If you run “nc -l 8000” and run the above code, you’ll see:

PUT / HTTP/1.1
User-Agent: PycURL/7.26.0
Host: localhost:8000
Accept: */*
Content-Length: 141
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------2def70e0b37a

Content-Disposition: form-data; name="foo"


…which is exactly what I wanted.

Author: automatthias

You won't believe what a skeptic I am.