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    *   http://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.netty.example.http.snoop;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.SimpleChannelInboundHandler;
23  import io.netty.handler.codec.DecoderResult;
24  import io.netty.handler.codec.http.Cookie;
25  import io.netty.handler.codec.http.CookieDecoder;
26  import io.netty.handler.codec.http.DefaultFullHttpResponse;
27  import io.netty.handler.codec.http.FullHttpResponse;
28  import io.netty.handler.codec.http.HttpContent;
29  import io.netty.handler.codec.http.HttpHeaders;
30  import io.netty.handler.codec.http.HttpObject;
31  import io.netty.handler.codec.http.HttpRequest;
32  import io.netty.handler.codec.http.LastHttpContent;
33  import io.netty.handler.codec.http.QueryStringDecoder;
34  import io.netty.handler.codec.http.ServerCookieEncoder;
35  import io.netty.util.CharsetUtil;
36  
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Map.Entry;
40  import java.util.Set;
41  
42  import static io.netty.handler.codec.http.HttpHeaders.Names.*;
43  import static io.netty.handler.codec.http.HttpResponseStatus.*;
44  import static io.netty.handler.codec.http.HttpVersion.*;
45  
46  public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object> {
47  
48      private HttpRequest request;
49      /** Buffer that stores the response content */
50      private final StringBuilder buf = new StringBuilder();
51  
52      @Override
53      public void channelReadComplete(ChannelHandlerContext ctx) {
54          ctx.flush();
55      }
56  
57      @Override
58      protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
59          if (msg instanceof HttpRequest) {
60              HttpRequest request = this.request = (HttpRequest) msg;
61  
62              if (HttpHeaders.is100ContinueExpected(request)) {
63                  send100Continue(ctx);
64              }
65  
66              buf.setLength(0);
67              buf.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
68              buf.append("===================================\r\n");
69  
70              buf.append("VERSION: ").append(request.getProtocolVersion()).append("\r\n");
71              buf.append("HOSTNAME: ").append(HttpHeaders.getHost(request, "unknown")).append("\r\n");
72              buf.append("REQUEST_URI: ").append(request.getUri()).append("\r\n\r\n");
73  
74              HttpHeaders headers = request.headers();
75              if (!headers.isEmpty()) {
76                  for (Map.Entry<String, String> h: headers) {
77                      String key = h.getKey();
78                      String value = h.getValue();
79                      buf.append("HEADER: ").append(key).append(" = ").append(value).append("\r\n");
80                  }
81                  buf.append("\r\n");
82              }
83  
84              QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
85              Map<String, List<String>> params = queryStringDecoder.parameters();
86              if (!params.isEmpty()) {
87                  for (Entry<String, List<String>> p: params.entrySet()) {
88                      String key = p.getKey();
89                      List<String> vals = p.getValue();
90                      for (String val : vals) {
91                          buf.append("PARAM: ").append(key).append(" = ").append(val).append("\r\n");
92                      }
93                  }
94                  buf.append("\r\n");
95              }
96  
97              appendDecoderResult(buf, request);
98          }
99  
100         if (msg instanceof HttpContent) {
101             HttpContent httpContent = (HttpContent) msg;
102 
103             ByteBuf content = httpContent.content();
104             if (content.isReadable()) {
105                 buf.append("CONTENT: ");
106                 buf.append(content.toString(CharsetUtil.UTF_8));
107                 buf.append("\r\n");
108                 appendDecoderResult(buf, request);
109             }
110 
111             if (msg instanceof LastHttpContent) {
112                 buf.append("END OF CONTENT\r\n");
113 
114                 LastHttpContent trailer = (LastHttpContent) msg;
115                 if (!trailer.trailingHeaders().isEmpty()) {
116                     buf.append("\r\n");
117                     for (String name: trailer.trailingHeaders().names()) {
118                         for (String value: trailer.trailingHeaders().getAll(name)) {
119                             buf.append("TRAILING HEADER: ");
120                             buf.append(name).append(" = ").append(value).append("\r\n");
121                         }
122                     }
123                     buf.append("\r\n");
124                 }
125 
126                 if (!writeResponse(trailer, ctx)) {
127                     // If keep-alive is off, close the connection once the content is fully written.
128                     ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
129                 }
130             }
131         }
132     }
133 
134     private static void appendDecoderResult(StringBuilder buf, HttpObject o) {
135         DecoderResult result = o.getDecoderResult();
136         if (result.isSuccess()) {
137             return;
138         }
139 
140         buf.append(".. WITH DECODER FAILURE: ");
141         buf.append(result.cause());
142         buf.append("\r\n");
143     }
144 
145     private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
146         // Decide whether to close the connection or not.
147         boolean keepAlive = HttpHeaders.isKeepAlive(request);
148         // Build the response object.
149         FullHttpResponse response = new DefaultFullHttpResponse(
150                 HTTP_1_1, currentObj.getDecoderResult().isSuccess()? OK : BAD_REQUEST,
151                 Unpooled.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
152 
153         response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
154 
155         if (keepAlive) {
156             // Add 'Content-Length' header only for a keep-alive connection.
157             response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
158             // Add keep alive header as per:
159             // - http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
160             response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
161         }
162 
163         // Encode the cookie.
164         String cookieString = request.headers().get(COOKIE);
165         if (cookieString != null) {
166             Set<Cookie> cookies = CookieDecoder.decode(cookieString);
167             if (!cookies.isEmpty()) {
168                 // Reset the cookies if necessary.
169                 for (Cookie cookie: cookies) {
170                     response.headers().add(SET_COOKIE, ServerCookieEncoder.encode(cookie));
171                 }
172             }
173         } else {
174             // Browser sent no cookie.  Add some.
175             response.headers().add(SET_COOKIE, ServerCookieEncoder.encode("key1", "value1"));
176             response.headers().add(SET_COOKIE, ServerCookieEncoder.encode("key2", "value2"));
177         }
178 
179         // Write the response.
180         ctx.write(response);
181 
182         return keepAlive;
183     }
184 
185     private static void send100Continue(ChannelHandlerContext ctx) {
186         FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE);
187         ctx.write(response);
188     }
189 
190     @Override
191     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
192         cause.printStackTrace();
193         ctx.close();
194     }
195 }