aboutsummaryrefslogtreecommitdiff
path: root/samples/python/tornado-httpserver.py
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2012-07-23 15:52:49 -0500
committerJoshua Peek <josh@joshpeek.com>2012-07-23 15:52:49 -0500
commit7b6caa0f6c7603aabb5e6ed657e24aaddbeafc78 (patch)
treeec0522eae75bb04ae73d28ae3f599682a2bfdaeb /samples/python/tornado-httpserver.py
parent314f0e485227915931db361be14d308a210bedea (diff)
Rename samples subdirectories
Diffstat (limited to 'samples/python/tornado-httpserver.py')
-rw-r--r--samples/python/tornado-httpserver.py486
1 files changed, 0 insertions, 486 deletions
diff --git a/samples/python/tornado-httpserver.py b/samples/python/tornado-httpserver.py
deleted file mode 100644
index 5667ba1..0000000
--- a/samples/python/tornado-httpserver.py
+++ /dev/null
@@ -1,486 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2009 Facebook
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""A non-blocking, single-threaded HTTP server.
-
-Typical applications have little direct interaction with the `HTTPServer`
-class except to start a server at the beginning of the process
-(and even that is often done indirectly via `tornado.web.Application.listen`).
-
-This module also defines the `HTTPRequest` class which is exposed via
-`tornado.web.RequestHandler.request`.
-"""
-
-from __future__ import absolute_import, division, with_statement
-
-import Cookie
-import logging
-import socket
-import time
-
-from tornado.escape import utf8, native_str, parse_qs_bytes
-from tornado import httputil
-from tornado import iostream
-from tornado.netutil import TCPServer
-from tornado import stack_context
-from tornado.util import b, bytes_type
-
-try:
- import ssl # Python 2.6+
-except ImportError:
- ssl = None
-
-
-class HTTPServer(TCPServer):
- r"""A non-blocking, single-threaded HTTP server.
-
- A server is defined by a request callback that takes an HTTPRequest
- instance as an argument and writes a valid HTTP response with
- `HTTPRequest.write`. `HTTPRequest.finish` finishes the request (but does
- not necessarily close the connection in the case of HTTP/1.1 keep-alive
- requests). A simple example server that echoes back the URI you
- requested::
-
- import httpserver
- import ioloop
-
- def handle_request(request):
- message = "You requested %s\n" % request.uri
- request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (
- len(message), message))
- request.finish()
-
- http_server = httpserver.HTTPServer(handle_request)
- http_server.listen(8888)
- ioloop.IOLoop.instance().start()
-
- `HTTPServer` is a very basic connection handler. Beyond parsing the
- HTTP request body and headers, the only HTTP semantics implemented
- in `HTTPServer` is HTTP/1.1 keep-alive connections. We do not, however,
- implement chunked encoding, so the request callback must provide a
- ``Content-Length`` header or implement chunked encoding for HTTP/1.1
- requests for the server to run correctly for HTTP/1.1 clients. If
- the request handler is unable to do this, you can provide the
- ``no_keep_alive`` argument to the `HTTPServer` constructor, which will
- ensure the connection is closed on every request no matter what HTTP
- version the client is using.
-
- If ``xheaders`` is ``True``, we support the ``X-Real-Ip`` and ``X-Scheme``
- headers, which override the remote IP and HTTP scheme for all requests.
- These headers are useful when running Tornado behind a reverse proxy or
- load balancer.
-
- `HTTPServer` can serve SSL traffic with Python 2.6+ and OpenSSL.
- To make this server serve SSL traffic, send the ssl_options dictionary
- argument with the arguments required for the `ssl.wrap_socket` method,
- including "certfile" and "keyfile"::
-
- HTTPServer(applicaton, ssl_options={
- "certfile": os.path.join(data_dir, "mydomain.crt"),
- "keyfile": os.path.join(data_dir, "mydomain.key"),
- })
-
- `HTTPServer` initialization follows one of three patterns (the
- initialization methods are defined on `tornado.netutil.TCPServer`):
-
- 1. `~tornado.netutil.TCPServer.listen`: simple single-process::
-
- server = HTTPServer(app)
- server.listen(8888)
- IOLoop.instance().start()
-
- In many cases, `tornado.web.Application.listen` can be used to avoid
- the need to explicitly create the `HTTPServer`.
-
- 2. `~tornado.netutil.TCPServer.bind`/`~tornado.netutil.TCPServer.start`:
- simple multi-process::
-
- server = HTTPServer(app)
- server.bind(8888)
- server.start(0) # Forks multiple sub-processes
- IOLoop.instance().start()
-
- When using this interface, an `IOLoop` must *not* be passed
- to the `HTTPServer` constructor. `start` will always start
- the server on the default singleton `IOLoop`.
-
- 3. `~tornado.netutil.TCPServer.add_sockets`: advanced multi-process::
-
- sockets = tornado.netutil.bind_sockets(8888)
- tornado.process.fork_processes(0)
- server = HTTPServer(app)
- server.add_sockets(sockets)
- IOLoop.instance().start()
-
- The `add_sockets` interface is more complicated, but it can be
- used with `tornado.process.fork_processes` to give you more
- flexibility in when the fork happens. `add_sockets` can
- also be used in single-process servers if you want to create
- your listening sockets in some way other than
- `tornado.netutil.bind_sockets`.
-
- """
- def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
- xheaders=False, ssl_options=None, **kwargs):
- self.request_callback = request_callback
- self.no_keep_alive = no_keep_alive
- self.xheaders = xheaders
- TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
- **kwargs)
-
- def handle_stream(self, stream, address):
- HTTPConnection(stream, address, self.request_callback,
- self.no_keep_alive, self.xheaders)
-
-
-class _BadRequestException(Exception):
- """Exception class for malformed HTTP requests."""
- pass
-
-
-class HTTPConnection(object):
- """Handles a connection to an HTTP client, executing HTTP requests.
-
- We parse HTTP headers and bodies, and execute the request callback
- until the HTTP conection is closed.
- """
- def __init__(self, stream, address, request_callback, no_keep_alive=False,
- xheaders=False):
- self.stream = stream
- self.address = address
- self.request_callback = request_callback
- self.no_keep_alive = no_keep_alive
- self.xheaders = xheaders
- self._request = None
- self._request_finished = False
- # Save stack context here, outside of any request. This keeps
- # contexts from one request from leaking into the next.
- self._header_callback = stack_context.wrap(self._on_headers)
- self.stream.read_until(b("\r\n\r\n"), self._header_callback)
- self._write_callback = None
-
- def write(self, chunk, callback=None):
- """Writes a chunk of output to the stream."""
- assert self._request, "Request closed"
- if not self.stream.closed():
- self._write_callback = stack_context.wrap(callback)
- self.stream.write(chunk, self._on_write_complete)
-
- def finish(self):
- """Finishes the request."""
- assert self._request, "Request closed"
- self._request_finished = True
- if not self.stream.writing():
- self._finish_request()
-
- def _on_write_complete(self):
- if self._write_callback is not None:
- callback = self._write_callback
- self._write_callback = None
- callback()
- # _on_write_complete is enqueued on the IOLoop whenever the
- # IOStream's write buffer becomes empty, but it's possible for
- # another callback that runs on the IOLoop before it to
- # simultaneously write more data and finish the request. If
- # there is still data in the IOStream, a future
- # _on_write_complete will be responsible for calling
- # _finish_request.
- if self._request_finished and not self.stream.writing():
- self._finish_request()
-
- def _finish_request(self):
- if self.no_keep_alive:
- disconnect = True
- else:
- connection_header = self._request.headers.get("Connection")
- if connection_header is not None:
- connection_header = connection_header.lower()
- if self._request.supports_http_1_1():
- disconnect = connection_header == "close"
- elif ("Content-Length" in self._request.headers
- or self._request.method in ("HEAD", "GET")):
- disconnect = connection_header != "keep-alive"
- else:
- disconnect = True
- self._request = None
- self._request_finished = False
- if disconnect:
- self.stream.close()
- return
- self.stream.read_until(b("\r\n\r\n"), self._header_callback)
-
- def _on_headers(self, data):
- try:
- data = native_str(data.decode('latin1'))
- eol = data.find("\r\n")
- start_line = data[:eol]
- try:
- method, uri, version = start_line.split(" ")
- except ValueError:
- raise _BadRequestException("Malformed HTTP request line")
- if not version.startswith("HTTP/"):
- raise _BadRequestException("Malformed HTTP version in HTTP Request-Line")
- headers = httputil.HTTPHeaders.parse(data[eol:])
-
- # HTTPRequest wants an IP, not a full socket address
- if getattr(self.stream.socket, 'family', socket.AF_INET) in (
- socket.AF_INET, socket.AF_INET6):
- # Jython 2.5.2 doesn't have the socket.family attribute,
- # so just assume IP in that case.
- remote_ip = self.address[0]
- else:
- # Unix (or other) socket; fake the remote address
- remote_ip = '0.0.0.0'
-
- self._request = HTTPRequest(
- connection=self, method=method, uri=uri, version=version,
- headers=headers, remote_ip=remote_ip)
-
- content_length = headers.get("Content-Length")
- if content_length:
- content_length = int(content_length)
- if content_length > self.stream.max_buffer_size:
- raise _BadRequestException("Content-Length too long")
- if headers.get("Expect") == "100-continue":
- self.stream.write(b("HTTP/1.1 100 (Continue)\r\n\r\n"))
- self.stream.read_bytes(content_length, self._on_request_body)
- return
-
- self.request_callback(self._request)
- except _BadRequestException, e:
- logging.info("Malformed HTTP request from %s: %s",
- self.address[0], e)
- self.stream.close()
- return
-
- def _on_request_body(self, data):
- self._request.body = data
- content_type = self._request.headers.get("Content-Type", "")
- if self._request.method in ("POST", "PATCH", "PUT"):
- if content_type.startswith("application/x-www-form-urlencoded"):
- arguments = parse_qs_bytes(native_str(self._request.body))
- for name, values in arguments.iteritems():
- values = [v for v in values if v]
- if values:
- self._request.arguments.setdefault(name, []).extend(
- values)
- elif content_type.startswith("multipart/form-data"):
- fields = content_type.split(";")
- for field in fields:
- k, sep, v = field.strip().partition("=")
- if k == "boundary" and v:
- httputil.parse_multipart_form_data(
- utf8(v), data,
- self._request.arguments,
- self._request.files)
- break
- else:
- logging.warning("Invalid multipart/form-data")
- self.request_callback(self._request)
-
-
-class HTTPRequest(object):
- """A single HTTP request.
-
- All attributes are type `str` unless otherwise noted.
-
- .. attribute:: method
-
- HTTP request method, e.g. "GET" or "POST"
-
- .. attribute:: uri
-
- The requested uri.
-
- .. attribute:: path
-
- The path portion of `uri`
-
- .. attribute:: query
-
- The query portion of `uri`
-
- .. attribute:: version
-
- HTTP version specified in request, e.g. "HTTP/1.1"
-
- .. attribute:: headers
-
- `HTTPHeader` dictionary-like object for request headers. Acts like
- a case-insensitive dictionary with additional methods for repeated
- headers.
-
- .. attribute:: body
-
- Request body, if present, as a byte string.
-
- .. attribute:: remote_ip
-
- Client's IP address as a string. If `HTTPServer.xheaders` is set,
- will pass along the real IP address provided by a load balancer
- in the ``X-Real-Ip`` header
-
- .. attribute:: protocol
-
- The protocol used, either "http" or "https". If `HTTPServer.xheaders`
- is set, will pass along the protocol used by a load balancer if
- reported via an ``X-Scheme`` header.
-
- .. attribute:: host
-
- The requested hostname, usually taken from the ``Host`` header.
-
- .. attribute:: arguments
-
- GET/POST arguments are available in the arguments property, which
- maps arguments names to lists of values (to support multiple values
- for individual names). Names are of type `str`, while arguments
- are byte strings. Note that this is different from
- `RequestHandler.get_argument`, which returns argument values as
- unicode strings.
-
- .. attribute:: files
-
- File uploads are available in the files property, which maps file
- names to lists of :class:`HTTPFile`.
-
- .. attribute:: connection
-
- An HTTP request is attached to a single HTTP connection, which can
- be accessed through the "connection" attribute. Since connections
- are typically kept open in HTTP/1.1, multiple requests can be handled
- sequentially on a single connection.
- """
- def __init__(self, method, uri, version="HTTP/1.0", headers=None,
- body=None, remote_ip=None, protocol=None, host=None,
- files=None, connection=None):
- self.method = method
- self.uri = uri
- self.version = version
- self.headers = headers or httputil.HTTPHeaders()
- self.body = body or ""
- if connection and connection.xheaders:
- # Squid uses X-Forwarded-For, others use X-Real-Ip
- self.remote_ip = self.headers.get(
- "X-Real-Ip", self.headers.get("X-Forwarded-For", remote_ip))
- if not self._valid_ip(self.remote_ip):
- self.remote_ip = remote_ip
- # AWS uses X-Forwarded-Proto
- self.protocol = self.headers.get(
- "X-Scheme", self.headers.get("X-Forwarded-Proto", protocol))
- if self.protocol not in ("http", "https"):
- self.protocol = "http"
- else:
- self.remote_ip = remote_ip
- if protocol:
- self.protocol = protocol
- elif connection and isinstance(connection.stream,
- iostream.SSLIOStream):
- self.protocol = "https"
- else:
- self.protocol = "http"
- self.host = host or self.headers.get("Host") or "127.0.0.1"
- self.files = files or {}
- self.connection = connection
- self._start_time = time.time()
- self._finish_time = None
-
- self.path, sep, self.query = uri.partition('?')
- arguments = parse_qs_bytes(self.query)
- self.arguments = {}
- for name, values in arguments.iteritems():
- values = [v for v in values if v]
- if values:
- self.arguments[name] = values
-
- def supports_http_1_1(self):
- """Returns True if this request supports HTTP/1.1 semantics"""
- return self.version == "HTTP/1.1"
-
- @property
- def cookies(self):
- """A dictionary of Cookie.Morsel objects."""
- if not hasattr(self, "_cookies"):
- self._cookies = Cookie.SimpleCookie()
- if "Cookie" in self.headers:
- try:
- self._cookies.load(
- native_str(self.headers["Cookie"]))
- except Exception:
- self._cookies = {}
- return self._cookies
-
- def write(self, chunk, callback=None):
- """Writes the given chunk to the response stream."""
- assert isinstance(chunk, bytes_type)
- self.connection.write(chunk, callback=callback)
-
- def finish(self):
- """Finishes this HTTP request on the open connection."""
- self.connection.finish()
- self._finish_time = time.time()
-
- def full_url(self):
- """Reconstructs the full URL for this request."""
- return self.protocol + "://" + self.host + self.uri
-
- def request_time(self):
- """Returns the amount of time it took for this request to execute."""
- if self._finish_time is None:
- return time.time() - self._start_time
- else:
- return self._finish_time - self._start_time
-
- def get_ssl_certificate(self):
- """Returns the client's SSL certificate, if any.
-
- To use client certificates, the HTTPServer must have been constructed
- with cert_reqs set in ssl_options, e.g.::
-
- server = HTTPServer(app,
- ssl_options=dict(
- certfile="foo.crt",
- keyfile="foo.key",
- cert_reqs=ssl.CERT_REQUIRED,
- ca_certs="cacert.crt"))
-
- The return value is a dictionary, see SSLSocket.getpeercert() in
- the standard library for more details.
- http://docs.python.org/library/ssl.html#sslsocket-objects
- """
- try:
- return self.connection.stream.socket.getpeercert()
- except ssl.SSLError:
- return None
-
- def __repr__(self):
- attrs = ("protocol", "host", "method", "uri", "version", "remote_ip",
- "body")
- args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs])
- return "%s(%s, headers=%s)" % (
- self.__class__.__name__, args, dict(self.headers))
-
- def _valid_ip(self, ip):
- try:
- res = socket.getaddrinfo(ip, 0, socket.AF_UNSPEC,
- socket.SOCK_STREAM,
- 0, socket.AI_NUMERICHOST)
- return bool(res)
- except socket.gaierror, e:
- if e.args[0] == socket.EAI_NONAME:
- return False
- raise
- return True