Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,8 +1512,7 @@ As a decorator:
- Generate HTTP GET request with headers

```python
>>> build_http_request(b'GET', b'/',
headers={b'Connection': b'close'})
>>> build_http_request(b'GET', b'/', conn_close=True)
b'GET / HTTP/1.1\r\nConnection: close\r\n\r\n'
```

Expand Down
2 changes: 1 addition & 1 deletion examples/https_connect_tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class HttpsConnectTunnelHandler(BaseTcpTunnelHandler):
PROXY_TUNNEL_UNSUPPORTED_SCHEME = memoryview(
build_http_response(
httpStatusCodes.BAD_REQUEST,
headers={b'Connection': b'close'},
reason=b'Unsupported protocol scheme',
conn_close=True,
),
)

Expand Down
20 changes: 13 additions & 7 deletions proxy/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ def build_http_request(
protocol_version: bytes = HTTP_1_1,
headers: Optional[Dict[bytes, bytes]] = None,
body: Optional[bytes] = None,
conn_close: bool = False,
) -> bytes:
"""Build and returns a HTTP request packet."""
if headers is None:
headers = {}
return build_http_pkt(
[method, url, protocol_version], headers, body,
[method, url, protocol_version],
headers or {},
body,
conn_close,
)


Expand All @@ -90,6 +92,7 @@ def build_http_response(
reason: Optional[bytes] = None,
headers: Optional[Dict[bytes, bytes]] = None,
body: Optional[bytes] = None,
conn_close: bool = False,
) -> bytes:
"""Build and returns a HTTP response packet."""
line = [protocol_version, bytes_(status_code)]
Expand All @@ -108,7 +111,7 @@ def build_http_response(
not has_transfer_encoding and \
not has_content_length:
headers[b'Content-Length'] = bytes_(len(body))
return build_http_pkt(line, headers, body)
return build_http_pkt(line, headers, body, conn_close)


def build_http_header(k: bytes, v: bytes) -> bytes:
Expand All @@ -120,12 +123,15 @@ def build_http_pkt(
line: List[bytes],
headers: Optional[Dict[bytes, bytes]] = None,
body: Optional[bytes] = None,
conn_close: bool = False,
) -> bytes:
"""Build and returns a HTTP request or response packet."""
pkt = WHITESPACE.join(line) + CRLF
if headers is not None:
for k, v in headers.items():
pkt += build_http_header(k, v) + CRLF
headers = headers or {}
if conn_close:
headers[b'Connection'] = b'close'
for k, v in headers.items():
pkt += build_http_header(k, v) + CRLF
pkt += CRLF
if body:
pkt += body
Expand Down
2 changes: 1 addition & 1 deletion proxy/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def handle_request(self, request: HttpParser) -> None:
headers={
b'Location': b'/dashboard/',
b'Content-Length': b'0',
b'Connection': b'close',
},
conn_close=True,
),
),
)
Expand Down
1 change: 1 addition & 0 deletions proxy/http/exception/http_request_rejected.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def response(self, _request: HttpParser) -> Optional[memoryview]:
reason=self.reason,
headers=self.headers,
body=self.body,
conn_close=True,
),
)
return None
2 changes: 1 addition & 1 deletion proxy/http/exception/proxy_auth_failed.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class ProxyAuthenticationFailed(HttpProtocolException):
headers={
PROXY_AGENT_HEADER_KEY: PROXY_AGENT_HEADER_VALUE,
b'Proxy-Authenticate': b'Basic',
b'Connection': b'close',
},
body=b'Proxy Authentication Required',
conn_close=True,
),
)

Expand Down
2 changes: 1 addition & 1 deletion proxy/http/exception/proxy_conn_failed.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class ProxyConnectionFailed(HttpProtocolException):
reason=b'Bad Gateway',
headers={
PROXY_AGENT_HEADER_KEY: PROXY_AGENT_HEADER_VALUE,
b'Connection': b'close',
},
body=b'Bad Gateway',
conn_close=True,
),
)

Expand Down
6 changes: 3 additions & 3 deletions proxy/http/server/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
headers={
b'Server': PROXY_AGENT_HEADER_VALUE,
b'Content-Length': b'0',
b'Connection': b'close',
},
conn_close=True,
),
)

Expand All @@ -93,8 +93,8 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
headers={
b'Server': PROXY_AGENT_HEADER_VALUE,
b'Content-Length': b'0',
b'Connection': b'close',
},
conn_close=True,
),
)

Expand Down Expand Up @@ -147,7 +147,6 @@ def read_and_build_static_file_response(path: str, min_compression_limit: int) -
headers = {
b'Content-Type': bytes_(content_type),
b'Cache-Control': b'max-age=86400',
b'Connection': b'close',
}
do_compress = len(content) > min_compression_limit
if do_compress:
Expand All @@ -160,6 +159,7 @@ def read_and_build_static_file_response(path: str, min_compression_limit: int) -
reason=b'OK',
headers=headers,
body=gzip.compress(content) if do_compress else content,
conn_close=True,
),
)
except FileNotFoundError:
Expand Down
6 changes: 2 additions & 4 deletions proxy/plugin/filter_by_client_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ def before_upstream_connection(
assert not self.flags.unix_socket_path and self.client.addr
if self.client.addr[0] in self.flags.filtered_client_ips.split(','):
raise HttpRequestRejected(
status_code=httpStatusCodes.I_AM_A_TEAPOT, reason=b'I\'m a tea pot',
headers={
b'Connection': b'close',
},
status_code=httpStatusCodes.I_AM_A_TEAPOT,
reason=b'I\'m a tea pot',
)
return request
6 changes: 2 additions & 4 deletions proxy/plugin/filter_by_upstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ def before_upstream_connection(
) -> Optional[HttpParser]:
if text_(request.host) in self.flags.filtered_upstream_hosts.split(','):
raise HttpRequestRejected(
status_code=httpStatusCodes.I_AM_A_TEAPOT, reason=b'I\'m a tea pot',
headers={
b'Connection': b'close',
},
status_code=httpStatusCodes.I_AM_A_TEAPOT,
reason=b'I\'m a tea pot',
)
return request
1 change: 0 additions & 1 deletion proxy/plugin/filter_by_url_regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ def handle_client_request(
# list
raise HttpRequestRejected(
status_code=httpStatusCodes.NOT_FOUND,
headers={b'Connection': b'close'},
reason=b'Blocked',
)
# stop looping through filter list
Expand Down
4 changes: 2 additions & 2 deletions proxy/plugin/shortlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def handle_client_request(
headers={
b'Location': b'http://' + self.SHORT_LINKS[request.host] + path,
b'Content-Length': b'0',
b'Connection': b'close',
},
conn_close=True,
),
),
)
Expand All @@ -84,8 +84,8 @@ def handle_client_request(
httpStatusCodes.NOT_FOUND, reason=b'NOT FOUND',
headers={
b'Content-Length': b'0',
b'Connection': b'close',
},
conn_close=True,
),
),
)
Expand Down
8 changes: 6 additions & 2 deletions tests/http/exceptions/test_http_request_rejected.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,23 @@ def test_status_code_response(self) -> None:
self.assertEqual(
e.response(self.request), CRLF.join([
b'HTTP/1.1 200 OK',
b'Connection: close',
CRLF,
]),
)

def test_body_response(self) -> None:
e = HttpRequestRejected(
status_code=httpStatusCodes.NOT_FOUND, reason=b'NOT FOUND',
status_code=httpStatusCodes.NOT_FOUND,
reason=b'NOT FOUND',
body=b'Nothing here',
)
self.assertEqual(
e.response(self.request),
build_http_response(
httpStatusCodes.NOT_FOUND,
reason=b'NOT FOUND', body=b'Nothing here',
reason=b'NOT FOUND',
body=b'Nothing here',
conn_close=True,
),
)
4 changes: 1 addition & 3 deletions tests/http/test_http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,7 @@ def test_is_not_http_1_1_keep_alive_with_close_header(self) -> None:
self.parser.parse(
build_http_request(
httpMethods.GET, b'/',
headers={
b'Connection': b'close',
},
conn_close=True,
),
)
self.assertFalse(self.parser.is_http_1_1_keep_alive)
Expand Down
9 changes: 6 additions & 3 deletions tests/http/test_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,13 @@ async def test_pac_file_served_from_disk(self) -> None:
)
self._conn.send.called_once_with(
build_http_response(
200, reason=b'OK', headers={
200,
reason=b'OK',
headers={
b'Content-Type': b'application/x-ns-proxy-autoconfig',
b'Connection': b'close',
}, body=self.expected_response,
},
body=self.expected_response,
conn_close=True,
),
)

Expand Down
6 changes: 2 additions & 4 deletions tests/plugin/test_http_proxy_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,7 @@ async def test_filter_by_upstream_host_plugin(self) -> None:
build_http_response(
status_code=httpStatusCodes.I_AM_A_TEAPOT,
reason=b'I\'m a tea pot',
headers={
b'Connection': b'close',
},
conn_close=True,
),
)

Expand Down Expand Up @@ -376,6 +374,6 @@ async def test_filter_by_url_regex_plugin(self) -> None:
build_http_response(
status_code=httpStatusCodes.NOT_FOUND,
reason=b'Blocked',
headers={b'Connection': b'close'},
conn_close=True,
),
)