wildfire chat server source initial submit
This commit is contained in:
@@ -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
|
||||
}
|
||||
+45
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -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 + ")");
|
||||
}
|
||||
}
|
||||
+420
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+179
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user