@SuppressWarnings("argument.type.incompatible")
 private void sendFullResponse(
     ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response)
     throws Exception {
   boolean keepAlive = HttpUtil.isKeepAlive(request);
   if (httpSessionManager.getSessionId(request) != null
       && httpSessionManager.getAuthenticatedUser(request) == null
       && !response.headers().contains("Set-Cookie")) {
     httpSessionManager.deleteSessionCookie(response);
   }
   response.headers().add("Glowroot-Layout-Version", layoutService.getLayoutVersion());
   if (response.headers().contains("Glowroot-Port-Changed")) {
     // current connection is the only open channel on the old port, keepAlive=false will add
     // the listener below to close the channel after the response completes
     //
     // remove the hacky header, no need to send it back to client
     response.headers().remove("Glowroot-Port-Changed");
     response.headers().add("Connection", "close");
     keepAlive = false;
   }
   response.headers().add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
   if (keepAlive && !request.protocolVersion().isKeepAliveDefault()) {
     response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
   }
   ChannelFuture f = ctx.write(response);
   if (!keepAlive) {
     f.addListener(ChannelFutureListener.CLOSE);
   }
 }
  @Override
  protected void handleOversizedMessage(final ChannelHandlerContext ctx, HttpMessage oversized)
      throws Exception {
    if (oversized instanceof HttpRequest) {
      // send back a 413 and close the connection
      ChannelFuture future =
          ctx.writeAndFlush(TOO_LARGE.duplicate().retain())
              .addListener(
                  new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                      if (!future.isSuccess()) {
                        logger.debug(
                            "Failed to send a 413 Request Entity Too Large.", future.cause());
                        ctx.close();
                      }
                    }
                  });

      // If the client started to send data already, close because it's impossible to recover.
      // If keep-alive is off and 'Expect: 100-continue' is missing, no need to leave the connection
      // open.
      if (oversized instanceof FullHttpMessage
          || !HttpUtil.is100ContinueExpected(oversized) && !HttpUtil.isKeepAlive(oversized)) {
        future.addListener(ChannelFutureListener.CLOSE);
      }

      // If an oversized request was handled properly and the connection is still alive
      // (i.e. rejected 100-continue). the decoder should prepare to handle a new message.
      HttpObjectDecoder decoder = ctx.pipeline().get(HttpObjectDecoder.class);
      if (decoder != null) {
        decoder.reset();
      }
    } else if (oversized instanceof HttpResponse) {
      ctx.close();
      throw new TooLongFrameException("Response entity too large: " + oversized);
    } else {
      throw new IllegalStateException();
    }
  }
  @Override
  protected Object newContinueResponse(
      HttpMessage start, int maxContentLength, ChannelPipeline pipeline) {
    if (HttpUtil.is100ContinueExpected(start)) {
      if (getContentLength(start, -1) <= maxContentLength) {
        return CONTINUE.duplicate().retain();
      }

      pipeline.fireUserEventTriggered(HttpExpectationFailedEvent.INSTANCE);
      return EXPECTATION_FAILED.duplicate().retain();
    }
    return null;
  }
 @Override
 public @Nullable FullHttpResponse handleRequest(ChannelHandlerContext ctx, HttpRequest request)
     throws Exception {
   QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
   List<String> agentIds = decoder.parameters().get("agent-id");
   if (agentIds == null) {
     agentIds = ImmutableList.of("");
   }
   String agentId = agentIds.get(0);
   List<String> traceIds = decoder.parameters().get("trace-id");
   checkNotNull(traceIds, "Missing trace id in query string: %s", request.uri());
   String traceId = traceIds.get(0);
   // check-live-traces is an optimization so glowroot server only has to check with remote
   // agents when necessary
   List<String> checkLiveTracesParams = decoder.parameters().get("check-live-traces");
   boolean checkLiveTraces = false;
   if (checkLiveTracesParams != null && !checkLiveTracesParams.isEmpty()) {
     checkLiveTraces = Boolean.parseBoolean(checkLiveTracesParams.get(0));
   }
   logger.debug(
       "handleRequest(): agentId={}, traceId={}, checkLiveTraces={}",
       agentId,
       traceId,
       checkLiveTraces);
   TraceExport traceExport = traceCommonService.getExport(agentId, traceId, checkLiveTraces);
   if (traceExport == null) {
     logger.warn("no trace found for id: {}", traceId);
     return new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND);
   }
   ChunkedInput<HttpContent> in = getExportChunkedInput(traceExport);
   HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
   response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
   response.headers().set(HttpHeaderNames.CONTENT_TYPE, MediaType.ZIP.toString());
   response
       .headers()
       .set("Content-Disposition", "attachment; filename=" + traceExport.fileName() + ".zip");
   boolean keepAlive = HttpUtil.isKeepAlive(request);
   if (keepAlive && !request.protocolVersion().isKeepAliveDefault()) {
     response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
   }
   HttpServices.preventCaching(response);
   ctx.write(response);
   ChannelFuture future = ctx.write(in);
   HttpServices.addErrorListener(future);
   if (!keepAlive) {
     HttpServices.addCloseListener(future);
   }
   // return null to indicate streaming
   return null;
 }
 @Override
 protected void finishAggregation(FullHttpMessage aggregated) throws Exception {
   // Set the 'Content-Length' header. If one isn't already set.
   // This is important as HEAD responses will use a 'Content-Length' header which
   // does not match the actual body, but the number of bytes that would be
   // transmitted if a GET would have been used.
   //
   // See rfc2616 14.13 Content-Length
   if (!HttpUtil.isContentLengthSet(aggregated)) {
     aggregated
         .headers()
         .set(
             HttpHeaderNames.CONTENT_LENGTH, String.valueOf(aggregated.content().readableBytes()));
   }
 }
  @Override
  protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
    assert !(start instanceof FullHttpMessage);

    HttpUtil.setTransferEncodingChunked(start, false);

    AggregatedFullHttpMessage ret;
    if (start instanceof HttpRequest) {
      ret = new AggregatedFullHttpRequest((HttpRequest) start, content, null);
    } else if (start instanceof HttpResponse) {
      ret = new AggregatedFullHttpResponse((HttpResponse) start, content, null);
    } else {
      throw new Error();
    }
    return ret;
  }
Example #7
0
  /** Write the response */
  private void writeResponse(ChannelHandlerContext ctx) {
    // Convert the response content to a ByteBuf.
    ByteBuf buf = Unpooled.copiedBuffer(responseContent.toString(), WaarpStringUtils.UTF8);
    responseContent.setLength(0);

    // Decide whether to close the connection or not.
    boolean keepAlive = HttpUtil.isKeepAlive(request);
    boolean close =
        HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(
                request.headers().get(HttpHeaderNames.CONNECTION))
            || (!keepAlive)
            || forceClose;

    // Build the response object.
    FullHttpResponse response =
        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
    response.headers().add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
    response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
    if (keepAlive) {
      response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
    }
    if (!close) {
      // There's no need to add 'Content-Length' header
      // if this is the last response.
      response.headers().set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(buf.readableBytes()));
    }

    handleCookies(response);

    // Write the response.
    ChannelFuture future = ctx.writeAndFlush(response);
    // Close the connection after the write operation is done if necessary.
    if (close) {
      future.addListener(WaarpSslUtility.SSLCLOSE);
    }
    if (shutdown) {
      ChannelUtils.startShutdown();
    }
  }
  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
    ChannelFuture lastWrite = null;
    boolean keepAlive = true;

    if (msg instanceof FullHttpRequest) {
      FullHttpRequest request = (FullHttpRequest) msg;
      keepAlive = HttpUtil.isKeepAlive(request);
      lastWrite = handleHttpRequest(ctx, request);
    } else if (msg instanceof WebSocketFrame) {
      lastWrite = handleWebSocketFrame(ctx, (WebSocketFrame) msg);
    }

    if (!keepAlive) {
      // Close the connection when the whole content is written out.
      if (lastWrite != null) {
        lastWrite.addListener(ChannelFutureListener.CLOSE);
      } else {
        ctx.close();
      }
    }
  }
  @Override
  public @Nullable FullHttpResponse handleRequest(ChannelHandlerContext ctx, HttpRequest request)
      throws Exception {

    File glowrootLogFile = new File(logDir, "glowroot.log");
    CharSource glowrootLogCharSource = Files.asCharSource(glowrootLogFile, Charsets.UTF_8);

    HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
    response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
    response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
    boolean keepAlive = HttpUtil.isKeepAlive(request);
    if (keepAlive && !request.protocolVersion().isKeepAliveDefault()) {
      response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
    }
    HttpServices.preventCaching(response);
    ctx.write(response);
    ChannelFuture future = ctx.write(ChunkedInputs.from(ChunkSource.from(glowrootLogCharSource)));
    HttpServices.addErrorListener(future);
    if (!keepAlive) {
      HttpServices.addCloseListener(future);
    }
    // return null to indicate streaming
    return null;
  }
  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);
  }
 /**
  * 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);
 }
  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);
    }
  }