All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.voovan.http.server.WebServerHandler Maven / Gradle / Ivy

package org.voovan.http.server;

import org.voovan.Global;
import org.voovan.http.HttpRequestType;
import org.voovan.http.message.HttpParser;
import org.voovan.http.message.HttpStatic;
import org.voovan.http.message.Request;
import org.voovan.http.server.context.WebContext;
import org.voovan.http.server.context.WebServerConfig;
import org.voovan.http.server.exception.RouterNotFound;
import org.voovan.http.websocket.WebSocketFrame;
import org.voovan.http.websocket.WebSocketTools;
import org.voovan.network.IoHandler;
import org.voovan.network.IoSession;
import org.voovan.network.exception.SendMessageException;
import org.voovan.tools.FastThreadLocal;
import org.voovan.tools.TObject;
import org.voovan.tools.buffer.ByteBufferChannel;
import org.voovan.tools.exception.MemoryReleasedException;
import org.voovan.tools.hashwheeltimer.HashWheelTask;
import org.voovan.tools.log.Logger;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeoutException;

/**
 * WebServer Socket 事件处理类
 *
 * @author helyho
 *
 * Voovan Framework.
 * WebSite: https://github.com/helyho/Voovan
 * Licence: Apache v2 License
 */
public class WebServerHandler implements IoHandler {
	private static FastThreadLocal THREAD_HTTP_REQUEST = FastThreadLocal.withInitial(()->new HttpRequest());
	private static FastThreadLocal THREAD_HTTP_RESPONSE = FastThreadLocal.withInitial(()->new HttpResponse());


	private HttpDispatcher		httpDispatcher;
	private WebSocketDispatcher	webSocketDispatcher;
	private WebServerConfig webConfig;
	private List keepAliveSessionList;


	public WebServerHandler(WebServerConfig webConfig, HttpDispatcher httpDispatcher, WebSocketDispatcher webSocketDispatcher) {
		this.httpDispatcher = httpDispatcher;
		this.webSocketDispatcher = webSocketDispatcher;
		this.webConfig = webConfig;
		keepAliveSessionList = new Vector();

		initKeepAliveTimer();
	}

	public static HttpSessionState getAttachment(IoSession session){
		Object[] attachment = (Object[]) session.getAttachment();

		HttpSessionState httpSessionState = (HttpSessionState)attachment[0];
		if(httpSessionState == null) {
			httpSessionState = new HttpSessionState();
			attachment[0] = httpSessionState;
		}

		return  httpSessionState;
	}

	/**
	 * 初始化连接保持 Timer
	 */
	public void initKeepAliveTimer(){
		Global.getHashWheelTimer().addTask(new HashWheelTask() {

			@Override
			public void run() {

				long currentTimeValue = System.currentTimeMillis();
				//遍历所有的 session
				for(int i=0; i webConfig.getGzipMinSize()){
				//检查 MimeType 是否启用 gzip
				for(String gzipMimeType : webConfig.getGzipMimeType()){
					if(httpResponse.header().get(HttpStatic.CONTENT_TYPE_STRING).contains(gzipMimeType)){
						httpResponse.setCompress(true);
					}
				}
			}
		}

		return httpResponse;
	}

	/**
	 * Http协议升级处
	 *
	 * @param session    HTTP-Session 对象
	 * @param httpRequest  HTTP 请求对象
	 * @param httpResponse HTTP 响应对象
	 * @return HTTP 响应对象
	 */
	private static String upgradeStatusCode = "Switching Protocols";
	public HttpResponse disposeUpgrade(IoSession session, HttpRequest httpRequest, HttpResponse httpResponse) {
		HttpSessionState httpSessionState = getAttachment(session);

		//如果不是匹配的路由则关闭连接
		if(webSocketDispatcher.findRouter(httpRequest)!=null){
			httpSessionState.setType(HttpRequestType.UPGRADE);

			//初始化响应消息
			httpResponse.protocol().setStatus(101);
			httpResponse.protocol().setStatusCode(upgradeStatusCode);
			httpResponse.header().put(HttpStatic.CONNECTION_STRING, HttpStatic.UPGRADE_STRING);

			//WebSocket 升级响应
			if(httpRequest.header()!=null && HttpStatic.WEB_SOCKET_STRING.equals(httpRequest.header().get(HttpStatic.UPGRADE_STRING))){
				httpResponse.header().put(HttpStatic.UPGRADE_STRING, HttpStatic.WEB_SOCKET_STRING);
				String webSocketKey = WebSocketTools.generateSecKey(httpRequest.header().get(HttpStatic.SEC_WEB_SOCKET_KEY_STRING));
				httpResponse.header().put(HttpStatic.SEC_WEB_SOCKET_ACCEPT_STRING, webSocketKey);
			}

			//http2 升级响应
			else if(httpRequest.header()!=null && "h2c".equals(httpRequest.header().get(HttpStatic.UPGRADE_STRING))){
				httpResponse.header().put(HttpStatic.UPGRADE_STRING, "h2c");
				//这里写 HTTP2的实现,暂时留空
			}
		} else {
			httpDispatcher.exceptionMessage(httpRequest, httpResponse, new RouterNotFound("Not avaliable router!"));
		}

		resetThreadLocal();
		return httpResponse;
	}

	/**
	 * WebSocket 帧处理
	 *
	 * @param session 	HTTP-Session 对象
	 * @param webSocketFrame WebSocket 帧对象
	 * @return WebSocket 帧对象
	 */
	public WebSocketFrame disposeWebSocket(IoSession session, WebSocketFrame webSocketFrame) {
		HttpSessionState httpSessionState = getAttachment(session);

		ByteBufferChannel byteBufferChannel = null;
		if(!session.containAttribute("WebSocketByteBufferChannel")){
			byteBufferChannel = new ByteBufferChannel(session.socketContext().getReadBufferSize());
			session.setAttribute("WebSocketByteBufferChannel",byteBufferChannel);
		}else{
			byteBufferChannel = (ByteBufferChannel)session.getAttribute("WebSocketByteBufferChannel");
		}

		HttpRequest reqWebSocket = httpSessionState.getHttpRequest();

		// WS_CLOSE 如果收到关闭帧则关闭连接
		if(webSocketFrame.getOpcode() == WebSocketFrame.Opcode.CLOSING) {
			return WebSocketFrame.newInstance(true, WebSocketFrame.Opcode.CLOSING, false, webSocketFrame.getFrameData());
		}
		// WS_PING 收到 ping 帧则返回 pong 帧
		else if(webSocketFrame.getOpcode() == WebSocketFrame.Opcode.PING) {
			return webSocketDispatcher.firePingEvent(session, reqWebSocket, webSocketFrame.getFrameData());
		}
		// WS_PING 收到 pong 帧则返回 ping 帧
		else if(webSocketFrame.getOpcode() == WebSocketFrame.Opcode.PONG) {
			refreshTimeout(session);
			webSocketDispatcher.firePoneEvent(session, reqWebSocket, webSocketFrame.getFrameData());
			return null;
		}else if(webSocketFrame.getOpcode() == WebSocketFrame.Opcode.CONTINUOUS){
			byteBufferChannel.writeEnd(webSocketFrame.getFrameData());
		}
		// WS_RECIVE 文本和二进制消息出发 Recived 事件
		else if (webSocketFrame.getOpcode() == WebSocketFrame.Opcode.TEXT || webSocketFrame.getOpcode() == WebSocketFrame.Opcode.BINARY) {

			byteBufferChannel.writeEnd(webSocketFrame.getFrameData());
			WebSocketFrame respWebSocketFrame = null;

			//判断解包是否有错
			if(webSocketFrame.getErrorCode()==0){
				ByteBuffer byteBuffer = byteBufferChannel.getByteBuffer();
				try {
					respWebSocketFrame = webSocketDispatcher.fireReceivedEvent(session, reqWebSocket, byteBuffer);
				} finally {
					byteBufferChannel.compact();
					byteBufferChannel.clear();
				}

			}else{
				//解析时出现异常,返回关闭消息
				respWebSocketFrame = WebSocketFrame.newInstance(true, WebSocketFrame.Opcode.CLOSING, false, ByteBuffer.wrap(WebSocketTools.intToByteArray(webSocketFrame.getErrorCode(), 2)));
			}
			return respWebSocketFrame;
		}

		return null;
	}

	private void refreshTimeout(IoSession session){
		HttpSessionState httpSessionState = getAttachment(session);

		int keepAliveTimeout = webConfig.getKeepAliveTimeout();
		long timeoutValue = System.currentTimeMillis()+keepAliveTimeout*1000;
		httpSessionState.setKeepAliveTimeout(timeoutValue);
	}

	@Override
	public void onSent(IoSession session, Object obj) {
		HttpSessionState httpSessionState = getAttachment(session);

		HttpRequest request = httpSessionState.getHttpRequest();

		//WebSocket 协议处理
		if(obj instanceof WebSocketFrame){
			WebSocketFrame webSocketFrame = (WebSocketFrame)obj;

			if(webSocketFrame.getOpcode() == WebSocketFrame.Opcode.CLOSING){
				session.close();
			} else if (webSocketFrame.getOpcode() != WebSocketFrame.Opcode.PING &&
					webSocketFrame.getOpcode() != WebSocketFrame.Opcode.PONG) {
				webSocketDispatcher.fireSentEvent(session, request, webSocketFrame.getFrameData());
			}
		}

		//针对 WebSocket 的处理协议升级
		if(httpSessionState.isUpgrade()) {
			httpSessionState.setType(HttpRequestType.WEBSOCKET);
			httpSessionState.setKeepAlive(true);

			//触发 onOpen 事件
			WebSocketFrame webSocketFrame = webSocketDispatcher.fireOpenEvent(session, request);

			if(webSocketFrame!=null) {

				try {
					session.syncSend(webSocketFrame);
				} catch (SendMessageException e) {
					session.close();
					Logger.error("WebSocket Open event writeToChannel frame error", e);
				}
			}

			//发送第一次心跳消息
			WebSocketDispatcher.getHeartBeatWheelTimer().addTask(new HashWheelTask() {
				@Override
				public void run() {
					//发送 ping 消息
					try {
						WebSocketFrame ping = WebSocketFrame.newInstance(true, WebSocketFrame.Opcode.PING, false, null);
						session.send(ping.toByteBuffer());
						session.flush();
					} catch (Exception e) {
						session.close();
						Logger.error("WebSocket writeToChannel Ping frame error", e);
					} finally {
						this.cancel();
					}
				}
			}, session.socketContext().getReadTimeout()/3/1000);
		}
	}

	@Override
	public void onFlush(IoSession session) {
		HttpSessionState httpSessionState = getAttachment(session);

		HttpRequest request = httpSessionState.getHttpRequest();
		//处理连接保持
		if (httpSessionState.isKeepAlive() && webConfig.getKeepAliveTimeout() > 0) {

			if (httpSessionState.isKeepLiveListContain()) {
				keepAliveSessionList.add(session);
				httpSessionState.setKeepLiveListContain(true);
			}
			//更新会话超时时间
			refreshTimeout(session);

		} else {
			keepAliveSessionList.remove(session);
			session.close();
		}
	}

	@Override
	public void onException(IoSession session, Exception e) {
		if(!(e instanceof MemoryReleasedException) && !(e instanceof TimeoutException)) {
			Logger.error("Http Server Error", e);
		}

		session.close();
	}

	@Override
	public void onIdle(IoSession session) {

	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy