Replaced these files with:
- I2PSocketServer.py - I2PBaseHTTPServer.py - I2PSimpleHTTPServer.py - I2PCGIHTTPServer.py difference being that these new modules are not hacks of the original python server modules, rather, they subclass the python server modules; this overcomes the Fear and Loathing expressed by some regarding multiplicity of licenses, and apart from that, is a better idea anyway. Only danger being if the modules in later Python releases change substantially, these modules could get broken.
This commit is contained in:
@ -1,575 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
"""HTTP server base class.
|
|
||||||
|
|
||||||
Hacked by aum for I2P SAM Socket compatibility
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
Note: the class in this module doesn't implement any HTTP request; see
|
|
||||||
SAMSimpleHTTPServer 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!)
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
# replace official python socket module with sunshine's I2P sam module
|
|
||||||
#import socket
|
|
||||||
import i2p
|
|
||||||
from i2p import sam as socket
|
|
||||||
|
|
||||||
import mimetools
|
|
||||||
from i2p import SocketServer
|
|
||||||
|
|
||||||
import cStringIO
|
|
||||||
|
|
||||||
__version__ = "0.3"
|
|
||||||
|
|
||||||
__all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
|
|
||||||
|
|
||||||
# Default error message
|
|
||||||
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>
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
self.server_name = self.server_address
|
|
||||||
|
|
||||||
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__
|
|
||||||
|
|
||||||
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 = "HTTP/0.9" # Default
|
|
||||||
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 (%s)" % `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 (%s)" % `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 (%s)" % `command`)
|
|
||||||
return False
|
|
||||||
elif not words:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.send_error(400, "Bad request syntax (%s)" % `requestline`)
|
|
||||||
return False
|
|
||||||
self.command, self.path, self.request_version = command, path, version
|
|
||||||
|
|
||||||
# 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 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 (%s)" % `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)
|
|
||||||
content = (self.error_message_format %
|
|
||||||
{'code': code, 'message': message, 'explain': explain})
|
|
||||||
self.send_response(code, message)
|
|
||||||
self.send_header("Content-Type", "text/html")
|
|
||||||
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
|
|
||||||
|
|
||||||
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_reponse().
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.log_message('"%s" %s %s',
|
|
||||||
self.requestline, str(code), str(size))
|
|
||||||
def log_error(self, *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(*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):
|
|
||||||
"""Return the current date and time formatted for a message header."""
|
|
||||||
now = time.time()
|
|
||||||
year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
|
|
||||||
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)
|
|
||||||
return self.client_address
|
|
||||||
|
|
||||||
|
|
||||||
# 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 http://www.w3.org/hypertext/WWW/Protocols/HTTP/HTRESP.html
|
|
||||||
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 response', '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 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'),
|
|
||||||
501: ('Not Implemented',
|
|
||||||
'Server does not support this operation'),
|
|
||||||
502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
|
|
||||||
503: ('Service temporarily overloaded',
|
|
||||||
'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:]:
|
|
||||||
server_address = sys.argv[1]
|
|
||||||
else:
|
|
||||||
server_address = "mytestxxx.i2p"
|
|
||||||
|
|
||||||
HandlerClass.protocol_version = protocol
|
|
||||||
httpd = ServerClass(server_address, HandlerClass)
|
|
||||||
|
|
||||||
print "Serving HTTP on", server_address, "..."
|
|
||||||
print "Destination follows:"
|
|
||||||
print httpd.socket.dest
|
|
||||||
httpd.serve_forever()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test()
|
|
@ -1,316 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
"""CGI-savvy HTTP Server.
|
|
||||||
|
|
||||||
This module builds on SAMSimpleHTTPServer by implementing GET and POST
|
|
||||||
requests to cgi-bin scripts.
|
|
||||||
|
|
||||||
If the os.fork() function is not present (e.g. on Windows),
|
|
||||||
os.popen2() is used as a fallback, with slightly altered semantics; if
|
|
||||||
that function is not present either (e.g. on Macintosh), only Python
|
|
||||||
scripts are supported, and they are executed by the current process.
|
|
||||||
|
|
||||||
In all cases, the implementation is intentionally naive -- all
|
|
||||||
requests are executed sychronously.
|
|
||||||
|
|
||||||
SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
|
|
||||||
-- it may execute arbitrary Python code or external programs.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.4"
|
|
||||||
|
|
||||||
__all__ = ["CGIHTTPRequestHandler"]
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import urllib
|
|
||||||
from i2p import BaseHTTPServer, SimpleHTTPServer
|
|
||||||
import select
|
|
||||||
|
|
||||||
# conveniences
|
|
||||||
HTTPServer = BaseHTTPServer.HTTPServer
|
|
||||||
class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|
||||||
|
|
||||||
"""Complete HTTP server with GET, HEAD and POST commands.
|
|
||||||
|
|
||||||
GET and HEAD also support running CGI scripts.
|
|
||||||
|
|
||||||
The POST command is *only* implemented for CGI scripts.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Determine platform specifics
|
|
||||||
have_fork = hasattr(os, 'fork')
|
|
||||||
have_popen2 = hasattr(os, 'popen2')
|
|
||||||
have_popen3 = hasattr(os, 'popen3')
|
|
||||||
|
|
||||||
# Make rfile unbuffered -- we need to read one line and then pass
|
|
||||||
# the rest to a subprocess, so we can't use buffered input.
|
|
||||||
rbufsize = 0
|
|
||||||
|
|
||||||
def do_POST(self):
|
|
||||||
"""Serve a POST request.
|
|
||||||
|
|
||||||
This is only implemented for CGI scripts.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.is_cgi():
|
|
||||||
self.run_cgi()
|
|
||||||
else:
|
|
||||||
self.send_error(501, "Can only POST to CGI scripts")
|
|
||||||
def send_head(self):
|
|
||||||
"""Version of send_head that support CGI scripts"""
|
|
||||||
if self.is_cgi():
|
|
||||||
return self.run_cgi()
|
|
||||||
else:
|
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
|
|
||||||
def is_cgi(self):
|
|
||||||
"""Test whether self.path corresponds to a CGI script.
|
|
||||||
|
|
||||||
Return a tuple (dir, rest) if self.path requires running a
|
|
||||||
CGI script, None if not. Note that rest begins with a
|
|
||||||
slash if it is not empty.
|
|
||||||
|
|
||||||
The default implementation tests whether the path
|
|
||||||
begins with one of the strings in the list
|
|
||||||
self.cgi_directories (and the next character is a '/'
|
|
||||||
or the end of the string).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
path = self.path
|
|
||||||
|
|
||||||
for x in self.cgi_directories:
|
|
||||||
i = len(x)
|
|
||||||
if path[:i] == x and (not path[i:] or path[i] == '/'):
|
|
||||||
self.cgi_info = path[:i], path[i+1:]
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
cgi_directories = ['/cgi-bin', '/htbin']
|
|
||||||
|
|
||||||
def is_executable(self, path):
|
|
||||||
"""Test whether argument path is an executable file."""
|
|
||||||
return executable(path)
|
|
||||||
def is_python(self, path):
|
|
||||||
"""Test whether argument path is a Python script."""
|
|
||||||
head, tail = os.path.splitext(path)
|
|
||||||
return tail.lower() in (".py", ".pyw")
|
|
||||||
def run_cgi(self):
|
|
||||||
"""Execute a CGI script."""
|
|
||||||
dir, rest = self.cgi_info
|
|
||||||
i = rest.rfind('?')
|
|
||||||
if i >= 0:
|
|
||||||
rest, query = rest[:i], rest[i+1:]
|
|
||||||
else:
|
|
||||||
query = ''
|
|
||||||
i = rest.find('/')
|
|
||||||
if i >= 0:
|
|
||||||
script, rest = rest[:i], rest[i:]
|
|
||||||
else:
|
|
||||||
script, rest = rest, ''
|
|
||||||
scriptname = dir + '/' + script
|
|
||||||
scriptfile = self.translate_path(scriptname)
|
|
||||||
if not os.path.exists(scriptfile):
|
|
||||||
self.send_error(404, "No such CGI script (%s)" % `scriptname`)
|
|
||||||
return
|
|
||||||
if not os.path.isfile(scriptfile):
|
|
||||||
self.send_error(403, "CGI script is not a plain file (%s)" %
|
|
||||||
`scriptname`)
|
|
||||||
return
|
|
||||||
ispy = self.is_python(scriptname)
|
|
||||||
if not ispy:
|
|
||||||
if not (self.have_fork or self.have_popen2 or self.have_popen3):
|
|
||||||
self.send_error(403, "CGI script is not a Python script (%s)" %
|
|
||||||
`scriptname`)
|
|
||||||
return
|
|
||||||
if not self.is_executable(scriptfile):
|
|
||||||
self.send_error(403, "CGI script is not executable (%s)" %
|
|
||||||
`scriptname`)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
|
|
||||||
# XXX Much of the following could be prepared ahead of time!
|
|
||||||
env = {}
|
|
||||||
env['SERVER_SOFTWARE'] = self.version_string()
|
|
||||||
env['SERVER_NAME'] = self.server.server_name
|
|
||||||
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
|
||||||
env['SERVER_PROTOCOL'] = self.protocol_version
|
|
||||||
env['SERVER_PORT'] = str(self.server.server_port)
|
|
||||||
env['REQUEST_METHOD'] = self.command
|
|
||||||
uqrest = urllib.unquote(rest)
|
|
||||||
env['PATH_INFO'] = uqrest
|
|
||||||
env['PATH_TRANSLATED'] = self.translate_path(uqrest)
|
|
||||||
env['SCRIPT_NAME'] = scriptname
|
|
||||||
if query:
|
|
||||||
env['QUERY_STRING'] = query
|
|
||||||
host = self.address_string()
|
|
||||||
if host != self.client_address[0]:
|
|
||||||
env['REMOTE_HOST'] = host
|
|
||||||
env['REMOTE_ADDR'] = self.client_address[0]
|
|
||||||
# XXX AUTH_TYPE
|
|
||||||
# XXX REMOTE_USER
|
|
||||||
# XXX REMOTE_IDENT
|
|
||||||
if self.headers.typeheader is None:
|
|
||||||
env['CONTENT_TYPE'] = self.headers.type
|
|
||||||
else:
|
|
||||||
env['CONTENT_TYPE'] = self.headers.typeheader
|
|
||||||
length = self.headers.getheader('content-length')
|
|
||||||
if length:
|
|
||||||
env['CONTENT_LENGTH'] = length
|
|
||||||
accept = []
|
|
||||||
for line in self.headers.getallmatchingheaders('accept'):
|
|
||||||
if line[:1] in "\t\n\r ":
|
|
||||||
accept.append(line.strip())
|
|
||||||
else:
|
|
||||||
accept = accept + line[7:].split(',')
|
|
||||||
env['HTTP_ACCEPT'] = ','.join(accept)
|
|
||||||
ua = self.headers.getheader('user-agent')
|
|
||||||
if ua:
|
|
||||||
env['HTTP_USER_AGENT'] = ua
|
|
||||||
co = filter(None, self.headers.getheaders('cookie'))
|
|
||||||
if co:
|
|
||||||
env['HTTP_COOKIE'] = ', '.join(co)
|
|
||||||
# XXX Other HTTP_* headers
|
|
||||||
# Since we're setting the env in the parent, provide empty
|
|
||||||
# values to override previously set values
|
|
||||||
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
|
|
||||||
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
|
|
||||||
env.setdefault(k, "")
|
|
||||||
os.environ.update(env)
|
|
||||||
|
|
||||||
self.send_response(200, "Script output follows")
|
|
||||||
|
|
||||||
decoded_query = query.replace('+', ' ')
|
|
||||||
|
|
||||||
if self.have_fork:
|
|
||||||
# Unix -- fork as we should
|
|
||||||
args = [script]
|
|
||||||
if '=' not in decoded_query:
|
|
||||||
args.append(decoded_query)
|
|
||||||
nobody = nobody_uid()
|
|
||||||
self.wfile.flush() # Always flush before forking
|
|
||||||
pid = os.fork()
|
|
||||||
if pid != 0:
|
|
||||||
# Parent
|
|
||||||
pid, sts = os.waitpid(pid, 0)
|
|
||||||
# throw away additional data [see bug #427345]
|
|
||||||
while select.select([self.rfile], [], [], 0)[0]:
|
|
||||||
if not self.rfile.read(1):
|
|
||||||
break
|
|
||||||
if sts:
|
|
||||||
self.log_error("CGI script exit status %#x", sts)
|
|
||||||
return
|
|
||||||
# Child
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
os.setuid(nobody)
|
|
||||||
except os.error:
|
|
||||||
pass
|
|
||||||
os.dup2(self.rfile.fileno(), 0)
|
|
||||||
os.dup2(self.wfile.fileno(), 1)
|
|
||||||
os.execve(scriptfile, args, os.environ)
|
|
||||||
except:
|
|
||||||
self.server.handle_error(self.request, self.client_address)
|
|
||||||
os._exit(127)
|
|
||||||
|
|
||||||
elif self.have_popen2 or self.have_popen3:
|
|
||||||
# Windows -- use popen2 or popen3 to create a subprocess
|
|
||||||
import shutil
|
|
||||||
if self.have_popen3:
|
|
||||||
popenx = os.popen3
|
|
||||||
else:
|
|
||||||
popenx = os.popen2
|
|
||||||
cmdline = scriptfile
|
|
||||||
if self.is_python(scriptfile):
|
|
||||||
interp = sys.executable
|
|
||||||
if interp.lower().endswith("w.exe"):
|
|
||||||
# On Windows, use python.exe, not pythonw.exe
|
|
||||||
interp = interp[:-5] + interp[-4:]
|
|
||||||
cmdline = "%s -u %s" % (interp, cmdline)
|
|
||||||
if '=' not in query and '"' not in query:
|
|
||||||
cmdline = '%s "%s"' % (cmdline, query)
|
|
||||||
self.log_message("command: %s", cmdline)
|
|
||||||
try:
|
|
||||||
nbytes = int(length)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
nbytes = 0
|
|
||||||
files = popenx(cmdline, 'b')
|
|
||||||
fi = files[0]
|
|
||||||
fo = files[1]
|
|
||||||
if self.have_popen3:
|
|
||||||
fe = files[2]
|
|
||||||
if self.command.lower() == "post" and nbytes > 0:
|
|
||||||
data = self.rfile.read(nbytes)
|
|
||||||
fi.write(data)
|
|
||||||
# throw away additional data [see bug #427345]
|
|
||||||
while select.select([self.rfile._sock], [], [], 0)[0]:
|
|
||||||
if not self.rfile._sock.recv(1):
|
|
||||||
break
|
|
||||||
fi.close()
|
|
||||||
shutil.copyfileobj(fo, self.wfile)
|
|
||||||
if self.have_popen3:
|
|
||||||
errors = fe.read()
|
|
||||||
fe.close()
|
|
||||||
if errors:
|
|
||||||
self.log_error('%s', errors)
|
|
||||||
sts = fo.close()
|
|
||||||
if sts:
|
|
||||||
self.log_error("CGI script exit status %#x", sts)
|
|
||||||
else:
|
|
||||||
self.log_message("CGI script exited OK")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Other O.S. -- execute script in this process
|
|
||||||
save_argv = sys.argv
|
|
||||||
save_stdin = sys.stdin
|
|
||||||
save_stdout = sys.stdout
|
|
||||||
save_stderr = sys.stderr
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
sys.argv = [scriptfile]
|
|
||||||
if '=' not in decoded_query:
|
|
||||||
sys.argv.append(decoded_query)
|
|
||||||
sys.stdout = self.wfile
|
|
||||||
sys.stdin = self.rfile
|
|
||||||
execfile(scriptfile, {"__name__": "__main__"})
|
|
||||||
finally:
|
|
||||||
sys.argv = save_argv
|
|
||||||
sys.stdin = save_stdin
|
|
||||||
sys.stdout = save_stdout
|
|
||||||
sys.stderr = save_stderr
|
|
||||||
except SystemExit, sts:
|
|
||||||
self.log_error("CGI script exit status %s", str(sts))
|
|
||||||
else:
|
|
||||||
self.log_message("CGI script exited OK")
|
|
||||||
nobody = None
|
|
||||||
|
|
||||||
def nobody_uid():
|
|
||||||
"""Internal routine to get nobody's uid"""
|
|
||||||
global nobody
|
|
||||||
if nobody:
|
|
||||||
return nobody
|
|
||||||
try:
|
|
||||||
import pwd
|
|
||||||
except ImportError:
|
|
||||||
return -1
|
|
||||||
try:
|
|
||||||
nobody = pwd.getpwnam('nobody')[2]
|
|
||||||
except KeyError:
|
|
||||||
nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
|
|
||||||
return nobody
|
|
||||||
def executable(path):
|
|
||||||
"""Test for executable file."""
|
|
||||||
try:
|
|
||||||
st = os.stat(path)
|
|
||||||
except os.error:
|
|
||||||
return False
|
|
||||||
return st.st_mode & 0111 != 0
|
|
||||||
def test(HandlerClass = CGIHTTPRequestHandler,
|
|
||||||
ServerClass = BaseHTTPServer.HTTPServer):
|
|
||||||
SimpleHTTPServer.test(HandlerClass, ServerClass)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test()
|
|
@ -1,201 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
"""Simple HTTP Server.
|
|
||||||
|
|
||||||
Hacked by aum from standard python module of the same name,
|
|
||||||
modified for compatibility with sunshine's 'i2p.sam.socket'.
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
This module builds on SAMBaseHTTPServer by implementing the standard GET
|
|
||||||
and HEAD requests in a fairly straightforward manner.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.6"
|
|
||||||
|
|
||||||
__all__ = ["SimpleHTTPRequestHandler"]
|
|
||||||
|
|
||||||
import os
|
|
||||||
import posixpath
|
|
||||||
from i2p import BaseHTTPServer
|
|
||||||
import urllib
|
|
||||||
import cgi
|
|
||||||
import shutil
|
|
||||||
import mimetypes
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
# conveniences
|
|
||||||
HTTPServer = BaseHTTPServer.HTTPServer
|
|
||||||
|
|
||||||
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|
||||||
|
|
||||||
"""Simple HTTP request handler with GET and HEAD commands.
|
|
||||||
|
|
||||||
This serves files from the current directory and any of its
|
|
||||||
subdirectories. It assumes that all files are plain text files
|
|
||||||
unless they have the extension ".html" in which case it assumes
|
|
||||||
they are HTML files.
|
|
||||||
|
|
||||||
The GET and HEAD requests are identical except that the HEAD
|
|
||||||
request omits the actual contents of the file.
|
|
||||||
|
|
||||||
"""
|
|
||||||
server_version = "SimpleHTTP/" + __version__
|
|
||||||
|
|
||||||
def do_GET(self):
|
|
||||||
"""Serve a GET request."""
|
|
||||||
f = self.send_head()
|
|
||||||
if f:
|
|
||||||
self.copyfile(f, self.wfile)
|
|
||||||
f.close()
|
|
||||||
def do_HEAD(self):
|
|
||||||
"""Serve a HEAD request."""
|
|
||||||
f = self.send_head()
|
|
||||||
if f:
|
|
||||||
f.close()
|
|
||||||
def send_head(self):
|
|
||||||
"""Common code for GET and HEAD commands.
|
|
||||||
|
|
||||||
This sends the response code and MIME headers.
|
|
||||||
|
|
||||||
Return value is either a file object (which has to be copied
|
|
||||||
to the outputfile by the caller unless the command was HEAD,
|
|
||||||
and must be closed by the caller under all circumstances), or
|
|
||||||
None, in which case the caller has nothing further to do.
|
|
||||||
|
|
||||||
"""
|
|
||||||
path = self.translate_path(self.path)
|
|
||||||
f = None
|
|
||||||
if os.path.isdir(path):
|
|
||||||
for index in "index.html", "index.htm":
|
|
||||||
index = os.path.join(path, index)
|
|
||||||
if os.path.exists(index):
|
|
||||||
path = index
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return self.list_directory(path)
|
|
||||||
ctype = self.guess_type(path)
|
|
||||||
if ctype.startswith('text/'):
|
|
||||||
mode = 'r'
|
|
||||||
else:
|
|
||||||
mode = 'rb'
|
|
||||||
try:
|
|
||||||
f = open(path, mode)
|
|
||||||
except IOError:
|
|
||||||
self.send_error(404, "File not found")
|
|
||||||
return None
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", ctype)
|
|
||||||
self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
|
|
||||||
self.end_headers()
|
|
||||||
return f
|
|
||||||
def list_directory(self, path):
|
|
||||||
"""Helper to produce a directory listing (absent index.html).
|
|
||||||
|
|
||||||
Return value is either a file object, or None (indicating an
|
|
||||||
error). In either case, the headers are sent, making the
|
|
||||||
interface the same as for send_head().
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
list = os.listdir(path)
|
|
||||||
except os.error:
|
|
||||||
self.send_error(404, "No permission to list directory")
|
|
||||||
return None
|
|
||||||
list.sort(lambda a, b: cmp(a.lower(), b.lower()))
|
|
||||||
f = StringIO()
|
|
||||||
f.write("<title>Directory listing for %s</title>\n" % self.path)
|
|
||||||
f.write("<h2>Directory listing for %s</h2>\n" % self.path)
|
|
||||||
f.write("<hr>\n<ul>\n")
|
|
||||||
for name in list:
|
|
||||||
fullname = os.path.join(path, name)
|
|
||||||
displayname = linkname = name = cgi.escape(name)
|
|
||||||
# Append / for directories or @ for symbolic links
|
|
||||||
if os.path.isdir(fullname):
|
|
||||||
displayname = name + "/"
|
|
||||||
linkname = name + "/"
|
|
||||||
if os.path.islink(fullname):
|
|
||||||
displayname = name + "@"
|
|
||||||
# Note: a link to a directory displays with @ and links with /
|
|
||||||
f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
|
|
||||||
f.write("</ul>\n<hr>\n")
|
|
||||||
length = f.tell()
|
|
||||||
f.seek(0)
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", "text/html")
|
|
||||||
self.send_header("Content-Length", str(length))
|
|
||||||
self.end_headers()
|
|
||||||
return f
|
|
||||||
def translate_path(self, path):
|
|
||||||
"""Translate a /-separated PATH to the local filename syntax.
|
|
||||||
|
|
||||||
Components that mean special things to the local file system
|
|
||||||
(e.g. drive or directory names) are ignored. (XXX They should
|
|
||||||
probably be diagnosed.)
|
|
||||||
|
|
||||||
"""
|
|
||||||
path = posixpath.normpath(urllib.unquote(path))
|
|
||||||
words = path.split('/')
|
|
||||||
words = filter(None, words)
|
|
||||||
path = os.getcwd()
|
|
||||||
for word in words:
|
|
||||||
drive, word = os.path.splitdrive(word)
|
|
||||||
head, word = os.path.split(word)
|
|
||||||
if word in (os.curdir, os.pardir): continue
|
|
||||||
path = os.path.join(path, word)
|
|
||||||
return path
|
|
||||||
def copyfile(self, source, outputfile):
|
|
||||||
"""Copy all data between two file objects.
|
|
||||||
|
|
||||||
The SOURCE argument is a file object open for reading
|
|
||||||
(or anything with a read() method) and the DESTINATION
|
|
||||||
argument is a file object open for writing (or
|
|
||||||
anything with a write() method).
|
|
||||||
|
|
||||||
The only reason for overriding this would be to change
|
|
||||||
the block size or perhaps to replace newlines by CRLF
|
|
||||||
-- note however that this the default server uses this
|
|
||||||
to copy binary data as well.
|
|
||||||
|
|
||||||
"""
|
|
||||||
shutil.copyfileobj(source, outputfile)
|
|
||||||
def guess_type(self, path):
|
|
||||||
"""Guess the type of a file.
|
|
||||||
|
|
||||||
Argument is a PATH (a filename).
|
|
||||||
|
|
||||||
Return value is a string of the form type/subtype,
|
|
||||||
usable for a MIME Content-type header.
|
|
||||||
|
|
||||||
The default implementation looks the file's extension
|
|
||||||
up in the table self.extensions_map, using text/plain
|
|
||||||
as a default; however it would be permissible (if
|
|
||||||
slow) to look inside the data to make a better guess.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
base, ext = posixpath.splitext(path)
|
|
||||||
if ext in self.extensions_map:
|
|
||||||
return self.extensions_map[ext]
|
|
||||||
ext = ext.lower()
|
|
||||||
if ext in self.extensions_map:
|
|
||||||
return self.extensions_map[ext]
|
|
||||||
else:
|
|
||||||
return self.extensions_map['']
|
|
||||||
|
|
||||||
extensions_map = mimetypes.types_map.copy()
|
|
||||||
extensions_map.update({
|
|
||||||
'': 'application/octet-stream', # Default
|
|
||||||
'.py': 'text/plain',
|
|
||||||
'.c': 'text/plain',
|
|
||||||
'.h': 'text/plain',
|
|
||||||
})
|
|
||||||
|
|
||||||
def test(HandlerClass = SimpleHTTPRequestHandler,
|
|
||||||
ServerClass = BaseHTTPServer.HTTPServer):
|
|
||||||
BaseHTTPServer.test(HandlerClass, ServerClass)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test()
|
|
@ -1,542 +0,0 @@
|
|||||||
|
|
||||||
"""Generic I2P SAM socket server classes.
|
|
||||||
|
|
||||||
This is taken almost verbatim from the official python SocketServer.py
|
|
||||||
module, but has been hacked by aum to work with I2P SAM sockets
|
|
||||||
(refer I2P - www.i2p.net - CVS: /i2p/apps/sam/python/src)
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
Original module docstring...
|
|
||||||
|
|
||||||
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 ForkingServer and ThreadingServer 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 mix-in request handler
|
|
||||||
classes 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# replace official python socket module with sunshine's I2P sam module
|
|
||||||
#import socket
|
|
||||||
import i2p
|
|
||||||
from i2p import sam as socket
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
__version__ = "0.1"
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer",
|
|
||||||
"ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler",
|
|
||||||
"StreamRequestHandler","DatagramRequestHandler",
|
|
||||||
"ThreadingMixIn", "ForkingMixIn"]
|
|
||||||
class BaseServer:
|
|
||||||
|
|
||||||
"""Base class for server classes.
|
|
||||||
|
|
||||||
Methods for the caller:
|
|
||||||
|
|
||||||
- __init__(server_address, RequestHandlerClass)
|
|
||||||
- serve_forever()
|
|
||||||
- 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
|
|
||||||
- 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:
|
|
||||||
|
|
||||||
- address_family
|
|
||||||
- socket_type
|
|
||||||
- allow_reuse_address
|
|
||||||
|
|
||||||
Instance variables:
|
|
||||||
|
|
||||||
- RequestHandlerClass
|
|
||||||
- socket
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, server_address, RequestHandlerClass):
|
|
||||||
"""Constructor. May be extended, do not override."""
|
|
||||||
self.server_address = server_address
|
|
||||||
self.RequestHandlerClass = RequestHandlerClass
|
|
||||||
def server_activate(self):
|
|
||||||
"""Called by constructor to activate the server.
|
|
||||||
|
|
||||||
May be overridden.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
def serve_forever(self):
|
|
||||||
"""Handle one request at a time until doomsday."""
|
|
||||||
while 1:
|
|
||||||
self.handle_request()
|
|
||||||
# The distinction between handling, getting, processing and
|
|
||||||
# finishing a request is fairly arbitrary. Remember:
|
|
||||||
#
|
|
||||||
# - handle_request() is the top-level call. It calls
|
|
||||||
# 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."""
|
|
||||||
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 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)
|
|
||||||
- serve_forever()
|
|
||||||
- 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
|
|
||||||
- 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:
|
|
||||||
|
|
||||||
- address_family
|
|
||||||
- socket_type
|
|
||||||
- request_queue_size (only for stream sockets)
|
|
||||||
- allow_reuse_address
|
|
||||||
|
|
||||||
Instance variables:
|
|
||||||
|
|
||||||
- server_address
|
|
||||||
- RequestHandlerClass
|
|
||||||
- socket
|
|
||||||
|
|
||||||
"""
|
|
||||||
socket_type = socket.SOCK_STREAM
|
|
||||||
|
|
||||||
request_queue_size = 5
|
|
||||||
|
|
||||||
allow_reuse_address = False
|
|
||||||
|
|
||||||
def __init__(self, server_address, RequestHandlerClass):
|
|
||||||
"""Constructor. May be extended, do not override."""
|
|
||||||
BaseServer.__init__(self, server_address, RequestHandlerClass)
|
|
||||||
|
|
||||||
#self.socket = socket.socket(self.address_family,
|
|
||||||
# self.socket_type)
|
|
||||||
self.server_address = server_address
|
|
||||||
self.socket = socket.socket(server_address,
|
|
||||||
self.socket_type)
|
|
||||||
|
|
||||||
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)
|
|
||||||
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."""
|
|
||||||
active_children = None
|
|
||||||
max_children = 40
|
|
||||||
|
|
||||||
def collect_children(self):
|
|
||||||
"""Internal routine to wait for died children."""
|
|
||||||
while self.active_children:
|
|
||||||
if len(self.active_children) < self.max_children:
|
|
||||||
options = os.WNOHANG
|
|
||||||
else:
|
|
||||||
# If the maximum number of children are already
|
|
||||||
# running, block while waiting for a child to exit
|
|
||||||
options = 0
|
|
||||||
try:
|
|
||||||
pid, status = os.waitpid(0, options)
|
|
||||||
except os.error:
|
|
||||||
pid = None
|
|
||||||
if not pid: break
|
|
||||||
self.active_children.remove(pid)
|
|
||||||
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."""
|
|
||||||
import threading
|
|
||||||
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
|
|
||||||
|
|
||||||
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):
|
|
||||||
import StringIO
|
|
||||||
self.packet, self.socket = self.request
|
|
||||||
self.rfile = StringIO.StringIO(self.packet)
|
|
||||||
self.wfile = StringIO.StringIO()
|
|
||||||
def finish(self):
|
|
||||||
self.socket.sendto(self.wfile.getvalue(), self.client_address)
|
|
@ -3,7 +3,8 @@ i2p -- I2P Python interface
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ['Error', 'RouterError', 'sam', 'eep', 'router',
|
__all__ = ['Error', 'RouterError', 'sam', 'eep', 'router',
|
||||||
'SocketServer', 'BaseHTTPServer', 'SimpleHTTPServer', 'CGIHTTPServer',
|
'I2PSocketServer', 'I2PBaseHTTPServer',
|
||||||
|
'I2PSimpleHTTPServer', 'I2PCGIHTTPServer',
|
||||||
]
|
]
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
|
Reference in New Issue
Block a user