Skip to content

Commit 1c14274

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. Several optimizations are included, including the optimization that small writes can be buffered in such a way to avoid chunked responses when possible. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12.
1 parent f292929 commit 1c14274

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpResponseImpl.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,7 @@ public synchronized BufferedWriter getWriter() throws IOException {
116116
*/
117117
public void close(Callback callback) {
118118
try {
119-
if (writer != null) {
120-
writer.flush();
121-
}
119+
// The writer has been constructed to do no buffering, so it does not need to be flushed
122120
if (contentSinkOutputStream != null) {
123121
// Do an asynchronous close, so large buffered content may be written without blocking
124122
contentSinkOutputStream.close(callback);

invoker/core/src/test/java/com/google/cloud/functions/invoker/IntegrationTest.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ abstract static class TestCase {
167167

168168
abstract int expectedResponseCode();
169169

170+
abstract Optional<Map<String, String>> expectedResponseHeaders();
171+
170172
abstract Optional<String> expectedResponseText();
171173

172174
abstract Optional<JsonObject> expectedJson();
@@ -186,6 +188,7 @@ static Builder builder() {
186188
.setUrl("/")
187189
.setRequestText("")
188190
.setExpectedResponseCode(HttpStatus.OK_200)
191+
.setExpectedResponseHeaders(ImmutableMap.of())
189192
.setExpectedResponseText("")
190193
.setHttpContentType("text/plain")
191194
.setHttpHeaders(ImmutableMap.of());
@@ -204,6 +207,8 @@ Builder setRequestText(String text) {
204207

205208
abstract Builder setExpectedResponseCode(int x);
206209

210+
abstract Builder setExpectedResponseHeaders(Map<String, String> x);
211+
207212
abstract Builder setExpectedResponseText(String x);
208213

209214
abstract Builder setExpectedResponseText(Optional<String> x);
@@ -249,11 +254,44 @@ public void helloWorld() throws Exception {
249254
testHttpFunction(
250255
fullTarget("HelloWorld"),
251256
ImmutableList.of(
252-
TestCase.builder().setExpectedResponseText("hello\n").build(),
257+
TestCase.builder()
258+
.setExpectedResponseText("hello\n")
259+
.setHttpHeaders(ImmutableMap.of(
260+
"Content-Length", "*"))
261+
.build(),
253262
FAVICON_TEST_CASE,
254263
ROBOTS_TXT_TEST_CASE));
255264
}
256265

266+
@Test
267+
public void bufferedWrites() throws Exception {
268+
// This test checks that writes are buffered and are written
269+
// in an efficient way with known content-length if possible.
270+
testHttpFunction(
271+
fullTarget("BufferedWrites"),
272+
ImmutableList.of(
273+
TestCase.builder()
274+
.setUrl("/target?writes=2")
275+
.setExpectedResponseText("write 0\nwrite 1\n")
276+
.setExpectedResponseHeaders(ImmutableMap.of(
277+
"x-write-0", "true",
278+
"x-write-1", "true",
279+
"x-written", "true",
280+
"Content-Length", "16"
281+
))
282+
.build(),
283+
TestCase.builder()
284+
.setUrl("/target?writes=2&flush=true")
285+
.setExpectedResponseText("write 0\nwrite 1\n")
286+
.setExpectedResponseHeaders(ImmutableMap.of(
287+
"x-write-0", "true",
288+
"x-write-1", "true",
289+
"x-written", "-",
290+
"Transfer-Encoding", "chunked"))
291+
.build()
292+
));
293+
}
294+
257295
@Test
258296
public void exceptionHttp() throws Exception {
259297
String exceptionExpectedOutput =
@@ -696,6 +734,17 @@ private void testFunction(
696734
.withMessage("Response to %s is %s %s", uri, response.getStatus(), response.getReason())
697735
.that(response.getStatus())
698736
.isEqualTo(testCase.expectedResponseCode());
737+
testCase.expectedResponseHeaders().ifPresent(map -> {
738+
for (Map.Entry<String, String> entry : map.entrySet()) {
739+
if ("*".equals(entry.getValue())) {
740+
expect.that(response.getHeaders().getFieldNamesCollection()).contains(entry.getKey());
741+
} else if ("-".equals(entry.getValue())) {
742+
expect.that(response.getHeaders().getFieldNamesCollection()).doesNotContain(entry.getKey());
743+
} else {
744+
expect.that(response.getHeaders().getValuesList(entry.getKey())).contains(entry.getValue());
745+
}
746+
}
747+
});
699748
testCase
700749
.expectedResponseText()
701750
.ifPresent(text -> expect.that(response.getContentAsString()).isEqualTo(text));
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.google.cloud.functions.invoker.testfunctions;
2+
3+
import com.google.cloud.functions.HttpFunction;
4+
import com.google.cloud.functions.HttpRequest;
5+
import com.google.cloud.functions.HttpResponse;
6+
import java.io.BufferedWriter;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public class BufferedWrites implements HttpFunction {
11+
@Override
12+
public void service(HttpRequest request, HttpResponse response) throws Exception {
13+
Map<String, List<String>> queryParameters = request.getQueryParameters();
14+
int writes = Integer.parseInt(request.getFirstQueryParameter("writes").orElse("0"));
15+
boolean flush = Boolean.parseBoolean(request.getFirstQueryParameter("flush").orElse("false"));
16+
17+
BufferedWriter writer = response.getWriter();
18+
for (int i = 0; i < writes; i++) {
19+
response.appendHeader("x-write-" + i, "true");
20+
writer.write("write " + i + "\n");
21+
}
22+
if (flush) {
23+
writer.flush();
24+
}
25+
response.appendHeader("x-written", "true");
26+
}
27+
}

0 commit comments

Comments
 (0)