/**
  * Set final headers and fire a channel read event
  *
  * @param ctx The context to fire the event on
  * @param msg The message to send
  * @param streamId the streamId of the message which is being fired
  */
 protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, int streamId) {
   removeMessage(streamId);
   HttpUtil.setContentLength(msg, msg.content().readableBytes());
   ctx.fireChannelRead(msg);
 }
  private ChannelFuture handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
    if (request.method() == HttpMethod.OPTIONS) {
      return HttpHelpers.sendPreflightApproval(ctx);
    }

    HttpRequestInfo CurrRequest = new HttpRequestInfo();

    try {
      CurrRequest.init(Info, request, SVID.makeSVID());
    } catch (URISyntaxException e1) {
      return HttpHelpers.sendError(CurrRequest, ApiErrors.HTTP_DECODE_ERROR);
    }

    URI uri = CurrRequest.RequestURI;

    l.debug(new SVLog("Agent HTTP request", CurrRequest));

    if (!request.decoderResult().isSuccess()) {
      return HttpHelpers.sendError(CurrRequest, ApiErrors.HTTP_DECODE_ERROR);
    }

    if (!authenticate(request, (InetSocketAddress) ctx.channel().remoteAddress())) {
      return HttpHelpers.sendError(CurrRequest, ApiErrors.BAD_AUTH);
    }

    String uriPath = uri.getPath();

    if (uriPath.equals(WEBSOCKET_PATH)) {
      String websockUrl = request.headers().get(HttpHeaderNames.HOST) + WEBSOCKET_PATH;
      WebSocketServerHandshakerFactory wsFactory =
          new WebSocketServerHandshakerFactory(websockUrl, null, false);

      Handshaker = wsFactory.newHandshaker(request);
      if (Handshaker == null) {
        return WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
      } else {
        WebsocketConnected = true;
        return Handshaker.handshake(ctx.channel(), request);
      }
    }

    if (uriPath.startsWith("/api/")) {
      // Invoking an API directly over HTTP

      String messageType = uriPath.substring(5);
      String messageBody = null;
      if (request.method() == HttpMethod.POST && request.content() != null) {
        messageBody = request.content().toString(StandardCharsets.UTF_8);
      }

      ByteBuf reply = null;
      try {
        reply = Dispatcher.dispatch(messageType, messageBody);
      } catch (JsonProcessingException e) {
        return HttpHelpers.sendError(CurrRequest, ApiErrors.JSON_ERROR, e.getMessage());
      } catch (SQLException e) {
        return HttpHelpers.sendError(CurrRequest, ApiErrors.DB_ERROR, e.getMessage());
      } catch (JsonApiException e) {
        return HttpHelpers.sendErrorJson(CurrRequest, e.Error, e.HttpStatus);
      } catch (Exception e) {
        return HttpHelpers.sendError(CurrRequest, ApiErrors.INTERNAL_SERVER_ERROR, e.getMessage());
      }

      HttpResponse response =
          new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, reply);

      if (reply != null) {
        HttpHelpers.setContentTypeHeader(response, "application/json");
        HttpUtil.setContentLength(response, reply.readableBytes());
      }

      response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");

      return ctx.writeAndFlush(response);
    }

    return HttpHelpers.sendError(CurrRequest, ApiErrors.NOT_FOUND);
  }
  protected void sendFile(
      final ChannelHandlerContext channelHandlerContext,
      FullHttpRequest fullHttpRequest,
      SyncFile syncFile)
      throws Exception {

    Path path = Paths.get(syncFile.getFilePathName());

    if (Files.notExists(path)) {
      _syncTrafficShapingHandler.decrementConnectionsCount();

      if (_logger.isTraceEnabled()) {
        Channel channel = channelHandlerContext.channel();

        _logger.trace("Client {}: file not found {}", channel.remoteAddress(), path);
      }

      _sendError(channelHandlerContext, NOT_FOUND);

      return;
    }

    if (_logger.isDebugEnabled()) {
      Channel channel = channelHandlerContext.channel();

      _logger.debug("Client {}: sending file {}", channel.remoteAddress(), path);
    }

    long modifiedTime = syncFile.getModifiedTime();
    long previousModifiedTime = syncFile.getPreviousModifiedTime();

    if (OSDetector.isApple()) {
      modifiedTime = modifiedTime / 1000 * 1000;
      previousModifiedTime = previousModifiedTime / 1000 * 1000;
    }

    FileTime currentFileTime = Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS);

    long currentTime = currentFileTime.toMillis();

    if ((currentTime != modifiedTime) && (currentTime != previousModifiedTime)) {

      _syncTrafficShapingHandler.decrementConnectionsCount();

      Channel channel = channelHandlerContext.channel();

      _logger.error(
          "Client {}: file modified {}, currentTime {}, modifiedTime "
              + "{}, previousModifiedTime {}",
          channel.remoteAddress(),
          path,
          currentTime,
          modifiedTime,
          previousModifiedTime);

      _sendError(channelHandlerContext, NOT_FOUND);

      return;
    }

    HttpResponse httpResponse = new DefaultHttpResponse(HTTP_1_1, OK);

    long size = Files.size(path);

    HttpUtil.setContentLength(httpResponse, size);

    HttpHeaders httpHeaders = httpResponse.headers();

    MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();

    httpHeaders.set(
        HttpHeaderNames.CONTENT_TYPE, mimetypesFileTypeMap.getContentType(syncFile.getName()));

    if (HttpUtil.isKeepAlive(fullHttpRequest)) {
      httpHeaders.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
    }

    channelHandlerContext.write(httpResponse);

    SyncChunkedFile syncChunkedFile = new SyncChunkedFile(path, size, 4 * 1024 * 1024, currentTime);

    ChannelFuture channelFuture =
        channelHandlerContext.writeAndFlush(
            new HttpChunkedInput(syncChunkedFile), channelHandlerContext.newProgressivePromise());

    channelFuture.addListener(
        new ChannelFutureListener() {

          @Override
          public void operationComplete(ChannelFuture channelFuture) throws Exception {

            _syncTrafficShapingHandler.decrementConnectionsCount();

            if (channelFuture.isSuccess()) {
              return;
            }

            Throwable exception = channelFuture.cause();

            Channel channel = channelHandlerContext.channel();

            _logger.error(
                "Client {}: {}", channel.remoteAddress(), exception.getMessage(), exception);

            channelHandlerContext.close();
          }
        });

    if (!HttpUtil.isKeepAlive(fullHttpRequest)) {
      channelFuture.addListener(ChannelFutureListener.CLOSE);
    }
  }