Skip to content

Commit 5328718

Browse files
committed
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.
1 parent 7df8be8 commit 5328718

File tree

9 files changed

+357
-258
lines changed

9 files changed

+357
-258
lines changed

invoker/core/pom.xml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
<properties>
2121
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2222
<junit.jupiter.version>5.3.2</junit.jupiter.version>
23-
<maven.compiler.source>11</maven.compiler.source>
24-
<maven.compiler.target>11</maven.compiler.target>
23+
<maven.compiler.source>17</maven.compiler.source>
24+
<maven.compiler.target>17</maven.compiler.target>
2525
<cloudevents.sdk.version>2.5.0</cloudevents.sdk.version>
26+
<jetty.version>12.0.2-SNAPSHOT</jetty.version>
2627
</properties>
2728

2829
<licenses>
@@ -46,11 +47,6 @@
4647
<artifactId>functions-framework-api</artifactId>
4748
<version>1.1.0</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.52.v20230823</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.52.v20230823</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.52.v20230823</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: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,32 @@
2929
import io.cloudevents.http.HttpMessageFactory;
3030
import java.io.BufferedReader;
3131
import java.io.IOException;
32+
import java.io.InputStreamReader;
3233
import java.io.Reader;
3334
import java.lang.reflect.Type;
35+
import java.nio.charset.StandardCharsets;
3436
import java.time.OffsetDateTime;
3537
import java.time.format.DateTimeFormatter;
3638
import java.util.ArrayList;
3739
import java.util.Arrays;
38-
import java.util.Collections;
3940
import java.util.List;
4041
import java.util.Map;
42+
import java.util.Objects;
4143
import java.util.Optional;
4244
import java.util.TreeMap;
4345
import java.util.logging.Level;
4446
import java.util.logging.Logger;
45-
import javax.servlet.http.HttpServlet;
46-
import javax.servlet.http.HttpServletRequest;
47-
import javax.servlet.http.HttpServletResponse;
47+
import org.eclipse.jetty.http.HttpField;
48+
import org.eclipse.jetty.http.HttpHeader;
49+
import org.eclipse.jetty.http.HttpStatus;
50+
import org.eclipse.jetty.io.Content;
51+
import org.eclipse.jetty.server.Handler;
52+
import org.eclipse.jetty.server.Request;
53+
import org.eclipse.jetty.server.Response;
54+
import org.eclipse.jetty.util.Callback;
4855

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

5360
private final FunctionExecutor<?> functionExecutor;
@@ -175,8 +182,10 @@ static Optional<Type> backgroundFunctionTypeArgument(
175182
.findFirst();
176183
}
177184

178-
private static Event parseLegacyEvent(HttpServletRequest req) throws IOException {
179-
try (BufferedReader bodyReader = req.getReader()) {
185+
private static Event parseLegacyEvent(Request req) throws IOException {
186+
try (BufferedReader bodyReader = new BufferedReader(
187+
new InputStreamReader(Content.Source.asInputStream(req),
188+
Objects.requireNonNullElse(Request.getCharset(req), StandardCharsets.ISO_8859_1)))) {
180189
return parseLegacyEvent(bodyReader);
181190
}
182191
}
@@ -223,7 +232,7 @@ private static Context contextFromCloudEvent(CloudEvent cloudEvent) {
223232
* for the various triggers. CloudEvents are ones that follow the standards defined by <a
224233
* href="https://cloudevents.io">cloudevents.io</a>.
225234
*
226-
* @param <CloudEventDataT> the type to be used in the {@link Unmarshallers} call when
235+
* @param <CloudEventDataT> the type to be used in the {code Unmarshallers} call when
227236
* unmarshalling this event, if it is a CloudEvent.
228237
*/
229238
private abstract static class FunctionExecutor<CloudEventDataT> {
@@ -320,20 +329,23 @@ void serviceCloudEvent(CloudEvent cloudEvent) throws Exception {
320329

321330
/** Executes the user's background function. This can handle all HTTP methods. */
322331
@Override
323-
public void service(HttpServletRequest req, HttpServletResponse res) throws IOException {
324-
String contentType = req.getContentType();
332+
public boolean handle(Request req, Response res, Callback callback) throws Exception {
333+
String contentType = req.getHeaders().get(HttpHeader.CONTENT_TYPE);
325334
try {
326335
if ((contentType != null && contentType.startsWith("application/cloudevents+json"))
327-
|| req.getHeader("ce-specversion") != null) {
336+
|| req.getHeaders().get("ce-specversion") != null) {
328337
serviceCloudEvent(req);
329338
} else {
330339
serviceLegacyEvent(req);
331340
}
332-
res.setStatus(HttpServletResponse.SC_OK);
341+
res.setStatus(HttpStatus.OK_200);
342+
callback.succeeded();
333343
} catch (Throwable t) {
334-
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
335344
logger.log(Level.SEVERE, "Failed to execute " + functionExecutor.functionName(), t);
345+
res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
346+
callback.succeeded();
336347
}
348+
return true;
337349
}
338350

339351
private enum CloudEventKind {
@@ -347,10 +359,11 @@ private enum CloudEventKind {
347359
* @param <CloudEventT> a fake type parameter, which corresponds to the type parameter of {@link
348360
* FunctionExecutor}.
349361
*/
350-
private <CloudEventT> void serviceCloudEvent(HttpServletRequest req) throws Exception {
362+
private <CloudEventT> void serviceCloudEvent(Request req) throws Exception {
351363
@SuppressWarnings("unchecked")
352364
FunctionExecutor<CloudEventT> executor = (FunctionExecutor<CloudEventT>) functionExecutor;
353-
byte[] body = req.getInputStream().readAllBytes();
365+
366+
byte[] body = Content.Source.asByteArrayAsync(req, -1).get();
354367
MessageReader reader = HttpMessageFactory.createReaderFromMultimap(headerMap(req), body);
355368
// It's important not to set the context ClassLoader earlier, because MessageUtils will use
356369
// ServiceLoader.load(EventFormat.class) to find a handler to deserialize a binary CloudEvent
@@ -364,17 +377,16 @@ private <CloudEventT> void serviceCloudEvent(HttpServletRequest req) throws Exce
364377
// https://github.com/cloudevents/sdk-java/pull/259.
365378
}
366379

367-
private static Map<String, List<String>> headerMap(HttpServletRequest req) {
380+
private static Map<String, List<String>> headerMap(Request req) {
368381
Map<String, List<String>> headerMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
369-
for (String header : Collections.list(req.getHeaderNames())) {
370-
for (String value : Collections.list(req.getHeaders(header))) {
371-
headerMap.computeIfAbsent(header, unused -> new ArrayList<>()).add(value);
372-
}
382+
for (HttpField field : req.getHeaders()) {
383+
headerMap.computeIfAbsent(field.getName(), unused -> new ArrayList<>())
384+
.addAll(field.getValueList());
373385
}
374386
return headerMap;
375387
}
376388

377-
private void serviceLegacyEvent(HttpServletRequest req) throws Exception {
389+
private void serviceLegacyEvent(Request req) throws Exception {
378390
Event event = parseLegacyEvent(req);
379391
runWithContextClassLoader(() -> functionExecutor.serviceLegacyEvent(event));
380392
}

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

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

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

3032
private final HttpFunction function;
@@ -59,19 +61,28 @@ public static HttpFunctionExecutor forClass(Class<?> functionClass) {
5961

6062
/** Executes the user's method, can handle all HTTP type methods. */
6163
@Override
62-
public void service(HttpServletRequest req, HttpServletResponse res) {
63-
HttpRequestImpl reqImpl = new HttpRequestImpl(req);
64-
HttpResponseImpl respImpl = new HttpResponseImpl(res);
64+
public boolean handle(Request request, Response response, Callback callback) throws Exception {
65+
66+
HttpRequestImpl reqImpl = new HttpRequestImpl(request);
67+
HttpResponseImpl respImpl = new HttpResponseImpl(response);
6568
ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader();
6669
try {
6770
Thread.currentThread().setContextClassLoader(function.getClass().getClassLoader());
6871
function.service(reqImpl, respImpl);
72+
respImpl.close();
73+
callback.succeeded();
6974
} catch (Throwable t) {
7075
logger.log(Level.SEVERE, "Failed to execute " + function.getClass().getName(), t);
71-
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
76+
if (response.isCommitted()) {
77+
callback.failed(t);
78+
} else {
79+
response.reset();
80+
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
81+
callback.succeeded();
82+
}
7283
} finally {
7384
Thread.currentThread().setContextClassLoader(oldContextLoader);
74-
respImpl.flush();
7585
}
86+
return true;
7687
}
7788
}

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

Lines changed: 22 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,28 @@ 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();
108+
callback.succeeded();
109+
} catch (Throwable t) {
110+
if (res.isCommitted()) {
111+
callback.failed(t);
112+
} else {
113+
res.reset();
114+
res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
115+
callback.succeeded();
116+
}
105117
} finally {
106118
Thread.currentThread().setContextClassLoader(oldContextClassLoader);
107-
resImpl.flush();
108119
}
120+
return true;
109121
}
110122

111123
private void handleRequest(HttpRequest req, HttpResponse res) {
@@ -114,7 +126,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) {
114126
reqObj = format.deserialize(req, argType);
115127
} catch (Throwable t) {
116128
logger.log(Level.SEVERE, "Failed to parse request for " + function.getClass().getName(), t);
117-
res.setStatusCode(HttpServletResponse.SC_BAD_REQUEST);
129+
res.setStatusCode(HttpStatus.BAD_REQUEST_400);
118130
return;
119131
}
120132

@@ -123,7 +135,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) {
123135
resObj = function.apply(reqObj);
124136
} catch (Throwable t) {
125137
logger.log(Level.SEVERE, "Failed to execute " + function.getClass().getName(), t);
126-
res.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
138+
res.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
127139
return;
128140
}
129141

@@ -132,7 +144,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) {
132144
} catch (Throwable t) {
133145
logger.log(
134146
Level.SEVERE, "Failed to serialize response for " + function.getClass().getName(), t);
135-
res.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
147+
res.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
136148
return;
137149
}
138150
}
@@ -147,7 +159,7 @@ private static class GsonWireFormat implements TypedFunction.WireFormat {
147159
@Override
148160
public void serialize(Object object, HttpResponse response) throws Exception {
149161
if (object == null) {
150-
response.setStatusCode(HttpServletResponse.SC_NO_CONTENT);
162+
response.setStatusCode(HttpStatus.NO_CONTENT_204);
151163
return;
152164
}
153165
try (BufferedWriter bodyWriter = response.getWriter()) {

0 commit comments

Comments
 (0)