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 theContact
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 addRoutingServlet
for routing a concrete request to a particular endpoint. - Decoder provides you with tools for parsing requests.
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(Mapper.ofEx(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:
@ProvidesContactDAO dao() { return new ContactDAOImpl();}
Now we have everything needed to create AsyncServlet
to handle requests:
@ProvidesAsyncServlet mainServlet(ContactDAO contactDAO) { Mustache contactListView = new DefaultMustacheFactory().compile("static/contactList.html"); return RoutingServlet.create() .map("/", request -> HttpResponse.ok200() .withBody(applyTemplate(contactListView, mapOf("contacts", contactDAO.list())))) .map(POST, "/add", request -> request.loadBody() .map($ -> { //[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 = mapOf("contacts", contactDAO.list()); if (decodedUser.isRight()) { //noinspection ConstantConditions - is 'right', hence not 'null' scopes.put("errors", decodedUser.getRight().toMap(SEPARATOR)); } return HttpResponse.ok200() .withBody(applyTemplate(contactListView, scopes)); }));}
- Here we provide an
AsyncServlet
, which receivesHttpRequests
from clients, createsHttpResponses
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 HTTPPOST
method that adds or declines adding new users. We will process this request parsing with the help of the aforementioned HTTPDecoder
by usingdecode(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 theisLeft()
andisRight()
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 v5.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