package org.voovan.http.server;
import org.voovan.http.message.Request;
import org.voovan.http.message.Response;
import org.voovan.http.server.WebSocketDispatcher.WebSocketEvent;
import org.voovan.http.server.context.WebContext;
import org.voovan.http.server.context.WebServerConfig;
import org.voovan.http.websocket.WebSocketFrame;
import org.voovan.http.websocket.WebSocketFrame.Opcode;
import org.voovan.http.websocket.WebSocketTools;
import org.voovan.network.IoHandler;
import org.voovan.network.IoSession;
import org.voovan.tools.TObject;
import org.voovan.tools.log.Logger;
import java.nio.ByteBuffer;
import java.nio.channels.InterruptedByTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* WebServer Socket 事件处理类
*
* @author helyho
*
* Voovan Framework.
* WebSite: https://github.com/helyho/Voovan
* Licence: Apache v2 License
*/
public class WebServerHandler implements IoHandler {
private HttpDispatcher httpDispatcher;
private WebSocketDispatcher webSocketDispatcher;
private WebServerConfig webConfig;
private Timer keepAliveTimer;
private List<IoSession> keepAliveSessionList;
public WebServerHandler(WebServerConfig webConfig, HttpDispatcher httpDispatcher, WebSocketDispatcher webSocketDispatcher) {
this.httpDispatcher = httpDispatcher;
this.webSocketDispatcher = webSocketDispatcher;
this.webConfig = webConfig;
keepAliveTimer = new Timer("VOOVAN@Keep_Alive_Timer");
keepAliveSessionList = new ArrayList<IoSession>();
initKeepAliveTimer();
}
/**
* 活的基于当前时间的超时毫秒值
* @return
*/
private long getTimeoutValue(){
int keepAliveTimeout = webConfig.getKeepAliveTimeout();
return System.currentTimeMillis()+keepAliveTimeout*1000;
}
/**
* 初始化连接保持 Timer
*/
public void initKeepAliveTimer(){
TimerTask keepAliveTask = new TimerTask() {
@Override
public void run() {
//临时保存需要清理的 session
List<IoSession> sessionNeedRemove= new ArrayList<IoSession>();
//遍历所有的 session
for(IoSession session : keepAliveSessionList){
long currentTimeValue = System.currentTimeMillis();
long timeOutValue = (long) session.getAttribute("TimeOutValue");
if(timeOutValue < currentTimeValue){
//如果超时则结束当前连接
//触发 WebSocket close 事件
webSocketDispatcher.fireCloseEvent(session);
session.close();
sessionNeedRemove.add(session);
}
}
//清理已经超时的会话
for(IoSession session : sessionNeedRemove){
keepAliveSessionList.remove(session);
}
}
};
keepAliveTimer.schedule(keepAliveTask, 1 , 1000);
}
@Override
public Object onConnect(IoSession session) {
return null;
}
@Override
public void onDisconnect(IoSession session) {
//清理 IoSession
keepAliveSessionList.remove(session);
//清理 HttpSession
HttpRequest httpRequest = TObject.cast(session.getAttribute("HttpRequest"));
if(httpRequest!=null) {
HttpSession httpSession = httpRequest.getSession();
if(httpSession!=null) {
httpSession.removeFromSessionManager();
}
}
}
@Override
public Object onReceive(IoSession session, Object obj) {
// 获取默认字符集
String defaultCharacterSet = webConfig.getCharacterSet();
// Http 请求
if (obj instanceof Request) {
// 构造请求对象
Request request = TObject.cast(obj);
// 构造响应对象
Response response = new Response();
response.setCompress(webConfig.isGzip());
// 构造 Http 请求/响应 对象
HttpRequest httpRequest = new HttpRequest(request, defaultCharacterSet);
HttpResponse httpResponse = new HttpResponse(response, defaultCharacterSet);
session.setAttribute("HttpRequest",httpRequest);
session.setAttribute("HttpResponse",httpResponse);
// 填充远程连接的IP 地址和端口
httpRequest.setRemoteAddres(session.remoteAddress());
httpRequest.setRemotePort(session.remotePort());
// WebSocket协议升级处理
if (WebSocketTools.isWebSocketUpgrade(request)) {
return disposeUpgrade(session, httpRequest, httpResponse);
}
// Http 1.1处理
else {
return disposeHttp(session, httpRequest, httpResponse);
}
}
//处理 WEBSocket 报文
else if (obj instanceof WebSocketFrame) {
return disposeWebSocket(session, (WebSocketFrame)obj);
}
// 如果协议判断失败关闭连接
session.close();
return null;
}
/**
* Http 请求响应处理
*
* @param session HTTP-Session 对象
* @param httpRequest HTTP 请求对象
* @param httpResponse HTTP 响应对象
* @return HTTP 响应对象
*/
public HttpResponse disposeHttp(IoSession session, HttpRequest httpRequest, HttpResponse httpResponse) {
session.setAttribute("Type", "HTTP");
// 处理响应请求
httpDispatcher.process(httpRequest, httpResponse);
//如果是长连接则填充响应报文
if (httpRequest.header().contain("Connection")
&& httpRequest.header().get("Connection").toLowerCase().contains("keep-alive")) {
session.setAttribute("IsKeepAlive", true);
httpResponse.header().put("Connection", httpRequest.header().get("Connection"));
}
httpResponse.header().put("Server", WebContext.getVERSION());
return httpResponse;
}
/**
* Http协议升级处理
*
* @param session HTTP-Session 对象
* @param httpRequest HTTP 请求对象
* @param httpResponse HTTP 响应对象
* @return HTTP 响应对象
*/
public HttpResponse disposeUpgrade(IoSession session, HttpRequest httpRequest, HttpResponse httpResponse) {
//保存必要参数
session.setAttribute("Type", "Upgrade");
session.setAttribute("IsKeepAlive", true);
// session.setAttribute("UpgradeRequest", httpRequest);
//初始化响应消息
httpResponse.protocol().setStatus(101);
httpResponse.protocol().setStatusCode("Switching Protocols");
httpResponse.header().put("Connection", "Upgrade");
if(httpRequest.header()!=null && "websocket".equalsIgnoreCase(httpRequest.header().get("Upgrade"))){
session.setAttribute("Type", "WebSocket");
httpResponse.header().put("Upgrade", "websocket");
String webSocketKey = WebSocketTools.generateSecKey(httpRequest.header().get("Sec-WebSocket-Key"));
httpResponse.header().put("Sec-WebSocket-Accept", webSocketKey);
// WS_CONNECT WebSocket Open事件
webSocketDispatcher.process(WebSocketEvent.OPEN, session, httpRequest, null);
}
else if(httpRequest.header()!=null && "h2c".equalsIgnoreCase(httpRequest.header().get("Upgrade"))){
session.setAttribute("Type", "H2C");
httpResponse.header().put("Upgrade", "h2c");
//这里写 HTTP2的实现,暂时留空
}
return httpResponse;
}
/**
* WebSocket 帧处理
*
* @param session HTTP-Session 对象
* @param webSocketFrame WebSocket 帧对象
* @return WebSocket 帧对象
*/
public WebSocketFrame disposeWebSocket(IoSession session, WebSocketFrame webSocketFrame) {
session.setAttribute("Type" , "WebSocket");
session.setAttribute("IsKeepAlive" , true);
session.setAttribute("WebSocketClose", false);
HttpRequest reqWebSocket = TObject.cast(session.getAttribute("HttpRequest"));
// WS_CLOSE 如果收到关闭帧则关闭连接
if (webSocketFrame.getOpcode() == Opcode.CLOSING) {
// WebSocket Close事件
webSocketDispatcher.process(WebSocketEvent.CLOSE, session, reqWebSocket, null);
session.setAttribute("WebSocketClose", true);
return WebSocketFrame.newInstance(true, Opcode.CLOSING, false, webSocketFrame.getFrameData());
}
// WS_PING 收到 ping 帧则返回 pong 帧
else if (webSocketFrame.getOpcode() == Opcode.PING) {
return WebSocketFrame.newInstance(true, Opcode.PONG, false, null);
}
// WS_PING 收到 pong 帧则返回 ping 帧
else if (webSocketFrame.getOpcode() == WebSocketFrame.Opcode.PONG) {
return WebSocketFrame.newInstance(true, WebSocketFrame.Opcode.PING, false, null);
}
// WS_RECIVE 文本和二进制消息出发 Recived 事件
else if (webSocketFrame.getOpcode() == Opcode.TEXT || webSocketFrame.getOpcode() == Opcode.BINARY) {
WebSocketFrame respWebSocketFrame = null;
//判断解包是否有错
if(webSocketFrame.getErrorCode()==0){
respWebSocketFrame = webSocketDispatcher.process(WebSocketEvent.RECIVED, session, reqWebSocket, webSocketFrame);
}else{
//解析时出现异常,返回关闭消息
respWebSocketFrame = WebSocketFrame.newInstance(true, Opcode.CLOSING, false, ByteBuffer.wrap(WebSocketTools.intToByteArray(webSocketFrame.getErrorCode(), 2)));
}
return respWebSocketFrame;
}
return null;
}
@Override
public void onSent(IoSession session, Object obj) {
if(WebSocketTools.isWebSocketFrame((ByteBuffer) obj) != -1){
HttpRequest reqWebSocket = TObject.cast(session.getAttribute("HttpRequest"));
WebSocketFrame webSocketFrame = WebSocketFrame.parse((ByteBuffer)obj);
webSocketDispatcher.process(WebSocketEvent.SENT, session, reqWebSocket, webSocketFrame);
}
//如果 WebSocket 关闭,则关闭对应的 Socket
if(session.containAttribute("WebSocketClose") && (boolean) session.getAttribute("WebSocketClose")){
session.close();
}
else if (session.containAttribute("IsKeepAlive")
&& (boolean) session.getAttribute("IsKeepAlive")
&& webConfig.getKeepAliveTimeout() > 0) {
if(!keepAliveSessionList.contains(session)){
keepAliveSessionList.add(session);
}
//更新会话超时时间
session.setAttribute("TimeOutValue", getTimeoutValue());
}else {
if(keepAliveSessionList.contains(session)){
keepAliveSessionList.remove(session);
}
session.close();
}
}
@Override
public void onException(IoSession session, Exception e) {
//忽略远程连接断开异常 和 超时断开异常
if(!(e instanceof InterruptedByTimeoutException)){
Logger.error("Http Server Error: \r\n" + e.getMessage(),e);
}
session.close();
}
}