ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: Http client to POST using multipart/form-data
Submitter: Wade Leftwich (other recipes)
Last Updated: 2002/08/23
Version no: 1.0
Category: Web

 

5 stars 5 vote(s)


Description:

A scripted web client that will post data to a site as if from a form using ENCTYPE="multipart/form-data". This is typically used to upload files, but also gets around a server's (e.g. ASP's) limitation on the amount of data that can be accepted via a standard POST (application/x-www-form-urlencoded).

Source: Text Source

import httplib, mimetypes

def post_multipart(host, selector, fields, files):
    """
    Post fields and files to an http host as multipart/form-data.
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return the server's response page.
    """
    content_type, body = encode_multipart_formdata(fields, files)
    h = httplib.HTTP(host)
    h.putrequest('POST', selector)
    h.putheader('content-type', content_type)
    h.putheader('content-length', str(len(body)))
    h.endheaders()
    h.send(body)
    errcode, errmsg, headers = h.getreply()
    return h.file.read()

def encode_multipart_formdata(fields, files):
    """
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (content_type, body) ready for httplib.HTTP instance
    """
    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"' % key)
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
        L.append('Content-Type: %s' % get_content_type(filename))
        L.append('')
        L.append(value)
    L.append('--' + BOUNDARY + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body

def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

Discussion:

At Python 9, Moshe Zadka showed how to create the multipart-mime data using MimeWriter ( http://www.python9.org/p9-zadka.ppt ). His recipe worked just fine for me when I was talking to a Zope server, but triggered a cryptic error message when pointed at an ASP server that was using the COM file upload component from persits.com .

The main problem with MimeWriter is that it does not use '\r\n' for newlines, and the ASP server insisted on this. Other bits of persnicketiness on the part of the ASP included rejection of a mime message that had content-type headers in its regular form fields, and insistence on at least five dashes at the front of the boundary marker.

The function encode_multipart_formdata() shown here takes a more direct approach to creating the mime data, and fairly closely mimics the data sent by Internet Explorer 5.5.



Add comment

Number of comments: 10

python mod, Not specified Not specified, 2004/01/05
I made a wrapper to urllib2.urlopen() in order to support file uploading http://fabien.seisen.org/python/ It uses boundary creation from mimetools and doesn't read the whole file in memory

import urllib2_file
import urllib2

data = {'name': 'value',
        'file':  open('/etc/services')
       }
urllib2.urlopen('http://site.com/script_upload.php', data)

Add comment

using urls, Chris Green, 2004/04/21

import urlparse

def posturl(url, fields, files):
    urlparts = urlparse.urlsplit(url)
    return post_multipart(urlparts[1], urlparts[2], fields,files)
This allows you to specify the form as a url and not worry about host and selector.
Add comment

Update to use HTTPConnection, chris hoke, 2004/09/06
simple update to use HTTPConnection instead of HTTP for the recipe to simplify it and also to use HTTP 1.1. Only replace first function of the recipe:

def post_multipart(host, selector, fields, files):
    content_type, body = encode_multipart_formdata(fields, files)
    h = httplib.HTTPConnection(host)  
    headers = {
        'User-Agent': 'INSERT USERAGENTNAME',
        'Content-Type': content_type
        }
    h.request('POST', selector, body, headers)
    res = h.getresponse()
    return res.status, res.reason, res.read()    
Should work as the original version.
Add comment

extended return..., chris hoke, 2004/09/06
new version does additionally return Status and Reason information. For exact same return of original version replace

    res.status, res.reason, res.read()  
with
    res.read()  

Add comment

With cookie support on Python 2.4, James Jurack, 2006/03/17
Here's a version of your code that supports cookies with python 2.4's urllib2 and cookielib.

import httplib, mimetypes, mimetools, urllib2, cookielib

cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

def post_multipart(host, selector, fields, files):
    """
    Post fields and files to an http host as multipart/form-data.
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return the server's response page.
    """
    content_type, body = encode_multipart_formdata(fields, files)
    headers = {'Content-Type': content_type,
               'Content-Length': str(len(body))}
    r = urllib2.Request("http://%s%s" % (host, selector), body, headers)
    return urllib2.urlopen(r).read()

def encode_multipart_formdata(fields, files):
    """
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (content_type, body) ready for httplib.HTTP instance
    """
    BOUNDARY = mimetools.choose_boundary()
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"' % key)
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
        L.append('Content-Type: %s' % get_content_type(filename))
        L.append('')
        L.append(value)
    L.append('--' + BOUNDARY + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body

def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

Add comment

a less intrusive version using the urllib2 hierarchy, Will Holcomb, 2006/04/06
Here is the same basic idea, but using a class inherited into the BasicHandler hierarchy of urllib2. It has the advantage of leaving all the existing urllib2 functionality intact. Example usage:

import MultipartPostHandler, urllib2, cookielib

cookies = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
                                MultipartPostHandler.MultipartPostHandler)
params = { "username" : "bob", "password" : "riviera",
           "file" : open("filename", "rb") }
opener.open("http://wwww.bobsite.com/upload/", params)
The code is at: http://odin.himinbi.org/MultipartPostHandler.py
Add comment

MultipartPostHandler didn't work for unicode files, Brian Schneider, 2007/08/09
I fixed it by reading in via StringIO class. fix posted here: http://peerit.blogspot.com/2007/07/multipartposthandler-doesnt-work-for.html
Add comment

Script which can be called from the command line, Thomas Guettler, 2007/10/11
This script is based on this recipe and can be called from the shell: http://fabien.seisen.org/python/urllib2_multipart.html
Add comment

Do NOT use this script verbatim, Tim Keating, 2008/02/01
It relies on a deprecated backward-compatibility module in httplib that didn't work at all for me. When I switched to using the more modern HTTPConnection version (described by another commenter above) it worked correctly first time out of the box, so I strongly encourage you to use that instead.
Add comment

how to deal this situation?, Lee June, 2008/07/03
I have a question in my case. I cannot find the solution, can anybody me out? thank.

Even for logout action, I have to supply the 'data' var as following

[code works]
import MultipartPostHandler, cookielib
cookies = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
MultipartPostHandler.MultipartPostHandler)
urllib2.install_opener(opener)
data = urllib.urlencode({'usr':'myname','pwd':'mypwd',})
request = urllib2.Request('http://host/cgi-bin/bbslogin', data)
data=urllib2.open(request).read()
utmpnum, utmpkey, utmpuserid=ReadFromWeb(data)
data = urllib.urlencode({'utmpnum':utmpnum,'utmpkey':utmpkey,'utmpuserid':utmpuserid})
request = urllib2.Request('http://host/cgi-bin/bbslogout', data)
[/code works]

If I do not supply 'data', I was told "you are not logged in"

So, in this case, how can I supply all of utmpnum, utmpkey, utmpuserid and the attached file?
I have this code, but the response still says "you are not logged in"!
[code does not work]
(this is previous log in code)

bbs_att_url='http://host/cgi-bin/bbsdoupload'

data = {
'utmpnum':utmpnum,
'utmpkey':utmpkey,
'utmpuserid':utmpuserid,
'upfile' : open("myfile.ico", "rb") ,
}
data=urllib.urlencode(data)
request = urllib2.Request(bbs_att_url, data)
fd=urllib2.urlopen(request)
data=fd.read()
print data            #"you are not logged in" can be found 
[/code does not work]

Add comment



Highest rated recipes:

1. Implementation of sets ...

2. bag collection class

3. deque collection class

4. Floating Point Simulator

5. HTML colors to/from RGB ...

6. Select the nth smallest ...

7. Function Decorators by ...

8. MS SQL Server log monitor

9. Table objects with ...

10. wx twisted support using ...




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.