Fork me on GitHub

Google App Engine Tutorial

This tutorial demonstrates running within Google App Engine.

Tutorial Source

Google App Engine

The configuration is just like any other Servlet application deployed to Google App Engine:

  • The application is packaged as a war
  • There is an empty web.xml:
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    	version="3.1">
    </web-app>
    
  • Along with the appengine-web.xml file, which for this tutorial is the following simple example:
    <?xml version="1.0" encoding="utf-8"?>
    <appengine-web-app
    	xmlns="http://appengine.google.com/ns/1.0">
    	<runtime>java8</runtime>
    	<threadsafe>true</threadsafe>
    	<warmup-requests-enabled>false</warmup-requests-enabled>
    </appengine-web-app>
    

Note: see Google App Engine documentation for further configuration.

WoOF

To configure to run within GCP, simply add the following dependency:

		<dependency>
			<groupId>net.officefloor.server</groupId>
			<artifactId>officeserver_appengine</artifactId>
		</dependency>

This then runs the application as any other Servlet application within the Google App Engine.

Deploying

Google App Engine tooling can be used as normal to deploy the application:

					<plugin>
						<groupId>com.google.cloud.tools</groupId>
						<artifactId>appengine-maven-plugin</artifactId>
						<configuration>
							<deploy.projectId>${project.artifactId}</deploy.projectId>
							<deploy.version>${project.version}</deploy.version>
						</configuration>
					</plugin>

Testing

Again the Google App Engine tooling can be used for testing. This allows testing simple applications:

	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension(false, 8481);

	@Test
	public void ensureGetResource() throws Exception {
		this.doTest("/index.html", "<html><body>Hello from GCP</body></html>");
	}

	@Test
	public void ensureRestEndPoint() throws Exception {
		this.doTest("/rest", "{\"message\":\"Hello from GCP\"}");
	}

	private void doTest(String path, String entity) throws IOException {

		// Obtain the resource
		HttpGet get = new HttpGet(this.client.url(path));
		HttpResponse response = this.client.execute(get);

		// Ensure obtained resource
		String actualEntity = EntityUtils.toString(response.getEntity());
		assertEquals(200, response.getStatusLine().getStatusCode(), "Should be successful: " + actualEntity);
		assertEquals(entity, actualEntity);
	}

However, most applications will require a data store. To use a local data store requires starting the data store from the command line. This disallows easy use within continuous integration/delivery pipelines.

OfficeFloor provides the following plugin to undertake integration testing within the Google App Engine emulator with a local data store:

					<plugin>
						<groupId>net.officefloor.maven</groupId>
						<artifactId>officefloor-appengine-maven-plugin</artifactId>
						<configuration>
							<port>8481</port>
						</configuration>
						<executions>
							<execution>
								<id>start-gcp</id>
								<phase>pre-integration-test</phase>
								<goals>
									<goal>start</goal>
								</goals>
							</execution>
							<execution>
								<id>stop-gcp</id>
								<phase>post-integration-test</phase>
								<goals>
									<goal>stop</goal>
								</goals>
							</execution>
						</executions>
					</plugin>
					<plugin>
						<groupId>org.apache.maven.plugins</groupId>
						<artifactId>maven-failsafe-plugin</artifactId>
						<executions>
							<execution>
								<goals>
									<goal>integration-test</goal>
									<goal>verify</goal>
								</goals>
							</execution>
						</executions>
					</plugin>

To access the data store started, the following test dependency needs to be added:

		<dependency>
			<groupId>net.officefloor.maven</groupId>
			<artifactId>officefloor-appengine-maven-plugin</artifactId>
			<scope>test</scope>
		</dependency>

This allows the application to be integration tested as follows:

	@Test
	public void ensureDatastoreEndPoint() throws Exception {

		// Obtain the datastore from integration setup
		Datastore datastore = IntegrationAppEngine.getDatastore();

		// Initialise datastore
		ObjectifyService.init(new ObjectifyFactory(datastore));
		ObjectifyService.register(Post.class);
		Post post = new Post(null, "TEST MESSAGE");
		Map<Key<Post>, Post> data = ObjectifyService.run(() -> {
			return ObjectifyService.ofy().save().entities(post).now();
		});
		assertEquals(1, data.size(), "Should persist data to retrieve");

		// Ensure able to retrieve the data
		HttpGet get = new HttpGet(this.client.url("/post/" + post.getId()));
		HttpResponse response = this.client.execute(get);

		// Ensure obtained resource
		String actualEntity = EntityUtils.toString(response.getEntity());
		assertEquals(200, response.getStatusLine().getStatusCode(), "Should be successful: " + actualEntity);
		Post retrievePost = new ObjectMapper().readValue(actualEntity, Post.class);
		assertEquals(post.getMessage(), retrievePost.getMessage(), "Incorrect post retrieved");
	}

Furthermore, Google AppEngine emulator only runs HTTP. There is no secure port. This makes testing secure end points difficult.

To enable testing of secure connection end points, the OfficeFloor plugin provides a Filter that flags all requests to be secure (avoiding the need for redirects).

This enables the following:

	@Test
	public void ensureSecureEndPoint() throws Exception {

		// Obtain the secure resource (made accessible for testing)
		HttpGet get = new HttpGet("http://localhost:8481/secure");
		HttpResponse response = this.client.execute(get);

		// Ensure obtained resource
		String actualEntity = EntityUtils.toString(response.getEntity());
		assertEquals(200, response.getStatusLine().getStatusCode(), "Should be successful: " + actualEntity);
		assertEquals("{\"message\":\"Secure hello from GCP\"}", actualEntity);
	}

Note: for running with the Google IDE plugins, the dependency can be also added to the class path to avoid secure redirects:

		<dependency>
			<groupId>net.officefloor.server</groupId>
			<artifactId>officeserver_appengineemulator</artifactId>
			<scope>test</scope>
		</dependency>

Next

The next tutorial covers integrating DynamoDB into OfficeFloor.