35

I am trying to replicate the following POST request using the requests module in python:

POST /example/asdfas HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------241652170216373
Content-Length: 279

-----------------------------241652170216373
Content-Disposition: form-data; name="value_1"

12345
-----------------------------241652170216373
Content-Disposition: form-data; name="value_2"

67890
-----------------------------241652170216373--

The documentation for requests suggests that the files argument should be used.

When I attempt the following call:

import requests
requests.post('http://example.com/example/asdfas', files={'value_1': '12345', 
                                                          'value_2': '67890'})

I get the following HTTP request:

'Accept': '*/*', 
'Accept-Encoding': 'gzip, deflate, compress', 
'Content-Length': '264', 
'User-Agent': 'python-requests/2.2.1 CPython/3.3.2 Windows/7', 
'Content-Type': 'multipart/form-data; boundary=273f13699c02429db4eb95c97f757d38'
--273f13699c02429db4eb95c97f757d38
Content-Disposition: form-data; name="value_1"; filename="value_1"

12345
--273f13699c02429db4eb95c97f757d38
Content-Disposition: form-data; name="value_2"; filename="value_2"

67890
--273f13699c02429db4eb95c97f757d38--

I have also tried to use the data argument:

import requests
requests.post('http://example.com/example/asdfas', data={'value_1': '12345', 
                                                         'value_2': '67890'})

resulting in the following HTTP request:

'Content-Type': 'application/x-www-form-urlencoded', 
'Content-Length': '27', 
'User-Agent': 'python-requests/2.2.1 CPython/3.3.2 Windows/7', 
'Accept': '*/*', 
'Accept-Encoding': 'gzip, deflate, compress'
value_2=67890&value_1=12345

The issue I'm having is that using the files argument results in a call that the server doesn't recognize, presumably due to the unexpected "filename" information sent in the HTTP request. Using the data argument sends the wrong Content-Type header.

The first request is known to be working on the server I wish to send the request to - what is the correct function call to identically replicate the first HTTP request?

Edit: Sample HTML form to replicate the working request:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <form action="http://example.com/example/asdfas" method="post" enctype="multipart/form-data">
        <label for="v1">Value 1</label>
        <input id="v1" type="text" name="value_1">
        <label for="v2">Value 2</label>
        <input id="v2" type="text" name="value_2">
        <input type="submit">
    </form>
</body>
</html>
sornars
  • 1,139
  • 1
  • 8
  • 12
  • That doesn't look like a good `form`... what happens if you try: `requests.post('http://example.com/example/asdfas', data={'name': '12345'})`? What does the server do? – Savir Apr 16 '14 at 22:25
  • The form may be non-standard. I've included the HTML that would be required to create a working request. The server returns XML on success or a URL not found server error on the failures. – sornars Apr 16 '14 at 23:10

2 Answers2

68

The solution is to use tuples when passing parameters to the files argument:

import requests
requests.post('http://example.com/example/asdfas', files={'value_1': (None, '12345'), 'value_2': (None, '67890')})

Works as expected:

'Accept': '*/*', 
'Accept-Encoding': 'gzip, deflate, compress', 
'Content-Length': '228', 
'User-Agent': 'python-requests/2.2.1 CPython/3.3.2 Windows/7', 
'Content-Type': 'multipart/form-data; boundary=85e90a4bbb05474ca1e23dbebdd68ed9'

--85e90a4bbb05474ca1e23dbebdd68ed9
Content-Disposition: form-data; name="value_1"

12345
--85e90a4bbb05474ca1e23dbebdd68ed9
Content-Disposition: form-data; name="value_2"

67890
--85e90a4bbb05474ca1e23dbebdd68ed9--
sornars
  • 1,139
  • 1
  • 8
  • 12
  • 2
    Nice catch! You should mark your solution as accepted! (I didn't know you could fool the `requests` module like that) **:-)** – Savir Apr 17 '14 at 12:24
  • 6
    @BorrajaX Heh, it's not fooling us, it's just an undocumented corner of our API. We're trying to decide if we're happy with it before we document it more clearly. You can use tuples of any length between 2 and 4 inclusive, and it'll affect different parts of the multipart body. – Lukasa Apr 19 '14 at 17:46
  • 1
    @sornars including the mentioned types, i also have, `------WebKitFormBoundaryGTNXgIPxfTWUu45A Content-Disposition: form-data; name="files[]"; filename="myvideo.mp4" Content-Type: video/mp4 ------WebKitFormBoundaryGTNXgIPxfTWUu45A--` how do I pass the parameter? please help – Shaardool Jun 06 '15 at 15:25
  • How can you use 2-tuples as values here and [use 4-tuples as values also](http://stackoverflow.com/a/22974646/1459594)? Either way works? – rschwieb Oct 05 '15 at 23:56
  • 1
    omg thank you! You solved what was starting to become a major headache – Jean-Marc S. Aug 10 '17 at 20:54
  • 1
    what does first element of tuple `None` mean? in my case i have such data `Content-Disposition: form-data; name="TtsParameter"; paramName="TEXT_TO_READ"` and how to post? – Lei Yang Jan 16 '19 at 01:42
  • wow, that is a weird fix but worked well for me dealing with a CloudFlare API, thanks! – uwe Oct 18 '22 at 22:50
-3
import requests
from requests_toolbelt import MultipartEncoder

url = 'http://example.com/example/asdfas'
fields = {'value_1':'12345', 'value_2': '67890'}

data = MultipartEncoder(fields=fields)
headers["Content-type"] = m.content_type

requests.post(url=url, data=data, headers=headers)
raitisd
  • 3,875
  • 5
  • 26
  • 37