This is an automated email from the ASF dual-hosted git repository.
fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-http.git
The following commit(s) were added to refs/heads/main by this push:
new 1f8fbc15e use byteAt instead of byteChar where it is easy to avoid the
char conversion (#801)
1f8fbc15e is described below
commit 1f8fbc15ecec524bf1433b52d5399900c56127c1
Author: PJ Fanning <[email protected]>
AuthorDate: Sat Sep 27 13:00:35 2025 +0100
use byteAt instead of byteChar where it is easy to avoid the char
conversion (#801)
* more performant boundary check
* try to fix tests
* Update BodyPartParser.scala
* Update BodyPartParser.scala
* Update BodyPartParser.scala
* Update BodyPartParser.scala
* add HttpConstants
* use ByteAt to avoid some char conversions
more changes
more changes
* add dash constant
---
.../http/impl/engine/parsing/BodyPartParser.scala | 4 +--
.../impl/engine/parsing/HttpHeaderParser.scala | 13 +++++----
.../impl/engine/parsing/HttpMessageParser.scala | 32 ++++++++++++----------
.../impl/engine/parsing/HttpRequestParser.scala | 19 +++++++------
.../impl/engine/parsing/HttpResponseParser.scala | 12 ++++----
.../parsing/SpecializedHeaderValueParsers.scala | 3 +-
.../http/impl/model/parser/CharacterClasses.scala | 10 +++----
.../pekko/http/impl/util/HttpConstants.scala | 2 ++
.../remove-lineparser-constants.excludes | 20 ++++++++++++++
.../scaladsl/unmarshalling/sse/LineParser.scala | 17 ++++--------
10 files changed, 77 insertions(+), 55 deletions(-)
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
index 1ca1f6d54..367ab2c27 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
@@ -18,6 +18,7 @@ import pekko.NotUsed
import pekko.annotation.InternalApi
import pekko.event.LoggingAdapter
import pekko.http.impl.util._
+import pekko.http.impl.util.HttpConstants._
import pekko.http.scaladsl.model._
import pekko.http.scaladsl.model.headers._
import pekko.stream.{ Attributes, FlowShape, Inlet, Outlet }
@@ -281,7 +282,7 @@ private[http] final class BodyPartParser(
def done(): StateResult = null // StateResult is a phantom type
def doubleDash(input: ByteString, offset: Int): Boolean =
- byteChar(input, offset) == '-' && byteChar(input, offset + 1) == '-'
+ byteAt(input, offset) == DASH_BYTE && byteAt(input, offset + 1) ==
DASH_BYTE
}
}
@@ -354,7 +355,6 @@ private[http] object BodyPartParser {
}
case class UndefinedEndOfLineConfiguration(boundary: String) extends
EndOfLineConfiguration {
- import HttpConstants._
override def eol: String = "\r\n"
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpHeaderParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpHeaderParser.scala
index 8e00dd545..c7bdbda34 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpHeaderParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpHeaderParser.scala
@@ -20,21 +20,22 @@ import java.lang.{ StringBuilder => JStringBuilder }
import org.apache.pekko
import pekko.annotation.InternalApi
import pekko.event.LoggingAdapter
+import pekko.http.scaladsl.settings.ParserSettings
import pekko.http.scaladsl.settings.ParserSettings.{
+ ErrorLoggingVerbosity,
IllegalResponseHeaderNameProcessingMode,
IllegalResponseHeaderValueProcessingMode
}
-import pekko.http.scaladsl.settings.ParserSettings.ErrorLoggingVerbosity
-import pekko.http.scaladsl.settings.ParserSettings
-
-import scala.annotation.tailrec
-import pekko.util.ByteString
import pekko.http.ccompat._
import pekko.http.impl.util._
+import pekko.http.impl.util.HttpConstants._
import pekko.http.scaladsl.model.{ ErrorInfo, HttpHeader, MediaTypes,
StatusCode, StatusCodes }
import pekko.http.scaladsl.model.headers.{ EmptyHeader, RawHeader }
import pekko.http.impl.model.parser.HeaderParser
import pekko.http.impl.model.parser.CharacterClasses._
+import pekko.util.ByteString
+
+import scala.annotation.tailrec
/**
* INTERNAL API
@@ -600,7 +601,7 @@ private[http] object HttpHeaderParser {
if (ix < limit)
byteChar(input, ix) match {
case '\t' => scanHeaderValue(hhp, input, start, limit, log,
mode)(appended(' '), ix + 1)
- case '\r' if byteChar(input, ix + 1) == '\n' =>
+ case '\r' if byteAt(input, ix + 1) == LF_BYTE =>
if (WSP(byteChar(input, ix + 2))) scanHeaderValue(hhp, input, start,
limit, log, mode)(appended(' '), ix + 3)
else (if (sb != null) sb.toString else asciiString(input, start,
ix), ix + 2)
case '\n' =>
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
index fbf4b29aa..d295d9fcd 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
@@ -15,21 +15,23 @@ package org.apache.pekko.http.impl.engine.parsing
import javax.net.ssl.SSLSession
-import org.apache.pekko
-import pekko.stream.TLSProtocol._
-
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
+
import org.parboiled2.CharUtils
-import pekko.util.ByteString
+
+import org.apache.pekko
+import pekko.annotation.InternalApi
import pekko.http.impl.model.parser.CharacterClasses
-import pekko.http.scaladsl.settings.ParserSettings
+import pekko.http.impl.util.HttpConstants._
import pekko.http.scaladsl.model.{ ParsingException => _, _ }
import pekko.http.scaladsl.model.headers._
import HttpProtocols._
import ParserOutput._
-import pekko.annotation.InternalApi
+import pekko.http.scaladsl.settings.ParserSettings
import
pekko.http.scaladsl.settings.ParserSettings.ConflictingContentTypeHeaderProcessingMode
+import pekko.stream.TLSProtocol._
+import pekko.util.ByteString
/**
* INTERNAL API
@@ -288,20 +290,20 @@ private[http] trait HttpMessageParser[Output >:
MessageOutput <: ParserOutput] {
emit(EntityChunk(HttpEntity.Chunk(input.slice(cursor,
chunkBodyEnd).compact, extension)))
Trampoline(_ => parseChunk(input, chunkBodyEnd + terminatorLen,
isLastMessage, totalBytesRead + chunkSize))
}
- byteChar(input, chunkBodyEnd) match {
- case '\r' if byteChar(input, chunkBodyEnd + 1) == '\n' => result(2)
- case '\n' => result(1)
- case x =>
failEntityStream("Illegal chunk termination")
+ byteAt(input, chunkBodyEnd) match {
+ case CR_BYTE if byteAt(input, chunkBodyEnd + 1) == LF_BYTE =>
result(2)
+ case LF_BYTE =>
result(1)
+ case x =>
failEntityStream("Illegal chunk termination")
}
} else parseTrailer(extension, cursor)
@tailrec def parseChunkExtensions(chunkSize: Int, cursor: Int)(startIx:
Int = cursor): StateResult =
if (cursor - startIx <= settings.maxChunkExtLength) {
def extension = asciiString(input, startIx, cursor)
- byteChar(input, cursor) match {
- case '\r' if byteChar(input, cursor + 1) == '\n' =>
parseChunkBody(chunkSize, extension, cursor + 2)
- case '\n' =>
parseChunkBody(chunkSize, extension, cursor + 1)
- case _ =>
parseChunkExtensions(chunkSize, cursor + 1)(startIx)
+ byteAt(input, cursor) match {
+ case CR_BYTE if byteAt(input, cursor + 1) == LF_BYTE =>
parseChunkBody(chunkSize, extension, cursor + 2)
+ case LF_BYTE =>
parseChunkBody(chunkSize, extension, cursor + 1)
+ case _ =>
parseChunkExtensions(chunkSize, cursor + 1)(startIx)
}
} else failEntityStream(
s"HTTP chunk extension length exceeds configured limit of
${settings.maxChunkExtLength} characters")
@@ -314,7 +316,7 @@ private[http] trait HttpMessageParser[Output >:
MessageOutput <: ParserOutput] {
failEntityStream(
s"HTTP chunk of $size bytes exceeds the configured limit of
${settings.maxChunkSize} bytes")
case ';' if cursor > offset => parseChunkExtensions(size.toInt,
cursor + 1)()
- case '\r' if cursor > offset && byteChar(input, cursor + 1) == '\n'
=>
+ case '\r' if cursor > offset && byteAt(input, cursor + 1) == LF_BYTE
=>
parseChunkBody(size.toInt, "", cursor + 2)
case '\n' if cursor > offset => parseChunkBody(size.toInt, "",
cursor + 1)
case c if CharacterClasses.WSP(c) => parseSize(cursor + 1, size) //
illegal according to the spec but can happen, see issue #1812
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpRequestParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpRequestParser.scala
index 3fa09c1ca..40951e4d4 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpRequestParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpRequestParser.scala
@@ -19,21 +19,22 @@ import javax.net.ssl.SSLSession
import scala.annotation.{ switch, tailrec }
import org.apache.pekko
-import pekko.http.scaladsl.settings.{ ParserSettings, WebSocketSettings }
-import pekko.util.ByteString
-import pekko.util.OptionVal
+import pekko.annotation.InternalApi
+import pekko.http.impl.engine.server.HttpAttributes
+import pekko.http.impl.util.ByteStringParserInput
+import pekko.http.impl.util.HttpConstants._
import pekko.http.impl.engine.ws.Handshake
import pekko.http.impl.model.parser.{ CharacterClasses, UriParser }
import pekko.http.scaladsl.model.{ ParsingException => _, _ }
import pekko.http.scaladsl.model.headers._
import pekko.http.scaladsl.model.StatusCodes._
+import pekko.http.scaladsl.settings.{ ParserSettings, WebSocketSettings }
import ParserOutput._
-import pekko.annotation.InternalApi
-import pekko.http.impl.engine.server.HttpAttributes
-import pekko.http.impl.util.ByteStringParserInput
import pekko.stream.{ Attributes, FlowShape, Inlet, Outlet }
import pekko.stream.TLSProtocol.SessionBytes
import pekko.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler
}
+import pekko.util.ByteString
+import pekko.util.OptionVal
import org.parboiled2.ParserInput
/**
@@ -91,9 +92,9 @@ private[http] final class HttpRequestParser(
var cursor = parseMethod(input, offset)
cursor = parseRequestTarget(input, cursor)
cursor = parseProtocol(input, cursor)
- if (byteChar(input, cursor) == '\r' && byteChar(input, cursor + 1)
== '\n')
+ if (byteAt(input, cursor) == CR_BYTE && byteAt(input, cursor + 1) ==
LF_BYTE)
parseHeaderLines(input, cursor + 2)
- else if (byteChar(input, cursor) == '\n')
+ else if (byteAt(input, cursor) == LF_BYTE)
parseHeaderLines(input, cursor + 1)
else onBadProtocol(input.drop(cursor))
} else
@@ -125,7 +126,7 @@ private[http] final class HttpRequestParser(
@tailrec def parseMethod(meth: HttpMethod, ix: Int = 1): Int =
if (ix == meth.value.length)
- if (byteChar(input, cursor + ix) == ' ') {
+ if (byteAt(input, cursor + ix) == SPACE_BYTE) {
method = meth
cursor + ix + 1
} else parseCustomMethod()
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpResponseParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpResponseParser.scala
index 95a893432..b022dcc4f 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpResponseParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpResponseParser.scala
@@ -17,14 +17,16 @@ import javax.net.ssl.SSLSession
import scala.annotation.tailrec
import scala.concurrent.Promise
import scala.util.control.{ NoStackTrace, NonFatal }
+
import org.apache.pekko
+import pekko.annotation.InternalApi
import pekko.http.scaladsl.settings.ParserSettings
import pekko.http.impl.model.parser.CharacterClasses
+import pekko.http.impl.util.HttpConstants._
import pekko.util.ByteString
import pekko.http.scaladsl.model.{ ParsingException => _, _ }
import pekko.http.scaladsl.model.headers._
import ParserOutput._
-import pekko.annotation.InternalApi
import pekko.http.impl.util.LogByteStringTools
import pekko.stream.scaladsl.Source
@@ -61,7 +63,7 @@ private[http] class HttpResponseParser(protected val
settings: ParserSettings,
override protected def parseMessage(input: ByteString, offset: Int):
StateResult =
if (contextForCurrentResponse.isDefined) {
var cursor = parseProtocol(input, offset)
- if (byteChar(input, cursor) == ' ') {
+ if (byteAt(input, cursor) == SPACE_BYTE) {
cursor = parseStatus(input, cursor + 1)
parseHeaderLines(input, cursor)
} else onBadProtocol(input.drop(cursor))
@@ -104,8 +106,8 @@ private[http] class HttpResponseParser(protected val
settings: ParserSettings,
}
}
- def isLF(idx: Int) = byteChar(input, idx) == '\n'
- def isCRLF(idx: Int) = byteChar(input, idx) == '\r' && isLF(idx + 1)
+ def isLF(idx: Int) = byteAt(input, idx) == LF_BYTE
+ def isCRLF(idx: Int) = byteAt(input, idx) == CR_BYTE && isLF(idx + 1)
def isNewLine(idx: Int) = isLF(idx) || isCRLF(idx)
def skipNewLine(idx: Int) = {
@@ -114,7 +116,7 @@ private[http] class HttpResponseParser(protected val
settings: ParserSettings,
else idx
}
- if (byteChar(input, cursor + 3) == ' ') {
+ if (byteAt(input, cursor + 3) == SPACE_BYTE) {
val startIdx = cursor + 4
@tailrec def scanNewLineIdx(idx: Int): Int =
if (idx - startIdx <= maxResponseReasonLength)
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/SpecializedHeaderValueParsers.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/SpecializedHeaderValueParsers.scala
index 2cde6dd8a..3b2977217 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/SpecializedHeaderValueParsers.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/SpecializedHeaderValueParsers.scala
@@ -19,6 +19,7 @@ import pekko.annotation.InternalApi
import scala.annotation.tailrec
import pekko.util.ByteString
import pekko.http.impl.model.parser.CharacterClasses._
+import pekko.http.impl.util.HttpConstants._
import pekko.http.scaladsl.model.{ ErrorInfo, HttpHeader }
import pekko.http.scaladsl.model.headers.`Content-Length`
@@ -39,7 +40,7 @@ private[parsing] object SpecializedHeaderValueParsers {
if (result < 0) fail("`Content-Length` header value must not exceed
63-bit integer range")
else if (DIGIT(c)) recurse(ix + 1, result * 10 + c - '0')
else if (WSP(c)) recurse(ix + 1, result)
- else if (c == '\r' && byteChar(input, ix + 1) == '\n')
(`Content-Length`(result), ix + 2)
+ else if (c == '\r' && byteAt(input, ix + 1) == LF_BYTE)
(`Content-Length`(result), ix + 2)
else if (c == '\n') (`Content-Length`(result), ix + 1)
else fail("Illegal `Content-Length` header value")
}
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/model/parser/CharacterClasses.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/model/parser/CharacterClasses.scala
index 35bbde9ae..f7f1ca542 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/model/parser/CharacterClasses.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/model/parser/CharacterClasses.scala
@@ -23,15 +23,15 @@ private[http] object CharacterClasses {
def ALPHA = CharPredicate.Alpha
def LOWER_ALPHA = CharPredicate.LowerAlpha
def UPPER_ALPHA = CharPredicate.UpperAlpha
- def CR = '\r'
+ final val CR = '\r'
val CTL = CharPredicate('\u0000' to '\u001F', '\u007F')
def DIGIT = CharPredicate.Digit
def ALPHANUM = CharPredicate.AlphaNum
- def DQUOTE = '"'
+ final val DQUOTE = '"'
def HEXDIG = CharPredicate.HexDigit
- def HTAB = '\t'
- def LF = '\n'
- def SP = ' '
+ final val HTAB = '\t'
+ final val LF = '\n'
+ final val SP = ' '
def VCHAR = CharPredicate.Visible
val WSP = CharPredicate(SP, HTAB)
val WSPCRLF = WSP ++ CR ++ LF
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/util/HttpConstants.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/util/HttpConstants.scala
index 05315f088..0252474fd 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/util/HttpConstants.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/util/HttpConstants.scala
@@ -29,4 +29,6 @@ import org.apache.pekko.annotation.InternalApi
private[http] object HttpConstants {
final val CR_BYTE: Byte = 13
final val LF_BYTE: Byte = 10
+ final val SPACE_BYTE: Byte = 32
+ final val DASH_BYTE: Byte = 45 // '-' (minus, dash, hyphen)
}
diff --git
a/http/src/main/mima-filters/2.0.x.backwards.excludes/remove-lineparser-constants.excludes
b/http/src/main/mima-filters/2.0.x.backwards.excludes/remove-lineparser-constants.excludes
new file mode 100644
index 000000000..75b36c842
--- /dev/null
+++
b/http/src/main/mima-filters/2.0.x.backwards.excludes/remove-lineparser-constants.excludes
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# remove LineParser constants (moved to HttpConstants)
+ProblemFilters.exclude[DirectMissingMethodProblem]("org.apache.pekko.http.scaladsl.unmarshalling.sse.LineParser.CR")
+ProblemFilters.exclude[DirectMissingMethodProblem]("org.apache.pekko.http.scaladsl.unmarshalling.sse.LineParser.LF")
diff --git
a/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/sse/LineParser.scala
b/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/sse/LineParser.scala
index 604d69043..e339c0ebc 100644
---
a/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/sse/LineParser.scala
+++
b/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/sse/LineParser.scala
@@ -21,18 +21,12 @@ import scala.annotation.tailrec
import org.apache.pekko
import pekko.annotation.InternalApi
import pekko.event.Logging
+import pekko.http.impl.util.HttpConstants._
import pekko.http.scaladsl.settings.OversizedSseStrategy
import pekko.stream.{ Attributes, FlowShape, Inlet, Outlet }
import pekko.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler
}
import pekko.util.ByteString
-/** INTERNAL API */
-@InternalApi
-private object LineParser {
- val CR = '\r'.toByte
- val LF = '\n'.toByte
-}
-
/**
* A wrapper for an SSE line which exceeds the configured limit. Used for
pattern matching.
* @param line The oversized contents of the SSE line being parsed.
@@ -51,7 +45,6 @@ private final class LineParser(maxLineSize: Int,
override def createLogic(attributes: Attributes) =
new GraphStageLogic(shape) with InHandler with OutHandler {
- import LineParser._
import shape._
private var buffer = ByteString.empty
@@ -91,7 +84,7 @@ private final class LineParser(maxLineSize: Int,
(bs.drop(from), parsedLines, lastCharWasCr)
else
bs(at) match {
- case CR if at < bs.length - 1 && bs(at + 1) == LF =>
+ case CR_BYTE if at < bs.length - 1 && bs(at + 1) == LF_BYTE =>
// Lookahead for LF after CR
val lineByteSize = at - from
val line = bs.slice(from, at).utf8String
@@ -102,7 +95,7 @@ private final class LineParser(maxLineSize: Int,
}
val newParsedLines =
processedLine.fold(parsedLines)(parsedLines :+ _)
parseLines(bs, at + 2, at + 2, newParsedLines, lastCharWasCr =
false)
- case CR =>
+ case CR_BYTE =>
// if is a CR but we don't know the next character, slice it
but flag that the last character was a CR so if the next happens to be a LF we
just ignore
val lineByteSize = at - from
val line = bs.slice(from, at).utf8String
@@ -113,10 +106,10 @@ private final class LineParser(maxLineSize: Int,
}
val newParsedLines =
processedLine.fold(parsedLines)(parsedLines :+ _)
parseLines(bs, at + 1, at + 1, newParsedLines, lastCharWasCr =
true)
- case LF if lastCharWasCr =>
+ case LF_BYTE if lastCharWasCr =>
// if is a LF and we just sliced a CR then we simply advance
parseLines(bs, at + 1, at + 1, parsedLines, lastCharWasCr =
false)
- case LF =>
+ case LF_BYTE =>
// a LF that wasn't preceded by a CR means we found a new slice
val lineByteSize = at - from
val line = bs.slice(from, at).utf8String
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]