基于AIO的超轻量HTTP服务器实现

阅读: 评论:0

基于AIO的超轻量HTTP服务器实现

基于AIO的超轻量HTTP服务器实现

 

高端大气上档次的网页PPT:

 

1.很多人写了多年代码,从未接触过Socket

2.很多人做了多年Web,从未了解过HTTP协议

3.很多人看过HTML5规范,从未使用过WebSocket

 

 

Java Aio , JDK1.7之后新出的一个重量级API,可以看作是JDK1.4 NIO 之后的一次升级,但相比NIO 的Reactor模型,AIO的Proactor模型优势在于当操作系统内核处于可读或则可写状态的时候,操作系统会主动通知应用程序,所以AIO使用起来比NIO就简单很多。

 

HTTP 协议,如果问起来,很多人一致的回答都是基于TCP的文本传输协议,但是真正了解它如何工作的人少之有少,什么是包头,什么是包体,分割符号是什么,程序如何才能够有条不紊地处理,什么是Keep-Alive,什么是长轮询,WebSocket的内部原理是什么。或许很多人看过相关书籍,但却止步于书籍,Tomcat、Netty、Jetty的源码又相当复杂,难于记忆。TCP协议、HTTP协议的一时半会儿说不清,所以就跟随我一步一步实现一个轻量的http-aio-server,相信大家很快就能够明白它们原理。

 

Server的目标1:实现常用的GET/POST请求方法,GET方法支持URL参数,POST方法支持application/x-www-form-urlencoded、multipart/form-data和流式上传,再就是全双工WebSocket协议封装。

Server的目标2:基于Spring的开发,并且实现类似Spring MVC的Controller开发。

 

源代码已上传,详细请见Maven项目源码,test目录的Bootstrap可以直接启动

 

 

 

 

 下面是精简后的源码,简单过目后便可大致了解AIO处理流程

 

/*** @author fangjialong* @description 服务器启动类,主要作用是创建线程池,绑定端口,开始接受Socket请求,该代码是精简后的代码,详细请下载源码*/
public class HttpServer {private ExecutorService channelWorkers;private ExecutorService processWorkers;private AsynchronousChannelGroup workerGroup = null;private AsynchronousServerSocketChannel serverSocket = null;private SocketAcceptHandler socketAcceptHandler;public synchronized void startup() throws IOException {//根据硬件环境创建线程池int availableProcessors = Runtime().availableProcessors();channelWorkers = wFixedThreadPool(availableProcessors+1,new ProcessorThreadFactory());workerGroup = AsynchronousChannelGroup.withCachedThreadPool(channelWorkers, 1);serverSocket = AsynchronousServerSocketChannel.open(workerGroup);//绑定服务器端口serverSocket.bind(new InetSocketAddress(80), 100);//开始接收请求,并且传入异步回调serverSocket.accept(null, socketAcceptHandler);}
}

 

下面是接受到请求后创建套接字会话,创建会话是为了方便编解码,和等待接受下次HTTP请求。

 

 

/*** @author cannonfang* @name 房佳龙* @date 2014-1-9* @qq 271398203* @todo 该类用于接受客户端TCP连接,如果有一个新的TCP连接,该类的completed函数将会被调用*/
@Component
public class SocketAcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object>{private static final Logger logger = Logger(SocketAcceptHandler.class);private HttpServer server;public void setServer(HttpServer server) {this.server = server;}@Overridepublic void completed(AsynchronousSocketChannel socket,Object obj) {try{SocketSession session = new SocketSession(socket,server);
//			logger.debug("Socket Session({}) Create",session.hashCode());ad();}catch(Throwable t){(t.getMessage(),t);try {socket.close();} catch (IOException e) {}}finally{server.accept();}}@Overridepublic void failed(Throwable t, Object obj) {server.accept();}}

 

 

HTTP 编码和解码对象,HTTP请求的解析总共分为三部,读取第一行,通过该行可以判断HTTP请求方法,版本和请求URI,每行以回车(ASCII:13)换行(ASCII:10)分割;然后读取HTTP Header , 每行以分好(:)和一个空白符作为分割组成Key-Value,已两个回车换行作为头部读取完毕;最后判断HTTP Header 中是否存在Content-Length头,如果存在,则通过Value中的数字作为长度继续向后读取作为包体,如果Content-Length: 1024, 那么就应该继续读取1024个字节作为包体。下面的代码部分来至Netty源码,相信阅读过Netty源码的同学会再这发现曾经的影子。

 

/*** @author cannonfang* @name 房佳龙* @date 2014-1-13* @qq 271398203* @todo HTTP Response Encode And Decode Class*/
@Component
public class HttpMessageSerializer implements InitializingBean{protected Logger logger = Logger(HttpMessageSerializer.class);private int maxInitialLineLength = 1024*2; //Default 2KBprivate int maxHeaderSize = 1024*4; //Default 4KBprivate int maxContextSize = 1024*1024*5 ;//Default 5MBprivate String charset = "UTF-8";private String dynamicSuffix;private String defaultIndex;private ServerConfig serverConfig;@Autowiredpublic void setServerConfig(ServerConfig serverConfig) {this.serverConfig = serverConfig;}@Overridepublic void afterPropertiesSet() throws Exception {this.charset = String("server.http.charset", charset);logger.info("server.http.charset : {}",charset);this.maxHeaderSize = BytesLength("server.http.maxHeaderSize", this.maxHeaderSize);logger.info("server.http.maxHeaderSize : {}",maxHeaderSize);this.maxContextSize = BytesLength("server.http.maxContextSize", this.maxContextSize);logger.info("server.http.maxContextSize : {}",maxContextSize);this.dynamicSuffix = String("server.http.dynamic.suffix", ".do");logger.info("server.http.dynamic.suffix : {}",dynamicSuffix);this.defaultIndex = String("server.http.index", ".html");logger.info("server.http.dynamic.suffix : {}",this.defaultIndex);}public boolean decode(ByteBuffer buffer,HttpProcessor processor)throws Exception{boolean finished = false;DefaultHttpRequest request = null;try{buffer.flip();HttpSocketStatus status = SocketStatus();request = Request();switch(status){case SKIP_CONTROL_CHARS: {skipControlCharacters(buffer);processor.setSocketStatus(HttpSocketStatus.READ_INITIAL);}case READ_INITIAL:{String line = readLine(buffer,maxInitialLineLength);if(line==null){break;}String[] initialLine = splitInitialLine(line);String text = initialLine[0].toUpperCase();HttpMethod method = HttpMethod(text);if(method==null){throw new HttpException(HttpResponseStatus.METHOD_NOT_ALLOWED, "Unsuported HTTP Method "+text);}String uri = initialLine[1];text = initialLine[2].toUpperCase();HttpVersion version;if (text.equals("HTTP/1.1")) {version=HttpVersion.HTTP_1_1;}else if (text.equals("HTTP/1.0")) {version=HttpVersion.HTTP_1_0;}else{throw new HttpException(HttpResponseStatus.BAD_REQUEST,"Unsuported HTTP Protocol "+text);}request = new DefaultHttpRequest(version,method,uri);request.setCharacterEncoding(charset);int at = uri.indexOf('?');String queryString ;if(at>=0){queryString = uri.substring(0, at);}else{queryString = uri;}dsWith("/")){queryString = queryString+this.defaultIndex;request.setQueryString(queryString);}else{request.setQueryString(queryString);}dsWith(this.dynamicSuffix)){request.setDynamic(true);if(at>0){String params = uri.substring(at);request.decodeContentAsURL(params,charset);}}else{request.setDynamic(false);}
//				logger.debug("Socket Session({}) : {}",processor.hashCode(),queryString);processor.setRequest(request);processor.setSocketStatus(HttpSocketStatus.READ_HEADER);}case READ_HEADER:{if(!readHeaders(buffer,request)){break;}long contentLength = ContentLength(request, -1);if(request.isDynamic()){if(contentLength>0){if(contentLength>this.maxContextSize){throw new HttpException(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, "Request Entity Too Large : "+contentLength);}try {ateContentBuffer((int)Header(HttpHeaders.Names.CONTENT_TYPE));} catch (IOException e) {logger.Message(),e);throw new HttpException(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());}processor.setSocketStatus(HttpSocketStatus.READ_VARIABLE_LENGTH_CONTENT);}else{processor.setSocketStatus(HttpSocketStatus.RUNNING);finished=true;break;}}else{if(contentLength>0){throw new HttpException(HttpResponseStatus.BAD_REQUEST,"Http Static Request Do Not Suport Content Length : " + contentLength);}else{processor.setSocketStatus(HttpSocketStatus.RUNNING);finished=true;break;}}}case READ_VARIABLE_LENGTH_CONTENT:{try {adContentBuffer(buffer)){processor.setSocketStatus(HttpSocketStatus.RUNNING);finished=true;}} catch (IOException e) {logger.Message(),e);throw new HttpException(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());}break;}default:throw new HttpException(HttpResponseStatus.BAD_REQUEST,"Error Scoket Status : " + status);}}catch(Exception e){if(request!=null){request.destroy();}throw e;}finally{if(buffer!=null){bufferpact();}}return finished;}public void encodeInitialLine(ByteBuffer buffer,HttpResponse response) throws IOException{byte[] bytes = ProtocolVersion().toString().getBytes(charset);buffer.put(bytes);buffer.put(HttpCodecUtil.SP);buffer.Status().getBytes());buffer.put(HttpCodecUtil.CRLF);}public void encodeHeaders(ByteBuffer buffer,HttpResponse response,SocketSession session) throws IOException, InterruptedException, ExecutionException {int remaining = aining();for(Entry<String,String> header : Headers()){byte[] key = Key().getBytes(charset);byte[] value = Value().getBytes(charset);remaining-=key.length+value.length+3;if(remaining<=0){buffer.flip();session.write(buffer).get();remaining = aining();bufferpact();}buffer.put(key);buffer.put(HttpCodecUtil.COLON_SP);buffer.put(value);buffer.put(HttpCodecUtil.CRLF);}if(remaining<=0){session.write(buffer).get();}buffer.put(HttpCodecUtil.CRLF);}public String getCharset() {return charset;}private boolean readHeaders(ByteBuffer buffer,HttpRequest request) throws HttpException {StringBuilder sb = new StringBuilder(64);int limit = buffer.limit();int position = buffer.position();int lineLength = 0;for(int index=position;index<limit;index++){byte nextByte = (index);if (nextByte == HttpConstants.CR) {nextByte = (index+1);if (nextByte == HttpConstants.LF) {buffer.position(index);if(lineLength==0){buffer.position(index+2);return true;}else{buffer.position(index);}readHeader(String());lineLength=0;sb.setLength(0);index++;}}else if (nextByte == HttpConstants.LF) {if(lineLength==0){buffer.position(index+2);return true;}else{buffer.position(index);}readHeader(String());lineLength=0;sb.setLength(0);index++;}else{if (lineLength >= maxHeaderSize) {throw new HttpException(HttpResponseStatus.BAD_REQUEST,"An HTTP header is larger than " + maxHeaderSize +" bytes.");}lineLength ++;sb.append((char) nextByte);}}return false;}private static void readHeader(HttpRequest request,String header){String[] kv = splitHeader(header);request.addHeader(kv[0], kv[1]);}private static String[] splitHeader(String sb) {final int length = sb.length();int nameStart;int nameEnd;int colonEnd;int valueStart;int valueEnd;nameStart = findNonWhitespace(sb, 0);for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {char ch = sb.charAt(nameEnd);if (ch == ':' || Character.isWhitespace(ch)) {break;}}for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {if (sb.charAt(colonEnd) == ':') {colonEnd ++;break;}}valueStart = findNonWhitespace(sb, colonEnd);if (valueStart == length) {return new String[] {sb.substring(nameStart, nameEnd),""};}valueEnd = findEndOfString(sb);return new String[] {sb.substring(nameStart, nameEnd),sb.substring(valueStart, valueEnd)};}private static String readLine(ByteBuffer buffer, int maxLineLength) throws HttpException {StringBuilder sb = new StringBuilder(64);int lineLength = 0;int limit = buffer.limit();int position = buffer.position();for(int index=position;index<limit;index++){byte nextByte = (index);if (nextByte == HttpConstants.CR) {nextByte = (index+1);if (nextByte == HttpConstants.LF) {buffer.position(index+2);String();}}else if (nextByte == HttpConstants.LF) {buffer.position(index+2);String();}else{if (lineLength >= maxLineLength) {throw new HttpException(HttpResponseStatus.REQUEST_URI_TOO_LONG,"An HTTP line is larger than " + maxLineLength +" bytes.");}lineLength ++;sb.append((char) nextByte);}}return null;}private static String[] splitInitialLine(String sb) {int aStart;int aEnd;int bStart;int bEnd;int cStart;int cEnd;aStart = findNonWhitespace(sb, 0);aEnd = findWhitespace(sb, aStart);bStart = findNonWhitespace(sb, aEnd);bEnd = findWhitespace(sb, bStart);cStart = findNonWhitespace(sb, bEnd);cEnd = findEndOfString(sb);return new String[] {sb.substring(aStart, aEnd),sb.substring(bStart, bEnd),cStart < cEnd? sb.substring(cStart, cEnd) : "" };}private static int findNonWhitespace(String sb, int offset) {int result;for (result = offset; result < sb.length(); result ++) {if (!Character.isWhitespace(sb.charAt(result))) {break;}}return result;}private static int findWhitespace(String sb, int offset) {int result;for (result = offset; result < sb.length(); result ++) {if (Character.isWhitespace(sb.charAt(result))) {break;}}return result;}private static int findEndOfString(String sb) {int result;for (result = sb.length(); result > 0; result --) {if (!Character.isWhitespace(sb.charAt(result - 1))) {break;}}return result;}private static void skipControlCharacters(ByteBuffer buffer) {int limit = buffer.limit();int position = buffer.position();for(int index=position;index<limit;index++){char c = (char) ((index) & 0xFF);if (!Character.isISOControl(c) &&!Character.isWhitespace(c)) {buffer.position(index);break;}}}
}

 

接下来是模拟Spring MVC的控制器,或许有人会问为什么不能直接用Spring MVC,因为Spring MVC实现的基础是在Servlet.api之上的.也就是可以反射传入HttpServletRequest接口实现类,但是在这而没有完全实现Servlet规范,所以无法与Spring MVC进行适配。但是其原理其实非常简单,就是通过Java自带的动态反射,或许会有人会说反射影响性能,其实解决这个问题不也不难,反射性能损耗最大的是通过类获取到Method对象,服务器启动的时候便可以对所有标记过Controller注解的类进行Method缓存,请求到来时只需要Invoke即可,此处几乎无性能损耗。为了实现Spring MVC的参数反转,在这里也对每种基本类型、包装类型、集合类型都做了判断,并且协助解析,达到Spring MVC最常用的参数自动解析的作用。

 

 

public final class MethodHandler {private static final Logger logger = Logger(MethodHandler.class);private final Object object;private final Method method;private final boolean responseBody;private final boolean xssFilter;private RequestParamType[] requestParamTypes;private int parameterLength;private ObjectMapper objectMapper;private boolean matcherHandler = false;private Pattern pattern;private String[] keys;public MethodHandler(Object object,Method method){this.object = hod = sponseBody=method.isAnnotationPresent(ResponseBody.class);XssFilter filter = Annotation(XssFilter.class);this.xssFilter = filter==null?true:filter.value();}public Pattern getPattern() {return pattern;}public String[] getKeys() {return keys;}public boolean isMatcherHandler() {return matcherHandler;}void setPathPattern(Pattern pattern,String[] keys) {this.pattern = pattern;this.matcherHandler = true;this.keys = keys;}void setObjectMapper(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}public Method getMethod() {return method;}public Object getObject() {return object;}void setParameterTypes(Class<?> clazz,Method method) {Class<?>[] parameterTypes = ParameterTypes();Annotation[][] parameterAnnotations = ParameterAnnotations();this.parameterLength = parameterTypes.questParamTypes = new RequestParamType[this.parameterLength];for(int i=0;i<this.parameterLength;i++){Class<?> classType = parameterTypes[i];RequestParamType paramType = new RequestParamType();Type type;if(classType==HttpRequest.class){type = Type.HTTP_REQUEST;}else if(classType==HttpResponse.class){type = Type.HTTP_RESPONSE;}else{if(classType==String.class){type = Type.STRING;}else if(classType.isAssignableFrom(List.class)){type = Type.LIST;}else if(classType.isAssignableFrom(Set.class)){type = Type.SET;}else if(classType.isAssignableFrom(Map.class)){type = Type.MAP;}else if(classType.isArray()){type = Type.ARRARY;}else if(classType==Boolean.class||classType==boolean.class){type = Type.BOOLEAN;}else if(classType==Short.class||classType==short.class){type = Type.SHORT;}else if(classType==Integer.class||classType==int.class){type = Type.INTEGER;}else if(classType==Long.class||classType==long.class){type = Type.LONG;}else if(classType==Float.class||classType==float.class){type = Type.FLOAT;}else if(classType==Double.class||classType==double.class){type = Type.DOUBLE;}else if(classType==Character.class||classType==char.class){type = Type.CHAR;}else if(classType==Byte.class||classType==byte.class){type = Type.BYTE;}else{throw new SimpleName()+"."&#Name()+" param["+i+"] is not suported to request params ioc");}Annotation[] annotations = parameterAnnotations[i];RequestParam requestParam = null;PathVariable pathVariable = null;for(Annotation annotation:annotations){if(annotation instanceof RequestParam){requestParam = (RequestParam)annotation;paramType.setName(requestParam.value());if(!requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)){paramType.setDefaultValue(requestParam.defaultValue());paramType.setRequired(false);}else{paramType.quired());}}else if(annotation instanceof PathVariable){pathVariable = (PathVariable)annotation;paramType.setName(pathVariable.value());paramType.setRequired(true);}}if(requestParam==null&& pathVariable == null){throw new SimpleName()+"."&#Name()+" param["+i+"] must be annotation present RequestParam or PathVariable");}}paramType.setType(type);questParamTypes[i] = paramType;}}public Object invoke(HttpRequest request,HttpResponse response) throws Throwable{try{if(this.parameterLength>0){Object[] params = new Object[this.parameterLength];for(int i=0;i<this.parameterLength;i++){RequestParamType requestParamType = requestParamTypes[i];Type type = Type();if(type==Type.HTTP_REQUEST){params[i]=request;}else if(type == Type.HTTP_RESPONSE){params[i]=response;}else{String name = Name();String value = Parameter(name);if(value==null){value = DefaultValue();}if(requestParamType.isRequired()&&value==null){logger.warn("bad request http param[{}]=null",name);throw new HttpException(HttpResponseStatus.BAD_REQUEST);}if(value==null){continue;}switch(type){case STRING:params[i]=value;break;case LIST:params[i]&#adValue(value, List.class);break;case SET:params[i]&#adValue(value, Set.class);break;case MAP:params[i]&#adValue(value, Map.class);break;case ARRARY:params[i]&#adValue(value, List.class).toArray();break;case BOOLEAN:params[i]=Boolean.parseBoolean(value);break;case SHORT:params[i]=Short.parseShort(value);break;case INTEGER:params[i]=Integer.parseInt(value);break;case LONG:params[i]=Long.parseLong(value);break;case FLOAT:params[i]=Float.parseFloat(value);break;case DOUBLE:params[i]=Double.parseDouble(value);break;case CHAR:params[i]=value.charAt(0);break;case BYTE:params[i]&#Bytes()[0];break;default:{}}}}return method.invoke(object,params);}else{return method.invoke(object);}}catch(InvocationTargetException e){TargetException();}}public boolean isResponseBody() {return responseBody;}public boolean isXssFilter(){return xssFilter;}
}

最终实现的结果如下,Object消息输出默认采用XSS过滤:

 

@Controller
public class TestController {protected Logger logger = Logger(TestController.class);@RequestMapping("/test1.do")@ResponseBodypublic Object test1(){Map<String,String> result = new HashMap<String,String>();result.put("msg", "Hello World!");return result;}@RequestMapping("/test2.do")@ResponseBodypublic Object test2(@RequestParam(required=true,value="id")String id,@RequestParam(value="name",defaultValue="Unknown")String name){Map<String,String> result = new HashMap<String,String>();result.put("id", id);result.put("name", name);return result;}@RequestMapping("/multipart.do")public String multipart(HttpRequest request) throws Exception{FileItem file = File("file");logger.FieldName()+":"&#FieldName()+":"&#Size());FileUtils.writeByteArrayToFile(new File("D://test.jpg"), ());return "success";}@RequestMapping("/xssFilter1.do")@ResponseBodypublic Object xssFilter1(HttpRequest request,HttpResponse response){ParametersMap();}@RequestMapping("/xssFilter2.do")@ResponseBody@XssFilter(false)public Object xssFilter2(HttpRequest request,HttpResponse response){ParametersMap();}@RequestMapping("/test/test/*")@ResponseBodypublic Object test10(){Map<String,String> result = new HashMap<String,String>();result.put("msg", "Hello World!");return result;}@RequestMapping("/test/{id}/index.do")@ResponseBodypublic Object test11(@PathVariable("id")String id){Map<String,String> result = new HashMap<String,String>();result.put("msg", "Hello World!");return result;}
}

 

 

 

接下来是WebSocket协议包体

 

 

 

当浏览器发起一个请求建立WebSocket连接的时候,首先还是发起一个原始的HTTP请求,这个请求通常是GET请求,但是Header(Connection)不在是Keep-Alive而是Upgrade,Header(Upgrade) 是WebSocket,并且会带上来一个密钥Header(Sec-WebSocket-Key)和一个Header(Sec-WebSocket-Version),最新的WebSocket版本是13,Chrome IE10 Firefox 等等发起的请求都是13。虽然Ajax 可以修改请求Header,但是WebSocket规范浏览器不允许浏览器设置以上4个Header所以服务器需要通过这四个头来判断请求的合法性,防止被伪造。

服务器判断成功后则将响应的返回码设置为101 Switching Protocols ,代表协议被切换,并且同样返回Upgrade 和Connection头,并且将密钥通过拼接规范指定的“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”后通过SHA1签名,再通过Base64加密返回,浏览器判断返回预期数据则表示WebSocket握手成功。

 

以下WebSocket包的协议规范,从左到有总共32位,1位表示是否是结束帧,2-4位保留位,除非特定协商才使用,5-8四位作为一个数字表示操作码,0:继续帧,1:文本帧,2:二进制帧,3-7:保留用于未来使用,8:关闭帧,9:ping帧,A:pong帧,B-F:保留用于未来使用;

第9位表示是否掩码,WebSocket规范浏览器发出的消息必须掩码,服务器发出的消息必须不掩码。

10-16位组成一个无符号的数字N最大127,如果小于等于125则代表包体长度只有N,如果126或则127则使用扩展长度,126使用16位扩展127则使用64位扩展,扩展长度中的无符号数字则表示包体长度。

如果第9位判断需要掩码,则后面4个字节(32位)作为掩码,读取完掩码之后按照之前读取出来的包体长度读取包体,掩码解码规则如下所示:

 

for(;index<end;index++,this.payloadIndex++){byte masked = (index);masked = (byte)(masked ^ (mask[(int)(this.payloadIndex%4)] & 0xFF));buffer.put(index, masked);
}

 

 

简单的聊天室测试类,浏览器想服务器发出一段消息,服务器数秒后,异步主动通知浏览器。

 

 

public class ChartSession extends SimpleWebSocket implements WebSocketSession{private Map<String,ChartSession> sessions;private String name;public ChartSession(String name ,Map<String,ChartSession> sessions) {super(65535);this.sessions = sessions;this.name = name;}private static final Logger logger = Logger(ChartSession.class);private WebSocketSession session;@Overridepublic void onClose() {logger.info("{} exit",name);ve(name);}@Overridepublic void setWebSocketSession(WebSocketSession session) {this.session = session;}@Overridepublic void onMessage(byte[] message) {String messageStr;try {messageStr = new String(message,"UTF-8");} catch (UnsupportedEncodingException e) {messageStr = new String(message);}logger.info("{} say : ",messageStr);Iterator<ChartSession> iter = sessions.values().iterator();byte[] m;try {m = (name+" say : "+messageStr).getBytes("UTF-8");} catch (UnsupportedEncodingException e1) {m = (name+" say : "+messageStr).getBytes();}while(iter.hasNext()){ChartSession s = ();try {s.sendText(m, 1, TimeUnit.SECONDS, null);} catch (InterruptedException e) {}}}@Overridepublic Future<Void> sendText(byte[] bytes, long timeout, TimeUnit unit,WebSocketCallback callback) throws InterruptedException {return session.sendText(bytes, timeout, unit, callback);}@Overridepublic Future<Void> sendBinary(byte[] bytes, long timeout, TimeUnit unit,WebSocketCallback callback) throws InterruptedException {// TODO Auto-generated method stubreturn session.sendBinary(bytes, timeout, unit, callback);}@Overridepublic Future<Void> close(WebSocketCallback callback, long timeout,TimeUnit unit) throws InterruptedException {// TODO Auto-generated method stubreturn session.close(callback, timeout, unit);}
}

 

 

 

 

 

--------------2014-03-16-更新

1.修改了启动方式,Http Server 通过传统构建,Controller采用Spring Context

2.增加了Velocity Controller 模版引擎

3.增加了Velocity 页面输出的deflate,gzip输出压缩,优先deflate

 

--------------2014-03-17-更新

1.修复了启动时,velocity 初始化配置过晚导致spring初始化类无法找到模版的BUG

2.调整了全局拦截器的执行位置,使其能够在未找到Handler时执行

 

 --------------2014-04-21-更新

1.将@RequestParam的required默认设置成了true

2.解决了指定静态目录的BUG

 

 --------------2014-04-27-更新

1.修复了302跳转没有返回Content-Length:0 ,导致FireFox处于盲听状态。

 

 --------------2014-06-19-更新

1.优化了静态服务处理类,处理htdocs路径的时候由getAbsolutePath改成了getCanonicalPath,并且将目录分割符号改成了File.separator,因为getAbsolutePath可能在指定相对静态目录路径的时候导致路径混淆。

 

本文发布于:2024-01-31 17:02:21,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170669174130049.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:超轻   服务器   AIO   HTTP
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23