wildfire chat server source initial submit

This commit is contained in:
heavyrain2012
2019-03-16 17:04:23 +08:00
parent e59217bd10
commit cab3ca0916
1466 changed files with 528862 additions and 15919 deletions
@@ -0,0 +1,144 @@
package com.xiaoleilu.loServer.handler;
import java.io.IOException;
import com.hazelcast.core.HazelcastInstance;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.action.FileAction;
import com.xiaoleilu.loServer.filter.Filter;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Action处理单元
*
* @author Looly
*/
abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger Logger = LoggerFactory.getLogger(ActionHandler.class);
public ActionHandler(IMessagesStore messagesStore, ISessionsStore sessionsStore) {
Action.messagesStore = messagesStore;
Action.sessionsStore = sessionsStore;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
Logger.info("Http request whit url {}", fullHttpRequest.uri());
final Request request = Request.build(ctx, fullHttpRequest);
final Response response = Response.build(ctx, request);
response.setContentType("application/json");
try {
//do filter
boolean isPass = this.doFilter(request, response);
if(isPass){
//do action
this.doAction(ctx, request, response);
}
} catch (Exception e) {
Action errorAction = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
request.putParam(UnknownErrorAction.ERROR_PARAM_NAME, e);
response.setContent(e.toString());
errorAction.doAction(request, response);
//如果发送请求未被触发,则触发之,否则跳过。
if(!response.isSent()){
response.send();
}
ctx.fireExceptionCaught(e);
ctx.close();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
Logger.warn("{}", cause.getMessage());
Channel channel = ctx.channel();
if(channel.isActive())ctx.close();
}
//---------------------------------------------------------------------------------------- Private method start
/**
* 执行过滤
* @param request 请求
* @param response 响应
* @@return 是否继续
*/
private boolean doFilter(Request request, Response response) {
//全局过滤器
Filter filter = ServerSetting.getFilter(ServerSetting.MAPPING_ALL);
if(null != filter){
if(false == filter.doFilter(request, response)){
return false;
}
}
//自定义Path过滤器
filter = ServerSetting.getFilter(request.getPath());
if(null != filter){
if(false == filter.doFilter(request, response)){
return false;
}
}
return true;
}
abstract boolean isValidePath(String path);
/**
* 执行Action
* @param request 请求对象
* @param response 响应对象
*/
private void doAction(ChannelHandlerContext ctx, Request request, Response response){
Action action;
if (isValidePath(request.getPath())) {
action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase());
} else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
if (action == null && (request.getPath().startsWith("/api") || request.getPath().startsWith("/admin"))) {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
if (null == action) {
//查找匹配所有路径的Action
action = ServerSetting.getAction(ServerSetting.MAPPING_ALL, request.getMethod());
if(null == action){
// 非Action方法,调用静态文件读取
action = Singleton.get(FileAction.class);
}
}
action.ctx = ctx;
boolean isSync = action.doAction(request, response);
if(isSync) {
//如果发送请求未被触发,则触发之,否则跳过。
if(!response.isSent()){
response.send();
}
}
}
//---------------------------------------------------------------------------------------- Private method start
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.handler;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.FileAction;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.filter.Filter;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Action处理单元
*
* @author Looly
*/
public class AdminActionHandler extends ActionHandler {
private static final Logger Logger = LoggerFactory.getLogger(AdminActionHandler.class);
public AdminActionHandler(IMessagesStore messagesStore, ISessionsStore sessionsStore) {
super(messagesStore, sessionsStore);
}
@Override
boolean isValidePath(String path) {
if (path.startsWith("/admin")) {
return true;
}
return false;
}
}
@@ -0,0 +1,28 @@
package com.xiaoleilu.loServer.handler;
import com.xiaoleilu.hutool.log.StaticLog;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.FileRegion;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpContentCompressor;
/**
* 解决大文件传输与Gzip压缩冲突问题
* @author Looly
*
*/
public class HttpChunkContentCompressor extends HttpContentCompressor {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
StaticLog.debug("Write object [{}]", msg);
if (msg instanceof FileRegion || msg instanceof DefaultHttpResponse) {
//文件传输不经过Gzip压缩
ctx.write(msg, promise);
}else{
super.write(ctx, msg, promise);
}
}
}
@@ -0,0 +1,75 @@
package com.xiaoleilu.loServer.handler;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class HttpFileServerController {
private static final Logger logger = LoggerFactory.getLogger(HttpFileServerController.class);
private Map<String, ChannelHandlerContext> contextMap = null; //
private static class BrokenUploadInfoHandlerHolder {
private static HttpFileServerController singleton = new HttpFileServerController();
}
/**
* constructor
*/
private HttpFileServerController() {
contextMap = new ConcurrentHashMap<String, ChannelHandlerContext>();
}
/**
* getInstance
*/
public static HttpFileServerController getInstance() {
return BrokenUploadInfoHandlerHolder.singleton;
}
/**
* mapChannelHandlerContext
*
* @param requestId
* @param ctx
*/
public synchronized void mapChannelHandlerContext(String requestId, ChannelHandlerContext ctx) {
if (contextMap.containsKey(requestId)) {
logger.warn("contextMap has already contained the key:" + requestId);
}
contextMap.put(requestId, ctx);
}
/**
* getContext
*
* @param requestId
* @return
*/
public ChannelHandlerContext getChannelHandlerContext(String requestId) {
if (!contextMap.containsKey(requestId)) {
logger.warn("contextMap not contains the key:" + requestId);
return null;
}
return contextMap.get(requestId);
}
/**
* removeContext
*
* @param requestId
*/
public void removeChannelHandlerContext(String requestId) {
if (!contextMap.containsKey(requestId)) {
logger.warn("contextMap not contains the key:" + requestId);
return;
}
contextMap.remove(requestId);
// logger.info("contextMap remove done.(" + requestId + ")");
}
}
@@ -0,0 +1,420 @@
package com.xiaoleilu.loServer.handler;
import io.netty.util.internal.StringUtil;
import io.moquette.server.config.MediaServerConfig;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.UUID;
import static com.xiaoleilu.loServer.handler.HttpResponseHelper.getFileExt;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class HttpFileServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(HttpFileServerHandler.class);
private static final HttpDataFactory factory = new DefaultHttpDataFactory(false);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
String requestId = UUID.randomUUID().toString().replace("-", "");
logger.info("HttpFileServerHandler received a request: method=" + request.getMethod() + ", uri=" + request.getUri() + ", requestId=" + requestId);
if (!request.getDecoderResult().isSuccess()) {
logger.warn("http decode failed!");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST);
return;
}
if (request.getMethod() == HttpMethod.GET) {
download(ctx, request, requestId);
} else if (request.getMethod() == HttpMethod.POST) {
multipartUpload(ctx, request, requestId);
} else if (request.getMethod() == HttpMethod.DELETE) {
delete(ctx, request, requestId);
} else if (request.getMethod() == HttpMethod.OPTIONS) {
doOptions(ctx, request);
} else {
logger.warn("METHOD_NOT_ALLOWED!(methodName=" + request.getMethod().name() + ")");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
}
} else {
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Not a http request");
}
} catch (Exception e) {
logger.error("HttpFileServerHandler.channelRead0() process error!", e);
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
try {
if (ctx.channel().isActive()) {
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
logger.warn("exceptionCaught:" + cause.getMessage());
}
} catch (Exception e) {
logger.warn("exceptionCaught error!" + e.getMessage());
}
}
/**
* multipart上传
*/
private void multipartUpload(ChannelHandlerContext ctx, FullHttpRequest request, String requestId) {
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(factory, request);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
logger.error("Failed to decode file data!", e1);
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Failed to decode file data!");
return;
}
if (decoder != null) {
if (request instanceof HttpContent) {
HttpContent chunk = (HttpContent) request;
try {
decoder.offer(chunk);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
logger.warn("BAD_REQUEST, Failed to decode file data");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Failed to decode file data");
return;
}
long fileTotalSize = 0;
if (request.headers().contains("X-File-Total-Size")) {
try {
fileTotalSize = Integer.parseInt(request.headers().get("X-File-Total-Size"));
} catch (Exception e) {
logger.warn("invalid X-File-Total-Size value!");
}
}
readHttpDataChunkByChunk(ctx, decoder, requestId, HttpHeaders.isKeepAlive(request));
if (chunk instanceof LastHttpContent) {
releaseRequest(request, decoder);
}
} else {
logger.warn("BAD_REQUEST, Not a http request");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Not a http request");
}
}
}
/**
* readHttpDataChunkByChunk
*/
private void readHttpDataChunkByChunk(ChannelHandlerContext ctx, HttpPostRequestDecoder decoder, String requestId, boolean isKeepAlive) {
try {
while (decoder.hasNext()) {
InterfaceHttpData data = decoder.next();
if (data != null) {
try {
writeFileUploadData(data, ctx, requestId, isKeepAlive);
} finally {
data.release();
}
}
}
} catch (Exception e) {
logger.info("chunk end");
}
}
/**
* writeFileUploadData
*/
private void writeFileUploadData(InterfaceHttpData data, ChannelHandlerContext ctx, String requestId, boolean isKeepAlive) {
try {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
FileUpload fileUpload = (FileUpload) data;
String remoteFileName = fileUpload.getFilename();
long remoteFileSize = fileUpload.length();
if (StringUtil.isNullOrEmpty(remoteFileName)) {
logger.warn("remoteFileName is empty!");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "file name is empty");
return;
}
if (StringUtil.isNullOrEmpty(requestId)) {
logger.warn("requestId is empty!");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "requestId is empty!");
return;
}
if (remoteFileSize > 10 * 1024 * 1024) {
logger.warn("file over limite!(" + remoteFileSize + ")");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "file over limite!");
return;
}
String remoteFileExt = "";
if (remoteFileName.lastIndexOf(".") == -1) {
remoteFileExt = "octetstream";
remoteFileName = remoteFileName + "." + remoteFileExt;
} else {
remoteFileExt = getFileExt(remoteFileName);
}
if (StringUtil.isNullOrEmpty(remoteFileExt) || remoteFileExt.equals("ing")) {
logger.warn("Invalid file extention name");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Invalid file extention name");
return;
}
int remoteFileTotalSize = (int) remoteFileSize;
HttpFileServerController.getInstance().mapChannelHandlerContext(requestId, ctx);
ByteBuf byteBuf = null;
int savedThunkSize = 0; // 分片接收保存的大小
int offset = 0; // 断点续传开始位置
File tmpFile = new File("./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + requestId);
boolean isError = false;
while (true) {
byte[] thunkData;
try {
byteBuf = fileUpload.getChunk(128 * 1024);
int readableBytesSize = byteBuf.readableBytes();
thunkData = new byte[readableBytesSize];
byteBuf.readBytes(thunkData);
put(tmpFile, offset, thunkData);
savedThunkSize += readableBytesSize;
offset += readableBytesSize;
if (savedThunkSize >= remoteFileSize) {
byteBuf.release();
fileUpload.release();
HttpResponseHelper.sendResponse(requestId, ctx, HttpResponseStatus.OK, HttpHeaders.Values.APPLICATION_JSON, "{\"key\":\"" + requestId + "\"}", true, false);
break;
}
} catch (Exception e) {
logger.error("save thunckData error!", e);
if (fileUpload != null)
fileUpload.release();
if (byteBuf != null)
byteBuf.release();
HttpResponseHelper.sendResponse(requestId, ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
isError = true;
return;
} finally {
thunkData = null;
if (isError) {
tmpFile.delete();
}
}
}
}
} catch (Exception e) {
logger.error("writeHttpData error!", e);
HttpResponseHelper.sendResponse(requestId, ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
public static void put(File file, long pos, byte[] data) throws Exception {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "rwd");
raf.seek(pos);
raf.write(data);
} finally {
try {
if (raf != null)
raf.close();
} catch (Exception e) {
logger.warn("release error!", e);
}
}
}
/**
* 下载
*/
private void download(ChannelHandlerContext ctx, FullHttpRequest request, String requestId) {
// try {
// final String httpUri = request.getUri();
// if (httpUri.toLowerCase().equals("/favicon.ico")) {
// logger.warn("invalid httpUri(" + httpUri + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid httpUri");
// return;
// }
//
// String typedDownloadInfo = MetaDataIndexHandler.getTypedDownloadInfo(httpUri);
// if (StringUtil.isNullOrEmpty(typedDownloadInfo)) {
// logger.warn("invalid httpUri(" + httpUri + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid httpUri");
// return;
// }
//
// MetaDataIndex metadataIndex = MetaDataIndexHandler.parseTypedDownloadInfo(typedDownloadInfo);
// if (metadataIndex == null) {
// logger.warn("invalid typedDownloadInfo!(" + typedDownloadInfo + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid downloadFileName");
// return;
// }
//
// int fileSize = MetaDataHandler.getDataFieldLength(metadataIndex);
// int chunkSize = FileStorageConst.Stroage_Remote_Invoke_Thunk_Size;
// int chunkCount = fileSize % chunkSize == 0 ? (int) (fileSize / chunkSize) : (int) (fileSize / chunkSize + 1);
//
// ClusterNode toNode = StorageClusterRouterManager.getInstance().getDownloadNode(metadataIndex.getClusterNode());
// if(toNode == null) {
// logger.warn("toNode is null!(" + typedDownloadInfo + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.NOT_FOUND, "File Not Existed");
// return;
// }
//
// ClusterNode fromNode = StorageClusterRouterManager.getInstance().getSelfNode();
// ActorSystem actorSystem = StorageClusterRouterManager.getInstance().getStorageActorSystem();
// ActorRef toActorRef = actorSystem.actorOf(toNode.getIp(), toNode.getRpcPort(), ActorType.DownloadActor.getName());
// ActorRef fromActorRef = actorSystem.actorOf(fromNode.getIp(),fromNode.getRpcPort(), ActorType.HttpFileServerActor.getName());
//
// HttpFileServerController.getInstance().mapChannelHandlerContext(requestId, ctx);
//
// DownloadMsg downloadMsg = new DownloadMsg();
// downloadMsg.setRequestId(requestId);
// downloadMsg.setIsKeepAlive(HttpHeaders.isKeepAlive(request));
// downloadMsg.setClusterNode(metadataIndex.getClusterNode());
// downloadMsg.setFileSize(fileSize);
// downloadMsg.setChunkIndex(0);
// downloadMsg.setChunkSize(chunkSize);
// downloadMsg.setChunkCount(chunkCount);
// downloadMsg.setTime(metadataIndex.getTime());
// downloadMsg.setBigFileIndex(metadataIndex.getBigFileIndex());
// downloadMsg.setOffset(metadataIndex.getOffset());
// downloadMsg.setMetaDataTotalLength(metadataIndex.getMetaDataTotalLength());
// downloadMsg.setFileType(metadataIndex.getFileType());
// downloadMsg.setBigFilePath(metadataIndex.getBigFilePath());
// downloadMsg.setStorageEngineVersion(metadataIndex.getStorageEngineVersion());
// toActorRef.tell(downloadMsg, fromActorRef);
// } catch (Exception e) {
// logger.error("download error!", e);
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
// } finally {
// releaseRequest(request, null);
// }
}
/**
* 删除
*/
private void delete(ChannelHandlerContext ctx, FullHttpRequest request, String requestId) {
// try {
// final String httpUri = request.getUri();
// String typedDownloadInfo = MetaDataIndexHandler.getTypedDownloadInfo(httpUri);
// if (StringUtil.isNullOrEmpty(typedDownloadInfo)) {
// logger.warn("invalid httpUri(" + httpUri + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid httpUri");
// return;
// }
//
// MetaDataIndex metadataIndex = MetaDataIndexHandler.parseTypedDownloadInfo(typedDownloadInfo);
// if (metadataIndex == null) {
// logger.warn("invalid typedDownloadInfo!(" + typedDownloadInfo + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid downloadFileName");
// }
//
// ClusterNode toNode = StorageClusterRouterManager.getInstance().getClusterNode(metadataIndex.getClusterNode());
// ClusterNode fromNode = StorageClusterRouterManager.getInstance().getSelfNode();
// ActorSystem actorSystem = StorageClusterRouterManager.getInstance().getStorageActorSystem();
// ActorRef toActorRef = actorSystem.actorOf(toNode.getIp(), toNode.getRpcPort(), ActorType.DeleteActor.getName());
// ActorRef fromActorRef = actorSystem.actorOf(fromNode.getIp(),fromNode.getRpcPort(), ActorType.HttpFileServerActor.getName());
//
// HttpFileServerController.getInstance().mapChannelHandlerContext(requestId, ctx);
//
// DeleteMsg downloadMsg = new DeleteMsg();
// downloadMsg.setRequestId(requestId);
// downloadMsg.setIsKeepAlive(HttpHeaders.isKeepAlive(request));
// downloadMsg.setClusterNode(metadataIndex.getClusterNode());
// downloadMsg.setTime(metadataIndex.getTime());
// downloadMsg.setBigFileIndex(metadataIndex.getBigFileIndex());
// downloadMsg.setOffset(metadataIndex.getOffset());
// downloadMsg.setMetaDataTotalLength(metadataIndex.getMetaDataTotalLength());
// downloadMsg.setFileType(metadataIndex.getFileType());
// downloadMsg.setBigFilePath(metadataIndex.getBigFilePath());
// downloadMsg.setStorageEngineVersion(metadataIndex.getStorageEngineVersion());
// toActorRef.tell(downloadMsg, fromActorRef);
// } catch (Exception e) {
// logger.error("delete error!", e);
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
// } finally {
// releaseRequest(request, null);
// }
}
/**
* HTTP能力探测
*/
private void doOptions(ChannelHandlerContext ctx, FullHttpRequest request) {
try {
boolean isKeepAlive = HttpHeaders.isKeepAlive(request);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NO_CONTENT);
response.headers().set(CONTENT_TYPE, HttpHeaders.Values.APPLICATION_JSON);
response.headers().set(TRANSFER_ENCODING, "chunked");
if (isKeepAlive)
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().set(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, DELETE, OPTIONS");
response.headers().set(ACCESS_CONTROL_ALLOW_HEADERS, "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization");
ChannelFuture future = ctx.channel().writeAndFlush(response);
if (!isKeepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
} catch (Exception e) {
logger.error("doOptions error!", e);
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
} finally {
releaseRequest(request, null);
}
}
/**
* releaseRequest
*/
private void releaseRequest(FullHttpRequest request, HttpPostRequestDecoder decoder) {
try {
if (request != null)
request.release();
request = null;
if (decoder != null)
decoder.destroy();
decoder = null;
} catch (Exception e) {
logger.error("reset error!", e);
}
}
}
@@ -0,0 +1,179 @@
package com.xiaoleilu.loServer.handler;
import io.netty.util.internal.StringUtil;
import com.xiaoleilu.hutool.util.FileUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.activation.MimetypesFileTypeMap;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_RANGE;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class HttpResponseHelper {
private static final Logger logger = LoggerFactory.getLogger(HttpResponseHelper.class);
private static final MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
/**
* sendResponse
*
* @param ctx
* @param status
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, HttpResponseStatus status) {
sendResponse(requestId, ctx, status, "status: " + status.toString() + "\r\n");
}
/**
* sendResponse
*
* @param ctx
* @param status
* @param msg
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, HttpResponseStatus status, String msg) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
removeChannelHandlerContext(requestId, ctx);
}
/**
* sendResponse(成功应答需要注意isKeepAlive和isCloseChannel的值)
*
* @param requestId
* @param ctx
* @param status
* @param contentType
* @param respBodyString
* @param isKeepAlive
* @param isCloseChannel
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, HttpResponseStatus status, String contentType, String respBodyString, boolean isKeepAlive, boolean isCloseChannel) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(respBodyString, CharsetUtil.UTF_8));
if (!StringUtil.isNullOrEmpty(contentType)) {
response.headers().set(CONTENT_TYPE, contentType);
} else {
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
}
if (isKeepAlive) {
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ChannelFuture future = ctx.channel().writeAndFlush(response);
if (!isKeepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
removeChannelHandlerContext(requestId, ctx, isCloseChannel);
}
/**
* sendResponse
*
* @param ctx
* @param statusCode
* @param resultDesc
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, int statusCode, String resultDesc) {
HttpResponseStatus httpResponseStatus = new HttpResponseStatus(statusCode, resultDesc);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, httpResponseStatus, Unpooled.copiedBuffer(resultDesc, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
removeChannelHandlerContext(requestId, ctx);
}
/**
* 断点上传204应答
*
* @param ctx
* @param contentRange
*/
public static void sendBrokenUpload204Resonse(String requestId, ChannelHandlerContext ctx, String contentRange, boolean isKeepAlive, boolean isCloseChannel) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NO_CONTENT, Unpooled.copiedBuffer("", CharsetUtil.UTF_8));
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().set(CONTENT_RANGE, contentRange);
ChannelFuture future = ctx.channel().writeAndFlush(response);
if (!isKeepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
removeChannelHandlerContext(requestId, ctx, isCloseChannel);
}
public static String getMultipartUploadResponseBody(String remoteFileName, String fileUrl) throws Exception {
return "{\"rc_url\":{\"path\":\"" + fileUrl + "\",\"type\":0}}";
}
/**
* getContentType
*
* @param fileName
* @return
*/
public static String getFileExt(String fileName) {
int index = fileName.lastIndexOf(".");
if (index == -1) {
return "";
}
return fileName.substring(index + 1).toLowerCase();
}
public static String getContentType(String fileName) {
String fileExtName = getFileExt(fileName);
if (fileExtName.equals("png")) { // MimetypesFileTypeMap目前缺少png类型
return "image/png";
} else {
return mimetypesFileTypeMap.getContentType(fileName);
}
}
/**
* removeChannelHandlerContext
*
* @param requestId
* @param ctx
*/
private static void removeChannelHandlerContext(String requestId, ChannelHandlerContext ctx) {
removeChannelHandlerContext(requestId, ctx, true);
}
private static void removeChannelHandlerContext(String requestId, ChannelHandlerContext ctx, boolean isCloseChannel) {
try {
if (!StringUtil.isNullOrEmpty(requestId)) {
// HttpFileServerController.getInstance().removeChannelHandlerContext(requestId);
}
if (isCloseChannel)
ctx.channel().close();
} catch (Exception e) {
logger.error("close error!", e);
}
}
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.handler;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.FileAction;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.filter.Filter;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Action处理单元
*
* @author Looly
*/
public class IMActionHandler extends ActionHandler {
private static final Logger Logger = LoggerFactory.getLogger(IMActionHandler.class);
public IMActionHandler(IMessagesStore messagesStore, ISessionsStore sessionsStore) {
super(messagesStore, sessionsStore);
}
@Override
boolean isValidePath(String path) {
if (!path.startsWith("/admin")) {
return true;
}
return false;
}
}
@@ -0,0 +1,531 @@
package com.xiaoleilu.loServer.handler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.xiaoleilu.hutool.http.HttpUtil;
import com.xiaoleilu.hutool.lang.Conver;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.CharsetUtil;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.hutool.util.URLUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
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.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import org.slf4j.LoggerFactory;
/**
* Http请求对象
*
* @author Looly
*
*/
public class Request {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(Request.class);
public static final String METHOD_DELETE = HttpMethod.DELETE.name();
public static final String METHOD_HEAD = HttpMethod.HEAD.name();
public static final String METHOD_GET = HttpMethod.GET.name();
public static final String METHOD_OPTIONS = HttpMethod.OPTIONS.name();
public static final String METHOD_POST = HttpMethod.POST.name();
public static final String METHOD_PUT = HttpMethod.PUT.name();
public static final String METHOD_TRACE = HttpMethod.TRACE.name();
private static final HttpDataFactory HTTP_DATA_FACTORY = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
private FullHttpRequest nettyRequest;
private String path;
private String ip;
private Map<String, String> headers = new HashMap<String, String>();
private Map<String, Object> params = new HashMap<String, Object>();
private Map<String, Cookie> cookies = new HashMap<String, Cookie>();
/**
* 构造
*
* @param ctx ChannelHandlerContext
* @param nettyRequest HttpRequest
*/
private Request(ChannelHandlerContext ctx, FullHttpRequest nettyRequest) {
this.nettyRequest = nettyRequest;
final String uri = nettyRequest.uri();
this.path = URLUtil.getPath(getUri());
this.putHeadersAndCookies(nettyRequest.headers());
// request URI parameters
this.putParams(new QueryStringDecoder(uri));
if(nettyRequest.method() != HttpMethod.GET){
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(HTTP_DATA_FACTORY, nettyRequest);
this.putParams(decoder);
} finally {
if(null != decoder){
decoder.destroy();
decoder = null;
}
}
}
// IP
this.putIp(ctx);
}
/**
* @return Netty的HttpRequest
*/
public HttpRequest getNettyRequest() {
return this.nettyRequest;
}
/**
* 获得版本信息
*
* @return 版本
*/
public String getProtocolVersion() {
return nettyRequest.protocolVersion().text();
}
/**
* 获得URI(带参数的路径)
*
* @return URI
*/
public String getUri() {
return nettyRequest.uri();
}
/**
* @return 获得path(不带参数的路径)
*/
public String getPath() {
return path;
}
/**
* 获得Http方法
*
* @return Http method
*/
public String getMethod() {
return nettyRequest.method().name();
}
/**
* 获得IP地址
*
* @return IP地址
*/
public String getIp() {
return ip;
}
/**
* 获得所有头信息
*
* @return 头信息Map
*/
public Map<String, String> getHeaders() {
return headers;
}
/**
* 使用ISO8859_1字符集获得Header内容<br>
* 由于Header中很少有中文,故一般情况下无需转码
*
* @param headerKey 头信息的KEY
* @return 值
*/
public String getHeader(String headerKey) {
return headers.get(headerKey);
}
/**
* @return 是否为普通表单(application/x-www-form-urlencoded)
*/
public boolean isXWwwFormUrlencoded() {
return "application/x-www-form-urlencoded".equals(getHeader("Content-Type"));
}
/**
* 获得指定的Cookie
*
* @param name cookie名
* @return Cookie对象
*/
public Cookie getCookie(String name) {
return cookies.get(name);
}
/**
* @return 获得所有Cookie信息
*/
public Map<String, Cookie> getCookies() {
return this.cookies;
}
/**
* @return 客户浏览器是否为IE
*/
public boolean isIE() {
String userAgent = getHeader("User-Agent");
if (StrUtil.isNotBlank(userAgent)) {
userAgent = userAgent.toUpperCase();
if (userAgent.contains("MSIE") || userAgent.contains("TRIDENT")) {
return true;
}
}
return false;
}
/**
* @param name 参数名
* @return 获得请求参数
*/
public String getParam(String name) {
final Object value = params.get(name);
if(null == value){
return null;
}
if(value instanceof String){
return (String)value;
}
return value.toString();
}
/**
* @param name 参数名
* @return 获得请求参数
*/
public Object getObjParam(String name) {
return params.get(name);
}
/**
* 获得GET请求参数<br>
* 会根据浏览器类型自动识别GET请求的编码方式从而解码<br>
* charsetOfServlet为null则默认的ISO_8859_1
*
* @param name 参数名
* @param charset 字符集
* @return 获得请求参数
*/
public String getParam(String name, Charset charset) {
if (null == charset) {
charset = Charset.forName(CharsetUtil.ISO_8859_1);
}
String destCharset = CharsetUtil.UTF_8;
if (isIE()) {
// IE浏览器GET请求使用GBK编码
destCharset = CharsetUtil.GBK;
}
String value = getParam(name);
if (METHOD_GET.equalsIgnoreCase(getMethod())) {
value = CharsetUtil.convert(value, charset.toString(), destCharset);
}
return value;
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得请求参数
*/
public String getParam(String name, String defaultValue) {
String param = getParam(name);
return StrUtil.isBlank(param) ? defaultValue : param;
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Integer类型请求参数
*/
public Integer getIntParam(String name, Integer defaultValue) {
return Conver.toInt(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得long类型请求参数
*/
public Long getLongParam(String name, Long defaultValue) {
return Conver.toLong(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Double类型请求参数
*/
public Double getDoubleParam(String name, Double defaultValue) {
return Conver.toDouble(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Float类型请求参数
*/
public Float getFloatParam(String name, Float defaultValue) {
return Conver.toFloat(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Boolean类型请求参数
*/
public Boolean getBoolParam(String name, Boolean defaultValue) {
return Conver.toBool(getParam(name), defaultValue);
}
/**
* 格式:<br>
* 1、yyyy-MM-dd HH:mm:ss <br>
* 2、yyyy-MM-dd <br>
* 3、HH:mm:ss <br>
*
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Date类型请求参数,默认格式:
*/
public Date getDateParam(String name, Date defaultValue) {
String param = getParam(name);
return StrUtil.isBlank(param) ? defaultValue : DateUtil.parse(param);
}
/**
* @param name 参数名
* @param format 格式
* @param defaultValue 当客户端未传参的默认值
* @return 获得Date类型请求参数
*/
public Date getDateParam(String name, String format, Date defaultValue) {
String param = getParam(name);
return StrUtil.isBlank(param) ? defaultValue : DateUtil.parse(param, format);
}
/**
* 获得请求参数<br>
* 列表类型值,常用于表单中的多选框
*
* @param name 参数名
* @return 数组
*/
@SuppressWarnings("unchecked")
public List<String> getArrayParam(String name) {
Object value = params.get(name);
if(null == value){
return null;
}
if(value instanceof List){
return (List<String>) value;
}else if(value instanceof String){
return StrUtil.split((String)value, ',');
}else{
throw new RuntimeException("Value is not a List type!");
}
}
/**
* 获得所有请求参数
*
* @return Map
*/
public Map<String, Object> getParams() {
return params;
}
/**
* @return 是否为长连接
*/
public boolean isKeepAlive() {
final String connectionHeader = getHeader(HttpHeaderNames.CONNECTION.toString());
// 无论任何版本Connection为close时都关闭连接
if (HttpHeaderValues.CLOSE.toString().equalsIgnoreCase(connectionHeader)) {
return false;
}
// HTTP/1.0只有Connection为Keep-Alive时才会保持连接
if (HttpVersion.HTTP_1_0.text().equals(getProtocolVersion())) {
if (false == HttpHeaderValues.KEEP_ALIVE.toString().equalsIgnoreCase(connectionHeader)) {
return false;
}
}
// HTTP/1.1默认打开Keep-Alive
return true;
}
// --------------------------------------------------------- Protected method start
/**
* 填充参数(GET请求的参数)
*
* @param decoder QueryStringDecoder
*/
protected void putParams(QueryStringDecoder decoder) {
if (null != decoder) {
List<String> valueList;
for (Entry<String, List<String>> entry : decoder.parameters().entrySet()) {
valueList = entry.getValue();
if(null != valueList){
this.putParam(entry.getKey(), 1 == valueList.size() ? valueList.get(0) : valueList);
}
}
}
}
/**
* 填充参数(POST请求的参数)
*
* @param decoder QueryStringDecoder
*/
protected void putParams(HttpPostRequestDecoder decoder) {
if (null == decoder) {
return;
}
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
putParam(data);
}
}
/**
* 填充参数
*
* @param data InterfaceHttpData
*/
protected void putParam(InterfaceHttpData data) {
final HttpDataType dataType = data.getHttpDataType();
if (dataType == HttpDataType.Attribute) {
//普通参数
Attribute attribute = (Attribute) data;
try {
this.putParam(attribute.getName(), attribute.getValue());
} catch (IOException e) {
Logger.error(e.toString());
}
}else if(dataType == HttpDataType.FileUpload){
//文件
FileUpload fileUpload = (FileUpload) data;
if(fileUpload.isCompleted()){
try {
this.putParam(data.getName(), fileUpload.getFile());
} catch (IOException e) {
Logger.error(e.toString(), "Get file param [{}] error!", data.getName());
}
}
}
}
/**
* 填充参数
*
* @param key 参数名
* @param value 参数值
*/
protected void putParam(String key, Object value) {
this.params.put(key, value);
}
/**
* 填充头部信息和Cookie信息
*
* @param headers HttpHeaders
*/
protected void putHeadersAndCookies(HttpHeaders headers) {
for (Entry<String, String> entry : headers) {
this.headers.put(entry.getKey(), entry.getValue());
}
// Cookie
final String cookieString = this.headers.get(HttpHeaderNames.COOKIE);
if (StrUtil.isNotBlank(cookieString)) {
final Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieString);
for (Cookie cookie : cookies) {
this.cookies.put(cookie.name(), cookie);
}
}
}
/**
* 设置客户端IP
*
* @param ctx ChannelHandlerContext
*/
protected void putIp(ChannelHandlerContext ctx) {
String ip = getHeader("X-Forwarded-For");
if (StrUtil.isNotBlank(ip)) {
ip = HttpUtil.getMultistageReverseProxyIp(ip);
} else {
final InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
ip = insocket.getAddress().getHostAddress();
}
this.ip = ip;
}
// --------------------------------------------------------- Protected method end
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("\r\nprotocolVersion: ").append(getProtocolVersion()).append("\r\n");
sb.append("uri: ").append(getUri()).append("\r\n");
sb.append("path: ").append(path).append("\r\n");
sb.append("method: ").append(getMethod()).append("\r\n");
sb.append("ip: ").append(ip).append("\r\n");
sb.append("headers:\r\n ");
for (Entry<String, String> entry : headers.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
sb.append("params: \r\n");
for (Entry<String, Object> entry : params.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
return sb.toString();
}
/**
* 构建Request对象
*
* @param ctx ChannelHandlerContext
* @param nettyRequest Netty的HttpRequest
* @return Request
*/
protected final static Request build(ChannelHandlerContext ctx, FullHttpRequest nettyRequest) {
return new Request(ctx, nettyRequest);
}
}
@@ -0,0 +1,592 @@
package com.xiaoleilu.loServer.handler;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import com.xiaoleilu.hutool.http.HttpUtil;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.CharsetUtil;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.listener.FileProgressiveFutureListener;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import org.slf4j.LoggerFactory;
/**
* 响应对象
*
* @author Looly
*
*/
public class Response {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(Response.class);
/** 返回内容类型:普通文本 */
public final static String CONTENT_TYPE_TEXT = "text/plain";
/** 返回内容类型:HTML */
public final static String CONTENT_TYPE_HTML = "text/html";
/** 返回内容类型:XML */
public final static String CONTENT_TYPE_XML = "text/xml";
/** 返回内容类型:JAVASCRIPT */
public final static String CONTENT_TYPE_JAVASCRIPT = "application/javascript";
/** 返回内容类型:JSON */
public final static String CONTENT_TYPE_JSON = "application/json";
public final static String CONTENT_TYPE_JSON_IE = "text/json";
private ChannelHandlerContext ctx;
private Request request;
private HttpVersion httpVersion = HttpVersion.HTTP_1_1;
private HttpResponseStatus status = HttpResponseStatus.OK;
private String contentType = CONTENT_TYPE_HTML;
private String charset = ServerSetting.getCharset();
private HttpHeaders headers = new DefaultHttpHeaders();
private Set<Cookie> cookies = new HashSet<Cookie>();
private Object content = Unpooled.EMPTY_BUFFER;
//发送完成标记
private boolean isSent;
public Response(ChannelHandlerContext ctx, Request request) {
this.ctx = ctx;
this.request = request;
}
/**
* 设置响应的Http版本号
*
* @param httpVersion http版本号对象
* @return 自己
*/
public Response setHttpVersion(HttpVersion httpVersion) {
this.httpVersion = httpVersion;
return this;
}
/**
* 响应状态码<br>
* 使用io.netty.handler.codec.http.HttpResponseStatus对象
*
* @param status 状态码
* @return 自己
*/
public Response setStatus(HttpResponseStatus status) {
this.status = status;
return this;
}
/**
* 响应状态码
*
* @param status 状态码
* @return 自己
*/
public Response setStatus(int status) {
return setStatus(HttpResponseStatus.valueOf(status));
}
/**
* 设置Content-Type
*
* @param contentType Content-Type
* @return 自己
*/
public Response setContentType(String contentType) {
this.contentType = contentType;
return this;
}
/**
* 设置返回内容的字符集编码
*
* @param charset 编码
* @return 自己
*/
public Response setCharset(String charset) {
this.charset = charset;
return this;
}
/**
* 增加响应的Header<br>
* 重复的Header将被叠加
*
* @param name 名
* @param value 值,可以是String,Date, int
* @return 自己
*/
public Response addHeader(String name, Object value) {
headers.add(name, value);
return this;
}
/**
* 设置响应的Header<br>
* 重复的Header将被替换
*
* @param name 名
* @param value 值,可以是String,Date, int
* @return 自己
*/
public Response setHeader(String name, Object value) {
headers.set(name, value);
return this;
}
/**
* 设置响应体长度
*
* @param contentLength 响应体长度
* @return 自己
*/
public Response setContentLength(long contentLength) {
setHeader(HttpHeaderNames.CONTENT_LENGTH.toString(), contentLength);
return this;
}
/**
* 设置是否长连接
*
* @return 自己
*/
public Response setKeepAlive() {
setHeader(HttpHeaderNames.CONNECTION.toString(), HttpHeaderValues.KEEP_ALIVE.toString());
return this;
}
// --------------------------------------------------------- Cookie start
/**
* 设定返回给客户端的Cookie
*
* @param cookie
* @return 自己
*/
public Response addCookie(Cookie cookie) {
cookies.add(cookie);
return this;
}
/**
* 设定返回给客户端的Cookie
*
* @param name Cookie名
* @param value Cookie值
* @return 自己
*/
public Response addCookie(String name, String value) {
return addCookie(new DefaultCookie(name, value));
}
/**
* 设定返回给客户端的Cookie
*
* @param name cookie名
* @param value cookie值
* @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. n>0 : Cookie存在的秒数.
* @param path Cookie的有效路径
* @param domain the Cookie可见的域,依据 RFC 2109 标准
* @return 自己
*/
public Response addCookie(String name, String value, int maxAgeInSeconds, String path, String domain) {
Cookie cookie = new DefaultCookie(name, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge(maxAgeInSeconds);
cookie.setPath(path);
return addCookie(cookie);
}
/**
* 设定返回给客户端的Cookie<br>
* Path: "/"<br>
* No Domain
*
* @param name cookie名
* @param value cookie值
* @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. n>0 : Cookie存在的秒数.
* @return 自己
*/
public Response addCookie(String name, String value, int maxAgeInSeconds) {
return addCookie(name, value, maxAgeInSeconds, "/", null);
}
// --------------------------------------------------------- Cookie end
/**
* 设置响应HTML文本内容
*
* @param contentText 响应的文本
* @return 自己
*/
public Response setContent(String contentText) {
this.content = Unpooled.copiedBuffer(contentText, Charset.forName(charset));
return this;
}
/**
* 设置响应文本内容
*
* @param contentText 响应的文本
* @return 自己
*/
public Response setTextContent(String contentText) {
setContentType(CONTENT_TYPE_TEXT);
return setContent(contentText);
}
/**
* 设置响应JSON文本内容
*
* @param contentText 响应的JSON文本
* @return 自己
*/
public Response setJsonContent(String contentText) {
setContentType(request.isIE() ? CONTENT_TYPE_JSON : CONTENT_TYPE_JSON);
return setContent(contentText);
}
/**
* 设置响应XML文本内容
*
* @param contentText 响应的XML文本
* @return 自己
*/
public Response setXmlContent(String contentText) {
setContentType(CONTENT_TYPE_XML);
return setContent(contentText);
}
/**
* 设置响应文本内容
*
* @param contentBytes 响应的字节
* @return 自己
*/
public Response setContent(byte[] contentBytes) {
return setContent(Unpooled.copiedBuffer(contentBytes));
}
/**
* 设置响应文本内容
*
* @param byteBuf 响应的字节
* @return 自己
*/
public Response setContent(ByteBuf byteBuf) {
this.content = byteBuf;
return this;
}
/**
* 设置响应到客户端的文件
*
* @param file 文件
* @return 自己
*/
public Response setContent(File file) {
this.content = file;
return this;
}
/**
* Sets the Date and Cache headers for the HTTP Response
*
* @param response HTTP response
* @param fileToCache file to extract content type
*/
/**
* 设置日期和过期时间
*
* @param lastModify 上一次修改时间
* @param httpCacheSeconds 缓存时间,单位秒
*/
public void setDateAndCache(long lastModify, int httpCacheSeconds) {
SimpleDateFormat formatter = new SimpleDateFormat(DateUtil.HTTP_DATETIME_PATTERN, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
// Date header
Calendar time = new GregorianCalendar();
setHeader(HttpHeaderNames.DATE.toString(), formatter.format(time.getTime()));
// Add cache headers
time.add(Calendar.SECOND, httpCacheSeconds);
setHeader(HttpHeaderNames.EXPIRES.toString(), formatter.format(time.getTime()));
setHeader(HttpHeaderNames.CACHE_CONTROL.toString(), "private, max-age=" + httpCacheSeconds);
setHeader(HttpHeaderNames.LAST_MODIFIED.toString(), formatter.format(DateUtil.date(lastModify)));
}
// -------------------------------------------------------------------------------------- build HttpResponse start
/**
* 转换为Netty所用Response<br>
* 不包括content,一般用于返回文件类型的响应
*
* @return DefaultHttpResponse
*/
private DefaultHttpResponse toDefaultHttpResponse() {
final DefaultHttpResponse defaultHttpResponse = new DefaultHttpResponse(httpVersion, status);
// headers
final HttpHeaders httpHeaders = defaultHttpResponse.headers().add(headers);
// Cookies
for (Cookie cookie : cookies) {
httpHeaders.add(HttpHeaderNames.SET_COOKIE.toString(), ServerCookieEncoder.LAX.encode(cookie));
}
return defaultHttpResponse;
}
/**
* 转换为Netty所用Response<br>
* 用于返回一般类型响应(文本)
*
* @return FullHttpResponse
*/
private FullHttpResponse toFullHttpResponse() {
final ByteBuf byteBuf = (ByteBuf)content;
final FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(httpVersion, status, byteBuf);
// headers
final HttpHeaders httpHeaders = fullHttpResponse.headers().add(headers);
if ("application/octet-stream".equals(contentType)) {
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE.toString(), contentType);
} else {
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE.toString(), StrUtil.format("{};charset={}", contentType, charset));
httpHeaders.set(HttpHeaderNames.CONTENT_ENCODING.toString(), charset);
}
httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH.toString(), byteBuf.readableBytes());
// Cookies
for (Cookie cookie : cookies) {
httpHeaders.add(HttpHeaderNames.SET_COOKIE.toString(), ServerCookieEncoder.LAX.encode(cookie));
}
return fullHttpResponse;
}
// -------------------------------------------------------------------------------------- build HttpResponse end
// -------------------------------------------------------------------------------------- send start
/**
* 发送响应到客户端<br>
*
* @return ChannelFuture
* @throws IOException
*/
public ChannelFuture send() {
ChannelFuture channelFuture;
if(content instanceof File){
//文件
File file = (File)content;
try {
channelFuture = sendFile(file);
} catch (IOException e) {
Logger.error(StrUtil.format("Send {} error!", file), e.toString());
channelFuture = sendError(HttpResponseStatus.FORBIDDEN, "");
}
}else{
//普通文本
channelFuture = sendFull();
}
this.isSent = true;
return channelFuture;
}
/**
* @return 是否已经出发发送请求,内部使用<br>
*/
protected boolean isSent(){
return this.isSent;
}
/**
* 发送响应到客户端
*
* @return ChannelFuture
*/
private ChannelFuture sendFull() {
if (request != null && request.isKeepAlive()) {
setKeepAlive();
return ctx.writeAndFlush(this.toFullHttpResponse());
} else {
return sendAndCloseFull();
}
}
/**
* 发送给到客户端并关闭ChannelHandlerContext
*
* @return ChannelFuture
*/
private ChannelFuture sendAndCloseFull() {
return ctx.writeAndFlush(this.toFullHttpResponse()).addListener(ChannelFutureListener.CLOSE);
}
/**
* 发送文件
*
* @param file 文件
* @return ChannelFuture
* @throws IOException
*/
private ChannelFuture sendFile(File file) throws IOException {
final RandomAccessFile raf = new RandomAccessFile(file, "r");
// 内容长度
long fileLength = raf.length();
this.setContentLength(fileLength);
//文件类型
String contentType = HttpUtil.getMimeType(file.getName());
if(StrUtil.isBlank(contentType)){
//无法识别默认使用数据流
contentType = "application/octet-stream";
}
this.setContentType(contentType);
ctx.write(this.toDefaultHttpResponse());
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise())
.addListener(FileProgressiveFutureListener.build(raf));
return sendEmptyLast();
}
/**
* 发送结尾标记,表示发送结束
* @return ChannelFuture
*/
private ChannelFuture sendEmptyLast(){
final ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
if (false == request.isKeepAlive()) {
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}
return lastContentFuture;
}
// -------------------------------------------------------------------------------------- send end
// ---------------------------------------------------------------------------- special response start
/**
* 302 重定向
*
* @param uri 重定向到的URI
* @return ChannelFuture
*/
public ChannelFuture sendRedirect(String uri) {
return this.setStatus(HttpResponseStatus.FOUND).setHeader(HttpHeaderNames.LOCATION.toString(), uri).send();
}
/**
* 304 文件未修改
*
* @return ChannelFuture
*/
public ChannelFuture sendNotModified() {
return this.setStatus(HttpResponseStatus.NOT_MODIFIED).setHeader(HttpHeaderNames.DATE.toString(), DateUtil.formatHttpDate(DateUtil.date())).send();
}
/**
* 发送错误消息
*
* @param status 错误状态码
* @param msg 消息内容
* @return ChannelFuture
*/
public ChannelFuture sendError(HttpResponseStatus status, String msg) {
if (ctx.channel().isActive()) {
return this.setStatus(status).setContent(msg).send();
}
return null;
}
/**
* 发送404 Not Found
*
* @param msg 消息内容
* @return ChannelFuture
*/
public ChannelFuture sendNotFound(String msg) {
return sendError(HttpResponseStatus.NOT_FOUND, msg);
}
/**
* 发送500 Internal Server Error
*
* @param msg 消息内容
* @return ChannelFuture
*/
public ChannelFuture sendServerError(String msg) {
return sendError(HttpResponseStatus.INTERNAL_SERVER_ERROR, msg);
}
// ---------------------------------------------------------------------------- special response end
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("headers:\r\n ");
for ( Entry<String, String> entry : headers.entries()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
sb.append("content: ").append(StrUtil.str(content, CharsetUtil.UTF_8));
return sb.toString();
}
// ---------------------------------------------------------------------------- static method start
/**
* 构建Response对象
*
* @param ctx ChannelHandlerContext
* @param request 请求对象
* @return Response对象
*/
protected static Response build(ChannelHandlerContext ctx, Request request) {
return new Response(ctx, request);
}
/**
* 构建Response对象,Request对象为空,将无法获得某些信息<br>
* 1. 无法使用长连接
*
* @param ctx ChannelHandlerContext
* @return Response对象
*/
protected static Response build(ChannelHandlerContext ctx) {
return new Response(ctx, null);
}
// ---------------------------------------------------------------------------- static method end
}