@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; }
/** 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); } }