Fork me on GitHub

Interactive HTTP Server Tutorial

This tutorial looks at handling a form submission.

The below example for this tutorial will implement a simple form submission. The form submission will validate a name was entered and provide a message if successfully entered.

The simple form for this tutorial is as follows:

InteractiveHttpServer screen shot.

Tutorial Source

Template.woof.html

The below is the content of the Template.woof.html.

<html>
	<body>
		<form action="#{POST:handleSubmission}" method="POST">
			<p>Name: <input type="text" name="name" value="${name}" /> ${nameIssue ${message} $}</p>
			<p>Description: <input type="text" name="description" value="${description}" /></p>
			<p><input type="submit" value="Add" /></p>
		</form>
		<p>${successMessage}</p>
	</body>
</html>

The difference to previous tutorials is that the HTTP method is qualified in the link. This configures WoOF to handle the POST method in this case. This is used to specify handling of various other HTTP methods (useful for REST interaction with the server).

The default handling for unqualified links is both GET and POST. This is to support common use cases of link navigation and form submissions.

TemplateLogic Class

The logic to handle the form submission is the following:

public class TemplateLogic {

	@Data
	@HttpParameters
	public static class Parameters implements Serializable {
		private static final long serialVersionUID = 1L;

		private String name;

		private Issue nameIssue;

		private String description;

		private String successMessage;
	}

	@Data
	public static class Issue implements Serializable {
		private static final long serialVersionUID = 1L;

		private final String message;
	}

	/**
	 * Obtains the bean for rendering the template.
	 * 
	 * @param submittedParameters Same {@link Parameters} that was constructed for
	 *                            {@link #handleSubmission(Parameters)}. This allows
	 *                            the page to be rendered with the values provided
	 *                            by the client.
	 * @return {@link Parameters} for rendering to page.
	 */
	public Parameters getTemplateData(Parameters submittedParameters) {
		return submittedParameters;
	}

	/**
	 * Reflectively invoked to handle form submission.
	 * 
	 * @param submittedParameters {@link Parameters} which is dependency injected.
	 *                            It is constructed via its default constructor and
	 *                            has the HTTP parameters values loaded by
	 *                            corresponding names.
	 */
	public void handleSubmission(Parameters submittedParameters) {

		// Ensure have a name provided
		String name = submittedParameters.getName();
		if ((name == null) || (name.trim().length() == 0)) {
			submittedParameters.setNameIssue(new Issue("Must provide name"));
			return;
		}

		// TODO use values to undertake some business logic. Typically would
		// provider further dependencies as parameters to this method to allow
		// this.

		// Provide success message (and clear values)
		submittedParameters.setSuccessMessage("Thank you " + name);
		submittedParameters.setName(null);
		submittedParameters.setDescription(null);
	}
}

The aspects to notice are:

  • the method handleSubmission() matches in name to the #{handleSubmission} of the HTML form action. As the names are the same, WoOF will reflectively invoke the method to handle the form submission. By default WoOF will re-render the page after the method completes. Later tutorials will look at controlling navigation to other pages.
  • the Parameters inner class is annotated with @HttpParameters. As this is dependency injected into the form handling method, WoOF sees the annotation and will construct an instance of the object by its default constructor and load the HTTP parameters to its setter methods by corresponding names (e.g. name to setName(String name)). Note that this object is constructed once for each HTTP request so is the same object dependency injected into the getTemplateData(...) method - allowing entered values to be re-rendered to the page.
  • the Parameters inner class is serializable. By default WoOF will undertake the Post/Redirect/Get pattern after the handleSubmission() method completes and before the template is re-rendered. To enable the state of the Parameters object to be available across the redirect, it is added to the HttpSession and subsequently must be serializable.

OfficeFloor achieves this simple interactive programming model by substituting into the rendered page a unique URL which it can map back to the corresponding method. The method is matched by its name and is free to have any parameters it requires (enabled by OfficeFloor's continuation injection and dependency injection). For example in more complex applications the handling methods may include a parameter for a DataSource or EntityManager to enable database interaction rather than just providing a message back to the client. Later tutorials will explain how to inject further dependencies.

Unit Test

The unit test requests the various URL's exposed from the template.

	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void pageInteraction() throws Exception {

		// Request the initial page
		HttpResponse response = this.client.execute(new HttpGet(this.client.url("/example")));
		assertEquals(200, response.getStatusLine().getStatusCode(), "Request should be successful");
		String responseEntity = EntityUtils.toString(response.getEntity());
		assertFalse(responseEntity.contains("Daniel"), "Should not have entry");

		// Send form submission
		HttpPost post = new HttpPost(this.client.url("/example+handleSubmission"));
		post.setHeader("Content-Type", "application/x-www-form-urlencoded");
		post.setEntity(new StringEntity("name=Daniel&description=founder"));
		response = this.client.execute(post);
		assertEquals(200, response.getStatusLine().getStatusCode(), "Should submit successfully");
		responseEntity = EntityUtils.toString(response.getEntity());
		assertTrue(responseEntity.contains("<p>Thank you Daniel</p>"), "Should indicate added");
	}

Next

The next tutorial looks at storing state between requests within a HTTP session.