Skip to content

Commit d616fc8

Browse files
[CacheResponsesPlugin] Add ability to cache request packets (#1056)
* Start of post encryption tests * Assertion on post encryption callback * Add `--cache-requests` flag * Clean up `on_client_data` API as we no longer have a chain within core http protocol handler * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix flake8 warnings * Fix `inconsistent-return-statements` Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent ac183f5 commit d616fc8

File tree

10 files changed

+170
-74
lines changed

10 files changed

+170
-74
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2284,7 +2284,7 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
22842284
[--ca-cert-file CA_CERT_FILE] [--ca-file CA_FILE]
22852285
[--ca-signing-key-file CA_SIGNING_KEY_FILE]
22862286
[--auth-plugin AUTH_PLUGIN] [--cache-dir CACHE_DIR]
2287-
[--proxy-pool PROXY_POOL] [--enable-web-server]
2287+
[--cache-requests] [--proxy-pool PROXY_POOL] [--enable-web-server]
22882288
[--enable-static-server] [--static-server-dir STATIC_SERVER_DIR]
22892289
[--min-compression-length MIN_COMPRESSION_LENGTH]
22902290
[--enable-reverse-proxy] [--pac-file PAC_FILE]
@@ -2425,6 +2425,7 @@ options:
24252425
Default: /Users/abhinavsingh/.proxy/cache. Flag only
24262426
applicable when cache plugin is used with on-disk
24272427
storage.
2428+
--cache-requests Default: False. Whether to also cache request packets.
24282429
--proxy-pool PROXY_POOL
24292430
List of upstream proxies to use in the pool
24302431
--enable-web-server Default: False. Whether to enable

proxy/common/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def _env_threadless_compliant() -> bool:
148148
DEFAULT_CACHE_DIRECTORY_PATH = os.path.join(
149149
DEFAULT_DATA_DIRECTORY_PATH, 'cache',
150150
)
151+
DEFAULT_CACHE_REQUESTS = False
151152

152153
# Cor plugins enabled by default or via flags
153154
DEFAULT_ABC_PLUGINS = [

proxy/http/handler.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,18 @@ def handle_data(self, data: memoryview) -> Optional[bool]:
163163
self.work.closed = True
164164
return True
165165
try:
166-
# Don't parse incoming data any further after 1st request has completed.
167-
#
168-
# This specially does happen for pipeline requests.
166+
# We don't parse incoming data any further after 1st HTTP request packet.
169167
#
170168
# Plugins can utilize on_client_data for such cases and
171169
# apply custom logic to handle request data sent after 1st
172170
# valid request.
173171
if self.request.state != httpParserStates.COMPLETE:
174172
if self._parse_first_request(data):
175173
return True
176-
else:
177-
# HttpProtocolHandlerPlugin.on_client_data
178-
# Can raise HttpProtocolException to tear down the connection
179-
if self.plugin:
180-
data = self.plugin.on_client_data(data) or data
174+
# HttpProtocolHandlerPlugin.on_client_data
175+
# Can raise HttpProtocolException to tear down the connection
176+
elif self.plugin:
177+
self.plugin.on_client_data(data)
181178
except HttpProtocolException as e:
182179
logger.info('HttpProtocolException: %s' % e)
183180
response: Optional[memoryview] = e.response(self.request)

proxy/http/plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ def protocols() -> List[int]:
7171
raise NotImplementedError()
7272

7373
@abstractmethod
74-
def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
74+
def on_client_data(self, raw: memoryview) -> None:
7575
"""Called only after original request has been completely received."""
76-
return raw # pragma: no cover
76+
pass # pragma: no cover
7777

7878
@abstractmethod
7979
def on_request_complete(self) -> Union[socket.socket, bool]:

proxy/http/proxy/plugin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ def handle_client_request(
121121
Return None to drop the request data, e.g. in case a response has already been queued.
122122
Raise HttpRequestRejected or HttpProtocolException directly to
123123
tear down the connection with client.
124-
125124
"""
126125
return request # pragma: no cover
127126

proxy/http/proxy/server.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ def on_response_chunk(self, chunk: List[memoryview]) -> List[memoryview]:
398398
return chunk
399399

400400
# Can return None to tear down connection
401-
def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
401+
def on_client_data(self, raw: memoryview) -> None:
402402
# For scenarios when an upstream connection was never established,
403403
# let plugin do whatever they wish to. These are special scenarios
404404
# where plugins are trying to do something magical. Within the core
@@ -413,7 +413,7 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
413413
for plugin in self.plugins.values():
414414
o = plugin.handle_client_data(raw)
415415
if o is None:
416-
return None
416+
return
417417
raw = o
418418
elif self.upstream and not self.upstream.closed:
419419
# For http proxy requests, handle pipeline case.
@@ -429,12 +429,17 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
429429
# upgrade request. Incoming client data now
430430
# must be treated as WebSocket protocol packets.
431431
self.upstream.queue(raw)
432-
return None
432+
return
433433

434434
if self.pipeline_request is None:
435435
# For pipeline requests, we never
436436
# want to use --enable-proxy-protocol flag
437437
# as proxy protocol header will not be present
438+
#
439+
# TODO: HTTP parser must be smart about detecting
440+
# HA proxy protocol or we must always explicitly pass
441+
# the flag when we are expecting HA proxy protocol
442+
# request line before HTTP request lines.
438443
self.pipeline_request = HttpParser(
439444
httpParserTypes.REQUEST_PARSER,
440445
)
@@ -447,7 +452,7 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
447452
assert self.pipeline_request is not None
448453
r = plugin.handle_client_request(self.pipeline_request)
449454
if r is None:
450-
return None
455+
return
451456
self.pipeline_request = r
452457
assert self.pipeline_request is not None
453458
# TODO(abhinavsingh): Remove memoryview wrapping here after
@@ -463,8 +468,6 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
463468
# simply queue for upstream server.
464469
else:
465470
self.upstream.queue(raw)
466-
return None
467-
return raw
468471

469472
def on_request_complete(self) -> Union[socket.socket, bool]:
470473
self.emit_request_complete()

proxy/http/server/web.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ async def read_from_descriptors(self, r: Readables) -> bool:
194194
return True
195195
return False
196196

197-
def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
197+
def on_client_data(self, raw: memoryview) -> None:
198198
if self.switched_protocol == httpProtocolTypes.WEBSOCKET:
199199
# TODO(abhinavsingh): Remove .tobytes after websocket frame parser
200200
# is memoryview compliant
@@ -211,7 +211,7 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
211211
assert self.route
212212
self.route.on_websocket_message(frame)
213213
frame.reset()
214-
return None
214+
return
215215
# If 1st valid request was completed and it's a HTTP/1.1 keep-alive
216216
# And only if we have a route, parse pipeline requests
217217
if self.request.is_complete and \
@@ -231,7 +231,6 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
231231
'Pipelined request is not keep-alive, will tear down request...',
232232
)
233233
self.pipeline_request = None
234-
return raw
235234

236235
def on_response_chunk(self, chunk: List[memoryview]) -> List[memoryview]:
237236
return chunk

proxy/plugin/cache/cache_responses.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
3030
self.flags.cache_dir,
3131
'responses',
3232
),
33+
cache_requests=self.flags.cache_requests,
3334
)
3435
self.set_store(self.disk_store)
3536

proxy/plugin/cache/store/disk.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from ....common.flag import flags
1717
from ....http.parser import HttpParser
1818
from ....common.utils import text_
19-
from ....common.constants import DEFAULT_CACHE_DIRECTORY_PATH
19+
from ....common.constants import (
20+
DEFAULT_CACHE_REQUESTS, DEFAULT_CACHE_DIRECTORY_PATH,
21+
)
2022

2123

2224
logger = logging.getLogger(__name__)
@@ -30,12 +32,21 @@
3032
'Flag only applicable when cache plugin is used with on-disk storage.',
3133
)
3234

35+
flags.add_argument(
36+
'--cache-requests',
37+
action='store_true',
38+
default=DEFAULT_CACHE_REQUESTS,
39+
help='Default: False. ' +
40+
'Whether to also cache request packets.',
41+
)
42+
3343

3444
class OnDiskCacheStore(CacheStore):
3545

36-
def __init__(self, uid: str, cache_dir: str) -> None:
46+
def __init__(self, uid: str, cache_dir: str, cache_requests: bool) -> None:
3747
super().__init__(uid)
3848
self.cache_dir = cache_dir
49+
self.cache_requests = cache_requests
3950
self.cache_file_path: Optional[str] = None
4051
self.cache_file: Optional[BinaryIO] = None
4152

@@ -47,6 +58,8 @@ def open(self, request: HttpParser) -> None:
4758
self.cache_file = open(self.cache_file_path, "wb")
4859

4960
def cache_request(self, request: HttpParser) -> Optional[HttpParser]:
61+
if self.cache_file and self.cache_requests:
62+
self.cache_file.write(request.build())
5063
return request
5164

5265
def cache_response_chunk(self, chunk: memoryview) -> memoryview:

0 commit comments

Comments
 (0)