Skip to content

Commit

Permalink
Merge pull request #1266 from UdashFramework/jetty12
Browse files Browse the repository at this point in the history
Jetty 12 with EE8
  • Loading branch information
ddworak authored Aug 20, 2024
2 parents 414ee5a + 6a646e1 commit 49f009c
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 101 deletions.
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 = {
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

0 comments on commit 49f009c

Please sign in to comment.