00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 """A non-blocking, single-threaded HTTP server.
00018
00019 Typical applications have little direct interaction with the `HTTPServer`
00020 class except to start a server at the beginning of the process
00021 (and even that is often done indirectly via `tornado.web.Application.listen`).
00022
00023 This module also defines the `HTTPRequest` class which is exposed via
00024 `tornado.web.RequestHandler.request`.
00025 """
00026
00027 from __future__ import absolute_import, division, with_statement
00028
00029 import Cookie
00030 import logging
00031 import socket
00032 import time
00033
00034 from tornado.escape import utf8, native_str, parse_qs_bytes
00035 from tornado import httputil
00036 from tornado import iostream
00037 from tornado.netutil import TCPServer
00038 from tornado import stack_context
00039 from tornado.util import b, bytes_type
00040
00041 try:
00042 import ssl
00043 except ImportError:
00044 ssl = None
00045
00046
00047 class HTTPServer(TCPServer):
00048 r"""A non-blocking, single-threaded HTTP server.
00049
00050 A server is defined by a request callback that takes an HTTPRequest
00051 instance as an argument and writes a valid HTTP response with
00052 `HTTPRequest.write`. `HTTPRequest.finish` finishes the request (but does
00053 not necessarily close the connection in the case of HTTP/1.1 keep-alive
00054 requests). A simple example server that echoes back the URI you
00055 requested::
00056
00057 import httpserver
00058 import ioloop
00059
00060 def handle_request(request):
00061 message = "You requested %s\n" % request.uri
00062 request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (
00063 len(message), message))
00064 request.finish()
00065
00066 http_server = httpserver.HTTPServer(handle_request)
00067 http_server.listen(8888)
00068 ioloop.IOLoop.instance().start()
00069
00070 `HTTPServer` is a very basic connection handler. Beyond parsing the
00071 HTTP request body and headers, the only HTTP semantics implemented
00072 in `HTTPServer` is HTTP/1.1 keep-alive connections. We do not, however,
00073 implement chunked encoding, so the request callback must provide a
00074 ``Content-Length`` header or implement chunked encoding for HTTP/1.1
00075 requests for the server to run correctly for HTTP/1.1 clients. If
00076 the request handler is unable to do this, you can provide the
00077 ``no_keep_alive`` argument to the `HTTPServer` constructor, which will
00078 ensure the connection is closed on every request no matter what HTTP
00079 version the client is using.
00080
00081 If ``xheaders`` is ``True``, we support the ``X-Real-Ip`` and ``X-Scheme``
00082 headers, which override the remote IP and HTTP scheme for all requests.
00083 These headers are useful when running Tornado behind a reverse proxy or
00084 load balancer.
00085
00086 `HTTPServer` can serve SSL traffic with Python 2.6+ and OpenSSL.
00087 To make this server serve SSL traffic, send the ssl_options dictionary
00088 argument with the arguments required for the `ssl.wrap_socket` method,
00089 including "certfile" and "keyfile"::
00090
00091 HTTPServer(applicaton, ssl_options={
00092 "certfile": os.path.join(data_dir, "mydomain.crt"),
00093 "keyfile": os.path.join(data_dir, "mydomain.key"),
00094 })
00095
00096 `HTTPServer` initialization follows one of three patterns (the
00097 initialization methods are defined on `tornado.netutil.TCPServer`):
00098
00099 1. `~tornado.netutil.TCPServer.listen`: simple single-process::
00100
00101 server = HTTPServer(app)
00102 server.listen(8888)
00103 IOLoop.instance().start()
00104
00105 In many cases, `tornado.web.Application.listen` can be used to avoid
00106 the need to explicitly create the `HTTPServer`.
00107
00108 2. `~tornado.netutil.TCPServer.bind`/`~tornado.netutil.TCPServer.start`:
00109 simple multi-process::
00110
00111 server = HTTPServer(app)
00112 server.bind(8888)
00113 server.start(0) # Forks multiple sub-processes
00114 IOLoop.instance().start()
00115
00116 When using this interface, an `IOLoop` must *not* be passed
00117 to the `HTTPServer` constructor. `start` will always start
00118 the server on the default singleton `IOLoop`.
00119
00120 3. `~tornado.netutil.TCPServer.add_sockets`: advanced multi-process::
00121
00122 sockets = tornado.netutil.bind_sockets(8888)
00123 tornado.process.fork_processes(0)
00124 server = HTTPServer(app)
00125 server.add_sockets(sockets)
00126 IOLoop.instance().start()
00127
00128 The `add_sockets` interface is more complicated, but it can be
00129 used with `tornado.process.fork_processes` to give you more
00130 flexibility in when the fork happens. `add_sockets` can
00131 also be used in single-process servers if you want to create
00132 your listening sockets in some way other than
00133 `tornado.netutil.bind_sockets`.
00134
00135 """
00136 def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
00137 xheaders=False, ssl_options=None, **kwargs):
00138 self.request_callback = request_callback
00139 self.no_keep_alive = no_keep_alive
00140 self.xheaders = xheaders
00141 TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
00142 **kwargs)
00143
00144 def handle_stream(self, stream, address):
00145 HTTPConnection(stream, address, self.request_callback,
00146 self.no_keep_alive, self.xheaders)
00147
00148
00149 class _BadRequestException(Exception):
00150 """Exception class for malformed HTTP requests."""
00151 pass
00152
00153
00154 class HTTPConnection(object):
00155 """Handles a connection to an HTTP client, executing HTTP requests.
00156
00157 We parse HTTP headers and bodies, and execute the request callback
00158 until the HTTP conection is closed.
00159 """
00160 def __init__(self, stream, address, request_callback, no_keep_alive=False,
00161 xheaders=False):
00162 self.stream = stream
00163 self.address = address
00164 self.request_callback = request_callback
00165 self.no_keep_alive = no_keep_alive
00166 self.xheaders = xheaders
00167 self._request = None
00168 self._request_finished = False
00169
00170
00171 self._header_callback = stack_context.wrap(self._on_headers)
00172 self.stream.read_until(b("\r\n\r\n"), self._header_callback)
00173 self._write_callback = None
00174
00175 def write(self, chunk, callback=None):
00176 """Writes a chunk of output to the stream."""
00177 assert self._request, "Request closed"
00178 if not self.stream.closed():
00179 self._write_callback = stack_context.wrap(callback)
00180 self.stream.write(chunk, self._on_write_complete)
00181
00182 def finish(self):
00183 """Finishes the request."""
00184 assert self._request, "Request closed"
00185 self._request_finished = True
00186 if not self.stream.writing():
00187 self._finish_request()
00188
00189 def _on_write_complete(self):
00190 if self._write_callback is not None:
00191 callback = self._write_callback
00192 self._write_callback = None
00193 callback()
00194
00195
00196
00197
00198
00199
00200
00201 if self._request_finished and not self.stream.writing():
00202 self._finish_request()
00203
00204 def _finish_request(self):
00205 if self.no_keep_alive:
00206 disconnect = True
00207 else:
00208 connection_header = self._request.headers.get("Connection")
00209 if connection_header is not None:
00210 connection_header = connection_header.lower()
00211 if self._request.supports_http_1_1():
00212 disconnect = connection_header == "close"
00213 elif ("Content-Length" in self._request.headers
00214 or self._request.method in ("HEAD", "GET")):
00215 disconnect = connection_header != "keep-alive"
00216 else:
00217 disconnect = True
00218 self._request = None
00219 self._request_finished = False
00220 if disconnect:
00221 self.stream.close()
00222 return
00223 self.stream.read_until(b("\r\n\r\n"), self._header_callback)
00224
00225 def _on_headers(self, data):
00226 try:
00227 data = native_str(data.decode('latin1'))
00228 eol = data.find("\r\n")
00229 start_line = data[:eol]
00230 try:
00231 method, uri, version = start_line.split(" ")
00232 except ValueError:
00233 raise _BadRequestException("Malformed HTTP request line")
00234 if not version.startswith("HTTP/"):
00235 raise _BadRequestException("Malformed HTTP version in HTTP Request-Line")
00236 headers = httputil.HTTPHeaders.parse(data[eol:])
00237
00238
00239 if getattr(self.stream.socket, 'family', socket.AF_INET) in (
00240 socket.AF_INET, socket.AF_INET6):
00241
00242
00243 remote_ip = self.address[0]
00244 else:
00245
00246 remote_ip = '0.0.0.0'
00247
00248 self._request = HTTPRequest(
00249 connection=self, method=method, uri=uri, version=version,
00250 headers=headers, remote_ip=remote_ip)
00251
00252 content_length = headers.get("Content-Length")
00253 if content_length:
00254 content_length = int(content_length)
00255 if content_length > self.stream.max_buffer_size:
00256 raise _BadRequestException("Content-Length too long")
00257 if headers.get("Expect") == "100-continue":
00258 self.stream.write(b("HTTP/1.1 100 (Continue)\r\n\r\n"))
00259 self.stream.read_bytes(content_length, self._on_request_body)
00260 return
00261
00262 self.request_callback(self._request)
00263 except _BadRequestException, e:
00264 logging.info("Malformed HTTP request from %s: %s",
00265 self.address[0], e)
00266 self.stream.close()
00267 return
00268
00269 def _on_request_body(self, data):
00270 self._request.body = data
00271 content_type = self._request.headers.get("Content-Type", "")
00272 if self._request.method in ("POST", "PATCH", "PUT"):
00273 if content_type.startswith("application/x-www-form-urlencoded"):
00274 arguments = parse_qs_bytes(native_str(self._request.body))
00275 for name, values in arguments.iteritems():
00276 values = [v for v in values if v]
00277 if values:
00278 self._request.arguments.setdefault(name, []).extend(
00279 values)
00280 elif content_type.startswith("multipart/form-data"):
00281 fields = content_type.split(";")
00282 for field in fields:
00283 k, sep, v = field.strip().partition("=")
00284 if k == "boundary" and v:
00285 httputil.parse_multipart_form_data(
00286 utf8(v), data,
00287 self._request.arguments,
00288 self._request.files)
00289 break
00290 else:
00291 logging.warning("Invalid multipart/form-data")
00292 self.request_callback(self._request)
00293
00294
00295 class HTTPRequest(object):
00296 """A single HTTP request.
00297
00298 All attributes are type `str` unless otherwise noted.
00299
00300 .. attribute:: method
00301
00302 HTTP request method, e.g. "GET" or "POST"
00303
00304 .. attribute:: uri
00305
00306 The requested uri.
00307
00308 .. attribute:: path
00309
00310 The path portion of `uri`
00311
00312 .. attribute:: query
00313
00314 The query portion of `uri`
00315
00316 .. attribute:: version
00317
00318 HTTP version specified in request, e.g. "HTTP/1.1"
00319
00320 .. attribute:: headers
00321
00322 `HTTPHeader` dictionary-like object for request headers. Acts like
00323 a case-insensitive dictionary with additional methods for repeated
00324 headers.
00325
00326 .. attribute:: body
00327
00328 Request body, if present, as a byte string.
00329
00330 .. attribute:: remote_ip
00331
00332 Client's IP address as a string. If `HTTPServer.xheaders` is set,
00333 will pass along the real IP address provided by a load balancer
00334 in the ``X-Real-Ip`` header
00335
00336 .. attribute:: protocol
00337
00338 The protocol used, either "http" or "https". If `HTTPServer.xheaders`
00339 is set, will pass along the protocol used by a load balancer if
00340 reported via an ``X-Scheme`` header.
00341
00342 .. attribute:: host
00343
00344 The requested hostname, usually taken from the ``Host`` header.
00345
00346 .. attribute:: arguments
00347
00348 GET/POST arguments are available in the arguments property, which
00349 maps arguments names to lists of values (to support multiple values
00350 for individual names). Names are of type `str`, while arguments
00351 are byte strings. Note that this is different from
00352 `RequestHandler.get_argument`, which returns argument values as
00353 unicode strings.
00354
00355 .. attribute:: files
00356
00357 File uploads are available in the files property, which maps file
00358 names to lists of :class:`HTTPFile`.
00359
00360 .. attribute:: connection
00361
00362 An HTTP request is attached to a single HTTP connection, which can
00363 be accessed through the "connection" attribute. Since connections
00364 are typically kept open in HTTP/1.1, multiple requests can be handled
00365 sequentially on a single connection.
00366 """
00367 def __init__(self, method, uri, version="HTTP/1.0", headers=None,
00368 body=None, remote_ip=None, protocol=None, host=None,
00369 files=None, connection=None):
00370 self.method = method
00371 self.uri = uri
00372 self.version = version
00373 self.headers = headers or httputil.HTTPHeaders()
00374 self.body = body or ""
00375 if connection and connection.xheaders:
00376
00377 self.remote_ip = self.headers.get(
00378 "X-Real-Ip", self.headers.get("X-Forwarded-For", remote_ip))
00379 if not self._valid_ip(self.remote_ip):
00380 self.remote_ip = remote_ip
00381
00382 self.protocol = self.headers.get(
00383 "X-Scheme", self.headers.get("X-Forwarded-Proto", protocol))
00384 if self.protocol not in ("http", "https"):
00385 self.protocol = "http"
00386 else:
00387 self.remote_ip = remote_ip
00388 if protocol:
00389 self.protocol = protocol
00390 elif connection and isinstance(connection.stream,
00391 iostream.SSLIOStream):
00392 self.protocol = "https"
00393 else:
00394 self.protocol = "http"
00395 self.host = host or self.headers.get("Host") or "127.0.0.1"
00396 self.files = files or {}
00397 self.connection = connection
00398 self._start_time = time.time()
00399 self._finish_time = None
00400
00401 self.path, sep, self.query = uri.partition('?')
00402 arguments = parse_qs_bytes(self.query)
00403 self.arguments = {}
00404 for name, values in arguments.iteritems():
00405 values = [v for v in values if v]
00406 if values:
00407 self.arguments[name] = values
00408
00409 def supports_http_1_1(self):
00410 """Returns True if this request supports HTTP/1.1 semantics"""
00411 return self.version == "HTTP/1.1"
00412
00413 @property
00414 def cookies(self):
00415 """A dictionary of Cookie.Morsel objects."""
00416 if not hasattr(self, "_cookies"):
00417 self._cookies = Cookie.SimpleCookie()
00418 if "Cookie" in self.headers:
00419 try:
00420 self._cookies.load(
00421 native_str(self.headers["Cookie"]))
00422 except Exception:
00423 self._cookies = {}
00424 return self._cookies
00425
00426 def write(self, chunk, callback=None):
00427 """Writes the given chunk to the response stream."""
00428 assert isinstance(chunk, bytes_type)
00429 self.connection.write(chunk, callback=callback)
00430
00431 def finish(self):
00432 """Finishes this HTTP request on the open connection."""
00433 self.connection.finish()
00434 self._finish_time = time.time()
00435
00436 def full_url(self):
00437 """Reconstructs the full URL for this request."""
00438 return self.protocol + "://" + self.host + self.uri
00439
00440 def request_time(self):
00441 """Returns the amount of time it took for this request to execute."""
00442 if self._finish_time is None:
00443 return time.time() - self._start_time
00444 else:
00445 return self._finish_time - self._start_time
00446
00447 def get_ssl_certificate(self):
00448 """Returns the client's SSL certificate, if any.
00449
00450 To use client certificates, the HTTPServer must have been constructed
00451 with cert_reqs set in ssl_options, e.g.::
00452
00453 server = HTTPServer(app,
00454 ssl_options=dict(
00455 certfile="foo.crt",
00456 keyfile="foo.key",
00457 cert_reqs=ssl.CERT_REQUIRED,
00458 ca_certs="cacert.crt"))
00459
00460 The return value is a dictionary, see SSLSocket.getpeercert() in
00461 the standard library for more details.
00462 http://docs.python.org/library/ssl.html#sslsocket-objects
00463 """
00464 try:
00465 return self.connection.stream.socket.getpeercert()
00466 except ssl.SSLError:
00467 return None
00468
00469 def __repr__(self):
00470 attrs = ("protocol", "host", "method", "uri", "version", "remote_ip",
00471 "body")
00472 args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs])
00473 return "%s(%s, headers=%s)" % (
00474 self.__class__.__name__, args, dict(self.headers))
00475
00476 def _valid_ip(self, ip):
00477 try:
00478 res = socket.getaddrinfo(ip, 0, socket.AF_UNSPEC,
00479 socket.SOCK_STREAM,
00480 0, socket.AI_NUMERICHOST)
00481 return bool(res)
00482 except socket.gaierror, e:
00483 if e.args[0] == socket.EAI_NONAME:
00484 return False
00485 raise
00486 return True