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.http;
21  
22  import io.netty.buffer.ByteBuf;
23  import io.netty.buffer.Unpooled;
24  import io.netty.channel.ChannelFuture;
25  import io.netty.channel.ChannelHandlerContext;
26  import io.netty.channel.SimpleChannelInboundHandler;
27  import io.netty.handler.codec.http.DefaultFullHttpResponse;
28  import io.netty.handler.codec.http.FullHttpRequest;
29  import io.netty.handler.codec.http.FullHttpResponse;
30  import io.netty.handler.codec.http.HttpContent;
31  import io.netty.handler.codec.http.HttpHeaderNames;
32  import io.netty.handler.codec.http.HttpHeaderValues;
33  import io.netty.handler.codec.http.HttpMethod;
34  import io.netty.handler.codec.http.HttpObject;
35  import io.netty.handler.codec.http.HttpRequest;
36  import io.netty.handler.codec.http.HttpResponseStatus;
37  import io.netty.handler.codec.http.HttpUtil;
38  import io.netty.handler.codec.http.HttpVersion;
39  import io.netty.handler.codec.http.LastHttpContent;
40  import io.netty.handler.codec.http.QueryStringDecoder;
41  import io.netty.handler.codec.http.cookie.Cookie;
42  import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
43  import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
44  import io.netty.handler.codec.http.multipart.Attribute;
45  import io.netty.handler.codec.http.multipart.FileUpload;
46  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
47  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
48  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
49  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
50  import io.netty.handler.codec.http.multipart.InterfaceHttpData;
51  import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
52  import org.waarp.common.crypto.ssl.WaarpSslUtility;
53  import org.waarp.common.database.DbSession;
54  import org.waarp.common.database.data.AbstractDbData.UpdatedInfo;
55  import org.waarp.common.logging.SysErrLogger;
56  import org.waarp.common.logging.WaarpLogger;
57  import org.waarp.common.logging.WaarpLoggerFactory;
58  import org.waarp.common.utility.WaarpStringUtils;
59  import org.waarp.gateway.kernel.AbstractHttpBusinessRequest;
60  import org.waarp.gateway.kernel.AbstractHttpField;
61  import org.waarp.gateway.kernel.AbstractHttpField.FieldPosition;
62  import org.waarp.gateway.kernel.AbstractHttpField.FieldRole;
63  import org.waarp.gateway.kernel.HttpBusinessFactory;
64  import org.waarp.gateway.kernel.HttpPage;
65  import org.waarp.gateway.kernel.HttpPage.PageRole;
66  import org.waarp.gateway.kernel.HttpPageHandler;
67  import org.waarp.gateway.kernel.database.DbConstantGateway;
68  import org.waarp.gateway.kernel.database.WaarpActionLogger;
69  import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException;
70  import org.waarp.gateway.kernel.session.DefaultHttpAuth;
71  import org.waarp.gateway.kernel.session.HttpSession;
72  
73  import java.io.IOException;
74  import java.nio.channels.ClosedChannelException;
75  import java.security.SecureRandom;
76  import java.util.Collections;
77  import java.util.HashSet;
78  import java.util.List;
79  import java.util.Map;
80  import java.util.Set;
81  
82  /**
83   *
84   */
85  public abstract class HttpRequestHandler
86      extends SimpleChannelInboundHandler<HttpObject> {
87    /**
88     * Internal Logger
89     */
90    private static final WaarpLogger logger =
91        WaarpLoggerFactory.getLogger(HttpRequestHandler.class);
92  
93    private static final SecureRandom random = new SecureRandom();
94  
95    protected final String baseStaticPath;
96    protected final String cookieSession;
97    protected final HttpPageHandler httpPageHandler;
98  
99    /**
100    * @param baseStaticPath
101    * @param cookieSession
102    * @param httpPageHandler
103    */
104   protected HttpRequestHandler(final String baseStaticPath,
105                                final String cookieSession,
106                                final HttpPageHandler httpPageHandler) {
107     this.baseStaticPath = baseStaticPath;
108     this.cookieSession = cookieSession;
109     this.httpPageHandler = httpPageHandler;
110   }
111 
112   protected HttpSession session;
113   protected HttpPostRequestDecoder decoder;
114   protected HttpPage httpPage;
115   protected AbstractHttpBusinessRequest businessRequest;
116 
117   protected HttpResponseStatus status = HttpResponseStatus.OK;
118   protected String errorMesg;
119 
120   protected HttpRequest request;
121   protected HttpMethod method;
122 
123   protected boolean willClose;
124 
125   /**
126    * Clean method
127    * <p>
128    * Override if needed
129    */
130   protected final void clean() {
131     if (businessRequest != null) {
132       businessRequest.cleanRequest();
133       businessRequest = null;
134     }
135     if (decoder != null) {
136       decoder.cleanFiles();
137       decoder = null;
138     }
139     if (session != null) {
140       session.setFilename(null);
141       session.setLogid(DbConstantGateway.ILLEGALVALUE);
142     }
143   }
144 
145   /**
146    * Called at the beginning of every new request
147    * <p>
148    * Override if needed
149    */
150   protected void initialize() {
151     // clean previous FileUpload if Any
152     clean();
153     willClose = false;
154     status = HttpResponseStatus.OK;
155     httpPage = null;
156     businessRequest = null;
157   }
158 
159   /**
160    * set values from URI
161    *
162    * @throws HttpIncorrectRequestException
163    */
164   protected final void getUriArgs() throws HttpIncorrectRequestException {
165     final QueryStringDecoder decoderQuery =
166         new QueryStringDecoder(request.uri());
167     final Map<String, List<String>> uriAttributes = decoderQuery.parameters();
168     final Set<String> attributes = uriAttributes.keySet();
169     for (final String name : attributes) {
170       final List<String> values = uriAttributes.get(name);
171       if (values != null) {
172         if (values.size() == 1) {
173           // only one element is allowed
174           httpPage.setValue(businessRequest, name, values.get(0),
175                             FieldPosition.URL);
176         } else if (values.size() > 1) {
177           // more than one element is not allowed
178           values.clear();
179           throw new HttpIncorrectRequestException(
180               "Too many values for " + name);
181         }
182         values.clear();
183       }
184     }
185   }
186 
187   /**
188    * set values from Header
189    *
190    * @throws HttpIncorrectRequestException
191    */
192   protected final void getHeaderArgs() throws HttpIncorrectRequestException {
193     final Set<String> headerNames = request.headers().names();
194     for (final String name : headerNames) {
195       final List<String> values = request.headers().getAll((CharSequence) name);
196       if (values != null) {
197         if (values.size() == 1) {
198           // only one element is allowed
199           httpPage.setValue(businessRequest, name, values.get(0),
200                             FieldPosition.HEADER);
201         } else if (values.size() > 1) {
202           // more than one element is not allowed
203           try {
204             values.clear();
205           } catch (final UnsupportedOperationException e) {
206             SysErrLogger.FAKE_LOGGER.ignoreLog(e);
207           }
208           throw new HttpIncorrectRequestException(
209               "Too many values for " + name);
210         }
211         try {
212           values.clear();
213         } catch (final UnsupportedOperationException e) {
214           SysErrLogger.FAKE_LOGGER.ignoreLog(e);
215         }
216       }
217     }
218   }
219 
220   /**
221    * set values from Cookies
222    *
223    * @throws HttpIncorrectRequestException
224    */
225   protected final void getCookieArgs() throws HttpIncorrectRequestException {
226     final Set<Cookie> cookies;
227     final String value = request.headers().get(HttpHeaderNames.COOKIE);
228     if (value == null) {
229       cookies = Collections.emptySet();
230     } else {
231       cookies = ServerCookieDecoder.LAX.decode(value);
232     }
233     if (!cookies.isEmpty()) {
234       for (final Cookie cookie : cookies) {
235         if (isCookieValid(cookie)) {
236           httpPage.setValue(businessRequest, cookie.name(), cookie.value(),
237                             FieldPosition.COOKIE);
238         }
239       }
240     }
241     cookies.clear();
242   }
243 
244   /**
245    * To be used for instance to check correctness of connection
246    *
247    * @param ctx
248    */
249   protected abstract void checkConnection(ChannelHandlerContext ctx)
250       throws HttpIncorrectRequestException;
251 
252   /**
253    * Called when an error is raised. Note that clean() will be called just
254    * after.
255    *
256    * @param ctx
257    */
258   protected abstract void error(ChannelHandlerContext ctx);
259 
260   @Override
261   protected void channelRead0(final ChannelHandlerContext ctx,
262                               final HttpObject msg) {
263     try {
264       if (msg instanceof HttpRequest) {
265         initialize();
266         request = (HttpRequest) msg;
267         method = request.method();
268         final QueryStringDecoder queryStringDecoder =
269             new QueryStringDecoder(request.uri());
270         final String uriRequest = queryStringDecoder.path();
271         final HttpPage httpPageTemp;
272         try {
273           httpPageTemp =
274               httpPageHandler.getHttpPage(uriRequest, method.name(), session);
275         } catch (final HttpIncorrectRequestException e1) {
276           // real error => 400
277           status = HttpResponseStatus.BAD_REQUEST;
278           errorMesg = e1.getMessage();
279           writeErrorPage(ctx);
280           return;
281           // end of task
282         }
283         if (httpPageTemp == null) {
284           // if Get => standard Get
285           if (method == HttpMethod.GET) {
286             logger.debug("simple get: {}", request.uri());
287             // send content (image for instance)
288             HttpWriteCacheEnable.writeFile(request, ctx,
289                                            baseStaticPath + uriRequest,
290                                            cookieSession);
291             // end of task
292           } else {
293             // real error => 404
294             status = HttpResponseStatus.NOT_FOUND;
295             writeErrorPage(ctx);
296           }
297           return;
298         }
299         httpPage = httpPageTemp;
300         session.setCurrentCommand(httpPage.getPagerole());
301         final DbSession dbSession = DbConstantGateway.admin != null?
302             DbConstantGateway.admin.getSession() : null;
303         WaarpActionLogger.logCreate(dbSession, "Request received: " +
304                                                httpPage.getPagename(), session);
305         if (httpPageTemp.getPagerole() == PageRole.ERROR) {
306           status = HttpResponseStatus.BAD_REQUEST;
307           error(ctx);
308           clean();
309           // order is important: first clean, then create new businessRequest
310           businessRequest = httpPage.newRequest(ctx.channel().remoteAddress());
311           willClose = true;
312           writeSimplePage(ctx);
313           WaarpActionLogger.logErrorAction(DbConstantGateway.admin.getSession(),
314                                            session,
315                                            "Error: " + httpPage.getPagename(),
316                                            status);
317           return;
318           // end of task
319         }
320         businessRequest = httpPage.newRequest(ctx.channel().remoteAddress());
321         getUriArgs();
322         getHeaderArgs();
323         getCookieArgs();
324         checkConnection(ctx);
325         switch (httpPage.getPagerole()) {
326           case DELETE:
327             // no body element
328             delete(ctx);
329             return;
330           case GETDOWNLOAD:
331             // no body element
332             getFile(ctx);
333             return;
334           case HTML:
335           case MENU:
336             // no body element
337             beforeSimplePage(ctx);
338             writeSimplePage(ctx);
339             return;
340           case POST:
341           case POSTUPLOAD:
342           case PUT:
343             post(ctx);
344             return;
345           default:
346             // real error => 400
347             status = HttpResponseStatus.BAD_REQUEST;
348             writeErrorPage(ctx);
349         }
350       } else {
351         // New chunk is received: only for Put, Post or PostMulti!
352         postChunk(ctx, (HttpContent) msg);
353       }
354     } catch (final HttpIncorrectRequestException e1) {
355       // real error => 400
356       if (status == HttpResponseStatus.OK) {
357         status = HttpResponseStatus.BAD_REQUEST;
358       }
359       errorMesg = e1.getMessage();
360       logger.warn("Error {}", e1.getMessage());
361       writeErrorPage(ctx);
362     }
363   }
364 
365   /**
366    * Utility to prepare error
367    *
368    * @param ctx
369    * @param message
370    *
371    * @throws HttpIncorrectRequestException
372    */
373   protected final void prepareError(final ChannelHandlerContext ctx,
374                                     final String message)
375       throws HttpIncorrectRequestException {
376     logger.debug("Debug {}", message);
377     if (!setErrorPage(ctx)) {
378       // really really bad !
379       return;
380     }
381     errorMesg = status.reasonPhrase() + " / " + message;
382     throw new HttpIncorrectRequestException(errorMesg);
383   }
384 
385   /**
386    * Instantiate the page and the businessRequest handler
387    *
388    * @param ctx
389    *
390    * @return True if initialized
391    */
392   protected final boolean setErrorPage(final ChannelHandlerContext ctx) {
393     httpPage = httpPageHandler.getHttpPage(status.code());
394     if (httpPage == null) {
395       return false;
396     }
397     businessRequest = httpPage.newRequest(ctx.channel().remoteAddress());
398     return true;
399   }
400 
401   /**
402    * Write an error page
403    *
404    * @param ctx
405    */
406   protected final void writeErrorPage(final ChannelHandlerContext ctx) {
407     final DbSession dbSession =
408         DbConstantGateway.admin != null? DbConstantGateway.admin.getSession() :
409             null;
410     WaarpActionLogger.logErrorAction(dbSession, session, "Error: " +
411                                                          (httpPage == null?
412                                                              "no page" :
413                                                              httpPage.getPagename()),
414                                      status);
415     error(ctx);
416     clean();
417     willClose = true;
418     if (!setErrorPage(ctx)) {
419       // really really bad !
420       forceClosing(ctx);
421       return;
422     }
423     try {
424       writeSimplePage(ctx);
425     } catch (final HttpIncorrectRequestException e) {
426       // force channel closing
427       forceClosing(ctx);
428     }
429   }
430 
431   /**
432    * To allow quick answer even if in very bad shape
433    *
434    * @param ctx
435    */
436   protected final void forceClosing(final ChannelHandlerContext ctx) {
437     if (status == HttpResponseStatus.OK) {
438       status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
439     }
440     if (ctx.channel().isActive()) {
441       willClose = true;
442       final String answer =
443           "<html><body>Error " + status.reasonPhrase() + "</body></html>";
444       final FullHttpResponse response = getResponse(
445           Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8)));
446       response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
447       response.headers().set(HttpHeaderNames.REFERER, request.uri());
448       final ChannelFuture future = ctx.writeAndFlush(response);
449       logger.debug("Will close");
450       future.addListener(WaarpSslUtility.SSLCLOSE);
451     }
452     WaarpActionLogger.logErrorAction(DbConstantGateway.admin.getSession(),
453                                      session,
454                                      "Error: " + httpPage.getPagename(),
455                                      status);
456   }
457 
458   /**
459    * Write a simple page from current httpPage and businessRequest
460    *
461    * @param ctx
462    *
463    * @throws HttpIncorrectRequestException
464    */
465   protected final void writeSimplePage(final ChannelHandlerContext ctx)
466       throws HttpIncorrectRequestException {
467     logger.debug("HttpPage: {} businessRequest: {}",
468                  httpPage != null? httpPage.getPagename() : "no page",
469                  businessRequest != null? businessRequest.getClass().getName() :
470                      "no BR");
471     if (httpPage != null && httpPage.getPagerole() == PageRole.ERROR) {
472       try {
473         httpPage.setValue(businessRequest, AbstractHttpField.ERRORINFO,
474                           errorMesg, FieldPosition.BODY);
475       } catch (final HttpIncorrectRequestException e) {
476         // ignore
477       }
478     }
479     final String answer =
480         httpPage != null? httpPage.getHtmlPage(businessRequest) : "BAD REQUEST";
481     final int length;
482     // Convert the response content to a ByteBuf.
483     final ByteBuf buf =
484         Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8));
485     final FullHttpResponse response = getResponse(buf);
486     if (businessRequest == null) {
487       response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
488     } else {
489       response.headers().set(HttpHeaderNames.CONTENT_TYPE,
490                              businessRequest.getContentType());
491     }
492     response.headers().set(HttpHeaderNames.REFERER, request.uri());
493     length = buf.readableBytes();
494     if (!willClose) {
495       // There's no need to add 'Content-Length' header
496       // if this is the last response.
497       response.headers()
498               .set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(length));
499     }
500     // Write the response.
501     final ChannelFuture future = ctx.writeAndFlush(response);
502     // Close the connection after the write operation is done if necessary.
503     if (willClose) {
504       logger.debug("Will close");
505       future.addListener(WaarpSslUtility.SSLCLOSE);
506     }
507   }
508 
509   /**
510    * Could be used for other method (as validation of an authent cookie)
511    *
512    * @param cookie
513    *
514    * @return True if this cookie is valid
515    */
516   protected abstract boolean isCookieValid(Cookie cookie);
517 
518   /**
519    * Method to add specific Cookies from business definition
520    * <p>
521    * Override if needed
522    *
523    * @param response
524    * @param cookieNames
525    */
526   protected final void addBusinessCookie(final FullHttpResponse response,
527                                          final Set<String> cookieNames) {
528     if (httpPage != null) {
529       for (final AbstractHttpField field : httpPage.getFieldsForRequest(
530           businessRequest).values()) {
531         if (field.isFieldcookieset() &&
532             !cookieNames.contains(field.getFieldname())) {
533           response.headers().add(HttpHeaderNames.SET_COOKIE,
534                                  ServerCookieEncoder.LAX.encode(
535                                      field.getFieldname(), field.fieldvalue));
536         }
537       }
538     }
539   }
540 
541   /**
542    * Method to set Cookies in response
543    *
544    * @param response
545    */
546   protected final void setCookieEncoder(final FullHttpResponse response) {
547     final Set<Cookie> cookies;
548     final String value = request.headers().get(HttpHeaderNames.COOKIE);
549     if (value == null) {
550       cookies = Collections.emptySet();
551     } else {
552       cookies = ServerCookieDecoder.LAX.decode(value);
553     }
554     boolean foundCookieSession = false;
555     final Set<String> cookiesName = new HashSet<String>();
556     if (!cookies.isEmpty()) {
557       // Reset the cookies if necessary.
558       for (final Cookie cookie : cookies) {
559         if (isCookieValid(cookie)) {
560           response.headers().add(HttpHeaderNames.SET_COOKIE,
561                                  ServerCookieEncoder.LAX.encode(cookie));
562           if (cookie.name().equals(cookieSession)) {
563             foundCookieSession = true;
564           }
565           cookiesName.add(cookie.name());
566         }
567       }
568     }
569     if (!foundCookieSession) {
570       response.headers().add(HttpHeaderNames.SET_COOKIE,
571                              ServerCookieEncoder.LAX.encode(cookieSession,
572                                                             session.getCookieSession()));
573       cookiesName.add(cookieSession);
574     }
575     addBusinessCookie(response, cookiesName);
576     cookiesName.clear();
577   }
578 
579   /**
580    * @param buf might be null
581    *
582    * @return the Http Response according to the status
583    */
584   protected final FullHttpResponse getResponse(final ByteBuf buf) {
585     // Decide whether to close the connection or not.
586     final FullHttpResponse response;
587     if (request == null) {
588       if (buf != null) {
589         response =
590             new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status, buf);
591         response.headers().add(HttpHeaderNames.CONTENT_LENGTH,
592                                response.content().readableBytes());
593       } else {
594         response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status);
595       }
596       setCookieEncoder(response);
597       willClose = true;
598       return response;
599     }
600     boolean keepAlive = HttpUtil.isKeepAlive(request);
601     willClose |= status != HttpResponseStatus.OK ||
602                  HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(
603                      request.headers().get(HttpHeaderNames.CONNECTION)) ||
604                  request.protocolVersion().equals(HttpVersion.HTTP_1_0) &&
605                  !keepAlive;
606     if (willClose) {
607       keepAlive = false;
608     }
609     // Build the response object.
610     if (buf != null) {
611       response =
612           new DefaultFullHttpResponse(request.protocolVersion(), status, buf);
613       response.headers().add(HttpHeaderNames.CONTENT_LENGTH,
614                              response.content().readableBytes());
615     } else {
616       response = new DefaultFullHttpResponse(request.protocolVersion(), status);
617     }
618     if (keepAlive) {
619       response.headers()
620               .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
621     }
622     setCookieEncoder(response);
623     return response;
624   }
625 
626   /**
627    * @return the filename used for this request
628    */
629   protected abstract String getFilename();
630 
631   /**
632    * Called before simple Page is called (Menu or HTML)
633    *
634    * @param ctx
635    *
636    * @throws HttpIncorrectRequestException
637    */
638   protected abstract void beforeSimplePage(ChannelHandlerContext ctx)
639       throws HttpIncorrectRequestException;
640 
641   /**
642    * Method that will use the result and send back the result
643    *
644    * @param ctx
645    *
646    * @throws HttpIncorrectRequestException
647    */
648   protected final void finalData(final ChannelHandlerContext ctx)
649       throws HttpIncorrectRequestException {
650     try {
651       businessValidRequestAfterAllDataReceived(ctx);
652       if (httpPage == null) {
653         // Cached
654         return;
655       }
656       if (!httpPage.isRequestValid(businessRequest)) {
657         throw new HttpIncorrectRequestException("Request unvalid");
658       }
659       final DbSession dbSession = DbConstantGateway.admin != null?
660           DbConstantGateway.admin.getSession() : null;
661       switch (httpPage.getPagerole()) {
662         case DELETE:
663           session.setFilename(getFilename());
664           finalDelete(ctx);
665           WaarpActionLogger.logAction(dbSession, session, "Delete OK", status,
666                                       UpdatedInfo.DONE);
667           break;
668         case GETDOWNLOAD:
669           finalGet(ctx);
670           WaarpActionLogger.logAction(dbSession, session, "Download OK", status,
671                                       UpdatedInfo.DONE);
672           break;
673         case POST:
674           finalPost(ctx);
675           WaarpActionLogger.logAction(dbSession, session, "Post OK", status,
676                                       UpdatedInfo.DONE);
677           break;
678         case POSTUPLOAD:
679           finalPostUpload(ctx);
680           WaarpActionLogger.logAction(dbSession, session, "PostUpload OK",
681                                       status, UpdatedInfo.DONE);
682           break;
683         case PUT:
684           finalPut(ctx);
685           WaarpActionLogger.logAction(dbSession, session, "Put OK", status,
686                                       UpdatedInfo.DONE);
687           break;
688         default:
689           // real error => 400
690           status = HttpResponseStatus.BAD_REQUEST;
691           throw new HttpIncorrectRequestException("Unknown request");
692       }
693     } catch (final HttpIncorrectRequestException e) {
694       // real error => 400
695       if (status == HttpResponseStatus.OK) {
696         status = HttpResponseStatus.BAD_REQUEST;
697       }
698       throw e;
699     }
700   }
701 
702   /**
703    * Method that will use the uploaded file and prepare the result
704    *
705    * @param ctx
706    */
707   protected abstract void finalDelete(ChannelHandlerContext ctx)
708       throws HttpIncorrectRequestException;
709 
710   /**
711    * Method that will use the uploaded file and send back the result <br>
712    * (this method must send back the answer using for instance a ChunkedInput
713    * handler and should try to call
714    * clean(), but taking into consideration that it will erase all data, so it
715    * must be ensured that all data are
716    * sent through the wire before calling it. Note however that when the
717    * connection is closed or when a new
718    * request on the same connection occurs, the clean method is automatically
719    * called. The usage of a
720    * HttpCleanChannelFutureListener on the last write might be useful.)
721    *
722    * @param ctx
723    */
724   protected abstract void finalGet(ChannelHandlerContext ctx)
725       throws HttpIncorrectRequestException;
726 
727   /**
728    * Method that will use the uploaded file and prepare the result
729    *
730    * @param ctx
731    */
732   protected abstract void finalPostUpload(ChannelHandlerContext ctx)
733       throws HttpIncorrectRequestException;
734 
735   /**
736    * Method that will use the post result and prepare the result
737    *
738    * @param ctx
739    */
740   protected abstract void finalPost(ChannelHandlerContext ctx)
741       throws HttpIncorrectRequestException;
742 
743   /**
744    * Method that will use the put result and prepare the result
745    *
746    * @param ctx
747    */
748   protected abstract void finalPut(ChannelHandlerContext ctx)
749       throws HttpIncorrectRequestException;
750 
751   /**
752    * Validate all data as they should be all received (done before the
753    * isRequestValid)
754    *
755    * @param ctx
756    *
757    * @throws HttpIncorrectRequestException
758    */
759   public abstract void businessValidRequestAfterAllDataReceived(
760       ChannelHandlerContext ctx) throws HttpIncorrectRequestException;
761 
762   /**
763    * Method that get "get" data, answer has to be written in the business part
764    * finalGet
765    *
766    * @param ctx
767    */
768   protected final void getFile(final ChannelHandlerContext ctx)
769       throws HttpIncorrectRequestException {
770     finalData(ctx);
771   }
772 
773   /**
774    * Method that get delete data
775    *
776    * @param ctx
777    */
778   protected final void delete(final ChannelHandlerContext ctx)
779       throws HttpIncorrectRequestException {
780     finalData(ctx);
781     writeSimplePage(ctx);
782     clean();
783   }
784 
785   /**
786    * Method that get post data
787    *
788    * @param ctx
789    *
790    * @throws HttpIncorrectRequestException
791    */
792   protected final void post(final ChannelHandlerContext ctx)
793       throws HttpIncorrectRequestException {
794     try {
795       decoder =
796           new HttpPostRequestDecoder(HttpBusinessFactory.factory, request);
797     } catch (final ErrorDataDecoderException e1) {
798       status = HttpResponseStatus.NOT_ACCEPTABLE;
799       throw new HttpIncorrectRequestException(e1);
800     } catch (final Exception e1) {
801       // GETDOWNLOAD Method: should not try to create a HttpPostRequestDecoder
802       // So OK but stop here
803       status = HttpResponseStatus.NOT_ACCEPTABLE;
804       throw new HttpIncorrectRequestException(e1);
805     }
806 
807     if (request instanceof FullHttpRequest) {
808       // Not chunk version
809       readHttpDataAllReceive(ctx);
810       finalData(ctx);
811       writeSimplePage(ctx);
812       clean();
813     }
814   }
815 
816   /**
817    * Method that get a chunk of data
818    *
819    * @param ctx
820    * @param chunk
821    *
822    * @throws HttpIncorrectRequestException
823    */
824   protected final void postChunk(final ChannelHandlerContext ctx,
825                                  final HttpContent chunk)
826       throws HttpIncorrectRequestException {
827     // New chunk is received: only for Post!
828     if (decoder == null) {
829       finalData(ctx);
830       writeSimplePage(ctx);
831       clean();
832       return;
833     }
834     try {
835       decoder.offer(chunk);
836     } catch (final ErrorDataDecoderException e1) {
837       status = HttpResponseStatus.NOT_ACCEPTABLE;
838       throw new HttpIncorrectRequestException(e1);
839     }
840     // example of reading chunk by chunk (minimize memory usage due to
841     // Factory)
842     readHttpDataChunkByChunk(ctx);
843     // example of reading only if at the end
844     if (chunk instanceof LastHttpContent) {
845       finalData(ctx);
846       writeSimplePage(ctx);
847       clean();
848     }
849   }
850 
851   @Override
852   public void exceptionCaught(final ChannelHandlerContext ctx,
853                               final Throwable cause) {
854     if (ctx.channel().isActive()) {
855       if (cause != null && cause.getMessage() != null) {
856         logger.warn("Exception {}", cause.getMessage());
857       } else {
858         logger.warn("Exception Received", cause);
859       }
860       if (cause instanceof ClosedChannelException) {
861         return;
862       }
863       status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
864       writeErrorPage(ctx);
865     }
866   }
867 
868   @Override
869   public void channelInactive(final ChannelHandlerContext ctx)
870       throws Exception {
871     super.channelInactive(ctx);
872     clean();
873   }
874 
875   /**
876    * Read all InterfaceHttpData from finished transfer
877    *
878    * @param ctx
879    *
880    * @throws HttpIncorrectRequestException
881    */
882   protected final void readHttpDataAllReceive(final ChannelHandlerContext ctx)
883       throws HttpIncorrectRequestException {
884     final List<InterfaceHttpData> datas;
885     try {
886       datas = decoder.getBodyHttpDatas();
887     } catch (final NotEnoughDataDecoderException e1) {
888       // Should not be!
889       logger.warn("decoder issue" + " : {}", e1.getMessage());
890       status = HttpResponseStatus.NOT_ACCEPTABLE;
891       throw new HttpIncorrectRequestException(e1);
892     }
893     for (final InterfaceHttpData data : datas) {
894       readHttpData(data, ctx);
895     }
896   }
897 
898   /**
899    * Read request by chunk and getting values from chunk to chunk
900    *
901    * @param ctx
902    *
903    * @throws HttpIncorrectRequestException
904    */
905   protected final void readHttpDataChunkByChunk(final ChannelHandlerContext ctx)
906       throws HttpIncorrectRequestException {
907     try {
908       while (decoder.hasNext()) {
909         final InterfaceHttpData data = decoder.next();
910         if (data != null) {
911           // new value
912           readHttpData(data, ctx);
913         }
914       }
915     } catch (final EndOfDataDecoderException e1) {
916       // end
917     }
918   }
919 
920   /**
921    * Read one Data
922    *
923    * @param data
924    * @param ctx
925    *
926    * @throws HttpIncorrectRequestException
927    */
928   protected final void readHttpData(final InterfaceHttpData data,
929                                     final ChannelHandlerContext ctx)
930       throws HttpIncorrectRequestException {
931     if (data.getHttpDataType() == HttpDataType.Attribute) {
932       final Attribute attribute = (Attribute) data;
933       final String name = attribute.getName();
934       try {
935         final String value = attribute.getValue();
936         httpPage.setValue(businessRequest, name, value, FieldPosition.BODY);
937       } catch (final IOException e) {
938         // Error while reading data from File, only print name and
939         // error
940         attribute.delete();
941         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
942         throw new HttpIncorrectRequestException(e);
943       }
944       attribute.delete();
945     } else if (data.getHttpDataType() == HttpDataType.FileUpload) {
946       final FileUpload fileUpload = (FileUpload) data;
947       if (fileUpload.isCompleted()) {
948         final AbstractHttpField field =
949             httpPage.getField(businessRequest, fileUpload.getName());
950         if (field != null &&
951             field.getFieldtype() == FieldRole.BUSINESS_INPUT_FILE) {
952           httpPage.setValue(businessRequest, field.getFieldname(), fileUpload);
953         } else {
954           logger.warn("File received but no variable for it");
955           fileUpload.delete();
956         }
957       } else {
958         logger.warn("File still pending but should not");
959         fileUpload.delete();
960       }
961     } else {
962       logger.warn("Unknown element: " + data);
963     }
964   }
965 
966   /**
967    * Default Session Cookie generator
968    *
969    * @return the new session cookie value
970    */
971   protected final String getNewCookieSession() {
972     return "Waarp" + Long.toHexString(random.nextLong());
973   }
974 
975   /**
976    * Default session creation
977    *
978    * @param ctx
979    */
980   protected final void createNewSessionAtConnection(
981       final ChannelHandlerContext ctx) {
982     session = new HttpSession();
983     session.setHttpAuth(new DefaultHttpAuth(session));
984     session.setCookieSession(getNewCookieSession());
985     session.setCurrentCommand(PageRole.HTML);
986   }
987 
988   @Override
989   public void channelActive(final ChannelHandlerContext ctx) throws Exception {
990     super.channelActive(ctx);
991     createNewSessionAtConnection(ctx);
992   }
993 
994 }