debian-quixote3/quixote/server/simple_server.py

156 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""A simple, single threaded, synchronous HTTP server.
"""
import sys
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import BaseServer
import urllib.request, urllib.parse, urllib.error
import quixote
from quixote import get_publisher
from quixote.util import import_object
try:
from scgi.systemd_socket import get_systemd_socket
except ImportError:
def get_systemd_socket():
return None
class SockInheritHTTPServer(HTTPServer):
def __init__(self, address_info, handler, bind_and_activate=True):
# This is ugly. We have to re-implement HTTPServer.__init__
# and server_bind(). We want to get the inherited socket if
# available. If we inherit then we need to skip the bind() call.
BaseServer.__init__(self, address_info, handler)
sock = get_systemd_socket()
if sock is not None:
print('Using inherited socket %s' % (sock.getsockname(),))
self._skip_bind = True
else:
sock = socket.socket(self.address_family, self.socket_type)
self._skip_bind = False
self.socket = sock
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
def server_bind(self):
if not self._skip_bind:
HTTPServer.server_bind(self)
else:
self.server_address = self.socket.getsockname()
host, port = self.socket.getsockname()[:2]
self.server_name = socket.getfqdn(host)
self.server_port = port
class HTTPRequestHandler(BaseHTTPRequestHandler):
required_cgi_environment = {}
protocol_version = 'HTTP/1.1'
def get_cgi_env(self, method):
env = dict(
SERVER_SOFTWARE="Quixote/%s" % quixote.__version__,
SERVER_NAME=self.server.server_name,
GATEWAY_INTERFACE='CGI/1.1',
SERVER_PROTOCOL=self.protocol_version,
SERVER_PORT=str(self.server.server_port),
REQUEST_METHOD=method,
REMOTE_ADDR=self.client_address[0],
SCRIPT_NAME='')
if '?' in self.path:
env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?', 1)
else:
env['PATH_INFO'] = self.path
env['PATH_INFO'] = urllib.parse.unquote(env['PATH_INFO'])
env['CONTENT_TYPE'] = self.headers.get('content-type')
env['CONTENT_LENGTH'] = self.headers.get('content-length') or "0"
for name, value in self.headers.items():
header_name = 'HTTP_' + name.upper().replace('-', '_')
env[header_name] = value
accept = []
for line in self.headers.getallmatchingheaders('accept'):
if line[:1] in "\t\n\r ":
accept.append(line.strip())
else:
accept = accept + line[7:].split(',')
env['HTTP_ACCEPT'] = ','.join(accept)
co = [c for c in self.headers.get_all('cookie') or [] if c]
if co:
env['HTTP_COOKIE'] = ', '.join(co)
env.update(self.required_cgi_environment)
return env
def process(self, env, include_body=True):
response = get_publisher().process(self.rfile, env)
if self.protocol_version == 'HTTP/1.1':
# single threaded server, persistent connections will block others
response.set_header('connection', 'close')
try:
self.send_response(response.get_status_code(), response.get_reason_phrase())
self.flush_headers()
response.write(self.wfile, include_status=False, include_body=include_body)
except IOError as err:
print("IOError while sending response ignored: %s" % err)
def do_POST(self):
return self.process(self.get_cgi_env('POST'))
def do_GET(self):
return self.process(self.get_cgi_env('GET'))
def do_HEAD(self):
return self.process(self.get_cgi_env('HEAD'), include_body=False)
def do_OPTIONS(self):
return self.process(self.get_cgi_env('OPTIONS'), include_body=False)
def send_response(self, code, message=None):
"""
Copied, with regret, from BaseHTTPRequestHandler, except that the line
that adds the 'Date' header is removed to avoid duplicating the one
that Quixote adds and the log_request() call has been removed.
"""
self.send_response_only(code, message)
self.send_header('Server', self.version_string())
def run(create_publisher, host='', port=80, https=False):
"""Runs a simple, single threaded, synchronous HTTP server that
publishes a Quixote application.
"""
if https:
HTTPRequestHandler.required_cgi_environment['HTTPS'] = 'on'
httpd = SockInheritHTTPServer((host, port), HTTPRequestHandler)
def handle_error(request, client_address):
HTTPServer.handle_error(httpd, request, client_address)
if sys.exc_info()[0] is SystemExit:
raise
httpd.handle_error = handle_error
publisher = create_publisher()
if publisher.logger.access_log is None:
publisher.logger.access_log = sys.stdout
try:
httpd.serve_forever()
finally:
httpd.server_close()
if __name__ == '__main__':
from quixote.server.util import get_server_parser
parser = get_server_parser(run.__doc__)
parser.add_option(
'--https', dest="https", default=False, action="store_true",
help=("Force the scheme for all requests to be https. "
"Not that this is for running the simple server "
"through a proxy or tunnel that provides real SSL "
"support. The simple server itself does not. "))
(options, args) = parser.parse_args()
run(import_object(options.factory), host=options.host, port=options.port,
https=options.https)