tinyproxy.py
223 lines
| 6.7 KiB
| text/x-python
|
PythonLexer
/ tests / tinyproxy.py
|
r46434 | #!/usr/bin/env python3 | ||
|
r2337 | |||
|
r28646 | from __future__ import absolute_import, print_function | ||
|
r27302 | |||
|
r2337 | __doc__ = """Tiny HTTP Proxy. | ||
This module implements GET, HEAD, POST, PUT and DELETE methods | ||||
on BaseHTTPServer, and behaves as an HTTP proxy. The CONNECT | ||||
method is also implemented experimentally, but has not been | ||||
tested yet. | ||||
Any help will be greatly appreciated. SUZUKI Hisao | ||||
""" | ||||
__version__ = "0.2.1" | ||||
|
r29565 | import optparse | ||
|
r27302 | import os | ||
import select | ||||
import socket | ||||
|
r28778 | import sys | ||
|
r29431 | |||
|
r41858 | from mercurial import ( | ||
pycompat, | ||||
util, | ||||
) | ||||
|
r29431 | |||
|
r29566 | httpserver = util.httpserver | ||
|
r29433 | socketserver = util.socketserver | ||
|
r31571 | urlreq = util.urlreq | ||
|
r2337 | |||
|
r31005 | if os.environ.get('HGIPV6', '0') == '1': | ||
family = socket.AF_INET6 | ||||
else: | ||||
family = socket.AF_INET | ||||
|
r43346 | |||
class ProxyHandler(httpserver.basehttprequesthandler): | ||||
|
r29566 | __base = httpserver.basehttprequesthandler | ||
|
r2337 | __base_handle = __base.handle | ||
server_version = "TinyHTTPProxy/" + __version__ | ||||
|
r43346 | rbufsize = 0 # self.rfile Be unbuffered | ||
|
r2337 | |||
def handle(self): | ||||
|
r27637 | (ip, port) = self.client_address | ||
|
r14971 | allowed = getattr(self, 'allowed_clients', None) | ||
if allowed is not None and ip not in allowed: | ||||
|
r2337 | self.raw_requestline = self.rfile.readline() | ||
|
r10282 | if self.parse_request(): | ||
self.send_error(403) | ||||
|
r2337 | else: | ||
self.__base_handle() | ||||
|
r14093 | def log_request(self, code='-', size='-'): | ||
xheaders = [h for h in self.headers.items() if h[0].startswith('x-')] | ||||
|
r43346 | self.log_message( | ||
'"%s" %s %s%s', | ||||
self.requestline, | ||||
str(code), | ||||
str(size), | ||||
''.join([' %s:%s' % h for h in sorted(xheaders)]), | ||||
) | ||||
|
r32906 | # Flush for Windows, so output isn't lost on TerminateProcess() | ||
|
r32916 | sys.stdout.flush() | ||
|
r32906 | sys.stderr.flush() | ||
|
r14093 | |||
|
r2337 | def _connect_to(self, netloc, soc): | ||
i = netloc.find(':') | ||||
if i >= 0: | ||||
|
r43346 | host_port = netloc[:i], int(netloc[i + 1 :]) | ||
|
r2337 | else: | ||
host_port = netloc, 80 | ||||
|
r28646 | print("\t" "connect to %s:%d" % host_port) | ||
|
r43346 | try: | ||
soc.connect(host_port) | ||||
|
r25660 | except socket.error as arg: | ||
|
r43346 | try: | ||
msg = arg[1] | ||||
except (IndexError, TypeError): | ||||
msg = arg | ||||
|
r2337 | self.send_error(404, msg) | ||
return 0 | ||||
return 1 | ||||
def do_CONNECT(self): | ||||
|
r31005 | soc = socket.socket(family, socket.SOCK_STREAM) | ||
|
r2337 | try: | ||
if self._connect_to(self.path, soc): | ||||
self.log_request(200) | ||||
|
r43346 | self.wfile.write( | ||
pycompat.bytestr(self.protocol_version) | ||||
+ b" 200 Connection established\r\n" | ||||
) | ||||
self.wfile.write( | ||||
b"Proxy-agent: %s\r\n" | ||||
% pycompat.bytestr(self.version_string()) | ||||
) | ||||
|
r41858 | self.wfile.write(b"\r\n") | ||
|
r2337 | self._read_write(soc, 300) | ||
finally: | ||||
|
r28646 | print("\t" "bye") | ||
|
r2337 | soc.close() | ||
self.connection.close() | ||||
def do_GET(self): | ||||
|
r31571 | (scm, netloc, path, params, query, fragment) = urlreq.urlparse( | ||
|
r43346 | self.path, 'http' | ||
) | ||||
|
r2337 | if scm != 'http' or fragment or not netloc: | ||
self.send_error(400, "bad url %s" % self.path) | ||||
return | ||||
|
r31005 | soc = socket.socket(family, socket.SOCK_STREAM) | ||
|
r2337 | try: | ||
if self._connect_to(netloc, soc): | ||||
self.log_request() | ||||
|
r41858 | url = urlreq.urlunparse(('', '', path, params, query, '')) | ||
|
r43346 | soc.send( | ||
b"%s %s %s\r\n" | ||||
% ( | ||||
pycompat.bytestr(self.command), | ||||
pycompat.bytestr(url), | ||||
pycompat.bytestr(self.request_version), | ||||
) | ||||
) | ||||
|
r2337 | self.headers['Connection'] = 'close' | ||
del self.headers['Proxy-Connection'] | ||||
|
r41858 | for key, val in self.headers.items(): | ||
|
r43346 | soc.send( | ||
b"%s: %s\r\n" | ||||
% (pycompat.bytestr(key), pycompat.bytestr(val)) | ||||
) | ||||
|
r41858 | soc.send(b"\r\n") | ||
|
r2337 | self._read_write(soc) | ||
finally: | ||||
|
r28646 | print("\t" "bye") | ||
|
r2337 | soc.close() | ||
self.connection.close() | ||||
def _read_write(self, soc, max_idling=20): | ||||
iw = [self.connection, soc] | ||||
ow = [] | ||||
count = 0 | ||||
|
r14494 | while True: | ||
|
r2337 | count += 1 | ||
(ins, _, exs) = select.select(iw, ow, iw, 3) | ||||
|
r10282 | if exs: | ||
break | ||||
|
r2337 | if ins: | ||
for i in ins: | ||||
if i is soc: | ||||
out = self.connection | ||||
else: | ||||
out = soc | ||||
|
r18519 | try: | ||
data = i.recv(8192) | ||||
except socket.error: | ||||
break | ||||
|
r2337 | if data: | ||
out.send(data) | ||||
count = 0 | ||||
else: | ||||
|
r28646 | print("\t" "idle", count) | ||
|
r10282 | if count == max_idling: | ||
break | ||||
|
r2337 | |||
do_HEAD = do_GET | ||||
do_POST = do_GET | ||||
|
r43346 | do_PUT = do_GET | ||
|
r10282 | do_DELETE = do_GET | ||
|
r2337 | |||
|
r43346 | |||
class ThreadingHTTPServer(socketserver.ThreadingMixIn, httpserver.httpserver): | ||||
|
r16300 | def __init__(self, *args, **kwargs): | ||
|
r29566 | httpserver.httpserver.__init__(self, *args, **kwargs) | ||
|
r16300 | a = open("proxy.pid", "w") | ||
a.write(str(os.getpid()) + "\n") | ||||
a.close() | ||||
|
r2337 | |||
|
r43346 | |||
|
r29565 | def runserver(port=8000, bind=""): | ||
server_address = (bind, port) | ||||
ProxyHandler.protocol_version = "HTTP/1.0" | ||||
httpd = ThreadingHTTPServer(server_address, ProxyHandler) | ||||
sa = httpd.socket.getsockname() | ||||
print("Serving HTTP on", sa[0], "port", sa[1], "...") | ||||
try: | ||||
httpd.serve_forever() | ||||
except KeyboardInterrupt: | ||||
print("\nKeyboard interrupt received, exiting.") | ||||
httpd.server_close() | ||||
sys.exit(0) | ||||
|
r43346 | |||
|
r2337 | if __name__ == '__main__': | ||
|
r28778 | argv = sys.argv | ||
|
r2337 | if argv[1:] and argv[1] in ('-h', '--help'): | ||
|
r28646 | print(argv[0], "[port [allowed_client_name ...]]") | ||
|
r2337 | else: | ||
if argv[2:]: | ||||
allowed = [] | ||||
for name in argv[2:]: | ||||
client = socket.gethostbyname(name) | ||||
allowed.append(client) | ||||
|
r28646 | print("Accept: %s (%s)" % (client, name)) | ||
|
r2337 | ProxyHandler.allowed_clients = allowed | ||
del argv[2:] | ||||
else: | ||||
|
r28646 | print("Any clients will be served...") | ||
|
r29565 | |||
parser = optparse.OptionParser() | ||||
|
r43346 | parser.add_option( | ||
'-b', | ||||
'--bind', | ||||
metavar='ADDRESS', | ||||
help='Specify alternate bind address ' '[default: all interfaces]', | ||||
default='', | ||||
) | ||||
|
r29565 | (options, args) = parser.parse_args() | ||
port = 8000 | ||||
if len(args) == 1: | ||||
port = int(args[0]) | ||||
runserver(port, options.bind) | ||||