| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.web.test.test_proxy -*- | |
| 2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 """ | |
| 6 Simplistic HTTP proxy support. | |
| 7 | |
| 8 This comes in two main variants - the Proxy and the ReverseProxy. | |
| 9 | |
| 10 When a Proxy is in use, a browser trying to connect to a server (say, | |
| 11 www.yahoo.com) will be intercepted by the Proxy, and the proxy will covertly | |
| 12 connect to the server, and return the result. | |
| 13 | |
| 14 When a ReverseProxy is in use, the client connects directly to the ReverseProxy | |
| 15 (say, www.yahoo.com) which farms off the request to one of a pool of servers, | |
| 16 and returns the result. | |
| 17 | |
| 18 Normally, a Proxy is used on the client end of an Internet connection, while a | |
| 19 ReverseProxy is used on the server end. | |
| 20 """ | |
| 21 | |
| 22 import urlparse | |
| 23 from urllib import quote as urlquote | |
| 24 | |
| 25 from twisted.internet import reactor | |
| 26 from twisted.internet.protocol import ClientFactory | |
| 27 from twisted.web.resource import Resource | |
| 28 from twisted.web.server import NOT_DONE_YET | |
| 29 from twisted.web.http import HTTPClient, Request, HTTPChannel | |
| 30 | |
| 31 | |
| 32 | |
| 33 class ProxyClient(HTTPClient): | |
| 34 """ | |
| 35 Used by ProxyClientFactory to implement a simple web proxy. | |
| 36 """ | |
| 37 | |
| 38 def __init__(self, command, rest, version, headers, data, father): | |
| 39 self.father = father | |
| 40 self.command = command | |
| 41 self.rest = rest | |
| 42 if "proxy-connection" in headers: | |
| 43 del headers["proxy-connection"] | |
| 44 headers["connection"] = "close" | |
| 45 self.headers = headers | |
| 46 self.data = data | |
| 47 | |
| 48 | |
| 49 def connectionMade(self): | |
| 50 self.sendCommand(self.command, self.rest) | |
| 51 for header, value in self.headers.items(): | |
| 52 self.sendHeader(header, value) | |
| 53 self.endHeaders() | |
| 54 self.transport.write(self.data) | |
| 55 | |
| 56 | |
| 57 def handleStatus(self, version, code, message): | |
| 58 if message: | |
| 59 # Add a whitespace to message, this allows empty messages | |
| 60 # transparently | |
| 61 message = " %s" % (message,) | |
| 62 self.father.transport.write("%s %s%s\r\n" % (version, code, message)) | |
| 63 | |
| 64 | |
| 65 def handleHeader(self, key, value): | |
| 66 self.father.transport.write("%s: %s\r\n" % (key, value)) | |
| 67 | |
| 68 | |
| 69 def handleEndHeaders(self): | |
| 70 self.father.transport.write("\r\n") | |
| 71 | |
| 72 | |
| 73 def handleResponsePart(self, buffer): | |
| 74 self.father.transport.write(buffer) | |
| 75 | |
| 76 | |
| 77 def handleResponseEnd(self): | |
| 78 self.transport.loseConnection() | |
| 79 self.father.channel.transport.loseConnection() | |
| 80 | |
| 81 | |
| 82 | |
| 83 class ProxyClientFactory(ClientFactory): | |
| 84 """ | |
| 85 Used by ProxyRequest to implement a simple web proxy. | |
| 86 """ | |
| 87 | |
| 88 protocol = ProxyClient | |
| 89 | |
| 90 | |
| 91 def __init__(self, command, rest, version, headers, data, father): | |
| 92 self.father = father | |
| 93 self.command = command | |
| 94 self.rest = rest | |
| 95 self.headers = headers | |
| 96 self.data = data | |
| 97 self.version = version | |
| 98 | |
| 99 | |
| 100 def buildProtocol(self, addr): | |
| 101 return self.protocol(self.command, self.rest, self.version, | |
| 102 self.headers, self.data, self.father) | |
| 103 | |
| 104 | |
| 105 def clientConnectionFailed(self, connector, reason): | |
| 106 self.father.transport.write("HTTP/1.0 501 Gateway error\r\n") | |
| 107 self.father.transport.write("Content-Type: text/html\r\n") | |
| 108 self.father.transport.write("\r\n") | |
| 109 self.father.transport.write('''<H1>Could not connect</H1>''') | |
| 110 self.father.transport.loseConnection() | |
| 111 | |
| 112 | |
| 113 | |
| 114 class ProxyRequest(Request): | |
| 115 """ | |
| 116 Used by Proxy to implement a simple web proxy. | |
| 117 | |
| 118 @ivar reactor: the reactor used to create connections. | |
| 119 @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP} | |
| 120 """ | |
| 121 | |
| 122 protocols = {'http': ProxyClientFactory} | |
| 123 ports = {'http': 80} | |
| 124 | |
| 125 def __init__(self, channel, queued, reactor=reactor): | |
| 126 Request.__init__(self, channel, queued) | |
| 127 self.reactor = reactor | |
| 128 | |
| 129 | |
| 130 def process(self): | |
| 131 parsed = urlparse.urlparse(self.uri) | |
| 132 protocol = parsed[0] | |
| 133 host = parsed[1] | |
| 134 port = self.ports[protocol] | |
| 135 if ':' in host: | |
| 136 host, port = host.split(':') | |
| 137 port = int(port) | |
| 138 rest = urlparse.urlunparse(('', '') + parsed[2:]) | |
| 139 if not rest: | |
| 140 rest = rest + '/' | |
| 141 class_ = self.protocols[protocol] | |
| 142 headers = self.getAllHeaders().copy() | |
| 143 if 'host' not in headers: | |
| 144 headers['host'] = host | |
| 145 self.content.seek(0, 0) | |
| 146 s = self.content.read() | |
| 147 clientFactory = class_(self.method, rest, self.clientproto, headers, | |
| 148 s, self) | |
| 149 self.reactor.connectTCP(host, port, clientFactory) | |
| 150 | |
| 151 | |
| 152 | |
| 153 class Proxy(HTTPChannel): | |
| 154 """ | |
| 155 This class implements a simple web proxy. | |
| 156 | |
| 157 Since it inherits from L{twisted.protocols.http.HTTPChannel}, to use it you | |
| 158 should do something like this:: | |
| 159 | |
| 160 from twisted.web import http | |
| 161 f = http.HTTPFactory() | |
| 162 f.protocol = Proxy | |
| 163 | |
| 164 Make the HTTPFactory a listener on a port as per usual, and you have | |
| 165 a fully-functioning web proxy! | |
| 166 """ | |
| 167 | |
| 168 requestFactory = ProxyRequest | |
| 169 | |
| 170 | |
| 171 | |
| 172 class ReverseProxyRequest(Request): | |
| 173 """ | |
| 174 Used by ReverseProxy to implement a simple reverse proxy. | |
| 175 | |
| 176 @ivar proxyClientFactoryClass: a proxy client factory class, used to create | |
| 177 new connections. | |
| 178 @type proxyClientFactoryClass: L{ClientFactory} | |
| 179 | |
| 180 @ivar reactor: the reactor used to create connections. | |
| 181 @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP} | |
| 182 """ | |
| 183 | |
| 184 proxyClientFactoryClass = ProxyClientFactory | |
| 185 | |
| 186 def __init__(self, channel, queued, reactor=reactor): | |
| 187 Request.__init__(self, channel, queued) | |
| 188 self.reactor = reactor | |
| 189 | |
| 190 | |
| 191 def process(self): | |
| 192 self.received_headers['host'] = self.factory.host | |
| 193 clientFactory = self.proxyClientFactoryClass( | |
| 194 self.method, self.uri, self.clientproto, self.getAllHeaders(), | |
| 195 self.content.read(), self) | |
| 196 self.reactor.connectTCP(self.factory.host, self.factory.port, | |
| 197 clientFactory) | |
| 198 | |
| 199 | |
| 200 | |
| 201 class ReverseProxy(HTTPChannel): | |
| 202 """ | |
| 203 Implements a simple reverse proxy. | |
| 204 | |
| 205 For details of usage, see the file examples/proxy.py. | |
| 206 """ | |
| 207 | |
| 208 requestFactory = ReverseProxyRequest | |
| 209 | |
| 210 | |
| 211 | |
| 212 class ReverseProxyResource(Resource): | |
| 213 """ | |
| 214 Resource that renders the results gotten from another server | |
| 215 | |
| 216 Put this resource in the tree to cause everything below it to be relayed | |
| 217 to a different server. | |
| 218 | |
| 219 @ivar proxyClientFactoryClass: a proxy client factory class, used to create | |
| 220 new connections. | |
| 221 @type proxyClientFactoryClass: L{ClientFactory} | |
| 222 | |
| 223 @ivar reactor: the reactor used to create connections. | |
| 224 @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP} | |
| 225 """ | |
| 226 | |
| 227 proxyClientFactoryClass = ProxyClientFactory | |
| 228 | |
| 229 | |
| 230 def __init__(self, host, port, path, reactor=reactor): | |
| 231 """ | |
| 232 @param host: the host of the web server to proxy. | |
| 233 @type host: C{str} | |
| 234 | |
| 235 @param port: the port of the web server to proxy. | |
| 236 @type port: C{port} | |
| 237 | |
| 238 @param path: the base path to fetch data from. Note that you shouldn't | |
| 239 put any trailing slashes in it, it will be added automatically in | |
| 240 request. For example, if you put B{/foo}, a request on B{/bar} will | |
| 241 be proxied to B{/foo/bar}. Any required encoding of special | |
| 242 characters (such as " " or "/") should have been done already. | |
| 243 | |
| 244 @type path: C{str} | |
| 245 """ | |
| 246 Resource.__init__(self) | |
| 247 self.host = host | |
| 248 self.port = port | |
| 249 self.path = path | |
| 250 self.reactor = reactor | |
| 251 | |
| 252 | |
| 253 def getChild(self, path, request): | |
| 254 """ | |
| 255 Create and return a proxy resource with the same proxy configuration | |
| 256 as this one, except that its path also contains the segment given by | |
| 257 C{path} at the end. | |
| 258 """ | |
| 259 return ReverseProxyResource( | |
| 260 self.host, self.port, self.path + '/' + urlquote(path, safe="")) | |
| 261 | |
| 262 | |
| 263 def render(self, request): | |
| 264 """ | |
| 265 Render a request by forwarding it to the proxied server. | |
| 266 """ | |
| 267 # RFC 2616 tells us that we can omit the port if it's the default port, | |
| 268 # but we have to provide it otherwise | |
| 269 if self.port == 80: | |
| 270 request.received_headers['host'] = self.host | |
| 271 else: | |
| 272 request.received_headers['host'] = "%s:%d" % (self.host, self.port) | |
| 273 request.content.seek(0, 0) | |
| 274 qs = urlparse.urlparse(request.uri)[4] | |
| 275 if qs: | |
| 276 rest = self.path + '?' + qs | |
| 277 else: | |
| 278 rest = self.path | |
| 279 clientFactory = self.proxyClientFactoryClass( | |
| 280 request.method, rest, request.clientproto, | |
| 281 request.getAllHeaders(), request.content.read(), request) | |
| 282 self.reactor.connectTCP(self.host, self.port, clientFactory) | |
| 283 return NOT_DONE_YET | |
| OLD | NEW |