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类。
一个典型的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连接。且有两种模式:
协议上符合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被关闭后回调此方法。
此模式下 以上方法都应该被重载。
实现一个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":2000001,"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) {
//以request中的sessionid字符串作为WebSocket的sessionid
return CompletableFuture.completedFuture(request.getSessionid(false));
}
};
}
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;
//设置当前用户信息, 通常在HttpServlet.preExecute方法里设置currentUser
//数据类型由@HttpUserType指定
public <T> HttpRequest setCurrentUser(T user);
//获取当前用户信息 数据类型由@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 AnyValue getHeaders();
//将请求Header转换成Map
public Map<String, String> getHeadersToMap(Map<String, String> map);
//获取所有的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 AnyValue getParameters();
//将请求参数转换成Map
public Map<String, String> getParametersToMap(Map<String, String> map);
//将请求参数转换成String, 字符串格式为: bean1={}&id=13&name=xxx
//不会返回null,没有参数返回空字符串
public String getParametersToString();
//将请求参数转换成String, 字符串格式为: prefix + bean1={}&id=13&name=xxx
//拼接前缀, 如果无参数,返回的字符串不会含有拼接前缀
//不会返回null,没有参数返回空字符串
public String getParametersToString(String prefix);
//获取所有参数名
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);
//创建CompletionHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出
public CompletionHandler createAsyncHandler();
//传入的CompletionHandler子类必须是public,且保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数
public <H extends CompletionHandler> H createAsyncHandler(Class<H> handlerClass);
//获取ByteBuffer生成器
public Supplier<ByteBuffer> getBufferSupplier();
//设置状态码
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, CompletionHandler<Integer, A> handler);
//异步输出指定内容
public <A> void sendBody(ByteBuffer[] buffers, A attachment, CompletionHandler<Integer, A> handler);
//关闭HTTP连接,如果是keep-alive则不强制关闭
public void finish();
//强制关闭HTTP连接
public void finish(boolean kill);
//将对象以JSON格式输出
public void finishJson(Object obj);
//将对象数组用Map的形式以JSON格式输出
//例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3}
public void finishMapJson(final Object... objs);
//将对象以JSON格式输出
public void finishJson(JsonConvert convert, Object obj);
//将对象数组用Map的形式以JSON格式输出
//例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3}
public void finishMapJson(final JsonConvert convert, final Object... objs);
//将对象以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);
//将结果对象输出
public void finish(final Object obj);
//将结果对象输出
public void finish(final Convert convert, final Object obj);
//将结果对象输出
public void finish(final Convert convert, final Type type, final Object obj);
//以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);
//给自身发送消息, 消息类型是key-value键值对 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMap(Object... messages);
//给自身发送消息, 消息类型是String或byte[]或可JavaBean对象 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> send(Object message, boolean last);
//给自身发送消息, 消息类型是key-value键值对 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMap(boolean last, Object... messages);
//给自身发送消息, 消息类型是JavaBean对象 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> send(Convert convert, Object message);
//给自身发送消息, 消息类型是JavaBean对象 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> send(Convert 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, Stream<G> userids);
//给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMessage(Convert convert, Object message, G... userids);
//给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMessage(Convert convert, Object message, Stream<G> userids);
//给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMessage(Object message, boolean last, G... userids);
//给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMessage(Object message, boolean last, Stream<G> userids);
//给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMessage(Convert convert, Object message, boolean last, G... userids);
//给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> sendMessage(Convert convert, Object message, boolean last, Stream<G> userids);
//给所有人广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final Object message);
//给所有人广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message);
//给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final WebSocketRange wsrange, final Object message);
//给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final WebSocketRange wsrange, final Convert convert, final Object message);
//给所有人广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final Object message, boolean last);
//给所有人广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message, boolean last);
//给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(final WebSocketRange wsrange, final Object message, boolean last);
//给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
public CompletableFuture<Integer> broadcastMessage(WebSocketRange wsrange, Convert convert, final Object message, boolean last);
//给指定userid的WebSocket节点发送操作
public CompletableFuture<Integer> sendAction(final WebSocketAction action, Serializable... userids);
//广播操作, 给所有人发操作指令
public CompletableFuture<Integer> broadcastAction(final WebSocketAction action);
//获取用户在线的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);
//强制关闭用户的所有WebSocket
public CompletableFuture<Integer> forceCloseWebSocket(Serializable userid);
//更改本WebSocket的userid
public CompletableFuture<Void> changeUserid(final G newuserid);
//获取指定userid的WebSocket数组, 没有返回null 此方法用于单用户多连接模式
protected Stream<WebSocket> getLocalWebSockets(G userid);
//获取指定userid的WebSocket数组, 没有返回null 此方法用于单用户单连接模式
protected WebSocket findLocalWebSocket(G userid);
//获取当前进程节点所有在线的WebSocket
protected Collection<WebSocket> getLocalWebSockets();
//获取ByteBuffer资源池
protected Supplier<ByteBuffer> getByteBufferSupplier();
//返回sessionid, null表示连接不合法或异常,默认实现是request.sessionid(true),通常需要重写该方法
protected CompletableFuture<String> onOpen(final HttpRequest request);
//创建userid, null表示异常, 必须实现该方法
protected abstract CompletableFuture<G> createUserid();
//WebSocket.broadcastMessage时的过滤条件
protected boolean predicate(WebSocketRange wsrange);
//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(String 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 getUserid();
//获取当前WebSocket的会话ID, 不会为null
public Serializable getSessionid();
//获取客户端直接地址, 当WebSocket连接是由代理服务器转发的,则该值固定为代理服务器的IP地址
public SocketAddress getRemoteAddress();
//获取客户端真实地址 同 HttpRequest.getRemoteAddr()
public String getRemoteAddr();
//获取WebSocket创建时间
public long getCreatetime();
//获取最后一次发送消息的时间
public long getLastSendTime();
//获取最后一次读取消息的时间
public long getLastReadTime();
//获取最后一次ping的时间
public long getLastPingTime();
//显式地关闭WebSocket
public void close();
//WebSocket是否已关闭
public boolean isClosed();
}
SNCP 协议
SNCP(Service Node Communicate Protocol)协议是Redkale独有的一种传输协议,用于进程之间的通信,即请求方的远程模式Service与响应方的Service之间的通信。是RPC(远程过程调用协议)的同类型协议,主要区别在于Redkale里SNCP几乎是透明的,写一个普通的Service通过配置即可实现远程调用,而不需要专门针对远程写接口。SNCP服务的配置与HTTP差不多,只是SNCP不需要Servlet,SncpServlet是通过Service动态生成的。
SNCP的数据包分包头和包体。包头描述请求的Service信息,请求包的包体描述参数的PROTOBUF值。
包头长度固定,其结构如下:
字 段 | 占用字节数 | 描 述 |
---|---|---|
序列号 | 8 | 请求的唯一序列号 |
包头长度 | 2 | 值固定为60 |
Service版本号 | 4 | 值为Service.version() |
Service资源hash值 | 16 | Service资源名class.getName():@Resource.name()的MD5值 |
Service方法hash值 | 16 | Service方法method.toString()的MD5值 |
发送方地址 | 6 | 前4字节为地址,后2字节为端口号 |
包体长度 | 4 | 整个包体的长度 |
结果码 | 4 | 请求方的值固定为0,响应方的值视为错误码,为0表示成功,非0为失败。 |
包体数据结构为 ([参数序号][参数PROTOBUF值])* N + [0][结果对象PROTOBUF]。 其中参数序号从1开始,只有当Service的方法存在@RpcCall回调才会有参数PROTOBUF值,序号为0表示为结果对象的PROTOBUF值。若方法为void返回类型,则不存在结果对象PROTOBUF值。
自定义协议
协议的网络框架包含五种对象:
Context : 协议上下文对象
Request : 服务请求对象
Response : 服务响应对象
Servlet : 服务逻辑处理对象
Server : 服务监听对象
通常自定义协议需要继承上面五种对象类,同时为了让Redkale能识别和加载自定义协议服务需要继承 org.redkale.boot.NodeServer 并指明 @NodeProtocol,实现可以参考 基于SOCKS5协议的反向代理服务器