1
2
3 """ HTTP support.
4
5 Copyright (c) 2012 The PyroScope Project <pyroscope.project@gmail.com>
6 """
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import os
22 import sys
23 import time
24 import httplib
25 import urlparse
26 import StringIO
27 import mimetypes
28
29 from pyrobase import fmt, parts
30
31
32 -class HttpPost(object):
33 """ Do a HTTP multipart/form-data POST.
34 """
35
36 - def __init__(self, url, fields, headers=None, mock_http=False):
37 """ Initialize POST data.
38
39 Field values can be strings or files; files
40 are expected to have a read() method and SHOULD have a 'name'
41 attribute (i.e. look like handles returned by 'open()').
42
43 @param url: the URL to POST to.
44 @param fields: sequence of (name, value) tuples.
45 @param headers: dict of additional headers.
46 """
47 self.url = url
48 self.fields = fields
49 self.headers = headers or {}
50 self.mock_http = mock_http
51
52
54 """ Show POST data.
55 """
56
57 return '\n'.join([
58 "POST to '%s' with these fields:" % (self.url,),
59 ] + [
60 " %s=%r" % i for i in self.fields
61 ])
62
63
65 """ Post fields and files to an HTTP server as multipart/form-data.
66 Return the server's response.
67 """
68 scheme, location, path, query, _ = urlparse.urlsplit(self.url)
69 assert scheme in ("http", "https"), "Unsupported scheme %r" % scheme
70
71 content_type, body = self._encode_multipart_formdata()
72 handle = getattr(httplib, scheme.upper() + "Connection")(location)
73 if self.mock_http:
74
75 handle.sock = parts.Bunch(
76 sendall=lambda x: sys.stdout.write(fmt.to_utf8(
77 ''.join((c if 32 <= ord(c) < 127 or ord(c) in (8, 10) else u'\u27ea%02X\u27eb' % ord(c)) for c in x)
78 )),
79 makefile=lambda dummy, _: StringIO.StringIO("\r\n".join((
80 "HTTP/1.0 204 NO CONTENT",
81 "Content-Length: 0",
82 "",
83 ))),
84 close=lambda: None,
85 )
86
87 handle.putrequest('POST', urlparse.urlunsplit(('', '', path, query, '')))
88 handle.putheader('Content-Type', content_type)
89 handle.putheader('Content-Length', str(len(body)))
90 for key, val in self.headers.items():
91 handle.putheader(key, val)
92 handle.endheaders()
93 handle.send(body)
94
95
96 return handle.getresponse()
97
98
100 """ Encode POST body.
101 Return (content_type, body) ready for httplib.HTTP instance
102 """
103 def get_content_type(filename):
104 "Helper to get MIME type."
105 return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
106
107 boundary = '----------ThIs_Is_tHe_b0uNdaRY_%d$' % (time.time())
108 logical_lines = []
109 for name, value in self.fields:
110 if value is None:
111 continue
112 logical_lines.append('--' + boundary)
113 if hasattr(value, "read"):
114 filename = getattr(value, "name", str(id(value))+".dat")
115 logical_lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (
116 name,
117 os.path.basename(filename).replace("'", '_').replace('"', '_')
118 ))
119 logical_lines.append('Content-Type: %s' % get_content_type(filename))
120 logical_lines.append('Content-Transfer-Encoding: binary')
121 value = value.read()
122 else:
123 logical_lines.append('Content-Disposition: form-data; name="%s"' % name)
124 logical_lines.append('Content-Type: text/plain; charset="UTF-8"')
125 value = fmt.to_utf8(value)
126
127 logical_lines.append('')
128 logical_lines.append(value)
129 logical_lines.append('--' + boundary + '--')
130 logical_lines.append('')
131
132 body = '\r\n'.join(logical_lines)
133 content_type = 'multipart/form-data; boundary=%s' % boundary
134 return content_type, body
135