Created
March 18, 2012 04:04
-
-
Save fmoo/2068759 to your computer and use it in GitHub Desktop.
CONNECT-enabled HTTP Proxy Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from twisted.web.proxy import Proxy, ProxyRequest | |
from twisted.internet.protocol import Protocol, ClientFactory | |
import urlparse | |
from twisted.python import log | |
class ConnectProxyRequest(ProxyRequest): | |
"""HTTP ProxyRequest handler (factory) that supports CONNECT""" | |
connectedProtocol = None | |
def process(self): | |
if self.method == 'CONNECT': | |
self.processConnectRequest() | |
else: | |
ProxyRequest.process(self) | |
def fail(self, message, body): | |
self.setResponseCode(501, message) | |
self.responseHeaders.addRawHeader("Content-Type", "text/html") | |
self.write(body) | |
self.finish() | |
def splitHostPort(self, hostport, default_port): | |
port = default_port | |
parts = hostport.split(':', 1) | |
if len(parts) == 2: | |
try: | |
port = int(parts[1]) | |
except ValueError: | |
pass | |
return parts[0], port | |
def processConnectRequest(self): | |
parsed = urlparse.urlparse(self.uri) | |
default_port = self.ports.get(parsed.scheme) | |
host, port = self.splitHostPort(parsed.netloc or parsed.path, | |
default_port) | |
if port is None: | |
self.fail("Bad CONNECT Request", | |
"Unable to parse port from URI: %s" % self.uri) | |
return | |
clientFactory = ConnectProxyClientFactory(host, port, self) | |
# TODO provide an API to set proxy connect timeouts | |
self.reactor.connectTCP(host, port, clientFactory) | |
class ConnectProxy(Proxy): | |
"""HTTP Server Protocol that supports CONNECT""" | |
requestFactory = ConnectProxyRequest | |
connectedRemote = None | |
def requestDone(self, request): | |
if request.method == 'CONNECT' and self.connectedRemote is not None: | |
self.connectedRemote.connectedClient = self | |
else: | |
Proxy.requestDone(self, request) | |
def connectionLost(self, reason): | |
if self.connectedRemote is not None: | |
self.connectedRemote.transport.loseConnection(reason) | |
Proxy.connectionLost(self, reason) | |
def dataReceived(self, data): | |
if self.connectedRemote is None: | |
Proxy.dataReceived(self, data) | |
else: | |
# Once proxy is connected, forward all bytes received | |
# from the original client to the remote server. | |
self.connectedRemote.transport.write(data) | |
class ConnectProxyClient(Protocol): | |
connectedClient = None | |
def connectionMade(self): | |
self.factory.request.channel.connectedRemote = self | |
self.factory.request.setResponseCode(200, "CONNECT OK") | |
self.factory.request.setHeader('X-Connected-IP', | |
self.transport.realAddress[0]) | |
self.factory.request.setHeader('Content-Length', '0') | |
self.factory.request.finish() | |
def connectionLost(self, reason): | |
if self.connectedClient is not None: | |
self.connectedClient.transport.loseConnection(reason) | |
def dataReceived(self, data): | |
if self.connectedClient is not None: | |
# Forward all bytes from the remote server back to the | |
# original connected client | |
self.connectedClient.transport.write(data) | |
else: | |
log.msg("UNEXPECTED DATA RECEIVED:", data) | |
class ConnectProxyClientFactory(ClientFactory): | |
protocol = ConnectProxyClient | |
def __init__(self, host, port, request): | |
self.request = request | |
self.host = host | |
self.port = port | |
def clientConnectionFailed(self, connector, reason): | |
self.request.fail("Gateway Error", str(reason)) | |
if __name__ == '__main__': | |
import sys | |
log.startLogging(sys.stderr) | |
import argparse | |
ap = argparse.ArgumentParser() | |
ap.add_argument('port', default=0, nargs='?', type=int) | |
ns = ap.parse_args() | |
import twisted.web.http | |
factory = twisted.web.http.HTTPFactory() | |
factory.protocol = ConnectProxy | |
import twisted.internet | |
c = twisted.internet.reactor.listenTCP(ns.port, factory) | |
twisted.internet.reactor.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment