Skip to content

Commit 3994241

Browse files
authored
Stop trying to guess a port for Jetty in http-tests (#1008)
This changes how we generate a port for Jetty in the http-tests. Previously we tried to guess one then tried to start jetty on it in a retry loop. After this change, we just let Jetty pick a port by assigning it 0. This wasn't an issue in this module, but it has proved to be a source of flakiness in Azure, so I'm doing this for consistency
1 parent 874a2bc commit 3994241

File tree

1 file changed

+69
-80
lines changed

1 file changed

+69
-80
lines changed

gcp-function-http-test/src/main/java/io/micronaut/gcp/function/http/test/InvokerHttpServer.java

+69-80
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2020 original authors
2+
* Copyright 2017-2023 original authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,13 +20,13 @@
2020
import io.micronaut.core.annotation.Experimental;
2121
import io.micronaut.core.annotation.Internal;
2222
import io.micronaut.core.annotation.Nullable;
23-
import io.micronaut.core.io.socket.SocketUtils;
2423
import io.micronaut.gcp.function.http.HttpFunction;
2524
import io.micronaut.http.server.HttpServerConfiguration;
2625
import io.micronaut.http.server.exceptions.HttpServerException;
2726
import io.micronaut.http.server.exceptions.ServerStartupException;
2827
import io.micronaut.runtime.ApplicationConfiguration;
2928
import io.micronaut.runtime.server.EmbeddedServer;
29+
import jakarta.inject.Singleton;
3030
import jakarta.servlet.MultipartConfigElement;
3131
import jakarta.servlet.ServletException;
3232
import jakarta.servlet.http.HttpServlet;
@@ -41,10 +41,11 @@
4141
import org.slf4j.Logger;
4242
import org.slf4j.LoggerFactory;
4343

44-
import jakarta.inject.Singleton;
45-
4644
import java.io.IOException;
47-
import java.net.*;
45+
import java.net.MalformedURLException;
46+
import java.net.URI;
47+
import java.net.URISyntaxException;
48+
import java.net.URL;
4849
import java.util.Arrays;
4950
import java.util.HashSet;
5051
import java.util.Optional;
@@ -63,95 +64,85 @@
6364
@Internal
6465
@Experimental
6566
public class InvokerHttpServer implements EmbeddedServer {
67+
6668
private static final Logger LOGGER = LoggerFactory.getLogger(InvokerHttpServer.class);
6769
private final ApplicationContext applicationContext;
68-
private final HttpServerConfiguration serverConfiguration;
69-
private final boolean randomPort;
70+
private final ServerPort serverPort;
7071
private final AtomicBoolean running = new AtomicBoolean(false);
71-
private int port;
7272
private Server server;
73+
private HttpFunction httpFunction;
7374

74-
public InvokerHttpServer(ApplicationContext applicationContext, HttpServerConfiguration serverConfiguration) {
75+
public InvokerHttpServer(ApplicationContext applicationContext, HttpServerConfiguration httpServerConfiguration) {
7576
this.applicationContext = applicationContext;
76-
this.serverConfiguration = serverConfiguration;
77-
Optional<Integer> port = serverConfiguration.getPort();
78-
if (port.isPresent()) {
79-
this.port = port.get();
80-
if (this.port == -1) {
81-
this.port = SocketUtils.findAvailableTcpPort();
82-
this.randomPort = true;
77+
this.serverPort = createServerPort(httpServerConfiguration);
78+
}
79+
80+
private ServerPort createServerPort(HttpServerConfiguration httpServerConfiguration) {
81+
Optional<Integer> portOpt = httpServerConfiguration.getPort();
82+
if (portOpt.isPresent()) {
83+
Integer port = portOpt.get();
84+
if (port == -1) {
85+
return new ServerPort(true, 0);
86+
8387
} else {
84-
this.randomPort = false;
88+
return new ServerPort(false, port);
8589
}
8690
} else {
8791
if (applicationContext.getEnvironment().getActiveNames().contains(Environment.TEST)) {
88-
this.randomPort = true;
89-
this.port = SocketUtils.findAvailableTcpPort();
92+
return new ServerPort(true, 0);
9093
} else {
91-
this.randomPort = false;
92-
this.port = 8080;
94+
return new ServerPort(false, 8080);
9395
}
9496
}
9597
}
9698

9799
@Override
98100
public EmbeddedServer start() {
99101
if (running.compareAndSet(false, true)) {
100-
int retryCount = 0;
101-
while (retryCount <= 3) {
102-
try {
103-
this.server = new Server(port);
104-
105-
ServletContextHandler servletContextHandler = new ServletContextHandler();
106-
servletContextHandler.setContextPath("/");
107-
server.setHandler(NotFoundHandler.forServlet(servletContextHandler));
102+
int port = serverPort.port();
103+
try {
104+
this.server = new Server(port);
105+
106+
ServletContextHandler servletContextHandler = new ServletContextHandler();
107+
servletContextHandler.setContextPath("/");
108+
server.setHandler(NotFoundHandler.forServlet(servletContextHandler));
109+
110+
httpFunction = new HttpFunction() {
111+
@Override
112+
protected ApplicationContext buildApplicationContext(@Nullable Object context) {
113+
ApplicationContext ctx = InvokerHttpServer.this.getApplicationContext();
114+
this.applicationContext = ctx;
115+
return ctx;
116+
}
117+
};
118+
ServletHolder servletHolder = getServletHolder();
119+
servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(""));
120+
servletContextHandler.addServlet(servletHolder, "/*");
108121

109-
HttpFunction httpFunction = new HttpFunction() {
110-
@Override
111-
protected ApplicationContext buildApplicationContext(@Nullable Object context) {
112-
ApplicationContext ctx = InvokerHttpServer.this.getApplicationContext();
113-
this.applicationContext = ctx;
114-
return ctx;
115-
}
116-
};
117-
HttpServlet servlet = new HttpServlet() {
118-
@Override
119-
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
122+
this.server.start();
123+
} catch (Exception e) {
124+
throw new ServerStartupException(e.getMessage(), e);
125+
}
126+
}
127+
return this;
128+
}
120129

121-
try {
122-
httpFunction.service(
123-
new HttpRequestImpl(req),
124-
new HttpResponseImpl(resp)
125-
);
126-
} catch (Exception e) {
127-
throw new ServletException(e);
128-
}
129-
}
130-
};
131-
ServletHolder servletHolder = new ServletHolder(servlet);
132-
servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(""));
133-
servletContextHandler.addServlet(servletHolder, "/*");
130+
private ServletHolder getServletHolder() {
131+
HttpServlet servlet = new HttpServlet() {
134132

135-
server.start();
136-
logServerInfo();
137-
break;
138-
} catch (BindException e) {
139-
if (randomPort) {
140-
this.port = SocketUtils.findAvailableTcpPort();
141-
retryCount++;
142-
} else {
143-
throw new ServerStartupException(e.getMessage(), e);
144-
}
145-
} catch (Exception e) {
146-
throw new ServerStartupException(
147-
"Error starting Google Cloud Function server: " + e.getMessage(),
148-
e
133+
@Override
134+
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
135+
try {
136+
httpFunction.service(
137+
new HttpRequestImpl(req),
138+
new HttpResponseImpl(resp)
149139
);
140+
} catch (Exception e) {
141+
throw new ServletException(e);
150142
}
151143
}
152-
153-
}
154-
return this;
144+
};
145+
return new ServletHolder(servlet);
155146
}
156147

157148
/**
@@ -167,6 +158,7 @@ protected Class<?> getFunctionClass() {
167158
public EmbeddedServer stop() {
168159
if (running.compareAndSet(true, false)) {
169160
try {
161+
httpFunction.close();
170162
applicationContext.close();
171163
server.stop();
172164
} catch (Exception e) {
@@ -181,7 +173,7 @@ public EmbeddedServer stop() {
181173

182174
@Override
183175
public int getPort() {
184-
return port;
176+
return server.getURI().getPort();
185177
}
186178

187179
@Override
@@ -220,29 +212,23 @@ public ApplicationContext getApplicationContext() {
220212

221213
@Override
222214
public ApplicationConfiguration getApplicationConfiguration() {
223-
return serverConfiguration.getApplicationConfiguration();
215+
return applicationContext.getBean(ApplicationConfiguration.class);
224216
}
225217

226218
@Override
227219
public boolean isRunning() {
228220
return running.get();
229221
}
230222

231-
private void logServerInfo() {
232-
LOGGER.info("Serving function...");
233-
LOGGER.info("Function: {}", getFunctionClass().getName());
234-
LOGGER.info("URL: http://localhost:{}/", port);
235-
}
236-
237223
/**
238224
* Wrapper that intercepts requests for {@code /favicon.ico} and {@code /robots.txt} and causes
239225
* them to produce a 404 status. Otherwise they would be sent to the function code, like any
240226
* other URL, meaning that someone testing their function by using a browser as an HTTP client
241227
* can see two requests, one for {@code /favicon.ico} and one for {@code /} (or whatever).
242228
*/
243229
private static class NotFoundHandler extends HandlerWrapper {
244-
private static final Set<String> NOT_FOUND_PATHS =
245-
new HashSet<>(Arrays.asList("/favicon.ico", "/robots.txt"));
230+
231+
private static final Set<String> NOT_FOUND_PATHS = new HashSet<>(Arrays.asList("/favicon.ico", "/robots.txt"));
246232

247233
@Override
248234
public void handle(String target, Request baseRequest, HttpServletRequest request,
@@ -260,4 +246,7 @@ static NotFoundHandler forServlet(ServletContextHandler servletHandler) {
260246
return handler;
261247
}
262248
}
249+
250+
private record ServerPort(boolean random, Integer port) {
251+
}
263252
}

0 commit comments

Comments
 (0)