=htc #16807 introduce akka.http.parsing.error-logging-verbosity setting
This commit is contained in:
parent
37aa2cb886
commit
14399f71f5
5 changed files with 58 additions and 30 deletions
|
|
@ -147,8 +147,19 @@ akka.http {
|
||||||
# under the HTTP specification even without this header.
|
# under the HTTP specification even without this header.
|
||||||
# If a header cannot be parsed into a high-level model instance it will be
|
# If a header cannot be parsed into a high-level model instance it will be
|
||||||
# provided as a `RawHeader`.
|
# provided as a `RawHeader`.
|
||||||
|
# If logging is enabled it is performed with the configured
|
||||||
|
# `error-logging-verbosity`.
|
||||||
illegal-header-warnings = on
|
illegal-header-warnings = on
|
||||||
|
|
||||||
|
# Configures the verbosity with which message (request or response) parsing
|
||||||
|
# errors are written to the application log.
|
||||||
|
#
|
||||||
|
# Supported settings:
|
||||||
|
# `off` : no log messages are produced
|
||||||
|
# `simple`: a condensed single-line message is logged
|
||||||
|
# `full` : the full error details (potentially spanning several lines) are logged
|
||||||
|
error-logging-verbosity = full
|
||||||
|
|
||||||
# limits for the number of different values per header type that the
|
# limits for the number of different values per header type that the
|
||||||
# header cache will hold
|
# header cache will hold
|
||||||
header-cache {
|
header-cache {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import akka.stream.scaladsl._
|
||||||
import akka.stream.scaladsl.OperationAttributes._
|
import akka.stream.scaladsl.OperationAttributes._
|
||||||
import akka.http.model.{ IllegalResponseException, HttpMethod, HttpRequest, HttpResponse }
|
import akka.http.model.{ IllegalResponseException, HttpMethod, HttpRequest, HttpResponse }
|
||||||
import akka.http.engine.rendering.{ RequestRenderingContext, HttpRequestRendererFactory }
|
import akka.http.engine.rendering.{ RequestRenderingContext, HttpRequestRendererFactory }
|
||||||
import akka.http.engine.parsing.{ ParserOutput, HttpHeaderParser, HttpResponseParser }
|
import akka.http.engine.parsing._
|
||||||
import akka.http.util._
|
import akka.http.util._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,10 +58,9 @@ private[http] object HttpClient {
|
||||||
|
|
||||||
// the initial header parser we initially use for every connection,
|
// the initial header parser we initially use for every connection,
|
||||||
// will not be mutated, all "shared copy" parsers copy on first-write into the header cache
|
// will not be mutated, all "shared copy" parsers copy on first-write into the header cache
|
||||||
val rootParser = new HttpResponseParser(
|
val rootParser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings) { info ⇒
|
||||||
parserSettings,
|
if (parserSettings.illegalHeaderWarnings)
|
||||||
HttpHeaderParser(parserSettings) { errorInfo ⇒
|
logParsingError(info withSummaryPrepended "Illegal response header", log, parserSettings.errorLoggingVerbosity)
|
||||||
if (parserSettings.illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal response header").formatPretty)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
val requestRendererFactory = new HttpRequestRendererFactory(userAgentHeader, requestHeaderSizeHint, log)
|
val requestRendererFactory = new HttpRequestRendererFactory(userAgentHeader, requestHeaderSizeHint, log)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package akka.http.engine.parsing
|
package akka.http.engine.parsing
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import akka.http.model.{ StatusCode, HttpMethod, Uri }
|
import akka.http.model.{ StatusCode, HttpMethod, Uri }
|
||||||
|
|
@ -21,6 +22,7 @@ final case class ParserSettings(
|
||||||
maxChunkSize: Int,
|
maxChunkSize: Int,
|
||||||
uriParsingMode: Uri.ParsingMode,
|
uriParsingMode: Uri.ParsingMode,
|
||||||
illegalHeaderWarnings: Boolean,
|
illegalHeaderWarnings: Boolean,
|
||||||
|
errorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity,
|
||||||
headerValueCacheLimits: Map[String, Int],
|
headerValueCacheLimits: Map[String, Int],
|
||||||
customMethods: String ⇒ Option[HttpMethod],
|
customMethods: String ⇒ Option[HttpMethod],
|
||||||
customStatusCodes: Int ⇒ Option[StatusCode]) extends HttpHeaderParser.Settings {
|
customStatusCodes: Int ⇒ Option[StatusCode]) extends HttpHeaderParser.Settings {
|
||||||
|
|
@ -66,9 +68,25 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsi
|
||||||
c getIntBytes "max-chunk-size",
|
c getIntBytes "max-chunk-size",
|
||||||
Uri.ParsingMode(c getString "uri-parsing-mode"),
|
Uri.ParsingMode(c getString "uri-parsing-mode"),
|
||||||
c getBoolean "illegal-header-warnings",
|
c getBoolean "illegal-header-warnings",
|
||||||
|
ErrorLoggingVerbosity(c getString "error-logging-verbosity"),
|
||||||
cacheConfig.entrySet.asScala.map(kvp ⇒ kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut),
|
cacheConfig.entrySet.asScala.map(kvp ⇒ kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut),
|
||||||
_ ⇒ None,
|
_ ⇒ None,
|
||||||
_ ⇒ None)
|
_ ⇒ None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed trait ErrorLoggingVerbosity
|
||||||
|
object ErrorLoggingVerbosity {
|
||||||
|
case object Off extends ErrorLoggingVerbosity
|
||||||
|
case object Simple extends ErrorLoggingVerbosity
|
||||||
|
case object Full extends ErrorLoggingVerbosity
|
||||||
|
|
||||||
|
def apply(string: String): ErrorLoggingVerbosity =
|
||||||
|
string.toLowerCase(Locale.ROOT) match {
|
||||||
|
case "off" ⇒ Off
|
||||||
|
case "simple" ⇒ Simple
|
||||||
|
case "full" ⇒ Full
|
||||||
|
case x ⇒ throw new IllegalArgumentException(s"[$x] is not a legal `error-logging-verbosity` setting")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,16 @@ package akka.http.engine
|
||||||
|
|
||||||
import java.lang.{ StringBuilder ⇒ JStringBuilder }
|
import java.lang.{ StringBuilder ⇒ JStringBuilder }
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
import akka.event.LoggingAdapter
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
import akka.http.model.{ ErrorInfo, StatusCode, StatusCodes }
|
import akka.http.model.{ ErrorInfo, StatusCode, StatusCodes }
|
||||||
import akka.http.util.SingletonException
|
import akka.http.util.SingletonException
|
||||||
|
|
||||||
package object parsing {
|
/**
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
|
package object parsing {
|
||||||
|
|
||||||
private[http] def escape(c: Char): String = c match {
|
private[http] def escape(c: Char): String = c match {
|
||||||
case '\t' ⇒ "\\t"
|
case '\t' ⇒ "\\t"
|
||||||
case '\r' ⇒ "\\r"
|
case '\r' ⇒ "\\r"
|
||||||
|
|
@ -23,25 +24,24 @@ package object parsing {
|
||||||
case x ⇒ x.toString
|
case x ⇒ x.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API
|
|
||||||
*/
|
|
||||||
private[http] def byteChar(input: ByteString, ix: Int): Char = byteAt(input, ix).toChar
|
private[http] def byteChar(input: ByteString, ix: Int): Char = byteAt(input, ix).toChar
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API
|
|
||||||
*/
|
|
||||||
private[http] def byteAt(input: ByteString, ix: Int): Byte =
|
private[http] def byteAt(input: ByteString, ix: Int): Byte =
|
||||||
if (ix < input.length) input(ix) else throw NotEnoughDataException
|
if (ix < input.length) input(ix) else throw NotEnoughDataException
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API
|
|
||||||
*/
|
|
||||||
private[http] def asciiString(input: ByteString, start: Int, end: Int): String = {
|
private[http] def asciiString(input: ByteString, start: Int, end: Int): String = {
|
||||||
@tailrec def build(ix: Int = start, sb: JStringBuilder = new JStringBuilder(end - start)): String =
|
@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))
|
if (ix == end) sb.toString else build(ix + 1, sb.append(input(ix).toChar))
|
||||||
if (start == end) "" else build()
|
if (start == end) "" else build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private[http] def logParsingError(info: ErrorInfo, log: LoggingAdapter,
|
||||||
|
setting: ParserSettings.ErrorLoggingVerbosity): Unit =
|
||||||
|
setting match {
|
||||||
|
case ParserSettings.ErrorLoggingVerbosity.Off ⇒ // nothing to do
|
||||||
|
case ParserSettings.ErrorLoggingVerbosity.Simple ⇒ log.warning(info.summary)
|
||||||
|
case ParserSettings.ErrorLoggingVerbosity.Full ⇒ log.warning(info.formatPretty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
package parsing {
|
package parsing {
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ import akka.stream.stage.PushPullStage
|
||||||
import akka.stream.scaladsl.OperationAttributes._
|
import akka.stream.scaladsl.OperationAttributes._
|
||||||
import akka.stream.scaladsl._
|
import akka.stream.scaladsl._
|
||||||
import akka.stream._
|
import akka.stream._
|
||||||
import akka.http.engine.parsing.{ HttpHeaderParser, HttpRequestParser }
|
import akka.http.engine.parsing._
|
||||||
import akka.http.engine.rendering.{ ResponseRenderingContext, HttpResponseRendererFactory }
|
import akka.http.engine.rendering.{ ResponseRenderingContext, HttpResponseRendererFactory }
|
||||||
import akka.http.engine.parsing.ParserOutput._
|
|
||||||
import akka.http.engine.TokenSourceActor
|
import akka.http.engine.TokenSourceActor
|
||||||
import akka.http.model._
|
import akka.http.model._
|
||||||
import akka.http.util._
|
import akka.http.util._
|
||||||
|
import ParserOutput._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
|
|
@ -50,14 +50,14 @@ private[http] object HttpServer {
|
||||||
|
|
||||||
def serverBlueprint(settings: ServerSettings,
|
def serverBlueprint(settings: ServerSettings,
|
||||||
log: LoggingAdapter)(implicit mat: FlowMaterializer): Graph[HttpServerPorts, Unit] = {
|
log: LoggingAdapter)(implicit mat: FlowMaterializer): Graph[HttpServerPorts, Unit] = {
|
||||||
|
import settings._
|
||||||
|
|
||||||
// the initial header parser we initially use for every connection,
|
// the initial header parser we initially use for every connection,
|
||||||
// will not be mutated, all "shared copy" parsers copy on first-write into the header cache
|
// will not be mutated, all "shared copy" parsers copy on first-write into the header cache
|
||||||
val rootParser = new HttpRequestParser(
|
val rootParser = new HttpRequestParser(parserSettings, rawRequestUriHeader,
|
||||||
settings.parserSettings,
|
HttpHeaderParser(parserSettings) { info ⇒
|
||||||
settings.rawRequestUriHeader,
|
if (parserSettings.illegalHeaderWarnings)
|
||||||
HttpHeaderParser(settings.parserSettings) { errorInfo ⇒
|
logParsingError(info withSummaryPrepended "Illegal request header", log, parserSettings.errorLoggingVerbosity)
|
||||||
if (settings.parserSettings.illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal request header").formatPretty)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
val responseRendererFactory = new HttpResponseRendererFactory(settings.serverHeader, settings.responseHeaderSizeHint, log)
|
val responseRendererFactory = new HttpResponseRendererFactory(settings.serverHeader, settings.responseHeaderSizeHint, log)
|
||||||
|
|
@ -149,7 +149,7 @@ private[http] object HttpServer {
|
||||||
this.requestStart = requestStart
|
this.requestStart = requestStart
|
||||||
ctx.changeCompletionHandling(waitingForApplicationResponseCompletionHandling)
|
ctx.changeCompletionHandling(waitingForApplicationResponseCompletionHandling)
|
||||||
waitingForApplicationResponse
|
waitingForApplicationResponse
|
||||||
case (ctx, _, MessageStartError(status, info)) ⇒ finishWithError(ctx, "request", status, info)
|
case (ctx, _, MessageStartError(status, info)) ⇒ finishWithError(ctx, status, info)
|
||||||
case _ ⇒ throw new IllegalStateException
|
case _ ⇒ throw new IllegalStateException
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,12 +181,13 @@ private[http] object HttpServer {
|
||||||
onUpstreamFailure = {
|
onUpstreamFailure = {
|
||||||
case (ctx, _, EntityStreamException(errorInfo)) ⇒
|
case (ctx, _, EntityStreamException(errorInfo)) ⇒
|
||||||
// the application has forwarded a request entity stream error to the response stream
|
// the application has forwarded a request entity stream error to the response stream
|
||||||
finishWithError(ctx, "request", StatusCodes.BadRequest, errorInfo)
|
finishWithError(ctx, StatusCodes.BadRequest, errorInfo)
|
||||||
case (ctx, _, error) ⇒ { ctx.fail(error); SameState }
|
case (ctx, _, error) ⇒ { ctx.fail(error); SameState }
|
||||||
})
|
})
|
||||||
|
|
||||||
def finishWithError(ctx: MergeLogicContextBase, target: String, status: StatusCode, info: ErrorInfo): State[Any] = {
|
def finishWithError(ctx: MergeLogicContextBase, status: StatusCode, info: ErrorInfo): State[Any] = {
|
||||||
log.warning("Illegal {}, responding with status '{}': {}", target, status, info.formatPretty)
|
logParsingError(info withSummaryPrepended s"Illegal request, responding with status '$status'",
|
||||||
|
log, settings.parserSettings.errorLoggingVerbosity)
|
||||||
val msg = if (settings.verboseErrorMessages) info.formatPretty else info.summary
|
val msg = if (settings.verboseErrorMessages) info.formatPretty else info.summary
|
||||||
// FIXME this is a workaround that is supposed to be solved by issue #16753
|
// FIXME this is a workaround that is supposed to be solved by issue #16753
|
||||||
ctx match {
|
ctx match {
|
||||||
|
|
@ -195,7 +196,6 @@ private[http] object HttpServer {
|
||||||
fullCtx.emit(ResponseRenderingContext(HttpResponse(status, entity = msg), closeRequested = true))
|
fullCtx.emit(ResponseRenderingContext(HttpResponse(status, entity = msg), closeRequested = true))
|
||||||
case other ⇒ throw new IllegalStateException(s"Unexpected MergeLogicContext [${other.getClass.getName}]")
|
case other ⇒ throw new IllegalStateException(s"Unexpected MergeLogicContext [${other.getClass.getName}]")
|
||||||
}
|
}
|
||||||
//
|
|
||||||
finish(ctx)
|
finish(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue