Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

proxy.py 9.6KB

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