View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.example.http.snoop;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.channel.ChannelFutureListeners;
20  import io.netty5.channel.ChannelHandlerContext;
21  import io.netty5.channel.SimpleChannelInboundHandler;
22  import io.netty5.handler.codec.DecoderResult;
23  import io.netty5.handler.codec.http.DefaultFullHttpResponse;
24  import io.netty5.handler.codec.http.FullHttpResponse;
25  import io.netty5.handler.codec.http.HttpContent;
26  import io.netty5.handler.codec.http.HttpHeaderNames;
27  import io.netty5.handler.codec.http.HttpHeaderValues;
28  import io.netty5.handler.codec.http.HttpHeaders;
29  import io.netty5.handler.codec.http.HttpObject;
30  import io.netty5.handler.codec.http.HttpRequest;
31  import io.netty5.handler.codec.http.HttpUtil;
32  import io.netty5.handler.codec.http.LastHttpContent;
33  import io.netty5.handler.codec.http.QueryStringDecoder;
34  import io.netty5.handler.codec.http.cookie.Cookie;
35  import io.netty5.handler.codec.http.cookie.ServerCookieDecoder;
36  import io.netty5.handler.codec.http.cookie.ServerCookieEncoder;
37  import io.netty5.util.CharsetUtil;
38  
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Map.Entry;
42  import java.util.Set;
43  
44  import static io.netty5.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
45  import static io.netty5.handler.codec.http.HttpResponseStatus.CONTINUE;
46  import static io.netty5.handler.codec.http.HttpResponseStatus.OK;
47  import static io.netty5.handler.codec.http.HttpVersion.HTTP_1_1;
48  import static java.nio.charset.StandardCharsets.UTF_8;
49  
50  public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object> {
51  
52      private HttpRequest request;
53      /** Buffer that stores the response content */
54      private final StringBuilder buf = new StringBuilder();
55  
56      @Override
57      public void channelReadComplete(ChannelHandlerContext ctx) {
58          ctx.flush();
59      }
60  
61      @Override
62      protected void messageReceived(ChannelHandlerContext ctx, Object msg) {
63          if (msg instanceof HttpRequest) {
64              HttpRequest request = this.request = (HttpRequest) msg;
65  
66              if (HttpUtil.is100ContinueExpected(request)) {
67                  send100Continue(ctx);
68              }
69  
70              buf.setLength(0);
71              buf.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
72              buf.append("===================================\r\n");
73  
74              buf.append("VERSION: ").append(request.protocolVersion()).append("\r\n");
75              buf.append("HOSTNAME: ").append(request.headers().get(HttpHeaderNames.HOST, "unknown")).append("\r\n");
76              buf.append("REQUEST_URI: ").append(request.uri()).append("\r\n\r\n");
77  
78              HttpHeaders headers = request.headers();
79              if (!headers.isEmpty()) {
80                  for (Map.Entry<String, String> h: headers) {
81                      CharSequence key = h.getKey();
82                      CharSequence value = h.getValue();
83                      buf.append("HEADER: ").append(key).append(" = ").append(value).append("\r\n");
84                  }
85                  buf.append("\r\n");
86              }
87  
88              QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
89              Map<String, List<String>> params = queryStringDecoder.parameters();
90              if (!params.isEmpty()) {
91                  for (Entry<String, List<String>> p: params.entrySet()) {
92                      String key = p.getKey();
93                      List<String> vals = p.getValue();
94                      for (String val : vals) {
95                          buf.append("PARAM: ").append(key).append(" = ").append(val).append("\r\n");
96                      }
97                  }
98                  buf.append("\r\n");
99              }
100 
101             appendDecoderResult(buf, request);
102         }
103 
104         if (msg instanceof HttpContent) {
105             HttpContent<?> httpContent = (HttpContent<?>) msg;
106 
107             Buffer content = httpContent.payload();
108             if (content.readableBytes() > 0) {
109                 buf.append("CONTENT: ");
110                 buf.append(content.toString(CharsetUtil.UTF_8));
111                 buf.append("\r\n");
112                 appendDecoderResult(buf, request);
113             }
114 
115             if (msg instanceof LastHttpContent) {
116                 buf.append("END OF CONTENT\r\n");
117 
118                 LastHttpContent<?> trailer = (LastHttpContent<?>) msg;
119                 if (!trailer.trailingHeaders().isEmpty()) {
120                     buf.append("\r\n");
121                     for (CharSequence name: trailer.trailingHeaders().names()) {
122                         for (CharSequence value: trailer.trailingHeaders().getAll(name)) {
123                             buf.append("TRAILING HEADER: ");
124                             buf.append(name).append(" = ").append(value).append("\r\n");
125                         }
126                     }
127                     buf.append("\r\n");
128                 }
129 
130                 if (!writeResponse(trailer, ctx)) {
131                     // If keep-alive is off, close the connection once the content is fully written.
132                     ctx.writeAndFlush(ctx.bufferAllocator().allocate(0)).addListener(ctx, ChannelFutureListeners.CLOSE);
133                 }
134             }
135         }
136     }
137 
138     private static void appendDecoderResult(StringBuilder buf, HttpObject o) {
139         DecoderResult result = o.decoderResult();
140         if (result.isSuccess()) {
141             return;
142         }
143 
144         buf.append(".. WITH DECODER FAILURE: ");
145         buf.append(result.cause());
146         buf.append("\r\n");
147     }
148 
149     private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
150         // Decide whether to close the connection or not.
151         boolean keepAlive = HttpUtil.isKeepAlive(request);
152         // Build the response object.
153         FullHttpResponse response = new DefaultFullHttpResponse(
154                 HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,
155                 ctx.bufferAllocator().copyOf(buf.toString(), UTF_8));
156 
157         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
158 
159         if (keepAlive) {
160             // Add 'Content-Length' header only for a keep-alive connection.
161             response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.payload().readableBytes());
162             // Add keep alive header as per:
163             // - https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
164             response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
165         }
166 
167         // Encode the cookie.
168         String cookieString = request.headers().get(HttpHeaderNames.COOKIE);
169         if (cookieString != null) {
170             Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
171             if (!cookies.isEmpty()) {
172                 // Reset the cookies if necessary.
173                 for (Cookie cookie: cookies) {
174                     response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
175                 }
176             }
177         } else {
178             // Browser sent no cookie.  Add some.
179             response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode("key1", "value1"));
180             response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode("key2", "value2"));
181         }
182 
183         // Write the response.
184         ctx.write(response);
185 
186         return keepAlive;
187     }
188 
189     private static void send100Continue(ChannelHandlerContext ctx) {
190         FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE,
191                 ctx.bufferAllocator().allocate(0));
192         ctx.write(response);
193     }
194 
195     @Override
196     public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
197         cause.printStackTrace();
198         ctx.close();
199     }
200 }