/**
* Copyright (c) 2005-2012 https://github.com/zhangkaitao
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
package com.sishuok.chapter4.web.upgrade.server;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*
* 协议升级:即从现有的http 1.1协议切换到如websocket、http2.0等客户端和服务器都支持的协议上。以达到如websocket全双工通信等。
*
* 必须http1.1
* 具体请参考 HTTP 1.1协议 14.42 Upgrade
* http://www.cnpaf.net/Class/HTTP/200811/23277.html
* 升级过程:
* 1、首先客户端发送请求,头信息中必须带着:
* Connection=Upgrade
* Upgrade=新的协议
* 2、如果服务器同意协议,则返回响应:
* 2.1、响应状态行(Status-Line):
* HTTP/1.1 101 Switching Protocols
* 2.2、响应头信息:
* Connection=Upgrade
* Upgrade=新的协议
*
* 3、如果服务器不支持协议,可以响应状态行
* HTTP/1.1 400 Bad Request
*
* 等,具体需要根据协议制定,可以参考websocket 打开阶段握手( https://github.com/zhangkaitao/websocket-protocol/wiki/4.%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B)
* 如上是一个简单的握手。
*
* 4、如果切换成功,接着就可以通信了,之后的通信将与Servlet无关。
*
* 如下内容摘自Servlet3.1规范(http://jinnianshilongnian.iteye.com/blog/1910981)
* 1、Servlet过滤器仅处理初始的HTTP请求和响应,然后它们将不会再参与到后续的通信中。换句话说,一旦请求被升级,它们将不会被调用。
* 2、协议处理器(ProtocolHandler)可以使用非阻塞I/O(non blocking I/O)消费和生产消息。
* 3、当收到一个升级(upgrade)请求,servlet可以调用HttpServletRequest.upgrade方法启动升级处理。应用准备和发送一个合适的响应到客户端。退出servlet service方法之后,servlet容器完成所有过滤器的处理并标记连接已交给协议处理器处理。然后调用协议处理器的init方法,传入一个WebConnection以允许协议处理器访问数据流。
* 4、在HTTP/1.1,Upgrade通用头(general-header)允许客户端指定其支持和希望使用的其他通信协议。如果服务器找到合适的切换协议,那么新的协议将在之后的通信中使用。Servlet容器提供了HTTP升级机制。不过,Servlet容器本身不知道任何升级协议。协议处理封装在协议处理器。在容器和协议处理器之间通过字节流进行数据读取或写入。
*
*
* 为了测试,我们制定了两个协议:
* echo : 回显
* time : 获取当前服务器时间
*
* 通过头字段Upgrade指定
*
* echo : 回显
* 客户端
* (内容|BYE)CRLF ----BYE表示客户端终止连接
* 服务器
* (内容)CRLF
*
* time : 获取当前服务器时间
* 客户端
* (time|BYE)CRLF ----BYE表示客户端终止连接
* 服务器
* (当前系统时间)CRLF
*
* 此处就是一个简单的例子,其实协议升级后就和普通的socket没啥区别了。
*
* 关键组件:
* HttpUpgradeHandler:协议升级处理器,负责协议升级的,容器将于该处理器通信;
* //HTTP Upgrade 升级完成时回调一次(仅一次),接着可以使用新协议来进行客户端/服务器通信(此时容器完全不知道细节)
* //使用WebConnection连接进行通信,通过它可以获取输入输出流(可以以非阻塞IO通信)
* public void init(WebConnection wc);
*
* 当客户端断开连接时回调
* public void destroy();
*
*
* <p>User: Zhang Kaitao
* <p>Date: 13-7-20 下午10:59
* <p>Version: 1.0
*/
@WebServlet(name = "myUpgradeServlet", urlPatterns = "/upgrade")
public class MyUpgradeServlet extends HttpServlet {
@Override
protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
String connection = req.getHeader("Connection");
String upgrade = req.getHeader("Upgrade");
if(!"Upgrade".equalsIgnoreCase(connection) || upgrade == null) {
//400 错误请求
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
//开始切换协议
switch (upgrade.toLowerCase()) {
case "echo" :
//协议升级处理器。此时可以调用它传些参数
EchoHttpUpgradeHandler echoHttpUpgradeHandler = req.upgrade(EchoHttpUpgradeHandler.class);
//告诉切换成功
resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
resp.setHeader("Upgrade", "echo");
resp.setHeader("Connection", "Upgrade");
//比如可以通过protocol头 告诉客户端支持的协议列表
resp.setHeader("protocol", "echo,time");
break;
case "time" :
TimeHttpUpgradeHandler timeHttpUpgradeHandler = req.upgrade(TimeHttpUpgradeHandler.class);
//告诉切换成功
resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
resp.setHeader("Upgrade", "time");
resp.setHeader("Connection", "Upgrade");
//比如可以通过protocol头 告诉客户端支持的协议列表
resp.setHeader("protocol", "echo,time");
break;
default:
//不支持的协议
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
//比如可以通过protocol头 告诉客户端支持的协议列表
resp.setHeader("protocol", "echo,time");
break;
}
}
}