123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- # -*- test-case-name: twisted.web.test.test_proxy -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- Simplistic HTTP proxy support.
-
- This comes in two main variants - the Proxy and the ReverseProxy.
-
- When a Proxy is in use, a browser trying to connect to a server (say,
- www.yahoo.com) will be intercepted by the Proxy, and the proxy will covertly
- connect to the server, and return the result.
-
- When a ReverseProxy is in use, the client connects directly to the ReverseProxy
- (say, www.yahoo.com) which farms off the request to one of a pool of servers,
- and returns the result.
-
- Normally, a Proxy is used on the client end of an Internet connection, while a
- ReverseProxy is used on the server end.
- """
-
- from urllib.parse import quote as urlquote, urlparse, urlunparse
-
- from twisted.internet import reactor
- from twisted.internet.protocol import ClientFactory
- from twisted.web.http import _QUEUED_SENTINEL, HTTPChannel, HTTPClient, Request
- from twisted.web.resource import Resource
- from twisted.web.server import NOT_DONE_YET
-
-
- class ProxyClient(HTTPClient):
- """
- Used by ProxyClientFactory to implement a simple web proxy.
-
- @ivar _finished: A flag which indicates whether or not the original request
- has been finished yet.
- """
-
- _finished = False
-
- def __init__(self, command, rest, version, headers, data, father):
- self.father = father
- self.command = command
- self.rest = rest
- if b"proxy-connection" in headers:
- del headers[b"proxy-connection"]
- headers[b"connection"] = b"close"
- headers.pop(b"keep-alive", None)
- self.headers = headers
- self.data = data
-
- def connectionMade(self):
- self.sendCommand(self.command, self.rest)
- for header, value in self.headers.items():
- self.sendHeader(header, value)
- self.endHeaders()
- self.transport.write(self.data)
-
- def handleStatus(self, version, code, message):
- self.father.setResponseCode(int(code), message)
-
- def handleHeader(self, key, value):
- # t.web.server.Request sets default values for these headers in its
- # 'process' method. When these headers are received from the remote
- # server, they ought to override the defaults, rather than append to
- # them.
- if key.lower() in [b"server", b"date", b"content-type"]:
- self.father.responseHeaders.setRawHeaders(key, [value])
- else:
- self.father.responseHeaders.addRawHeader(key, value)
-
- def handleResponsePart(self, buffer):
- self.father.write(buffer)
-
- def handleResponseEnd(self):
- """
- Finish the original request, indicating that the response has been
- completely written to it, and disconnect the outgoing transport.
- """
- if not self._finished:
- self._finished = True
- self.father.finish()
- self.transport.loseConnection()
-
-
- class ProxyClientFactory(ClientFactory):
- """
- Used by ProxyRequest to implement a simple web proxy.
- """
-
- # Type is wrong. See: https://twistedmatrix.com/trac/ticket/10006
- protocol = ProxyClient # type: ignore[assignment]
-
- def __init__(self, command, rest, version, headers, data, father):
- self.father = father
- self.command = command
- self.rest = rest
- self.headers = headers
- self.data = data
- self.version = version
-
- def buildProtocol(self, addr):
- return self.protocol(
- self.command, self.rest, self.version, self.headers, self.data, self.father
- )
-
- def clientConnectionFailed(self, connector, reason):
- """
- Report a connection failure in a response to the incoming request as
- an error.
- """
- self.father.setResponseCode(501, b"Gateway error")
- self.father.responseHeaders.addRawHeader(b"Content-Type", b"text/html")
- self.father.write(b"<H1>Could not connect</H1>")
- self.father.finish()
-
-
- class ProxyRequest(Request):
- """
- Used by Proxy to implement a simple web proxy.
-
- @ivar reactor: the reactor used to create connections.
- @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
- """
-
- protocols = {b"http": ProxyClientFactory}
- ports = {b"http": 80}
-
- def __init__(self, channel, queued=_QUEUED_SENTINEL, reactor=reactor):
- Request.__init__(self, channel, queued)
- self.reactor = reactor
-
- def process(self):
- parsed = urlparse(self.uri)
- protocol = parsed[0]
- host = parsed[1].decode("ascii")
- port = self.ports[protocol]
- if ":" in host:
- host, port = host.split(":")
- port = int(port)
- rest = urlunparse((b"", b"") + parsed[2:])
- if not rest:
- rest = rest + b"/"
- class_ = self.protocols[protocol]
- headers = self.getAllHeaders().copy()
- if b"host" not in headers:
- headers[b"host"] = host.encode("ascii")
- self.content.seek(0, 0)
- s = self.content.read()
- clientFactory = class_(self.method, rest, self.clientproto, headers, s, self)
- self.reactor.connectTCP(host, port, clientFactory)
-
-
- class Proxy(HTTPChannel):
- """
- This class implements a simple web proxy.
-
- Since it inherits from L{twisted.web.http.HTTPChannel}, to use it you
- should do something like this::
-
- from twisted.web import http
- f = http.HTTPFactory()
- f.protocol = Proxy
-
- Make the HTTPFactory a listener on a port as per usual, and you have
- a fully-functioning web proxy!
- """
-
- requestFactory = ProxyRequest
-
-
- class ReverseProxyRequest(Request):
- """
- Used by ReverseProxy to implement a simple reverse proxy.
-
- @ivar proxyClientFactoryClass: a proxy client factory class, used to create
- new connections.
- @type proxyClientFactoryClass: L{ClientFactory}
-
- @ivar reactor: the reactor used to create connections.
- @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
- """
-
- proxyClientFactoryClass = ProxyClientFactory
-
- def __init__(self, channel, queued=_QUEUED_SENTINEL, reactor=reactor):
- Request.__init__(self, channel, queued)
- self.reactor = reactor
-
- def process(self):
- """
- Handle this request by connecting to the proxied server and forwarding
- it there, then forwarding the response back as the response to this
- request.
- """
- self.requestHeaders.setRawHeaders(b"host", [self.factory.host.encode("ascii")])
- clientFactory = self.proxyClientFactoryClass(
- self.method,
- self.uri,
- self.clientproto,
- self.getAllHeaders(),
- self.content.read(),
- self,
- )
- self.reactor.connectTCP(self.factory.host, self.factory.port, clientFactory)
-
-
- class ReverseProxy(HTTPChannel):
- """
- Implements a simple reverse proxy.
-
- For details of usage, see the file examples/reverse-proxy.py.
- """
-
- requestFactory = ReverseProxyRequest
-
-
- class ReverseProxyResource(Resource):
- """
- Resource that renders the results gotten from another server
-
- Put this resource in the tree to cause everything below it to be relayed
- to a different server.
-
- @ivar proxyClientFactoryClass: a proxy client factory class, used to create
- new connections.
- @type proxyClientFactoryClass: L{ClientFactory}
-
- @ivar reactor: the reactor used to create connections.
- @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
- """
-
- proxyClientFactoryClass = ProxyClientFactory
-
- def __init__(self, host, port, path, reactor=reactor):
- """
- @param host: the host of the web server to proxy.
- @type host: C{str}
-
- @param port: the port of the web server to proxy.
- @type port: C{port}
-
- @param path: the base path to fetch data from. Note that you shouldn't
- put any trailing slashes in it, it will be added automatically in
- request. For example, if you put B{/foo}, a request on B{/bar} will
- be proxied to B{/foo/bar}. Any required encoding of special
- characters (such as " " or "/") should have been done already.
-
- @type path: C{bytes}
- """
- Resource.__init__(self)
- self.host = host
- self.port = port
- self.path = path
- self.reactor = reactor
-
- def getChild(self, path, request):
- """
- Create and return a proxy resource with the same proxy configuration
- as this one, except that its path also contains the segment given by
- C{path} at the end.
- """
- return ReverseProxyResource(
- self.host,
- self.port,
- self.path + b"/" + urlquote(path, safe=b"").encode("utf-8"),
- self.reactor,
- )
-
- def render(self, request):
- """
- Render a request by forwarding it to the proxied server.
- """
- # RFC 2616 tells us that we can omit the port if it's the default port,
- # but we have to provide it otherwise
- if self.port == 80:
- host = self.host
- else:
- host = "%s:%d" % (self.host, self.port)
- request.requestHeaders.setRawHeaders(b"host", [host.encode("ascii")])
- request.content.seek(0, 0)
- qs = urlparse(request.uri)[4]
- if qs:
- rest = self.path + b"?" + qs
- else:
- rest = self.path
- clientFactory = self.proxyClientFactoryClass(
- request.method,
- rest,
- request.clientproto,
- request.getAllHeaders(),
- request.content.read(),
- request,
- )
- self.reactor.connectTCP(self.host, self.port, clientFactory)
- return NOT_DONE_YET
|