bottom_shelf: cognitive-complexity and dedupe pass over the five hot functions (#545)
* refactor(bottom_shelf): decompose _HttpConnection._processData
Cognitive complexity 162 -> 29. The read loop keeps only flow control;
the per-request work moves into named steps with the same ordering and
the same destroy-vs-flush-then-close behaviour per rejection:
- _rejectMalformedHead / _rejectUnsupportedBody, via a _reject helper
that replaces eleven copies of "socket.add(error); tear down; return"
- _parseRequestUri
- _attachBody, returning the body plus the bytes of the next request
- _startBodyTimer, _buildRequest, _hijack, _handleProcessingError
onPause/onResume closures become _pauseReads/_resumeReads tear-offs.
No test changes; 148 pass.
* refactor(bottom_shelf): flatten _executeRequest and share the error paths
Cognitive complexity: _executeRequest 35 -> 17, _handleAsyncError 19 -> <15,
start 16 -> <15.
- keep-alive tail uses early returns instead of nesting
- five copies of config.onConnectionError?.call(...) -> _reportError
- four copies of socket.close().then(destroy, onError: destroy)
-> _closeThenDestroy; two of `await socket.close(); _destroy()`
-> _closeAndDestroy
- onHeadersSent closure -> _markResponseSent tear-off
- socket listen callbacks become _onSocketError / _onSocketDone
No behaviour change; 148 tests pass.
* refactor(bottom_shelf): move RawHttpParser field completion out of the byte loop
Cognitive complexity of process() 121 -> 35. The per-byte validation stays
inline in the switch; the work done once per field (method, URL, version,
header key, header value, end of headers) moves to _finish* methods, with
_requireCrlf and _startField replacing three and five repeated fragments.
Version parsing becomes a single guard plus a switch.
Same exceptions in the same order; 148 tests pass.
* refactor(bottom_shelf): split ChunkedBodyController.add by state
Cognitive complexity 56 -> below threshold. Each state's byte handling is
its own method (_onSizeByte, _takeChunkData, _expectAfterChunkData,
_onTrailerByte); the loop only dispatches.
Removes _stateExt, which nothing ever entered: a ';' in the size line
throws notImplemented before the state could be reached. The int
_trailerState flag becomes bool _inTrailerLine.
148 tests pass.
* refactor(bottom_shelf): decompose RawShelfResponseSerializer.writeResponse
Cognitive complexity: writeResponse 64 -> below threshold, _writeStreamResponse
54 -> 18, with the header pass now in _addHeaders (27).
- _addHeaders serializes headers and returns (contentLength, isChunked)
- _addHeader / _addSetCookieHeaders / _addColonSpace
- _sendScratch replaces seven copies of "copy scratch, add, onHeadersSent"
- _writeBufferedBody holds the in-memory body cases
- _writeHeadersWithFirstChunk / _writeChunk hold the four coalescing
variants of the streaming path; _chunkSizeLine, _contentLengthMismatch
Byte output is unchanged; 148 tests pass.
* perf(bottom_shelf): mark the extracted byte-loop helpers prefer-inline
The parser and chunked-body refactors moved once-per-field and
once-per-segment work out of their byte loops into small methods.
utils.dart already pins its byte classifiers with
@pragma('vm:prefer-inline') so AOT does not leave a call on the hot
path; do the same for these.
Measured with the bench_press micro suite (jit+aot, 66 cells): with the
pragmas in place every cell that moved is within the run-to-run
variance of two identical baseline runs (which themselves differ by up to
39% on two 256 KB / chunked cells and 3-27% on sixteen others).
* refactor(bottom_shelf): share the buffering base of the two body controllers
FixedLengthBodyController and ChunkedBodyController carried identical
copies of the pre-listen buffering: the StreamController setup with its
onListen replay, takeBufferedData, _addChunk, _close and addError (dedupe:
five clusters, 167 duplicate lines in this file). They move to an
abstract base _BufferingBodyController; each subclass keeps only its
framing state, isDone, add and its own close() semantics.
body_stream.dart 347 -> 287 lines. 148 tests pass.Shelf makes it easy to create and compose web servers and parts of web servers. How?
It was inspired by Connect for NodeJS and Rack for Ruby.
See the package:shelf readme for more information.
| Package | Description | Issues | Version |
|---|---|---|---|
| shelf | A model for web server middleware that encourages composition and easy reuse. | ||
| shelf_packages_handler | A shelf handler for serving a packages/ directory. | ||
| shelf_proxy | A shelf handler for proxying HTTP requests to another server. | ||
| shelf_router | A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations. | ||
| shelf_router_generator | A package:build-compatible builder for generating request routers for the shelf web-framework based on source annotations. | ||
| shelf_static | Static file server support for the shelf package and ecosystem. | ||
| shelf_test_handler | A Shelf handler that makes it easy to test HTTP interactions. | ||
| shelf_web_socket | A shelf handler that wires up a listener for every connection. | ||
| _shelf_compliance | HTTP/1.1 compliance and hardening tests. | (not published) |
For information about our publishing automation and release process, see https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.