/*
* 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.common.utils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
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.LocalFileFetcher.FETCHER;
import static com.sohail.alam.http.server.ServerProperties.PROP;
import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
/**
* User: Sohail Alam
* Version: 1.0.0
* Date: 22/9/13
* Time: 11:06 PM
*/
public class HttpResponseSender {
/**
* Send channel future.
*
* @param ctx the ctx
* @param status the status
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send(ChannelHandlerContext ctx, HttpResponseStatus status, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
final HttpResponse response;
// Create the headers map if null
if (headersMap == null) {
headersMap = new HashMap<String, String>();
}
// If data is not null then add content length header and send Full Http Response
if (data != null) {
ByteBuf dataBuffer = copiedBuffer(data);
response = new DefaultFullHttpResponse(HTTP_1_1, status, dataBuffer);
// If no content length is supplied then calculate it and add it
if (headersMap.get(CONTENT_LENGTH) == null) {
headersMap.put(CONTENT_LENGTH, String.valueOf(data.length));
}
}
// If data is null then add content length header to 0
else {
response = new DefaultHttpResponse(HTTP_1_1, status);
headersMap.put(CONTENT_LENGTH, String.valueOf(0));
}
// Iterate all headers from map and set response headers
for (String header : headersMap.keySet()) {
response.headers().set(header, headersMap.get(header));
}
// Send the response
ChannelFuture future = ctx.channel().write(response);
// Use default future listener if needed
if (useDefaultListener) {
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
LOGGER.debug("Response sent successfully:\n{}", response);
} else {
LOGGER.debug("FAILED TO SEND RESPONSE!!\n{}", response);
}
}
});
}
return future;
}
/**
* Check before send.
*
* @param ctx the ctx
* @param status the status
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
* @param defaultMessage the default message
*
* @return the channel future
*/
private static ChannelFuture checkBeforeSend(ChannelHandlerContext ctx, HttpResponseStatus status, Map<String, String> headersMap, byte[] data, boolean useDefaultListener, String defaultMessage) {
if (data == null) {
return send(ctx, status, headersMap, defaultMessage.getBytes(), useDefaultListener);
} else {
return send(ctx, status, headersMap, data, useDefaultListener);
}
}
/**
* Send 200 oK.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send200OK(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return send(ctx, OK, headersMap, data, useDefaultListener);
}
/**
* Send 400 bad request.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send400BadRequest(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, BAD_REQUEST, headersMap, data, useDefaultListener, "BAD REQUEST");
}
/**
* Send 401 unauthorized.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send401Unauthorized(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, UNAUTHORIZED, headersMap, data, useDefaultListener, "UNAUTHORIZED");
}
/**
* Send 403 forbidden.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send403Forbidden(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, FORBIDDEN, headersMap, data, useDefaultListener, "FORBIDDEN");
}
/**
* Send 404 not found.
* If the data is null then this method automatically takes care of
* sending a default 404 Page if configured and found,
* otherwise sends a custom message.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send404NotFound(final ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
final Map<String, String> headers = new HashMap<String, String>();
// If data is null then try to send the default 404 Page
if (data == null) {
FETCHER.fetch(PROP.DEFAULT_404_PAGE, new LocalFileFetcherCallback() {
// Send the default 404 Page if found
@Override
public void fetchSuccess(String path, byte[] data, String mediaType, long dataLength) {
headers.put(CONTENT_TYPE, mediaType);
headers.put(CONTENT_LENGTH, String.valueOf(dataLength));
// Send data as 404 Not Found
send404NotFound(ctx, headers, data, true);
}
// Otherwise send a 404 NOT FOUND message
@Override
public void fileNotFound(String path, Throwable cause) {
headers.put(CONTENT_TYPE, MediaType.getType(".html"));
send404NotFound(ctx, headers, "404 NOT FOUND".getBytes(), true);
}
@Override
public void exceptionCaught(String path, Throwable cause) {
headers.put(CONTENT_TYPE, MediaType.getType(".html"));
send404NotFound(ctx, headers, "404 NOT FOUND".getBytes(), true);
}
});
}
return send(ctx, NOT_FOUND, headersMap, data, useDefaultListener);
}
/**
* Send 405 method not allowed.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send405MethodNotAllowed(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, METHOD_NOT_ALLOWED, headersMap, data, useDefaultListener, "METHOD NOT ALLOWED");
}
/**
* Send 407 proxy authentication required.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send407ProxyAuthenticationRequired(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, PROXY_AUTHENTICATION_REQUIRED, headersMap, data, useDefaultListener, "PROXY AUTHENTICATION REQUIRED");
}
/**
* Send 408 request timeout.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send408RequestTimeout(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, REQUEST_TIMEOUT, headersMap, data, useDefaultListener, "REQUEST TIMEOUT");
}
/**
* Send 500 internal server error.
*
* @param ctx the ctx
* @param headersMap the headers map
* @param data the data
* @param useDefaultListener use default listener
*
* @return the channel future
*/
public static ChannelFuture send500InternalServerError(ChannelHandlerContext ctx, Map<String, String> headersMap, byte[] data, boolean useDefaultListener) {
return checkBeforeSend(ctx, INTERNAL_SERVER_ERROR, headersMap, data, useDefaultListener, "INTERNAL SERVER ERROR");
}
}