/lib/python/gunicorn/http/wsgi.py
Python | 233 lines | 223 code | 6 blank | 4 comment | 0 complexity | 676697224a04b9e532313db8f545c1e9 MD5 | raw file
- # -*- coding: utf-8 -
- #
- # This file is part of gunicorn released under the MIT license.
- # See the NOTICE for more information.
- import logging
- import os
- import re
- import sys
- from urllib import unquote
- from gunicorn import SERVER_SOFTWARE
- import gunicorn.util as util
- NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
- log = logging.getLogger(__name__)
- def create(req, sock, client, server, cfg):
- resp = Response(req, sock)
- environ = {
- "wsgi.input": req.body,
- "wsgi.errors": sys.stderr,
- "wsgi.version": (1, 0),
- "wsgi.multithread": False,
- "wsgi.multiprocess": (cfg.workers > 1),
- "wsgi.run_once": False,
- "gunicorn.socket": sock,
- "SERVER_SOFTWARE": SERVER_SOFTWARE,
- "REQUEST_METHOD": req.method,
- "QUERY_STRING": req.query,
- "RAW_URI": req.uri,
- "SERVER_PROTOCOL": "HTTP/%s" % ".".join(map(str, req.version)),
- "CONTENT_TYPE": "",
- "CONTENT_LENGTH": ""
- }
-
- # authors should be aware that REMOTE_HOST and REMOTE_ADDR
- # may not qualify the remote addr:
- # http://www.ietf.org/rfc/rfc3875
- client = client or "127.0.0.1"
- forward = client
- url_scheme = "http"
- script_name = os.environ.get("SCRIPT_NAME", "")
- for hdr_name, hdr_value in req.headers:
- if hdr_name == "EXPECT":
- # handle expect
- if hdr_value.lower() == "100-continue":
- sock.send("HTTP/1.1 100 Continue\r\n\r\n")
- elif hdr_name == "X-FORWARDED-FOR":
- forward = hdr_value
- elif hdr_name == "X-FORWARDED-PROTOCOL" and hdr_value.lower() == "ssl":
- url_scheme = "https"
- elif hdr_name == "X-FORWARDED-SSL" and hdr_value.lower() == "on":
- url_scheme = "https"
- elif hdr_name == "HOST":
- server = hdr_value
- elif hdr_name == "SCRIPT_NAME":
- script_name = hdr_value
- elif hdr_name == "CONTENT-TYPE":
- environ['CONTENT_TYPE'] = hdr_value
- continue
- elif hdr_name == "CONTENT-LENGTH":
- environ['CONTENT_LENGTH'] = hdr_value
- continue
-
- key = 'HTTP_' + hdr_name.replace('-', '_')
- environ[key] = hdr_value
- environ['wsgi.url_scheme'] = url_scheme
-
- if isinstance(forward, basestring):
- # we only took the last one
- # http://en.wikipedia.org/wiki/X-Forwarded-For
- if forward.find(",") >= 0:
- forward = forward.rsplit(",", 1)[1].strip()
- # find host and port on ipv6 address
- if '[' in forward and ']' in forward:
- host = forward.split(']')[0][1:].lower()
- elif ":" in forward and forward.count(":") == 1:
- host = forward.split(":")[0].lower()
- else:
- host = forward
- forward = forward.split(']')[-1]
- if ":" in forward and forward.count(":") == 1:
- port = forward.split(':', 1)[1]
- else:
- port = 80
- remote = (host, port)
- else:
- remote = forward
- environ['REMOTE_ADDR'] = remote[0]
- environ['REMOTE_PORT'] = str(remote[1])
- if isinstance(server, basestring):
- server = server.split(":")
- if len(server) == 1:
- if url_scheme == "http":
- server.append("80")
- elif url_scheme == "https":
- server.append("443")
- else:
- server.append('')
- environ['SERVER_NAME'] = server[0]
- environ['SERVER_PORT'] = server[1]
- path_info = req.path
- if script_name:
- path_info = path_info.split(script_name, 1)[1]
- environ['PATH_INFO'] = unquote(path_info)
- environ['SCRIPT_NAME'] = script_name
- return resp, environ
- class Response(object):
- def __init__(self, req, sock):
- self.req = req
- self.sock = sock
- self.version = SERVER_SOFTWARE
- self.status = None
- self.chunked = False
- self.must_close = False
- self.headers = []
- self.headers_sent = False
- self.clength = None
- self.sent = 0
- def force_close(self):
- self.must_close = True
- def should_close(self):
- if self.must_close or self.req.should_close():
- return True
- if self.clength is not None or self.chunked:
- return False
- return True
- def start_response(self, status, headers, exc_info=None):
- if exc_info:
- try:
- if self.status and self.headers_sent:
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- exc_info = None
- elif self.status is not None:
- raise AssertionError("Response headers already set!")
- self.status = status
- self.process_headers(headers)
- self.chunked = self.is_chunked()
- return self.write
- def process_headers(self, headers):
- for name, value in headers:
- assert isinstance(name, basestring), "%r is not a string" % name
- lname = name.lower().strip()
- if lname == "content-length":
- self.clength = int(value)
- elif util.is_hoppish(name):
- if lname == "connection":
- # handle websocket
- if value.lower().strip() != "upgrade":
- continue
- else:
- # ignore hopbyhop headers
- continue
- self.headers.append((name.strip(), str(value).strip()))
- def is_chunked(self):
- # Only use chunked responses when the client is
- # speaking HTTP/1.1 or newer and there was
- # no Content-Length header set.
- if self.clength is not None:
- return False
- elif self.req.version <= (1,0):
- return False
- return True
- def default_headers(self):
- connection = "keep-alive"
- if self.should_close():
- connection = "close"
- headers = [
- "HTTP/%s.%s %s\r\n" % (self.req.version[0],
- self.req.version[1], self.status),
- "Server: %s\r\n" % self.version,
- "Date: %s\r\n" % util.http_date(),
- "Connection: %s\r\n" % connection
- ]
- if self.chunked:
- headers.append("Transfer-Encoding: chunked\r\n")
- return headers
- def send_headers(self):
- if self.headers_sent:
- return
- tosend = self.default_headers()
- tosend.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers])
- util.write(self.sock, "%s\r\n" % "".join(tosend))
- self.headers_sent = True
- def write(self, arg):
- self.send_headers()
- assert isinstance(arg, basestring), "%r is not a string." % arg
- arglen = len(arg)
- tosend = arglen
- if self.clength is not None:
- if self.sent >= self.clength:
- # Never write more than self.clength bytes
- return
- tosend = min(self.clength - self.sent, tosend)
- if tosend < arglen:
- arg = arg[:tosend]
- self.sent += tosend
- util.write(self.sock, arg, self.chunked)
- def close(self):
- if not self.headers_sent:
- self.send_headers()
- if self.chunked:
- util.write_chunk(self.sock, "")