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

fix: don't auto-start jetty service for negative port #44652

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions spring-boot-project/spring-boot-dependencies/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ bom {
releaseNotes("https://github.com/eclipse-ee4j/angus-mail/releases/tag/{version}")
}
}
library("Armeria", "1.32.0") {
group("com.linecorp.armeria") {
imports = [
"armeria-bom"
]
}
links {
site("https://armeria.dev")
releaseNotes("https://armeria.dev/release-notes/{version}")
}
}
library("Artemis", "2.39.0") {
group("org.apache.activemq") {
imports = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,23 @@ public void start() throws WebServerException {
return;
}
this.server.setConnectors(this.connectors);
if (!this.autoStart) {
return;
}
try {
this.server.start();
for (Handler handler : this.server.getHandlers()) {
handleDeferredInitialize(handler);
}
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
try {
connector.start();
}
catch (IOException ex) {
if (connector instanceof NetworkConnector networkConnector) {
PortInUseException.throwIfPortBindingException(ex, networkConnector::getPort);
if (this.autoStart) {
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
try {
connector.start();
}
catch (IOException ex) {
if (connector instanceof NetworkConnector networkConnector) {
PortInUseException.throwIfPortBindingException(ex, networkConnector::getPort);
}
throw ex;
}
throw ex;
}
}
this.started = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id "java"
}

description = "Spring Boot Armeria Jetty smoke test"

dependencies {
implementation("com.linecorp.armeria:armeria")
implementation("com.linecorp.armeria:armeria-spring-boot3-autoconfigure")
implementation("com.linecorp.armeria:armeria-jetty12")

implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) {
exclude module: "spring-boot-starter-tomcat"
}
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jetty"))

testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.armeria.jetty;

import com.linecorp.armeria.common.HttpResponse;
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 org.eclipse.jetty.server.Server;

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;

/**
* 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.
*
* @author Dogac Eldenk
*/
@Configuration
public class ArmeriaConfiguration {

/**
* 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);
}

/**
* 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("/jetty", jettyService.decorate((delegate, ctx, req) -> {
ctx.addAdditionalResponseHeader("armeria-forwarded", "true");
return delegate.serve(ctx, req);
})).serviceUnder("/armeria", (ctx, req) -> HttpResponse.of("Hello from Armeria!"));
}

/**
* 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,29 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.armeria.jetty;

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

@SpringBootApplication
public class SampleArmeriaJettyApplication {

public static void main(String[] args) {
SpringApplication.run(SampleArmeriaJettyApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.armeria.jetty.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldService {

@Value("${test.name:World}")
private String name;

public String getHelloMessage() {
return "Hello " + this.name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.armeria.jetty.web;

import smoketest.armeria.jetty.service.HelloWorldService;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SampleController {

private final HelloWorldService helloWorldService;

public SampleController(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}

@GetMapping("/")
@ResponseBody
public String helloWorld() {
return this.helloWorldService.getHelloMessage();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Prevent the embedded Jetty 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
@@ -0,0 +1,68 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smoketest.armeria.jetty;

import com.linecorp.armeria.server.Server;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.DefaultUriBuilderFactory;

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

/**
* Basic integration tests for demo application.
*
* @author Dogac Eldenk
*/
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
class SampleArmeriaJettyApplicationTests {

@Autowired
private Server server;

@Autowired
private TestRestTemplate restTemplate;

@BeforeEach
void setup() {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://127.0.0.1:" + server.activeLocalPort());
this.restTemplate.getRestTemplate().setUriTemplateHandler(factory);
}

@Test
void testJetty() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/jetty", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("Hello World");
assertThat(entity.getHeaders().getFirst("armeria-forwarded")).isEqualTo("true");
}

@Test
void testArmeria() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/armeria", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("Hello from Armeria!");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
id "java"
}

description = "Spring Boot Armeria Tomcat smoke test"

dependencies {
implementation("com.linecorp.armeria:armeria")
implementation("com.linecorp.armeria:armeria-spring-boot3-autoconfigure")
implementation("com.linecorp.armeria:armeria-tomcat10")

implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-tomcat"))
implementation("org.springframework:spring-webmvc")

testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
}
Loading