Web features¶
Servlets, filters¶
Servlets and filters could be registered either with guice ServletModule or using extensions.
Guice servlet module¶
Example:
public class WebModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/*").through(MyFilter.class);
serve("/myservlet").with(MyServlet.class);
}
}
Pros
Only ServletModule
allows mappings by regexp:
serveRegex("(.)*ajax(.)*").with(MyAjaxServlet.class)
Warning
It is important to note that GuiceFilter
dispatch all requests for filters and servlets
registered by ServletModule
internally and so you may have problems combining servlets from
ServletModule
with filters in main scope.
It is never a blocking issues, but often "not obvious to understand" situations.
Web extensions¶
Extensions declared with standard jakarta.servlet
annotations.
Servlet registration:
@WebServlet("/mapped")
public class MyServlet extends HttpServlet { ... }
Extension recognized by @WebServlet
annotation.
Could be registered on admin context:
@WebServlet("/mapped")
@AdminContext
public class MyServlet extends HttpServlet { ... }
Or even on both contexts at the same time: @AdminContext(andAdmin=true)
.
Filter:
@WebFilter("/some/*")
public class MyFilter implements Filter { ... }
Extension recognized by @WebFilter
annotation.
Web listeners (servlet, request, session):
@WebListener
public class MyListener implements ServletContextListener {...}
Extension recognized by @WebListener
annotation.
Pros
Installation through extensions has more abilities comparing to ServletModule
:
- Installation into admin context
- Async support
- Filter may be applied to exact servlet(s) (
@WebFilter(servletNames = "servletName")
) - Request, servlet context or session listeners installation
If you don't want to use web installers or have problems with it (e.g. because they use jakarta.servlet
annotations)
you can disable all of them at once by disabling bundle:
GuiceBundle.builder()
.disableBindles(WebInstallersBundle.class)
...
Manual registration¶
Alternatively, you can always register servlet or filter manually with dropwizard api:
public class App extends Application {
public void initialize(Bootstrap bootstrap) {
bootstrap.addBundle(GuiceBundle.builder().build());
}
public void run(Configuration configuration, Environment environment) {
final MyFilter filter = InjectorLookup.getInstance(this, MyFilterBean.class).get();
environment.servlets().addFilter("manualFilter", filter)
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
}
}
Resources¶
Dropwizard provides AssetsBundle for serving static files from classpath:
bootstrap.addBundle(new AssetsBundle("/assets/app/", "/", "index.html"));
http://localhost:8080/foo.css
→ src/main/resources/assets/app/foo.css
http://localhost:8080/
→ src/main/resources/assets/app/index.html
HTML5 routing¶
But, if you develop SPA application with HTML5 routes, server will not handle these routes
properly. Use guicey SPA bundle which adds proper SPA routing support above dropwizard AssetBundle
GuiceBundle.builder()
.bundles(SpaBundle.app("spaApp", "/assets/app/", "/").build());
http://localhost:8080/
→ src/main/resources/assets/app/index.html
http://localhost:8080/route/path
→ src/main/resources/assets/app/index.html
Templates¶
Dropwizard provides ViewBundle for handling templates (freemarker and mustache out of the box, more engines could be plugged).
bootstrap.addBundle(new ViewBundle());
Which allows you to serve rendered templates from rest endpoints.
Templates + resources¶
But it is not quite handful to use it together with static resources (AssetsBundle) because static resources will have different urls (as they are not served from rest).
If you would like to have JSP-like behaviour (when templates and resources live at the same
location and so could easily reference each other) - then use guicey GSP bundle
(which is actually just a "glue" for dropwizard ViewBundle
and AssetsBundle
).
com/exmaple/app/
person.ftl
foo.ftl
style.css
<#-- Sample template without model (/foo.ftl) -->
<html>
<body>
<h1>Hello, it's a template: ${12+22}!</h1>
</body>
</html>
<#-- Template with model, rendered by rest endpoint (/person/) -->
<#-- @ftlvariable name="" type="com.example.views.PersonView" -->
<html>
<head>
<link href="/style.css" rel="stylesheet">
</head>
<body>
<!-- calls getPerson().getName() and sanitizes it -->
<h1>Hello, ${person.name?html}!</h1>
</body>
</html>
public class PersonView extends TemplateView {
private final Person person;
public PersonView(Person person) {
super('person.ftl');
this.person = person;
}
public Person getPerson() {
return person;
}
}
// Path starts with application name
@Path("/com.example.app/person/")
@Produces(MediaType.TEXT_HTML)
// Important marker
@Template
public class PersonPage {
@Inject
private PersonDAO dao;
@GET
@Path("/")
public PersonView getMaster() {
return new PersonView(dao.find(1));
}
@GET
@Path("/{id}")
public PersonView getPerson(@PathParam("id") String id) {
return new PersonView(dao.find(id));
}
}
GuiceBundle.builder()
.bundles(
// global views support
ServerPagesBundle.builder().build(),
// application registration
ServerPagesBundle.app("com.example.app", "/com/example/app/", "/")
// rest path as index page
.indexPage("person/")
.build());
Static resource call:
http://localhost:8080/style.css
→ src/main/resources/com/example/app/style.css
Direct template call:
http://localhost:8080/foo.ftl
→ src/main/resources/com/example/app/foo.ftl
Rest-driven template call:
http://localhost:8080/person/12
→ /rest/com.example.app/person/12
Index page:
http://localhost:8080/
→ /rest/com.example.app/person/
Summary
Declaration differences from pure dropwizard views:
- Model extends
TemplateView
- Rest endpoints always annotated with
@Template
- Rest endpoints paths starts with registered application name (
ServerPagesBundle.app("com.example.app"
) to be able to differentiate rest for different UI applications
Warning
Standard errors handling in views (templates, custom pages) is replaced by custom mechanism, required to implement per-ui-app errors support.