View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.gateway.kernel.rest;
21  
22  import com.fasterxml.jackson.databind.JsonNode;
23  import com.fasterxml.jackson.databind.node.ObjectNode;
24  import io.netty.buffer.ByteBuf;
25  import io.netty.buffer.Unpooled;
26  import io.netty.channel.ChannelFuture;
27  import io.netty.channel.ChannelFutureListener;
28  import io.netty.channel.ChannelHandlerContext;
29  import io.netty.channel.SimpleChannelInboundHandler;
30  import io.netty.channel.group.ChannelGroup;
31  import io.netty.handler.codec.http.DefaultFullHttpResponse;
32  import io.netty.handler.codec.http.FullHttpRequest;
33  import io.netty.handler.codec.http.FullHttpResponse;
34  import io.netty.handler.codec.http.HttpContent;
35  import io.netty.handler.codec.http.HttpHeaderNames;
36  import io.netty.handler.codec.http.HttpHeaderValues;
37  import io.netty.handler.codec.http.HttpMethod;
38  import io.netty.handler.codec.http.HttpObject;
39  import io.netty.handler.codec.http.HttpRequest;
40  import io.netty.handler.codec.http.HttpResponseStatus;
41  import io.netty.handler.codec.http.HttpUtil;
42  import io.netty.handler.codec.http.HttpVersion;
43  import io.netty.handler.codec.http.LastHttpContent;
44  import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
45  import io.netty.handler.codec.http.multipart.Attribute;
46  import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
47  import io.netty.handler.codec.http.multipart.DiskAttribute;
48  import io.netty.handler.codec.http.multipart.DiskFileUpload;
49  import io.netty.handler.codec.http.multipart.FileUpload;
50  import io.netty.handler.codec.http.multipart.HttpDataFactory;
51  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
52  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
53  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
54  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
55  import io.netty.handler.codec.http.multipart.InterfaceHttpData;
56  import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
57  import org.waarp.common.crypto.ssl.WaarpSslUtility;
58  import org.waarp.common.database.DbSession;
59  import org.waarp.common.exception.CryptoException;
60  import org.waarp.common.json.JsonHandler;
61  import org.waarp.common.logging.WaarpLogger;
62  import org.waarp.common.logging.WaarpLoggerFactory;
63  import org.waarp.common.utility.WaarpNettyUtil;
64  import org.waarp.common.utility.WaarpStringUtils;
65  import org.waarp.gateway.kernel.database.DbConstantGateway;
66  import org.waarp.gateway.kernel.exception.HttpForbiddenRequestException;
67  import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException;
68  import org.waarp.gateway.kernel.exception.HttpInvalidAuthenticationException;
69  import org.waarp.gateway.kernel.exception.HttpMethodNotAllowedRequestException;
70  import org.waarp.gateway.kernel.exception.HttpNotFoundRequestException;
71  
72  import java.io.File;
73  import java.io.IOException;
74  import java.nio.channels.ClosedChannelException;
75  import java.util.HashMap;
76  import java.util.Iterator;
77  import java.util.List;
78  import java.util.Map.Entry;
79  
80  /**
81   * Handler for HTTP Rest support
82   */
83  public abstract class HttpRestHandler
84      extends SimpleChannelInboundHandler<HttpObject> {
85    /**
86     * Internal Logger
87     */
88    private static final WaarpLogger logger =
89        WaarpLoggerFactory.getLogger(HttpRestHandler.class);
90  
91    /*
92     * Note: Presence de BODY dans toutes les requetes/responses = Content-Length ou Transfer-Encoding HEAD:
93     * response pas de BODY
94     *
95     */
96  
97    public enum METHOD {
98      /**
99       * REST: Standard GET item
100      * <p>
101      * The GET method means retrieve whatever information (in the form of
102      * an
103      * entity) is identified by the
104      * Request-URI. If the Request-URI refers to a data-producing process,
105      * it is
106      * the produced data which shall be
107      * returned as the entity in the response and not the source text of
108      * the
109      * process, unless that text happens to
110      * be the output of the process.
111      */
112     GET(HttpMethod.GET),
113     /**
114      * REST: Update existing item
115      * <p>
116      * The PUT method requests that the enclosed entity be stored under the
117      * supplied Request-URI.
118      */
119     PUT(HttpMethod.PUT),
120     /**
121      * REST: Create a new item
122      * <p>
123      * The POST method is used to request that the origin server accept the
124      * entity enclosed in the request as a
125      * new subordinate of the resource identified by the Request-URI in the
126      * Request-Line.
127      */
128     POST(HttpMethod.POST),
129     /**
130      * REST: Delete existing item
131      * <p>
132      * The DELETE method requests that the origin server delete the resource
133      * identified by the Request-URI.
134      */
135     DELETE(HttpMethod.DELETE),
136     /**
137      * REST: what options are supported for the URI
138      * <p>
139      * The OPTIONS method represents a request for information about the
140      * communication options available on the
141      * request/response chain identified by the Request-URI. This method
142      * allows
143      * the client to determine the
144      * options and/or requirements associated with a resource, or the
145      * capabilities of a server, without implying a
146      * resource action or initiating a resource retrieval.
147      */
148     OPTIONS(HttpMethod.OPTIONS),
149     /**
150      * REST: as GET but no BODY (existence ? metadata ?)
151      * <p>
152      * The HEAD method is identical to GET except that the server MUST NOT
153      * return a message-body in the response.
154      */
155     HEAD(HttpMethod.HEAD),
156     /**
157      * REST: should not be used, use POST instead
158      * <p>
159      * The PATCH method requests that a set of changes described in the
160      * request
161      * entity be applied to the resource
162      * identified by the Request-URI.
163      */
164     PATCH(HttpMethod.PATCH),
165     /**
166      * REST: unknown usage
167      * <p>
168      * The TRACE method is used to invoke a remote, application-layer
169      * loop-back
170      * of the request message.
171      */
172     TRACE(HttpMethod.TRACE),
173     /**
174      * REST: unknown
175      * <p>
176      * This specification reserves the method name CONNECT for use with a
177      * proxy
178      * that can dynamically switch to
179      * being a tunnel
180      */
181     CONNECT(HttpMethod.CONNECT);
182 
183     public final HttpMethod method;
184 
185     METHOD(final HttpMethod method) {
186       this.method = method;
187     }
188   }
189 
190   public static final HttpDataFactory factory =
191       new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
192   // Disk if size exceed MINSIZE = 16K
193   // XXX FIXME TODO to setup outside !
194   public static String TempPath = "J:/GG/ARK/TMP";
195 
196   public static ChannelGroup group;
197 
198   /**
199    * Initialize the Disk support
200    *
201    * @param tempPath system temp directory
202    *
203    * @throws IOException
204    * @throws CryptoException
205    */
206   public static void initialize(final String tempPath) {
207     TempPath = tempPath;
208     final File file = new File(tempPath);
209     file.mkdirs();//NOSONAR
210     DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file
211     // on exit (in normal
212     // exit)
213     DiskFileUpload.baseDirectory = TempPath; // system temp
214     // directory
215     DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on
216     // exit (in normal exit)
217     DiskAttribute.baseDirectory = TempPath; // system temp directory
218   }
219 
220   public static final RestConfiguration defaultConfiguration =
221       new RestConfiguration();
222 
223   public HashMap<String, RestMethodHandler> restHashMap;
224   public final RestConfiguration restConfiguration;
225   protected final RootOptionsRestMethodHandler rootHandler;
226 
227   protected HttpPostRequestDecoder decoder;
228   protected HttpResponseStatus status = HttpResponseStatus.OK;
229 
230   protected HttpRequest request;
231   protected RestMethodHandler handler;
232 
233   protected DbSession dbSession;
234 
235   private boolean willClose;
236 
237   /**
238    * Arguments received
239    */
240   protected RestArgument arguments;
241   /**
242    * The only structure that might be needed is: ARGS_COOKIE (subset)
243    */
244   protected RestArgument response;
245   /**
246    * JSON decoded object
247    */
248   protected Object jsonObject;
249   /**
250    * Cumulative chunks
251    */
252   protected ByteBuf cumulativeBody;
253 
254   protected HttpRestHandler(final RestConfiguration config) {
255     restConfiguration = config;
256     rootHandler = new RootOptionsRestMethodHandler(config);
257   }
258 
259   protected static class HttpCleanChannelFutureListener
260       implements ChannelFutureListener {
261     protected final HttpRestHandler handler;
262 
263     /**
264      * @param handler
265      */
266     public HttpCleanChannelFutureListener(final HttpRestHandler handler) {
267       this.handler = handler;
268     }
269 
270     @Override
271     public final void operationComplete(final ChannelFuture future) {
272       handler.clean();
273     }
274   }
275 
276   @Override
277   public final void channelActive(final ChannelHandlerContext ctx)
278       throws Exception {
279     if (group != null) {
280       group.add(ctx.channel());
281     }
282     super.channelActive(ctx);
283   }
284 
285   /**
286    * Clean method
287    * <p>
288    * Override if needed
289    */
290   protected final void clean() {
291     if (arguments != null) {
292       arguments.clean();
293       arguments = null;
294     }
295     if (response != null) {
296       response.clean();
297       response = null;
298     }
299     if (decoder != null) {
300       decoder.cleanFiles();
301       decoder = null;
302     }
303     handler = null;
304     cumulativeBody = null;
305     jsonObject = null;
306     if (dbSession != null) {
307       dbSession.enUseConnectionNoDisconnect();
308       dbSession = null;
309     }
310   }
311 
312   /**
313    * Called at the beginning of every new request
314    * <p>
315    * Override if needed
316    */
317   protected final void initialize() {
318     // clean previous FileUpload if Any
319     clean();
320     status = HttpResponseStatus.OK;
321     request = null;
322     setWillClose(false);
323     arguments = new RestArgument(JsonHandler.createObjectNode());
324     response = new RestArgument(JsonHandler.createObjectNode());
325   }
326 
327   /**
328    * @return the DbSession associated with the current request (might be Admin
329    *     dbSession if none)
330    */
331   public final DbSession getDbSession() {
332     return dbSession == null? DbConstantGateway.admin.getSession() : dbSession;
333   }
334 
335   /**
336    * To be used for instance to check correctness of connection<br>
337    * Note that ARG_METHOD is only set from current request. It might be also
338    * set
339    * from URI or HEADER and
340    * therefore should be done in this method.
341    *
342    * @param ctx
343    *
344    * @throws HttpInvalidAuthenticationException
345    */
346   protected abstract void checkConnection(ChannelHandlerContext ctx)
347       throws HttpInvalidAuthenticationException;
348 
349   /**
350    * Method to set Cookies in httpResponse from response ObjectNode
351    *
352    * @param httpResponse
353    */
354   protected final void setCookies(final FullHttpResponse httpResponse) {
355     if (response == null) {
356       return;
357     }
358     final ObjectNode cookieON = response.getCookieArgs();
359     if (!cookieON.isMissingNode()) {
360       final Iterator<Entry<String, JsonNode>> iter = cookieON.fields();
361       while (iter.hasNext()) {
362         final Entry<String, JsonNode> entry = iter.next();
363         httpResponse.headers().add(HttpHeaderNames.SET_COOKIE,
364                                    ServerCookieEncoder.LAX.encode(
365                                        entry.getKey(),
366                                        entry.getValue().asText()));
367       }
368     }
369   }
370 
371   /**
372    * Could be overwritten if necessary
373    *
374    * @return RestMethodHandler associated with the current context
375    *
376    * @throws HttpIncorrectRequestException
377    * @throws HttpMethodNotAllowedRequestException
378    * @throws HttpForbiddenRequestException
379    */
380   protected final RestMethodHandler getHandler()
381       throws HttpMethodNotAllowedRequestException,
382              HttpForbiddenRequestException {
383     final METHOD method = arguments.getMethod();
384     final String uri = arguments.getBaseUri();
385     boolean restFound = false;
386     RestMethodHandler handlerNew = restHashMap.get(uri);
387     if (handlerNew != null) {
388       handlerNew.checkHandlerSessionCorrectness(this, arguments, response);
389       if (handlerNew.isMethodIncluded(method)) {
390         restFound = true;
391       }
392     }
393     if (handlerNew == null && method == METHOD.OPTIONS) {
394       handlerNew = rootHandler;
395       // use Options default handler
396       restFound = true;
397     }
398     logger.debug("{} {} {}", method, uri, restFound);
399     if (!restFound) {
400       throw new HttpMethodNotAllowedRequestException(
401           "No Method found for that URI: " + uri);
402     }
403     return handlerNew;
404   }
405 
406   @Override
407   protected void channelRead0(final ChannelHandlerContext ctx,
408                               final HttpObject msg) {
409     logger.debug("Msg Received");
410     try {
411       if (msg instanceof HttpRequest) {
412         initialize();
413         request = (HttpRequest) msg;
414         arguments.setRequest(request);
415         final Iterator<Entry<CharSequence, CharSequence>> iterator =
416             request.headers().iteratorCharSequence();
417         arguments.setHeaderArgs(iterator);
418         arguments.setCookieArgs(request.headers().get(HttpHeaderNames.COOKIE));
419         logger.debug("DEBUG: {}", arguments);
420         checkConnection(ctx);
421         handler = getHandler();
422         if (arguments.getMethod() == METHOD.OPTIONS) {
423           response.setFromArgument(arguments);
424           handler.optionsCommand(this, arguments, response);
425           finalizeSend(ctx);
426           return;
427         }
428         if (request instanceof FullHttpRequest) {
429           if (handler.isBodyJsonDecoded()) {
430             final ByteBuf buffer = ((FullHttpRequest) request).content();
431             jsonObject = getBodyJsonArgs(buffer);
432           } else {
433             // decoder for 1 chunk
434             createDecoder();
435             // Not chunk version
436             readAllHttpData();
437           }
438           response.setFromArgument(arguments);
439           handler.endParsingRequest(this, arguments, response, jsonObject);
440           finalizeSend(ctx);
441           return;
442         }
443         // no body yet
444         if (!handler.isBodyJsonDecoded()) {
445           createDecoder();
446         }
447       } else {
448         // New chunk is received
449         if (handler != null) {
450           bodyChunk(ctx, (HttpContent) msg);
451         }
452       }
453     } catch (final HttpIncorrectRequestException e1) {
454       // real error => 400
455       if (handler != null) {
456         status =
457             handler.handleException(this, arguments, response, jsonObject, e1);
458       }
459       if (status == HttpResponseStatus.OK) {
460         status = HttpResponseStatus.BAD_REQUEST;
461       }
462       logger.warn("Error: {}", e1.getMessage(), e1);
463       if (response.getDetail().isEmpty()) {
464         response.setDetail(e1.getMessage());
465       }
466       if (handler != null) {
467         finalizeSend(ctx);
468       } else {
469         forceClosing(ctx);
470       }
471     } catch (final HttpMethodNotAllowedRequestException e1) {
472       if (handler != null) {
473         status =
474             handler.handleException(this, arguments, response, jsonObject, e1);
475       }
476       if (status == HttpResponseStatus.OK) {
477         status = HttpResponseStatus.METHOD_NOT_ALLOWED;
478       }
479       logger.warn("Error: {}", e1.getMessage());
480       if (response.getDetail().isEmpty()) {
481         response.setDetail(e1.getMessage());
482       }
483       if (handler != null) {
484         finalizeSend(ctx);
485       } else {
486         forceClosing(ctx);
487       }
488     } catch (final HttpForbiddenRequestException e1) {
489       if (handler != null) {
490         status =
491             handler.handleException(this, arguments, response, jsonObject, e1);
492       }
493       if (status == HttpResponseStatus.OK) {
494         status = HttpResponseStatus.FORBIDDEN;
495       }
496       logger.warn("Error: {}", e1.getMessage());
497       if (response.getDetail().isEmpty()) {
498         response.setDetail(e1.getMessage());
499       }
500       if (handler != null) {
501         finalizeSend(ctx);
502       } else {
503         forceClosing(ctx);
504       }
505     } catch (final HttpInvalidAuthenticationException e1) {
506       if (handler != null) {
507         status =
508             handler.handleException(this, arguments, response, jsonObject, e1);
509       }
510       if (status == HttpResponseStatus.OK) {
511         status = HttpResponseStatus.UNAUTHORIZED;
512       }
513       logger.warn("Error: {}", e1.getMessage());
514       if (response.getDetail().isEmpty()) {
515         response.setDetail(e1.getMessage());
516       }
517       if (handler != null) {
518         finalizeSend(ctx);
519       } else {
520         forceClosing(ctx);
521       }
522     } catch (final HttpNotFoundRequestException e1) {
523       if (handler != null) {
524         status =
525             handler.handleException(this, arguments, response, jsonObject, e1);
526       }
527       if (status == HttpResponseStatus.OK) {
528         status = HttpResponseStatus.NOT_FOUND;
529       }
530       logger.warn("Error: {}", e1.getMessage());
531       if (response.getDetail().isEmpty()) {
532         response.setDetail(e1.getMessage());
533       }
534       if (handler != null) {
535         finalizeSend(ctx);
536       } else {
537         forceClosing(ctx);
538       }
539     }
540   }
541 
542   /**
543    * Create the decoder
544    *
545    * @throws HttpIncorrectRequestException
546    */
547   protected final void createDecoder() throws HttpIncorrectRequestException {
548     final HttpMethod method = request.method();
549     if (!method.equals(HttpMethod.HEAD)) {
550       // in order decoder allows to parse
551       request.setMethod(HttpMethod.POST);
552     }
553     try {
554       decoder = new HttpPostRequestDecoder(factory, request);
555     } catch (final ErrorDataDecoderException e1) {
556       status = HttpResponseStatus.NOT_ACCEPTABLE;
557       throw new HttpIncorrectRequestException(e1);
558     } catch (final Exception e1) {
559       // GETDOWNLOAD Method: should not try to create a HttpPostRequestDecoder
560       // So OK but stop here
561       status = HttpResponseStatus.NOT_ACCEPTABLE;
562       throw new HttpIncorrectRequestException(e1);
563     }
564   }
565 
566   /**
567    * Read all InterfaceHttpData from finished transfer
568    *
569    * @throws HttpIncorrectRequestException
570    */
571   protected final void readAllHttpData() throws HttpIncorrectRequestException {
572     final List<InterfaceHttpData> datas;
573     try {
574       datas = decoder.getBodyHttpDatas();
575     } catch (final NotEnoughDataDecoderException e1) {
576       // Should not be!
577       logger.warn("decoder issue: {}", e1.getMessage());
578       status = HttpResponseStatus.NOT_ACCEPTABLE;
579       throw new HttpIncorrectRequestException(e1);
580     }
581     for (final InterfaceHttpData data : datas) {
582       readHttpData(data);
583     }
584   }
585 
586   /**
587    * Read one Data
588    *
589    * @param data
590    *
591    * @throws HttpIncorrectRequestException
592    */
593   protected final void readHttpData(final InterfaceHttpData data)
594       throws HttpIncorrectRequestException {
595     if (data.getHttpDataType() == HttpDataType.Attribute) {
596       final ObjectNode body = arguments.getBody();
597       try {
598         body.put(data.getName(), ((Attribute) data).getValue());
599       } catch (final IOException e) {
600         throw new HttpIncorrectRequestException("Bad reading", e);
601       }
602     } else if (data.getHttpDataType() == HttpDataType.FileUpload) {
603       final FileUpload fileUpload = (FileUpload) data;
604       if (fileUpload.isCompleted()) {
605         handler.getFileUpload(this, fileUpload, arguments, response);
606       } else {
607         logger.warn("File still pending but should not");
608         fileUpload.delete();
609         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
610         throw new HttpIncorrectRequestException(
611             "File still pending but should not");
612       }
613     } else {
614       logger.warn("Unknown element: " + data);
615     }
616   }
617 
618   /**
619    * To allow quick answer even if in very bad shape
620    *
621    * @param ctx
622    */
623   protected final void forceClosing(final ChannelHandlerContext ctx) {
624     if (status == HttpResponseStatus.OK) {
625       status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
626     }
627     if (ctx.channel().isActive()) {
628       setWillClose(true);
629       final String answer =
630           "<html><body>Error " + status.reasonPhrase() + "</body></html>";
631       final FullHttpResponse httpResponse = getResponse(
632           Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8)));
633       httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
634       httpResponse.headers().set(HttpHeaderNames.REFERER, request.uri());
635       final ChannelFuture future = ctx.writeAndFlush(httpResponse);
636       logger.debug("Will close");
637       future.addListener(WaarpSslUtility.SSLCLOSE);
638     }
639     clean();
640   }
641 
642   /**
643    * @param content
644    *
645    * @return the Http Response according to the status and the content if not
646    *     null (setting the CONTENT_LENGTH)
647    */
648   public final FullHttpResponse getResponse(final ByteBuf content) {
649     // Decide whether to close the connection or not.
650     if (request == null) {
651       final FullHttpResponse httpResponse;
652       if (content == null) {
653         httpResponse =
654             new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status);
655       } else {
656         httpResponse =
657             new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status, content);
658         httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,
659                                    content.array().length);
660       }
661       setCookies(httpResponse);
662       setWillClose(true);
663       return httpResponse;
664     }
665     boolean keepAlive = HttpUtil.isKeepAlive(request);
666     setWillClose(isWillClose() || status != HttpResponseStatus.OK ||
667                  HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(
668                      request.headers().get(HttpHeaderNames.CONNECTION)) ||
669                  request.protocolVersion().equals(HttpVersion.HTTP_1_0) &&
670                  !keepAlive);
671     if (isWillClose()) {
672       keepAlive = false;
673     }
674     // Build the response object.
675     final FullHttpResponse httpResponse;
676     if (content != null) {
677       httpResponse =
678           new DefaultFullHttpResponse(request.protocolVersion(), status,
679                                       content);
680       httpResponse.headers()
681                   .set(HttpHeaderNames.CONTENT_LENGTH, content.array().length);
682     } else {
683       httpResponse =
684           new DefaultFullHttpResponse(request.protocolVersion(), status);
685     }
686     if (keepAlive) {
687       httpResponse.headers()
688                   .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
689     }
690     setCookies(httpResponse);
691     return httpResponse;
692   }
693 
694   /**
695    * Method that get a chunk of data
696    *
697    * @param ctx
698    * @param chunk
699    *
700    * @throws HttpIncorrectRequestException
701    * @throws HttpInvalidAuthenticationException
702    * @throws HttpNotFoundRequestException
703    */
704   protected final void bodyChunk(final ChannelHandlerContext ctx,
705                                  final HttpContent chunk)
706       throws HttpIncorrectRequestException, HttpInvalidAuthenticationException,
707              HttpNotFoundRequestException {
708     // New chunk is received: only for Post!
709     if (handler.isBodyJsonDecoded()) {
710       final ByteBuf buffer = chunk.content();
711       if (cumulativeBody != null) {
712         if (buffer.isReadable()) {
713           cumulativeBody = Unpooled.wrappedBuffer(cumulativeBody, buffer);
714         }
715       } else {
716         cumulativeBody = buffer.slice();
717       }
718     } else {
719       try {
720         decoder.offer(chunk);
721       } catch (final ErrorDataDecoderException e1) {
722         status = HttpResponseStatus.NOT_ACCEPTABLE;
723         throw new HttpIncorrectRequestException(e1);
724       }
725       // example of reading chunk by chunk (minimize memory usage due to
726       // Factory)
727       readHttpDataChunkByChunk();
728     }
729     // example of reading only if at the end
730     if (chunk instanceof LastHttpContent) {
731       if (handler.isBodyJsonDecoded()) {
732         jsonObject = getBodyJsonArgs(cumulativeBody);
733         WaarpNettyUtil.release(cumulativeBody);
734         cumulativeBody = null;
735       }
736       response.setFromArgument(arguments);
737       handler.endParsingRequest(this, arguments, response, jsonObject);
738       finalizeSend(ctx);
739     }
740   }
741 
742   protected final void finalizeSend(final ChannelHandlerContext ctx) {
743     final ChannelFuture future;
744     if (arguments.getMethod() == METHOD.OPTIONS) {
745       future = handler.sendOptionsResponse(this, ctx, response, status);
746     } else {
747       future = handler.sendResponse(this, ctx, arguments, response, jsonObject,
748                                     status);
749     }
750     if (future != null) {
751       future.addListener(WaarpSslUtility.SSLCLOSE);
752     }
753     clean();
754     logger.debug("Cleaned");
755   }
756 
757   /**
758    * Get Body args as JSON body
759    *
760    * @param data
761    *
762    * @throws HttpIncorrectRequestException
763    */
764   protected final Object getBodyJsonArgs(final ByteBuf data)
765       throws HttpIncorrectRequestException {
766     if (data == null || data.readableBytes() == 0) {
767       return null;
768     }
769     return handler.getBody(this, data, arguments, response);
770   }
771 
772   /**
773    * Read request by chunk and getting values from chunk to chunk
774    *
775    * @throws HttpIncorrectRequestException
776    */
777   protected final void readHttpDataChunkByChunk()
778       throws HttpIncorrectRequestException {
779     try {
780       while (decoder.hasNext()) {
781         final InterfaceHttpData data = decoder.next();
782         if (data != null) {
783           // new value
784           readHttpData(data);
785         }
786       }
787     } catch (final EndOfDataDecoderException e1) {
788       // end
789     }
790   }
791 
792   @Override
793   public final void exceptionCaught(final ChannelHandlerContext ctx,
794                                     final Throwable cause) {
795     if (ctx.channel().isActive()) {
796       if (cause != null && cause.getMessage() != null) {
797         logger.warn("Exception {}", cause.getMessage());
798       } else {
799         logger.warn("Exception Received", cause);
800       }
801       if (cause instanceof ClosedChannelException ||
802           cause instanceof IOException) {
803         return;
804       }
805       if (handler != null) {
806         status = handler.handleException(this, arguments, response, jsonObject,
807                                          (Exception) cause);
808       }
809       if (status == HttpResponseStatus.OK) {
810         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
811       }
812       if (handler != null) {
813         finalizeSend(ctx);
814       } else {
815         forceClosing(ctx);
816       }
817     }
818   }
819 
820   @Override
821   public void channelInactive(final ChannelHandlerContext ctx)
822       throws Exception {
823     super.channelInactive(ctx);
824     clean();
825   }
826 
827   /**
828    * @return the status
829    */
830   public final HttpResponseStatus getStatus() {
831     return status;
832   }
833 
834   /**
835    * @param status the status to set
836    */
837   public final void setStatus(final HttpResponseStatus status) {
838     this.status = status;
839   }
840 
841   /**
842    * @return the request
843    */
844   public final HttpRequest getRequest() {
845     return request;
846   }
847 
848   /**
849    * @return the willClose
850    */
851   public final boolean isWillClose() {
852     return willClose;
853   }
854 
855   /**
856    * @param willClose the willClose to set
857    */
858   public final void setWillClose(final boolean willClose) {
859     this.willClose = willClose;
860   }
861 }