Skip to main content

Form Validation Using HTTP Decoder

In this example we will create an async servlet that adds contacts to a list, parses requests and processes form validation with the help of Decoder. Consider this example as a concise representation of the MVC pattern:

  • To model a Contact representation, we will create a plain Java class with fields (name, age, address), constructor, and accessors to the fields.
  • To simplify the example, we will use an ArrayList to store the Contact objects. ContactDAO interface and its implementation are used for this purpose.
  • To build a view we will use a single HTML file, compiled with the help of the Mustache template engine.
  • An AsyncServlet will be used as a controller. We will also add RoutingServlet for routing a concrete request to a particular endpoint.
  • Decoder provides you with tools for parsing requests.
See full example on GitHub

Here we will consider only HttpDecoderExample class with AsyncServlet as it contains ActiveJ-specific features.

Creating HttpDecoderExample Class#

Let's create HttpDecoderExample class which extends HttpServerLauncher. By extending HttpServerLauncher we will take care of the server's lifecycle and service management. Next, we provide two custom parsers based on HTTP Decoder- ADDRESS_DECODER and CONTACT_DECODER - which will be used for validation.

public final class HttpDecoderExample extends HttpServerLauncher {
private static final String SEPARATOR = "-";
private static final Decoder<Address> ADDRESS_DECODER = Decoder.of(Address::new,
ofPost("title", "")
.validate(param -> !param.isEmpty(), "Title cannot be empty")
);
private static final Decoder<Contact> CONTACT_DECODER = Decoder.of(Contact::new,
ofPost("name")
.validate(name -> !name.isEmpty(), "Name cannot be empty"),
ofPost("age")
.map(Integer::valueOf, "Cannot decode age")
.validate(age -> age >= 18, "Age must not be less than 18"),
ADDRESS_DECODER.withId("contact-address")
);
note

If you want to learn more about template engines integration, check out this example

Also, we need to create applyTemplate(Mustache mustache, Map<String, Object> scopes) method to fill the provided Mustache template with the given data:

private static ByteBuf applyTemplate(Mustache mustache, Map<String, Object> scopes) {
ByteBufWriter writer = new ByteBufWriter();
mustache.execute(writer, scopes);
return writer.getBuf();
}

Next, let's provide a ContactDAOImpl factory method:

@Provides
ContactDAO dao() {
return new ContactDAOImpl();
}

Now we have everything needed to create AsyncServlet to handle requests:

@Provides
AsyncServlet mainServlet(ContactDAO contactDAO) {
Mustache contactListView = new DefaultMustacheFactory().compile("static/contactList.html");
return RoutingServlet.create()
.map("/", request ->
HttpResponse.ok200()
.withBody(applyTemplate(contactListView, map("contacts", contactDAO.list()))))
.map(POST, "/add", AsyncServletDecorator.loadBody()
.serve(request -> {
//[START REGION_3]
Either<Contact, DecodeErrors> decodedUser = CONTACT_DECODER.decode(request);
//[END REGION_3]
if (decodedUser.isLeft()) {
contactDAO.add(decodedUser.getLeft());
}
Map<String, Object> scopes = map("contacts", contactDAO.list());
if (decodedUser.isRight()) {
scopes.put("errors", decodedUser.getRight().toMap(SEPARATOR));
}
return HttpResponse.ok200()
.withBody(applyTemplate(contactListView, scopes));
}));
}
  • Here we provide an AsyncServlet, which receives HttpRequests from clients, creates HttpResponses depending on route path and sends it.
  • Inside the RoutingServlet two route paths are defined. The first one matches requests to the root route "/" - it simply displays a contact list. The second one, "/add" - is an HTTP POST method that adds or declines adding new users. We will process this request parsing with the help of the aforementioned HTTP Decoder by using decode(request) method:
Either<Contact, DecodeErrors> decodedUser = CONTACT_DECODER.decode(request);
  • Either represents a value of two possible data types (Contact, DecodeErrors). Either is either Left(Contact) or Right(DecodeErrors). In order to determine whether a parse was successful or not, we check it's value by using the isLeft() and isRight() methods.

Finally, write down the main() method which will launch our application:

public static void main(String[] args) throws Exception {
Launcher launcher = new HttpDecoderExample();
launcher.launch(args);
}

Running the application#

If you want to run the example, you need to clone ActiveJ from GitHub and import it as a Maven project. Check out branch v4.3. Before running the example, build the project (Ctrl + F9 for IntelliJ IDEA).

Then open HttpDecoderExample class, which is located at activej/examples/tutorials/decoder and run its main() method. Open your favourite browser and go to localhost:8080