[REM] python25-compat
bzr revid: al@openerp.com-20110924153418-kgkrqg0s2mtniofm
This commit is contained in:
parent
02ee5ee64c
commit
94393cc074
|
@ -1,587 +0,0 @@
|
||||||
"""HTTP server base class.
|
|
||||||
|
|
||||||
Note: the class in this module doesn't implement any HTTP request; see
|
|
||||||
SimpleHTTPServer for simple implementations of GET, HEAD and POST
|
|
||||||
(including CGI scripts). It does, however, optionally implement HTTP/1.1
|
|
||||||
persistent connections, as of version 0.3.
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
- BaseHTTPRequestHandler: HTTP request handler base class
|
|
||||||
- test: test function
|
|
||||||
|
|
||||||
XXX To do:
|
|
||||||
|
|
||||||
- log requests even later (to capture byte count)
|
|
||||||
- log user-agent header and other interesting goodies
|
|
||||||
- send error log to separate file
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# See also:
|
|
||||||
#
|
|
||||||
# HTTP Working Group T. Berners-Lee
|
|
||||||
# INTERNET-DRAFT R. T. Fielding
|
|
||||||
# <draft-ietf-http-v10-spec-00.txt> H. Frystyk Nielsen
|
|
||||||
# Expires September 8, 1995 March 8, 1995
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
# ---------
|
|
||||||
#
|
|
||||||
# Here's a quote from the NCSA httpd docs about log file format.
|
|
||||||
#
|
|
||||||
# | The logfile format is as follows. Each line consists of:
|
|
||||||
# |
|
|
||||||
# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb
|
|
||||||
# |
|
|
||||||
# | host: Either the DNS name or the IP number of the remote client
|
|
||||||
# | rfc931: Any information returned by identd for this person,
|
|
||||||
# | - otherwise.
|
|
||||||
# | authuser: If user sent a userid for authentication, the user name,
|
|
||||||
# | - otherwise.
|
|
||||||
# | DD: Day
|
|
||||||
# | Mon: Month (calendar name)
|
|
||||||
# | YYYY: Year
|
|
||||||
# | hh: hour (24-hour format, the machine's timezone)
|
|
||||||
# | mm: minutes
|
|
||||||
# | ss: seconds
|
|
||||||
# | request: The first line of the HTTP request as sent by the client.
|
|
||||||
# | ddd: the status code returned by the server, - if not available.
|
|
||||||
# | bbbb: the total number of bytes sent,
|
|
||||||
# | *not including the HTTP/1.0 header*, - if not available
|
|
||||||
# |
|
|
||||||
# | You can determine the name of the file accessed through request.
|
|
||||||
#
|
|
||||||
# (Actually, the latter is only true if you know the server configuration
|
|
||||||
# at the time the request was made!)
|
|
||||||
|
|
||||||
__version__ = "0.3"
|
|
||||||
|
|
||||||
__all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import socket # For gethostbyaddr()
|
|
||||||
import mimetools
|
|
||||||
import SocketServer
|
|
||||||
|
|
||||||
# Default error message template
|
|
||||||
DEFAULT_ERROR_MESSAGE = """\
|
|
||||||
<head>
|
|
||||||
<title>Error response</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Error response</h1>
|
|
||||||
<p>Error code %(code)d.
|
|
||||||
<p>Message: %(message)s.
|
|
||||||
<p>Error code explanation: %(code)s = %(explain)s.
|
|
||||||
</body>
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEFAULT_ERROR_CONTENT_TYPE = "text/html"
|
|
||||||
|
|
||||||
def _quote_html(html):
|
|
||||||
return html.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
||||||
|
|
||||||
class HTTPServer(SocketServer.TCPServer):
|
|
||||||
|
|
||||||
allow_reuse_address = 1 # Seems to make sense in testing environment
|
|
||||||
|
|
||||||
def server_bind(self):
|
|
||||||
"""Override server_bind to store the server name."""
|
|
||||||
SocketServer.TCPServer.server_bind(self)
|
|
||||||
host, port = self.socket.getsockname()[:2]
|
|
||||||
self.server_name = socket.getfqdn(host)
|
|
||||||
self.server_port = port
|
|
||||||
|
|
||||||
|
|
||||||
class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
|
|
||||||
|
|
||||||
"""HTTP request handler base class.
|
|
||||||
|
|
||||||
The following explanation of HTTP serves to guide you through the
|
|
||||||
code as well as to expose any misunderstandings I may have about
|
|
||||||
HTTP (so you don't need to read the code to figure out I'm wrong
|
|
||||||
:-).
|
|
||||||
|
|
||||||
HTTP (HyperText Transfer Protocol) is an extensible protocol on
|
|
||||||
top of a reliable stream transport (e.g. TCP/IP). The protocol
|
|
||||||
recognizes three parts to a request:
|
|
||||||
|
|
||||||
1. One line identifying the request type and path
|
|
||||||
2. An optional set of RFC-822-style headers
|
|
||||||
3. An optional data part
|
|
||||||
|
|
||||||
The headers and data are separated by a blank line.
|
|
||||||
|
|
||||||
The first line of the request has the form
|
|
||||||
|
|
||||||
<command> <path> <version>
|
|
||||||
|
|
||||||
where <command> is a (case-sensitive) keyword such as GET or POST,
|
|
||||||
<path> is a string containing path information for the request,
|
|
||||||
and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
|
|
||||||
<path> is encoded using the URL encoding scheme (using %xx to signify
|
|
||||||
the ASCII character with hex code xx).
|
|
||||||
|
|
||||||
The specification specifies that lines are separated by CRLF but
|
|
||||||
for compatibility with the widest range of clients recommends
|
|
||||||
servers also handle LF. Similarly, whitespace in the request line
|
|
||||||
is treated sensibly (allowing multiple spaces between components
|
|
||||||
and allowing trailing whitespace).
|
|
||||||
|
|
||||||
Similarly, for output, lines ought to be separated by CRLF pairs
|
|
||||||
but most clients grok LF characters just fine.
|
|
||||||
|
|
||||||
If the first line of the request has the form
|
|
||||||
|
|
||||||
<command> <path>
|
|
||||||
|
|
||||||
(i.e. <version> is left out) then this is assumed to be an HTTP
|
|
||||||
0.9 request; this form has no optional headers and data part and
|
|
||||||
the reply consists of just the data.
|
|
||||||
|
|
||||||
The reply form of the HTTP 1.x protocol again has three parts:
|
|
||||||
|
|
||||||
1. One line giving the response code
|
|
||||||
2. An optional set of RFC-822-style headers
|
|
||||||
3. The data
|
|
||||||
|
|
||||||
Again, the headers and data are separated by a blank line.
|
|
||||||
|
|
||||||
The response code line has the form
|
|
||||||
|
|
||||||
<version> <responsecode> <responsestring>
|
|
||||||
|
|
||||||
where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
|
|
||||||
<responsecode> is a 3-digit response code indicating success or
|
|
||||||
failure of the request, and <responsestring> is an optional
|
|
||||||
human-readable string explaining what the response code means.
|
|
||||||
|
|
||||||
This server parses the request and the headers, and then calls a
|
|
||||||
function specific to the request type (<command>). Specifically,
|
|
||||||
a request SPAM will be handled by a method do_SPAM(). If no
|
|
||||||
such method exists the server sends an error response to the
|
|
||||||
client. If it exists, it is called with no arguments:
|
|
||||||
|
|
||||||
do_SPAM()
|
|
||||||
|
|
||||||
Note that the request name is case sensitive (i.e. SPAM and spam
|
|
||||||
are different requests).
|
|
||||||
|
|
||||||
The various request details are stored in instance variables:
|
|
||||||
|
|
||||||
- client_address is the client IP address in the form (host,
|
|
||||||
port);
|
|
||||||
|
|
||||||
- command, path and version are the broken-down request line;
|
|
||||||
|
|
||||||
- headers is an instance of mimetools.Message (or a derived
|
|
||||||
class) containing the header information;
|
|
||||||
|
|
||||||
- rfile is a file object open for reading positioned at the
|
|
||||||
start of the optional input data part;
|
|
||||||
|
|
||||||
- wfile is a file object open for writing.
|
|
||||||
|
|
||||||
IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!
|
|
||||||
|
|
||||||
The first thing to be written must be the response line. Then
|
|
||||||
follow 0 or more header lines, then a blank line, and then the
|
|
||||||
actual data (if any). The meaning of the header lines depends on
|
|
||||||
the command executed by the server; in most cases, when data is
|
|
||||||
returned, there should be at least one header line of the form
|
|
||||||
|
|
||||||
Content-type: <type>/<subtype>
|
|
||||||
|
|
||||||
where <type> and <subtype> should be registered MIME types,
|
|
||||||
e.g. "text/html" or "text/plain".
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The Python system version, truncated to its first component.
|
|
||||||
sys_version = "Python/" + sys.version.split()[0]
|
|
||||||
|
|
||||||
# The server software version. You may want to override this.
|
|
||||||
# The format is multiple whitespace-separated strings,
|
|
||||||
# where each string is of the form name[/version].
|
|
||||||
server_version = "BaseHTTP/" + __version__
|
|
||||||
|
|
||||||
# The default request version. This only affects responses up until
|
|
||||||
# the point where the request line is parsed, so it mainly decides what
|
|
||||||
# the client gets back when sending a malformed request line.
|
|
||||||
# Most web servers default to HTTP 0.9, i.e. don't send a status line.
|
|
||||||
default_request_version = "HTTP/0.9"
|
|
||||||
|
|
||||||
def parse_request(self):
|
|
||||||
"""Parse a request (internal).
|
|
||||||
|
|
||||||
The request should be stored in self.raw_requestline; the results
|
|
||||||
are in self.command, self.path, self.request_version and
|
|
||||||
self.headers.
|
|
||||||
|
|
||||||
Return True for success, False for failure; on failure, an
|
|
||||||
error is sent back.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.command = None # set in case of error on the first line
|
|
||||||
self.request_version = version = self.default_request_version
|
|
||||||
self.close_connection = 1
|
|
||||||
requestline = self.raw_requestline
|
|
||||||
if requestline[-2:] == '\r\n':
|
|
||||||
requestline = requestline[:-2]
|
|
||||||
elif requestline[-1:] == '\n':
|
|
||||||
requestline = requestline[:-1]
|
|
||||||
self.requestline = requestline
|
|
||||||
words = requestline.split()
|
|
||||||
if len(words) == 3:
|
|
||||||
[command, path, version] = words
|
|
||||||
if version[:5] != 'HTTP/':
|
|
||||||
self.send_error(400, "Bad request version (%r)" % version)
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
base_version_number = version.split('/', 1)[1]
|
|
||||||
version_number = base_version_number.split(".")
|
|
||||||
# RFC 2145 section 3.1 says there can be only one "." and
|
|
||||||
# - major and minor numbers MUST be treated as
|
|
||||||
# separate integers;
|
|
||||||
# - HTTP/2.4 is a lower version than HTTP/2.13, which in
|
|
||||||
# turn is lower than HTTP/12.3;
|
|
||||||
# - Leading zeros MUST be ignored by recipients.
|
|
||||||
if len(version_number) != 2:
|
|
||||||
raise ValueError
|
|
||||||
version_number = int(version_number[0]), int(version_number[1])
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
self.send_error(400, "Bad request version (%r)" % version)
|
|
||||||
return False
|
|
||||||
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 (%s)" % base_version_number)
|
|
||||||
return False
|
|
||||||
elif len(words) == 2:
|
|
||||||
[command, path] = words
|
|
||||||
self.close_connection = 1
|
|
||||||
if command != 'GET':
|
|
||||||
self.send_error(400,
|
|
||||||
"Bad HTTP/0.9 request type (%r)" % command)
|
|
||||||
return False
|
|
||||||
elif not words:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.send_error(400, "Bad request syntax (%r)" % requestline)
|
|
||||||
return False
|
|
||||||
self.command, self.path, self.request_version = command, path, version
|
|
||||||
|
|
||||||
# Examine the headers and look for a Connection directive
|
|
||||||
self.headers = self.MessageClass(self.rfile, 0)
|
|
||||||
|
|
||||||
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 True
|
|
||||||
|
|
||||||
def handle_one_request(self):
|
|
||||||
"""Handle a single HTTP request.
|
|
||||||
|
|
||||||
You normally don't need to override this method; see the class
|
|
||||||
__doc__ string for information on how to handle specific HTTP
|
|
||||||
commands such as GET and POST.
|
|
||||||
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
return
|
|
||||||
mname = 'do_' + self.command
|
|
||||||
if not hasattr(self, mname):
|
|
||||||
self.send_error(501, "Unsupported method (%r)" % self.command)
|
|
||||||
return
|
|
||||||
method = getattr(self, mname)
|
|
||||||
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):
|
|
||||||
"""Send and log an error reply.
|
|
||||||
|
|
||||||
Arguments are the error code, and a detailed message.
|
|
||||||
The detailed message defaults to the short entry matching the
|
|
||||||
response code.
|
|
||||||
|
|
||||||
This sends an error response (so it must be called before any
|
|
||||||
output has been generated), logs the error, and finally sends
|
|
||||||
a piece of HTML explaining the error to the user.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
short, long = self.responses[code]
|
|
||||||
except KeyError:
|
|
||||||
short, long = '???', '???'
|
|
||||||
if message is None:
|
|
||||||
message = short
|
|
||||||
explain = long
|
|
||||||
self.log_error("code %d, message %s", code, message)
|
|
||||||
# using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
|
|
||||||
content = (self.error_message_format %
|
|
||||||
{'code': code, 'message': _quote_html(message), 'explain': explain})
|
|
||||||
self.send_response(code, message)
|
|
||||||
self.send_header("Content-Type", self.error_content_type)
|
|
||||||
self.send_header('Connection', 'close')
|
|
||||||
self.end_headers()
|
|
||||||
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
|
|
||||||
self.wfile.write(content)
|
|
||||||
|
|
||||||
error_message_format = DEFAULT_ERROR_MESSAGE
|
|
||||||
error_content_type = DEFAULT_ERROR_CONTENT_TYPE
|
|
||||||
|
|
||||||
def send_response(self, code, message=None):
|
|
||||||
"""Send the response header and log the response code.
|
|
||||||
|
|
||||||
Also send two standard headers with the server software
|
|
||||||
version and the current date.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.log_request(code)
|
|
||||||
if message is None:
|
|
||||||
if code in self.responses:
|
|
||||||
message = self.responses[code][0]
|
|
||||||
else:
|
|
||||||
message = ''
|
|
||||||
if self.request_version != 'HTTP/0.9':
|
|
||||||
self.wfile.write("%s %d %s\r\n" %
|
|
||||||
(self.protocol_version, code, message))
|
|
||||||
# print (self.protocol_version, code, message)
|
|
||||||
self.send_header('Server', self.version_string())
|
|
||||||
self.send_header('Date', self.date_time_string())
|
|
||||||
|
|
||||||
def send_header(self, keyword, value):
|
|
||||||
"""Send a MIME header."""
|
|
||||||
if self.request_version != 'HTTP/0.9':
|
|
||||||
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):
|
|
||||||
"""Send the blank line ending the MIME headers."""
|
|
||||||
if self.request_version != 'HTTP/0.9':
|
|
||||||
self.wfile.write("\r\n")
|
|
||||||
|
|
||||||
def log_request(self, code='-', size='-'):
|
|
||||||
"""Log an accepted request.
|
|
||||||
|
|
||||||
This is called by send_response().
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.log_message('"%s" %s %s',
|
|
||||||
self.requestline, str(code), str(size))
|
|
||||||
|
|
||||||
def log_error(self, format, *args):
|
|
||||||
"""Log an error.
|
|
||||||
|
|
||||||
This is called when a request cannot be fulfilled. By
|
|
||||||
default it passes the message on to log_message().
|
|
||||||
|
|
||||||
Arguments are the same as for log_message().
|
|
||||||
|
|
||||||
XXX This should go to the separate error log.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.log_message(format, *args)
|
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
|
||||||
"""Log an arbitrary message.
|
|
||||||
|
|
||||||
This is used by all other logging functions. Override
|
|
||||||
it if you have specific logging wishes.
|
|
||||||
|
|
||||||
The first argument, FORMAT, is a format string for the
|
|
||||||
message to be logged. If the format string contains
|
|
||||||
any % escapes requiring parameters, they should be
|
|
||||||
specified as subsequent arguments (it's just like
|
|
||||||
printf!).
|
|
||||||
|
|
||||||
The client host and current date/time are prefixed to
|
|
||||||
every message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
sys.stderr.write("%s - - [%s] %s\n" %
|
|
||||||
(self.address_string(),
|
|
||||||
self.log_date_time_string(),
|
|
||||||
format%args))
|
|
||||||
|
|
||||||
def version_string(self):
|
|
||||||
"""Return the server software version string."""
|
|
||||||
return self.server_version + ' ' + self.sys_version
|
|
||||||
|
|
||||||
def date_time_string(self, timestamp=None):
|
|
||||||
"""Return the current date and time formatted for a message header."""
|
|
||||||
if timestamp is None:
|
|
||||||
timestamp = time.time()
|
|
||||||
year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
|
|
||||||
s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
|
|
||||||
self.weekdayname[wd],
|
|
||||||
day, self.monthname[month], year,
|
|
||||||
hh, mm, ss)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def log_date_time_string(self):
|
|
||||||
"""Return the current time formatted for logging."""
|
|
||||||
now = time.time()
|
|
||||||
year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
|
|
||||||
s = "%02d/%3s/%04d %02d:%02d:%02d" % (
|
|
||||||
day, self.monthname[month], year, hh, mm, ss)
|
|
||||||
return s
|
|
||||||
|
|
||||||
weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
|
||||||
|
|
||||||
monthname = [None,
|
|
||||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
||||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
||||||
|
|
||||||
def address_string(self):
|
|
||||||
"""Return the client address formatted for logging.
|
|
||||||
|
|
||||||
This version looks up the full hostname using gethostbyaddr(),
|
|
||||||
and tries to find a name that contains at least one dot.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
host, port = self.client_address[:2]
|
|
||||||
return socket.getfqdn(host)
|
|
||||||
|
|
||||||
# Essentially static class variables
|
|
||||||
|
|
||||||
# The version of the HTTP protocol we support.
|
|
||||||
# Set this to HTTP/1.1 to enable automatic keepalive
|
|
||||||
protocol_version = "HTTP/1.0"
|
|
||||||
|
|
||||||
# The Message-like class used to parse headers
|
|
||||||
MessageClass = mimetools.Message
|
|
||||||
|
|
||||||
# Table mapping response codes to messages; entries have the
|
|
||||||
# form {code: (shortmessage, longmessage)}.
|
|
||||||
# See RFC 2616.
|
|
||||||
responses = {
|
|
||||||
100: ('Continue', 'Request received, please continue'),
|
|
||||||
101: ('Switching Protocols',
|
|
||||||
'Switching to new protocol; obey Upgrade header'),
|
|
||||||
|
|
||||||
200: ('OK', 'Request fulfilled, document follows'),
|
|
||||||
201: ('Created', 'Document created, URL follows'),
|
|
||||||
202: ('Accepted',
|
|
||||||
'Request accepted, processing continues off-line'),
|
|
||||||
203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
|
|
||||||
204: ('No Content', 'Request fulfilled, nothing follows'),
|
|
||||||
205: ('Reset Content', 'Clear input form for further input.'),
|
|
||||||
206: ('Partial Content', 'Partial content follows.'),
|
|
||||||
|
|
||||||
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'),
|
|
||||||
303: ('See Other', 'Object moved -- see Method and URL list'),
|
|
||||||
304: ('Not Modified',
|
|
||||||
'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',
|
|
||||||
'Bad request syntax or unsupported method'),
|
|
||||||
401: ('Unauthorized',
|
|
||||||
'No permission -- see authorization schemes'),
|
|
||||||
402: ('Payment Required',
|
|
||||||
'No payment -- see charging schemes'),
|
|
||||||
403: ('Forbidden',
|
|
||||||
'Request forbidden -- authorization will not help'),
|
|
||||||
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 Timeout', '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 Server Error', 'Server got itself in trouble'),
|
|
||||||
501: ('Not Implemented',
|
|
||||||
'Server does not support this operation'),
|
|
||||||
502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
|
|
||||||
503: ('Service Unavailable',
|
|
||||||
'The server cannot process the request due to a high load'),
|
|
||||||
504: ('Gateway Timeout',
|
|
||||||
'The gateway server did not receive a timely response'),
|
|
||||||
505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test(HandlerClass = BaseHTTPRequestHandler,
|
|
||||||
ServerClass = HTTPServer, protocol="HTTP/1.0"):
|
|
||||||
"""Test the HTTP request handler class.
|
|
||||||
|
|
||||||
This runs an HTTP server on port 8000 (or the first command line
|
|
||||||
argument).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if sys.argv[1:]:
|
|
||||||
port = int(sys.argv[1])
|
|
||||||
else:
|
|
||||||
port = 8000
|
|
||||||
server_address = ('', port)
|
|
||||||
|
|
||||||
HandlerClass.protocol_version = protocol
|
|
||||||
httpd = ServerClass(server_address, HandlerClass)
|
|
||||||
|
|
||||||
sa = httpd.socket.getsockname()
|
|
||||||
print "Serving HTTP on", sa[0], "port", sa[1], "..."
|
|
||||||
httpd.serve_forever()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test()
|
|
|
@ -1,611 +0,0 @@
|
||||||
"""Simple XML-RPC Server.
|
|
||||||
|
|
||||||
This module can be used to create simple XML-RPC servers
|
|
||||||
by creating a server and either installing functions, a
|
|
||||||
class instance, or by extending the SimpleXMLRPCServer
|
|
||||||
class.
|
|
||||||
|
|
||||||
It can also be used to handle XML-RPC requests in a CGI
|
|
||||||
environment using CGIXMLRPCRequestHandler.
|
|
||||||
|
|
||||||
A list of possible usage patterns follows:
|
|
||||||
|
|
||||||
1. Install functions:
|
|
||||||
|
|
||||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
|
||||||
server.register_function(pow)
|
|
||||||
server.register_function(lambda x,y: x+y, 'add')
|
|
||||||
server.serve_forever()
|
|
||||||
|
|
||||||
2. Install an instance:
|
|
||||||
|
|
||||||
class MyFuncs:
|
|
||||||
def __init__(self):
|
|
||||||
# make all of the string functions available through
|
|
||||||
# string.func_name
|
|
||||||
import string
|
|
||||||
self.string = string
|
|
||||||
def _listMethods(self):
|
|
||||||
# implement this method so that system.listMethods
|
|
||||||
# knows to advertise the strings methods
|
|
||||||
return list_public_methods(self) + \
|
|
||||||
['string.' + method for method in list_public_methods(self.string)]
|
|
||||||
def pow(self, x, y): return pow(x, y)
|
|
||||||
def add(self, x, y) : return x + y
|
|
||||||
|
|
||||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
|
||||||
server.register_introspection_functions()
|
|
||||||
server.register_instance(MyFuncs())
|
|
||||||
server.serve_forever()
|
|
||||||
|
|
||||||
3. Install an instance with custom dispatch method:
|
|
||||||
|
|
||||||
class Math:
|
|
||||||
def _listMethods(self):
|
|
||||||
# this method must be present for system.listMethods
|
|
||||||
# to work
|
|
||||||
return ['add', 'pow']
|
|
||||||
def _methodHelp(self, method):
|
|
||||||
# this method must be present for system.methodHelp
|
|
||||||
# to work
|
|
||||||
if method == 'add':
|
|
||||||
return "add(2,3) => 5"
|
|
||||||
elif method == 'pow':
|
|
||||||
return "pow(x, y[, z]) => number"
|
|
||||||
else:
|
|
||||||
# By convention, return empty
|
|
||||||
# string if no help is available
|
|
||||||
return ""
|
|
||||||
def _dispatch(self, method, params):
|
|
||||||
if method == 'pow':
|
|
||||||
return pow(*params)
|
|
||||||
elif method == 'add':
|
|
||||||
return params[0] + params[1]
|
|
||||||
else:
|
|
||||||
raise 'bad method'
|
|
||||||
|
|
||||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
|
||||||
server.register_introspection_functions()
|
|
||||||
server.register_instance(Math())
|
|
||||||
server.serve_forever()
|
|
||||||
|
|
||||||
4. Subclass SimpleXMLRPCServer:
|
|
||||||
|
|
||||||
class MathServer(SimpleXMLRPCServer):
|
|
||||||
def _dispatch(self, method, params):
|
|
||||||
try:
|
|
||||||
# We are forcing the 'export_' prefix on methods that are
|
|
||||||
# callable through XML-RPC to prevent potential security
|
|
||||||
# problems
|
|
||||||
func = getattr(self, 'export_' + method)
|
|
||||||
except AttributeError:
|
|
||||||
raise Exception('method "%s" is not supported' % method)
|
|
||||||
else:
|
|
||||||
return func(*params)
|
|
||||||
|
|
||||||
def export_add(self, x, y):
|
|
||||||
return x + y
|
|
||||||
|
|
||||||
server = MathServer(("localhost", 8000))
|
|
||||||
server.serve_forever()
|
|
||||||
|
|
||||||
5. CGI script:
|
|
||||||
|
|
||||||
server = CGIXMLRPCRequestHandler()
|
|
||||||
server.register_function(pow)
|
|
||||||
server.handle_request()
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Written by Brian Quinlan (brian@sweetapp.com).
|
|
||||||
# Based on code written by Fredrik Lundh.
|
|
||||||
|
|
||||||
import xmlrpclib
|
|
||||||
from xmlrpclib import Fault
|
|
||||||
import SocketServer
|
|
||||||
import BaseHTTPServer
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import traceback
|
|
||||||
try:
|
|
||||||
import fcntl
|
|
||||||
except ImportError:
|
|
||||||
fcntl = None
|
|
||||||
|
|
||||||
def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
|
|
||||||
"""resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
|
|
||||||
|
|
||||||
Resolves a dotted attribute name to an object. Raises
|
|
||||||
an AttributeError if any attribute in the chain starts with a '_'.
|
|
||||||
|
|
||||||
If the optional allow_dotted_names argument is false, dots are not
|
|
||||||
supported and this function operates similar to getattr(obj, attr).
|
|
||||||
"""
|
|
||||||
|
|
||||||
if allow_dotted_names:
|
|
||||||
attrs = attr.split('.')
|
|
||||||
else:
|
|
||||||
attrs = [attr]
|
|
||||||
|
|
||||||
for i in attrs:
|
|
||||||
if i.startswith('_'):
|
|
||||||
raise AttributeError(
|
|
||||||
'attempt to access private attribute "%s"' % i
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
obj = getattr(obj,i)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def list_public_methods(obj):
|
|
||||||
"""Returns a list of attribute strings, found in the specified
|
|
||||||
object, which represent callable attributes"""
|
|
||||||
|
|
||||||
return [member for member in dir(obj)
|
|
||||||
if not member.startswith('_') and
|
|
||||||
hasattr(getattr(obj, member), '__call__')]
|
|
||||||
|
|
||||||
def remove_duplicates(lst):
|
|
||||||
"""remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
|
|
||||||
|
|
||||||
Returns a copy of a list without duplicates. Every list
|
|
||||||
item must be hashable and the order of the items in the
|
|
||||||
resulting list is not defined.
|
|
||||||
"""
|
|
||||||
u = {}
|
|
||||||
for x in lst:
|
|
||||||
u[x] = 1
|
|
||||||
|
|
||||||
return u.keys()
|
|
||||||
|
|
||||||
class SimpleXMLRPCDispatcher:
|
|
||||||
"""Mix-in class that dispatches XML-RPC requests.
|
|
||||||
|
|
||||||
This class is used to register XML-RPC method handlers
|
|
||||||
and then to dispatch them. There should never be any
|
|
||||||
reason to instantiate this class directly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, allow_none, encoding):
|
|
||||||
self.funcs = {}
|
|
||||||
self.instance = None
|
|
||||||
self.allow_none = allow_none
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
def register_instance(self, instance, allow_dotted_names=False):
|
|
||||||
"""Registers an instance to respond to XML-RPC requests.
|
|
||||||
|
|
||||||
Only one instance can be installed at a time.
|
|
||||||
|
|
||||||
If the registered instance has a _dispatch method then that
|
|
||||||
method will be called with the name of the XML-RPC method and
|
|
||||||
its parameters as a tuple
|
|
||||||
e.g. instance._dispatch('add',(2,3))
|
|
||||||
|
|
||||||
If the registered instance does not have a _dispatch method
|
|
||||||
then the instance will be searched to find a matching method
|
|
||||||
and, if found, will be called. Methods beginning with an '_'
|
|
||||||
are considered private and will not be called by
|
|
||||||
SimpleXMLRPCServer.
|
|
||||||
|
|
||||||
If a registered function matches a XML-RPC request, then it
|
|
||||||
will be called instead of the registered instance.
|
|
||||||
|
|
||||||
If the optional allow_dotted_names argument is true and the
|
|
||||||
instance does not have a _dispatch method, method names
|
|
||||||
containing dots are supported and resolved, as long as none of
|
|
||||||
the name segments start with an '_'.
|
|
||||||
|
|
||||||
*** SECURITY WARNING: ***
|
|
||||||
|
|
||||||
Enabling the allow_dotted_names options allows intruders
|
|
||||||
to access your module's global variables and may allow
|
|
||||||
intruders to execute arbitrary code on your machine. Only
|
|
||||||
use this option on a secure, closed network.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.instance = instance
|
|
||||||
self.allow_dotted_names = allow_dotted_names
|
|
||||||
|
|
||||||
def register_function(self, function, name = None):
|
|
||||||
"""Registers a function to respond to XML-RPC requests.
|
|
||||||
|
|
||||||
The optional name argument can be used to set a Unicode name
|
|
||||||
for the function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if name is None:
|
|
||||||
name = function.__name__
|
|
||||||
self.funcs[name] = function
|
|
||||||
|
|
||||||
def register_introspection_functions(self):
|
|
||||||
"""Registers the XML-RPC introspection methods in the system
|
|
||||||
namespace.
|
|
||||||
|
|
||||||
see http://xmlrpc.usefulinc.com/doc/reserved.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.funcs.update({'system.listMethods' : self.system_listMethods,
|
|
||||||
'system.methodSignature' : self.system_methodSignature,
|
|
||||||
'system.methodHelp' : self.system_methodHelp})
|
|
||||||
|
|
||||||
def register_multicall_functions(self):
|
|
||||||
"""Registers the XML-RPC multicall method in the system
|
|
||||||
namespace.
|
|
||||||
|
|
||||||
see http://www.xmlrpc.com/discuss/msgReader$1208"""
|
|
||||||
|
|
||||||
self.funcs.update({'system.multicall' : self.system_multicall})
|
|
||||||
|
|
||||||
def _marshaled_dispatch(self, data, dispatch_method = None):
|
|
||||||
"""Dispatches an XML-RPC method from marshalled (XML) data.
|
|
||||||
|
|
||||||
XML-RPC methods are dispatched from the marshalled (XML) data
|
|
||||||
using the _dispatch method and the result is returned as
|
|
||||||
marshalled data. For backwards compatibility, a dispatch
|
|
||||||
function can be provided as an argument (see comment in
|
|
||||||
SimpleXMLRPCRequestHandler.do_POST) but overriding the
|
|
||||||
existing method through subclassing is the prefered means
|
|
||||||
of changing method dispatch behavior.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
params, method = xmlrpclib.loads(data)
|
|
||||||
|
|
||||||
# generate response
|
|
||||||
if dispatch_method is not None:
|
|
||||||
response = dispatch_method(method, params)
|
|
||||||
else:
|
|
||||||
response = self._dispatch(method, params)
|
|
||||||
# wrap response in a singleton tuple
|
|
||||||
response = (response,)
|
|
||||||
response = xmlrpclib.dumps(response, methodresponse=1,
|
|
||||||
allow_none=self.allow_none, encoding=self.encoding)
|
|
||||||
except Fault, fault:
|
|
||||||
response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
|
|
||||||
encoding=self.encoding)
|
|
||||||
except:
|
|
||||||
# report exception back to server
|
|
||||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
||||||
response = xmlrpclib.dumps(
|
|
||||||
xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
|
|
||||||
encoding=self.encoding, allow_none=self.allow_none,
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def system_listMethods(self):
|
|
||||||
"""system.listMethods() => ['add', 'subtract', 'multiple']
|
|
||||||
|
|
||||||
Returns a list of the methods supported by the server."""
|
|
||||||
|
|
||||||
methods = self.funcs.keys()
|
|
||||||
if self.instance is not None:
|
|
||||||
# Instance can implement _listMethod to return a list of
|
|
||||||
# methods
|
|
||||||
if hasattr(self.instance, '_listMethods'):
|
|
||||||
methods = remove_duplicates(
|
|
||||||
methods + self.instance._listMethods()
|
|
||||||
)
|
|
||||||
# if the instance has a _dispatch method then we
|
|
||||||
# don't have enough information to provide a list
|
|
||||||
# of methods
|
|
||||||
elif not hasattr(self.instance, '_dispatch'):
|
|
||||||
methods = remove_duplicates(
|
|
||||||
methods + list_public_methods(self.instance)
|
|
||||||
)
|
|
||||||
methods.sort()
|
|
||||||
return methods
|
|
||||||
|
|
||||||
def system_methodSignature(self, method_name):
|
|
||||||
"""system.methodSignature('add') => [double, int, int]
|
|
||||||
|
|
||||||
Returns a list describing the signature of the method. In the
|
|
||||||
above example, the add method takes two integers as arguments
|
|
||||||
and returns a double result.
|
|
||||||
|
|
||||||
This server does NOT support system.methodSignature."""
|
|
||||||
|
|
||||||
# See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
|
|
||||||
|
|
||||||
return 'signatures not supported'
|
|
||||||
|
|
||||||
def system_methodHelp(self, method_name):
|
|
||||||
"""system.methodHelp('add') => "Adds two integers together"
|
|
||||||
|
|
||||||
Returns a string containing documentation for the specified method."""
|
|
||||||
|
|
||||||
method = None
|
|
||||||
if method_name in self.funcs:
|
|
||||||
method = self.funcs[method_name]
|
|
||||||
elif self.instance is not None:
|
|
||||||
# Instance can implement _methodHelp to return help for a method
|
|
||||||
if hasattr(self.instance, '_methodHelp'):
|
|
||||||
return self.instance._methodHelp(method_name)
|
|
||||||
# if the instance has a _dispatch method then we
|
|
||||||
# don't have enough information to provide help
|
|
||||||
elif not hasattr(self.instance, '_dispatch'):
|
|
||||||
try:
|
|
||||||
method = resolve_dotted_attribute(
|
|
||||||
self.instance,
|
|
||||||
method_name,
|
|
||||||
self.allow_dotted_names
|
|
||||||
)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Note that we aren't checking that the method actually
|
|
||||||
# be a callable object of some kind
|
|
||||||
if method is None:
|
|
||||||
return ""
|
|
||||||
else:
|
|
||||||
import pydoc
|
|
||||||
return pydoc.getdoc(method)
|
|
||||||
|
|
||||||
def system_multicall(self, call_list):
|
|
||||||
"""system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
|
|
||||||
[[4], ...]
|
|
||||||
|
|
||||||
Allows the caller to package multiple XML-RPC calls into a single
|
|
||||||
request.
|
|
||||||
|
|
||||||
See http://www.xmlrpc.com/discuss/msgReader$1208
|
|
||||||
"""
|
|
||||||
|
|
||||||
results = []
|
|
||||||
for call in call_list:
|
|
||||||
method_name = call['methodName']
|
|
||||||
params = call['params']
|
|
||||||
|
|
||||||
try:
|
|
||||||
# XXX A marshalling error in any response will fail the entire
|
|
||||||
# multicall. If someone cares they should fix this.
|
|
||||||
results.append([self._dispatch(method_name, params)])
|
|
||||||
except Fault, fault:
|
|
||||||
results.append(
|
|
||||||
{'faultCode' : fault.faultCode,
|
|
||||||
'faultString' : fault.faultString}
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
||||||
results.append(
|
|
||||||
{'faultCode' : 1,
|
|
||||||
'faultString' : "%s:%s" % (exc_type, exc_value)}
|
|
||||||
)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def _dispatch(self, method, params):
|
|
||||||
"""Dispatches the XML-RPC method.
|
|
||||||
|
|
||||||
XML-RPC calls are forwarded to a registered function that
|
|
||||||
matches the called XML-RPC method name. If no such function
|
|
||||||
exists then the call is forwarded to the registered instance,
|
|
||||||
if available.
|
|
||||||
|
|
||||||
If the registered instance has a _dispatch method then that
|
|
||||||
method will be called with the name of the XML-RPC method and
|
|
||||||
its parameters as a tuple
|
|
||||||
e.g. instance._dispatch('add',(2,3))
|
|
||||||
|
|
||||||
If the registered instance does not have a _dispatch method
|
|
||||||
then the instance will be searched to find a matching method
|
|
||||||
and, if found, will be called.
|
|
||||||
|
|
||||||
Methods beginning with an '_' are considered private and will
|
|
||||||
not be called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
func = None
|
|
||||||
try:
|
|
||||||
# check to see if a matching function has been registered
|
|
||||||
func = self.funcs[method]
|
|
||||||
except KeyError:
|
|
||||||
if self.instance is not None:
|
|
||||||
# check for a _dispatch method
|
|
||||||
if hasattr(self.instance, '_dispatch'):
|
|
||||||
return self.instance._dispatch(method, params)
|
|
||||||
else:
|
|
||||||
# call instance method directly
|
|
||||||
try:
|
|
||||||
func = resolve_dotted_attribute(
|
|
||||||
self.instance,
|
|
||||||
method,
|
|
||||||
self.allow_dotted_names
|
|
||||||
)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if func is not None:
|
|
||||||
return func(*params)
|
|
||||||
else:
|
|
||||||
raise Exception('method "%s" is not supported' % method)
|
|
||||||
|
|
||||||
class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|
||||||
"""Simple XML-RPC request handler class.
|
|
||||||
|
|
||||||
Handles all HTTP POST requests and attempts to decode them as
|
|
||||||
XML-RPC requests.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Class attribute listing the accessible path components;
|
|
||||||
# paths not on this list will result in a 404 error.
|
|
||||||
rpc_paths = ('/', '/RPC2')
|
|
||||||
|
|
||||||
def is_rpc_path_valid(self):
|
|
||||||
if self.rpc_paths:
|
|
||||||
return self.path in self.rpc_paths
|
|
||||||
else:
|
|
||||||
# If .rpc_paths is empty, just assume all paths are legal
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_POST(self):
|
|
||||||
"""Handles the HTTP POST request.
|
|
||||||
|
|
||||||
Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
|
||||||
which are forwarded to the server's _dispatch method for handling.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check that the path is legal
|
|
||||||
if not self.is_rpc_path_valid():
|
|
||||||
self.report_404()
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get arguments by reading body of request.
|
|
||||||
# We read this in chunks to avoid straining
|
|
||||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
|
||||||
# begin to have problems (bug #792570).
|
|
||||||
max_chunk_size = 10*1024*1024
|
|
||||||
size_remaining = int(self.headers["content-length"])
|
|
||||||
L = []
|
|
||||||
while size_remaining:
|
|
||||||
chunk_size = min(size_remaining, max_chunk_size)
|
|
||||||
L.append(self.rfile.read(chunk_size))
|
|
||||||
size_remaining -= len(L[-1])
|
|
||||||
data = ''.join(L)
|
|
||||||
|
|
||||||
# In previous versions of SimpleXMLRPCServer, _dispatch
|
|
||||||
# could be overridden in this class, instead of in
|
|
||||||
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
|
||||||
# check to see if a subclass implements _dispatch and dispatch
|
|
||||||
# using that method if present.
|
|
||||||
response = self.server._marshaled_dispatch(
|
|
||||||
data, getattr(self, '_dispatch', None)
|
|
||||||
)
|
|
||||||
except Exception, e: # This should only happen if the module is buggy
|
|
||||||
# internal error, report as HTTP server error
|
|
||||||
self.send_response(500)
|
|
||||||
|
|
||||||
# Send information about the exception if requested
|
|
||||||
if hasattr(self.server, '_send_traceback_header') and \
|
|
||||||
self.server._send_traceback_header:
|
|
||||||
self.send_header("X-exception", str(e))
|
|
||||||
self.send_header("X-traceback", traceback.format_exc())
|
|
||||||
|
|
||||||
self.end_headers()
|
|
||||||
else:
|
|
||||||
# got a valid XML RPC response
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", "text/xml")
|
|
||||||
self.send_header("Content-length", str(len(response)))
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(response)
|
|
||||||
|
|
||||||
# shut down the connection
|
|
||||||
self.wfile.flush()
|
|
||||||
self.connection.shutdown(1)
|
|
||||||
|
|
||||||
def report_404 (self):
|
|
||||||
# Report a 404 error
|
|
||||||
self.send_response(404)
|
|
||||||
response = 'No such page'
|
|
||||||
self.send_header("Content-type", "text/plain")
|
|
||||||
self.send_header("Content-length", str(len(response)))
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(response)
|
|
||||||
# shut down the connection
|
|
||||||
self.wfile.flush()
|
|
||||||
self.connection.shutdown(1)
|
|
||||||
|
|
||||||
def log_request(self, code='-', size='-'):
|
|
||||||
"""Selectively log an accepted request."""
|
|
||||||
|
|
||||||
if self.server.logRequests:
|
|
||||||
BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
|
|
||||||
|
|
||||||
class SimpleXMLRPCServer(SocketServer.TCPServer,
|
|
||||||
SimpleXMLRPCDispatcher):
|
|
||||||
"""Simple XML-RPC server.
|
|
||||||
|
|
||||||
Simple XML-RPC server that allows functions and a single instance
|
|
||||||
to be installed to handle requests. The default implementation
|
|
||||||
attempts to dispatch XML-RPC calls to the functions or instance
|
|
||||||
installed in the server. Override the _dispatch method inhereted
|
|
||||||
from SimpleXMLRPCDispatcher to change this behavior.
|
|
||||||
"""
|
|
||||||
|
|
||||||
allow_reuse_address = True
|
|
||||||
|
|
||||||
# Warning: this is for debugging purposes only! Never set this to True in
|
|
||||||
# production code, as will be sending out sensitive information (exception
|
|
||||||
# and stack trace details) when exceptions are raised inside
|
|
||||||
# SimpleXMLRPCRequestHandler.do_POST
|
|
||||||
_send_traceback_header = False
|
|
||||||
|
|
||||||
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
|
|
||||||
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
|
|
||||||
self.logRequests = logRequests
|
|
||||||
|
|
||||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
|
||||||
SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
|
|
||||||
|
|
||||||
# [Bug #1222790] If possible, set close-on-exec flag; if a
|
|
||||||
# method spawns a subprocess, the subprocess shouldn't have
|
|
||||||
# the listening socket open.
|
|
||||||
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
|
||||||
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
|
||||||
flags |= fcntl.FD_CLOEXEC
|
|
||||||
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
|
||||||
|
|
||||||
class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
|
|
||||||
"""Simple handler for XML-RPC data passed through CGI."""
|
|
||||||
|
|
||||||
def __init__(self, allow_none=False, encoding=None):
|
|
||||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
|
||||||
|
|
||||||
def handle_xmlrpc(self, request_text):
|
|
||||||
"""Handle a single XML-RPC request"""
|
|
||||||
|
|
||||||
response = self._marshaled_dispatch(request_text)
|
|
||||||
|
|
||||||
print 'Content-Type: text/xml'
|
|
||||||
print 'Content-Length: %d' % len(response)
|
|
||||||
print
|
|
||||||
sys.stdout.write(response)
|
|
||||||
|
|
||||||
def handle_get(self):
|
|
||||||
"""Handle a single HTTP GET request.
|
|
||||||
|
|
||||||
Default implementation indicates an error because
|
|
||||||
XML-RPC uses the POST method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
code = 400
|
|
||||||
message, explain = \
|
|
||||||
BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
|
|
||||||
|
|
||||||
response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
|
|
||||||
{
|
|
||||||
'code' : code,
|
|
||||||
'message' : message,
|
|
||||||
'explain' : explain
|
|
||||||
}
|
|
||||||
print 'Status: %d %s' % (code, message)
|
|
||||||
print 'Content-Type: text/html'
|
|
||||||
print 'Content-Length: %d' % len(response)
|
|
||||||
print
|
|
||||||
sys.stdout.write(response)
|
|
||||||
|
|
||||||
def handle_request(self, request_text = None):
|
|
||||||
"""Handle a single XML-RPC request passed through a CGI post method.
|
|
||||||
|
|
||||||
If no XML data is given then it is read from stdin. The resulting
|
|
||||||
XML-RPC response is printed to stdout along with the correct HTTP
|
|
||||||
headers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if request_text is None and \
|
|
||||||
os.environ.get('REQUEST_METHOD', None) == 'GET':
|
|
||||||
self.handle_get()
|
|
||||||
else:
|
|
||||||
# POST data is normally available through stdin
|
|
||||||
if request_text is None:
|
|
||||||
request_text = sys.stdin.read()
|
|
||||||
|
|
||||||
self.handle_xmlrpc(request_text)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print 'Running XML-RPC server on port 8000'
|
|
||||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
|
||||||
server.register_function(pow)
|
|
||||||
server.register_function(lambda x,y: x+y, 'add')
|
|
||||||
server.serve_forever()
|
|
|
@ -1,681 +0,0 @@
|
||||||
"""Generic socket server classes.
|
|
||||||
|
|
||||||
This module tries to capture the various aspects of defining a server:
|
|
||||||
|
|
||||||
For socket-based servers:
|
|
||||||
|
|
||||||
- address family:
|
|
||||||
- AF_INET{,6}: IP (Internet Protocol) sockets (default)
|
|
||||||
- AF_UNIX: Unix domain sockets
|
|
||||||
- others, e.g. AF_DECNET are conceivable (see <socket.h>
|
|
||||||
- socket type:
|
|
||||||
- SOCK_STREAM (reliable stream, e.g. TCP)
|
|
||||||
- SOCK_DGRAM (datagrams, e.g. UDP)
|
|
||||||
|
|
||||||
For request-based servers (including socket-based):
|
|
||||||
|
|
||||||
- client address verification before further looking at the request
|
|
||||||
(This is actually a hook for any processing that needs to look
|
|
||||||
at the request before anything else, e.g. logging)
|
|
||||||
- how to handle multiple requests:
|
|
||||||
- synchronous (one request is handled at a time)
|
|
||||||
- forking (each request is handled by a new process)
|
|
||||||
- threading (each request is handled by a new thread)
|
|
||||||
|
|
||||||
The classes in this module favor the server type that is simplest to
|
|
||||||
write: a synchronous TCP/IP server. This is bad class design, but
|
|
||||||
save some typing. (There's also the issue that a deep class hierarchy
|
|
||||||
slows down method lookups.)
|
|
||||||
|
|
||||||
There are five classes in an inheritance diagram, four of which represent
|
|
||||||
synchronous servers of four types:
|
|
||||||
|
|
||||||
+------------+
|
|
||||||
| BaseServer |
|
|
||||||
+------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+-----------+ +------------------+
|
|
||||||
| TCPServer |------->| UnixStreamServer |
|
|
||||||
+-----------+ +------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+-----------+ +--------------------+
|
|
||||||
| UDPServer |------->| UnixDatagramServer |
|
|
||||||
+-----------+ +--------------------+
|
|
||||||
|
|
||||||
Note that UnixDatagramServer derives from UDPServer, not from
|
|
||||||
UnixStreamServer -- the only difference between an IP and a Unix
|
|
||||||
stream server is the address family, which is simply repeated in both
|
|
||||||
unix server classes.
|
|
||||||
|
|
||||||
Forking and threading versions of each type of server can be created
|
|
||||||
using the ForkingMixIn and ThreadingMixIn mix-in classes. For
|
|
||||||
instance, a threading UDP server class is created as follows:
|
|
||||||
|
|
||||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
|
|
||||||
|
|
||||||
The Mix-in class must come first, since it overrides a method defined
|
|
||||||
in UDPServer! Setting the various member variables also changes
|
|
||||||
the behavior of the underlying server mechanism.
|
|
||||||
|
|
||||||
To implement a service, you must derive a class from
|
|
||||||
BaseRequestHandler and redefine its handle() method. You can then run
|
|
||||||
various versions of the service by combining one of the server classes
|
|
||||||
with your request handler class.
|
|
||||||
|
|
||||||
The request handler class must be different for datagram or stream
|
|
||||||
services. This can be hidden by using the request handler
|
|
||||||
subclasses StreamRequestHandler or DatagramRequestHandler.
|
|
||||||
|
|
||||||
Of course, you still have to use your head!
|
|
||||||
|
|
||||||
For instance, it makes no sense to use a forking server if the service
|
|
||||||
contains state in memory that can be modified by requests (since the
|
|
||||||
modifications in the child process would never reach the initial state
|
|
||||||
kept in the parent process and passed to each child). In this case,
|
|
||||||
you can use a threading server, but you will probably have to use
|
|
||||||
locks to avoid two requests that come in nearly simultaneous to apply
|
|
||||||
conflicting changes to the server state.
|
|
||||||
|
|
||||||
On the other hand, if you are building e.g. an HTTP server, where all
|
|
||||||
data is stored externally (e.g. in the file system), a synchronous
|
|
||||||
class will essentially render the service "deaf" while one request is
|
|
||||||
being handled -- which may be for a very long time if a client is slow
|
|
||||||
to reqd all the data it has requested. Here a threading or forking
|
|
||||||
server is appropriate.
|
|
||||||
|
|
||||||
In some cases, it may be appropriate to process part of a request
|
|
||||||
synchronously, but to finish processing in a forked child depending on
|
|
||||||
the request data. This can be implemented by using a synchronous
|
|
||||||
server and doing an explicit fork in the request handler class
|
|
||||||
handle() method.
|
|
||||||
|
|
||||||
Another approach to handling multiple simultaneous requests in an
|
|
||||||
environment that supports neither threads nor fork (or where these are
|
|
||||||
too expensive or inappropriate for the service) is to maintain an
|
|
||||||
explicit table of partially finished requests and to use select() to
|
|
||||||
decide which request to work on next (or whether to handle a new
|
|
||||||
incoming request). This is particularly important for stream services
|
|
||||||
where each client can potentially be connected for a long time (if
|
|
||||||
threads or subprocesses cannot be used).
|
|
||||||
|
|
||||||
Future work:
|
|
||||||
- Standard classes for Sun RPC (which uses either UDP or TCP)
|
|
||||||
- Standard mix-in classes to implement various authentication
|
|
||||||
and encryption schemes
|
|
||||||
- Standard framework for select-based multiplexing
|
|
||||||
|
|
||||||
XXX Open problems:
|
|
||||||
- What to do with out-of-band data?
|
|
||||||
|
|
||||||
BaseServer:
|
|
||||||
- split generic "request" functionality out into BaseServer class.
|
|
||||||
Copyright (C) 2000 Luke Kenneth Casson Leighton <lkcl@samba.org>
|
|
||||||
|
|
||||||
example: read entries from a SQL database (requires overriding
|
|
||||||
get_request() to return a table entry from the database).
|
|
||||||
entry is processed by a RequestHandlerClass.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Author of the BaseServer patch: Luke Kenneth Casson Leighton
|
|
||||||
|
|
||||||
# XXX Warning!
|
|
||||||
# There is a test suite for this module, but it cannot be run by the
|
|
||||||
# standard regression test.
|
|
||||||
# To run it manually, run Lib/test/test_socketserver.py.
|
|
||||||
|
|
||||||
__version__ = "0.4"
|
|
||||||
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
try:
|
|
||||||
import threading
|
|
||||||
except ImportError:
|
|
||||||
import dummy_threading as threading
|
|
||||||
|
|
||||||
__all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer",
|
|
||||||
"ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler",
|
|
||||||
"StreamRequestHandler","DatagramRequestHandler",
|
|
||||||
"ThreadingMixIn", "ForkingMixIn"]
|
|
||||||
if hasattr(socket, "AF_UNIX"):
|
|
||||||
__all__.extend(["UnixStreamServer","UnixDatagramServer",
|
|
||||||
"ThreadingUnixStreamServer",
|
|
||||||
"ThreadingUnixDatagramServer"])
|
|
||||||
|
|
||||||
class BaseServer:
|
|
||||||
|
|
||||||
"""Base class for server classes.
|
|
||||||
|
|
||||||
Methods for the caller:
|
|
||||||
|
|
||||||
- __init__(server_address, RequestHandlerClass)
|
|
||||||
- serve_forever(poll_interval=0.5)
|
|
||||||
- shutdown()
|
|
||||||
- handle_request() # if you do not use serve_forever()
|
|
||||||
- fileno() -> int # for select()
|
|
||||||
|
|
||||||
Methods that may be overridden:
|
|
||||||
|
|
||||||
- server_bind()
|
|
||||||
- server_activate()
|
|
||||||
- get_request() -> request, client_address
|
|
||||||
- handle_timeout()
|
|
||||||
- verify_request(request, client_address)
|
|
||||||
- server_close()
|
|
||||||
- process_request(request, client_address)
|
|
||||||
- close_request(request)
|
|
||||||
- handle_error()
|
|
||||||
|
|
||||||
Methods for derived classes:
|
|
||||||
|
|
||||||
- finish_request(request, client_address)
|
|
||||||
|
|
||||||
Class variables that may be overridden by derived classes or
|
|
||||||
instances:
|
|
||||||
|
|
||||||
- timeout
|
|
||||||
- address_family
|
|
||||||
- socket_type
|
|
||||||
- allow_reuse_address
|
|
||||||
|
|
||||||
Instance variables:
|
|
||||||
|
|
||||||
- RequestHandlerClass
|
|
||||||
- socket
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
timeout = None
|
|
||||||
|
|
||||||
def __init__(self, server_address, RequestHandlerClass):
|
|
||||||
"""Constructor. May be extended, do not override."""
|
|
||||||
self.server_address = server_address
|
|
||||||
self.RequestHandlerClass = RequestHandlerClass
|
|
||||||
self.__is_shut_down = threading.Event()
|
|
||||||
self.__serving = False
|
|
||||||
|
|
||||||
def server_activate(self):
|
|
||||||
"""Called by constructor to activate the server.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def serve_forever(self, poll_interval=0.5):
|
|
||||||
"""Handle one request at a time until shutdown.
|
|
||||||
|
|
||||||
Polls for shutdown every poll_interval seconds. Ignores
|
|
||||||
self.timeout. If you need to do periodic tasks, do them in
|
|
||||||
another thread.
|
|
||||||
"""
|
|
||||||
self.__serving = True
|
|
||||||
self.__is_shut_down.clear()
|
|
||||||
while self.__serving:
|
|
||||||
# XXX: Consider using another file descriptor or
|
|
||||||
# connecting to the socket to wake this up instead of
|
|
||||||
# polling. Polling reduces our responsiveness to a
|
|
||||||
# shutdown request and wastes cpu at all other times.
|
|
||||||
r, w, e = select.select([self], [], [], poll_interval)
|
|
||||||
if r:
|
|
||||||
self._handle_request_noblock()
|
|
||||||
self.__is_shut_down.set()
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
"""Stops the serve_forever loop.
|
|
||||||
|
|
||||||
Blocks until the loop has finished. This must be called while
|
|
||||||
serve_forever() is running in another thread, or it will
|
|
||||||
deadlock.
|
|
||||||
"""
|
|
||||||
self.__serving = False
|
|
||||||
self.__is_shut_down.wait()
|
|
||||||
|
|
||||||
# The distinction between handling, getting, processing and
|
|
||||||
# finishing a request is fairly arbitrary. Remember:
|
|
||||||
#
|
|
||||||
# - handle_request() is the top-level call. It calls
|
|
||||||
# select, get_request(), verify_request() and process_request()
|
|
||||||
# - get_request() is different for stream or datagram sockets
|
|
||||||
# - process_request() is the place that may fork a new process
|
|
||||||
# or create a new thread to finish the request
|
|
||||||
# - finish_request() instantiates the request handler class;
|
|
||||||
# this constructor will handle the request all by itself
|
|
||||||
|
|
||||||
def handle_request(self):
|
|
||||||
"""Handle one request, possibly blocking.
|
|
||||||
|
|
||||||
Respects self.timeout.
|
|
||||||
"""
|
|
||||||
# Support people who used socket.settimeout() to escape
|
|
||||||
# handle_request before self.timeout was available.
|
|
||||||
timeout = self.socket.gettimeout()
|
|
||||||
if timeout is None:
|
|
||||||
timeout = self.timeout
|
|
||||||
elif self.timeout is not None:
|
|
||||||
timeout = min(timeout, self.timeout)
|
|
||||||
fd_sets = select.select([self], [], [], timeout)
|
|
||||||
if not fd_sets[0]:
|
|
||||||
self.handle_timeout()
|
|
||||||
return
|
|
||||||
self._handle_request_noblock()
|
|
||||||
|
|
||||||
def _handle_request_noblock(self):
|
|
||||||
"""Handle one request, without blocking.
|
|
||||||
|
|
||||||
I assume that select.select has returned that the socket is
|
|
||||||
readable before this function was called, so there should be
|
|
||||||
no risk of blocking in get_request().
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
request, client_address = self.get_request()
|
|
||||||
except socket.error:
|
|
||||||
return
|
|
||||||
if self.verify_request(request, client_address):
|
|
||||||
try:
|
|
||||||
self.process_request(request, client_address)
|
|
||||||
except:
|
|
||||||
self.handle_error(request, client_address)
|
|
||||||
self.close_request(request)
|
|
||||||
|
|
||||||
def handle_timeout(self):
|
|
||||||
"""Called if no new request arrives within self.timeout.
|
|
||||||
|
|
||||||
Overridden by ForkingMixIn.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def verify_request(self, request, client_address):
|
|
||||||
"""Verify the request. May be overridden.
|
|
||||||
|
|
||||||
Return True if we should proceed with this request.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
|
||||||
"""Call finish_request.
|
|
||||||
|
|
||||||
Overridden by ForkingMixIn and ThreadingMixIn.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.finish_request(request, client_address)
|
|
||||||
self.close_request(request)
|
|
||||||
|
|
||||||
def server_close(self):
|
|
||||||
"""Called to clean-up the server.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def finish_request(self, request, client_address):
|
|
||||||
"""Finish one request by instantiating RequestHandlerClass."""
|
|
||||||
self.RequestHandlerClass(request, client_address, self)
|
|
||||||
|
|
||||||
def close_request(self, request):
|
|
||||||
"""Called to clean up an individual request."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle_error(self, request, client_address):
|
|
||||||
"""Handle an error gracefully. May be overridden.
|
|
||||||
|
|
||||||
The default is to print a traceback and continue.
|
|
||||||
|
|
||||||
"""
|
|
||||||
print '-'*40
|
|
||||||
print 'Exception happened during processing of request from',
|
|
||||||
print client_address
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc() # XXX But this goes to stderr!
|
|
||||||
print '-'*40
|
|
||||||
|
|
||||||
|
|
||||||
class TCPServer(BaseServer):
|
|
||||||
|
|
||||||
"""Base class for various socket-based server classes.
|
|
||||||
|
|
||||||
Defaults to synchronous IP stream (i.e., TCP).
|
|
||||||
|
|
||||||
Methods for the caller:
|
|
||||||
|
|
||||||
- __init__(server_address, RequestHandlerClass, bind_and_activate=True)
|
|
||||||
- serve_forever(poll_interval=0.5)
|
|
||||||
- shutdown()
|
|
||||||
- handle_request() # if you don't use serve_forever()
|
|
||||||
- fileno() -> int # for select()
|
|
||||||
|
|
||||||
Methods that may be overridden:
|
|
||||||
|
|
||||||
- server_bind()
|
|
||||||
- server_activate()
|
|
||||||
- get_request() -> request, client_address
|
|
||||||
- handle_timeout()
|
|
||||||
- verify_request(request, client_address)
|
|
||||||
- process_request(request, client_address)
|
|
||||||
- close_request(request)
|
|
||||||
- handle_error()
|
|
||||||
|
|
||||||
Methods for derived classes:
|
|
||||||
|
|
||||||
- finish_request(request, client_address)
|
|
||||||
|
|
||||||
Class variables that may be overridden by derived classes or
|
|
||||||
instances:
|
|
||||||
|
|
||||||
- timeout
|
|
||||||
- address_family
|
|
||||||
- socket_type
|
|
||||||
- request_queue_size (only for stream sockets)
|
|
||||||
- allow_reuse_address
|
|
||||||
|
|
||||||
Instance variables:
|
|
||||||
|
|
||||||
- server_address
|
|
||||||
- RequestHandlerClass
|
|
||||||
- socket
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
address_family = socket.AF_INET
|
|
||||||
|
|
||||||
socket_type = socket.SOCK_STREAM
|
|
||||||
|
|
||||||
request_queue_size = 5
|
|
||||||
|
|
||||||
allow_reuse_address = False
|
|
||||||
|
|
||||||
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
|
|
||||||
"""Constructor. May be extended, do not override."""
|
|
||||||
BaseServer.__init__(self, server_address, RequestHandlerClass)
|
|
||||||
self.socket = socket.socket(self.address_family,
|
|
||||||
self.socket_type)
|
|
||||||
if bind_and_activate:
|
|
||||||
self.server_bind()
|
|
||||||
self.server_activate()
|
|
||||||
|
|
||||||
def server_bind(self):
|
|
||||||
"""Called by constructor to bind the socket.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.allow_reuse_address:
|
|
||||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self.socket.bind(self.server_address)
|
|
||||||
self.server_address = self.socket.getsockname()
|
|
||||||
|
|
||||||
def server_activate(self):
|
|
||||||
"""Called by constructor to activate the server.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.socket.listen(self.request_queue_size)
|
|
||||||
|
|
||||||
def server_close(self):
|
|
||||||
"""Called to clean-up the server.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.socket.close()
|
|
||||||
|
|
||||||
def fileno(self):
|
|
||||||
"""Return socket file number.
|
|
||||||
|
|
||||||
Interface required by select().
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self.socket.fileno()
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
"""Get the request and client address from the socket.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self.socket.accept()
|
|
||||||
|
|
||||||
def close_request(self, request):
|
|
||||||
"""Called to clean up an individual request."""
|
|
||||||
request.close()
|
|
||||||
|
|
||||||
|
|
||||||
class UDPServer(TCPServer):
|
|
||||||
|
|
||||||
"""UDP server class."""
|
|
||||||
|
|
||||||
allow_reuse_address = False
|
|
||||||
|
|
||||||
socket_type = socket.SOCK_DGRAM
|
|
||||||
|
|
||||||
max_packet_size = 8192
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
data, client_addr = self.socket.recvfrom(self.max_packet_size)
|
|
||||||
return (data, self.socket), client_addr
|
|
||||||
|
|
||||||
def server_activate(self):
|
|
||||||
# No need to call listen() for UDP.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close_request(self, request):
|
|
||||||
# No need to close anything.
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ForkingMixIn:
|
|
||||||
|
|
||||||
"""Mix-in class to handle each request in a new process."""
|
|
||||||
|
|
||||||
timeout = 300
|
|
||||||
active_children = None
|
|
||||||
max_children = 40
|
|
||||||
|
|
||||||
def collect_children(self):
|
|
||||||
"""Internal routine to wait for children that have exited."""
|
|
||||||
if self.active_children is None: return
|
|
||||||
while len(self.active_children) >= self.max_children:
|
|
||||||
# XXX: This will wait for any child process, not just ones
|
|
||||||
# spawned by this library. This could confuse other
|
|
||||||
# libraries that expect to be able to wait for their own
|
|
||||||
# children.
|
|
||||||
try:
|
|
||||||
pid, status = os.waitpid(0, options=0)
|
|
||||||
except os.error:
|
|
||||||
pid = None
|
|
||||||
if pid not in self.active_children: continue
|
|
||||||
self.active_children.remove(pid)
|
|
||||||
|
|
||||||
# XXX: This loop runs more system calls than it ought
|
|
||||||
# to. There should be a way to put the active_children into a
|
|
||||||
# process group and then use os.waitpid(-pgid) to wait for any
|
|
||||||
# of that set, but I couldn't find a way to allocate pgids
|
|
||||||
# that couldn't collide.
|
|
||||||
for child in self.active_children:
|
|
||||||
try:
|
|
||||||
pid, status = os.waitpid(child, os.WNOHANG)
|
|
||||||
except os.error:
|
|
||||||
pid = None
|
|
||||||
if not pid: continue
|
|
||||||
try:
|
|
||||||
self.active_children.remove(pid)
|
|
||||||
except ValueError, e:
|
|
||||||
raise ValueError('%s. x=%d and list=%r' % (e.message, pid,
|
|
||||||
self.active_children))
|
|
||||||
|
|
||||||
def handle_timeout(self):
|
|
||||||
"""Wait for zombies after self.timeout seconds of inactivity.
|
|
||||||
|
|
||||||
May be extended, do not override.
|
|
||||||
"""
|
|
||||||
self.collect_children()
|
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
|
||||||
"""Fork a new subprocess to process the request."""
|
|
||||||
self.collect_children()
|
|
||||||
pid = os.fork()
|
|
||||||
if pid:
|
|
||||||
# Parent process
|
|
||||||
if self.active_children is None:
|
|
||||||
self.active_children = []
|
|
||||||
self.active_children.append(pid)
|
|
||||||
self.close_request(request)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Child process.
|
|
||||||
# This must never return, hence os._exit()!
|
|
||||||
try:
|
|
||||||
self.finish_request(request, client_address)
|
|
||||||
os._exit(0)
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
self.handle_error(request, client_address)
|
|
||||||
finally:
|
|
||||||
os._exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
class ThreadingMixIn:
|
|
||||||
"""Mix-in class to handle each request in a new thread."""
|
|
||||||
|
|
||||||
# Decides how threads will act upon termination of the
|
|
||||||
# main process
|
|
||||||
daemon_threads = False
|
|
||||||
|
|
||||||
def process_request_thread(self, request, client_address):
|
|
||||||
"""Same as in BaseServer but as a thread.
|
|
||||||
|
|
||||||
In addition, exception handling is done here.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.finish_request(request, client_address)
|
|
||||||
self.close_request(request)
|
|
||||||
except:
|
|
||||||
self.handle_error(request, client_address)
|
|
||||||
self.close_request(request)
|
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
|
||||||
"""Start a new thread to process the request."""
|
|
||||||
t = threading.Thread(target = self.process_request_thread,
|
|
||||||
args = (request, client_address))
|
|
||||||
if self.daemon_threads:
|
|
||||||
t.setDaemon (1)
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
|
|
||||||
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
|
|
||||||
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
|
|
||||||
|
|
||||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
|
|
||||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
|
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
|
||||||
|
|
||||||
class UnixStreamServer(TCPServer):
|
|
||||||
address_family = socket.AF_UNIX
|
|
||||||
|
|
||||||
class UnixDatagramServer(UDPServer):
|
|
||||||
address_family = socket.AF_UNIX
|
|
||||||
|
|
||||||
class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass
|
|
||||||
|
|
||||||
class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass
|
|
||||||
|
|
||||||
class BaseRequestHandler:
|
|
||||||
|
|
||||||
"""Base class for request handler classes.
|
|
||||||
|
|
||||||
This class is instantiated for each request to be handled. The
|
|
||||||
constructor sets the instance variables request, client_address
|
|
||||||
and server, and then calls the handle() method. To implement a
|
|
||||||
specific service, all you need to do is to derive a class which
|
|
||||||
defines a handle() method.
|
|
||||||
|
|
||||||
The handle() method can find the request as self.request, the
|
|
||||||
client address as self.client_address, and the server (in case it
|
|
||||||
needs access to per-server information) as self.server. Since a
|
|
||||||
separate instance is created for each request, the handle() method
|
|
||||||
can define arbitrary other instance variariables.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request, client_address, server):
|
|
||||||
self.request = request
|
|
||||||
self.client_address = client_address
|
|
||||||
self.server = server
|
|
||||||
try:
|
|
||||||
self.setup()
|
|
||||||
self.handle()
|
|
||||||
self.finish()
|
|
||||||
finally:
|
|
||||||
sys.exc_traceback = None # Help garbage collection
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# The following two classes make it possible to use the same service
|
|
||||||
# class for stream or datagram servers.
|
|
||||||
# Each class sets up these instance variables:
|
|
||||||
# - rfile: a file object from which receives the request is read
|
|
||||||
# - wfile: a file object to which the reply is written
|
|
||||||
# When the handle() method returns, wfile is flushed properly
|
|
||||||
|
|
||||||
|
|
||||||
class StreamRequestHandler(BaseRequestHandler):
|
|
||||||
|
|
||||||
"""Define self.rfile and self.wfile for stream sockets."""
|
|
||||||
|
|
||||||
# Default buffer sizes for rfile, wfile.
|
|
||||||
# We default rfile to buffered because otherwise it could be
|
|
||||||
# really slow for large data (a getc() call per byte); we make
|
|
||||||
# wfile unbuffered because (a) often after a write() we want to
|
|
||||||
# read and we need to flush the line; (b) big writes to unbuffered
|
|
||||||
# files are typically optimized by stdio even when big reads
|
|
||||||
# aren't.
|
|
||||||
rbufsize = -1
|
|
||||||
wbufsize = 0
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
self.connection = self.request
|
|
||||||
self.rfile = self.connection.makefile('rb', self.rbufsize)
|
|
||||||
self.wfile = self.connection.makefile('wb', self.wbufsize)
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
if not self.wfile.closed:
|
|
||||||
self.wfile.flush()
|
|
||||||
self.wfile.close()
|
|
||||||
self.rfile.close()
|
|
||||||
|
|
||||||
|
|
||||||
class DatagramRequestHandler(BaseRequestHandler):
|
|
||||||
|
|
||||||
# XXX Regrettably, I cannot get this working on Linux;
|
|
||||||
# s.recvfrom() doesn't return a meaningful client address.
|
|
||||||
|
|
||||||
"""Define self.rfile and self.wfile for datagram sockets."""
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
try:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from StringIO import StringIO
|
|
||||||
self.packet, self.socket = self.request
|
|
||||||
self.rfile = StringIO(self.packet)
|
|
||||||
self.wfile = StringIO()
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
self.socket.sendto(self.wfile.getvalue(), self.client_address)
|
|
Loading…
Reference in New Issue