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):
38
40 if not isinstance(self._headers, MultiDict):
41 self._headers = MultiDict(self._headers or [])
42 return self._headers
45 headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
46
48 if self.url is None:
49 raise ValueError("url isn't set")
50 return urlparse.urlparse(self.url)
51 parsed_url = property(_parsed_url, doc="parsed url")
52
59 path = property(_path__get)
60
62 try:
63 h = self.parsed_url.netloc.encode('ascii')
64 except UnicodeEncodeError:
65 h = self.parsed_url.netloc.encode('idna')
66
67 hdr_host = self.headers.iget("host")
68 if not hdr_host:
69 return h
70 return hdr_host
71 host = property(_host__get)
72
74 te = self.headers.iget("transfer-encoding")
75 return (te is not None and te.lower() == "chunked")
76
79
80 - def _set_body(self, body):
81 ctype = self.headers.ipop('content-type', None)
82 clen = self.headers.ipop('content-length', None)
83
84 if isinstance(body, dict):
85 if ctype is not None and \
86 ctype.startswith("multipart/form-data"):
87 type_, opts = cgi.parse_header(ctype)
88 boundary = opts.get('boundary', uuid.uuid4().hex)
89 self._body, self.headers = multipart_form_encode(body,
90 self.headers, boundary)
91
92
93
94
95 ctype = self.headers.ipop('content-type', None)
96 else:
97 ctype = "application/x-www-form-urlencoded; charset=utf-8"
98 self._body = form_encode(body)
99 elif hasattr(body, "boundary") and hasattr(body, "get_size"):
100 ctype = "multipart/form-data; boundary=%s" % body.boundary
101 clen = body.get_size()
102 self._body = body
103 else:
104 self._body = body
105
106 if not ctype:
107 ctype = 'application/octet-stream'
108 if hasattr(self.body, 'name'):
109 ctype = mimetypes.guess_type(body.name)[0]
110
111 if not clen:
112 if hasattr(self._body, 'fileno'):
113 try:
114 self._body.flush()
115 except IOError:
116 pass
117 try:
118 fno = self._body.fileno()
119 clen = str(os.fstat(fno)[6])
120 except IOError:
121 if not self.is_chunked():
122 clen = len(self._body.read())
123 elif hasattr(self._body, 'getvalue') and not \
124 self.is_chunked():
125 clen = len(self._body.getvalue())
126 elif isinstance(self._body, types.StringTypes):
127 self._body = to_bytestring(self._body)
128 clen = len(self._body)
129
130 if clen is not None:
131 self.headers['Content-Length'] = clen
132
133
134
135
136 if ctype is not None:
137 self.headers['Content-Type'] = ctype
138
139 - def _get_body(self):
141 body = property(_get_body, _set_body, doc="request body")
142
144 if self.body is not None:
145 if not hasattr(self.body, 'seek') and \
146 not isinstance(self.body, types.StringTypes):
147 raise RequestError("error: '%s', body can't be rewind."
148 % msg)
149
150
151 -class BodyWrapper(object):
152
153 - def __init__(self, resp, connection):
154 self.resp = resp
155 self.body = resp._body
156 self.connection = connection
157
158 - def __enter__(self):
160
161 - def __exit__(self, exc_type, exc_val, traceback):
163
165 """ release connection """
166 self.connection.release(self.resp.should_close)
167
168 - def __iter__(self):
170
172 try:
173 return self.body.next()
174 except StopIteration:
175 self.close()
176 raise
177
178 - def read(self, n=-1):
179 data = self.body.read(n)
180 if not data:
181 self.close()
182 return data
183
184 - def readline(self, limit=-1):
185 line = self.body.readline(limit)
186 if not line:
187 self.close()
188 return line
189
190 - def readlines(self, hint=None):
191 lines = self.body.readlines(hint)
192 if self.body.close:
193 self.close()
194 return lines
195
196
198
199 charset = "utf8"
200 unicode_errors = 'strict'
201
202 - def __init__(self, connection, request, resp):
203 self.request = request
204 self.connection = connection
205
206 self._resp = resp
207
208
209 self.headers = resp.headers()
210 self.status = resp.status()
211 self.status_int = resp.status_code()
212 self.version = resp.version()
213 self.headerslist = self.headers.items()
214 self.location = self.headers.get('location')
215 self.final_url = request.url
216 self.should_close = not resp.should_keep_alive()
217
218
219 self._closed = False
220 self._already_read = False
221
222 if request.method == "HEAD":
223 """ no body on HEAD, release the connection now """
224 self.connection.release()
225 self._body = StringIO("")
226 else:
227 self._body = resp.body_file()
228
230 try:
231 return getattr(self, key)
232 except AttributeError:
233 pass
234 return self.headers.get(key)
235
238
241
243 return not self._already_read
244
245 - def body_string(self, charset=None, unicode_errors="strict"):
246 """ return body string, by default in bytestring """
247
248 if not self.can_read():
249 raise AlreadyRead()
250
251
252 body = self._body.read()
253 self._already_read = True
254
255
256 self.connection.release(self.should_close)
257
258 if charset is not None:
259 try:
260 body = body.decode(charset, unicode_errors)
261 except UnicodeDecodeError:
262 pass
263 return body
264
265 - def body_stream(self):
266 """ stream body """
267 if not self.can_read():
268 raise AlreadyRead()
269
270 self._already_read = True
271
272 return BodyWrapper(self, self.connection)
273
274
276 """ copy response input to standard output or a file if length >
277 sock.MAX_BODY. This make possible to reuse it in your
278 appplication. When all the input has been read, connection is
279 released """
280 return ResponseTeeInput(self, self.connection,
281 should_close=self.should_close)
282 ClientResponse = Response
283