0001import sys
0002import urlparse
0003import re
0004from cStringIO import StringIO
0005import httplib2
0006from paste.request import construct_url
0007from paste.util.multidict import MultiDict
0008from httpencode.registry import get_format, find_format_match, find_format_by_type, find_accept_for_type
0010from httpencode.format import Format
0011from paste.response import header_value, replace_header, remove_header
0012
0013__all__ = ['HTTP']
0014
0015
0016scheme_re = re.compile(r'^[a-zA-Z0-9]+:')
0017
0018class HTTP(object):
0019
0020 default_encoding = 'utf8'
0021 default_input_content_type = None
0022 default_input_format = None
0023 prefer_input_mimetypes = [
0024 "application/x-www-forurlencoded",
0025 "multipart/form-data",
0026 "application/xml",
0027 "*"]
0028 default_post_input_type = 'python'
0029
0030 mock_wsgi_app = None
0031 redirections = httplib2.DEFAULT_MAX_REDIRECTS
0032
0033 def __init__(self, cache=None, default_encoding=None,
0034 default_input_content_type=None,
0035 default_input_format=None,
0036 prefer_input_mimetypes=None,
0037 default_post_input_type=None,
0038 redirections=None):
0039 self.httplib2 = httplib2.Http(cache)
0040 self.cache = cache
0041 if default_encoding is not None:
0042 self.default_encoding = default_encoding
0043 if default_input_content_type is not None:
0044 self.default_input_content_type = default_input_content_type
0045 if default_input_format is not None:
0046 if isinstance(default_input_format, basestring):
0047 default_input_format = get_format(default_input_format)
0048 self.default_input_format = default_input_format
0049 if prefer_input_mimetypes is not None:
0050 if isinstance(prefer_input_mimetypes, basestring):
0051 raise TypeError(
0052 "prefer_input_mimetypes must be a list (not %r)"
0053 % prefer_input_mimetypes)
0054 self.prefer_input_mimetypes = prefer_input_mimetypes
0055 if default_post_input_type is not None:
0056 self.default_post_input_type = default_post_input_type
0057 if redirections is not None:
0058 self.redirections = redirections
0059 for name in ['add_credentials', 'clear_credentials']:
0060 setattr(self, name, getattr(self.httplib2, name))
0061 self._raw_request = self.httplib2.request
0062
0063 def clone(self, cache=None, default_encoding=None,
0064 default_input_content_type=None,
0065 default_input_format=None,
0066 prefer_input_mimetypes=None,
0067 default_post_input_type=None,
0068 redirections=None):
0069 """
0070 Create another HTTP instance with the same settings as the
0071 current one, but potentially overriding some settings.
0072 """
0073 if cache is None:
0074 cache = self.cache
0075 if default_encoding is None:
0076 default_encoding = self.default_encoding
0077 if default_input_content_type is None:
0078 default_input_content_type = self.default_input_content_type
0079 if prefer_input_mimetypes is None:
0080 prefer_input_mimetypes = self.prefer_input_mimetypes
0081 if default_post_input_type is None:
0082 default_post_input_type = self.default_post_input_type
0083 if redirections is None:
0084 redirections = self.redirections
0085 return self.__class__(
0086 cache=cache, default_encoding=default_encoding,
0087 default_input_content_type=default_input_content_type,
0088 prefer_input_mimetypes=prefer_input_mimetypes,
0089 default_post_input_type=default_post_input_type,
0090 redirections=redirections)
0091
0092 def GET(self, uri, headers=None,
0093 wsgi_request=None, output=None, trusted=False):
0094 return self.request(
0095 uri, method='GET', body=None, headers=headers,
0096 wsgi_request=wsgi_request,
0097 input=None, output=output, trusted=trusted)
0098
0099 def POST(self, uri, body=None, headers=None,
0100 wsgi_request=None, input=None, output=None, trusted=False):
0101 if not isinstance(body, basestring) and not input:
0102 input = self.default_post_input_type
0103 return self.request(
0104 uri, method='POST', body=body, headers=headers,
0105 wsgi_request=wsgi_request,
0106 input=input, output=output, trusted=trusted)
0107
0108 def PUT(self, uri, body=None, headers=None,
0109 wsgi_request=None, input=None, output=None, trusted=False):
0110 return self.request(
0111 uri, method='PUT', body=body, headers=headers,
0112 wsgi_request=wsgi_request,
0113 input=input, output=output, trusted=trusted)
0114
0115 def DELETE(self, uri, headers=None,
0116 wsgi_request=None, output=None, trusted=False):
0117 return self.request(
0118 uri, method='PUT', body=None, headers=headers,
0119 wsgi_request=wsgi_request,
0120 input=None, output=output, trusted=trusted)
0121
0122 def request(self, uri, method="GET", body=None, headers=None,
0123 wsgi_request=None,
0124 input=None, output=None, trusted=False):
0125 method = method.upper()
0126 wsgi_request = self._coerce_wsgi_request(wsgi_request)
0127 headers = self._coerce_headers(headers)
0128 if isinstance(output, basestring) and output.startswith('name '):
0129 output = get_format(output[5:].strip())
0130 input, body, headers = self._coerce_input(
0131 input, body, headers)
0132 if body and not header_value(headers, 'content-type'):
0133
0134 content_type = input.choose_mimetype(headers, body)
0135 replace_header(headers, 'content-type', content_type)
0136 headers = self._set_accept(headers, output)
0137 if wsgi_request is not None:
0138 uri = self._resolve_uri(uri, wsgi_request)
0139 if self._internally_resolvable(uri, wsgi_request):
0140 return self._internal_request(
0141 uri, method=method, body=body, headers=headers,
0142 wsgi_request=wsgi_request,
0143 input=input, output=output, trusted=trusted)
0144 else:
0145 if not scheme_re.search(uri):
0146 raise ValueError(
0147 'You gave a non-absolute URI (%r) and no wsgi_request to '
0148 'normalize it against' % uri)
0149 return self._external_request(
0150 uri, method=method, body=body, headers=headers,
0151 wsgi_request=wsgi_request,
0152 input=input, output=output, trusted=trusted)
0153
0154 def _set_accept(self, headers, output):
0155 if not output:
0156
0157 return
0158 if isinstance(output, Format):
0159 accept = output.content_types
0160 elif isinstance(output, basestring):
0161
0162 accept = find_accept_for_type(output)
0163 else:
0164 raise TypeError(
0165 "output should be a mimetype or Format object, not %r"
0166 % output)
0167 replace_header(headers, 'Accept', ', '.join(accept))
0168 return headers
0169
0170
0171 def _resolve_uri(self, uri, wsgi_request):
0172 orig_uri = construct_url(wsgi_request)
0173 return urlparse.urljoin(orig_uri, uri)
0174
0175 def _coerce_wsgi_request(self, wsgi_request):
0176 if wsgi_request is not None and hasattr(wsgi_request, 'environ'):
0177 wsgi_request = wsgi_request.environ
0178 return wsgi_request
0179
0180 def _coerce_headers(self, headers):
0181 if headers is None:
0182 return []
0183 if hasattr(headers, 'headers'):
0184
0185
0186
0187 return [
0188 tuple(h.split(': ', 1)) for h in headers.headers]
0189 elif hasattr(headers, items):
0190 return headers.items()
0191 else:
0192 return list(headers)
0193
0194 def _coerce_input(self, input, body, headers):
0195
0196 if body is None or body == '':
0197 return None, '', headers
0198 if isinstance(input, Format):
0199
0200 return input, body, headers
0201 if isinstance(input, basestring) and input.startswith('name '):
0202
0203 input = get_format(input[5:].strip())
0204 return input, body, headers
0205 if not input and isinstance(body, basestring):
0206 if isinstance(body, unicode):
0207 if not self.default_input_encoding:
0208 raise ValueError(
0209 "There is no default_input_encoding, and you gave a unicode request body")
0210 input = input.encode(self.default_input_encoding)
0211
0212
0213 return input, body, headers
0214 if not input:
0215
0216
0217 if self.default_input_format:
0218 input = self.default_input_format
0219 else:
0220 raise ValueError(
0221 "You gave a non-string body (%r) and no input "
0222 "(nor is there a default_input_format)" % body)
0223 return input, body, headers
0224 if isinstance(input, basestring):
0225
0226 input = find_format_by_type(
0227 input, self.prefer_input_mimetypes)
0228 else:
0229
0230 raise TypeError(
0231 "Invalid value for input: %r" % input)
0232 return input, body, headers
0233
0234 def _internally_resolvable(self, uri, wsgi_request):
0235 if self.mock_wsgi_app is not None:
0236 return True
0237 if 'paste.recursive.script_name' not in wsgi_request:
0238 return False
0239 scheme, netloc, path, qs, fragment = urlparse.urlsplit(uri)
0240 if scheme != wsgi_request.get('wsgi.url_scheme', False):
0241 return False
0242 if 'HTTP_HOST' not in wsgi_request:
0243 return False
0244 if (self._normalize_netloc(scheme, netloc) !=
0245 self._normalize_netloc(wsgi_request['wsgi.url_scheme'], wsgi_request['HTTP_HOST'])):
0246 return False
0247 script_name = wsgi_request['paste.recursive.script_name']
0248 if not path.startswith(script_name):
0249 return False
0250 return True
0251
0252 def _normalize_netloc(self, scheme, netloc):
0253 if ':' not in netloc:
0254 if scheme.lower() == 'http':
0255 netloc += ':80'
0256 elif scheme.lower() == 'https':
0257 netloc += '443'
0258 else:
0259 raise ValueError(
0260 'Do not understand scheme: %r' % scheme)
0261 return netloc
0262
0263 def _internal_request(self, uri, method, body, headers,
0264 wsgi_request, input, output, trusted):
0265 if self.mock_wsgi_app is not None:
0266 script_name = ''
0267 app = self.mock_wsgi_app
0268 else:
0269 script_name = wsgi_request['paste.recursive.script_name']
0270 app = wsgi_request['paste.recursive.include_app_iter'].application
0271 scheme, netloc, path, fragment, qs = urlparse.urlsplit(uri)
0272 environ = self._make_internal_environ(
0273 uri, script_name, method, input, body, headers,
0274 wsgi_request)
0275 out = []
0276 caught = []
0277 def start_response(status, headers, exc_info=None):
0278 caught[:] = [status, headers]
0279
0280 return out.append
0281 app_iter = app(environ, start_response)
0282 if out or not caught:
0283
0284
0285 try:
0286 for item in app_iter:
0287 out.append(item)
0288 finally:
0289 if hasattr(app_iter, 'close'):
0290 app_iter.close()
0291 if not caught:
0292 raise Exception(
0293 "Application %r did not call start_response"
0294 % app)
0295 status, headers = caught
0296 return self._create_response(
0297 status, headers, output, app_iter=out)
0298 else:
0299 status, headers = caught
0300 return self._create_response(
0301 status, headers, output, app_iter, trusted)
0302
0303 def _make_internal_environ(self, uri, script_name, method,
0304 input, body, headers, wsgi_request):
0305 scheme, netloc, path, qs, fragment = urlparse.urlsplit(uri)
0306 assert path.startswith(script_name)
0307 path_info = path[len(script_name):]
0308 assert not path_info or path_info.startswith('/')
0309 if ':' in netloc:
0310 server_name, server_port = netloc.split(':', 1)
0311 else:
0312 server_name = netloc
0313 if scheme == 'http':
0314 server_port = '80'
0315 elif scheme == 'https':
0316 server_port = '443'
0317 else:
0318 raise TypeError(
0319 "Unknown scheme: %r" % scheme)
0320 wsgi_input, content_length = self._make_input(input, body, headers, True)
0321 environ = {
0322 'REQUEST_METHOD': method,
0323 'SCRIPT_NAME': script_name,
0324 'PATH_INFO': path_info,
0325 'SERVER_NAME': server_name,
0326 'SERVER_PORT': server_port,
0327 'SERVER_PROTOCOL': "HTTP/1.0",
0328 'wsgi.version': (1, 0),
0329 'wsgi.url_scheme': scheme,
0330 'wsgi.input': wsgi_input,
0331 'CONTENT_LENGTH': content_length,
0332
0333 'wsgi.errors': wsgi_request['wsgi.errors'],
0334 'wsgi.multithread': wsgi_request['wsgi.multithread'],
0335 'wsgi.multiprocess': wsgi_request['wsgi.multiprocess'],
0336 'wsgi.run_once': False,
0337 'httpencode.internal_request': True
0338 }
0339 for name, value in headers:
0340 name = 'HTTP_' + name.upper().replace('-', '_')
0341 if name == 'HTTP_CONTENT_TYPE':
0342 name = 'CONTENT_TYPE'
0343 elif name == 'HTTP_CONTENT_LENGTH':
0344 name = 'CONTENT_LENGTH'
0345 environ[name] = value
0346 return environ
0347
0348 def _make_input(self, input, body, headers, internal):
0349 if input:
0350 return input.make_wsgi_input_length(body, headers, internal)
0351 else:
0352 return StringIO(body), str(len(body))
0353
0354 def _serialize_body(self, input, body, headers):
0355 if input:
0356 return ''.join(input.dump_iter(body, header_value(headers, 'content-type')))
0357 else:
0358 return body
0359
0360 def _external_request(self, uri, method, body, headers,
0361 wsgi_request, input, output, trusted):
0362 body = self._serialize_body(input, body, headers)
0363
0364 dict_headers = MultiDict(headers)
0365 (res, content) = self.httplib2.request(
0366 uri, method=method,
0367 body=body, headers=dict_headers, redirections=self.redirections)
0368 status = '%s %s' % (res.status, res.reason)
0369
0370 headers = res.items()
0371 remove_header(headers, 'status')
0372 return self._create_response(
0373 status, headers, output, [content], trusted)
0374
0375 def _create_response(self, status, headers, output, app_iter, trusted):
0376 content_type = header_value(headers, 'content-type')
0377 if app_iter is None:
0378
0379
0380 return self._make_response(
0381 status, headers, data=None)
0382 if not output:
0383
0384
0385 return self._make_response(
0386 status, headers, data=content)
0387 if isinstance(output, basestring):
0388 if output.startswith('name '):
0389 output = get_format(output[5:].strip())
0390 else:
0391
0392 output = find_format_match(
0393 output, content_type)
0394 elif isinstance(output, Format):
0395 pass
0396 else:
0397 raise TypeError(
0398 "Invalid value for output: %r" % output)
0399 data = output.parse_wsgi_response(
0400 status, headers, app_iter, trusted=trusted)
0401 return self._make_response(
0402 status, headers, data=data)
0403
0404 def _make_response(self, status, headers, data):
0405 return data