Skip to content

Commit 95f4edc

Browse files
author
sam
committed
Upgrade libraries
- latest jetty 9 - cats effect 3
1 parent 1d3bda3 commit 95f4edc

File tree

13 files changed

+228
-35
lines changed

13 files changed

+228
-35
lines changed

build.sbt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
val jettyVersion = "9.4.35.v20201120"
1+
val jettyVersion = "9.4.46.v20220331"
22

33
name := "samatra-extras"
44

@@ -23,8 +23,8 @@ lazy val commonSettings = Seq(
2323
"org.eclipse.jetty" % "jetty-util" % jettyVersion,
2424
"org.eclipse.jetty" % "jetty-jmx" % jettyVersion,
2525

26-
"org.slf4j" % "slf4j-api" % "1.7.30",
27-
"org.asynchttpclient" % "async-http-client" % "2.12.1"
26+
"org.slf4j" % "slf4j-api" % "1.7.36",
27+
"org.asynchttpclient" % "async-http-client" % "2.12.3"
2828
)
2929
)
3030

samatra-extras-cats/build.sbt

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1-
libraryDependencies += "org.typelevel" %% "cats-core" % "2.2.0"
2-
libraryDependencies += "org.typelevel" %% "cats-effect" % "2.2.0"
1+
val jettyVersion = "9.4.46.v20220331"
2+
3+
libraryDependencies ++=
4+
Seq(
5+
"org.typelevel" %% "cats-core" % "2.7.0",
6+
"org.typelevel" %% "cats-effect" % "3.3.11",
7+
"org.eclipse.jetty" % "jetty-webapp" % jettyVersion,
8+
"org.eclipse.jetty" % "jetty-server" % jettyVersion,
9+
"org.scalatest" %% "scalatest" % "3.2.11" % "test"
10+
)
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.springer.samatra.extras.cats
22

3-
import cats.effect.IO
3+
import cats.effect.{Async, IO}
44
import org.asynchttpclient.ListenableFuture
55

66
import scala.concurrent.{ExecutionContext, ExecutionException}
77

8-
object AsyncHttpIoExtensions {
8+
object AsyncHttpCatsAsycnExtensions {
99

1010
implicit class IoWithFilter[A](val io: IO[A]) extends AnyVal {
1111
def flatMap[B](f: A => IO[B]): IO[B] = io.flatMap(f)
@@ -15,22 +15,22 @@ object AsyncHttpIoExtensions {
1515

1616
implicit class ListenableFutureIoOps[A](val listenableFuture: ListenableFuture[A]) extends AnyVal {
1717

18-
def toIO(implicit ec: ExecutionContext = ExecutionContext.global): IO[A] = IO.cancelable { cb =>
18+
def toAsync[F[_]: Async](implicit ex: ExecutionContext = scala.concurrent.ExecutionContext.global): F[A] = Async[F].async { cb =>
1919

2020
val c = listenableFuture.addListener(
2121
() => cb(scala.util.Try(listenableFuture.get()).toEither.left.map {
2222
case exc: ExecutionException => exc.getCause
2323
case throwable => throwable
2424
}),
25-
(command: Runnable) => ec.execute(command)
25+
(command: Runnable) => ex.execute(command)
2626
)
2727

2828
// Cancellation logic, suspended in IO
29-
IO.apply({
29+
Async[F].delay(Some(Async[F].delay({
3030
if (!c.isDone && !c.isCancelled) {
3131
c.cancel(true)
3232
}
33-
})
33+
})))
3434
}
3535
}
3636
}

samatra-extras-cats/src/main/scala/com/springer/samatra/extras/cats/IoResponses.scala

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
package com.springer.samatra.extras.cats
22

3-
import java.util.concurrent.atomic.AtomicReference
4-
import cats.effect.{CancelToken, IO}
5-
import cats.effect.IO.shift
3+
import cats.effect.IO
4+
import cats.effect.unsafe.IORuntime
65
import com.springer.samatra.routing.FutureResponses.{Rendering, Running, State, TimingOutListener}
76
import com.springer.samatra.routing.Routings.HttpResp
87

8+
import java.util.concurrent.atomic.AtomicReference
99
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
1010
import javax.servlet.{AsyncContext, AsyncEvent}
11-
import scala.concurrent.ExecutionContext
1211

1312
object IoResponses {
1413

1514
case class Timeout(t: Long)
1615

1716
case class ResponseOnTimeout(statusCode: Int)
1817

19-
implicit class IoResponse[A](io: IO[A])(implicit rest: A => HttpResp, ex: ExecutionContext = ExecutionContext.global, timeout: Timeout = Timeout(5000), responseOnTimeout: ResponseOnTimeout = ResponseOnTimeout(500)) extends HttpResp {
18+
def fromIOWithTimeout[T](fa: IO[T])(timeout: Timeout, responseOnTimeout: ResponseOnTimeout = ResponseOnTimeout(500))(implicit rest: T => HttpResp, rt: IORuntime): HttpResp =
19+
IoResponse(fa)(rest, timeout, responseOnTimeout, rt)
20+
21+
implicit class IoResponse[A](io: IO[A])(implicit rest: A => HttpResp, timeout: Timeout = Timeout(5000), responseOnTimeout: ResponseOnTimeout = ResponseOnTimeout(500), rt: IORuntime) extends HttpResp {
2022
override def process(req: HttpServletRequest, resp: HttpServletResponse): Unit = {
2123
if (req.isAsyncStarted) {
2224
throw new IllegalStateException("Async already started. Have you wrapper a IOResponse inside another IOResponse?")
@@ -27,9 +29,8 @@ object IoResponses {
2729
val async: AsyncContext = req.startAsync(req, resp)
2830
async.setTimeout(timeout.t)
2931

30-
val cancel: CancelToken[IO] = shift(ex)
31-
.flatMap(_ => io)
32-
.unsafeRunCancelable(_.fold(
32+
val cancel =
33+
io.attempt.map(_.fold(
3334
err => {
3435
if (state.getAndSet(Rendering) == Running) {
3536
val asyncResponse: HttpServletResponse = async.getResponse.asInstanceOf[HttpServletResponse]
@@ -53,12 +54,12 @@ object IoResponses {
5354
}
5455
}
5556
}
56-
))
57+
)).unsafeRunCancelable()
5758

5859
async.addListener(new TimingOutListener(state, timeout.t, false, responseOnTimeout.statusCode) {
5960
override def onTimeout(event: AsyncEvent): Unit = {
6061
super.onTimeout(event)
61-
cancel.unsafeRunSync()
62+
cancel()
6263
}
6364
})
6465
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.springer.samatra.extras.cats
2+
3+
import cats.effect.IO
4+
import com.springer.samatra.extras.cats.AsyncHttpCatsAsycnExtensions.ListenableFutureIoOps
5+
import com.springer.samatra.routing.Routings.{Controller, Routes}
6+
import com.springer.samatra.routing.StandardResponses.Implicits.fromString
7+
import org.asynchttpclient.Dsl.asyncHttpClient
8+
import org.eclipse.jetty.server.handler.gzip.GzipHandler
9+
import org.eclipse.jetty.server.{Connector, Server, ServerConnector}
10+
import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
11+
import org.scalatest.BeforeAndAfterAll
12+
import org.scalatest.funspec.AnyFunSpec
13+
import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper
14+
15+
class AsyncHttpCatsAsyncTest extends AnyFunSpec with BeforeAndAfterAll {
16+
17+
private val server = new Server() {
18+
addConnector(new ServerConnector(this) {
19+
setPort(0)
20+
})
21+
}
22+
23+
private val handler = new GzipHandler()
24+
handler.setHandler(new ServletContextHandler() {
25+
setContextPath("/test")
26+
27+
addServlet(new ServletHolder(
28+
Routes(new Controller {
29+
30+
get("/test") { _ =>
31+
"test"
32+
}
33+
})), "/cats/*")
34+
})
35+
36+
server.setHandler(handler)
37+
38+
it("can lift an async http client to Async") {
39+
import cats.effect.unsafe.implicits.global
40+
41+
val io = asyncHttpClient.prepareGet(s"$host/test/cats/test").execute().toAsync[IO]
42+
43+
io.unsafeRunSync().getStatusCode shouldBe 200
44+
}
45+
46+
47+
override protected def beforeAll(): Unit = {
48+
server.start()
49+
val connectors: Array[Connector] = server.getConnectors
50+
val port: Int = connectors(0).asInstanceOf[ServerConnector].getLocalPort
51+
52+
host = s"http://localhost:$port"
53+
}
54+
55+
override protected def afterAll(): Unit = server.stop()
56+
57+
var host: String = _
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.springer.samatra.extras.cats
2+
3+
import cats.effect.IO
4+
import com.springer.samatra.extras.cats.IoResponses.{IoResponse, Timeout, fromIOWithTimeout}
5+
import com.springer.samatra.routing.Routings.{Controller, HttpResp, Routes}
6+
import com.springer.samatra.routing.StandardResponses.Implicits.{fromFile, fromString}
7+
import io.netty.handler.codec.http.DefaultHttpHeaders
8+
import org.asynchttpclient.Dsl.asyncHttpClient
9+
import org.asynchttpclient.Response
10+
import org.eclipse.jetty.server.handler.gzip.GzipHandler
11+
import org.eclipse.jetty.server.{Connector, Server, ServerConnector}
12+
import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
13+
import org.scalatest.BeforeAndAfterAll
14+
import org.scalatest.funspec.AnyFunSpec
15+
import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper
16+
17+
import scala.concurrent.duration.DurationInt
18+
import scala.jdk.CollectionConverters.{CollectionHasAsScala, IterableHasAsJava}
19+
import cats.effect.unsafe.implicits.global
20+
import com.springer.samatra.routing.StandardResponses.{AddCookie, Halt, Redirect, WithCookies, WithHeaders}
21+
22+
import java.nio.file.Paths
23+
24+
class CatsEffectEndToEndTest extends AnyFunSpec with BeforeAndAfterAll {
25+
26+
private val server = new Server() {
27+
addConnector(new ServerConnector(this) {
28+
setPort(0)
29+
})
30+
}
31+
32+
private val handler = new GzipHandler()
33+
handler.setHandler(new ServletContextHandler() {
34+
setContextPath("/test")
35+
36+
addServlet(new ServletHolder(
37+
Routes(new Controller {
38+
39+
get("/relative") { req =>
40+
IO.sleep(100.millis).map { _ =>
41+
req.relativePath
42+
}
43+
}
44+
45+
get("/morethanone/:type") { req =>
46+
IO[HttpResp] {
47+
req.captured("type") match {
48+
case "redirect" => Redirect("/getandpost")
49+
case "string" => "String"
50+
case "Error" => Halt(500, Some(new RuntimeException("error")))
51+
case "NotFound" => Halt(404)
52+
case "file" => WithHeaders("Content-Type" -> "application/xml") {
53+
Paths.get("build.sbt")
54+
}
55+
case "headers" => WithHeaders("hi" -> "there")("body")
56+
case "cookies" =>
57+
WithCookies(AddCookie("cookie", "tasty"))("body")
58+
case "securedcookies" =>
59+
WithCookies(AddCookie("cookie", "tasty", httpOnly = true))("body")
60+
}
61+
}
62+
}
63+
64+
//use explicits
65+
get("/timeout") { _ =>
66+
fromIOWithTimeout(IO.sleep(100.second).map { _ =>
67+
"unexpceted"
68+
})(Timeout(200))
69+
}
70+
71+
})), "/cats/*")
72+
})
73+
74+
server.setHandler(handler)
75+
76+
it("has correct relative path after async") {
77+
get("/test/cats/relative").getResponseBody shouldBe "/relative"
78+
}
79+
80+
it("HEAD should return 200, 302, 404 and 500 error codes") {
81+
head("/test/cats/morethanone/Error").getStatusCode shouldBe 500
82+
head("/test/cats/morethanone/NotFound").getStatusCode shouldBe 404
83+
head("/test/cats/morethanone/redirect").getStatusCode shouldBe 302
84+
head("/test/cats/morethanone/string").getStatusCode shouldBe 200
85+
head("/test/cats/morethanone/headers").getHeader("hi") shouldBe "there"
86+
87+
val cookies = head("/test/cats/morethanone/cookies").getCookies
88+
val cookie = cookies.asScala.collectFirst {
89+
case c if c.name() == "cookie" => c.value()
90+
}
91+
92+
cookie shouldBe Some("tasty")
93+
}
94+
95+
it("can timeout") {
96+
val res = get("/test/cats/timeout")
97+
res.getStatusCode shouldBe 500
98+
}
99+
100+
101+
def head(path: String): Response = asyncHttpClient.prepareHead(s"$host$path").execute().get()
102+
103+
def get(path: String, headers: Map[String, Seq[String]] = Map.empty): Response = {
104+
val hs = new DefaultHttpHeaders()
105+
headers.foreach { case (k, v) => hs.add(k, v.asJava) }
106+
107+
asyncHttpClient.prepareGet(s"$host$path")
108+
.setHeaders(hs)
109+
.execute().get()
110+
}
111+
112+
override protected def beforeAll(): Unit = {
113+
server.start()
114+
val connectors: Array[Connector] = server.getConnectors
115+
val port: Int = connectors(0).asInstanceOf[ServerConnector].getLocalPort
116+
117+
host = s"http://localhost:$port"
118+
}
119+
120+
override protected def afterAll(): Unit = server.stop()
121+
122+
var host: String = _
123+
}

samatra-extras-core/build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.3" % "test"
1+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.11" % "test"
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package com.springer.samatra.extras.core.jetty
22

3-
import java.nio.ByteBuffer
4-
53
import com.springer.samatra.routing.Routings.HttpResp
6-
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
74
import org.eclipse.jetty.server.HttpOutput
85

6+
import java.nio.ByteBuffer
7+
import javax.servlet.ServletOutputStream
8+
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
99
import scala.language.implicitConversions
1010

1111
object JettySpecificResponses {
1212

13-
implicit def fromByteBuffer(b: ByteBuffer): HttpResp = (req: HttpServletRequest, resp: HttpServletResponse) => resp.getOutputStream match {
14-
case httpOut: HttpOutput => httpOut.sendContent(b)
15-
case notHttpOut@_ => notHttpOut.write(b.array())
13+
implicit def fromByteBuffer(b: ByteBuffer): HttpResp = (req: HttpServletRequest, resp: HttpServletResponse) => {
14+
val stream: ServletOutputStream = resp.getOutputStream
15+
stream match {
16+
case httpOut: HttpOutput => httpOut.sendContent(b)
17+
case notHttpOut@_ => notHttpOut.write(b.array())
18+
}
1619
}
1720
}

samatra-extras-mustache/build.sbt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
libraryDependencies ++= Seq(
2-
"com.samskivert" % "jmustache" % "1.14",
3-
"org.scalatest" %% "scalatest" % "3.2.3" % "test"
2+
"com.samskivert" % "jmustache" % "1.15",
3+
"org.scalatest" %% "scalatest" % "3.2.11" % "test"
44
)

samatra-extras-newrelic/build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
libraryDependencies += "com.newrelic.agent.java" % "newrelic-api" % "6.2.1"
1+
libraryDependencies += "com.newrelic.agent.java" % "newrelic-api" % "7.6.0"
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
libraryDependencies ++= Seq(
22
"org.javassist" % "javassist" % "3.27.0-GA",
3-
"org.scalatest" %% "scalatest" % "3.2.3" % "test"
3+
"org.scalatest" %% "scalatest" % "3.2.11" % "test"
44
)

samatra-extras-websockets/build.sbt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
val jettyVersion = "9.4.35.v20201120"
1+
val jettyVersion = "9.4.46.v20220331"
22

33
libraryDependencies ++= Seq(
44
"com.github.westernsam.samatra" %% "samatra-websockets" % "v1.1",
55
"org.eclipse.jetty.websocket" % "javax-websocket-server-impl" % jettyVersion,
6-
"org.scalatest" %% "scalatest" % "3.2.3" % "test"
6+
"org.scalatest" %% "scalatest" % "3.2.11" % "test"
77
)

samatra-extras-xml/build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.3.0"
1+
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "2.1.0"

0 commit comments

Comments
 (0)