`
76756154
  • 浏览: 34928 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

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

    博客分类:
  • Java
阅读更多

 

高端大气上档次的网页PPT:http://www.ipresst.com/play/52df66bb1f3a3b3448003c67

 

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原理图 下面是精简后的源码,简单过目后便可大致了解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.getRuntime().availableProcessors();
		channelWorkers = Executors.newFixedThreadPool(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 = LoggerFactory.getLogger(SocketAcceptHandler.class);
	
	private HttpServer server;
	
	public void setServer(HttpServer server) {
		this.server = server;
	}

	@Override
	public void completed(AsynchronousSocketChannel socket,
			Object obj) {
		try{
			SocketSession session = new SocketSession(socket,server);
//			logger.debug("Socket Session({}) Create",session.hashCode());
			session.read();
		}catch(Throwable t){
			logger.error(t.getMessage(),t);
			try {
				socket.close();
			} catch (IOException e) {}
		}finally{
			server.accept();
		}
	}
	
	@Override
	public 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 = LoggerFactory.getLogger(HttpMessageSerializer.class);

	private int maxInitialLineLength = 1024*2; //Default 2KB
	private int maxHeaderSize = 1024*4; //Default 4KB
	private int maxContextSize = 1024*1024*5 ;//Default 5MB
	private String charset = "UTF-8";
	private String dynamicSuffix;
	private String defaultIndex;

	private ServerConfig serverConfig;

	@Autowired
	public void setServerConfig(ServerConfig serverConfig) {
		this.serverConfig = serverConfig;
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		this.charset = serverConfig.getString("server.http.charset", charset);
		logger.info("server.http.charset : {}",charset);
		this.maxHeaderSize = serverConfig.getBytesLength("server.http.maxHeaderSize", this.maxHeaderSize);
		logger.info("server.http.maxHeaderSize : {}",maxHeaderSize);

		this.maxContextSize = serverConfig.getBytesLength("server.http.maxContextSize", this.maxContextSize);
		logger.info("server.http.maxContextSize : {}",maxContextSize);

		this.dynamicSuffix = serverConfig.getString("server.http.dynamic.suffix", ".do");
		logger.info("server.http.dynamic.suffix : {}",dynamicSuffix);
		
		this.defaultIndex = serverConfig.getString("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 = processor.getSocketStatus();
			request = processor.getRequest();
			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.getHttpMethod(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;
				}
				if(queryString.endsWith("/")){
					queryString = queryString+this.defaultIndex;
					request.setQueryString(queryString);
				}else{
					request.setQueryString(queryString);
				}
				
				if(queryString.endsWith(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 = HttpHeaders.getContentLength(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 {
							request.createContentBuffer((int)contentLength,request.getHeader(HttpHeaders.Names.CONTENT_TYPE));
						} catch (IOException e) {
							logger.info(e.getMessage(),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 {
					if(request.readContentBuffer(buffer)){
						processor.setSocketStatus(HttpSocketStatus.RUNNING);
						finished=true;
					}
				} catch (IOException e) {
					logger.info(e.getMessage(),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){
				buffer.compact();
			}
		}
		return finished;
	}
	
	
	

	public void encodeInitialLine(ByteBuffer buffer,HttpResponse response) throws IOException{
		byte[] bytes = response.getProtocolVersion().toString().getBytes(charset);
		buffer.put(bytes);
		buffer.put(HttpCodecUtil.SP);
		buffer.put(response.getStatus().getBytes());
		buffer.put(HttpCodecUtil.CRLF);
	}
	public void encodeHeaders(ByteBuffer buffer,HttpResponse response,SocketSession session) throws IOException, InterruptedException, ExecutionException {
		int remaining = buffer.remaining();
		
		for(Entry<String,String> header : response.getHeaders()){
			byte[] key = header.getKey().getBytes(charset);
			byte[] value = header.getValue().getBytes(charset);
			remaining-=key.length+value.length+3;
			if(remaining<=0){
				buffer.flip();
				session.write(buffer).get();
				remaining = buffer.remaining();
				buffer.compact();
			}
			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 = buffer.get(index);
			if (nextByte == HttpConstants.CR) {
				nextByte = buffer.get(index+1);
				if (nextByte == HttpConstants.LF) {
					buffer.position(index);
					if(lineLength==0){
						buffer.position(index+2);
						return true;
					}else{
						buffer.position(index);
					}
					readHeader(request,sb.toString());
					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(request,sb.toString());
				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 = buffer.get(index);
			if (nextByte == HttpConstants.CR) {
				nextByte = buffer.get(index+1);
				if (nextByte == HttpConstants.LF) {
					buffer.position(index+2);
					return sb.toString();
				}
			}else if (nextByte == HttpConstants.LF) {
				buffer.position(index+2);
				return sb.toString();
			}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) (buffer.get(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 = LoggerFactory.getLogger(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 = object;
		this.method = method;
		this.responseBody=method.isAnnotationPresent(ResponseBody.class);
		XssFilter filter = method.getAnnotation(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 = method.getParameterTypes();
		Annotation[][] parameterAnnotations = method.getParameterAnnotations();
		this.parameterLength = parameterTypes.length;
		this.requestParamTypes = 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 RuntimeException(clazz.getSimpleName()+"."+method.getName()+" 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.setRequired(requestParam.required());
						}
					}else if(annotation instanceof PathVariable){
						pathVariable = (PathVariable)annotation;
						paramType.setName(pathVariable.value());
						paramType.setRequired(true);
					}
				}
				if(requestParam==null&& pathVariable == null){
					throw new RuntimeException(clazz.getSimpleName()+"."+method.getName()+" param["+i+"] must be annotation present RequestParam or PathVariable");
				}
			}
			paramType.setType(type);
			this.requestParamTypes[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 = requestParamType.getType();
					if(type==Type.HTTP_REQUEST){
						params[i]=request;
					}else if(type == Type.HTTP_RESPONSE){
						params[i]=response;
					}else{
						String name = requestParamType.getName();
						String value = request.getParameter(name);
						if(value==null){
							value = requestParamType.getDefaultValue();
						}
						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]=objectMapper.readValue(value, List.class);break;
						case SET:params[i]=objectMapper.readValue(value, Set.class);break;
						case MAP:params[i]=objectMapper.readValue(value, Map.class);break;
						case ARRARY:params[i]=objectMapper.readValue(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]=value.getBytes()[0];break;
						default:{}
						}
					}
				}
				return method.invoke(object,params);
			}else{
				return method.invoke(object);
			}
		}catch(InvocationTargetException e){
			throw e.getTargetException();
		}
	}
	public boolean isResponseBody() {
		return responseBody;
	}
	public boolean isXssFilter(){
		return xssFilter;
	}
}

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

 

@Controller
public class TestController {
	protected Logger logger = LoggerFactory.getLogger(TestController.class);
	@RequestMapping("/test1.do")
	@ResponseBody
	public Object test1(){
		Map<String,String> result = new HashMap<String,String>();
		result.put("msg", "Hello World!");
		return result;
	}
	@RequestMapping("/test2.do")
	@ResponseBody
	public 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 = request.getFile("file");
		logger.info(file.getFieldName()+":"+file.getFieldName()+":"+file.getSize());
		FileUtils.writeByteArrayToFile(new File("D://test.jpg"), file.get());
		return "success";
	}
	@RequestMapping("/xssFilter1.do")
	@ResponseBody
	public Object xssFilter1(HttpRequest request,HttpResponse response){
		return request.getParametersMap();
	}
	@RequestMapping("/xssFilter2.do")
	@ResponseBody
	@XssFilter(false)
	public Object xssFilter2(HttpRequest request,HttpResponse response){
		return request.getParametersMap();
	}
	
	
	@RequestMapping("/test/test/*")
	@ResponseBody
	public Object test10(){
		Map<String,String> result = new HashMap<String,String>();
		result.put("msg", "Hello World!");
		return result;
	}
	@RequestMapping("/test/{id}/index.do")
	@ResponseBody
	public 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 = buffer.get(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 = LoggerFactory.getLogger(ChartSession.class);
	private WebSocketSession session;
	@Override
	public void onClose() {
		logger.info("{} exit",name);
		sessions.remove(name);
	}
	@Override
	public void setWebSocketSession(WebSocketSession session) {
		this.session = session;
	}
	@Override
	public 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 = iter.next();
			try {
				s.sendText(m, 1, TimeUnit.SECONDS, null);
			} catch (InterruptedException e) {}
		}
		
	}
	@Override
	public Future<Void> sendText(byte[] bytes, long timeout, TimeUnit unit,
			WebSocketCallback callback) throws InterruptedException {
		return session.sendText(bytes, timeout, unit, callback);
	}
	@Override
	public Future<Void> sendBinary(byte[] bytes, long timeout, TimeUnit unit,
			WebSocketCallback callback) throws InterruptedException {
		// TODO Auto-generated method stub
		return session.sendBinary(bytes, timeout, unit, callback);
	}
	@Override
	public Future<Void> close(WebSocketCallback callback, long timeout,
			TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return 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可能在指定相对静态目录路径的时候导致路径混淆。

 

  • 大小: 29.4 KB
  • 大小: 6.8 KB
  • 大小: 44.9 KB
分享到:
评论
13 楼 songshunyao 2017-01-09  
12 楼 爱知冰点 2016-11-01  
牛                    逼
11 楼 psfu 2016-07-29  
问下博主,这个后来用上了么?
10 楼 inotgaoshou 2015-11-24  
9 楼 softdzx 2014-07-29  
去阿里了?
8 楼 宋建勇 2014-06-06  
期待博主的书籍啊!
7 楼 niedj 2014-06-04  
这个必须要赞。。
6 楼 mtsw2011 2014-06-04  
关注楼主!
5 楼 zhangth 2014-06-03  
不错,牛人
4 楼 yanqingluo 2014-06-02  
3 楼 76756154 2014-05-06  
mordecai 写道
谢谢分享,不过你那个链接里的内容好象没有了?

链接以开启
2 楼 mordecai 2014-04-29  
谢谢分享,不过你那个链接里的内容好象没有了?
1 楼 wf_7758520 2014-03-15  

相关推荐

    基于java的BIO、NIO、AIO通讯模型代码实现

    Java作为一门广泛使用的开发语言,提供了多种I/O(Input/Output)通信模型,包括传统的阻塞I/O(BIO)、非阻塞I/O(NIO)以及异步I/O(AIO)。这些通信模型在不同的场景下有着各自的优势,理解和掌握它们对于优化...

    基于java AIO实现的RPC调用框架.zip

    基于Java AIO实现的RPC调用框架,其核心思想是利用AIO的非阻塞特性,实现客户端发起请求后,无需等待服务器响应即可继续执行其他任务,而服务器在接收到请求后,也无需立即返回结果,而是通过回调机制通知客户端。...

    基于javaaio实现,开源、简单、易用、低延迟、高性能百万级java mqtt client组件和java mqtt br服务

    基于Java AIO实现的MQTT客户端和broker服务,如标题和描述中提到的,旨在提供一个开源、简单、易用、低延迟且高性能的解决方案,特别适合于物联网(IoT)、边缘计算以及大规模服务器应用中处理大量的设备连接和消息...

    基于 java aio 实现的低延迟、高性能百万级 mqtt client 组件和 mqtt broker 服务

    基于 java aio 实现的低延迟、高性能百万级 mqtt client 组件和 mqtt broker 服务。支持 MQTT v3.1、v3.1.1 以及 v5.0 协议。 支持 websocket mqtt 子协议(支持 mqtt.js)。 支持 http rest api, 支持 MQTT client...

    使用AIO实现非阻塞socket通信

    4. **事件驱动**: AIO还支持基于事件的编程模型,例如`Selector`和`SelectionKey`。`Selector`可以监视多个通道的事件,当某个通道准备好读写时,`Selector`会通知我们,这样可以进一步优化多路复用,提高系统吞吐量...

    BIO,NIO,AIO实现的demo

    在提供的"**BIO_TimeServer**"压缩包中,我们可以找到基于BIO实现的时间服务器示例。这个示例通常包括一个服务器端(SERVER)和一个客户端(CLIENT)。服务器端会创建一个监听套接字,等待客户端的连接请求。当...

    鹊桥(内网穿透),使用java基于aio/nio实现的内网穿透工具,致力于帮助开发者内网开发供外部调试使用

    鹊桥,又称为MagpieBridge,是一款基于Java的内网穿透工具,利用先进的异步I/O模型(AIO/NIO)来实现高效的网络通信。它为开发者提供了在内网环境中进行开发并允许外部进行调试的强大功能,对于远程协作、云服务测试...

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    jaca视频教程 jaca游戏服务器端开发 Netty NIO AIO Mina视频教程 课程目录: 一、Netty快速入门教程 01、第一课NIO 02、第二课netty服务端 03、第三课netty客户端 04、第四课netty线程模型源码分析(一) 05、...

    bio nio aio demo

    AIO最适合于大文件传输或者低频率、高吞吐量的网络通信。 在"bio nio aio demo"项目中,我们可以通过运行`main`方法来体验这三种模型的不同之处。这个示例可能会包含三个部分:BIO服务器、NIO服务器和AIO服务器。每...

    基于java aio 的RPC 远程调用框架.zip

    基于Java AIO实现的RPC框架,能够利用异步I/O的优势,提高远程服务调用的效率和响应速度。 在设计基于Java AIO的RPC框架时,我们需要考虑以下几个核心知识点: 1. **Java NIO基础**:理解Java NIO的基础概念,包括...

    基于AIO固件2.0.5版本进行修改_ESP32-small-TV.zip

    基于AIO固件2.0.5版本进行修改_ESP32-small-TV

    AIO高并发服务器、客户端聊天室源码

    I/O 与 NIO 一个比较重要的区别是我们使用 I/O 的时候往往会引入多线程,每个连接使用一个单独的线程,而 NIO 则是使用单线程或者只使用少量的多线程,每个连接共用一个线程...AIO高并发服务器、客户端聊天室源码奉献。

    基于java编程的服务器程序

    【基于Java编程的服务器程序】 Java作为一种广泛使用的编程语言,其在服务器端的应用十分广泛,尤其是在构建高性能、可扩展的网络应用程序中。本篇将深入探讨如何使用Java来编写服务器程序,以及涉及到的相关技术与...

    aio方式socket文件传输--改进

    本文将深入探讨aio方式的socket文件传输及其在客户端和服务器端的应用。 首先,我们需要理解什么是aio。在传统的同步I/O模型中,数据读写操作是阻塞的,意味着当一个进程进行I/O操作时,必须等待该操作完成才能继续...

Global site tag (gtag.js) - Google Analytics