Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetty 12 with EE8 #1266

Merged
merged 12 commits into from
Aug 20, 2024
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
package io.udash.web.server

import io.udash.rest._
import io.udash.rpc._
import com.avsystem.commons.universalOps
import io.udash.rest.*
import io.udash.rpc.*
import io.udash.rpc.utils.CallLogging
import io.udash.web.guide.demos.activity.{Call, CallLogger}
import io.udash.web.guide.demos.rest.MainServerREST
import io.udash.web.guide.rest.ExposedRestInterfaces
import io.udash.web.guide.rpc.ExposedRpcInterfaces
import io.udash.web.guide.{GuideExceptions, MainServerRPC}
import monix.execution.Scheduler
import org.eclipse.jetty.ee8.nested.SessionHandler
import org.eclipse.jetty.ee8.servlet.{DefaultServlet, ServletContextHandler, ServletHolder}
import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer
import org.eclipse.jetty.rewrite.handler.{RewriteHandler, RewriteRegexRule}
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.ContextHandlerCollection
import org.eclipse.jetty.server.handler.gzip.GzipHandler
import org.eclipse.jetty.servlet.{DefaultServlet, ServletContextHandler, ServletHolder}
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer
import org.eclipse.jetty.util.resource.ResourceFactory

import java.nio.file.Path

class ApplicationServer(val port: Int, homepageResourceBase: String, guideResourceBase: String)(implicit scheduler: Scheduler) {
private val server = new Server(port)

def start(): Unit = {
def start(): Unit =
server.start()
}

def stop(): Unit = {
def stop(): Unit =
server.stop()
}

private val homepage = {
val ctx = createContextHandler(Array("udash.io", "www.udash.io", "udash.local", "127.0.0.1"))
ctx.addServlet(createStaticHandler(homepageResourceBase), "/*")
ctx
}
private val homepage =
new GzipHandler(createContextHandler(
hosts = Array("udash.io", "www.udash.io", "udash.local", "127.0.0.1"),
resourceBase = homepageResourceBase
).get())

private val guide = {
val ctx = createContextHandler(Array("guide.udash.io", "www.guide.udash.io", "guide.udash.local", "127.0.0.2", "localhost"))
ctx.getSessionHandler.addEventListener(new org.atmosphere.cpr.SessionSupport())
ctx.addServlet(createStaticHandler(guideResourceBase), "/*")
val contextHandler = createContextHandler(
hosts = Array("guide.udash.io", "www.guide.udash.io", "guide.udash.local", "127.0.0.2", "localhost"),
resourceBase = guideResourceBase
)
contextHandler.getSessionHandler.addEventListener(new org.atmosphere.cpr.SessionSupport())

val atmosphereHolder = {
val config = new DefaultAtmosphereServiceConfig[MainServerRPC](clientId => {
Expand All @@ -49,53 +55,30 @@ class ApplicationServer(val port: Int, homepageResourceBase: String, guideResour
})

val framework = new DefaultAtmosphereFramework(config, exceptionsRegistry = GuideExceptions.registry)
val atmosphereHolder = new ServletHolder(new RpcServlet(framework))
atmosphereHolder.setAsyncSupported(true)
atmosphereHolder
new ServletHolder(new RpcServlet(framework))
}
ctx.addServlet(atmosphereHolder, "/atm/*")
contextHandler.addServlet(atmosphereHolder, "/atm/*")

//required for org.atmosphere.container.JSR356AsyncSupport
JavaxWebSocketServletContainerInitializer.configure(ctx, null)
JavaxWebSocketServletContainerInitializer.configure(contextHandler, null)

val restHolder = new ServletHolder(
RestServlet[MainServerREST](new ExposedRestInterfaces)
)
restHolder.setAsyncSupported(true)
ctx.addServlet(restHolder, "/rest_api/*")
ctx
}
contextHandler.addServlet(new ServletHolder(RestServlet[MainServerREST](new ExposedRestInterfaces)), "/rest_api/*")

private val contexts = new ContextHandlerCollection
contexts.setHandlers(Array(homepage, guide))

private val rewriteHandler = {
import org.eclipse.jetty.rewrite.handler.RewriteRegexRule
val rewrite = new org.eclipse.jetty.rewrite.handler.RewriteHandler()
rewrite.setRewriteRequestURI(true)
rewrite.setRewritePathInfo(false)

val spaRewrite = new RewriteRegexRule
spaRewrite.setRegex("^/(?!assets|scripts|styles|atm|rest_api)(.*/?)*$")
spaRewrite.setReplacement("/")
rewrite.addRule(spaRewrite)
rewrite.setHandler(contexts)
rewrite
new GzipHandler(contextHandler.get())
}

server.setHandler(rewriteHandler)

private def createContextHandler(hosts: Array[String]): ServletContextHandler = {
val context = new ServletContextHandler(ServletContextHandler.SESSIONS)
context.insertHandler(new GzipHandler)
context.setVirtualHosts(hosts)
context
server.setHandler(
new RewriteHandler(new ContextHandlerCollection().setup(_.setHandlers(homepage, guide)))
.setup(_.addRule(new RewriteRegexRule("^/(?!assets|scripts|styles|atm|rest_api)(.*/?)*$", "/")))
)

private def createContextHandler(hosts: Array[String], resourceBase: String): ServletContextHandler = {
ddworak marked this conversation as resolved.
Show resolved Hide resolved
val contextHandler = new ServletContextHandler
contextHandler.setSessionHandler(new SessionHandler)
contextHandler.setVirtualHosts(hosts)
contextHandler.setBaseResource(ResourceFactory.of(contextHandler).newResource(Path.of(resourceBase).toRealPath()))
contextHandler.addServlet(new ServletHolder(new DefaultServlet), "/*")
contextHandler
}

private def createStaticHandler(resourceBase: String): ServletHolder = {
val appHolder = new ServletHolder(new DefaultServlet)
appHolder.setAsyncSupported(true)
appHolder.setInitParameter("resourceBase", resourceBase)
appHolder
}
}
6 changes: 3 additions & 3 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object Dependencies {

val scalaLoggingVersion = "3.9.5"

val jettyVersion = "10.0.22"
val jettyVersion = "12.0.12"
val typesafeConfigVersion = "1.4.3"
val flexmarkVersion = "0.64.8"
val logbackVersion = "1.3.14"
Expand Down Expand Up @@ -114,7 +114,7 @@ object Dependencies {
"javax.servlet" % "javax.servlet-api" % servletVersion,
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
"org.eclipse.jetty" % "jetty-server" % jettyVersion % Test,
"org.eclipse.jetty" % "jetty-servlet" % jettyVersion % Test
"org.eclipse.jetty.ee8" % "jetty-ee8-servlet" % jettyVersion % Test
))

val restSjsDeps = restCrossDeps
Expand Down Expand Up @@ -162,7 +162,7 @@ object Dependencies {
"org.codehaus.janino" % "janino" % janinoVersion, //conditional processing in logback

"org.eclipse.jetty" % "jetty-rewrite" % jettyVersion,
"org.eclipse.jetty.websocket" % "websocket-javax-server" % jettyVersion,
"org.eclipse.jetty.ee8.websocket" % "jetty-ee8-websocket-javax-server" % jettyVersion,

"com.typesafe" % "config" % typesafeConfigVersion,

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.udash
package rest

import io.udash.rest.raw._
import io.udash.rest.raw.*
import io.udash.testing.UdashSharedTest
import monix.execution.Scheduler
import org.eclipse.jetty.ee8.nested.SessionHandler
import org.eclipse.jetty.ee8.servlet.{ServletContextHandler, ServletHolder}
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.gzip.GzipHandler
import org.eclipse.jetty.server.session.SessionHandler
import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
import org.scalatest.BeforeAndAfterAll
import org.scalatest.concurrent.{Eventually, ScalaFutures}
import org.scalatest.time.{Millis, Seconds, Span}
Expand All @@ -17,22 +17,22 @@ import sttp.client3.SttpClientException.ConnectException
import scala.concurrent.duration.DurationLong
import scala.concurrent.{Await, Future}

class EndpointsIntegrationTest extends UdashSharedTest with BeforeAndAfterAll with Eventually with ScalaFutures {
class EndpointsIntegrationTest extends UdashSharedTest with UsesHttpServer with BeforeAndAfterAll with Eventually with ScalaFutures {
implicit def scheduler: Scheduler = Scheduler.global

val port = 44598
val contextPrefix = "/rest_api"
val baseUri = s"http://127.0.0.1:$port$contextPrefix"
val server = new Server(port)
val context = new ServletContextHandler()
context.setSessionHandler(new SessionHandler)
context.insertHandler(new GzipHandler)
private val contextPrefix = "/rest_api"
private def baseUri = s"http://127.0.0.1:$port$contextPrefix"

private val servlet = io.udash.rest.RestServlet[TestServerRESTInterface](new TestServerRESTInterfaceImpl)
val holder = new ServletHolder(servlet)
holder.setAsyncSupported(true)
context.addServlet(holder, s"$contextPrefix/*")
server.setHandler(context)
override protected def setupServer(server: Server): Unit = {
val holder = new ServletHolder(io.udash.rest.RestServlet[TestServerRESTInterface](new TestServerRESTInterfaceImpl))
holder.setAsyncSupported(true)

val contextHandler = new ServletContextHandler()
contextHandler.setSessionHandler(new SessionHandler)
contextHandler.addServlet(holder, s"$contextPrefix/*")

server.setHandler(new GzipHandler(contextHandler.get()))
}

def futureHandle(rawHandle: RawRest.HandleRequest): RestRequest => Future[RestResponse] =
rawHandle.andThen(FutureRestImplicits.futureFromTask.fromTask)
Expand All @@ -55,23 +55,13 @@ class EndpointsIntegrationTest extends UdashSharedTest with BeforeAndAfterAll wi

implicit val backend: SttpBackend[Future, Any] = SttpRestClient.defaultBackend()

val rawHandler = futureHandle(SttpRestClient.asHandleRequest[Future](baseUri))
val proxy: TestServerRESTInterface = SttpRestClient[TestServerRESTInterface, Future](baseUri)
val badRawHandler = futureHandle(SttpRestClient.asHandleRequest[Future](s"http://127.0.0.1:69$contextPrefix"))
def rawHandler = futureHandle(SttpRestClient.asHandleRequest[Future](baseUri))
def proxy: TestServerRESTInterface = SttpRestClient[TestServerRESTInterface, Future](baseUri)
def badRawHandler = futureHandle(SttpRestClient.asHandleRequest[Future](s"http://127.0.0.1:69$contextPrefix"))

def await[T](f: Future[T]): T =
Await.result(f, 3 seconds)

override protected def beforeAll(): Unit = {
super.beforeAll()
server.start()
}

override protected def afterAll(): Unit = {
super.afterAll()
server.stop()
}

"REST endpoint" should {
"work with Udash REST client (1)" in {
await(proxy.serviceOne().deeper().load(5)) should
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.udash
package rest

import org.eclipse.jetty.ee8.servlet.{ServletContextHandler, ServletHolder}
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.{ServletHandler, ServletHolder}
import org.eclipse.jetty.ee8.servlet.{ServletHandler, ServletHolder}

import scala.concurrent.duration._

Expand All @@ -15,8 +16,8 @@ abstract class ServletBasedRestApiTest extends RestApiTest with UsesHttpServer {
protected def setupServer(server: Server): Unit = {
val servlet = new RestServlet(serverHandle, serverTimeout, maxPayloadSize)
val holder = new ServletHolder(servlet)
val handler = new ServletHandler
handler.addServletWithMapping(holder, "/api/*")
val handler = new ServletContextHandler()
handler.addServlet(holder, "/api/*")
server.setHandler(handler)
}
}
15 changes: 13 additions & 2 deletions rest/.jvm/src/test/scala/io/udash/rest/UsesHttpServer.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package io.udash
package rest

import org.eclipse.jetty.server.{AbstractNetworkConnector, Server}
import com.avsystem.commons.JEnumSet
import org.eclipse.jetty.http.UriCompliance
import org.eclipse.jetty.http.UriCompliance.Violation
import org.eclipse.jetty.server.{AbstractNetworkConnector, HttpConnectionFactory, Server}
import org.scalatest.{BeforeAndAfterAll, Suite}

trait UsesHttpServer extends BeforeAndAfterAll { this: Suite =>
private val server: Server = new Server(0)
protected final def port: Int = server.getConnectors.head.asInstanceOf[AbstractNetworkConnector].getLocalPort
private val connector = server.getConnectors.head.asInstanceOf[AbstractNetworkConnector]
protected final def port: Int = connector.getLocalPort
protected final def baseUrl = s"http://localhost:$port"

protected def setupServer(server: Server): Unit

override protected def beforeAll(): Unit = {
super.beforeAll()
// Unsafe URI compliance is required for testing purposes
connector.getConnectionFactory(classOf[HttpConnectionFactory]).getHttpConfiguration.setUriCompliance(UsesHttpServer.LegacyJettyCompliance)
setupServer(server)
server.start()
}
Expand All @@ -22,3 +28,8 @@ trait UsesHttpServer extends BeforeAndAfterAll { this: Suite =>
super.afterAll()
}
}

private object UsesHttpServer {
// Jetty 10 default URI compliance
final val LegacyJettyCompliance = new UriCompliance("LEGACY_DEFAULT", JEnumSet(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_ENCODING))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package rest.examples
import io.udash.rest.RestServlet
import monix.execution.Scheduler
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
import org.eclipse.jetty.ee8.servlet.{ServletContextHandler, ServletHolder}

import scala.concurrent.Future

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ import com.avsystem.commons.annotation.explicitGenerics
import io.udash.rest.raw._
import io.udash.utils.URLEncoder
import monix.eval.Task
import org.eclipse.jetty.client.HttpClient
import org.eclipse.jetty.client.api.Result
import org.eclipse.jetty.client.util.{BufferingResponseListener, BytesRequestContent, StringRequestContent}
import org.eclipse.jetty.http.{HttpHeader, MimeTypes}
import org.eclipse.jetty.client.{BufferingResponseListener, BytesRequestContent, HttpClient, Result, StringRequestContent}
import org.eclipse.jetty.http.{HttpCookie, HttpHeader, MimeTypes}

import java.net.HttpCookie
import java.nio.charset.Charset
import scala.concurrent.duration._
import scala.util.{Failure, Success}
Expand Down Expand Up @@ -46,8 +43,8 @@ object JettyRestClient {
case (name, PlainValue(value)) => httpReq.headers(headers => headers.add(name, value))
}
request.parameters.cookies.entries.foreach {
case (name, PlainValue(value)) => httpReq.cookie(new HttpCookie(
URLEncoder.encode(name, spaceAsPlus = true), URLEncoder.encode(value, spaceAsPlus = true)))
case (name, PlainValue(value)) => httpReq.cookie(HttpCookie.build(
URLEncoder.encode(name, spaceAsPlus = true), URLEncoder.encode(value, spaceAsPlus = true)).build())
}

request.body match {
Expand Down