Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit ddd9a4a

Browse files
committed
Implement HEAD Method
1 parent 5c60f82 commit ddd9a4a

4 files changed

Lines changed: 136 additions & 2 deletions

File tree

jooby-core/src/main/java/jooby/Jooby.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,14 @@ public Route.Definition post(final String path, final Filter filter) {
439439
return route(new Route.Definition("POST", path, filter));
440440
}
441441

442+
public Route.Definition head(final String path, final Router route) {
443+
return route(new Route.Definition("HEAD", path, route));
444+
}
445+
446+
public Route.Definition head(final String path, final Filter filter) {
447+
return route(new Route.Definition("HEAD", path, filter));
448+
}
449+
442450
/**
443451
* Define an in-line route that supports HTTP PUT method:
444452
*

jooby-core/src/main/java/jooby/Route.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,60 @@ private Route asRoute(final RouteMatcher matcher, final List<MediaType> produces
288288

289289
}
290290

291+
public class Forwarding implements Route {
292+
293+
private final Route route;
294+
295+
public Forwarding(final Route route) {
296+
this.route = requireNonNull(route, "A route is required.");
297+
}
298+
299+
@Override
300+
public String path() {
301+
return route.path();
302+
}
303+
304+
@Override
305+
public String verb() {
306+
return route.verb();
307+
}
308+
309+
@Override
310+
public String pattern() {
311+
return route.pattern();
312+
}
313+
314+
@Override
315+
public String name() {
316+
return route.name();
317+
}
318+
319+
@Override
320+
public Map<String, String> vars() {
321+
return route.vars();
322+
}
323+
324+
@Override
325+
public List<MediaType> consume() {
326+
return route.consume();
327+
}
328+
329+
@Override
330+
public List<MediaType> produces() {
331+
return route.produces();
332+
}
333+
334+
@Override
335+
public String toString() {
336+
return route.toString();
337+
}
338+
339+
public Route delegate() {
340+
return route;
341+
}
342+
343+
}
344+
291345
interface Chain {
292346
void next(Request request, Response response) throws Exception;
293347
}

jooby-core/src/main/java/jooby/internal/RouteHandler.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ private static Route.Chain chain(final Iterator<Route> it) {
175175

176176
@Override
177177
public void next(final Request req, final Response res) throws Exception {
178-
RouteImpl route = (RouteImpl) it.next();
178+
RouteImpl route = get(it.next());
179179

180180
// set route
181181
set(req, route);
@@ -184,6 +184,15 @@ public void next(final Request req, final Response res) throws Exception {
184184
route.handle(req, res, this);
185185
}
186186

187+
private RouteImpl get(final Route next) {
188+
Route root = next;
189+
// Is there a better way to set route info?
190+
while (root instanceof Route.Forwarding) {
191+
root = ((Route.Forwarding) root).delegate();
192+
}
193+
return (RouteImpl)root;
194+
}
195+
187196
private void set(final Request req, final Route route) {
188197
Request root = req;
189198
// Is there a better way to set route info?
@@ -212,6 +221,11 @@ private List<Route> routes(final String verb, final String requestURI, final Med
212221
final List<MediaType> accept) {
213222
String path = verb + requestURI;
214223
List<Route> routes = findRoutes(verb, requestURI, type, accept);
224+
if ("HEAD".equals(verb)
225+
&& !routes.stream().filter(r -> r.verb().equals("HEAD")).findFirst().isPresent()) {
226+
// override HEAD when it's missing.
227+
routes = findRoutes("GET", requestURI, type, accept, "HEAD");
228+
}
215229

216230
// 406 or 415
217231
routes.add(RouteImpl.fromStatus((req, res, chain) -> {
@@ -243,17 +257,32 @@ private List<Route> routes(final String verb, final String requestURI, final Med
243257

244258
private List<Route> findRoutes(final String verb, final String path, final MediaType type,
245259
final List<MediaType> accept) {
260+
return findRoutes(verb, path, type, accept, verb);
261+
}
262+
263+
private List<Route> findRoutes(final String verb, final String path, final MediaType type,
264+
final List<MediaType> accept, final String overrideVerb) {
246265

247266
LinkedList<Route> routes = new LinkedList<Route>();
248267
for (Route.Definition routeDef : routeDefs) {
249268
Optional<Route> route = routeDef.matches(verb, path, type, accept);
250269
if (route.isPresent()) {
251-
routes.add(route.get());
270+
routes.add(verb.equals(overrideVerb) ? route.get()
271+
: overrideVerb(route.get(), overrideVerb));
252272
}
253273
}
254274
return routes;
255275
}
256276

277+
private static Route overrideVerb(final Route route, final String verb) {
278+
return new Route.Forwarding(route) {
279+
@Override
280+
public String verb() {
281+
return verb;
282+
}
283+
};
284+
}
285+
257286
private void defaultErrorPage(final Request request, final Response response,
258287
final HttpStatus status, final Map<String, Object> model) throws Exception {
259288
StringBuilder html = new StringBuilder("<!doctype html>")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package jooby;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import jooby.FilterFeature.HttpResponseValidator;
5+
6+
import org.apache.http.HttpResponse;
7+
import org.apache.http.client.fluent.Request;
8+
import org.apache.http.client.utils.URIBuilder;
9+
import org.junit.Test;
10+
11+
public class HeadRequestFeature extends ServerFeature {
12+
13+
{
14+
get("/", (req, res) -> res.send(req.route().verb()));
15+
16+
head("/head", (req, res) -> res.send(req.path()));
17+
}
18+
19+
@Test
20+
public void defaultHead() throws Exception {
21+
assertEquals(null, execute(HEAD(uri("/")), (response) -> {
22+
assertEquals(200, response.getStatusLine().getStatusCode());
23+
}));
24+
}
25+
26+
@Test
27+
public void realHead() throws Exception {
28+
assertEquals(null, execute(HEAD(uri("/head")), (response) -> {
29+
assertEquals(200, response.getStatusLine().getStatusCode());
30+
}));
31+
}
32+
33+
private static Request HEAD(final URIBuilder uri) throws Exception {
34+
return Request.Head(uri.build());
35+
}
36+
37+
private static Object execute(final Request request, final HttpResponseValidator validator)
38+
throws Exception {
39+
HttpResponse resp = request.execute().returnResponse();
40+
validator.validate(resp);
41+
return resp.getEntity();
42+
}
43+
}

0 commit comments

Comments
 (0)