/* * Copyright 2013 The Http Server & Proxy * * The Http Server & Proxy Project licenses this file to you under the Apache License, version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ package com.sohail.alam.http.server; import com.sohail.alam.http.common.utils.HttpMethodCodes; import com.sohail.alam.http.common.utils.LocalFileFetcherCallback; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import static com.sohail.alam.http.common.LoggerManager.LOGGER; import static com.sohail.alam.http.common.utils.HttpResponseSender.*; import static com.sohail.alam.http.common.utils.LocalFileFetcher.FETCHER; import static com.sohail.alam.http.server.ServerProperties.PROP; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; /** * User: Sohail Alam * Version: 1.0.0 * Date: 21/9/13 * Time: 9:45 PM */ public class HttpClientHandler extends SimpleChannelInboundHandler<HttpObject> { private HttpRequest request; private ChannelHandlerContext ctx; private HttpMethod requestMethod; private String requestUriPath; /** * Instantiates a new Http client handler. */ public HttpClientHandler() { LOGGER.trace("Http Client Handler Initialized"); } /** * Channel active. * * @param ctx the ctx * * @throws Exception the exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); this.ctx = ctx; } /** * Message received. * * @param ctx the ctx * @param msg the msg * * @throws Exception the exception */ @Override protected void messageReceived(ChannelHandlerContext ctx, HttpObject msg) throws Exception { // If decoder failed if (!msg.getDecoderResult().isSuccess()) { LOGGER.debug("Could not decode received request"); send400BadRequest(ctx, null, "Could not decode received request".getBytes(), true); return; } // If the msg is a HttpRequest then process it if (msg instanceof HttpRequest) { request = (HttpRequest) msg; processHttpRequest(); } } /** * Exception caught. * * @param ctx the ctx * @param cause the cause * * @throws Exception the exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.debug("Handler Exception Caught: {}", cause.getMessage()); send500InternalServerError(ctx, null, null, true); } /** * Process http request. */ private void processHttpRequest() { this.requestMethod = request.getMethod(); try { URI uri = new URI(request.getUri()); this.requestUriPath = uri.getPath(); } catch (URISyntaxException e) { e.printStackTrace(); } LOGGER.info("Request Received: => {} => {} => {}", this.ctx.channel().remoteAddress(), this.requestMethod, this.requestUriPath); LOGGER.debug("Processing Http Request:\n{}", request); switch (HttpMethodCodes.httpMethodCode.get(this.requestMethod)) { case HttpMethodCodes.CONNECT: if (PROP.IS_CONNECT_ALLOWED) { processHttpConnectRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.DELETE: if (PROP.IS_DELETE_ALLOWED) { processHttpDeleteRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.GET: if (PROP.IS_GET_ALLOWED) { processHttpGetRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.HEAD: if (PROP.IS_HEAD_ALLOWED) { processHttpHeadRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.OPTIONS: if (PROP.IS_OPTIONS_ALLOWED) { processHttpOptionsRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.PATCH: if (PROP.IS_PATCH_ALLOWED) { processHttpPatchRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.POST: if (PROP.IS_POST_ALLOWED) { processHttpPostRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.PUT: if (PROP.IS_PUT_ALLOWED) { processHttpPutRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; case HttpMethodCodes.TRACE: if (PROP.IS_TRACE_ALLOWED) { processHttpTraceRequest(); } else { send405MethodNotAllowed(ctx, null, null, true); } break; default: send405MethodNotAllowed(ctx, null, null, true); break; } } /** * TODO: Process http connect request. */ private void processHttpConnectRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * TODO: Process http delete request. */ private void processHttpDeleteRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * TODO: Process http patch request. */ private void processHttpPatchRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * Process http get request. */ private void processHttpGetRequest() { FETCHER.fetch(this.requestUriPath, new FileFetcherCallback()); } /** * TODO: Process http head request. */ private void processHttpHeadRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * TODO: Process http options request. */ private void processHttpOptionsRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * TODO: Process http post request. */ private void processHttpPostRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * TODO: Process http put request. */ private void processHttpPutRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * TODO: Process http trace request. */ private void processHttpTraceRequest() { send200OK(ctx, null, "Functionality to be provided soon!".getBytes(), true); } /** * The File fetcher callback class. */ private class FileFetcherCallback implements LocalFileFetcherCallback { private Map<String, String> headers = new HashMap<String, String>(); /** * Fetch success is called when a file is read successfully and * the data is ready to be delivered. * * @param path the path from which the file was read (Normalized Path) * @param data the data as byte array * @param mediaType the media type * @param dataLength the data length */ @Override public void fetchSuccess(String path, byte[] data, String mediaType, long dataLength) { LOGGER.debug("File {} Successfully fetched, length: {}", path, data.length); headers.put(CONTENT_TYPE, mediaType); headers.put(CONTENT_LENGTH, String.valueOf(dataLength)); send200OK(ctx, headers, data, true); } /** * Exception caught. * * @param path the path * @param cause the cause */ @Override public void exceptionCaught(String path, Throwable cause) { LOGGER.debug("Exception Caught: {}", cause.getMessage()); if (cause.getMessage().contains("Access is denied")) { send500InternalServerError(ctx, headers, "ACCESS DENIED".getBytes(), true); } else { send500InternalServerError(ctx, headers, null, true); } } /** * Fetch failed is called whenever there was an error reading the file * * @param path the path from which the file was to be read (Normalized Path) * @param cause the throwable object containing the cause */ @Override public void fileNotFound(String path, final Throwable cause) { LOGGER.debug("File Not Found: {}", cause.getMessage()); // Check if access is denied or file not found if (cause.getMessage().contains("(Access is denied)")) { send403Forbidden(ctx, headers, null, true); } else { send404NotFound(ctx, headers, null, true); } } } }