]>
jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/werkzeug/middleware/http_proxy.py
5 .. autoclass:: ProxyMiddleware
7 :copyright: 2007 Pallets
10 from __future__
import annotations
13 from http
import client
14 from urllib
.parse
import quote
15 from urllib
.parse
import urlsplit
17 from ..datastructures
import EnvironHeaders
18 from ..http
import is_hop_by_hop_header
19 from ..wsgi
import get_input_stream
22 from _typeshed
.wsgi
import StartResponse
23 from _typeshed
.wsgi
import WSGIApplication
24 from _typeshed
.wsgi
import WSGIEnvironment
27 class ProxyMiddleware
:
28 """Proxy requests under a path to an external server, routing other
31 This middleware can only proxy HTTP requests, as HTTP is the only
32 protocol handled by the WSGI server. Other protocols, such as
33 WebSocket requests, cannot be proxied at this layer. This should
34 only be used for development, in production a real proxy server
37 The middleware takes a dict mapping a path prefix to a dict
38 describing the host to be proxied to::
40 app = ProxyMiddleware(app, {
42 "target": "http://127.0.0.1:5001/",
46 Each host has the following options:
49 The target URL to dispatch to. This is required.
51 Whether to remove the prefix from the URL before dispatching it
52 to the target. The default is ``False``.
54 ``"<auto>"`` (default):
55 The host header is automatically rewritten to the URL of the
58 The host header is unmodified from the client request.
60 The host header is overwritten with the value.
62 A dictionary of headers to be sent with the request to the
63 target. The default is ``{}``.
65 A :class:`ssl.SSLContext` defining how to verify requests if the
66 target is HTTPS. The default is ``None``.
68 In the example above, everything under ``"/static/"`` is proxied to
69 the server on port 5001. The host header is rewritten to the target,
70 and the ``"/static/"`` prefix is removed from the URLs.
72 :param app: The WSGI application to wrap.
73 :param targets: Proxy target configurations. See description above.
74 :param chunk_size: Size of chunks to read from input stream and
76 :param timeout: Seconds before an operation to a target fails.
78 .. versionadded:: 0.14
84 targets
: t
.Mapping
[str, dict[str, t
.Any
]],
85 chunk_size
: int = 2 << 13,
88 def _set_defaults(opts
: dict[str, t
.Any
]) -> dict[str, t
.Any
]:
89 opts
.setdefault("remove_prefix", False)
90 opts
.setdefault("host", "<auto>")
91 opts
.setdefault("headers", {})
92 opts
.setdefault("ssl_context", None)
97 f
"/{k.strip('/')}/": _set_defaults(v
) for k
, v
in targets
.items()
99 self
.chunk_size
= chunk_size
100 self
.timeout
= timeout
103 self
, opts
: dict[str, t
.Any
], path
: str, prefix
: str
104 ) -> WSGIApplication
:
105 target
= urlsplit(opts
["target"])
106 # socket can handle unicode host, but header must be ascii
107 host
= target
.hostname
.encode("idna").decode("ascii")
110 environ
: WSGIEnvironment
, start_response
: StartResponse
111 ) -> t
.Iterable
[bytes]:
112 headers
= list(EnvironHeaders(environ
).items())
116 if not is_hop_by_hop_header(k
)
117 and k
.lower() not in ("content-length", "host")
119 headers
.append(("Connection", "close"))
121 if opts
["host"] == "<auto>":
122 headers
.append(("Host", host
))
123 elif opts
["host"] is None:
124 headers
.append(("Host", environ
["HTTP_HOST"]))
126 headers
.append(("Host", opts
["host"]))
128 headers
.extend(opts
["headers"].items())
131 if opts
["remove_prefix"]:
132 remote_path
= remote_path
[len(prefix
) :].lstrip("/")
133 remote_path
= f
"{target.path.rstrip('/')}/{remote_path}"
135 content_length
= environ
.get("CONTENT_LENGTH")
138 if content_length
not in ("", None):
139 headers
.append(("Content-Length", content_length
)) # type: ignore
140 elif content_length
is not None:
141 headers
.append(("Transfer-Encoding", "chunked"))
145 if target
.scheme
== "http":
146 con
= client
.HTTPConnection(
147 host
, target
.port
or 80, timeout
=self
.timeout
149 elif target
.scheme
== "https":
150 con
= client
.HTTPSConnection(
153 timeout
=self
.timeout
,
154 context
=opts
["ssl_context"],
158 "Target scheme must be 'http' or 'https', got"
159 f
" {target.scheme!r}."
163 # safe = https://url.spec.whatwg.org/#url-path-segment-string
164 # as well as percent for things that are already quoted
165 remote_url
= quote(remote_path
, safe
="!$&'()*+,/:;=@%")
166 querystring
= environ
["QUERY_STRING"]
169 remote_url
= f
"{remote_url}?{querystring}"
171 con
.putrequest(environ
["REQUEST_METHOD"], remote_url
, skip_host
=True)
174 if k
.lower() == "connection":
180 stream
= get_input_stream(environ
)
183 data
= stream
.read(self
.chunk_size
)
189 con
.send(b
"%x\r\n%s\r\n" % (len(data
), data
))
193 resp
= con
.getresponse()
195 from ..exceptions
import BadGateway
197 return BadGateway()(environ
, start_response
)
200 f
"{resp.status} {resp.reason}",
203 for k
, v
in resp
.getheaders()
204 if not is_hop_by_hop_header(k
)
208 def read() -> t
.Iterator
[bytes]:
211 data
= resp
.read(self
.chunk_size
)
225 self
, environ
: WSGIEnvironment
, start_response
: StartResponse
226 ) -> t
.Iterable
[bytes]:
227 path
= environ
["PATH_INFO"]
230 for prefix
, opts
in self
.targets
.items():
231 if path
.startswith(prefix
):
232 app
= self
.proxy_to(opts
, path
, prefix
)
235 return app(environ
, start_response
)