Net 组件介绍

        Net 组件是基于AIO(NIO.2)的一套TCP/UDP的网络框架,且只提供异步接口。org.redkale.net 是所有网络协议服务的基础包。Redkale内置HTTP和远程模式Service依赖的SNCP(Service Node Communicate Protocol)协议的实现包。Redkale启动的<server>节点服务都是基于Net组件实现的协议。下面详细介绍 HTTP服务SNCP协议

HTTP 服务

        Redkale自实现的HTTP服务接口并不遵循Java EE规范JSR 340(Servlet 3.1),Redkale提倡的是HTTP+JSON的服务接口方式因此没有实现JSP规范,HTTP+JSON服务接口几乎适合所有类型的客户端(PC应用程序、PC Web、微信H5、移动APP、移动Web)开发。其与JSR 340(Servlet 3.1)的主要区别如下:
                1、内置参数的JSON反序列化和响应结果的序列化接口。
                2、对数值型的参数和header值提供简易接口。
                3、不支持JSP,提倡的是HTTP+JSON接口方式。
                4、无HttpSession对象,session可通过配置CacheSource实现。
                5、内置文件上传接口,且可自实现断点续传。
                6、对响应结果内容可以进行短期缓存从而减少数据源操作的压力。
                7、内置WebSocket的集群与组功能,且提供伪WebSocket连接功能。
                8、HttpResponse只能异步输出。

        编写Redkale的HttpServlet与 JSR 340中的javax.servlet.http.HttpServlet 基本相同,只需继承 org.redkale.net.http.HttpServlet, 比较好的习惯是一个项目先定义一个项目级的BaseServlet类,这样方便以后加入类似javax.servlet.Filter的功能。

        一个典型的BaseSerlvet实现:

@HttpUserType(UserInfo.class)
public class BaseSerlvet extends HttpServlet {

    protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());

    protected final boolean fine = logger.isLoggable(Level.FINE);

    @Resource(name = "APP_TIME") //[Redkale内置资源]  进程的启动时间
    protected long serverCreateTime;

    @Resource //[Redkale内置资源]
    protected JsonConvert jsonConvert;

    @Resource //[Redkale内置资源]
    protected JsonFactory jsonFactory;

    //[Redkale内置资源], 当前进程的根目录,字段类型可以是 String、java.io.File、java.nio.file.Path
    @Resource(name = "APP_HOME")
    protected File home;

    //[Redkale内置资源], 当前Http Server的web页面的根目录,字段类型可以是 String、java.io.File、java.nio.file.Path
    @Resource(name = "SERVER_ROOT")
    protected File webroot;

    @Resource
    private UserService service;

    //在调用authenticate之前调用, 必须在此处设置currentUser用户信息
    //该方法可以用于判断请求源是否合法或加入一些全局的拦截操作
    @Override
    public void preExecute(final HttpRequest request, final HttpResponse response) throws IOException {
        if (!request.getHeader("User-Agent", "").contains("Redkale-Agent")) {  //只用移动APP的接口可以判断User-Agent是否正确
            response.addHeader("retcode", "10001");
            response.addHeader("retmessage", "User-Agent error");
            response.finish(201, "{'success':false, 'message':'User-Agent error, must be Redkale-Agent'}");
            return;
        }
        //可以加上一些统计操作
        if (fine) response.recycleListener((req, resp) -> {  //记录处理时间太长的请求操作
                long e = System.currentTimeMillis() - request.getCreatetime();
                if (e > 500) logger.fine("耗时居然用了 " + e + " 毫秒. 请求为: " + req);
            });
        final String sessionid = request.getSessionid(false);
        if (sessionid != null) request.setCurrentUser(userService.current(sessionid));
        response.nextEvent();
    }

    //一般用于判断用户的登录态, 返回false表示鉴权失败
    //moduleid值来自 @WebServlet.moduleid()用于定义模块ID; actionid值自来@HttpMapping.actionid()用于定义操作ID; 需要系统化的鉴权需要定义这两个值
    @Override
    public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
        UserInfo info = request.currentUser();
        if (info == null) {
            response.finishJson(RetCodes.retResult(RetCodes.RET_USER_UNLOGIN));
            return;
        } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
            response.finishJson(RetCodes.retResult(RetCodes.RET_USER_AUTH_ILLEGAL));
            return;
        }
        response.nextEvent();
    }
}
                

        继承HttpServlet的子类可以使用其自带的鉴权、请求分支、缓存等功能, 一个典型的操作用户HttpServlet:

@WebServlet(value = {"/user/*"}, comment = "用户模块服务")  //拦截所有 /user/ 开头的请求
public class UserServlet extends BaseSerlvet {

    @Resource
    private UserService service;

    //登录操作 
    //因为HttpMapping的判断规则用的是String.startsWith,所以HttpMapping.url不能用正则表达式,只能是getRequestURI的前缀
    //且同一个HttpServlet类内的所有HttpMapping不能存在包含关系, 如 /user/myinfo 和 /user/myinforecord 不能存在同一HttpServlet中。
    @HttpMapping(url = "/user/login", auth = false, comment = "用户登录")
    @HttpParam(name = "bean", type = LoginBean.class, comment = "登录参数对象")
    public void login(HttpRequest req, HttpResponse resp) throws IOException {
        LoginBean bean = req.getJsonParameter(LoginBean.class, "bean"); //获取参数
        RetResult<UserInfo> result = service.login(bean); //登录操作, service内部判断bean的合法性
        resp.finishJson(result); //输出结果
    }

    //获取当前用户信息
    //未登录的请求会被BaseSerlvet.authenticate方法拦截,因此能进入该方法说明用户态存在
    @HttpMapping(url = "/user/myinfo", comment = "获取当前用户信息")
    public void myinfo(HttpRequest req, HttpResponse resp) throws IOException {
        UserInfo user = service.current(req.getSessionid(false));
        //或者使用 user = req.getAttribute("_current_userinfo"); 因为BaseSerlvet.authenticate方法已经将UserInfo注入到_current_userinfo属性中
        resp.finishJson(user);  //输出用户信息
    }

    //获取指定用户ID的用户信息, 请求如: /user/username/43565443
    // 翻页查询想缓存就需要将翻页信息带进url: /user/query/page:2/size:50 。
    @HttpMapping(url = "/user/userinfo/", comment = "获取指定用户ID的用户信息")
    @HttpParam(name = "#", type = int.class, comment = "用户ID")
    public void userinfo(HttpRequest req, HttpResponse resp) throws IOException {
        UserInfo user = service.findUserInfo(Integer.parseInt(req.getRequstURILastPath()));
        resp.finishJson(user);  //输出用户信息
    }

    //更新个人头像
    @HttpMapping(url = "/user/updateface", comment = "更新用户头像")
    public void updateface(HttpRequest req, HttpResponse resp) throws IOException {
        UserInfo user = service.current(req.getSessionid(false));
        for (MultiPart part : req.multiParts()) { //遍历上传文件列表 
            byte[] byts = part.getContentBytes(5 * 1024 * 1024L);  //图片大小不能超过5M,超过5M返回null
            if (byts == null) {
                resp.finishJson(new RetResult(102, "上传的文件过大"));
            } else {
                BufferedImage img = ImageIO.read(new ByteArrayInputStream(byts));
                //... 此处处理并存储头像图片
                resp.finishJson(RetResult.SUCCESS);  //输出成功信息
            }
            return; //头像图片仅会传一个, 只需要读取一个即可返回
        }
        resp.finishJson(new RetResult(103, "没有上传图片"));
    }
}
                

        如上,所有/user/前缀的请求都会进入UserServlet,若没匹配的则返回505错误,为了方便以后编写前方静动分离服务器转发规则,比较好的习惯是将项目中所有动态Servlet加一个固定前缀,在 application.xml 里设置path即可。

<server protocol="HTTP" port="6060" root="root"> 
    <services autoload="true" />  
    <servlets path="/pipes" autoload="true"/>
</server>

        如上, 配置了/pipes 前缀后,客户端发送Servlet请求需带上前缀,请求当前用户信息的url就变成:/pipes/user/myinfo 。


   API Doc

        在小型互联网公司里,基本是追求敏捷开发,很少先花时间设计接口文档再进行开发,通常都是直接进行数据库设计和开发,开发完后需要开发人员需要编写接口文档提供给前端人员开发。为了减少文档的编写工作量和强制开发人员对接口进行注释,Redkale提供了apidoc命令,apidoc遍历当前进程中所有标记@WebServlet的可用HttpServlet,根据Servlet中@HttpMapping、@HttpParam生成json对象并输出文件。在系统运行时执行apidoc命令会在进程根目录下生成apidoc.json、apidoc.html文件。apidoc.html的模板可以定制, 只需在conf/目录下存放一个 apidoc-template.html 文件,且文件中必须包含关键字 ${content} 即可实现接口文档的自定义。
        为了能正确显示接口文档,开发人员需要在编写HttpServlet时,在每个@HttpMapping方法上加上comment属性和@HttpParam注解, 一个方法上可以注解多个@HttpParam, 如上面的 UserServlet 。RestServlet是由RestService自动生成,@HttpMapping、@HttpParam在REST组件中存在对应的注解@RestMapping、@RestParam,因此在编写RestService时需要加上@RestMapping、@RestParam且加上comment属性。如范例: HelloService

   WebSokcet 服务

        WebSokcet协议遵循IETF RFC 6455,其接口并不符合Java EE中的WebSocket的接口规范。
        一个WebSocket连接对应一个WebSocket实体,即一个WebSocket会绑定一个TCP连接。且有两种模式:
         1) 普通模式: 协议上符合HTML5规范, 其流程顺序如下:
                 1.1 onOpen               若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
                 1.2 createUserid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
                 1.3 onConnected      WebSocket成功连接后在准备接收数据前回调此方法。
                 1.4 onMessage/onFragment+    WebSocket接收到消息后回调此消息类方法。
                 1.5 onClose              WebSocket被关闭后回调此方法。
        此模式下 以上方法都应该被重载。

         2) 原始二进制模式: 此模式有别于HTML5规范,可以视为原始的TCP连接。通常用于音频视频通讯场景。其流程顺序如下:
                 2.1 onOpen               如果方法返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
                 2.2 createUserid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
                 2.3 onRead               WebSocket成功连接后回调此方法, 由此方法处理原始的TCP连接, 同时业务代码去控制WebSocket的关闭。
        此模式下 以上方法都应该被重载。

        实现一个WebSocket服务需要继承 org.redkale.net.http.WebSocketServlet,以下是一个简单的聊天范例:

@WebServlet("/ws/chat")
public class ChatWebSocketServlet extends WebSocketServlet {

    @Resource
    private UserService userService;

    @Override
    public void init(HttpContext context, AnyValue conf) {
        System.out.println("本实例的WebSocketNode: " + super.node);
    }

    @Override
    public void destroy(HttpContext context, AnyValue conf) {
        System.out.println("关闭了ChatWebSocketServlet");
    }

    @Override
    protected WebSocket<Integer, ChatMessage> createWebSocket() {

        return new WebSocket<Integer, ChatMessage>() {

            private UserInfo user;

            @Override
            public void onMessage(ChatMessage message, boolean last) { // text 接收的格式:  {"receiveid":200000001, "content":"Hi Redkale!"}
                message.sendid = user.getUserid(); //将当前用户设为消息的发送方
                message.sendtime = System.currentTimeMillis(); //设置消息发送时间
                //给接收方发送消息, 即使接收方在其他WebSocket进程节点上有链接,Redkale也会自动发送到其他链接进程节点上。
                super.sendMessage(message, message.receiveid);
            }

            @Override
            protected CompletableFuture<Integer> createUserid() { //创建用户ID
                this.user = userService.current(String.valueOf(super.getSessionid()));
                return CompletableFuture.completedFuture(this.user == null ? null : this.user.getUserid());
            }

            @Override
            public CompletableFuture<String> onOpen(HttpRequest request) {
                return CompletableFuture.completedFuture(request.getSessionid(false)); //以request中的sessionid字符串作为WebSocket的sessionid
            }

        };
    }

    public static class ChatMessage {

        public int sendid; //发送方用户ID

        public int receiveid; //接收方用户ID        

        public String content; //文本消息内容

        public long sendtime;  //消息发送时间
    }
}
                

  . HttpRequest 对象

public class HttpRequest {

    //获取客户端地址IP
    public SocketAddress getRemoteAddress();

    //获取客户端地址IP, 与getRemoteAddres() 的区别在于:本方法优先取header中指定为RemoteAddress名的值,没有则返回getRemoteAddres()的getHostAddress()。
    //本方法适用于服务前端有如nginx的代理服务器进行中转,通过getRemoteAddres()是获取不到客户端的真实IP。
    public String getRemoteAddr();

    //获取请求内容指定的编码字符串
    public String getBody(Charset charset);

    //获取请求内容的UTF-8编码字符串
    public String getBodyUTF8();

    //获取请求内容的byte[]
    public byte[] getBody();
    
    //获取请求内容的JavaBean对象
    public <T> T getBodyJson(java.lang.reflect.Type type);

    //获取请求内容的JavaBean对象
    public <T> T getBodyJson(JsonConvert convert, java.lang.reflect.Type type);
    
    //获取文件上传对象
    public MultiContext getMultiContext();

    //获取文件上传信息列表 等价于 getMultiContext().parts();
    public Iterable<MultiPart> multiParts() throws IOException;

    //获取当前用户信息 数据类型由@HttpUserType指定
    public <T> T currentUser();
    
    //获取模块ID,来自@HttpServlet.moduleid()
    public int getModuleid();
    
    //获取操作ID,来自@HttpMapping.actionid()
    public int getActionid();
    
    //获取sessionid
    public String getSessionid(boolean autoCreate);

    //更新sessionid
    public String changeSessionid();

    //指定值更新sessionid
    public String changeSessionid(String newsessionid);

    //使sessionid失效
    public void invalidateSession();

    //获取所有Cookie对象
    public java.net.HttpCookie[] getCookies();

    //获取Cookie值
    public String getCookie(String name);

    //获取Cookie值, 没有返回默认值
    public String getCookie(String name, String defaultValue);

    //获取协议名 http、https、ws、wss等
    public String getProtocol();

    //获取请求方法 GET、POST等
    public String getMethod();

    //获取Content-Type的header值
    public String getContentType();

    //获取请求内容的长度, 为-1表示内容长度不确定
    public long getContentLength();

    //获取Connection的Header值
    public String getConnection();

    //获取Host的Header值
    public String getHost();

    //获取请求的URL
    public String getRequestURI();

    //截取getRequestURI最后的一个/后面的部分
    public String getRequstURILastPath();

    // 获取请求URL最后的一个/后面的部分的short值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: short type = request.getRequstURILastPath((short)0); //type = 2
    public short getRequstURILastPath(short defvalue);

    // 获取请求URL最后的一个/后面的部分的short值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: short type = request.getRequstURILastPath((short)0); //type = 2
    public short getRequstURILastPath(int radix, short defvalue);

    // 获取请求URL最后的一个/后面的部分的int值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: int type = request.getRequstURILastPath(0); //type = 2
    public int getRequstURILastPath(int defvalue);

    // 获取请求URL最后的一个/后面的部分的int值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: int type = request.getRequstURILastPath(0); //type = 2
    public int getRequstURILastPath(int radix, int defvalue);

    // 获取请求URL最后的一个/后面的部分的float值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: float type = request.getRequstURILastPath(0.f); //type = 2.f
    public float getRequstURILastPath(float defvalue);

    // 获取请求URL最后的一个/后面的部分的long值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: long type = request.getRequstURILastPath(0L); //type = 2
    public long getRequstURILastPath(long defvalue);

    // 获取请求URL最后的一个/后面的部分的long值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: long type = request.getRequstURILastPath(0L); //type = 2
    public long getRequstURILastPath(int radix, long defvalue);

    // 获取请求URL最后的一个/后面的部分的double值   <br>
    // 例如请求URL /pipes/record/query/2   <br>
    // 获取type参数: double type = request.getRequstURILastPath(0.0); //type = 2.0
    public double getRequstURILastPath(double defvalue);

    //从prefix之后截取getRequestURI再对"/"进行分隔
    public String[] getRequstURIPaths(String prefix);

    // 获取请求URL分段中含prefix段的值
    // 例如请求URL /pipes/record/query/name:hello
    // 获取name参数: String name = request.getRequstURIPath("name:", "none");
    public String getRequstURIPath(String prefix, String defaultValue);

    // 获取请求URL分段中含prefix段的short值
    // 例如请求URL /pipes/record/query/type:10
    // 获取type参数: short type = request.getRequstURIPath("type:", (short)0);
    public short getRequstURIPath(String prefix, short defaultValue);

    // 获取请求URL分段中含prefix段的short值
    // 例如请求URL /pipes/record/query/type:a
    // 获取type参数: short type = request.getRequstURIPath(16, "type:", (short)0); type = 10
    public short getRequstURIPath(int radix, String prefix, short defvalue);

    // 获取请求URL分段中含prefix段的int值
    // 例如请求URL /pipes/record/query/offset:2/limit:50
    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
    // 获取limit参数: int limit = request.getRequstURIPath("limit:", 20);
    public int getRequstURIPath(String prefix, int defaultValue);

    // 获取请求URL分段中含prefix段的int值
    // 例如请求URL /pipes/record/query/offset:2/limit:10
    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
    // 获取limit参数: int limit = request.getRequstURIPath(16, "limit:", 20); // limit = 16
    public int getRequstURIPath(int radix, String prefix, int defaultValue);

    // 获取请求URL分段中含prefix段的float值   
    // 例如请求URL /pipes/record/query/point:40.0  
    // 获取time参数: float point = request.getRequstURIPath("point:", 0.0f);
    public float getRequstURIPath(String prefix, float defvalue);

    // 获取请求URL分段中含prefix段的long值
    // 例如请求URL /pipes/record/query/time:1453104341363/id:40
    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
    public long getRequstURIPath(String prefix, long defaultValue);

    // 获取请求URL分段中含prefix段的long值
    // 例如请求URL /pipes/record/query/time:1453104341363/id:40
    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
    public long getRequstURIPath(int radix, String prefix, long defvalue);

    // 获取请求URL分段中含prefix段的double值   <br>
    // 例如请求URL /pipes/record/query/point:40.0   <br>
    // 获取time参数: double point = request.getRequstURIPath("point:", 0.0);
    public double getRequstURIPath(String prefix, double defvalue);

    //获取所有的header名
    public String[] getHeaderNames();

    // 获取指定的header值
    public String getHeader(String name);

    //获取指定的header值, 没有返回默认值
    public String getHeader(String name, String defaultValue);

    //获取指定的header的json值
    public <T> T getJsonHeader(Type type, String name);

    //获取指定的header的json值
    public <T> T getJsonHeader(JsonConvert convert, Type type, String name);

    //获取指定的header的boolean值, 没有返回默认boolean值
    public boolean getBooleanHeader(String name, boolean defaultValue);

    // 获取指定的header的short值, 没有返回默认short值
    public short getShortHeader(String name, short defaultValue);

    // 获取指定的header的short值, 没有返回默认short值
    public short getShortHeader(int radix, String name, short defaultValue);

    // 获取指定的header的short值, 没有返回默认short值
    public short getShortHeader(String name, int defaultValue);

    // 获取指定的header的short值, 没有返回默认short值
    public short getShortHeader(int radix, String name, int defaultValue);

    //获取指定的header的int值, 没有返回默认int值
    public int getIntHeader(String name, int defaultValue);

    //获取指定的header的int值, 没有返回默认int值
    public int getIntHeader(int radix, String name, int defaultValue);

    // 获取指定的header的long值, 没有返回默认long值
    public long getLongHeader(String name, long defaultValue);

    // 获取指定的header的long值, 没有返回默认long值
    public long getLongHeader(int radix, String name, long defaultValue);

    // 获取指定的header的float值, 没有返回默认float值
    public float getFloatHeader(String name, float defaultValue);

    //获取指定的header的double值, 没有返回默认double值
    public double getDoubleHeader(String name, double defaultValue);

    //获取所有参数名
    public String[] getParameterNames();

    //获取指定的参数值
    public String getParameter(String name);

    //获取指定的参数值, 没有返回默认值
    public String getParameter(String name, String defaultValue);

    //获取指定的参数json值
    public <T> T getJsonParameter(Type type, String name);

    //获取指定的参数json值
    public <T> T getJsonParameter(JsonConvert convert, Type type, String name);

    //获取指定的参数boolean值, 没有返回默认boolean值
    public boolean getBooleanParameter(String name, boolean defaultValue);

    //获取指定的参数short值, 没有返回默认short值
    public short getShortParameter(String name, short defaultValue);

    //获取指定的参数short值, 没有返回默认short值
    public short getShortParameter(int radix, String name, short defaultValue);

    //获取指定的参数short值, 没有返回默认short值
    public short getShortParameter(int radix, String name, int defaultValue);

    //获取指定的参数int值, 没有返回默认int值
    public int getIntParameter(String name, int defaultValue);

    //获取指定的参数int值, 没有返回默认int值
    public int getIntParameter(int radix, String name, int defaultValue);

    //获取指定的参数long值, 没有返回默认long值
    public long getLongParameter(String name, long defaultValue);

    //获取指定的参数long值, 没有返回默认long值
    public long getLongParameter(int radix, String name, long defaultValue);

    //获取指定的参数float值, 没有返回默认float值
    public float getFloatParameter(String name, float defaultValue);

    //获取指定的参数double值, 没有返回默认double值
    public double getDoubleParameter(String name, double defaultValue);

    //获取翻页对象 同 getFlipper("flipper", false, 0);
    public org.redkale.source.Flipper getFlipper();

    //获取翻页对象 同 getFlipper("flipper", needcreate, 0);
    public org.redkale.source.Flipper getFlipper(boolean needcreate);

    //获取翻页对象 同 getFlipper("flipper", false, maxLimit);
    public org.redkale.source.Flipper getFlipper(int maxLimit);

    //获取翻页对象 同 getFlipper("flipper", needcreate, maxLimit)
    public org.redkale.source.Flipper getFlipper(boolean needcreate, int maxLimit);

    //获取翻页对象 http://redkale.org/pipes/records/list/offset:0/limit:20/sort:createtime%20ASC
    //http://redkale.org/pipes/records/list?flipper={'offset':0,'limit':20, 'sort':'createtime ASC'}
    //以上两种接口都可以获取到翻页对象
    public org.redkale.source.Flipper getFlipper(String name, boolean needcreate, int maxLimit);

    //获取HTTP上下文对象
    public HttpContext getContext();

    //获取所有属性值, servlet执行完后会被清空
    public Map<String, Object> getAttributes();

    //获取指定属性值, servlet执行完后会被清空
    public <T> T getAttribute(String name);

    //删除指定属性
    public void removeAttribute(String name);

    //设置属性值, servlet执行完后会被清空
    public void setAttribute(String name, Object value);

    //获取request创建时间
    public long getCreatetime();
}
                

  . HttpResponse 对象

public class HttpResponse {

    //增加Cookie值
    public HttpResponse addCookie(HttpCookie... cookies);

    //增加Cookie值
    public HttpResponse addCookie(Collection<HttpCookie> cookies);

    //创建AsyncHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出
    public AsyncHandler createAsyncHandler();

    //传入的AsyncHandler子类必须是public,且保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数
    public <H extends AsyncHandler> H createAsyncHandler(Class<H> handlerClass);
    
    //设置状态码
    public void setStatus(int status);

    //获取状态码
    public int getStatus();

    //获取 ContentType
    public String getContentType();

    //设置 ContentType
    public HttpResponse setContentType(String contentType);

    //获取内容长度
    public long getContentLength();

    //设置内容长度
    public HttpResponse setContentLength(long contentLength);

    //设置Header值
    public HttpResponse setHeader(String name, Object value);

    //添加Header值
    public HttpResponse addHeader(String name, Object value);

    //添加Header值
    public HttpResponse addHeader(Map<String, ?> map);

    //跳过header的输出
    //通常应用场景是,调用者的输出内容里已经包含了HTTP的响应头信息,因此需要调用此方法避免重复输出HTTP响应头信息。
    public HttpResponse skipHeader();

    //异步输出指定内容
    public <A> void sendBody(ByteBuffer buffer, A attachment, AsyncHandler<Integer, A> handler);

    //异步输出指定内容
    public <A> void sendBody(ByteBuffer[] buffers, A attachment, AsyncHandler<Integer, A> handler);
    
    //关闭HTTP连接,如果是keep-alive则不强制关闭
    public void finish();

    //强制关闭HTTP连接
    public void finish(boolean kill);

    //将对象以JSON格式输出
    public void finishJson(Object obj);

    //将对象以JSON格式输出
    public void finishJson(JsonConvert convert, Object obj);

    //将对象以JSON格式输出
    public void finishJson(Type type, Object obj);

    //将对象以JSON格式输出
    public void finishJson(final JsonConvert convert, final Type type, final Object obj);

    //将对象以JSON格式输出
    public void finishJson(final Object... objs);

    //将RetResult对象以JSON格式输出
    public void finishJson(final org.redkale.service.RetResult ret);

    //将RetResult对象以JSON格式输出
    public void finishJson(final JsonConvert convert, final org.redkale.service.RetResult ret);

    //将CompletableFuture的结果对象以JSON格式输出
    public void finishJson(final CompletableFuture future);

    //将CompletableFuture的结果对象以JSON格式输出
    public void finishJson(final JsonConvert convert, final CompletableFuture future);

    //将CompletableFuture的结果对象以JSON格式输出
    public void finishJson(final JsonConvert convert, final Type type, final CompletableFuture future);

    //将HttpResult的结果对象以JSON格式输出
    public void finishJson(final HttpResult result);
    
    //将HttpResult的结果对象以JSON格式输出
    public void finishJson(final JsonConvert convert, final HttpResult result) ;
    
    //将指定字符串以响应结果输出
    public void finish(String obj);

    //以指定响应码附带内容输出, message 可以为null
    public void finish(int status, String message);

    //以304状态码输出
    public void finish304();

    //以404状态码输出
    public void finish404();

    //将指定byte[]按响应结果输出
    public void finish(final byte[] bs);

    //将指定ByteBuffer按响应结果输出
    public void finish(ByteBuffer buffer);

    //将指定ByteBuffer按响应结果输出
    //kill   输出后是否强制关闭连接
    public void finish(boolean kill, ByteBuffer buffer);

    //将指定ByteBuffer数组按响应结果输出
    public void finish(ByteBuffer... buffers);

    //将指定ByteBuffer数组按响应结果输出
    //kill   输出后是否强制关闭连接
    public void finish(boolean kill, ByteBuffer... buffers);

    //将指定文件按响应结果输出
    public void finish(File file) throws IOException;

    //将文件按指定文件名输出
    public void finish(final String filename, File file) throws IOException;

    //HttpResponse回收时回调的监听方法
    public void recycleListener(BiConsumer<HttpRequest, HttpResponse> recycleListener);

}
                

  . WebSocket 对象

public abstract class WebSocket<G, T> {

    //给自身发送消息, 消息类型是String或byte[]或可JavaBean对象  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> send(Object message);

    //给自身发送消息, 消息类型是String或byte[]或可JavaBean对象  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> send(Object message, boolean last);

    //给自身发送消息, 消息类型是JavaBean对象  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> send(JsonConvert convert, Object message);

    //给自身发送消息, 消息类型是JavaBean对象  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> send(JsonConvert convert, Object message, boolean last);

    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> sendMessage(Object message, G... userids);

    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> sendMessage(Object message, boolean last, G... userids);

    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> broadcastMessage(final Object message);

    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> broadcastMessage(final Object message, boolean last);

    //获取用户在线的SNCP节点地址列表,不是分布式则返回元素数量为1,且元素值为null的列表
    public CompletableFuture<Collection<InetSocketAddress>> getRpcNodeAddresses(final Serializable userid);

    //获取在线用户的详细连接信息
    public CompletableFuture<Map<InetSocketAddress, List<String>>> getRpcNodeWebSocketAddresses(final Serializable userid);

    //发送PING消息  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> sendPing();

    //发送PING消息,附带其他信息  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> sendPing(byte[] data);

    //发送PONG消息,附带其他信息  返回结果0表示成功,非0表示错误码
    public CompletableFuture<Integer> sendPong(byte[] data);


    //获取指定userid的WebSocket数组, 没有返回null  此方法用于单用户多连接模式
    protected Stream<WebSocket> getLocalWebSockets(G userid);


    //获取指定userid的WebSocket数组, 没有返回null 此方法用于单用户单连接模式
    protected WebSocket findLocalWebSocket(G userid);


    //获取当前进程节点所有在线的WebSocket
    protected Collection<WebSocket> getLocalWebSockets();


    //返回sessionid, null表示连接不合法或异常,默认实现是request.sessionid(true),通常需要重写该方法
    protected CompletableFuture<String> onOpen(final HttpRequest request);


    //创建userid, null表示异常, 必须实现该方法
    protected abstract G createUserid();

    //标记为@WebSocketBinary才需要重写此方法
    public void onRead(AsyncConnection channel);

    //WebSokcet连接成功后的回调方法
    public void onConnected();

    //ping后的回调方法
    public void onPing(byte[] bytes);

    //pong后的回调方法
    public void onPong(byte[] bytes);

    //接收到消息的回调方法
    public void onMessage(T message, boolean last);

    //接收到二进制消息的回调方法
    public void onMessage(byte[] bytes, boolean last);

    //关闭的回调方法,调用此方法时WebSocket已经被关闭
    public void onClose(int code, String reason);

    //获取当前WebSocket下的属性
    public T getAttribute(String name);

    //移出当前WebSocket下的属性
    public T removeAttribute(String name);

    //给当前WebSocket下的增加属性
    public void setAttribute(String name, Object value);

    //获取当前WebSocket所属的userid
    public G userid();

    //获取当前WebSocket的会话ID, 不会为null
    public Serializable getSessionid();

    //获取客户端直接地址, 当WebSocket连接是由代理服务器转发的,则该值固定为代理服务器的IP地址
    public SocketAddress getRemoteAddress();

    //获取客户端真实地址 同 HttpRequest.getRemoteAddr()
    public String getRemoteAddr();

    //获取WebSocket创建时间
    public long getCreatetime();

    //获取最后一次发送消息的时间
    public long getLastSendTime();

    //显式地关闭WebSocket
    public void close();
}
                

SNCP 协议

        SNCP(Service Node Communicate Protocol)协议是Redkale独有的一种传输协议,用于进程之间的通信,即请求方的远程模式Service与响应方的Service之间的通信。是RPC(远程过程调用协议)的同类型协议,主要区别在于Redkale里SNCP几乎是透明的,写一个普通的Service通过配置即可实现远程调用,而不需要专门针对远程写接口。SNCP服务的配置与HTTP差不多,只是SNCP不需要Servlet,SncpServlet是通过Service动态生成的。
        SNCP的数据包分包头和包体。包头描述请求的Service信息,请求包的包体描述参数的BSON值,响应包的包体描述回调的参数对象和结果对象的BSON值
    包头长度固定,其结构如下:

字 段占用字节数描 述
序列号8请求的唯一序列号
包头长度2值固定为60
Service版本号4值为Service.version()
Service资源hash值16Service资源名class.getName():@Resource.name()的MD5值
Service方法hash值16Service方法method.toString()的MD5值
发送方地址6前4字节为地址,后2字节为端口号
包体长度4整个包体的长度
结果码4请求方的值固定为0,响应方的值视为错误码,为0表示成功,非0为失败。

    包体数据结构为 ([参数序号][参数BSON值])* N + [0][结果对象BSON]。 其中参数序号从1开始,只有当Service的方法存在@RpcCall回调才会有参数BSON值,序号为0表示为结果对象的BSON值。若方法为void返回类型,则不存在结果对象BSON值

自定义协议

        协议的网络框架包含五种对象:
                Context     : 协议上下文对象
                Request    : 服务请求对象
                Response : 服务响应对象
                Servlet      : 服务逻辑处理对象
                Server       : 服务监听对象
        通常自定义协议需要继承上面五种对象类,同时为了让Redkale能识别和加载自定义协议服务需要继承 org.redkale.boot.NodeServer 并指明 @NodeProtocol,实现可以参考 基于SOCKS5协议的反向代理服务器