HttpRequestHandler.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */
package org.waarp.gateway.kernel.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import org.waarp.common.crypto.ssl.WaarpSslUtility;
import org.waarp.common.database.DbSession;
import org.waarp.common.database.data.AbstractDbData.UpdatedInfo;
import org.waarp.common.logging.SysErrLogger;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.gateway.kernel.AbstractHttpBusinessRequest;
import org.waarp.gateway.kernel.AbstractHttpField;
import org.waarp.gateway.kernel.AbstractHttpField.FieldPosition;
import org.waarp.gateway.kernel.AbstractHttpField.FieldRole;
import org.waarp.gateway.kernel.HttpBusinessFactory;
import org.waarp.gateway.kernel.HttpPage;
import org.waarp.gateway.kernel.HttpPage.PageRole;
import org.waarp.gateway.kernel.HttpPageHandler;
import org.waarp.gateway.kernel.database.DbConstantGateway;
import org.waarp.gateway.kernel.database.WaarpActionLogger;
import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException;
import org.waarp.gateway.kernel.session.DefaultHttpAuth;
import org.waarp.gateway.kernel.session.HttpSession;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 */
public abstract class HttpRequestHandler
    extends SimpleChannelInboundHandler<HttpObject> {
  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(HttpRequestHandler.class);

  private static final SecureRandom random = new SecureRandom();

  protected final String baseStaticPath;
  protected final String cookieSession;
  protected final HttpPageHandler httpPageHandler;

  /**
   * @param baseStaticPath
   * @param cookieSession
   * @param httpPageHandler
   */
  protected HttpRequestHandler(final String baseStaticPath,
                               final String cookieSession,
                               final HttpPageHandler httpPageHandler) {
    this.baseStaticPath = baseStaticPath;
    this.cookieSession = cookieSession;
    this.httpPageHandler = httpPageHandler;
  }

  protected HttpSession session;
  protected HttpPostRequestDecoder decoder;
  protected HttpPage httpPage;
  protected AbstractHttpBusinessRequest businessRequest;

  protected HttpResponseStatus status = HttpResponseStatus.OK;
  protected String errorMesg;

  protected HttpRequest request;
  protected HttpMethod method;

  protected boolean willClose;

  /**
   * Clean method
   * <p>
   * Override if needed
   */
  protected final void clean() {
    if (businessRequest != null) {
      businessRequest.cleanRequest();
      businessRequest = null;
    }
    if (decoder != null) {
      decoder.cleanFiles();
      decoder = null;
    }
    if (session != null) {
      session.setFilename(null);
      session.setLogid(DbConstantGateway.ILLEGALVALUE);
    }
  }

  /**
   * Called at the beginning of every new request
   * <p>
   * Override if needed
   */
  protected void initialize() {
    // clean previous FileUpload if Any
    clean();
    willClose = false;
    status = HttpResponseStatus.OK;
    httpPage = null;
    businessRequest = null;
  }

  /**
   * set values from URI
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void getUriArgs() throws HttpIncorrectRequestException {
    final QueryStringDecoder decoderQuery =
        new QueryStringDecoder(request.uri());
    final Map<String, List<String>> uriAttributes = decoderQuery.parameters();
    final Set<String> attributes = uriAttributes.keySet();
    for (final String name : attributes) {
      final List<String> values = uriAttributes.get(name);
      if (values != null) {
        if (values.size() == 1) {
          // only one element is allowed
          httpPage.setValue(businessRequest, name, values.get(0),
                            FieldPosition.URL);
        } else if (values.size() > 1) {
          // more than one element is not allowed
          values.clear();
          throw new HttpIncorrectRequestException(
              "Too many values for " + name);
        }
        values.clear();
      }
    }
  }

  /**
   * set values from Header
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void getHeaderArgs() throws HttpIncorrectRequestException {
    final Set<String> headerNames = request.headers().names();
    for (final String name : headerNames) {
      final List<String> values = request.headers().getAll((CharSequence) name);
      if (values != null) {
        if (values.size() == 1) {
          // only one element is allowed
          httpPage.setValue(businessRequest, name, values.get(0),
                            FieldPosition.HEADER);
        } else if (values.size() > 1) {
          // more than one element is not allowed
          try {
            values.clear();
          } catch (final UnsupportedOperationException e) {
            SysErrLogger.FAKE_LOGGER.ignoreLog(e);
          }
          throw new HttpIncorrectRequestException(
              "Too many values for " + name);
        }
        try {
          values.clear();
        } catch (final UnsupportedOperationException e) {
          SysErrLogger.FAKE_LOGGER.ignoreLog(e);
        }
      }
    }
  }

  /**
   * set values from Cookies
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void getCookieArgs() throws HttpIncorrectRequestException {
    final Set<Cookie> cookies;
    final String value = request.headers().get(HttpHeaderNames.COOKIE);
    if (value == null) {
      cookies = Collections.emptySet();
    } else {
      cookies = ServerCookieDecoder.LAX.decode(value);
    }
    if (!cookies.isEmpty()) {
      for (final Cookie cookie : cookies) {
        if (isCookieValid(cookie)) {
          httpPage.setValue(businessRequest, cookie.name(), cookie.value(),
                            FieldPosition.COOKIE);
        }
      }
    }
    cookies.clear();
  }

  /**
   * To be used for instance to check correctness of connection
   *
   * @param ctx
   */
  protected abstract void checkConnection(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Called when an error is raised. Note that clean() will be called just
   * after.
   *
   * @param ctx
   */
  protected abstract void error(ChannelHandlerContext ctx);

  @Override
  protected void channelRead0(final ChannelHandlerContext ctx,
                              final HttpObject msg) {
    try {
      if (msg instanceof HttpRequest) {
        initialize();
        request = (HttpRequest) msg;
        method = request.method();
        final QueryStringDecoder queryStringDecoder =
            new QueryStringDecoder(request.uri());
        final String uriRequest = queryStringDecoder.path();
        final HttpPage httpPageTemp;
        try {
          httpPageTemp =
              httpPageHandler.getHttpPage(uriRequest, method.name(), session);
        } catch (final HttpIncorrectRequestException e1) {
          // real error => 400
          status = HttpResponseStatus.BAD_REQUEST;
          errorMesg = e1.getMessage();
          writeErrorPage(ctx);
          return;
          // end of task
        }
        if (httpPageTemp == null) {
          // if Get => standard Get
          if (method == HttpMethod.GET) {
            logger.debug("simple get: {}", request.uri());
            // send content (image for instance)
            HttpWriteCacheEnable.writeFile(request, ctx,
                                           baseStaticPath + uriRequest,
                                           cookieSession);
            // end of task
          } else {
            // real error => 404
            status = HttpResponseStatus.NOT_FOUND;
            writeErrorPage(ctx);
          }
          return;
        }
        httpPage = httpPageTemp;
        session.setCurrentCommand(httpPage.getPagerole());
        final DbSession dbSession = DbConstantGateway.admin != null?
            DbConstantGateway.admin.getSession() : null;
        WaarpActionLogger.logCreate(dbSession, "Request received: " +
                                               httpPage.getPagename(), session);
        if (httpPageTemp.getPagerole() == PageRole.ERROR) {
          status = HttpResponseStatus.BAD_REQUEST;
          error(ctx);
          clean();
          // order is important: first clean, then create new businessRequest
          businessRequest = httpPage.newRequest(ctx.channel().remoteAddress());
          willClose = true;
          writeSimplePage(ctx);
          WaarpActionLogger.logErrorAction(DbConstantGateway.admin.getSession(),
                                           session,
                                           "Error: " + httpPage.getPagename(),
                                           status);
          return;
          // end of task
        }
        businessRequest = httpPage.newRequest(ctx.channel().remoteAddress());
        getUriArgs();
        getHeaderArgs();
        getCookieArgs();
        checkConnection(ctx);
        switch (httpPage.getPagerole()) {
          case DELETE:
            // no body element
            delete(ctx);
            return;
          case GETDOWNLOAD:
            // no body element
            getFile(ctx);
            return;
          case HTML:
          case MENU:
            // no body element
            beforeSimplePage(ctx);
            writeSimplePage(ctx);
            return;
          case POST:
          case POSTUPLOAD:
          case PUT:
            post(ctx);
            return;
          default:
            // real error => 400
            status = HttpResponseStatus.BAD_REQUEST;
            writeErrorPage(ctx);
        }
      } else {
        // New chunk is received: only for Put, Post or PostMulti!
        postChunk(ctx, (HttpContent) msg);
      }
    } catch (final HttpIncorrectRequestException e1) {
      // real error => 400
      if (status == HttpResponseStatus.OK) {
        status = HttpResponseStatus.BAD_REQUEST;
      }
      errorMesg = e1.getMessage();
      logger.warn("Error {}", e1.getMessage());
      writeErrorPage(ctx);
    }
  }

  /**
   * Utility to prepare error
   *
   * @param ctx
   * @param message
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void prepareError(final ChannelHandlerContext ctx,
                                    final String message)
      throws HttpIncorrectRequestException {
    logger.debug("Debug {}", message);
    if (!setErrorPage(ctx)) {
      // really really bad !
      return;
    }
    errorMesg = status.reasonPhrase() + " / " + message;
    throw new HttpIncorrectRequestException(errorMesg);
  }

  /**
   * Instantiate the page and the businessRequest handler
   *
   * @param ctx
   *
   * @return True if initialized
   */
  protected final boolean setErrorPage(final ChannelHandlerContext ctx) {
    httpPage = httpPageHandler.getHttpPage(status.code());
    if (httpPage == null) {
      return false;
    }
    businessRequest = httpPage.newRequest(ctx.channel().remoteAddress());
    return true;
  }

  /**
   * Write an error page
   *
   * @param ctx
   */
  protected final void writeErrorPage(final ChannelHandlerContext ctx) {
    final DbSession dbSession =
        DbConstantGateway.admin != null? DbConstantGateway.admin.getSession() :
            null;
    WaarpActionLogger.logErrorAction(dbSession, session, "Error: " +
                                                         (httpPage == null?
                                                             "no page" :
                                                             httpPage.getPagename()),
                                     status);
    error(ctx);
    clean();
    willClose = true;
    if (!setErrorPage(ctx)) {
      // really really bad !
      forceClosing(ctx);
      return;
    }
    try {
      writeSimplePage(ctx);
    } catch (final HttpIncorrectRequestException e) {
      // force channel closing
      forceClosing(ctx);
    }
  }

  /**
   * To allow quick answer even if in very bad shape
   *
   * @param ctx
   */
  protected final void forceClosing(final ChannelHandlerContext ctx) {
    if (status == HttpResponseStatus.OK) {
      status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
    }
    if (ctx.channel().isActive()) {
      willClose = true;
      final String answer =
          "<html><body>Error " + status.reasonPhrase() + "</body></html>";
      final FullHttpResponse response = getResponse(
          Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8)));
      response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
      response.headers().set(HttpHeaderNames.REFERER, request.uri());
      final ChannelFuture future = ctx.writeAndFlush(response);
      logger.debug("Will close");
      future.addListener(WaarpSslUtility.SSLCLOSE);
    }
    WaarpActionLogger.logErrorAction(DbConstantGateway.admin.getSession(),
                                     session,
                                     "Error: " + httpPage.getPagename(),
                                     status);
  }

  /**
   * Write a simple page from current httpPage and businessRequest
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void writeSimplePage(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    logger.debug("HttpPage: {} businessRequest: {}",
                 httpPage != null? httpPage.getPagename() : "no page",
                 businessRequest != null? businessRequest.getClass().getName() :
                     "no BR");
    if (httpPage != null && httpPage.getPagerole() == PageRole.ERROR) {
      try {
        httpPage.setValue(businessRequest, AbstractHttpField.ERRORINFO,
                          errorMesg, FieldPosition.BODY);
      } catch (final HttpIncorrectRequestException e) {
        // ignore
      }
    }
    final String answer =
        httpPage != null? httpPage.getHtmlPage(businessRequest) : "BAD REQUEST";
    final int length;
    // Convert the response content to a ByteBuf.
    final ByteBuf buf =
        Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8));
    final FullHttpResponse response = getResponse(buf);
    if (businessRequest == null) {
      response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
    } else {
      response.headers().set(HttpHeaderNames.CONTENT_TYPE,
                             businessRequest.getContentType());
    }
    response.headers().set(HttpHeaderNames.REFERER, request.uri());
    length = buf.readableBytes();
    if (!willClose) {
      // There's no need to add 'Content-Length' header
      // if this is the last response.
      response.headers()
              .set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(length));
    }
    // Write the response.
    final ChannelFuture future = ctx.writeAndFlush(response);
    // Close the connection after the write operation is done if necessary.
    if (willClose) {
      logger.debug("Will close");
      future.addListener(WaarpSslUtility.SSLCLOSE);
    }
  }

  /**
   * Could be used for other method (as validation of an authent cookie)
   *
   * @param cookie
   *
   * @return True if this cookie is valid
   */
  protected abstract boolean isCookieValid(Cookie cookie);

  /**
   * Method to add specific Cookies from business definition
   * <p>
   * Override if needed
   *
   * @param response
   * @param cookieNames
   */
  protected final void addBusinessCookie(final FullHttpResponse response,
                                         final Set<String> cookieNames) {
    if (httpPage != null) {
      for (final AbstractHttpField field : httpPage.getFieldsForRequest(
          businessRequest).values()) {
        if (field.isFieldcookieset() &&
            !cookieNames.contains(field.getFieldname())) {
          response.headers().add(HttpHeaderNames.SET_COOKIE,
                                 ServerCookieEncoder.LAX.encode(
                                     field.getFieldname(), field.fieldvalue));
        }
      }
    }
  }

  /**
   * Method to set Cookies in response
   *
   * @param response
   */
  protected final void setCookieEncoder(final FullHttpResponse response) {
    final Set<Cookie> cookies;
    final String value = request.headers().get(HttpHeaderNames.COOKIE);
    if (value == null) {
      cookies = Collections.emptySet();
    } else {
      cookies = ServerCookieDecoder.LAX.decode(value);
    }
    boolean foundCookieSession = false;
    final Set<String> cookiesName = new HashSet<String>();
    if (!cookies.isEmpty()) {
      // Reset the cookies if necessary.
      for (final Cookie cookie : cookies) {
        if (isCookieValid(cookie)) {
          response.headers().add(HttpHeaderNames.SET_COOKIE,
                                 ServerCookieEncoder.LAX.encode(cookie));
          if (cookie.name().equals(cookieSession)) {
            foundCookieSession = true;
          }
          cookiesName.add(cookie.name());
        }
      }
    }
    if (!foundCookieSession) {
      response.headers().add(HttpHeaderNames.SET_COOKIE,
                             ServerCookieEncoder.LAX.encode(cookieSession,
                                                            session.getCookieSession()));
      cookiesName.add(cookieSession);
    }
    addBusinessCookie(response, cookiesName);
    cookiesName.clear();
  }

  /**
   * @param buf might be null
   *
   * @return the Http Response according to the status
   */
  protected final FullHttpResponse getResponse(final ByteBuf buf) {
    // Decide whether to close the connection or not.
    final FullHttpResponse response;
    if (request == null) {
      if (buf != null) {
        response =
            new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status, buf);
        response.headers().add(HttpHeaderNames.CONTENT_LENGTH,
                               response.content().readableBytes());
      } else {
        response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status);
      }
      setCookieEncoder(response);
      willClose = true;
      return response;
    }
    boolean keepAlive = HttpUtil.isKeepAlive(request);
    willClose |= status != HttpResponseStatus.OK ||
                 HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(
                     request.headers().get(HttpHeaderNames.CONNECTION)) ||
                 request.protocolVersion().equals(HttpVersion.HTTP_1_0) &&
                 !keepAlive;
    if (willClose) {
      keepAlive = false;
    }
    // Build the response object.
    if (buf != null) {
      response =
          new DefaultFullHttpResponse(request.protocolVersion(), status, buf);
      response.headers().add(HttpHeaderNames.CONTENT_LENGTH,
                             response.content().readableBytes());
    } else {
      response = new DefaultFullHttpResponse(request.protocolVersion(), status);
    }
    if (keepAlive) {
      response.headers()
              .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
    }
    setCookieEncoder(response);
    return response;
  }

  /**
   * @return the filename used for this request
   */
  protected abstract String getFilename();

  /**
   * Called before simple Page is called (Menu or HTML)
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected abstract void beforeSimplePage(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Method that will use the result and send back the result
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void finalData(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    try {
      businessValidRequestAfterAllDataReceived(ctx);
      if (httpPage == null) {
        // Cached
        return;
      }
      if (!httpPage.isRequestValid(businessRequest)) {
        throw new HttpIncorrectRequestException("Request unvalid");
      }
      final DbSession dbSession = DbConstantGateway.admin != null?
          DbConstantGateway.admin.getSession() : null;
      switch (httpPage.getPagerole()) {
        case DELETE:
          session.setFilename(getFilename());
          finalDelete(ctx);
          WaarpActionLogger.logAction(dbSession, session, "Delete OK", status,
                                      UpdatedInfo.DONE);
          break;
        case GETDOWNLOAD:
          finalGet(ctx);
          WaarpActionLogger.logAction(dbSession, session, "Download OK", status,
                                      UpdatedInfo.DONE);
          break;
        case POST:
          finalPost(ctx);
          WaarpActionLogger.logAction(dbSession, session, "Post OK", status,
                                      UpdatedInfo.DONE);
          break;
        case POSTUPLOAD:
          finalPostUpload(ctx);
          WaarpActionLogger.logAction(dbSession, session, "PostUpload OK",
                                      status, UpdatedInfo.DONE);
          break;
        case PUT:
          finalPut(ctx);
          WaarpActionLogger.logAction(dbSession, session, "Put OK", status,
                                      UpdatedInfo.DONE);
          break;
        default:
          // real error => 400
          status = HttpResponseStatus.BAD_REQUEST;
          throw new HttpIncorrectRequestException("Unknown request");
      }
    } catch (final HttpIncorrectRequestException e) {
      // real error => 400
      if (status == HttpResponseStatus.OK) {
        status = HttpResponseStatus.BAD_REQUEST;
      }
      throw e;
    }
  }

  /**
   * Method that will use the uploaded file and prepare the result
   *
   * @param ctx
   */
  protected abstract void finalDelete(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Method that will use the uploaded file and send back the result <br>
   * (this method must send back the answer using for instance a ChunkedInput
   * handler and should try to call
   * clean(), but taking into consideration that it will erase all data, so it
   * must be ensured that all data are
   * sent through the wire before calling it. Note however that when the
   * connection is closed or when a new
   * request on the same connection occurs, the clean method is automatically
   * called. The usage of a
   * HttpCleanChannelFutureListener on the last write might be useful.)
   *
   * @param ctx
   */
  protected abstract void finalGet(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Method that will use the uploaded file and prepare the result
   *
   * @param ctx
   */
  protected abstract void finalPostUpload(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Method that will use the post result and prepare the result
   *
   * @param ctx
   */
  protected abstract void finalPost(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Method that will use the put result and prepare the result
   *
   * @param ctx
   */
  protected abstract void finalPut(ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException;

  /**
   * Validate all data as they should be all received (done before the
   * isRequestValid)
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  public abstract void businessValidRequestAfterAllDataReceived(
      ChannelHandlerContext ctx) throws HttpIncorrectRequestException;

  /**
   * Method that get "get" data, answer has to be written in the business part
   * finalGet
   *
   * @param ctx
   */
  protected final void getFile(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    finalData(ctx);
  }

  /**
   * Method that get delete data
   *
   * @param ctx
   */
  protected final void delete(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    finalData(ctx);
    writeSimplePage(ctx);
    clean();
  }

  /**
   * Method that get post data
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void post(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    try {
      decoder =
          new HttpPostRequestDecoder(HttpBusinessFactory.factory, request);
    } catch (final ErrorDataDecoderException e1) {
      status = HttpResponseStatus.NOT_ACCEPTABLE;
      throw new HttpIncorrectRequestException(e1);
    } catch (final Exception e1) {
      // GETDOWNLOAD Method: should not try to create a HttpPostRequestDecoder
      // So OK but stop here
      status = HttpResponseStatus.NOT_ACCEPTABLE;
      throw new HttpIncorrectRequestException(e1);
    }

    if (request instanceof FullHttpRequest) {
      // Not chunk version
      readHttpDataAllReceive(ctx);
      finalData(ctx);
      writeSimplePage(ctx);
      clean();
    }
  }

  /**
   * Method that get a chunk of data
   *
   * @param ctx
   * @param chunk
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void postChunk(final ChannelHandlerContext ctx,
                                 final HttpContent chunk)
      throws HttpIncorrectRequestException {
    // New chunk is received: only for Post!
    if (decoder == null) {
      finalData(ctx);
      writeSimplePage(ctx);
      clean();
      return;
    }
    try {
      decoder.offer(chunk);
    } catch (final ErrorDataDecoderException e1) {
      status = HttpResponseStatus.NOT_ACCEPTABLE;
      throw new HttpIncorrectRequestException(e1);
    }
    // example of reading chunk by chunk (minimize memory usage due to
    // Factory)
    readHttpDataChunkByChunk(ctx);
    // example of reading only if at the end
    if (chunk instanceof LastHttpContent) {
      finalData(ctx);
      writeSimplePage(ctx);
      clean();
    }
  }

  @Override
  public void exceptionCaught(final ChannelHandlerContext ctx,
                              final Throwable cause) {
    if (ctx.channel().isActive()) {
      if (cause != null && cause.getMessage() != null) {
        logger.warn("Exception {}", cause.getMessage());
      } else {
        logger.warn("Exception Received", cause);
      }
      if (cause instanceof ClosedChannelException) {
        return;
      }
      status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
      writeErrorPage(ctx);
    }
  }

  @Override
  public void channelInactive(final ChannelHandlerContext ctx)
      throws Exception {
    super.channelInactive(ctx);
    clean();
  }

  /**
   * Read all InterfaceHttpData from finished transfer
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void readHttpDataAllReceive(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    final List<InterfaceHttpData> datas;
    try {
      datas = decoder.getBodyHttpDatas();
    } catch (final NotEnoughDataDecoderException e1) {
      // Should not be!
      logger.warn("decoder issue" + " : {}", e1.getMessage());
      status = HttpResponseStatus.NOT_ACCEPTABLE;
      throw new HttpIncorrectRequestException(e1);
    }
    for (final InterfaceHttpData data : datas) {
      readHttpData(data, ctx);
    }
  }

  /**
   * Read request by chunk and getting values from chunk to chunk
   *
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void readHttpDataChunkByChunk(final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    try {
      while (decoder.hasNext()) {
        final InterfaceHttpData data = decoder.next();
        if (data != null) {
          // new value
          readHttpData(data, ctx);
        }
      }
    } catch (final EndOfDataDecoderException e1) {
      // end
    }
  }

  /**
   * Read one Data
   *
   * @param data
   * @param ctx
   *
   * @throws HttpIncorrectRequestException
   */
  protected final void readHttpData(final InterfaceHttpData data,
                                    final ChannelHandlerContext ctx)
      throws HttpIncorrectRequestException {
    if (data.getHttpDataType() == HttpDataType.Attribute) {
      final Attribute attribute = (Attribute) data;
      final String name = attribute.getName();
      try {
        final String value = attribute.getValue();
        httpPage.setValue(businessRequest, name, value, FieldPosition.BODY);
      } catch (final IOException e) {
        // Error while reading data from File, only print name and
        // error
        attribute.delete();
        status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
        throw new HttpIncorrectRequestException(e);
      }
      attribute.delete();
    } else if (data.getHttpDataType() == HttpDataType.FileUpload) {
      final FileUpload fileUpload = (FileUpload) data;
      if (fileUpload.isCompleted()) {
        final AbstractHttpField field =
            httpPage.getField(businessRequest, fileUpload.getName());
        if (field != null &&
            field.getFieldtype() == FieldRole.BUSINESS_INPUT_FILE) {
          httpPage.setValue(businessRequest, field.getFieldname(), fileUpload);
        } else {
          logger.warn("File received but no variable for it");
          fileUpload.delete();
        }
      } else {
        logger.warn("File still pending but should not");
        fileUpload.delete();
      }
    } else {
      logger.warn("Unknown element: " + data);
    }
  }

  /**
   * Default Session Cookie generator
   *
   * @return the new session cookie value
   */
  protected final String getNewCookieSession() {
    return "Waarp" + Long.toHexString(random.nextLong());
  }

  /**
   * Default session creation
   *
   * @param ctx
   */
  protected final void createNewSessionAtConnection(
      final ChannelHandlerContext ctx) {
    session = new HttpSession();
    session.setHttpAuth(new DefaultHttpAuth(session));
    session.setCookieSession(getNewCookieSession());
    session.setCurrentCommand(PageRole.HTML);
  }

  @Override
  public void channelActive(final ChannelHandlerContext ctx) throws Exception {
    super.channelActive(ctx);
    createNewSessionAtConnection(ctx);
  }

}