View Javadoc
1   /*
2    * Copyright 2017 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.handler.codec.http;
17  
18  import io.netty5.buffer.api.BufferAllocator;
19  import io.netty5.channel.ChannelFutureListeners;
20  import io.netty5.channel.ChannelHandler;
21  import io.netty5.channel.ChannelHandlerContext;
22  import io.netty5.util.Resource;
23  
24  import static io.netty5.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
25  import static io.netty5.handler.codec.http.HttpResponseStatus.CONTINUE;
26  import static io.netty5.handler.codec.http.HttpResponseStatus.EXPECTATION_FAILED;
27  import static io.netty5.handler.codec.http.HttpVersion.HTTP_1_1;
28  
29  /**
30   * Sends a <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3">100 CONTINUE</a>
31   * {@link HttpResponse} to {@link HttpRequest}s which contain a 'expect: 100-continue' header. It
32   * should only be used for applications which do <b>not</b> install the {@link HttpObjectAggregator}.
33   * <p>
34   * By default it accepts all expectations.
35   * <p>
36   * Since {@link HttpServerExpectContinueHandler} expects {@link HttpRequest}s it should be added after {@link
37   * HttpServerCodec} but before any other handlers that might send a {@link HttpResponse}. <blockquote>
38   * <pre>
39   *  {@link io.netty5.channel.ChannelPipeline} p = ...;
40   *  ...
41   *  p.addLast("serverCodec", new {@link HttpServerCodec}());
42   *  p.addLast("respondExpectContinue", <b>new {@link HttpServerExpectContinueHandler}()</b>);
43   *  ...
44   *  p.addLast("handler", new HttpRequestHandler());
45   *  </pre>
46   * </blockquote>
47   */
48  public class HttpServerExpectContinueHandler implements ChannelHandler {
49      /**
50       * Produces a {@link HttpResponse} for {@link HttpRequest}s which define an expectation. Returns {@code null} if the
51       * request should be rejected. See {@link #rejectResponse(BufferAllocator, HttpRequest)}.
52       */
53      protected HttpResponse acceptMessage(BufferAllocator allocator, @SuppressWarnings("unused") HttpRequest request) {
54          return newEmptyResponse(allocator, CONTINUE);
55      }
56  
57      /**
58       * Returns the appropriate 4XX {@link HttpResponse} for the given {@link HttpRequest}.
59       */
60      protected HttpResponse rejectResponse(BufferAllocator allocator, @SuppressWarnings("unused") HttpRequest request) {
61          return newEmptyResponse(allocator, EXPECTATION_FAILED);
62      }
63  
64      private static DefaultFullHttpResponse newEmptyResponse(BufferAllocator allocator, HttpResponseStatus status) {
65          final DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, status, allocator.allocate(0));
66          resp.headers().set(CONTENT_LENGTH, 0);
67          return resp;
68      }
69  
70      @Override
71      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
72          if (msg instanceof HttpRequest) {
73              HttpRequest req = (HttpRequest) msg;
74  
75              if (HttpUtil.is100ContinueExpected(req)) {
76                  HttpResponse accept = acceptMessage(ctx.bufferAllocator(), req);
77  
78                  if (accept == null) {
79                      // the expectation failed so we refuse the request.
80                      HttpResponse rejection = rejectResponse(ctx.bufferAllocator(), req);
81                      Resource.dispose(msg);
82                      ctx.writeAndFlush(rejection).addListener(ctx.channel(), ChannelFutureListeners.CLOSE_ON_FAILURE);
83                      return;
84                  }
85  
86                  ctx.writeAndFlush(accept).addListener(ctx.channel(), ChannelFutureListeners.CLOSE_ON_FAILURE);
87                  req.headers().remove(HttpHeaderNames.EXPECT);
88              }
89          }
90          ctx.fireChannelRead(msg);
91      }
92  }