Skip to content

Commit 4864672

Browse files
author
Ace Nassri
authored
GCF: add multipart sample (GoogleCloudPlatform#2688)
1 parent 714b733 commit 4864672

File tree

4 files changed

+459
-0
lines changed

4 files changed

+459
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
Copyright 2020 Google LLC
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-->
18+
19+
<project xmlns="http://maven.apache.org/POM/4.0.0"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
24+
<groupId>com.example.cloud.functions</groupId>
25+
<artifactId>functions-http-form-data</artifactId>
26+
27+
<parent>
28+
<groupId>com.google.cloud.samples</groupId>
29+
<artifactId>shared-configuration</artifactId>
30+
<version>1.0.17</version>
31+
</parent>
32+
33+
<properties>
34+
<powermock.version>2.0.7</powermock.version>
35+
<maven.compiler.target>11</maven.compiler.target>
36+
<maven.compiler.source>11</maven.compiler.source>
37+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
38+
</properties>
39+
40+
<!-- Disable tests during GCF builds (from parent POM) -->
41+
<!-- You can remove this profile to run tests -->
42+
<!-- when deploying, but we recommend creating -->
43+
<!-- a CI/CD pipeline via Cloud Build instead -->
44+
<profiles>
45+
<profile>
46+
<id>skip_tests_on_gcf</id>
47+
<activation>
48+
<property>
49+
<name>env.NEW_BUILD</name>
50+
</property>
51+
</activation>
52+
<properties>
53+
<skipTests>true</skipTests>
54+
</properties>
55+
</profile>
56+
</profiles>
57+
58+
<dependencies>
59+
<dependency>
60+
<groupId>com.google.code.gson</groupId>
61+
<artifactId>gson</artifactId>
62+
<version>2.8.6</version>
63+
</dependency>
64+
<dependency>
65+
<groupId>javax.servlet</groupId>
66+
<artifactId>javax.servlet-api</artifactId>
67+
<version>4.0.1</version>
68+
<scope>provided</scope>
69+
</dependency>
70+
<dependency>
71+
<groupId>com.google.cloud.functions</groupId>
72+
<artifactId>functions-framework-api</artifactId>
73+
<version>1.0.1</version>
74+
</dependency>
75+
76+
<!-- The following dependencies are only required for testing -->
77+
<dependency>
78+
<groupId>com.google.truth</groupId>
79+
<artifactId>truth</artifactId>
80+
<version>1.0.1</version>
81+
<scope>test</scope>
82+
</dependency>
83+
<dependency>
84+
<groupId>org.powermock</groupId>
85+
<artifactId>powermock-core</artifactId>
86+
<version>${powermock.version}</version>
87+
<scope>test</scope>
88+
</dependency>
89+
<dependency>
90+
<groupId>org.powermock</groupId>
91+
<artifactId>powermock-module-junit4</artifactId>
92+
<version>${powermock.version}</version>
93+
<scope>test</scope>
94+
</dependency>
95+
<dependency>
96+
<groupId>org.powermock</groupId>
97+
<artifactId>powermock-api-mockito2</artifactId>
98+
<version>${powermock.version}</version>
99+
<scope>test</scope>
100+
</dependency>
101+
<dependency>
102+
<groupId>com.google.guava</groupId>
103+
<artifactId>guava-testlib</artifactId>
104+
<version>29.0-jre</version>
105+
<scope>test</scope>
106+
</dependency>
107+
108+
<dependency>
109+
<groupId>junit</groupId>
110+
<artifactId>junit</artifactId>
111+
<version>4.13</version>
112+
<scope>test</scope>
113+
</dependency>
114+
<dependency>
115+
<groupId>com.google.guava</groupId>
116+
<artifactId>guava-testlib</artifactId>
117+
<version>29.0-jre</version>
118+
<scope>compile</scope>
119+
</dependency>
120+
</dependencies>
121+
122+
<build>
123+
<plugins>
124+
<plugin>
125+
<!--
126+
Google Cloud Functions Framework Maven plugin
127+
128+
This plugin allows you to run Cloud Functions Java code
129+
locally. Use the following terminal command to run a
130+
given function locally:
131+
132+
mvn function:run -Drun.functionTarget=your.package.yourFunction
133+
-->
134+
<groupId>com.google.cloud.functions</groupId>
135+
<artifactId>function-maven-plugin</artifactId>
136+
<version>0.9.2</version>
137+
<configuration>
138+
<functionTarget>functions.HttpFormData</functionTarget>
139+
</configuration>
140+
</plugin>
141+
<plugin>
142+
<groupId>org.apache.maven.plugins</groupId>
143+
<artifactId>maven-surefire-plugin</artifactId>
144+
<version>3.0.0-M4</version>
145+
<configuration>
146+
<skipTests>${skipTests}</skipTests>
147+
<reportNameSuffix>sponge_log</reportNameSuffix>
148+
<trimStackTrace>false</trimStackTrace>
149+
</configuration>
150+
</plugin>
151+
<plugin> <!-- Required for Java 8 (Alpha) functions in the inline editor -->
152+
<groupId>org.apache.maven.plugins</groupId>
153+
<artifactId>maven-compiler-plugin</artifactId>
154+
<executions>
155+
<execution>
156+
<id>compile</id>
157+
<phase>compile</phase>
158+
<goals>
159+
<goal>compile</goal>
160+
</goals>
161+
</execution>
162+
<execution>
163+
<id>testCompile</id>
164+
<phase>test-compile</phase>
165+
<goals>
166+
<goal>testCompile</goal>
167+
</goals>
168+
</execution>
169+
</executions>
170+
<configuration>
171+
<excludes>
172+
<exclude>.google/</exclude>
173+
</excludes>
174+
</configuration>
175+
</plugin>
176+
</plugins>
177+
</build>
178+
</project>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package functions;
18+
19+
// [START functions_http_form_data]
20+
21+
import com.google.cloud.functions.HttpFunction;
22+
import com.google.cloud.functions.HttpRequest;
23+
import com.google.cloud.functions.HttpResponse;
24+
import java.io.IOException;
25+
import java.net.HttpURLConnection;
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
29+
import java.nio.file.StandardCopyOption;
30+
import java.util.logging.Logger;
31+
import javax.servlet.annotation.MultipartConfig;
32+
33+
@MultipartConfig
34+
public class HttpFormData implements HttpFunction {
35+
private static final Logger LOGGER = Logger.getLogger(HttpFormData.class.getName());
36+
37+
@Override
38+
public void service(HttpRequest request, HttpResponse response)
39+
throws IOException {
40+
41+
if (!"POST".equals(request.getMethod())) {
42+
response.setStatusCode(HttpURLConnection.HTTP_BAD_METHOD);
43+
return;
44+
}
45+
46+
// This code will process each file uploaded.
47+
String tempDirectory = System.getProperty("java.io.tmpdir");
48+
for (HttpRequest.HttpPart httpPart : request.getParts().values()) {
49+
String filename = httpPart.getFileName().orElse(null);
50+
if (filename == null) {
51+
continue;
52+
}
53+
54+
LOGGER.info("Processed file: " + filename);
55+
56+
// Note: GCF's temp directory is an in-memory file system
57+
// Thus, any files in it must fit in the instance's memory.
58+
Path filePath = Paths.get(tempDirectory, filename).toAbsolutePath();
59+
60+
// Note: files saved to a GCF instance itself may not persist across executions.
61+
// Persistent files should be stored elsewhere, e.g. a Cloud Storage bucket.
62+
Files.copy(httpPart.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
63+
64+
// TODO(developer): process saved files here
65+
Files.delete(filePath);
66+
}
67+
68+
// This code will process other form fields.
69+
for (String fieldName : request.getQueryParameters().keySet()) {
70+
String firstFieldValue = request.getFirstQueryParameter(fieldName).get();
71+
72+
// TODO(developer): process field values here
73+
LOGGER.info(String.format(
74+
"Processed field: %s (value: %s)", fieldName, firstFieldValue));
75+
}
76+
}
77+
}
78+
// [END functions_http_form_data]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package functions;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.mockito.Mockito.times;
21+
import static org.mockito.Mockito.verify;
22+
import static org.powermock.api.mockito.PowerMockito.mock;
23+
import static org.powermock.api.mockito.PowerMockito.when;
24+
25+
import com.google.cloud.functions.HttpRequest;
26+
import com.google.cloud.functions.HttpResponse;
27+
import com.google.common.testing.TestLogHandler;
28+
import java.io.BufferedWriter;
29+
import java.io.ByteArrayInputStream;
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
import java.io.StringWriter;
33+
import java.net.HttpURLConnection;
34+
import java.nio.charset.StandardCharsets;
35+
import java.util.ArrayList;
36+
import java.util.Arrays;
37+
import java.util.HashMap;
38+
import java.util.List;
39+
import java.util.Optional;
40+
import java.util.logging.Logger;
41+
import org.junit.Before;
42+
import org.junit.BeforeClass;
43+
import org.junit.Test;
44+
import org.junit.runner.RunWith;
45+
import org.junit.runners.JUnit4;
46+
import org.mockito.Mock;
47+
import org.mockito.Mockito;
48+
import org.powermock.api.mockito.PowerMockito;
49+
50+
@RunWith(JUnit4.class)
51+
public class HttpFormDataTest {
52+
@Mock private HttpRequest request;
53+
@Mock private HttpResponse response;
54+
55+
private BufferedWriter writerOut;
56+
private StringWriter responseOut;
57+
58+
private static final Logger LOGGER = Logger.getLogger(HttpFormData.class.getName());
59+
private static final TestLogHandler logHandler = new TestLogHandler();
60+
61+
@BeforeClass
62+
public static void setUp() {
63+
LOGGER.addHandler(logHandler);
64+
}
65+
66+
@Before
67+
public void beforeTest() throws IOException {
68+
Mockito.mockitoSession().initMocks(this);
69+
70+
request = mock(HttpRequest.class);
71+
response = mock(HttpResponse.class);
72+
73+
responseOut = new StringWriter();
74+
writerOut = new BufferedWriter(responseOut);
75+
PowerMockito.when(response.getWriter()).thenReturn(writerOut);
76+
77+
logHandler.clear();
78+
}
79+
80+
@Test
81+
public void functionsHttpMethod_shouldErrorOnGet() throws IOException {
82+
when(request.getMethod()).thenReturn("GET");
83+
84+
new HttpFormData().service(request, response);
85+
86+
writerOut.flush();
87+
verify(response, times(1)).setStatusCode(HttpURLConnection.HTTP_BAD_METHOD);
88+
}
89+
90+
@Test
91+
public void functionsHttpFormData_shouldSaveFiles() throws IOException {
92+
when(request.getMethod()).thenReturn("POST");
93+
94+
ArrayList<HttpRequest.HttpPart> partsList = new ArrayList<>();
95+
96+
InputStream stream = new ByteArrayInputStream("foo text%n".getBytes(StandardCharsets.UTF_8));
97+
98+
MockHttpPart mockHttpPart = new MockHttpPart();
99+
mockHttpPart.setFileName("foo.txt");
100+
mockHttpPart.setInputStream(stream);
101+
partsList.add(mockHttpPart);
102+
103+
HashMap<String, HttpRequest.HttpPart> httpParts = new HashMap<>();
104+
httpParts.put("mock", mockHttpPart);
105+
when(request.getParts()).thenReturn(httpParts);
106+
107+
new HttpFormData().service(request, response);
108+
109+
assertThat(logHandler.getStoredLogRecords().get(0).getMessage()).isEqualTo(
110+
"Processed file: foo.txt");
111+
}
112+
113+
@Test
114+
public void functionsHttpFormData_shouldProcessFields() throws IOException {
115+
when(request.getMethod()).thenReturn("POST");
116+
when(request.getParts()).thenReturn(new HashMap<>());
117+
118+
HashMap<String, List<String>> queryParams = new HashMap<>();
119+
queryParams.put("foo", Arrays.asList(new String[]{"bar"}));
120+
121+
when(request.getQueryParameters()).thenReturn(queryParams);
122+
when(request.getFirstQueryParameter("foo")).thenReturn(Optional.of("bar"));
123+
124+
new HttpFormData().service(request, response);
125+
126+
assertThat(logHandler.getStoredLogRecords().get(0).getMessage()).isEqualTo(
127+
"Processed field: foo (value: bar)");
128+
}
129+
}

0 commit comments

Comments
 (0)