package org.dcache.http; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.CharsetUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.channels.ClosedChannelException; import java.util.List; import diskCacheV111.util.HttpByteRange; import dmg.util.HttpException; import static io.netty.handler.codec.http.HttpHeaders.Names.*; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.NOT_IMPLEMENTED; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; public class HttpRequestHandler extends SimpleChannelInboundHandler<Object> { protected static final String CRLF = "\r\n"; private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestHandler.class); @Override public void channelRead0(ChannelHandlerContext ctx, Object msg) { if (msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; ChannelFuture future; if (request.getMethod() == HttpMethod.GET) { future = doOnGet(ctx, request); } else if (request.getMethod() == HttpMethod.PUT) { future = doOnPut(ctx, request); } else if (request.getMethod() == HttpMethod.POST) { future = doOnPost(ctx, request); } else if (request.getMethod() == HttpMethod.DELETE) { future = doOnDelete(ctx, request); } else if (request.getMethod() == HttpMethod.HEAD) { future = doOnHead(ctx, request); } else { future = unsupported(ctx); } if (future != null) { future.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); return; } } if (msg instanceof HttpContent) { ChannelFuture future = doOnContent(ctx, (HttpContent) msg); if (future != null) { future.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } } } protected ChannelFuture doOnGet(ChannelHandlerContext context, HttpRequest request) { LOGGER.debug("Received a GET request, writing a default response."); return unsupported(context); } protected ChannelFuture doOnPut(ChannelHandlerContext context, HttpRequest request) { LOGGER.debug("Received a PUT request, writing a default response."); return unsupported(context); } protected ChannelFuture doOnPost(ChannelHandlerContext context, HttpRequest request) { LOGGER.debug("Received a POST request, writing default response."); return unsupported(context); } protected ChannelFuture doOnDelete(ChannelHandlerContext context, HttpRequest request) { LOGGER.debug("Received a DELETE request, writing default response."); return unsupported(context); } protected ChannelFuture doOnContent(ChannelHandlerContext context, HttpContent chunk) { LOGGER.debug("Received an HTTP chunk, writing default response."); return unsupported(context); } protected ChannelFuture doOnHead(ChannelHandlerContext context, HttpRequest request) { LOGGER.debug("Received a HEAD request, writing default response."); return unsupported(context); } protected ChannelFuture unsupported( ChannelHandlerContext context) { return context.writeAndFlush(createErrorResponse(NOT_IMPLEMENTED, "The requested operation is not supported by dCache")); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) { if (t instanceof TooLongFrameException) { HttpTextResponse response = createErrorResponse(BAD_REQUEST, "Max request length exceeded"); HttpHeaders.setKeepAlive(response, false); ctx.channel().writeAndFlush(response); } else if (ctx.channel().isActive()) { // We cannot know whether the error was generated before or // after we sent the response headers - if we already sent // response headers then we cannot send an error response now. // Better just to close the channel. ctx.channel().close(); } if (t instanceof ClosedChannelException) { LOGGER.trace("ClosedChannelException for HTTP channel to {}", ctx.channel().remoteAddress()); } else if (t instanceof RuntimeException || t instanceof Error) { Thread me = Thread.currentThread(); me.getUncaughtExceptionHandler().uncaughtException(me, t); } else { LOGGER.warn(t.toString()); } } /** * Parse the HTTPRanges in the request, from the Range Header. * <p> * Return null if no range was found. * * @param request * @param lowerRange, as imposed by the backing physical file * @param upperRange, as imposed by the backing physical file * @return First byte range that was parsed */ protected List<HttpByteRange> parseHttpRange(HttpRequest request, long lowerRange, long upperRange) throws HttpException { String rangeHeader = request.headers().get(RANGE); if (rangeHeader != null) { try { return HttpByteRange.parseRanges(rangeHeader, lowerRange, upperRange); } catch (HttpException e) { /* * ignore errors in the range, if the If-Range header is present */ if (request.headers().get(IF_RANGE) == null) { throw e; } } } return null; } public static HttpTextResponse createErrorResponse(int status, String message) { return createErrorResponse(HttpResponseStatus.valueOf(status), message); } public static HttpTextResponse createErrorResponse(HttpResponseStatus status, String message) { if (message == null || message.isEmpty()) { message = "An unexpected server error has occurred."; } LOGGER.info("Sending error {} with message '{}' to client.", status, message); return new HttpTextResponse(status, message); } protected static class HttpTextResponse extends DefaultFullHttpResponse { public HttpTextResponse(HttpResponseStatus status, String message) { super(HTTP_1_1, status, Unpooled.copiedBuffer(message + CRLF, CharsetUtil.UTF_8)); headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); headers().set(CONTENT_LENGTH, content().readableBytes()); } } }