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 |