Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.

Commit 5e3808a

Browse files
authored
support multipart/form-data requests (#168)
1 parent 413e434 commit 5e3808a

File tree

4 files changed

+70
-8
lines changed

4 files changed

+70
-8
lines changed

endpoints-framework/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ dependencies {
9696
exclude group: 'com.google.guava', module: 'guava-jdk5'
9797
}
9898
compile group: 'com.google.code.findbugs', name: 'jsr305', version: findbugsVersion
99+
compile group: 'commons-fileupload', name: 'commons-fileupload', version: fileUploadVersion
99100
compile group: 'io.swagger', name: 'swagger-models', version: swaggerVersion
100101
compile group: 'io.swagger', name: 'swagger-core', version: swaggerVersion
101102
compile group: 'org.slf4j', name: 'slf4j-nop', version: slf4jVersion

endpoints-framework/src/main/java/com/google/api/server/spi/request/RestServletRequestParamReader.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@
3131
import com.fasterxml.jackson.databind.node.ArrayNode;
3232
import com.fasterxml.jackson.databind.node.ObjectNode;
3333

34+
import org.apache.commons.fileupload.FileItemIterator;
35+
import org.apache.commons.fileupload.FileItemStream;
36+
import org.apache.commons.fileupload.FileUploadException;
37+
import org.apache.commons.fileupload.servlet.ServletFileUpload;
38+
3439
import java.io.IOException;
3540
import java.lang.reflect.InvocationTargetException;
36-
import java.util.Collection;
3741
import java.util.Enumeration;
3842
import java.util.List;
3943
import java.util.Map;
@@ -83,13 +87,35 @@ public Object[] read() throws ServiceException {
8387
return new Object[0];
8488
}
8589
HttpServletRequest servletRequest = endpointsContext.getRequest();
86-
String requestBody = IoUtil.readRequestBody(servletRequest);
87-
logger.log(Level.FINE, "requestBody=" + requestBody);
88-
// Unlike the Lily protocol, which essentially always requires a JSON body to exist (due to
89-
// path and query parameters being injected into the body), bodies are optional here, so we
90-
// create an empty body and inject named parameters to make deserialize work.
91-
JsonNode node = Strings.isEmptyOrWhitespace(requestBody) ? objectReader.createObjectNode()
92-
: objectReader.readTree(requestBody);
90+
JsonNode node;
91+
// multipart/form-data requests can be used for requests which have no resource body. In
92+
// this case, each part represents a named parameter instead.
93+
if (ServletFileUpload.isMultipartContent(servletRequest)) {
94+
try {
95+
ServletFileUpload upload = new ServletFileUpload();
96+
FileItemIterator iter = upload.getItemIterator(servletRequest);
97+
ObjectNode obj = (ObjectNode) objectReader.createObjectNode();
98+
while (iter.hasNext()) {
99+
FileItemStream item = iter.next();
100+
if (item.isFormField()) {
101+
obj.put(item.getFieldName(), IoUtil.readStream(item.openStream()));
102+
} else {
103+
throw new BadRequestException("unable to parse multipart form field");
104+
}
105+
}
106+
node = obj;
107+
} catch (FileUploadException e) {
108+
throw new BadRequestException("unable to parse multipart request", e);
109+
}
110+
} else {
111+
String requestBody = IoUtil.readRequestBody(servletRequest);
112+
logger.log(Level.FINE, "requestBody=" + requestBody);
113+
// Unlike the Lily protocol, which essentially always requires a JSON body to exist (due to
114+
// path and query parameters being injected into the body), bodies are optional here, so we
115+
// create an empty body and inject named parameters to make deserialize work.
116+
node = Strings.isEmptyOrWhitespace(requestBody) ? objectReader.createObjectNode()
117+
: objectReader.readTree(requestBody);
118+
}
93119
if (!node.isObject()) {
94120
throw new BadRequestException("expected a JSON object body");
95121
}

endpoints-framework/src/test/java/com/google/api/server/spi/request/RestServletRequestParamReaderTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,31 @@ public void arrayPathParam() throws Exception {
204204
.containsExactly(ImmutableList.of("4", "3", "2", "1"));
205205
}
206206

207+
@Test
208+
public void multipartFormData() throws Exception {
209+
endpointMethod = EndpointMethod.create(TestApi.class,
210+
TestApi.class.getMethod("testFormData", String.class, Integer.class));
211+
methodConfig = apiConfig.getApiClassConfig().getMethods().get(endpointMethod);
212+
request.setContentType("multipart/form-data; boundary=----test");
213+
request.setMethod("POST");
214+
String requestContent =
215+
"------test\r\n" +
216+
"Content-Disposition: form-data; name=\"foo\"\r\n\r\n" +
217+
"test\r\n" +
218+
"------test\r\n" +
219+
"Content-Disposition: form-data; name=\"bar\"\r\n\r\n" +
220+
"1234\r\n" +
221+
"------test--\r\n";
222+
request.setContent(requestContent.getBytes(StandardCharsets.UTF_8));
223+
RestServletRequestParamReader reader = createReader(ImmutableMap.<String, String>of());
224+
225+
Object[] params = reader.read();
226+
227+
assertThat(params).hasLength(endpointMethod.getParameterClasses().length);
228+
assertThat(params).asList()
229+
.containsExactly("test", 1234);
230+
}
231+
207232
private RestServletRequestParamReader createReader(Map<String, String> rawPathParameters) {
208233
EndpointsContext endpointsContext =
209234
new EndpointsContext("GET", "/", request, new MockHttpServletResponse(), true);
@@ -243,6 +268,15 @@ public void test(
243268
path = "testArrayPathParam/{values}")
244269
public void testArrayPathParam(@Named("values") ArrayList<String> values) {
245270
}
271+
272+
@ApiMethod(
273+
name = "testFormData",
274+
httpMethod = HttpMethod.POST,
275+
path = "testFormData")
276+
public void testFormData(
277+
@Nullable @Named("foo") String foo,
278+
@Nullable @Named("bar") Integer bar) {
279+
}
246280
}
247281

248282
private static byte[] compress(byte[] bytes) {

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jacksonVersion=2.9.6
1111
gradleAppenginePluginVersion=1.9.59
1212
appengineVersion=1.9.60
1313
apiclientVersion=1.23.0
14+
fileUploadVersion=1.3.3
1415
findbugsVersion=3.0.1
1516
swaggerVersion=1.5.9
1617
slf4jVersion=1.7.21

0 commit comments

Comments
 (0)