Um problema que sempre enfrento é enviar arquivos e dados (parâmetros, variáveis) usando python via HTTP POST. Existem soluções para enviar somente arquivos ou somente parâmetros que servem para a maior parte do uso, mas não para o meu. Um é exemplo enviar uma foto com título e descrição. Uma das únicas boas referências que eu achei foi este post no ActiveState feito em 2002, mas o problema é que ele não funcionar com UTF-8.
Este post é para mostrar um código que funciona para o envio de arquivos e parâmetros ao mesmo tempo em qualquer codificação.
In english:
One problem I ever face is when sending files and data (parameters, variables) using python via HTTP POST.
There are solutions to send only files or only parameters that are suitable for most use, but not mine. One example is sending a photo with title and description. One of the only good reference I found was this post on ActiveState made in 2002, but the problem is that it does not work with UTF-8.
This post is to show a code that works for sending files and parameters simultaneously in any encoding.
Abaixo segue o código:
import httplib
import mimetypes
import base64
def post_multipart(host, selector, fields, files, encoding='utf-8'):
"""
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)
body = body.encode(encoding)
h = httplib.HTTP(host)
h.putrequest(u'POST', selector)
h.putheader(u'Content-Type', content_type)
h.putheader(u'Charset', encoding)
h.putheader(u'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, encoding='utf-8'):
"""
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 = u'----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = u'rn'
L = []
for (key, value) in fields:
L.append(u'--' + BOUNDARY)
L.append(u'Content-Disposition: form-data; name="%s"' % key)
L.append(u'Content-Type: text/plain;charset=%s' % encoding)
L.append(u'Content-Transfer-Encoding: 8bit')
L.append(u'')
L.append(value)
for (key, filename, value) in files:
L.append(u'--' + BOUNDARY)
L.append(u'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append(u'Content-Type: %s;charset=%s' % (get_content_type(filename), encoding))
L.append(u'Content-Transfer-Encoding: base64')
L.append(u'')
L.append(base64.b64encode(value).decode())
L.append(u'--' + BOUNDARY + u'--')
L.append(u'')
body = CRLF.join(L)
content_type = u'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'
As diferenças entro o meu código e o código original são:
- Uso de strings unicode e a codificação é feita apenas no momento do envio da requisição.
- Uso da variável encoding para definir o encoding que será enviada a requisição.
- Codificando o arquivo em base64 para não ter problemas com acentuação interna.
- Indicando no cabeçalho do boundary que o arquivo está codificado em base64.
- Adicionando Charset no cabeçalho da requisição e nos cabeçalhos do boundary.