diff --git a/core/src/main/scala/sttp/tapir/model/ServerRequest.scala b/core/src/main/scala/sttp/tapir/model/ServerRequest.scala index d1b3b7dfba..5abcf63661 100644 --- a/core/src/main/scala/sttp/tapir/model/ServerRequest.scala +++ b/core/src/main/scala/sttp/tapir/model/ServerRequest.scala @@ -7,6 +7,11 @@ import sttp.tapir.AttributeKey import java.net.InetSocketAddress import scala.collection.immutable.Seq +/** Implement for specific request representation of a particular server. Consider overriding [[ServerRequest#uri]] and + * [[ServerRequest#showShort]] for a backend-specific implementation of constructing the Uri and printing the path + query params. This way + * you'll avoid a performance overhead of parsing [[sttp.model.Uri]]. See https://softwaremill.com/benchmarking-tapir-part-2/ for more + * details. + */ trait ServerRequest extends RequestMetadata { def protocol: String def connectionInfo: ConnectionInfo diff --git a/server/akka-http-server/src/main/scala/sttp/tapir/server/akkahttp/AkkaServerRequest.scala b/server/akka-http-server/src/main/scala/sttp/tapir/server/akkahttp/AkkaServerRequest.scala index 2cb8da5a35..07218b8ec5 100644 --- a/server/akka-http-server/src/main/scala/sttp/tapir/server/akkahttp/AkkaServerRequest.scala +++ b/server/akka-http-server/src/main/scala/sttp/tapir/server/akkahttp/AkkaServerRequest.scala @@ -28,7 +28,7 @@ private[akkahttp] case class AkkaServerRequest(ctx: RequestContext, attributes: } override lazy val queryParameters: QueryParams = QueryParams.fromMultiMap(ctx.request.uri.query().toMultiMap) override lazy val method: Method = Method(ctx.request.method.value.toUpperCase) - + private def queryToSegments(query: AkkaUri.Query): List[QuerySegment] = { @tailrec def run(q: AkkaUri.Query, acc: List[QuerySegment]): List[QuerySegment] = q match { @@ -45,6 +45,7 @@ private[akkahttp] case class AkkaServerRequest(ctx: RequestContext, attributes: run(query, Nil) } + override lazy val showShort: String = s"$method ${ctx.request.uri.path}${ctx.request.uri.rawQueryString.getOrElse("")}" override lazy val uri: Uri = { val pekkoUri = ctx.request.uri Uri( @@ -57,7 +58,6 @@ private[akkahttp] case class AkkaServerRequest(ctx: RequestContext, attributes: ) } - private val EmptyContentType = "none/none" // Add low-level headers that have been removed by akka-http. diff --git a/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerRequest.scala b/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerRequest.scala index 7e65623354..6e3b264c47 100644 --- a/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerRequest.scala +++ b/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerRequest.scala @@ -25,6 +25,7 @@ private[http4s] case class Http4sServerRequest[F[_]](req: Request[F], attributes override lazy val queryParameters: QueryParams = QueryParams.fromMultiMap(req.multiParams) override def method: Method = Method(req.method.name.toUpperCase) + override lazy val showShort: String = s"$method ${req.uri.copy(scheme = None, authority = None, fragment = None).toString}" override lazy val uri: Uri = Uri.apply( req.uri.scheme.map(_.value), diff --git a/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyServerRequest.scala b/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyServerRequest.scala index 7b9be99466..6b0bd9a3ca 100644 --- a/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyServerRequest.scala +++ b/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyServerRequest.scala @@ -24,6 +24,7 @@ case class NettyServerRequest(req: HttpRequest, attributes: AttributeMap = Attri QueryParams.fromMultiMap(multiMap) } override lazy val method: Method = Method.unsafeApply(req.method().name()) + override lazy val showShort: String = s"$method ${req.uri()}" override lazy val uri: Uri = Uri.unsafeParse(req.uri()) override lazy val pathSegments: List[String] = uri.pathSegments.segments.map(_.v).filter(_.nonEmpty).toList override lazy val headers: Seq[Header] = req.headers().toHeaderSeq ::: (req match { diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaServerRequest.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaServerRequest.scala index f3d8f80e5e..ba1778c4c4 100644 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaServerRequest.scala +++ b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaServerRequest.scala @@ -18,6 +18,12 @@ private[nima] case class NimaServerRequest(r: JavaNimaServerRequest, attributes: override def queryParameters: QueryParams = uri.params override def method: Method = Method.unsafeApply(r.prologue().method().text()) + override lazy val showShort = { + val path = emptyIfNull(r.path().rawPath()) + val rawQuery = emptyIfNull(r.query().rawValue()) + if (rawQuery.isEmpty) s"$method $path" else s"$method $path?$rawQuery" + } + override lazy val uri: Uri = { val protocol = emptyIfNull(r.prologue().protocol()) val authority = emptyIfNull(r.authority()) diff --git a/server/pekko-http-server/src/main/scala/sttp/tapir/server/pekkohttp/PekkoServerRequest.scala b/server/pekko-http-server/src/main/scala/sttp/tapir/server/pekkohttp/PekkoServerRequest.scala index d8a49abab9..18cf633298 100644 --- a/server/pekko-http-server/src/main/scala/sttp/tapir/server/pekkohttp/PekkoServerRequest.scala +++ b/server/pekko-http-server/src/main/scala/sttp/tapir/server/pekkohttp/PekkoServerRequest.scala @@ -44,6 +44,7 @@ private[pekkohttp] case class PekkoServerRequest(ctx: RequestContext, attributes run(query, Nil) } + override lazy val showShort: String = s"$method ${ctx.request.uri.path}${ctx.request.uri.rawQueryString.getOrElse("")}" override lazy val uri: Uri = { val pekkoUri = ctx.request.uri Uri( diff --git a/server/play-server/src/main/scala/sttp/tapir/server/play/PlayServerRequest.scala b/server/play-server/src/main/scala/sttp/tapir/server/play/PlayServerRequest.scala index 92aa085cdf..5aef8da0d1 100644 --- a/server/play-server/src/main/scala/sttp/tapir/server/play/PlayServerRequest.scala +++ b/server/play-server/src/main/scala/sttp/tapir/server/play/PlayServerRequest.scala @@ -16,6 +16,7 @@ private[play] case class PlayServerRequest( ) extends ServerRequest { override lazy val method: Method = Method(requestHeader.method.toUpperCase) override def protocol: String = requestHeader.version + override lazy val showShort: String = s"$method ${requestHeader.target.path}${requestHeader.target.uriString.split('?').mkString("?")}" override lazy val uri: Uri = Uri.unsafeParse(requestHeader.uri) override lazy val connectionInfo: ConnectionInfo = ConnectionInfo(None, None, Some(requestHeader.secure)) override lazy val headers: Seq[Header] = requestHeader.headers.headers.map { case (k, v) => Header(k, v) }.toList diff --git a/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/decoders/VertxServerRequest.scala b/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/decoders/VertxServerRequest.scala index bb89790a49..68826b9e4c 100644 --- a/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/decoders/VertxServerRequest.scala +++ b/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/decoders/VertxServerRequest.scala @@ -22,6 +22,7 @@ private[vertx] case class VertxServerRequest(rc: RoutingContext, attributes: Att } override lazy val method: Method = MethodMapping.vertxToSttp(rc.request) override lazy val protocol: String = Option(rc.request.scheme).getOrElse("") + override lazy val showShort: String = s"$method ${rc.request.uri}" override lazy val uri: Uri = Uri.unsafeParse(rc.request.uri) override lazy val headers: Seq[Header] = rc.request.headers.entries.asScala.iterator.map(e => Header(e.getKey, e.getValue)).toList override lazy val queryParameters: QueryParams = Uri.unsafeParse(rc.request.uri()).params diff --git a/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpServerRequest.scala b/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpServerRequest.scala index 9a92eeae98..97331b2388 100644 --- a/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpServerRequest.scala +++ b/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpServerRequest.scala @@ -3,10 +3,13 @@ package sttp.tapir.server.ziohttp import sttp.model.{QueryParams, Uri, Header => SttpHeader, Method => SttpMethod} import sttp.tapir.{AttributeKey, AttributeMap} import sttp.tapir.model.{ConnectionInfo, ServerRequest} -import zio.http.Request +import zio.http.{Request, QueryParams => ZQueryParams} import java.net.InetSocketAddress import scala.collection.immutable.Seq +import java.nio.charset.Charset +import io.netty.handler.codec.http.QueryStringEncoder +import zio.http.Charsets case class ZioHttpServerRequest(req: Request, attributes: AttributeMap = AttributeMap.Empty) extends ServerRequest { override def protocol: String = "HTTP/1.1" // missing field in request @@ -20,9 +23,26 @@ case class ZioHttpServerRequest(req: Request, attributes: AttributeMap = Attribu override lazy val pathSegments: List[String] = uri.pathSegments.segments.map(_.v).filter(_.nonEmpty).toList override lazy val queryParameters: QueryParams = QueryParams.fromMultiMap(req.url.queryParams.map) override lazy val method: SttpMethod = SttpMethod(req.method.name.toUpperCase) + override lazy val showShort: String = s"$method ${encodeQueryParams(req.url.path.encode, req.url.queryParams.normalize, Charsets.Http)}" + override lazy val uri: Uri = Uri.unsafeParse(req.url.encode) override lazy val headers: Seq[SttpHeader] = req.headers.toList.map { h => SttpHeader(h.headerName, h.renderedValue) } override def attribute[T](k: AttributeKey[T]): Option[T] = attributes.get(k) override def attribute[T](k: AttributeKey[T], v: T): ZioHttpServerRequest = copy(attributes = attributes.put(k, v)) override def withUnderlying(underlying: Any): ServerRequest = ZioHttpServerRequest(req = underlying.asInstanceOf[Request], attributes) + + // Copied from zio.http.netty.QueryParamEncoder.encode + private def encodeQueryParams(baseUri: String, queryParams: ZQueryParams, charset: Charset): String = { + val encoder = new QueryStringEncoder(baseUri, charset) + queryParams.map.foreach { case (key, values) => + if (key != "") { + if (values.isEmpty) { + encoder.addParam(key, "") + } else + values.foreach(value => encoder.addParam(key, value)) + } + } + + encoder.toString + } }