1
2
3
4
5
6 import cgi
7 import copy
8 import mimetypes
9 import os
10 from StringIO import StringIO
11 import types
12 import urlparse
13 import uuid
14
15 from restkit.datastructures import MultiDict
16 from restkit.errors import AlreadyRead, RequestError
17 from restkit.forms import multipart_form_encode, form_encode
18 from restkit.tee import ResponseTeeInput
19 from restkit.util import to_bytestring
20
22
23 - def __init__(self, url, method='GET', body=None, headers=None):
36
38 if not isinstance(self._headers, MultiDict):
39 self._headers = MultiDict(self._headers or [])
40 return self._headers
43 headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
44
46 if self.url is None:
47 raise ValueError("url isn't set")
48 return urlparse.urlparse(self.url)
49 parsed_url = property(_parsed_url, doc="parsed url")
50
57 path = property(_path__get)
58
60 try:
61 h = self.parsed_url.netloc.encode('ascii')
62 except UnicodeEncodeError:
63 h = self.parsed_url.netloc.encode('idna')
64
65 hdr_host = self.headers.iget("host")
66 if not hdr_host:
67 return h
68 return hdr_host
69 host = property(_host__get)
70
72 te = self.headers.iget("transfer-encoding")
73 return (te is not None and te.lower() == "chunked")
74
77
78 - def _set_body(self, body):
79 ctype = self.headers.ipop('content-type', None)
80 clen = self.headers.ipop('content-length', None)
81
82 if isinstance(body, dict):
83 if ctype is not None and \
84 ctype.startswith("multipart/form-data"):
85 type_, opts = cgi.parse_header(ctype)
86 boundary = opts.get('boundary', uuid.uuid4().hex)
87 self._body, self.headers = multipart_form_encode(body,
88 self.headers, boundary)
89 else:
90 ctype = "application/x-www-form-urlencoded; charset=utf-8"
91 self._body = form_encode(body)
92 elif hasattr(body, "boundary") and hasattr(body, "get_size"):
93 ctype = "multipart/form-data; boundary=%s" % body.boundary
94 clen = body.get_size()
95 self._body = body
96 else:
97 self._body = body
98
99 if not ctype:
100 ctype = 'application/octet-stream'
101 if hasattr(self.body, 'name'):
102 ctype = mimetypes.guess_type(body.name)[0]
103
104 if not clen:
105 if hasattr(self._body, 'fileno'):
106 try:
107 self._body.flush()
108 except IOError:
109 pass
110 try:
111 fno = self._body.fileno()
112 clen = str(os.fstat(fno)[6])
113 except IOError:
114 if not self.is_chunked():
115 clen = len(self._body.read())
116 elif hasattr(self._body, 'getvalue') and not \
117 self.is_chunked():
118 clen = len(self._body.getvalue())
119 elif isinstance(self._body, types.StringTypes):
120 self._body = to_bytestring(self._body)
121 clen = len(self._body)
122
123 if clen is not None:
124 self.headers['Content-Length'] = clen
125
126 if ctype is not None:
127 self.headers['Content-Type'] = ctype
128
129 - def _get_body(self):
131 body = property(_get_body, _set_body, doc="request body")
132
133
134 -class BodyWrapper(object):
135
136 - def __init__(self, resp, connection):
137 self.resp = resp
138 self.body = resp._body
139 self.connection = connection
140
141 - def __enter__(self):
143
144 - def __exit__(self, exc_type, exc_val, traceback):
146
148 """ release connection """
149 self.connection.release(self.resp.should_close)
150
151 - def __iter__(self):
153
155 try:
156 return self.body.next()
157 except StopIteration:
158 self.close()
159 raise
160
161 - def read(self, n=-1):
162 data = self.body.read(n)
163 if not data:
164 self.close()
165 return data
166
167 - def readline(self, limit=-1):
168 line = self.body.readline(limit)
169 if not line:
170 self.close()
171 return line
172
173 - def readlines(self, hint=None):
174 lines = self.body.readlines(hint)
175 if self.body.close:
176 self.close()
177 return lines
178
179
181
182 charset = "utf8"
183 unicode_errors = 'strict'
184
185 - def __init__(self, connection, request, resp):
186 self.request = request
187 self.connection = connection
188
189 self._resp = resp
190
191
192 self.headers = resp.headers()
193 self.status = resp.status()
194 self.status_int = resp.status_code()
195 self.version = resp.version()
196 self.headerslist = self.headers.items()
197 self.location = self.headers.get('location')
198 self.final_url = request.url
199 self.should_close = not resp.should_keep_alive()
200
201
202 self._closed = False
203 self._already_read = False
204
205 if request.method == "HEAD":
206 """ no body on HEAD, release the connection now """
207 self.connection.release()
208 self._body = StringIO()
209
210 else:
211 self._body = resp.body_file()
212
214 try:
215 return getattr(self, key)
216 except AttributeError:
217 pass
218 return self.headers.get(key)
219
222
225
227 return not self._already_read
228
229 - def body_string(self, charset=None, unicode_errors="strict"):
230 """ return body string, by default in bytestring """
231
232 if not self.can_read():
233 raise AlreadyRead()
234
235
236 body = self._body.read()
237 self._already_read = True
238
239
240 self.connection.release(self.should_close)
241
242 if charset is not None:
243 try:
244 body = body.decode(charset, unicode_errors)
245 except UnicodeDecodeError:
246 pass
247 return body
248
249 - def body_stream(self):
250 """ stream body """
251 if not self.can_read():
252 raise AlreadyRead()
253
254 self._already_read = True
255
256 return BodyWrapper(self, self.connection)
257
258
260 """ copy response input to standard output or a file if length >
261 sock.MAX_BODY. This make possible to reuse it in your
262 appplication. When all the input has been read, connection is
263 released """
264 return ResponseTeeInput(self, self.connection,
265 should_close=self.should_close)
266 ClientResponse = Response
267