diff options
author | Bert JW Regeer <bertjw@regeer.org> | 2017-05-22 17:47:35 -0700 |
---|---|---|
committer | Bert JW Regeer <bertjw@regeer.org> | 2017-05-22 17:47:35 -0700 |
commit | 9c66e8dd7c22da2ebdb25e5e0d4ec0b9883a6b8a (patch) | |
tree | be4d35ed0990bebaa341031cd01a5c6f80ae6252 /src/webob/client.py | |
parent | 7a5c99789cc580f97decf3adf606a3de86689f74 (diff) | |
download | webob-9c66e8dd7c22da2ebdb25e5e0d4ec0b9883a6b8a.tar.gz |
Move files around, make testing better
Diffstat (limited to 'src/webob/client.py')
-rw-r--r-- | src/webob/client.py | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/webob/client.py b/src/webob/client.py new file mode 100644 index 0000000..339e4c0 --- /dev/null +++ b/src/webob/client.py @@ -0,0 +1,180 @@ +import errno +import sys +import re +try: + import httplib +except ImportError: + import http.client as httplib +from webob.compat import url_quote +import socket +from webob import exc +from webob.compat import PY2 + +__all__ = ['send_request_app', 'SendRequest'] + +class SendRequest: + """ + Sends the request, as described by the environ, over actual HTTP. + All controls about how it is sent are contained in the request + environ itself. + + This connects to the server given in SERVER_NAME:SERVER_PORT, and + sends the Host header in HTTP_HOST -- they do not have to match. + You can send requests to servers despite what DNS says. + + Set ``environ['webob.client.timeout'] = 10`` to set the timeout on + the request (to, for example, 10 seconds). + + Does not add X-Forwarded-For or other standard headers + + If you use ``send_request_app`` then simple ``httplib`` + connections will be used. + """ + + def __init__(self, HTTPConnection=httplib.HTTPConnection, + HTTPSConnection=httplib.HTTPSConnection): + self.HTTPConnection = HTTPConnection + self.HTTPSConnection = HTTPSConnection + + def __call__(self, environ, start_response): + scheme = environ['wsgi.url_scheme'] + if scheme == 'http': + ConnClass = self.HTTPConnection + elif scheme == 'https': + ConnClass = self.HTTPSConnection + else: + raise ValueError( + "Unknown scheme: %r" % scheme) + if 'SERVER_NAME' not in environ: + host = environ.get('HTTP_HOST') + if not host: + raise ValueError( + "environ contains neither SERVER_NAME nor HTTP_HOST") + if ':' in host: + host, port = host.split(':', 1) + else: + if scheme == 'http': + port = '80' + else: + port = '443' + environ['SERVER_NAME'] = host + environ['SERVER_PORT'] = port + kw = {} + if ('webob.client.timeout' in environ and + self._timeout_supported(ConnClass) ): + kw['timeout'] = environ['webob.client.timeout'] + conn = ConnClass('%(SERVER_NAME)s:%(SERVER_PORT)s' % environ, **kw) + headers = {} + for key, value in environ.items(): + if key.startswith('HTTP_'): + key = key[5:].replace('_', '-').title() + headers[key] = value + path = (url_quote(environ.get('SCRIPT_NAME', '')) + + url_quote(environ.get('PATH_INFO', ''))) + if environ.get('QUERY_STRING'): + path += '?' + environ['QUERY_STRING'] + try: + content_length = int(environ.get('CONTENT_LENGTH', '0')) + except ValueError: + content_length = 0 + ## FIXME: there is no streaming of the body, and that might be useful + ## in some cases + if content_length: + body = environ['wsgi.input'].read(content_length) + else: + body = '' + headers['Content-Length'] = content_length + if environ.get('CONTENT_TYPE'): + headers['Content-Type'] = environ['CONTENT_TYPE'] + if not path.startswith("/"): + path = "/" + path + try: + conn.request(environ['REQUEST_METHOD'], + path, body, headers) + res = conn.getresponse() + except socket.timeout: + resp = exc.HTTPGatewayTimeout() + return resp(environ, start_response) + except (socket.error, socket.gaierror) as e: + if ((isinstance(e, socket.error) and e.args[0] == -2) or + (isinstance(e, socket.gaierror) and e.args[0] == 8)): + # Name or service not known + resp = exc.HTTPBadGateway( + "Name or service not known (bad domain name: %s)" + % environ['SERVER_NAME']) + return resp(environ, start_response) + elif e.args[0] in _e_refused: # pragma: no cover + # Connection refused + resp = exc.HTTPBadGateway("Connection refused") + return resp(environ, start_response) + raise + headers_out = self.parse_headers(res.msg) + status = '%s %s' % (res.status, res.reason) + start_response(status, headers_out) + length = res.getheader('content-length') + # FIXME: This shouldn't really read in all the content at once + if length is not None: + body = res.read(int(length)) + else: + body = res.read() + conn.close() + return [body] + + # Remove these headers from response (specify lower case header + # names): + filtered_headers = ( + 'transfer-encoding', + ) + + MULTILINE_RE = re.compile(r'\r?\n\s*') + + def parse_headers(self, message): + """ + Turn a Message object into a list of WSGI-style headers. + """ + headers_out = [] + if not PY2: + headers = message._headers + else: + headers = message.headers + for full_header in headers: + if not full_header: # pragma: no cover + # Shouldn't happen, but we'll just ignore + continue + if full_header[0].isspace(): # pragma: no cover + # Continuation line, add to the last header + if not headers_out: + raise ValueError( + "First header starts with a space (%r)" % full_header) + last_header, last_value = headers_out.pop() + value = last_value + ', ' + full_header.strip() + headers_out.append((last_header, value)) + continue + if isinstance(full_header, tuple): # pragma: no cover + header, value = full_header + else: # pragma: no cover + try: + header, value = full_header.split(':', 1) + except: + raise ValueError("Invalid header: %r" % (full_header,)) + value = value.strip() + if '\n' in value or '\r\n' in value: # pragma: no cover + # Python 3 has multiline values for continuations, Python 2 + # has two items in headers + value = self.MULTILINE_RE.sub(', ', value) + if header.lower() not in self.filtered_headers: + headers_out.append((header, value)) + return headers_out + + def _timeout_supported(self, ConnClass): + if sys.version_info < (2, 7) and ConnClass in ( + httplib.HTTPConnection, httplib.HTTPSConnection): # pragma: no cover + return False + return True + + +send_request_app = SendRequest() + +_e_refused = (errno.ECONNREFUSED,) +if hasattr(errno, 'ENODATA'): # pragma: no cover + _e_refused += (errno.ENODATA,) |