|
@@ -2,135 +2,122 @@ package hello;
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
import io.netty.buffer.Unpooled;
|
|
|
+
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.ScheduledFuture;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.*;
|
|
|
+import io.netty.channel.Channel;
|
|
|
+import io.netty.channel.ChannelFuture;
|
|
|
import io.netty.channel.ChannelFutureListener;
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
-import io.netty.channel.MessageList;
|
|
|
import io.netty.channel.SimpleChannelInboundHandler;
|
|
|
-import io.netty.handler.codec.http.Cookie;
|
|
|
-import io.netty.handler.codec.http.CookieDecoder;
|
|
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
|
|
import io.netty.handler.codec.http.FullHttpResponse;
|
|
|
import io.netty.handler.codec.http.HttpHeaders;
|
|
|
import io.netty.handler.codec.http.HttpRequest;
|
|
|
-import io.netty.handler.codec.http.HttpResponse;
|
|
|
-import io.netty.handler.codec.http.ServerCookieEncoder;
|
|
|
-
|
|
|
-import java.util.Map;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.Set;
|
|
|
-
|
|
|
-import java.io.*;
|
|
|
-
|
|
|
-import com.fasterxml.jackson.databind.*;
|
|
|
+import io.netty.handler.codec.http.HttpResponseStatus;
|
|
|
+import io.netty.handler.codec.http.HttpVersion;
|
|
|
import io.netty.util.CharsetUtil;
|
|
|
|
|
|
-import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
|
|
-import static io.netty.handler.codec.http.HttpHeaders.*;
|
|
|
-import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
|
|
-import static io.netty.handler.codec.http.HttpVersion.*;
|
|
|
|
|
|
-public class HelloServerHandler extends SimpleChannelInboundHandler<Object>{
|
|
|
+public class HelloServerHandler extends SimpleChannelInboundHandler<Object> {
|
|
|
+ private final SimpleDateFormat format = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
|
|
|
+ private CharSequence date;
|
|
|
|
|
|
- /** Buffer that stores the response content */
|
|
|
- private static final ObjectMapper mapper = new ObjectMapper();
|
|
|
+ private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
|
+ private final ByteBuf buffer = Unpooled.directBuffer().writeBytes("Hello, World!".getBytes(CharsetUtil.UTF_8));
|
|
|
+ private final CharSequence contentLength = HttpHeaders.newEntity(String.valueOf(buffer.readableBytes()));
|
|
|
|
|
|
- private MessageList<Object> out;
|
|
|
+ private static final CharSequence TYPE_PLAIN = HttpHeaders.newEntity("text/plain; charset=UTF-8");
|
|
|
+ private static final CharSequence TYPE_JSON = HttpHeaders.newEntity("application/json; charset=UTF-8");
|
|
|
|
|
|
- @Override
|
|
|
- public void beginMessageReceived(ChannelHandlerContext ctx) throws Exception {
|
|
|
- out = MessageList.newInstance();
|
|
|
- super.beginMessageReceived(ctx);
|
|
|
- }
|
|
|
+ private static final CharSequence SERVER_NAME = HttpHeaders.newEntity("Netty");
|
|
|
+ private static final CharSequence CONTENT_TYPE_ENTITY = HttpHeaders.newEntity(HttpHeaders.Names.CONTENT_TYPE);
|
|
|
+ private static final CharSequence DATE_ENTITY = HttpHeaders.newEntity(HttpHeaders.Names.DATE);
|
|
|
+ private static final CharSequence CONTENT_LENGTH_ENTITY = HttpHeaders.newEntity(HttpHeaders.Names.CONTENT_LENGTH);
|
|
|
+ private static final CharSequence SERVER_ENTITY = HttpHeaders.newEntity(HttpHeaders.Names.SERVER);
|
|
|
|
|
|
@Override
|
|
|
- public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
|
+ public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
|
if (msg instanceof HttpRequest) {
|
|
|
HttpRequest request = (HttpRequest) msg;
|
|
|
-
|
|
|
- if (is100ContinueExpected(request)) {
|
|
|
- send100Continue(out);
|
|
|
+ String uri = request.getUri();
|
|
|
+ switch (uri) {
|
|
|
+ case "/plaintext":
|
|
|
+ writeResponse(ctx, request, buffer.duplicate().retain(), TYPE_PLAIN, contentLength);
|
|
|
+ return;
|
|
|
+ case "/json":
|
|
|
+ byte[] json = MAPPER.writeValueAsBytes(Collections.singletonMap("message", "Hello, World!"));
|
|
|
+ writeResponse(ctx, request, ctx.alloc().buffer(json.length).writeBytes(json), TYPE_JSON,
|
|
|
+ String.valueOf(json.length));
|
|
|
+ return;
|
|
|
}
|
|
|
- ByteBuf buf = ctx.alloc().buffer();
|
|
|
- if("/plaintext".equals(request.getUri())) {
|
|
|
- buf.writeBytes("Hello, World!".getBytes(CharsetUtil.UTF_8));
|
|
|
- } else {
|
|
|
- Map<String, String> data = new HashMap<String, String>();
|
|
|
- data.put("message", "Hello, world");
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- buf.writeBytes(HelloServerHandler.mapper.writeValueAsBytes(data));
|
|
|
- }
|
|
|
- catch (IOException ex)
|
|
|
- {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
- }
|
|
|
- writeResponse(ctx, request, buf, out);
|
|
|
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, Unpooled.EMPTY_BUFFER, false);
|
|
|
+ ctx.write(response).addListener(ChannelFutureListener.CLOSE);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void writeResponse(ChannelHandlerContext ctx, HttpRequest request, ByteBuf buf, MessageList<Object> out) {
|
|
|
+ private void writeResponse(ChannelHandlerContext ctx, HttpRequest request, ByteBuf buf,
|
|
|
+ CharSequence contentType, CharSequence contentLength) {
|
|
|
// Decide whether to close the connection or not.
|
|
|
- boolean keepAlive = isKeepAlive(request);
|
|
|
+ boolean keepAlive = HttpHeaders.isKeepAlive(request);
|
|
|
// Build the response object.
|
|
|
FullHttpResponse response = new DefaultFullHttpResponse(
|
|
|
- HTTP_1_1, OK, buf);
|
|
|
-
|
|
|
- response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
|
|
|
+ HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf, false);
|
|
|
+ HttpHeaders headers = response.headers();
|
|
|
+ headers.set(CONTENT_TYPE_ENTITY, contentType);
|
|
|
+ headers.set(SERVER_ENTITY, SERVER_NAME);
|
|
|
+ headers.set(DATE_ENTITY, date);
|
|
|
|
|
|
if (keepAlive) {
|
|
|
- // Add 'Content-Length' header only for a keep-alive connection.
|
|
|
- response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
|
|
|
- // Add keep alive header as per:
|
|
|
- // - http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
|
|
|
- response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
|
|
|
- }
|
|
|
-
|
|
|
- // Encode the cookie.
|
|
|
- String cookieString = request.headers().get(COOKIE);
|
|
|
- if (cookieString != null) {
|
|
|
- Set<Cookie> cookies = CookieDecoder.decode(cookieString);
|
|
|
- if (!cookies.isEmpty()) {
|
|
|
- // Reset the cookies if necessary.
|
|
|
- for (Cookie cookie: cookies) {
|
|
|
- response.headers().add(SET_COOKIE, ServerCookieEncoder.encode(cookie));
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- // Browser sent no cookie. Add some.
|
|
|
- response.headers().add(SET_COOKIE, ServerCookieEncoder.encode("key1", "value1"));
|
|
|
- response.headers().add(SET_COOKIE, ServerCookieEncoder.encode("key2", "value2"));
|
|
|
+ headers.set(CONTENT_LENGTH_ENTITY, contentLength);
|
|
|
}
|
|
|
|
|
|
- // Write the response.
|
|
|
- out.add(response);
|
|
|
-
|
|
|
// Close the non-keep-alive connection after the write operation is done.
|
|
|
if (!keepAlive) {
|
|
|
- this.out = MessageList.newInstance();
|
|
|
- ctx.write(out).addListener(ChannelFutureListener.CLOSE);
|
|
|
+ ctx.write(response).addListener(ChannelFutureListener.CLOSE);
|
|
|
+ } else {
|
|
|
+ ctx.write(response, ctx.voidPromise());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void send100Continue(MessageList<Object> out) {
|
|
|
- HttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER);
|
|
|
- out.add(response);
|
|
|
+ @Override
|
|
|
+ public void exceptionCaught(
|
|
|
+ ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
|
+ ctx.close();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void endMessageReceived(ChannelHandlerContext ctx) throws Exception {
|
|
|
- if (out != null) {
|
|
|
- MessageList<Object> msgs = out;
|
|
|
- this.out = null;
|
|
|
- ctx.write(msgs);
|
|
|
- }
|
|
|
+ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
|
|
+ ctx.flush();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void exceptionCaught(
|
|
|
- ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
|
- cause.printStackTrace();
|
|
|
- ctx.close();
|
|
|
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
|
+ super.handlerRemoved(ctx);
|
|
|
+ buffer.release();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
|
+ date = HttpHeaders.newEntity(format.format(new Date()));
|
|
|
+
|
|
|
+ Channel channel = ctx.channel();
|
|
|
+ final ScheduledFuture<?> future = channel.eventLoop().scheduleWithFixedDelay(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ date = HttpHeaders.newEntity(format.format(new Date()));
|
|
|
+ }
|
|
|
+ }, 1000, 1000, TimeUnit.MILLISECONDS);
|
|
|
+
|
|
|
+ channel.closeFuture().addListener(new ChannelFutureListener() {
|
|
|
+ @Override
|
|
|
+ public void operationComplete(ChannelFuture channelFuture) throws Exception {
|
|
|
+ future.cancel(false);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
}
|