Skip to content

Commit

Permalink
ZIO HTTP Server should not fold set-cookie headers
Browse files Browse the repository at this point in the history
This change makes the behaviour of handling multiple Set-Cookie headers consistent among multiple server backends.

See #3654 for more details.
  • Loading branch information
mkubala committed Apr 25, 2024
1 parent 628ec8e commit b23e631
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class AllServerTests[F[_], OPTIONS, ROUTE](
oneOfBody: Boolean = true,
cors: Boolean = true,
options: Boolean = true,
maxContentLength: Boolean = true
maxContentLength: Boolean = true,
cookieHeaders: Boolean = true
)(implicit
m: MonadError[F]
) {
Expand All @@ -46,5 +47,6 @@ class AllServerTests[F[_], OPTIONS, ROUTE](
(if (validation) new ServerValidationTests(createServerTest).tests() else Nil) ++
(if (oneOfBody) new ServerOneOfBodyTests(createServerTest).tests() else Nil) ++
(if (cors) new ServerCORSTests(createServerTest).tests() else Nil) ++
(if (options) new ServerOptionsTests(createServerTest, serverInterpreter).tests() else Nil)
(if (options) new ServerOptionsTests(createServerTest, serverInterpreter).tests() else Nil) ++
(if (cookieHeaders) new ServerCookieHeadersTests(createServerTest).tests() else Nil)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package sttp.tapir.server.tests

import cats.implicits.catsSyntaxEitherId
import org.scalatest.matchers.should.Matchers._
import sttp.client3._
import sttp.model._
import sttp.model.headers.CookieWithMeta
import sttp.monad.MonadError
import sttp.tapir._
import sttp.tapir.tests.Test

class ServerCookieHeadersTests[F[_], OPTIONS, ROUTE](createServerTest: CreateServerTest[F, Any, OPTIONS, ROUTE])(implicit m: MonadError[F]) {

import createServerTest._

def tests(): List[Test] = requestTests

val requestTests = List(
testServerLogic(
endpoint.in("cookies-test").get.out(setCookies).serverLogic { _ =>
pureResult(
List(
CookieWithMeta.unsafeApply("name1", "value1", path = Some("/path1")),
CookieWithMeta.unsafeApply("name2", "value2", path = Some("/path2"))
).asRight[Unit]
)
},
"Multple Set-Cookie headers should not be compacted"
) { (backend, baseUri) =>
basicRequest.response(asStringAlways).get(uri"$baseUri/cookies-test").send(backend).map { r =>
r.headers should contain allOf(
Header.setCookie(CookieWithMeta.unsafeApply("name1", "value1", path = Some("/path1"))),
Header.setCookie(CookieWithMeta.unsafeApply("name2", "value2", path = Some("/path2")))
)
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ package sttp.tapir.server.ziohttp

import sttp.capabilities.WebSockets
import sttp.capabilities.zio.ZioStreams
import sttp.model.Method
import sttp.model.{Header => SttpHeader}
import sttp.monad.MonadError
import sttp.tapir.server.interceptor.RequestResult
import sttp.tapir.server.interceptor.reject.RejectInterceptor
import sttp.tapir.server.interpreter.FilterServerEndpoints
import sttp.tapir.server.interpreter.ServerInterpreter
import sttp.tapir.server.interpreter.{FilterServerEndpoints, ServerInterpreter}
import sttp.tapir.server.model.ServerResponse
import sttp.tapir.ztapir._
import zio._
import zio.http.{Header => ZioHttpHeader}
import zio.http.{Headers => ZioHttpHeaders}
import zio.http._
import zio.http.{Header => ZioHttpHeader, Headers => ZioHttpHeaders, _}

trait ZioHttpInterpreter[R] {
def zioHttpServerOptions: ZioHttpServerOptions[R] = ZioHttpServerOptions.default
Expand Down Expand Up @@ -88,7 +84,7 @@ trait ZioHttpInterpreter[R] {
resp: ServerResponse[ZioResponseBody],
body: Option[ZioHttpResponseBody]
): UIO[Response] = {
val baseHeaders = resp.headers.groupBy(_.name).map(sttpToZioHttpHeader).toList
val baseHeaders = resp.headers.groupBy(_.name).flatMap(sttpToZioHttpHeader).toList
val allHeaders = body.flatMap(_.contentLength) match {
case Some(contentLength) if resp.contentLength.isEmpty => ZioHttpHeader.ContentLength(contentLength) :: baseHeaders
case _ => baseHeaders
Expand All @@ -110,8 +106,17 @@ trait ZioHttpInterpreter[R] {
)
}

private def sttpToZioHttpHeader(hl: (String, Seq[SttpHeader])): ZioHttpHeader =
ZioHttpHeader.Custom(hl._1, hl._2.map(_.value).mkString(", "))
private def sttpToZioHttpHeader(hl: (String, Seq[SttpHeader])): Seq[ZioHttpHeader] = {
hl._1.toLowerCase match {
case "set-cookie" =>
hl._2.map(_.value).map { rawValue =>
ZioHttpHeader.SetCookie.parse(rawValue).toOption.getOrElse {
ZioHttpHeader.Custom(hl._1, rawValue)
}
}
case _ => List(ZioHttpHeader.Custom(hl._1, hl._2.map(_.value).mkString(", ")))
}
}
}

object ZioHttpInterpreter {
Expand Down

0 comments on commit b23e631

Please sign in to comment.