I have python server serving cgi scripts,
I want to add status code to my response. I did,
try:
cgi = CGI()
output = cgi.fire()
print 'Content-Type text/json'
print 'Status:200 success'
print
print json.dumps(output)
except:
print 'Content-Type: text/json'
print 'Status: 403 Forbidden'
print
print json.dumps({'msg':'error'})
But when I request the this script via dojo xhr request, I get 200 request status. Why is so?
Header
Request URL:http://192.168.2.72:8080/cgi-bin/cgi.py
Request Method:POST
Status Code:200 Script output follows
Request Headersview source
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Content-Length:125
Content-Type:application/x-www-form-urlencoded
Host:192.168.2.72:7999
Origin:http://192.168.2.72:7999
Pragma:no-cache
Referer:http://192.168.2.72:7999/home.html
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.22 (KHTML, like Gecko) Ubuntu Chromium/25.0.1364.160 Chrome/25.0.1364.160 Safari/537.22
X-Requested-With:XMLHttpRequest
Form Dataview sourceview URL encoded
Response Headersview source
Content-Type:text/json
Date:Fri, 08 Aug 2014 05:16:29 GMT
Server:SimpleHTTP/0.6 Python/2.7.3
Status:403 Forbidden
Any inputs?
what I already have tried:
result.ioArgs.xhr.getAllResponseHeaders() // returns string
ioargs.xhr.status // returns the request status.
If json.dumps(output) raises an exception, you will have already printed your headers including status code (generally would be spelled as Status: 200 OK) and a blank line to end the header section of the HTTP response.
Then, the except block will print a second set of headers, but those are actually considered part of the body of the response at that point because printing an empty line ended the headers. See the HTTP message spec.
The solution is to wait until you know what your output is going to be to print any headers.
-more-
json.dumps can raise exceptions if you give it input that is not serializable. And given that cgi.fire() appears to be a method of some custom CGI object (builtin cgi module doesn't have that method) it could be returning anything.
To debug you need to log what exception is being raised, preferably with traceback. The bare except: block you have will catch all errors and then do nothing with them, so you don't know what's going on, nor does anyone looking at the question. You might also need to log the value of output.
To complement what Jason S says I reproduced exactly in his answer I reproduced the exactly same failure with a non json serializable object (in this example a md5 hash) and have the same behaviour than original poster a 200 return code
#!/usr/bin/env python
import json
import traceback
class CGI:
def fire(self):
import md5
return md5.md5()
try:
cgi = CGI()
output = cgi.fire()
print 'Content-Type text/json'
print 'Status:200 success'
print
print json.dumps(output)
except:
traceback.print_exc()
print 'Content-Type: text/json'
print 'Status: 403 Forbidden'
print
print json.dumps({'msg':'error'})
interacting with the server
$ socat - TCP4:localhost:8000
input
GET /cgi-bin/test.py HTTP/1.0
output
HTTP/1.0 200 Script output follows
Server: SimpleHTTP/0.6 Python/3.4.0
Date: Sun, 17 Aug 2014 16:16:19 GMT
Content-Type text/json
Status:200 success
Content-Type: text/json
Status: 403 Forbidden
{"msg": "error"}
traceback:
127.0.0.1 - - [17/Aug/2014 18:16:19] "GET /cgi-bin/test.py HTTP/1.0" 200 -
Traceback (most recent call last):
File "/home/xcombelle/dev/test/cgi-bin/test.py", line 16, in <module>
print json.dumps(output)
File "/usr/lib/python2.7/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.7/json/encoder.py", line 200, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python2.7/json/encoder.py", line 263, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python2.7/json/encoder.py", line 177, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <md5 HASH object # 0x7fcb090d0a30> is not JSON serializable
just do json.dumps() to a string before outputting your headers and you should be fine no?
that will protect you from setting headers and then getting an exception as exception in print is unlikely
For anyone coming across this in future, the reason for this is the python http.server which was being used to serve the content. For some reason this is designed to spit out a Status 200: script output follows header before the cgi script starts running. This means that you can't change the status code returned within your script (see the documentation for CGIHTTPRequestHandler on this page)
This actually makes it a real pain to use when developing as the errors don't propagate in the same way they would in production.
It looks to me like you are setting a Status: header field but you want to set Status-Code:.
Does your script really write Status Code:200 Script output follows as a header field?
Related
I've spent a total of 30 minutes in python lol, so take that into consideration when you answer lol:
I'm trying to send an HTTP POST request with a body and reading the response. I'm using Python 3.6.5 on Windows 10. This is what I have so far:
import http.client
import xml.dom.minidom
HOST = "www.mysite.com"
API_URL = "/service"
def do_request(xml_location):
request = open(xml_location, "r").read()
webservice = http.client.HTTPConnection(HOST)
webservice.request("POST", API_URL)
webservice.putheader("Host", HOST)
webservice.putheader("User-Agent", "Python Post")
webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
webservice.putheader("Content-length", "%d" % len(request))
webservice.endheaders()
webservice.send(request)
statuscode, statusmessage, header = webservice.getreply()
result = webservice.getfile().read()
resultxml = xml.dom.minidom.parseString(result)
print (statuscode, statusmessage, header)
print (resultxml.toprettyxml())
with open("output-%s" % xml_location, "w") as xmlfile:
xmlfile.write(resultxml.toprettyxml())
do_request("test.xml")
test.xml contains the XML request. When I run, I get an error:
Traceback (most recent call last):
File "C:\Users\xxx\Documents\test.py", line 33, in <module>
do_request("test.xml")
File "C:\Users\xxx\Documents\test.py", line 14, in do_request
webservice.putheader("Host", HOST)
File "C:\Users\xxx\AppData\Local\Programs\Python\Python36\lib\http\client.py", line 1201, in putheader
raise CannotSendHeader()
http.client.CannotSendHeader
Your problem is that you mixed up the request and putrequest methods. (Not surprisingly, given the brevity and sparsity of the documentation… most modules in Python are documented a lot better than this, so don't let that worry you about the future.)
The request method is a convenience function that adds the request line, all the headers, and the data all in one go. After you've done that, it's way too late to add a header, hence the error message.
So, you can fix it either way.
(1) Change it to use putrequest. I realize there's no example using putrequest or putheader anywhere in the docs, but it looks like this:
webservice.putrequest("POST", API_URL)
webservice.putheader("Host", HOST)
webservice.putheader("User-Agent", "Python Post")
webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
webservice.putheader("Content-length", "%d" % len(request))
webservice.endheaders()
webservice.send(request)
(2) Change it to use request. This is what all the examples in the docs do; you just need to build up a dict of headers to pass to it:
headers = {
"Host": HOST,
"User-Agent": "Python Post",
"Content-type", "text/xml; charset=\"UTF-8\"",
"Content-length", "%d" % len(request)
}
webservice.request("POST", API_URL, headers=headers, body=request)
(3) Read this at the top of the docs:
This module defines classes which implement the client side of the HTTP and HTTPS protocols. It is normally not used directly — the module urllib.request uses it to handle URLs that use HTTP and HTTPS.
See also The Requests package is recommended for a higher-level HTTP client interface.
For most real-life cases, you want to use requests if you can use a third-party library, and urllib.request if you can't. They're both simpler, and better documented.
def get(self):
self.set_status(400, '["reason"]')
self.finish()
return
When you get this response you can get response.error.message which has the message HTTP 400: ["reason"]. But what if you wanted a pure json response as an error. What would be the best way to get that?
The second argument to set_status() is the "reason" string, i.e. the "Not Found" in HTTP/1.1 404 Not Found. It's human-readable, not machine readable, and many HTTP clients simply discard it. You should only use this parameter when you are sending a status code that is not found in the standard list.
Instead, when you want to send a JSON message along with an error, call self.set_status(code), and then write your output as usual into the body:
self.set_status(400)
self.finish({"reason": reason})
Here's my understanding of the desired sequence of operations when uploading a file to an entity's dropbox submission:
Create feedback for a user (in my case, I am populating a rubric assessment automatically)
Upload a file, receive unique file key
Attach that file to the user's feedback, using the key received in (2)
I can upload a file for the user using the web interface to d2L, but obviously would like to streamline this if I am preparing the feedback through automation anyway. My current status is:
Create feedback for a user (in my case, I am populating a rubric assessment automatically) (200 - Success)
Upload a file, receive unique file key (200 - Success)
Attach that file to the user's feedback, using the key received in (2) (404 - Not Found)
I've been struggling with how to debug uploading and attaching a file to a dropbox feedback. I followed the resumable upload instructions at http://docs.valence.desire2learn.com/basic/fileupload.html#resumable-uploads, and (generally) that went OK, but I'm getting a final 404 error when attaching that uploaded file to the dropbox folder for this entity.
Below is my debug output that demonstrates what I understand as the state of the system for me.
Note: I replaced COURSEID, DROPBOXID, ENTITYID, and a few Keys using search and replace in an external editor, to preserve the illusion of security.
POST /d2l/api/le/1.4/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID
Posts the feedback to the dropbox for this user
{}
Confirms that the post was successful
filepath=092215-0952/Testing Download Sep 21, 2015 1114 AM_upload/ENTITYID-DROPBOXID - Student Name-Due 18, YYYY TIME AM - netid_assignmentname.zip_upload.zip
length=76630
https://d2l.domain.tld/d2l/api/le/1.4/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID/upload?x_b=_________USERID___________&x_c=_________KEY_I_DONT_UNDERSTAND___________&x_a=_________APP_ID___________&x_d=_________ANOTHER_KEY_I_DONT_UNDERSTAND___________&x_t=1443053113
{'Content-Length': '76630', 'User-Agent': 'python-requests/2.7.0 CPython/3.3.6 Darwin/14.5.0', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'Accept': '*/*', 'X-Upload-Content-Type': 'application/zip', 'X-Upload-File-Name': 'temp.zip', 'X-Upload-Content-Length': '76630'}
Received new URL to which to upload this file: revising post for this fileKey
result=qafSoMUYAm
https://d2l.domain.tld/d2l/upload/qafSoMUYAm
result of revised post is 200
With this, it seems that I initiated, and completed, the resumable upload. I will say, this part was made easier by the instructions from valence, but there was a lot of muttering on my part trying to debug it until I realized that some of the built-in post commands in d2lservice would always fail since I had to change the URL after the first post. :) By the way, I can make this return a 416 if I intentionally mess up the byte count for the file length. I did this to make sure that I wasn't getting 200 for the wrong reason.
fileKey=qafSoMUYAm, attaching to the dropbox now
GET /d2l/api/le/1.1/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID
Feedback exists for org_unit_id=COURSEID,folder_id=DROPBOXID,entity_id=ENTITYID
Here, I am making sure that the feedback truly exists for this user, so I can compare the URL to the one that fails below. NOTE: I am using version 1.4 for almost everything, but in the example below I was hardcoding the version to be 1.1 in case there was a version difference in the APIs; I tried 1.1, 1.4, but nothing else.
I am also force-marking the feedback to be feedback['IsGraded']=False, in case for some reason it's not possible to add a file to feedback if the feedback has been published. I then re-post the feedback (after marking it as ungraded), and attempt to attach the uploaded file to this feedback. An additional variable is that I have also tried (and removed) the use of a fileName param for the post (it is listed as optional in the APIs).
POST /d2l/api/le/1.4/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID
I am turning on debug now...
ready for POST=/d2l/api/le/1.1/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID/attach
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): d2l.domain.tld
send: b'POST /d2l/api/le/1.1/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID/attach?fileKey=qafSoMUYAm&x_b=_________USERID___________&x_a=_________APP_ID___________&x_d=sw6PXGCvBOsBj07VJCPu4RJ6hN8AN7OIId2ONOj-_CA&x_t=1443053116&x_c=LIrzfSOnb8rz3wkN-ZTbfnckwvrfheUU9kkarOhGpok HTTP/1.1\r\nHost: d2l.domain.tld\r\nConnection: keep-alive\r\nUser-Agent: python-requests/2.7.0 CPython/3.3.6 Darwin/14.5.0\r\nContent-Length: 0\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate\r\n\r\n'
reply: 'HTTP/1.1 404 Not Found\r\n'
DEBUG:requests.packages.urllib3.connectionpool:"POST /d2l/api/le/1.1/COURSEID/dropbox/folders/DROPBOXID/feedback/user/ENTITYID/attach?fileKey=qafSoMUYAm&x_b=_________USERID___________&x_a=_________APP_ID___________&x_d=sw6PXGCvBOsBj07VJCPu4RJ6hN8AN7OIId2ONOj-_CA&x_t=1443053116&x_c=LIrzfSOnb8rz3wkN-ZTbfnckwvrfheUU9kkarOhGpok HTTP/1.1" 404 0
So, I get a 404 when trying to make this post, and a bunch of errors below from Python that don't really help (but I include them anyway).
header: Server header: X-XSS-Protection header: X-UA-Compatible header: X-Powered-By header: Date header: Content-Length header: Set-Cookie <Response [404]>
Traceback (most recent call last):
File "../valence/sprinkle/production/postgradenoui.py", line 239, in <module>
print(postGrades_handler(dropboxid, gradeddirname,gradeddir_uploadname))
File "../valence/sprinkle/production/postgradenoui.py", line 230, in postGrades_handler
result = sprinkleutil.postRubricFeedback(request, _ac, dropboxid, gradeddirname, _CFG_COURSE['courseorgunit'], gradeddir_uploadname)
File "/Users/sprinkle/work/teaching/ece275-2015F/grading/valence/sprinkle/production/sprinkleutil.py", line 1279, in postRubricFeedback
upload_feedback_files_for_dropbox_feedback(uc,orgunitNeeded,dropboxNeeded,dir,dbfFile,'1.4')
File "/Users/sprinkle/work/teaching/ece275-2015F/grading/valence/sprinkle/production/sprinkleutil.py", line 791, in upload_feedback_files_for_dropbox_feedback
result = attach_uploaded_file(uc,org_unit_id,folder_id,entity_id,fileKey,'temp.zip')
File "/Users/sprinkle/work/teaching/ece275-2015F/grading/valence/sprinkle/production/sprinkleutil.py", line 523, in attach_uploaded_file
return d2lservice._fetch_content(r)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/d2lvalence_util/service.py", line 46, in _fetch_content
r.raise_for_status()
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/requests/models.py", line 851, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found
I appreciate any pointers from anyway who has successfully carried out this sequence of operations. My d2l admins don't have bandwidth to support me, and I'm not sure if I can access the logs at all, so I don't know how to improve debugging on my side. Thanks in advance!
For your attach stage, it looks like you're sending in the fileKey as a query parameter. It needs to be a POST form parameter, right?
So the content-type for the POST would be application/x-www-form-urlencoded, and the POST body itself would be
fileKey=qafSoMUYAm&fileName=SomeFileName.zip
For example, see How to mimic an HTML form submission in a POST request.
I have a strange problem i've been trying to 'google-out' for several hours.
I've tried also solutions from similar topics on stack but still wiht no positive result:
How do I set cookies using Python urlopen?
Handling rss redirects with Python/urllib2
So the case is that i want to download whole set of articles form some webpage. Its sub-links with proper content differ with just one number, so I loop for whole range ( 1 to 400 000 ) and write html's to files. Whats importatnt here is this webpage need the cookies to be re-send in order to get to proper url, and after a lecture of How to use Python to login to a webpage and retrieve cookies for later usage? i have this done.
But some times my script returns error:
response = meth(req, response)
File "/usr/lib/python3.1/urllib/request.py", line 468, in http_response
'http', request, response, code, msg, hdrs)
....
File "/usr/lib/python3.1/urllib/request.py", line 553, in http_error_302 self.inf_msg + msg, headers, fp)
urllib.error.HTTPError: HTTP Error 302: The HTTP server returned a redirect error that would lead to an infinite loop.
The last 30x error message was:
Found
This problem is hard to reproduce because script generally works fine but it happens randomly after a few thousands of 'for loops'.
Here is curl ouptut from server:
$ curl -I "http://my.url/"
HTTP/1.1 200 OK
Date: Wed, 17 Oct 2012 10:14:13 GMT
Server: Apache/2.2.15 (Oracle)
X-Powered-By: PHP/5.3.3
Set-Cookie: Kuuxk=ae7s3isu2cEshhijte4nb1clk5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Connection: close
Content-Type: text/html; charset=UTF-8
Some folks suggested to use mechanize or try to catch exception but i have no clue how to do this, others said that error is caused by wrong cookie handling but i tried also to get and send cookies 'manually' using urllib2 and add_header('cookie', cookie) with similar result.
I wonder if my for loop and mabey to short sleep might cause script to fail some times..
Anwyay - any help appreciated.
edit:
In case this might work - how to catch the exception and try ignore it ?
edit:
Solved by simply ignoring this error. No everything goes fine.
I used
try:
#here open url
except any_HTTPError:
pass
On each time i use url.open instruction.
TO BE CLOSED.
Let me suggest another solution:
HTTP status code 302 means Found redirection (See: https://en.wikipedia.org/wiki/HTTP_302).
For example:
HTTP/1.1 302 Found
Location: http://www.iana.org/domains/example/
You can grab the Location header and try fetching this url.
There are 8 redirection status codes (301-308). You can for Location header if 301 <= status code <= 308.
I'm trying to write some python code which can create multipart mime http requests in the client, and then appropriately interpret then on the server. I have, I think, partially succeeded on the client end with this:
from email.mime.multipart import MIMEMultipart, MIMEBase
import httplib
h1 = httplib.HTTPConnection('localhost:8080')
msg = MIMEMultipart()
fp = open('myfile.zip', 'rb')
base = MIMEBase("application", "octet-stream")
base.set_payload(fp.read())
msg.attach(base)
h1.request("POST", "http://localhost:8080/server", msg.as_string())
The only problem with this is that the email library also includes the Content-Type and MIME-Version headers, and I'm not sure how they're going to be related to the HTTP headers included by httplib:
Content-Type: multipart/mixed; boundary="===============2050792481=="
MIME-Version: 1.0
--===============2050792481==
Content-Type: application/octet-stream
MIME-Version: 1.0
This may be the reason that when this request is received by my web.py application, I just get an error message. The web.py POST handler:
class MultipartServer:
def POST(self, collection):
print web.input()
Throws this error:
Traceback (most recent call last):
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 242, in process
return self.handle()
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 233, in handle
return self._delegate(fn, self.fvars, args)
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 415, in _delegate
return handle_class(cls)
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/application.py", line 390, in handle_class
return tocall(*args)
File "/home/richard/Development/server/webservice.py", line 31, in POST
print web.input()
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/webapi.py", line 279, in input
return storify(out, *requireds, **defaults)
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 150, in storify
value = getvalue(value)
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 139, in getvalue
return unicodify(x)
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 130, in unicodify
if _unicode and isinstance(s, str): return safeunicode(s)
File "/usr/local/lib/python2.6/dist-packages/web.py-0.34-py2.6.egg/web/utils.py", line 326, in safeunicode
return obj.decode(encoding)
File "/usr/lib/python2.6/encodings/utf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode bytes in position 137-138: invalid data
My line of code is represented by the error line about half way down:
File "/home/richard/Development/server/webservice.py", line 31, in POST
print web.input()
It's coming along, but I'm not sure where to go from here. Is this a problem with my client code, or a limitation of web.py (perhaps it just can't support multipart requests)? Any hints or suggestions of alternative code libraries would be gratefully received.
EDIT
The error above was caused by the data not being automatically base64 encoded. Adding
encoders.encode_base64(base)
Gets rid of this error, and now the problem is clear. HTTP request isn't being interpreted correctly in the server, presumably because the email library is including what should be the HTTP headers in the body instead:
<Storage {'Content-Type: multipart/mixed': u'',
' boundary': u'"===============1342637378=="\n'
'MIME-Version: 1.0\n\n--===============1342637378==\n'
'Content-Type: application/octet-stream\n'
'MIME-Version: 1.0\n'
'Content-Transfer-Encoding: base64\n'
'\n0fINCs PBk1jAAAAAAAAA.... etc
So something is not right there.
Thanks
Richard
I used this package by Will Holcomb http://pypi.python.org/pypi/MultipartPostHandler/0.1.0 to make multi-part requests with urllib2, it may help you out.
After a bit of exploration, the answer to this question has become clear. The short answer is that although the Content-Disposition is optional in a Mime-encoded message, web.py requires it for each mime-part in order to correctly parse out the HTTP request.
Contrary to other comments on this question, the difference between HTTP and Email is irrelevant, as they are simply transport mechanisms for the Mime message and nothing more. Multipart/related (not multipart/form-data) messages are common in content exchanging webservices, which is the use case here. The code snippets provided are accurate, though, and led me to a slightly briefer solution to the problem.
# open an HTTP connection
h1 = httplib.HTTPConnection('localhost:8080')
# create a mime multipart message of type multipart/related
msg = MIMEMultipart("related")
# create a mime-part containing a zip file, with a Content-Disposition header
# on the section
fp = open('file.zip', 'rb')
base = MIMEBase("application", "zip")
base['Content-Disposition'] = 'file; name="package"; filename="file.zip"'
base.set_payload(fp.read())
encoders.encode_base64(base)
msg.attach(base)
# Here's a rubbish bit: chomp through the header rows, until hitting a newline on
# its own, and read each string on the way as an HTTP header, and reading the rest
# of the message into a new variable
header_mode = True
headers = {}
body = []
for line in msg.as_string().splitlines(True):
if line == "\n" and header_mode == True:
header_mode = False
if header_mode:
(key, value) = line.split(":", 1)
headers[key.strip()] = value.strip()
else:
body.append(line)
body = "".join(body)
# do the request, with the separated headers and body
h1.request("POST", "http://localhost:8080/server", body, headers)
This is picked up perfectly well by web.py, so it's clear that email.mime.multipart is suitable for creating Mime messages to be transported by HTTP, with the exception of its header handling.
My other overall conern is in scalability. Neither this solution nor the others proposed here scale well, as they read the contents of a file into a variable before bundling up in the mime message. A better solution would be one which could serialise on demand as the content is piped out over the HTTP connection. It's not urgent for me to fix that, but I'll come back here with a solution if I get to it.
There is a number of things wrong with your request. As TokenMacGuy suggests, multipart/mixed is unused in HTTP; use multipart/form-data instead. In addition, parts should have a Content-disposition header. A python fragment to do that can be found in the Code Recipes.