pjfanning opened a new pull request, #1229:
URL: https://github.com/apache/pekko-http/pull/1229

   ### Motivation
   `asciiString` (`impl/engine/parsing/package.scala`) built its `String` one 
character at a time:
   
   ```scala
   @tailrec def build(ix: Int = start, sb: JStringBuilder = new 
JStringBuilder(end - start)): String =
     if (ix == end) sb.toString else build(ix + 1, sb.append(input(ix).toChar))
   ```
   
   Two problems. Indexed access into a multi-fragment `ByteString` walks the 
fragment list on every byte, and the loop cannot use the JDK's bulk `String` 
decode. `ByteStringParserInput.sliceString` already does the fast thing for 
exactly the same job - `bytes.slice(start, end).decodeString(ISO_8859_1)` - so 
the two were inconsistent.
   
   It also **sign extended**: `input(ix).toChar` turns byte `0xC3` into 
`U+FFC3`, not `U+00C3`. Three call sites can see bytes above 0x7F:
   
   - the response reason phrase (`HttpResponseParser:99`) - `scanNewLineIdx` 
accepts any byte until CR/LF;
   - chunk extensions (`HttpMessageParser:299`) - `parseChunkExtensions` 
accepts any byte until CR/LF;
   - header names (`HttpHeaderParser:169`) - when 
`illegal-response-header-name-processing-mode` is `warn` or `ignore`.
   
   The two call sites in `scanHeaderValue` cannot: `sb` stays `null` only while 
every character so far is legal 7-bit ASCII (`' ' <= c <= 0x7F`), so the range 
handed to `asciiString` there is always ASCII, where old and new agree exactly.
   
   ### Modification
   ```scala
   private[http] def asciiString(input: ByteString, start: Int, end: Int): 
String =
     if (start == end) "" else input.slice(start, end).decodeString(ISO88591)
   ```
   
   Reusing the existing `ISO88591` constant from `pekko.http.impl.util`. The 
name `asciiString` is kept - callers expect it and most ranges really are ASCII 
- with a scaladoc note about the above-0x7F behaviour.
   
   ### Result
   One bulk decode instead of a per-byte loop, and byte ranges above 0x7F 
decode to the Latin-1 character instead of a sign-extended one.
   
   Measured over 3M extractions of a 4-character header name from a 
request-sized `ByteString`: **234 ms -> 167 ms**.
   
   Note this is on the *uncached* header path - `HttpHeaderParser` reaches 
`asciiString` only when a header is not already in its trie, so for traffic 
with repetitive headers most requests never get here. This is an 
allocation-pressure and tail-latency improvement rather than something that 
moves steady-state throughput.
   
   A pekko-side `decodeString(charset, from, until)` would take this further by 
removing the intermediate slice as well (the same measurement puts that at ~108 
ms), and this becomes a one-line follow-up if and when that lands. The `slice` 
form works on every pekko version in the meantime, including the 1.1.5 that the 
1.4.x branch is pinned to.
   
   ### Tests
   - `sbt "http-core / Test / testOnly 
org.apache.pekko.http.impl.engine.parsing.*"` - 242 passed.
   - New `ResponseParserSpec` case: a response with a non-ASCII header name, 
parsed with `IllegalResponseHeaderNameProcessingMode.Ignore`. It **fails before 
this change**, where the two UTF-8 bytes of the name are parsed as the 
sign-extended characters `U+FFC3 U+FFB6` instead of `U+00C3 U+00B6`.
   - `sbt http-core/mimaReportBinaryIssues` - clean.
   - `scalafmt --mode diff-ref=upstream/main` - clean.
   
   ### Note on an existing test gap
   While writing the test I first tried to assert a non-ASCII *chunk 
extension*, which is the other affected path. That turned out to be untestable 
through these specs: `StrictEqualHttpResponse` compares entities via 
`toStrict`, which collapses a chunked entity to its concatenated data and 
discards the extensions. So the existing "message chunk with and without 
extension" case does not actually assert the extension strings either. Worth a 
separate look, out of scope here.
   
   ### References
   None - found while auditing ByteString usage across the code base
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to