Skip to content

Commit ee424ac

Browse files
authored
--max-sendbuf-size flag to speed up large file upload/download (#1060)
* Add `--max-sendbuf-size` flag which now defaults to 64Kb * Use `server_recvbuf_size` flag with base tunnel implementation * isort * Add to readme
1 parent f084342 commit ee424ac

File tree

9 files changed

+30
-11
lines changed

9 files changed

+30
-11
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@
250250
- See `--enable-static-server` and `--static-server-dir` flags
251251

252252
- Optimized for large file uploads and downloads
253-
- See `--client-recvbuf-size` and `--server-recvbuf-size` flag
253+
- See `--client-recvbuf-size`, `--server-recvbuf-size`, `--max-sendbuf-size` flags
254254

255255
- `IPv4` and `IPv6` support
256256
- See `--hostname` flag
@@ -2280,7 +2280,8 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
22802280
[--work-klass WORK_KLASS] [--pid-file PID_FILE]
22812281
[--enable-proxy-protocol] [--enable-conn-pool] [--key-file KEY_FILE]
22822282
[--cert-file CERT_FILE] [--client-recvbuf-size CLIENT_RECVBUF_SIZE]
2283-
[--server-recvbuf-size SERVER_RECVBUF_SIZE] [--timeout TIMEOUT]
2283+
[--server-recvbuf-size SERVER_RECVBUF_SIZE]
2284+
[--max-sendbuf-size MAX_SENDBUF_SIZE] [--timeout TIMEOUT]
22842285
[--disable-http-proxy] [--disable-headers DISABLE_HEADERS]
22852286
[--ca-key-file CA_KEY_FILE] [--ca-cert-dir CA_CERT_DIR]
22862287
[--ca-cert-file CA_CERT_FILE] [--ca-file CA_FILE]
@@ -2391,6 +2392,9 @@ options:
23912392
--server-recvbuf-size SERVER_RECVBUF_SIZE
23922393
Default: 128 KB. Maximum amount of data received from
23932394
the server in a single recv() operation.
2395+
--max-sendbuf-size MAX_SENDBUF_SIZE
2396+
Default: 64 KB. Maximum amount of data to dispatch in
2397+
a single send() operation.
23942398
--timeout TIMEOUT Default: 10.0. Number of seconds after which an
23952399
inactive connection must be dropped. Inactivity is
23962400
defined by no data sent or received by the client.

proxy/common/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def _env_threadless_compliant() -> bool:
7474
# Defaults
7575
DEFAULT_BACKLOG = 100
7676
DEFAULT_BASIC_AUTH = None
77+
DEFAULT_MAX_SEND_SIZE = 64 * 1024
7778
DEFAULT_BUFFER_SIZE = 128 * 1024
7879
DEFAULT_CA_CERT_DIR = None
7980
DEFAULT_CA_CERT_FILE = None
@@ -131,7 +132,6 @@ def _env_threadless_compliant() -> bool:
131132
DEFAULT_VERSION = False
132133
DEFAULT_HTTP_PORT = 80
133134
DEFAULT_HTTPS_PORT = 443
134-
DEFAULT_MAX_SEND_SIZE = 16 * 1024
135135
DEFAULT_WORK_KLASS = 'proxy.http.HttpProtocolHandler'
136136
DEFAULT_ENABLE_PROXY_PROTOCOL = False
137137
# 25 milliseconds to keep the loops hot

proxy/core/base/tcp_server.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
from ...core.connection import TcpClientConnection
2828
from ...common.constants import (
2929
DEFAULT_TIMEOUT, DEFAULT_KEY_FILE, DEFAULT_CERT_FILE,
30-
DEFAULT_CLIENT_RECVBUF_SIZE, DEFAULT_SERVER_RECVBUF_SIZE,
30+
DEFAULT_MAX_SEND_SIZE, DEFAULT_CLIENT_RECVBUF_SIZE,
31+
DEFAULT_SERVER_RECVBUF_SIZE,
3132
)
3233

3334

@@ -68,6 +69,14 @@
6869
'server in a single recv() operation.',
6970
)
7071

72+
flags.add_argument(
73+
'--max-sendbuf-size',
74+
type=int,
75+
default=DEFAULT_MAX_SEND_SIZE,
76+
help='Default: ' + str(int(DEFAULT_MAX_SEND_SIZE / 1024)) +
77+
' KB. Maximum amount of data to dispatch in a single send() operation.',
78+
)
79+
7180
flags.add_argument(
7281
'--timeout',
7382
type=int,
@@ -164,7 +173,7 @@ async def handle_writables(self, writables: Writables) -> bool:
164173
logger.debug(
165174
'Flushing buffer to client {0}'.format(self.work.address),
166175
)
167-
self.work.flush()
176+
self.work.flush(self.flags.max_sendbuf_size)
168177
if self.must_flush_before_shutdown is True and \
169178
not self.work.has_buffer():
170179
teardown = True

proxy/core/base/tcp_tunnel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ async def handle_events(
8989
return do_shutdown
9090
# Handle server events
9191
if self.upstream and self.upstream.connection.fileno() in readables:
92-
data = self.upstream.recv()
92+
data = self.upstream.recv(self.flags.server_recvbuf_size)
9393
if data is None:
9494
# Server closed connection
9595
logger.debug('Connection closed by server')
9696
return True
9797
# tunnel data to client
9898
self.work.queue(data)
9999
if self.upstream and self.upstream.connection.fileno() in writables:
100-
self.upstream.flush()
100+
self.upstream.flush(self.flags.max_sendbuf_size)
101101
return False
102102

103103
def connect_upstream(self) -> None:

proxy/core/base/tcp_upstream.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ async def write_to_descriptors(self, w: Writables) -> bool:
9696
self.upstream.connection.fileno() in w and \
9797
self.upstream.has_buffer():
9898
try:
99+
# TODO: max sendbuf size flag currently not used here
99100
self.upstream.flush()
100101
except ssl.SSLWantWriteError: # pragma: no cover
101102
logger.info('Upstream SSLWantWriteError, will retry')

proxy/core/connection/connection.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,15 @@ def queue(self, mv: memoryview) -> None:
7979
self.buffer.append(mv)
8080
self._num_buffer += 1
8181

82-
def flush(self) -> int:
82+
def flush(self, max_send_size: Optional[int] = None) -> int:
8383
"""Users must handle BrokenPipeError exceptions"""
8484
if not self.has_buffer():
8585
return 0
8686
mv = self.buffer[0].tobytes()
87-
sent: int = self.send(mv[:DEFAULT_MAX_SEND_SIZE])
87+
max_send_size = max_send_size or DEFAULT_MAX_SEND_SIZE
88+
# TODO: Assemble multiple packets if total
89+
# size remains below max send size.
90+
sent: int = self.send(mv[:max_send_size])
8891
if sent == len(mv):
8992
self.buffer.pop(0)
9093
self._num_buffer -= 1

proxy/http/handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def _flush(self) -> None:
396396
] = self.selector.select(timeout=DEFAULT_SELECTOR_SELECT_TIMEOUT)
397397
if len(ev) == 0:
398398
continue
399-
self.work.flush()
399+
self.work.flush(self.flags.max_sendbuf_size)
400400
except BrokenPipeError:
401401
pass
402402
finally:

proxy/http/proxy/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ async def write_to_descriptors(self, w: Writables) -> bool:
188188
self.upstream.connection.fileno() in w:
189189
logger.debug('Server is write ready, flushing...')
190190
try:
191-
self.upstream.flush()
191+
self.upstream.flush(self.flags.max_sendbuf_size)
192192
except ssl.SSLWantWriteError:
193193
logger.warning(
194194
'SSLWantWriteError while trying to flush to server, will retry',

proxy/http/websocket/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def run_once(self) -> bool:
9494
self.selector.unregister(self.sock)
9595
for _, mask in events:
9696
if mask & selectors.EVENT_READ and self.on_message:
97+
# TODO: client recvbuf size flag currently not used here
9798
raw = self.recv()
9899
if raw is None or raw.tobytes() == b'':
99100
self.closed = True
@@ -104,6 +105,7 @@ def run_once(self) -> bool:
104105
frame.parse(raw.tobytes())
105106
self.on_message(frame)
106107
elif mask & selectors.EVENT_WRITE:
108+
# TODO: max sendbuf size flag currently not used here
107109
self.flush()
108110
return False
109111

0 commit comments

Comments
 (0)