Skip to content

Commit e23f98f

Browse files
chore(implementation): use Jetty-12.1 core without servlets (#333)
* chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts <[email protected]> --------- Signed-off-by: Lachlan Roberts <[email protected]>
1 parent 2297f3e commit e23f98f

File tree

14 files changed

+702
-386
lines changed

14 files changed

+702
-386
lines changed

invoker/core/pom.xml

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<maven.compiler.source>17</maven.compiler.source>
2424
<maven.compiler.target>17</maven.compiler.target>
2525
<cloudevents.sdk.version>4.0.1</cloudevents.sdk.version>
26+
<jetty.version>12.1.3</jetty.version>
2627
</properties>
2728

2829
<licenses>
@@ -46,11 +47,6 @@
4647
<artifactId>functions-framework-api</artifactId>
4748
<version>1.1.4</version>
4849
</dependency>
49-
<dependency>
50-
<groupId>javax.servlet</groupId>
51-
<artifactId>javax.servlet-api</artifactId>
52-
<version>4.0.1</version>
53-
</dependency>
5450
<dependency>
5551
<groupId>io.cloudevents</groupId>
5652
<artifactId>cloudevents-core</artifactId>
@@ -97,13 +93,13 @@
9793
</dependency>
9894
<dependency>
9995
<groupId>org.eclipse.jetty</groupId>
100-
<artifactId>jetty-servlet</artifactId>
101-
<version>9.4.58.v20250814</version>
96+
<artifactId>jetty-server</artifactId>
97+
<version>${jetty.version}</version>
10298
</dependency>
10399
<dependency>
104-
<groupId>org.eclipse.jetty</groupId>
105-
<artifactId>jetty-server</artifactId>
106-
<version>9.4.58.v20250814</version>
100+
<groupId>org.slf4j</groupId>
101+
<artifactId>slf4j-jdk14</artifactId>
102+
<version>2.0.9</version>
107103
</dependency>
108104
<dependency>
109105
<groupId>com.beust</groupId>
@@ -151,7 +147,7 @@
151147
<dependency>
152148
<groupId>org.eclipse.jetty</groupId>
153149
<artifactId>jetty-client</artifactId>
154-
<version>9.4.58.v20250814</version>
150+
<version>${jetty.version}</version>
155151
<scope>test</scope>
156152
</dependency>
157153
</dependencies>

invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,32 @@
3030
import io.cloudevents.http.HttpMessageFactory;
3131
import java.io.BufferedReader;
3232
import java.io.IOException;
33+
import java.io.InputStreamReader;
3334
import java.io.Reader;
3435
import java.lang.reflect.Type;
36+
import java.nio.charset.StandardCharsets;
3537
import java.time.OffsetDateTime;
3638
import java.time.format.DateTimeFormatter;
3739
import java.util.ArrayList;
3840
import java.util.Arrays;
39-
import java.util.Collections;
4041
import java.util.List;
4142
import java.util.Map;
43+
import java.util.Objects;
4244
import java.util.Optional;
4345
import java.util.TreeMap;
4446
import java.util.logging.Level;
4547
import java.util.logging.Logger;
46-
import javax.servlet.http.HttpServlet;
47-
import javax.servlet.http.HttpServletRequest;
48-
import javax.servlet.http.HttpServletResponse;
48+
import org.eclipse.jetty.http.HttpField;
49+
import org.eclipse.jetty.http.HttpHeader;
50+
import org.eclipse.jetty.http.HttpStatus;
51+
import org.eclipse.jetty.io.Content;
52+
import org.eclipse.jetty.server.Handler;
53+
import org.eclipse.jetty.server.Request;
54+
import org.eclipse.jetty.server.Response;
55+
import org.eclipse.jetty.util.Callback;
4956

5057
/** Executes the user's background function. */
51-
public final class BackgroundFunctionExecutor extends HttpServlet {
58+
public final class BackgroundFunctionExecutor extends Handler.Abstract {
5259
private static final Logger logger = Logger.getLogger("com.google.cloud.functions.invoker");
5360

5461
private final FunctionExecutor<?> functionExecutor;
@@ -177,8 +184,13 @@ static Optional<Type> backgroundFunctionTypeArgument(
177184
.findFirst();
178185
}
179186

180-
private static Event parseLegacyEvent(HttpServletRequest req) throws IOException {
181-
try (BufferedReader bodyReader = req.getReader()) {
187+
private static Event parseLegacyEvent(Request req) throws IOException {
188+
try (BufferedReader bodyReader =
189+
new BufferedReader(
190+
new InputStreamReader(
191+
Content.Source.asInputStream(req),
192+
Objects.requireNonNullElse(
193+
Request.getCharset(req), StandardCharsets.ISO_8859_1)))) {
182194
return parseLegacyEvent(bodyReader);
183195
}
184196
}
@@ -225,7 +237,7 @@ private static Context contextFromCloudEvent(CloudEvent cloudEvent) {
225237
* for the various triggers. CloudEvents are ones that follow the standards defined by <a
226238
* href="https://cloudevents.io">cloudevents.io</a>.
227239
*
228-
* @param <CloudEventDataT> the type to be used in the {@link Unmarshallers} call when
240+
* @param <CloudEventDataT> the type to be used in the {code Unmarshallers} call when
229241
* unmarshalling this event, if it is a CloudEvent.
230242
*/
231243
private abstract static class FunctionExecutor<CloudEventDataT> {
@@ -322,23 +334,25 @@ void serviceCloudEvent(CloudEvent cloudEvent) throws Exception {
322334

323335
/** Executes the user's background function. This can handle all HTTP methods. */
324336
@Override
325-
public void service(HttpServletRequest req, HttpServletResponse res) throws IOException {
326-
String contentType = req.getContentType();
337+
public boolean handle(Request req, Response res, Callback callback) throws Exception {
338+
String contentType = req.getHeaders().get(HttpHeader.CONTENT_TYPE);
327339
try {
328340
executionIdUtil.storeExecutionId(req);
329341
if ((contentType != null && contentType.startsWith("application/cloudevents+json"))
330-
|| req.getHeader("ce-specversion") != null) {
342+
|| req.getHeaders().get("ce-specversion") != null) {
331343
serviceCloudEvent(req);
332344
} else {
333345
serviceLegacyEvent(req);
334346
}
335-
res.setStatus(HttpServletResponse.SC_OK);
347+
res.setStatus(HttpStatus.OK_200);
348+
callback.succeeded();
336349
} catch (Throwable t) {
337-
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
338350
logger.log(Level.SEVERE, "Failed to execute " + functionExecutor.functionName(), t);
351+
Response.writeError(req, res, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, null);
339352
} finally {
340353
executionIdUtil.removeExecutionId();
341354
}
355+
return true;
342356
}
343357

344358
private enum CloudEventKind {
@@ -352,10 +366,14 @@ private enum CloudEventKind {
352366
* @param <CloudEventT> a fake type parameter, which corresponds to the type parameter of {@link
353367
* FunctionExecutor}.
354368
*/
355-
private <CloudEventT> void serviceCloudEvent(HttpServletRequest req) throws Exception {
369+
private <CloudEventT> void serviceCloudEvent(Request req) throws Exception {
356370
@SuppressWarnings("unchecked")
357371
FunctionExecutor<CloudEventT> executor = (FunctionExecutor<CloudEventT>) functionExecutor;
358-
byte[] body = req.getInputStream().readAllBytes();
372+
373+
// Read the entire request body into a byte array.
374+
// TODO: this method is deprecated for removal, use the method introduced by
375+
// https://github.com/jetty/jetty.project/pull/13939 when it is released.
376+
byte[] body = Content.Source.asByteArrayAsync(req, -1).get();
359377
MessageReader reader = HttpMessageFactory.createReaderFromMultimap(headerMap(req), body);
360378
// It's important not to set the context ClassLoader earlier, because MessageUtils will use
361379
// ServiceLoader.load(EventFormat.class) to find a handler to deserialize a binary CloudEvent
@@ -369,17 +387,17 @@ private <CloudEventT> void serviceCloudEvent(HttpServletRequest req) throws Exce
369387
// https://github.com/cloudevents/sdk-java/pull/259.
370388
}
371389

372-
private static Map<String, List<String>> headerMap(HttpServletRequest req) {
390+
private static Map<String, List<String>> headerMap(Request req) {
373391
Map<String, List<String>> headerMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
374-
for (String header : Collections.list(req.getHeaderNames())) {
375-
for (String value : Collections.list(req.getHeaders(header))) {
376-
headerMap.computeIfAbsent(header, unused -> new ArrayList<>()).add(value);
377-
}
392+
for (HttpField field : req.getHeaders()) {
393+
headerMap
394+
.computeIfAbsent(field.getName(), unused -> new ArrayList<>())
395+
.addAll(field.getValueList());
378396
}
379397
return headerMap;
380398
}
381399

382-
private void serviceLegacyEvent(HttpServletRequest req) throws Exception {
400+
private void serviceLegacyEvent(Request req) throws Exception {
383401
Event event = parseLegacyEvent(req);
384402
runWithContextClassLoader(() -> functionExecutor.serviceLegacyEvent(event));
385403
}

invoker/core/src/main/java/com/google/cloud/functions/invoker/HttpFunctionExecutor.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import com.google.cloud.functions.invoker.http.HttpResponseImpl;
2121
import java.util.logging.Level;
2222
import java.util.logging.Logger;
23-
import javax.servlet.http.HttpServlet;
24-
import javax.servlet.http.HttpServletRequest;
25-
import javax.servlet.http.HttpServletResponse;
23+
import org.eclipse.jetty.http.HttpStatus;
24+
import org.eclipse.jetty.server.Handler;
25+
import org.eclipse.jetty.server.Request;
26+
import org.eclipse.jetty.server.Response;
27+
import org.eclipse.jetty.util.Callback;
2628

2729
/** Executes the user's method. */
28-
public class HttpFunctionExecutor extends HttpServlet {
30+
public class HttpFunctionExecutor extends Handler.Abstract {
2931
private static final Logger logger = Logger.getLogger("com.google.cloud.functions.invoker");
3032

3133
private final HttpFunction function;
@@ -65,21 +67,23 @@ public static HttpFunctionExecutor forClass(Class<?> functionClass) {
6567

6668
/** Executes the user's method, can handle all HTTP type methods. */
6769
@Override
68-
public void service(HttpServletRequest req, HttpServletResponse res) {
69-
HttpRequestImpl reqImpl = new HttpRequestImpl(req);
70-
HttpResponseImpl respImpl = new HttpResponseImpl(res);
70+
public boolean handle(Request request, Response response, Callback callback) throws Exception {
71+
72+
HttpRequestImpl reqImpl = new HttpRequestImpl(request);
73+
HttpResponseImpl respImpl = new HttpResponseImpl(response);
7174
ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader();
7275
try {
73-
executionIdUtil.storeExecutionId(req);
76+
executionIdUtil.storeExecutionId(request);
7477
Thread.currentThread().setContextClassLoader(function.getClass().getClassLoader());
7578
function.service(reqImpl, respImpl);
79+
respImpl.close(callback);
7680
} catch (Throwable t) {
7781
logger.log(Level.SEVERE, "Failed to execute " + function.getClass().getName(), t);
78-
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
82+
Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, null);
7983
} finally {
8084
Thread.currentThread().setContextClassLoader(oldContextLoader);
8185
executionIdUtil.removeExecutionId();
82-
respImpl.flush();
8386
}
87+
return true;
8488
}
8589
}

invoker/core/src/main/java/com/google/cloud/functions/invoker/TypedFunctionExecutor.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
import java.util.Optional;
1616
import java.util.logging.Level;
1717
import java.util.logging.Logger;
18-
import javax.servlet.http.HttpServlet;
19-
import javax.servlet.http.HttpServletRequest;
20-
import javax.servlet.http.HttpServletResponse;
18+
import org.eclipse.jetty.http.HttpStatus;
19+
import org.eclipse.jetty.server.Handler;
20+
import org.eclipse.jetty.server.Request;
21+
import org.eclipse.jetty.server.Response;
22+
import org.eclipse.jetty.util.Callback;
2123

22-
public class TypedFunctionExecutor extends HttpServlet {
24+
public class TypedFunctionExecutor extends Handler.Abstract {
2325
private static final String APPLY_METHOD = "apply";
2426
private static final Logger logger = Logger.getLogger("com.google.cloud.functions.invoker");
2527

@@ -94,18 +96,21 @@ static Optional<Type> handlerTypeArgument(Class<? extends TypedFunction<?, ?>> f
9496

9597
/** Executes the user's method, can handle all HTTP type methods. */
9698
@Override
97-
public void service(HttpServletRequest req, HttpServletResponse res) {
99+
public boolean handle(Request req, Response res, Callback callback) throws Exception {
98100
HttpRequestImpl reqImpl = new HttpRequestImpl(req);
99101
HttpResponseImpl resImpl = new HttpResponseImpl(res);
100102
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
101103

102104
try {
103105
Thread.currentThread().setContextClassLoader(function.getClass().getClassLoader());
104106
handleRequest(reqImpl, resImpl);
107+
resImpl.close(callback);
108+
} catch (Throwable t) {
109+
Response.writeError(req, res, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, null, t);
105110
} finally {
106111
Thread.currentThread().setContextClassLoader(oldContextClassLoader);
107-
resImpl.flush();
108112
}
113+
return true;
109114
}
110115

111116
private void handleRequest(HttpRequest req, HttpResponse res) {
@@ -114,7 +119,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) {
114119
reqObj = format.deserialize(req, argType);
115120
} catch (Throwable t) {
116121
logger.log(Level.SEVERE, "Failed to parse request for " + function.getClass().getName(), t);
117-
res.setStatusCode(HttpServletResponse.SC_BAD_REQUEST);
122+
res.setStatusCode(HttpStatus.BAD_REQUEST_400);
118123
return;
119124
}
120125

@@ -123,7 +128,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) {
123128
resObj = function.apply(reqObj);
124129
} catch (Throwable t) {
125130
logger.log(Level.SEVERE, "Failed to execute " + function.getClass().getName(), t);
126-
res.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
131+
res.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
127132
return;
128133
}
129134

@@ -132,7 +137,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) {
132137
} catch (Throwable t) {
133138
logger.log(
134139
Level.SEVERE, "Failed to serialize response for " + function.getClass().getName(), t);
135-
res.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
140+
res.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
136141
return;
137142
}
138143
}
@@ -147,7 +152,7 @@ private static class GsonWireFormat implements TypedFunction.WireFormat {
147152
@Override
148153
public void serialize(Object object, HttpResponse response) throws Exception {
149154
if (object == null) {
150-
response.setStatusCode(HttpServletResponse.SC_NO_CONTENT);
155+
response.setStatusCode(HttpStatus.NO_CONTENT_204);
151156
return;
152157
}
153158
try (BufferedWriter bodyWriter = response.getWriter()) {

invoker/core/src/main/java/com/google/cloud/functions/invoker/gcf/ExecutionIdUtil.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.util.concurrent.ThreadLocalRandom;
66
import java.util.logging.Handler;
77
import java.util.logging.Logger;
8-
import javax.servlet.http.HttpServletRequest;
8+
import org.eclipse.jetty.server.Request;
99

1010
/**
1111
* A helper class that either fetches a unique execution id from request HTTP headers or generates a
@@ -23,7 +23,7 @@ public final class ExecutionIdUtil {
2323
* Add mapping to root logger from current thread id to execution id. This mapping will be used to
2424
* append the execution id to log lines.
2525
*/
26-
public void storeExecutionId(HttpServletRequest request) {
26+
public void storeExecutionId(Request request) {
2727
if (!executionIdLoggingEnabled()) {
2828
return;
2929
}
@@ -47,8 +47,8 @@ public void removeExecutionId() {
4747
}
4848
}
4949

50-
private String getOrGenerateExecutionId(HttpServletRequest request) {
51-
String executionId = request.getHeader(EXECUTION_ID_HTTP_HEADER);
50+
private String getOrGenerateExecutionId(Request request) {
51+
String executionId = request.getHeaders().get(EXECUTION_ID_HTTP_HEADER);
5252
if (executionId == null) {
5353
byte[] array = new byte[EXECUTION_ID_LENGTH];
5454
random.nextBytes(array);

0 commit comments

Comments
 (0)