Skip to content

Commit

Permalink
Add Jetty10 and Jetty11 modules (#4845)
Browse files Browse the repository at this point in the history
Motivations:
It would be nice if we support Jetty 10 and Jetty 11

Modifications:
- Added Jetty10 and Jetty11 module
  - There're quite a few breaking changes when updating to the version 10.
    - `eventListeners` and `lifeCycleListeners` are both `EventListener`. https://github.com/line/armeria/blob/fbab0e3e5f3ba576a5db9bfc8ca1980d60b41006/jetty/jetty9/src/main/java/com/linecorp/armeria/server/jetty/JettyServiceBuilder.java#L51-L52
    - Setting a request content has changed.
    - Creating HttpFields(Http headers) is changed.
    - `HttpChannel` is created from `ArmeriaHttpConnection`.
  - There aren't many breaking changes between 10 and 11 except that 11 uses Jakarta while 10 uses Javax
  - Both requires slf4j 2.x
    - We cannot exclude slf4j 1.x completely at the moment because the core module added it with the api configuration.
    - Will revisit this later if this becomes a problem.
- Updated Jetty 9 version from 9.4.50.v20221201 to 9.4.51.v20230217
- Added Spring Boot Jetty example

Result:
- You can now use Armeria with Jetty 10 and Jetty 11
  • Loading branch information
minwoox authored Jun 3, 2023
1 parent 7d731ec commit 61b0d5c
Show file tree
Hide file tree
Showing 46 changed files with 2,457 additions and 418 deletions.
65 changes: 63 additions & 2 deletions dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ akka-http-cors = "1.0.0"
akka-grpc-runtime = "1.0.3"
apache-httpclient5 = "5.2.1"
apache-httpclient4 = "4.5.14"
asm = "9.5"
assertj = "3.24.2"
awaitility = "4.2.0"
blockhound = "1.0.8.RELEASE"
Expand Down Expand Up @@ -52,8 +53,14 @@ javax-inject = "1"
javax-jsr311 = "1.1.1"
javax-validation = "2.0.1.Final"
jctools = "4.0.1"
jetty93 = '9.3.30.v20211001'
jetty94 = "9.4.50.v20221201"
# Find the latest version of the major 10 https://central.sonatype.com/artifact/org.eclipse.jetty/jetty-server/
jetty10 = "10.0.15"
# Find the latest version of the major 10 https://central.sonatype.com/artifact/org.eclipse.jetty/apache-jstl/
jetty10-jstl = "10.0.15"
jetty11 = "11.0.15"
jetty11-jstl = "11.0.0"
jetty93 = "9.3.30.v20211001"
jetty94 = "9.4.51.v20230217"
jetty-alpn-api = "1.1.3.v20160715"
jetty-alpn-agent = "2.0.10"
jmh-core = "1.36"
Expand All @@ -71,6 +78,7 @@ kotlin = "1.8.10"
kotlin-coroutine = "1.6.4"
ktlint-gradle-plugin = "11.3.2"
logback = "1.2.11"
logback14 = "1.4.7"
micrometer = "1.10.5"
micrometer13 = "1.3.20"
mockito = "4.11.0"
Expand Down Expand Up @@ -116,6 +124,7 @@ shadow-gradle-plugin = "7.1.2"
shibboleth-utilities = "7.5.2"
snappy = "1.1.9.1"
slf4j = "1.7.36"
slf4j2 = "2.0.7"
spring6 = "6.0.6"
spring-boot2 = "2.7.10"
spring-boot3 = "3.0.5"
Expand Down Expand Up @@ -173,6 +182,11 @@ module = "org.apache.httpcomponents:httpclient"
version.ref = "apache-httpclient4"
exclusions = "commons-logging:commons-logging"

# This is only used for testing Jetty
[libraries.asm]
module = "org.ow2.asm:asm"
version.ref = "asm"

[libraries.assertj]
module = "org.assertj:assertj-core"
version.ref = "assertj"
Expand Down Expand Up @@ -565,6 +579,40 @@ module = "org.jctools:jctools-core"
version.ref = "jctools"
relocations = { from = "org.jctools", to = "com.linecorp.armeria.internal.shaded.jctools" }

[libraries.jetty10-annotations]
module = "org.eclipse.jetty:jetty-annotations"
version.ref = "jetty10"
[libraries.jetty10-apache-jsp]
module = "org.eclipse.jetty:apache-jsp"
version.ref = "jetty10"
[libraries.jetty10-apache-jstl]
module = "org.eclipse.jetty:apache-jstl"
version.ref = "jetty10-jstl"
[libraries.jetty10-server]
module = "org.eclipse.jetty:jetty-server"
version.ref = "jetty10"
# jetty-webapp for testing interoperability with other servers.
[libraries.jetty10-webapp]
module = "org.eclipse.jetty:jetty-webapp"
version.ref = "jetty10"

[libraries.jetty11-annotations]
module = "org.eclipse.jetty:jetty-annotations"
version.ref = "jetty11"
[libraries.jetty11-apache-jsp]
module = "org.eclipse.jetty:apache-jsp"
version.ref = "jetty11"
[libraries.jetty11-apache-jstl]
module = "org.eclipse.jetty:apache-jstl"
version.ref = "jetty11-jstl"
[libraries.jetty11-server]
module = "org.eclipse.jetty:jetty-server"
version.ref = "jetty11"
# jetty-webapp for testing interoperability with other servers.
[libraries.jetty11-webapp]
module = "org.eclipse.jetty:jetty-webapp"
version.ref = "jetty11"

[libraries.jetty93-annotations]
module = "org.eclipse.jetty:jetty-annotations"
exclusions = ["org.ow2.asm:asm", "org.ow2.asm:asm-commons"]
Expand Down Expand Up @@ -705,6 +753,11 @@ module = "ch.qos.logback:logback-classic"
version.ref = "logback"
javadocs = "https://www.javadoc.io/doc/ch.qos.logback/logback-classic/1.2.12/"

[libraries.logback14]
module = "ch.qos.logback:logback-classic"
version.ref = "logback14"
javadocs = "https://www.javadoc.io/doc/ch.qos.logback/logback-classic/1.4.7/"

[libraries.micrometer-core]
module = "io.micrometer:micrometer-core"
version.ref = "micrometer"
Expand Down Expand Up @@ -1026,6 +1079,11 @@ version.ref = "slf4j"
module = "org.slf4j:slf4j-simple"
version.ref = "slf4j"

[libraries.slf4j2-api]
module = "org.slf4j:slf4j-api"
version.ref = "slf4j2"
javadocs = "https://www.javadoc.io/doc/org.slf4j/slf4j-api/2.0.7/"

[libraries.spring6-web]
module = "org.springframework:spring-web"
version.ref = "spring6"
Expand Down Expand Up @@ -1073,6 +1131,9 @@ javadocs = "https://docs.spring.io/spring/docs/current/javadoc-api/"
[libraries.spring-boot3-starter-actuator]
module = "org.springframework.boot:spring-boot-starter-actuator"
version.ref = "spring-boot3"
[libraries.spring-boot3-starter-jetty]
module = "org.springframework.boot:spring-boot-starter-jetty"
version.ref = "spring-boot3"
[libraries.spring-boot3-starter-security]
module = "org.springframework.boot:spring-boot-starter-security"
version.ref = "spring-boot3"
Expand Down
24 changes: 24 additions & 0 deletions examples/spring-boot-jetty/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
alias libs.plugins.spring.boot
}

dependencies {
implementation project(':core')
implementation project(':spring:boot3-starter')
implementation project(':jetty11')

implementation libs.slf4j2.api
implementation libs.spring.boot3.starter.jetty

implementation(libs.spring.boot3.starter.web) {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
annotationProcessor libs.spring.boot3.configuration.processor

runtimeOnly project(':spring:boot3-actuator-starter')

testImplementation libs.assertj
testImplementation libs.junit5.jupiter.api
testImplementation libs.logback14
testImplementation libs.spring.boot3.starter.test
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package example.springframework.boot.jetty;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.healthcheck.HealthChecker;
import com.linecorp.armeria.server.jetty.JettyService;
import com.linecorp.armeria.spring.ArmeriaServerConfigurator;

import jakarta.servlet.Servlet;

/**
* Configures an Armeria server to redirect the incoming requests to the Jetty instance provided by
* Spring Boot. It also sets up a {@link HealthChecker} so that it works well with a load balancer.
*/
@Configuration
public class HelloConfiguration {

/**
* Returns a new {@link HealthChecker} that marks the server as unhealthy when Tomcat becomes unavailable.
*/
@Bean
public HealthChecker jettyHealthChecker(ServletWebServerApplicationContext applicationContext) {
final Server server = jettyServer(applicationContext).getServer();
return server::isRunning;
}

/**
* Returns a new {@link JettyService} that redirects the incoming requests to the Jetty instance
* provided by Spring Boot.
*/
@Bean
public JettyService jettyService(ServletWebServerApplicationContext applicationContext) {
final JettyWebServer jettyWebServer = jettyServer(applicationContext);
return JettyService.of(jettyWebServer.getServer(), null, false);
}

/**
* Returns a new {@link ArmeriaServerConfigurator} that is responsible for configuring a {@link Server}
* using the given {@link ServerBuilder}.
*/
@Bean
public ArmeriaServerConfigurator armeriaServiceInitializer(JettyService jettyService) {
return sb -> sb.serviceUnder("/", jettyService);
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
static class EmbeddedJetty {

@Bean
JettyServletWebServerFactory jettyServletWebServerFactory(
ObjectProvider<JettyServerCustomizer> serverCustomizers) {
final JettyServletWebServerFactory factory = new JettyServletWebServerFactory() {

@Override
protected JettyWebServer getJettyWebServer(Server server) {
return new JettyWebServer(server, true);
}
};
factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().toList());
return factory;
}
}

/**
* Extracts a Jetty {@link Server} from Spring webapp context.
*/
private static JettyWebServer jettyServer(ServletWebServerApplicationContext applicationContext) {
return (JettyWebServer) applicationContext.getWebServer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package example.springframework.boot.jetty;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
@RequestMapping(method = RequestMethod.GET, path = "/")
String index() {
return "index";
}

@RequestMapping(method = RequestMethod.GET, path = "/hello")
String hello() {
return "Hello, World";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package example.springframework.boot.jetty;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
spring.profiles.active: local
# Prevent the embedded Tomcat from opening a TCP/IP port.
server.port: -1
---
spring.config.activate.on-profile: local
armeria:
ports:
- port: 8080
protocols: HTTP
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package example.springframework.boot.jetty;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import jakarta.inject.Inject;

@ActiveProfiles("testbed")
@WebMvcTest(HelloController.class)
class HelloControllerTest {
@Inject
private MockMvc mvc;

@Test
void index() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(status().isOk())
.andExpect(content().string("index"));
}

@Test
void hello() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, World"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package example.springframework.boot.jetty;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ActiveProfiles;

import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.common.AggregatedHttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.server.Server;

import jakarta.inject.Inject;

@ActiveProfiles("testbed")
@SpringBootTest(
classes = {
HelloConfiguration.class,
HelloController.class
},
webEnvironment = WebEnvironment.DEFINED_PORT)
@EnableAutoConfiguration
class HelloIntegrationTest {

@Inject
private Server server;
private WebClient client;

@BeforeEach
void initClient() {
if (client == null) {
client = WebClient.of("http://127.0.0.1:" + server.activeLocalPort());
}
}

@Test
void index() {
final AggregatedHttpResponse res = client.get("/").aggregate().join();
assertThat(res.status()).isEqualTo(HttpStatus.OK);
assertThat(res.contentUtf8()).isEqualTo("index");
}

@Test
void hello() throws Exception {
final AggregatedHttpResponse res = client.get("/hello").aggregate().join();
assertThat(res.status()).isEqualTo(HttpStatus.OK);
assertThat(res.contentUtf8()).isEqualTo("Hello, World");
}

@Test
void healthCheck() throws Exception {
final AggregatedHttpResponse res = client.get("/internal/healthcheck").aggregate().join();
assertThat(res.status()).isEqualTo(HttpStatus.OK);
assertThat(res.contentUtf8()).isEqualTo("{\"healthy\":true}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Prevent the embedded Tomcat from opening a TCP/IP port.
server.port: -1
---
armeria:
ports:
- port: 0
protocols: HTTP
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ public TomcatService tomcatService(ServletWebServerApplicationContext applicatio
*/
@Bean
public ArmeriaServerConfigurator armeriaServiceInitializer(TomcatService tomcatService) {
return sb -> sb.service("prefix:/", tomcatService);
return sb -> sb.serviceUnder("/", tomcatService);
}
}
Loading

0 comments on commit 61b0d5c

Please sign in to comment.