Skip to content

Commit 0907814

Browse files
Fix multipart support for HTTP functions. (#27)
Jetty requires a servlet to be configured explicitly before it will accept multipart input.
1 parent 33a8928 commit 0907814

File tree

3 files changed

+75
-6
lines changed

3 files changed

+75
-6
lines changed

invoker/core/src/main/java/com/google/cloud/functions/invoker/runner/Invoker.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import java.util.logging.Level;
5656
import java.util.logging.Logger;
5757
import java.util.stream.Stream;
58+
import javax.servlet.MultipartConfigElement;
5859
import javax.servlet.ServletException;
5960
import javax.servlet.http.HttpServlet;
6061
import javax.servlet.http.HttpServletRequest;
@@ -284,7 +285,9 @@ public void startServer() throws Exception {
284285
functionSignatureType);
285286
throw new RuntimeException(error);
286287
}
287-
servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
288+
ServletHolder servletHolder = new ServletHolder(servlet);
289+
servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(""));
290+
servletContextHandler.addServlet(servletHolder, "/*");
288291

289292
server.start();
290293
logServerInfo();

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

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,13 @@
5959
import java.util.concurrent.TimeUnit;
6060
import java.util.regex.Pattern;
6161
import org.eclipse.jetty.client.HttpClient;
62+
import org.eclipse.jetty.client.api.ContentProvider;
6263
import org.eclipse.jetty.client.api.ContentResponse;
6364
import org.eclipse.jetty.client.api.Request;
65+
import org.eclipse.jetty.client.util.BytesContentProvider;
66+
import org.eclipse.jetty.client.util.MultiPartContentProvider;
6467
import org.eclipse.jetty.client.util.StringContentProvider;
68+
import org.eclipse.jetty.http.HttpFields;
6569
import org.eclipse.jetty.http.HttpHeader;
6670
import org.eclipse.jetty.http.HttpStatus;
6771
import org.junit.BeforeClass;
@@ -150,7 +154,7 @@ abstract static class TestCase {
150154

151155
abstract String url();
152156

153-
abstract String requestText();
157+
abstract ContentProvider requestContent();
154158

155159
abstract int expectedResponseCode();
156160

@@ -162,7 +166,7 @@ abstract static class TestCase {
162166

163167
abstract Optional<String> expectedOutput();
164168

165-
abstract String httpContentType();
169+
abstract Optional<String> httpContentType();
166170

167171
abstract ImmutableMap<String, String> httpHeaders();
168172

@@ -183,7 +187,11 @@ abstract static class Builder {
183187

184188
abstract Builder setUrl(String x);
185189

186-
abstract Builder setRequestText(String x);
190+
abstract Builder setRequestContent(ContentProvider x);
191+
192+
Builder setRequestText(String text) {
193+
return setRequestContent(new StringContentProvider(text));
194+
}
187195

188196
abstract Builder setExpectedResponseCode(int x);
189197

@@ -199,6 +207,8 @@ abstract static class Builder {
199207

200208
abstract Builder setHttpContentType(String x);
201209

210+
abstract Builder setHttpContentType(Optional<String> x);
211+
202212
abstract Builder setHttpHeaders(ImmutableMap<String, String> x);
203213

204214
abstract Builder setSnoopFile(File x);
@@ -408,6 +418,25 @@ public void packageless() throws Exception {
408418
ImmutableList.of(TestCase.builder().setExpectedResponseText("hello, world\n").build()));
409419
}
410420

421+
@Test
422+
public void multipart() throws Exception {
423+
MultiPartContentProvider multiPartProvider = new MultiPartContentProvider();
424+
byte[] bytes = new byte[17];
425+
multiPartProvider.addFieldPart("bytes", new BytesContentProvider(bytes), new HttpFields());
426+
String string = "1234567890";
427+
multiPartProvider.addFieldPart("string", new StringContentProvider(string), new HttpFields());
428+
String expectedResponse = "part bytes type application/octet-stream length 17\n"
429+
+ "part string type text/plain;charset=UTF-8 length 10\n";
430+
testHttpFunction(
431+
fullTarget("Multipart"),
432+
ImmutableList.of(
433+
TestCase.builder()
434+
.setHttpContentType(Optional.empty())
435+
.setRequestContent(multiPartProvider)
436+
.setExpectedResponseText(expectedResponse)
437+
.build()));
438+
}
439+
411440
private File snoopFile() throws IOException {
412441
return temporaryFolder.newFile(testName.getMethodName() + ".txt");
413442
}
@@ -522,9 +551,10 @@ private void testFunction(
522551
testCase.snoopFile().ifPresent(File::delete);
523552
String uri = "http://localhost:" + serverPort + testCase.url();
524553
Request request = httpClient.POST(uri);
525-
request.header(HttpHeader.CONTENT_TYPE, testCase.httpContentType());
554+
testCase.httpContentType().ifPresent(
555+
contentType -> request.header(HttpHeader.CONTENT_TYPE, contentType));
526556
testCase.httpHeaders().forEach((header, value) -> request.header(header, value));
527-
request.content(new StringContentProvider(testCase.requestText()));
557+
request.content(testCase.requestContent());
528558
ContentResponse response = request.send();
529559
expect
530560
.withMessage("Response to %s is %s %s", uri, response.getStatus(), response.getReason())
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.HttpRequest.HttpPart;
6+
import com.google.cloud.functions.HttpResponse;
7+
import java.io.PrintWriter;
8+
import java.util.NavigableMap;
9+
import java.util.TreeMap;
10+
11+
/**
12+
* A simple proof-of-concept function for multipart handling.
13+
*
14+
* {@code HttpFunctionTest} contains more detailed testing, but this function is part of the
15+
* integration test that shows that we can indeed access the multipart API from a function.
16+
*/
17+
public class Multipart implements HttpFunction {
18+
@Override
19+
public void service(HttpRequest request, HttpResponse response) throws Exception {
20+
response.setContentType("text/plain");
21+
String contentType = request.getContentType().orElse("<unknown>");
22+
if (!contentType.startsWith("multipart/form-data")) {
23+
response.getWriter().write("Content-Type is " + contentType + " not multipart/form-data");
24+
return;
25+
}
26+
PrintWriter writer = new PrintWriter(response.getWriter());
27+
NavigableMap<String, HttpPart> parts = new TreeMap<>(request.getParts());
28+
parts.forEach(
29+
(name, contents) -> {
30+
writer.printf(
31+
"part %s type %s length %d\n",
32+
name, contents.getContentType().get(), contents.getContentLength());
33+
}
34+
);
35+
}
36+
}

0 commit comments

Comments
 (0)