SaltyCrane Blog — Notes on JavaScript and web development

Example POSTing binary data using pycurl

Since urllib2 doesn't easily support POSTing binary data, I used pycurl. It's less fun to use. I think I remember reading that Requests supports this, but someone said something about speed a while back. It may warrant a second look. I couldn't figure out if httplib2 supports POSTing binary data.

Here's how I would do it with curl:

$ curl -v --data-binary @blank-contact-photo.jpg -H 'Content-Type: image/jpeg' 'http://localhost:8000' 
* About to connect() to localhost port 8000 (#0)
*   Trying 127.0.0.1... connected
> POST / HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8000
> Accept: */*
> Content-Type: image/jpeg
> Content-Length: 1335
> Expect: 100-continue
> 
* Empty reply from server
* Connection #0 to host localhost left intact
curl: (52) Empty reply from server
* Closing connection #0

Here's the headers received by my simulated server:

['User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3\r\n',
 'Host: localhost:8000\r\n',
 'Accept: */*\r\n',
 'Content-Type: image/jpeg\r\n',
 'Content-Length: 1335\r\n',
 'Expect: 100-continue\r\n']

Here's what I tried. Method 4 is what worked.

import StringIO
import os.path
import pycurl

def main():
    """
    http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
    http://code.activestate.com/recipes/576422-python-http-post-binary-file-upload-with-pycurl/
    http://pycurl.cvs.sourceforge.net/pycurl/pycurl/tests/test_post2.py?view=markup
    """
    method = 4
    filename = 'blank-contact-photo.jpg'
    url = 'http://localhost:8000'

    c = pycurl.Curl()
    c.setopt(pycurl.VERBOSE, 1)
    c.setopt(pycurl.URL, url)
    fout = StringIO.StringIO()
    c.setopt(pycurl.WRITEFUNCTION, fout.write)

    if method == 1:
        c.setopt(pycurl.HTTPPOST, [
                ("file1",
                 (c.FORM_FILE, filename))])
        c.setopt(pycurl.HTTPHEADER, ['Content-Type: image/jpeg'])
    elif method == 2:
        c.setopt(c.HTTPPOST, [
                ("uploadfieldname",
                 (c.FORM_FILE, filename,
                  c.FORM_CONTENTTYPE, "image/jpeg"))])
    elif method == 3:
        c.setopt(pycurl.UPLOAD, 1)
        c.setopt(pycurl.READFUNCTION, open(filename, 'rb').read)
        filesize = os.path.getsize(filename)
        c.setopt(pycurl.INFILESIZE, filesize)
    elif method == 4:
        c.setopt(pycurl.POST, 1)
        c.setopt(pycurl.HTTPHEADER, [
                'Content-Type: image/jpeg'])

        filesize = os.path.getsize(filename)
        c.setopt(pycurl.POSTFIELDSIZE, filesize)
        fin = open(filename, 'rb')
        c.setopt(pycurl.READFUNCTION, fin.read)

    c.perform()
    response_code = c.getinfo(pycurl.RESPONSE_CODE)
    response_data = fout.getvalue()
    print response_code
    print response_data
    c.close()


if __name__ == '__main__':
    main()

Here's the code for my simulated server in case you're curious:

import BaseHTTPServer
import SocketServer


class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_POST(self):
        # from debugtools import pvar; pvar('self')
        # from debugtools import pvar; pvar('dir(self)')
        # from debugtools import pvar; pvar('vars(self)')
        # from debugtools import pvar; pvar('self.request')
        # from debugtools import pvar; pvar('self.rfile')
        # from debugtools import pvar; pvar('self.headers')
        # from debugtools import pvar; pvar('self.headers.headers')
        print self.headers.headers
        return 'asdfasdf'

PORT = 8000

Handler = MyHandler

SocketServer.TCPServer.allow_reuse_address = True
httpd = SocketServer.TCPServer(("", PORT), Handler)

print "serving at port", PORT
try:
    httpd.serve_forever()
except KeyboardInterrupt:
    httpd.shutdown()

Comments