Skip to content

Commit 6a4822f

Browse files
committed
HTTP HEAD implementation
1 parent 3ffe3f6 commit 6a4822f

File tree

13 files changed

+471
-160
lines changed

13 files changed

+471
-160
lines changed

jooby/src/main/java/io/jooby/ForwardingContext.java

Lines changed: 113 additions & 113 deletions
Large diffs are not rendered by default.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby;
7+
8+
import javax.annotation.Nonnull;
9+
10+
public class HeadHandler implements Route.Decorator {
11+
@Nonnull @Override public Route.Handler apply(@Nonnull Route.Handler next) {
12+
// NOOP, but we need it for marking the route as HTTP HEAD
13+
return ctx -> next.apply(ctx);
14+
}
15+
16+
@Nonnull @Override public Route.Decorator setRoute(@Nonnull Route route) {
17+
route.setHttpHead(true);
18+
return this;
19+
}
20+
}

jooby/src/main/java/io/jooby/Route.java

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ public Route(@Nonnull String method, @Nonnull String pattern, @Nonnull Handler h
654654
* @return True if route support HTTP OPTIONS.
655655
*/
656656
public boolean isHttpOptions() {
657-
return supportedMethod != null && supportedMethod.contains(Router.OPTIONS);
657+
return isHttpMethod(Router.OPTIONS);
658658
}
659659

660660
/**
@@ -663,47 +663,68 @@ public boolean isHttpOptions() {
663663
* @return True if route support HTTP TRACE.
664664
*/
665665
public boolean isHttpTrace() {
666-
return supportedMethod != null && supportedMethod.contains(Router.TRACE);
666+
return isHttpMethod(Router.TRACE);
667+
}
668+
669+
/**
670+
* True if route support HTTP HEAD.
671+
*
672+
* @return True if route support HTTP HEAD.
673+
*/
674+
public boolean isHttpHead() {
675+
return getMethod().equals(Router.GET) && isHttpMethod(Router.HEAD);
667676
}
668677

669678
/**
670679
* Enabled or disabled HTTP Options.
671680
*
672681
* @param enabled Enabled or disabled HTTP Options.
673-
* @return This route.
682+
* @return This route.
674683
*/
675684
public @Nonnull Route setHttpOptions(boolean enabled) {
676-
if (supportedMethod == null) {
677-
supportedMethod = new HashSet<>();
678-
}
679-
if (enabled) {
680-
supportedMethod.add(Router.OPTIONS);
681-
} else {
682-
supportedMethod.remove(Router.OPTIONS);
683-
}
685+
addHttpMethod(enabled, Router.OPTIONS);
684686
return this;
685687
}
686688

687689
/**
688690
* Enabled or disabled HTTP TRACE.
689691
*
690692
* @param enabled Enabled or disabled HTTP TRACE.
691-
* @return This route.
693+
* @return This route.
692694
*/
693695
public @Nonnull Route setHttpTrace(boolean enabled) {
696+
addHttpMethod(enabled, Router.TRACE);
697+
return this;
698+
}
699+
700+
/**
701+
* Enabled or disabled HTTP HEAD.
702+
*
703+
* @param enabled Enabled or disabled HTTP HEAD.
704+
* @return This route.
705+
*/
706+
public @Nonnull Route setHttpHead(boolean enabled) {
707+
addHttpMethod(enabled, Router.HEAD);
708+
return this;
709+
}
710+
711+
@Override public String toString() {
712+
return method + " " + pattern;
713+
}
714+
715+
private boolean isHttpMethod(String httpMethod) {
716+
return supportedMethod != null && supportedMethod.contains(httpMethod);
717+
}
718+
719+
private void addHttpMethod(boolean enabled, String httpMethod) {
694720
if (supportedMethod == null) {
695721
supportedMethod = new HashSet<>();
696722
}
697723
if (enabled) {
698-
supportedMethod.add(Router.TRACE);
724+
supportedMethod.add(httpMethod);
699725
} else {
700-
supportedMethod.remove(Router.TRACE);
726+
supportedMethod.remove(httpMethod);
701727
}
702-
return this;
703-
}
704-
705-
@Override public String toString() {
706-
return method + " " + pattern;
707728
}
708729

709730
private Route.Handler computePipeline() {

jooby/src/main/java/io/jooby/internal/CompositeMessageEncoder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@
2424

2525
public class CompositeMessageEncoder implements MessageEncoder {
2626

27-
private List<MessageEncoder> decoders = new ArrayList<>(2);
27+
private List<MessageEncoder> encoder = new ArrayList<>(2);
2828

2929
private List<TemplateEngine> templateEngine = new ArrayList<>(2);
3030

3131
public CompositeMessageEncoder add(MessageEncoder encoder) {
3232
if (encoder instanceof TemplateEngine) {
3333
templateEngine.add((TemplateEngine) encoder);
3434
} else {
35-
decoders.add(encoder);
35+
this.encoder.add(encoder);
3636
}
3737
return this;
3838
}
@@ -85,7 +85,7 @@ public CompositeMessageEncoder add(MessageEncoder encoder) {
8585
ctx.send((ByteBuffer) value);
8686
return null;
8787
}
88-
Iterator<MessageEncoder> iterator = decoders.iterator();
88+
Iterator<MessageEncoder> iterator = encoder.iterator();
8989
/** NOTE: looks like an infinite loop but there is a default renderer at the end of iterator. */
9090
byte[] result = null;
9191
while (result == null) {
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal;
7+
8+
import io.jooby.AttachedFile;
9+
import io.jooby.Context;
10+
import io.jooby.DefaultContext;
11+
import io.jooby.ForwardingContext;
12+
import io.jooby.MediaType;
13+
import io.jooby.MessageEncoder;
14+
import io.jooby.Route;
15+
import io.jooby.Sender;
16+
import io.jooby.SneakyThrows;
17+
import io.jooby.StatusCode;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
import javax.annotation.Nonnull;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.OutputStream;
24+
import java.io.PrintWriter;
25+
import java.nio.ByteBuffer;
26+
import java.nio.channels.FileChannel;
27+
import java.nio.channels.ReadableByteChannel;
28+
import java.nio.charset.Charset;
29+
import java.nio.charset.StandardCharsets;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
33+
public class HeadContext extends ForwardingContext {
34+
/**
35+
* Creates a new forwarding context.
36+
*
37+
* @param context Source context.
38+
*/
39+
public HeadContext(@Nonnull Context context) {
40+
super(context);
41+
}
42+
43+
@Nonnull @Override public Context send(@Nonnull Path file) {
44+
try {
45+
ctx.setResponseLength(Files.size(file));
46+
ctx.setResponseType(MediaType.byFile(file));
47+
ctx.send(StatusCode.OK);
48+
return this;
49+
} catch (IOException x) {
50+
throw SneakyThrows.propagate(x);
51+
}
52+
}
53+
54+
@Nonnull @Override public Context send(@Nonnull byte[] data) {
55+
ctx.setResponseLength(data.length);
56+
ctx.send(StatusCode.OK);
57+
return this;
58+
}
59+
60+
@Nonnull @Override public Context send(@Nonnull String data) {
61+
return send(data, StandardCharsets.UTF_8);
62+
}
63+
64+
@Nonnull @Override public Context send(@Nonnull ByteBuffer data) {
65+
ctx.setResponseLength(data.remaining());
66+
ctx.send(StatusCode.OK);
67+
return this;
68+
}
69+
70+
@Nonnull @Override public Context send(@Nonnull FileChannel file) {
71+
try {
72+
ctx.setResponseLength(file.size());
73+
ctx.send(StatusCode.OK);
74+
return this;
75+
} catch (IOException x) {
76+
throw SneakyThrows.propagate(x);
77+
}
78+
}
79+
80+
@Nonnull @Override public Context send(@Nonnull AttachedFile file) {
81+
ctx.setResponseLength(file.getFileSize());
82+
ctx.setResponseType(file.getContentType());
83+
ctx.send(StatusCode.OK);
84+
return this;
85+
}
86+
87+
@Nonnull @Override public Context send(@Nonnull InputStream input) {
88+
ctx.setResponseHeader("Transfer-Encoding", "chunked");
89+
ctx.send(input);
90+
ctx.send(StatusCode.OK);
91+
return this;
92+
}
93+
94+
@Nonnull @Override public Context send(@Nonnull StatusCode statusCode) {
95+
ctx.send(statusCode);
96+
return this;
97+
}
98+
99+
@Nonnull @Override public Context send(@Nonnull ReadableByteChannel channel) {
100+
ctx.setResponseHeader("Transfer-Encoding", "chunked");
101+
ctx.send(StatusCode.OK);
102+
return this;
103+
}
104+
105+
@Nonnull @Override public Context send(@Nonnull String data, @Nonnull Charset charset) {
106+
ctx.setResponseLength(data.getBytes(charset).length);
107+
ctx.send(StatusCode.OK);
108+
return this;
109+
}
110+
111+
@Nonnull @Override public Context render(@Nonnull Object value) {
112+
try {
113+
Route route = getRoute();
114+
MessageEncoder encoder = route.getEncoder();
115+
byte[] bytes = encoder.encode(this, value);
116+
if (bytes == null) {
117+
if (!isResponseStarted()) {
118+
throw new IllegalStateException("The response was not encoded");
119+
}
120+
} else {
121+
send(bytes);
122+
}
123+
return this;
124+
} catch (Exception x) {
125+
throw SneakyThrows.propagate(x);
126+
}
127+
}
128+
129+
@Nonnull @Override public Sender responseSender() {
130+
ctx.setResponseHeader("Transfer-Encoding", "chunked");
131+
ctx.send(StatusCode.OK);
132+
return new NoopSender();
133+
}
134+
135+
@Nonnull @Override public OutputStream responseStream() {
136+
ctx.setResponseHeader("Transfer-Encoding", "chunked");
137+
ctx.send(StatusCode.OK);
138+
return new NoopOutputStream();
139+
}
140+
141+
@Nonnull @Override public PrintWriter responseWriter() {
142+
return new PrintWriter(responseStream());
143+
}
144+
145+
private static class NoopOutputStream extends OutputStream {
146+
@Override public void write(@NotNull byte[] b) throws IOException {
147+
}
148+
149+
@Override public void write(@NotNull byte[] b, int off, int len) throws IOException {
150+
}
151+
152+
@Override public void write(int b) throws IOException {
153+
}
154+
}
155+
156+
private static class NoopSender implements Sender {
157+
@Nonnull @Override public Sender write(@Nonnull byte[] data, @Nonnull Callback callback) {
158+
return this;
159+
}
160+
161+
@Override public void close() {
162+
}
163+
}
164+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal;
7+
8+
import io.jooby.Context;
9+
import io.jooby.Route;
10+
import io.jooby.Router;
11+
import io.jooby.internal.handler.LinkedHandler;
12+
13+
import javax.annotation.Nonnull;
14+
15+
public class HeadResponseHandler implements LinkedHandler {
16+
private Route.Handler next;
17+
18+
public HeadResponseHandler(Route.Handler next) {
19+
this.next = next;
20+
}
21+
22+
@Override public Route.Handler next() {
23+
return next;
24+
}
25+
26+
@Nonnull @Override public Object apply(@Nonnull Context ctx) throws Exception {
27+
return ctx.getMethod().equals(Router.HEAD)
28+
? next.apply(new HeadContext(ctx))
29+
: next.apply(ctx);
30+
}
31+
}

0 commit comments

Comments
 (0)