From 9c7e3e859c0d68e94121a49e502ca046be8c7b41 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 4 Mar 2024 14:18:45 +0100 Subject: [PATCH] Optimize server request .uri for pekko-http and akka-http --- .../server/akkahttp/AkkaServerRequest.scala | 31 ++++++++++++++++- .../server/pekkohttp/PekkoServerRequest.scala | 34 +++++++++++++++++-- 2 files changed, 61 insertions(+), 4 deletions(-) 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 7acab90b35..2cb8da5a35 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 @@ -3,6 +3,7 @@ package sttp.tapir.server.akkahttp import akka.http.scaladsl.server.RequestContext import akka.http.scaladsl.model.headers.{`Content-Length`, `Content-Type`} import akka.http.scaladsl.model.{Uri => AkkaUri} +import sttp.model.Uri.{Authority, FragmentSegment, HostSegment, PathSegments, QuerySegment} import sttp.model.{Header, Method, QueryParams, Uri} import sttp.tapir.{AttributeKey, AttributeMap} import sttp.tapir.model.{ConnectionInfo, ServerRequest} @@ -27,7 +28,35 @@ 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) - override lazy val uri: Uri = Uri.unsafeParse(ctx.request.uri.toString()) + + private def queryToSegments(query: AkkaUri.Query): List[QuerySegment] = { + @tailrec + def run(q: AkkaUri.Query, acc: List[QuerySegment]): List[QuerySegment] = q match { + case AkkaUri.Query.Cons(k, v, tail) => { + if (k.isEmpty) + run(tail, QuerySegment.Value(v) :: acc) + else if (v.isEmpty) + run(tail, QuerySegment.Value(k) :: acc) + else + run(tail, QuerySegment.KeyValue(k, v) :: acc) + } + case AkkaUri.Query.Empty => acc.reverse + } + run(query, Nil) + } + + override lazy val uri: Uri = { + val pekkoUri = ctx.request.uri + Uri( + Some(pekkoUri.scheme), + // UserInfo is available only as a raw string, but we can skip it as it's not needed + Some(Authority(userInfo = None, HostSegment(pekkoUri.authority.host.address), Some(pekkoUri.effectivePort))), + PathSegments.absoluteOrEmptyS(pathSegments ++ (if (pekkoUri.path.endsWithSlash) Seq("") else Nil)), + queryToSegments(ctx.request.uri.query()), + ctx.request.uri.fragment.map(f => FragmentSegment(f)) + ) + } + private val EmptyContentType = "none/none" 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 44ed64d6e3..d8a49abab9 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 @@ -1,10 +1,11 @@ package sttp.tapir.server.pekkohttp -import org.apache.pekko.http.scaladsl.server.RequestContext import org.apache.pekko.http.scaladsl.model.{Uri => PekkoUri} +import org.apache.pekko.http.scaladsl.server.RequestContext +import sttp.model.Uri.{Authority, FragmentSegment, HostSegment, PathSegments, QuerySegment} import sttp.model.{Header, HeaderNames, Method, QueryParams, Uri} -import sttp.tapir.{AttributeKey, AttributeMap} import sttp.tapir.model.{ConnectionInfo, ServerRequest} +import sttp.tapir.{AttributeKey, AttributeMap} import scala.annotation.tailrec import scala.collection.immutable.Seq @@ -26,7 +27,34 @@ private[pekkohttp] case class PekkoServerRequest(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) - override lazy val uri: Uri = Uri.unsafeParse(ctx.request.uri.toString()) + + private def queryToSegments(query: PekkoUri.Query): List[QuerySegment] = { + @tailrec + def run(q: PekkoUri.Query, acc: List[QuerySegment]): List[QuerySegment] = q match { + case PekkoUri.Query.Cons(k, v, tail) => { + if (k.isEmpty) + run(tail, QuerySegment.Value(v) :: acc) + else if (v.isEmpty) + run(tail, QuerySegment.Value(k) :: acc) + else + run(tail, QuerySegment.KeyValue(k, v) :: acc) + } + case PekkoUri.Query.Empty => acc.reverse + } + run(query, Nil) + } + + override lazy val uri: Uri = { + val pekkoUri = ctx.request.uri + Uri( + Some(pekkoUri.scheme), + // UserInfo is available only as a raw string, but we can skip it as it's not needed + Some(Authority(userInfo = None, HostSegment(pekkoUri.authority.host.address), Some(pekkoUri.effectivePort))), + PathSegments.absoluteOrEmptyS(pathSegments ++ (if (pekkoUri.path.endsWithSlash) Seq("") else Nil)), + queryToSegments(ctx.request.uri.query()), + ctx.request.uri.fragment.map(f => FragmentSegment(f)) + ) + } private val EmptyContentType = "none/none"