Kaydet (Commit) 587c98c8 authored tarafından Martin v. Löwis's avatar Martin v. Löwis

Patch #430706: Persistent connections in BaseHTTPServer.

üst 8ec03e05
...@@ -123,9 +123,12 @@ class variable. ...@@ -123,9 +123,12 @@ class variable.
\end{memberdesc} \end{memberdesc}
\begin{memberdesc}{protocol_version} \begin{memberdesc}{protocol_version}
This specifies the HTTP protocol version used in responses. This specifies the HTTP protocol version used in responses. If set
Typically, this should not be overridden. Defaults to to \code{'HTTP/1.1'}, the server will permit HTTP persistent
\code{'HTTP/1.0'}. connections; however, your server \emph{must} then include an
accurate \code{Content-Length} header (using \method{send_header()})
in all of its responses to clients. For backwards compatibility,
the setting defaults to \code{'HTTP/1.0'}.
\end{memberdesc} \end{memberdesc}
\begin{memberdesc}{MessageClass} \begin{memberdesc}{MessageClass}
...@@ -148,9 +151,16 @@ error response, and \var{longmessage} as the \var{explain} key ...@@ -148,9 +151,16 @@ error response, and \var{longmessage} as the \var{explain} key
A \class{BaseHTTPRequestHandler} instance has the following methods: A \class{BaseHTTPRequestHandler} instance has the following methods:
\begin{methoddesc}{handle}{} \begin{methoddesc}{handle}{}
Overrides the superclass' \method{handle()} method to provide the Calls \method{handle_one_request()} once (or, if persistent connections
specific handler behavior. This method will parse and dispatch are enabled, multiple times) to handle incoming HTTP requests.
the request to the appropriate \method{do_*()} method. You should never need to override it; instead, implement appropriate
\method{do_*()} methods.
\end{methoddesc}
\begin{methoddesc}{handle_one_request}{}
This method will parse and dispatch
the request to the appropriate \method{do_*()} method. You should
never need to override it.
\end{methoddesc} \end{methoddesc}
\begin{methoddesc}{send_error}{code\optional{, message}} \begin{methoddesc}{send_error}{code\optional{, message}}
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
Note: the class in this module doesn't implement any HTTP request; see Note: the class in this module doesn't implement any HTTP request; see
SimpleHTTPServer for simple implementations of GET, HEAD and POST SimpleHTTPServer for simple implementations of GET, HEAD and POST
(including CGI scripts). (including CGI scripts). It does, however, optionally implement HTTP/1.1
persistent connections, as of version 0.3.
Contents: Contents:
...@@ -11,12 +12,9 @@ Contents: ...@@ -11,12 +12,9 @@ Contents:
XXX To do: XXX To do:
- send server version
- log requests even later (to capture byte count) - log requests even later (to capture byte count)
- log user-agent header and other interesting goodies - log user-agent header and other interesting goodies
- send error log to separate file - send error log to separate file
- are request names really case sensitive?
""" """
...@@ -28,7 +26,15 @@ XXX To do: ...@@ -28,7 +26,15 @@ XXX To do:
# Expires September 8, 1995 March 8, 1995 # Expires September 8, 1995 March 8, 1995
# #
# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt # URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
#
# and
#
# Network Working Group R. Fielding
# Request for Comments: 2616 et al
# Obsoletes: 2068 June 1999
# Category: Standards Track
#
# URL: http://www.faqs.org/rfcs/rfc2616.html
# Log files # Log files
# --------- # ---------
...@@ -60,8 +66,7 @@ XXX To do: ...@@ -60,8 +66,7 @@ XXX To do:
# (Actually, the latter is only true if you know the server configuration # (Actually, the latter is only true if you know the server configuration
# at the time the request was made!) # at the time the request was made!)
__version__ = "0.3"
__version__ = "0.2"
__all__ = ["HTTPServer", "BaseHTTPRequestHandler"] __all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
...@@ -70,6 +75,7 @@ import time ...@@ -70,6 +75,7 @@ import time
import socket # For gethostbyaddr() import socket # For gethostbyaddr()
import mimetools import mimetools
import SocketServer import SocketServer
import cStringIO
# Default error message # Default error message
DEFAULT_ERROR_MESSAGE = """\ DEFAULT_ERROR_MESSAGE = """\
...@@ -122,9 +128,9 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -122,9 +128,9 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
where <command> is a (case-sensitive) keyword such as GET or POST, where <command> is a (case-sensitive) keyword such as GET or POST,
<path> is a string containing path information for the request, <path> is a string containing path information for the request,
and <version> should be the string "HTTP/1.0". <path> is encoded and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
using the URL encoding scheme (using %xx to signify the ASCII <path> is encoded using the URL encoding scheme (using %xx to signify
character with hex code xx). the ASCII character with hex code xx).
The protocol is vague about whether lines are separated by LF The protocol is vague about whether lines are separated by LF
characters or by CRLF pairs -- for compatibility with the widest characters or by CRLF pairs -- for compatibility with the widest
...@@ -143,7 +149,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -143,7 +149,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
0.9 request; this form has no optional headers and data part and 0.9 request; this form has no optional headers and data part and
the reply consists of just the data. the reply consists of just the data.
The reply form of the HTTP 1.0 protocol again has three parts: The reply form of the HTTP 1.x protocol again has three parts:
1. One line giving the response code 1. One line giving the response code
2. An optional set of RFC-822-style headers 2. An optional set of RFC-822-style headers
...@@ -155,7 +161,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -155,7 +161,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
<version> <responsecode> <responsestring> <version> <responsecode> <responsestring>
where <version> is the protocol version (always "HTTP/1.0"), where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
<responsecode> is a 3-digit response code indicating success or <responsecode> is a 3-digit response code indicating success or
failure of the request, and <responsestring> is an optional failure of the request, and <responsestring> is an optional
human-readable string explaining what the response code means. human-readable string explaining what the response code means.
...@@ -221,6 +227,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -221,6 +227,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
""" """
self.request_version = version = "HTTP/0.9" # Default self.request_version = version = "HTTP/0.9" # Default
self.close_connection = 1
requestline = self.raw_requestline requestline = self.raw_requestline
if requestline[-2:] == '\r\n': if requestline[-2:] == '\r\n':
requestline = requestline[:-2] requestline = requestline[:-2]
...@@ -233,20 +240,52 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -233,20 +240,52 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
if version[:5] != 'HTTP/': if version[:5] != 'HTTP/':
self.send_error(400, "Bad request version (%s)" % `version`) self.send_error(400, "Bad request version (%s)" % `version`)
return 0 return 0
try:
version_number = float(version.split('/', 1)[1])
except ValueError:
self.send_error(400, "Bad request version (%s)" % `version`)
return 0
if version_number >= 1.1 and self.protocol_version >= "HTTP/1.1":
self.close_connection = 0
if version_number >= 2.0:
self.send_error(505,
"Invalid HTTP Version (%f)" % version_number)
return 0
elif len(words) == 2: elif len(words) == 2:
[command, path] = words [command, path] = words
self.close_connection = 1
if command != 'GET': if command != 'GET':
self.send_error(400, self.send_error(400,
"Bad HTTP/0.9 request type (%s)" % `command`) "Bad HTTP/0.9 request type (%s)" % `command`)
return 0 return 0
elif not words:
return 0
else: else:
self.send_error(400, "Bad request syntax (%s)" % `requestline`) self.send_error(400, "Bad request syntax (%s)" % `requestline`)
return 0 return 0
self.command, self.path, self.request_version = command, path, version self.command, self.path, self.request_version = command, path, version
self.headers = self.MessageClass(self.rfile, 0)
# Deal with pipelining
bytes = ""
while 1:
line = self.rfile.readline()
bytes = bytes + line
if line == '\r\n' or line == '\n' or line == '':
break
# Examine the headers and look for a Connection directive
hfile = cStringIO.StringIO(bytes)
self.headers = self.MessageClass(hfile)
conntype = self.headers.get('Connection', "")
if conntype.lower() == 'close':
self.close_connection = 1
elif (conntype.lower() == 'keep-alive' and
self.protocol_version >= "HTTP/1.1"):
self.close_connection = 0
return 1 return 1
def handle(self): def handle_one_request(self):
"""Handle a single HTTP request. """Handle a single HTTP request.
You normally don't need to override this method; see the class You normally don't need to override this method; see the class
...@@ -254,8 +293,10 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -254,8 +293,10 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
commands such as GET and POST. commands such as GET and POST.
""" """
self.raw_requestline = self.rfile.readline() self.raw_requestline = self.rfile.readline()
if not self.raw_requestline:
self.close_connection = 1
return
if not self.parse_request(): # An error code has been sent, just exit if not self.parse_request(): # An error code has been sent, just exit
return return
mname = 'do_' + self.command mname = 'do_' + self.command
...@@ -265,6 +306,14 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -265,6 +306,14 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
method = getattr(self, mname) method = getattr(self, mname)
method() method()
def handle(self):
"""Handle multiple requests if necessary."""
self.close_connection = 1
self.handle_one_request()
while not self.close_connection:
self.handle_one_request()
def send_error(self, code, message=None): def send_error(self, code, message=None):
"""Send and log an error reply. """Send and log an error reply.
...@@ -286,13 +335,14 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -286,13 +335,14 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
message = short message = short
explain = long explain = long
self.log_error("code %d, message %s", code, message) self.log_error("code %d, message %s", code, message)
content = (self.error_message_format %
{'code': code, 'message': message, 'explain': explain})
self.send_response(code, message) self.send_response(code, message)
self.send_header("Content-Type", "text/html") self.send_header("Content-Type", "text/html")
self.send_header('Connection', 'close')
self.end_headers() self.end_headers()
self.wfile.write(self.error_message_format % if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
{'code': code, self.wfile.write(content)
'message': message,
'explain': explain})
error_message_format = DEFAULT_ERROR_MESSAGE error_message_format = DEFAULT_ERROR_MESSAGE
...@@ -305,13 +355,14 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -305,13 +355,14 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
""" """
self.log_request(code) self.log_request(code)
if message is None: if message is None:
if self.responses.has_key(code): if code in self.responses:
message = self.responses[code][0] message = self.responses[code][0]
else: else:
message = '' message = ''
if self.request_version != 'HTTP/0.9': if self.request_version != 'HTTP/0.9':
self.wfile.write("%s %s %s\r\n" % self.wfile.write("%s %d %s\r\n" %
(self.protocol_version, str(code), message)) (self.protocol_version, code, message))
# print (self.protocol_version, code, message)
self.send_header('Server', self.version_string()) self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string()) self.send_header('Date', self.date_time_string())
...@@ -320,6 +371,12 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -320,6 +371,12 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
if self.request_version != 'HTTP/0.9': if self.request_version != 'HTTP/0.9':
self.wfile.write("%s: %s\r\n" % (keyword, value)) self.wfile.write("%s: %s\r\n" % (keyword, value))
if keyword.lower() == 'connection':
if value.lower() == 'close':
self.close_connection = 1
elif value.lower() == 'keep-alive':
self.close_connection = 0
def end_headers(self): def end_headers(self):
"""Send the blank line ending the MIME headers.""" """Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9': if self.request_version != 'HTTP/0.9':
...@@ -413,8 +470,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -413,8 +470,7 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
# Essentially static class variables # Essentially static class variables
# The version of the HTTP protocol we support. # The version of the HTTP protocol we support.
# Don't override unless you know what you're doing (hint: incoming # Set this to HTTP/1.1 to enable automatic keepalive
# requests are required to have exactly this version string).
protocol_version = "HTTP/1.0" protocol_version = "HTTP/1.0"
# The Message-like class used to parse headers # The Message-like class used to parse headers
...@@ -424,18 +480,31 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -424,18 +480,31 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
# form {code: (shortmessage, longmessage)}. # form {code: (shortmessage, longmessage)}.
# See http://www.w3.org/hypertext/WWW/Protocols/HTTP/HTRESP.html # See http://www.w3.org/hypertext/WWW/Protocols/HTTP/HTRESP.html
responses = { responses = {
100: ('Continue', 'Request received, please continue'),
101: ('Switching Protocols',
'Switching to new protocol; obey Upgrade header'),
200: ('OK', 'Request fulfilled, document follows'), 200: ('OK', 'Request fulfilled, document follows'),
201: ('Created', 'Document created, URL follows'), 201: ('Created', 'Document created, URL follows'),
202: ('Accepted', 202: ('Accepted',
'Request accepted, processing continues off-line'), 'Request accepted, processing continues off-line'),
203: ('Partial information', 'Request fulfilled from cache'), 203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
204: ('No response', 'Request fulfilled, nothing follows'), 204: ('No response', 'Request fulfilled, nothing follows'),
205: ('Reset Content', 'Clear input form for further input.'),
206: ('Partial Content', 'Partial content follows.'),
301: ('Moved', 'Object moved permanently -- see URI list'), 300: ('Multiple Choices',
'Object has several resources -- see URI list'),
301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
302: ('Found', 'Object moved temporarily -- see URI list'), 302: ('Found', 'Object moved temporarily -- see URI list'),
303: ('Method', 'Object moved -- see Method and URL list'), 303: ('See Other', 'Object moved -- see Method and URL list'),
304: ('Not modified', 304: ('Not modified',
'Document has not changed singe given time'), 'Document has not changed since given time'),
305: ('Use Proxy',
'You must use proxy specified in Location to access this '
'resource.'),
307: ('Temporary Redirect',
'Object moved temporarily -- see URI list'),
400: ('Bad request', 400: ('Bad request',
'Bad request syntax or unsupported method'), 'Bad request syntax or unsupported method'),
...@@ -445,21 +514,40 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler): ...@@ -445,21 +514,40 @@ class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
'No payment -- see charging schemes'), 'No payment -- see charging schemes'),
403: ('Forbidden', 403: ('Forbidden',
'Request forbidden -- authorization will not help'), 'Request forbidden -- authorization will not help'),
404: ('Not found', 'Nothing matches the given URI'), 404: ('Not Found', 'Nothing matches the given URI'),
405: ('Method Not Allowed',
'Specified method is invalid for this server.'),
406: ('Not Acceptable', 'URI not available in preferred format.'),
407: ('Proxy Authentication Required', 'You must authenticate with '
'this proxy before proceeding.'),
408: ('Request Time-out', 'Request timed out; try again later.'),
409: ('Conflict', 'Request conflict.'),
410: ('Gone',
'URI no longer exists and has been permanently removed.'),
411: ('Length Required', 'Client must specify Content-Length.'),
412: ('Precondition Failed', 'Precondition in headers is false.'),
413: ('Request Entity Too Large', 'Entity is too large.'),
414: ('Request-URI Too Long', 'URI is too long.'),
415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
416: ('Requested Range Not Satisfiable',
'Cannot satisfy request range.'),
417: ('Expectation Failed',
'Expect condition could not be satisfied.'),
500: ('Internal error', 'Server got itself in trouble'), 500: ('Internal error', 'Server got itself in trouble'),
501: ('Not implemented', 501: ('Not Implemented',
'Server does not support this operation'), 'Server does not support this operation'),
502: ('Service temporarily overloaded', 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
503: ('Service temporarily overloaded',
'The server cannot process the request due to a high load'), 'The server cannot process the request due to a high load'),
503: ('Gateway timeout', 504: ('Gateway timeout',
'The gateway server did not receive a timely response'), 'The gateway server did not receive a timely response'),
505: ('HTTP Version not supported', 'Cannot fulfill request.'),
} }
def test(HandlerClass = BaseHTTPRequestHandler, def test(HandlerClass = BaseHTTPRequestHandler,
ServerClass = HTTPServer): ServerClass = HTTPServer, protocol="HTTP/1.0"):
"""Test the HTTP request handler class. """Test the HTTP request handler class.
This runs an HTTP server on port 8000 (or the first command line This runs an HTTP server on port 8000 (or the first command line
...@@ -473,6 +561,7 @@ def test(HandlerClass = BaseHTTPRequestHandler, ...@@ -473,6 +561,7 @@ def test(HandlerClass = BaseHTTPRequestHandler,
port = 8000 port = 8000
server_address = ('', port) server_address = ('', port)
HandlerClass.protocol_version = protocol
httpd = ServerClass(server_address, HandlerClass) httpd = ServerClass(server_address, HandlerClass)
sa = httpd.socket.getsockname() sa = httpd.socket.getsockname()
......
...@@ -82,6 +82,7 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -82,6 +82,7 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
return None return None
self.send_response(200) self.send_response(200)
self.send_header("Content-type", ctype) self.send_header("Content-type", ctype)
self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
self.end_headers() self.end_headers()
return f return f
...@@ -115,9 +116,11 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -115,9 +116,11 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# Note: a link to a directory displays with @ and links with / # Note: a link to a directory displays with @ and links with /
f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname)) f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
f.write("</ul>\n<hr>\n") f.write("</ul>\n<hr>\n")
length = f.tell()
f.seek(0) f.seek(0)
self.send_response(200) self.send_response(200)
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.send_header("Content-Length", str(length))
self.end_headers() self.end_headers()
return f return f
......
...@@ -44,6 +44,9 @@ Extension modules ...@@ -44,6 +44,9 @@ Extension modules
Library Library
- The BaseHTTPServer implements now optionally HTTP/1.1 persistent
connections.
- socket module: the SSL support was broken out of the main - socket module: the SSL support was broken out of the main
_socket module C helper and placed into a new _ssl helper _socket module C helper and placed into a new _ssl helper
which now gets imported by socket.py if available and working. which now gets imported by socket.py if available and working.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment