Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ec2bf211a | ||
|
|
b3c54e4db5 | ||
|
|
52838f04a9 | ||
|
|
fa5bd95a2b | ||
|
|
b2e73d378c | ||
|
|
6895b31ad0 | ||
|
|
56637ff7ef | ||
|
|
40b46f9c7f | ||
|
|
0aed26652d | ||
|
|
14238a0203 | ||
|
|
369a70e857 | ||
|
|
6ede9b0f31 | ||
|
|
6c039dc8f4 | ||
|
|
1e69a6755f | ||
|
|
f6c617574c | ||
|
|
2291beb5e7 | ||
|
|
d8e091f888 | ||
|
|
6a42ae7570 | ||
|
|
756e4634d9 | ||
|
|
06773ccdc0 | ||
|
|
0da74be2fa | ||
|
|
1d1b732a74 | ||
|
|
ab90c80785 | ||
|
|
6cd232efd2 | ||
|
|
96e4b8834d | ||
|
|
7ef5ddfd46 | ||
|
|
73a973e0ed | ||
|
|
e0411a94f6 | ||
|
|
054e853074 | ||
|
|
88672b5522 | ||
|
|
0a60b81a98 | ||
|
|
37f8208b1b | ||
|
|
a8b9cc9753 | ||
|
|
a99c7d3454 | ||
|
|
b88987dd98 | ||
|
|
54f4f8e35d | ||
|
|
97670261e6 | ||
|
|
82a2a513f5 | ||
|
|
174a8a2a0c | ||
|
|
92b3d0bbd4 | ||
|
|
24c90b015a | ||
|
|
2742e935cb | ||
|
|
b7770c89b8 | ||
|
|
779a9cca4d | ||
|
|
493c27beb5 | ||
|
|
c68d988d51 | ||
|
|
ceeb924d4d | ||
|
|
4d0a16d35d | ||
|
|
1e60adf5bb | ||
|
|
59b08684a8 | ||
|
|
c326b7ed05 | ||
|
|
d9bf5c8412 | ||
|
|
39d4e6405f | ||
|
|
03bcea30df | ||
|
|
27b67cde0e | ||
|
|
31fca5630b | ||
|
|
3504d735c1 | ||
|
|
74f554fe33 | ||
|
|
b02d4e6731 | ||
|
|
23fd72b116 | ||
|
|
4cd1b10c35 | ||
|
|
71c0763304 | ||
|
|
0ea4ddb5eb | ||
|
|
9bc266ca61 | ||
|
|
b202de4916 | ||
|
|
7d26198e88 | ||
|
|
d98e249fd7 | ||
|
|
7a8b6cac9d | ||
|
|
822ac078b5 | ||
|
|
5e3718af19 | ||
|
|
713fdefb56 | ||
|
|
804e0e27e7 | ||
|
|
2653808f8f | ||
|
|
ba12df2cba | ||
|
|
d2bfa9ab56 | ||
|
|
0fb108bd9b | ||
|
|
145527db38 | ||
|
|
d7c50532cf | ||
|
|
6562ac0a2b | ||
|
|
6f4c9dca48 | ||
|
|
f18ef4f94d | ||
|
|
4786e17243 | ||
|
|
96beb9cef4 | ||
|
|
a59bf92ee7 | ||
|
|
cdc3dbf9ea | ||
|
|
a957a18e32 | ||
|
|
95c53f99e0 | ||
|
|
03ac849451 | ||
|
|
d9946ceb64 | ||
|
|
fda9c30dc4 | ||
|
|
205162ce38 | ||
|
|
f5379df63b | ||
|
|
859e56af4d | ||
|
|
a29cc94f32 | ||
|
|
9e6840f5cb | ||
|
|
52d559ea4a | ||
|
|
95a2b752af | ||
|
|
33da94960c | ||
|
|
dee2002cf3 | ||
|
|
d9a318bba8 | ||
|
|
05925b4f78 | ||
|
|
45fe7cb3e9 | ||
|
|
58271c803b | ||
|
|
88942c61b5 | ||
|
|
88c4824c4f | ||
|
|
62b0be802e | ||
|
|
cdec316312 | ||
|
|
8dcb999444 | ||
|
|
ee460b4196 | ||
|
|
d049b3f9ea | ||
|
|
c244c4edab | ||
|
|
7c533ce8d3 | ||
|
|
f68686114d | ||
|
|
0859dee201 | ||
|
|
46ccd83acc | ||
|
|
d5e44787c0 | ||
|
|
4029b09d81 | ||
|
|
e939241a8c | ||
|
|
cee2c47d9a | ||
|
|
ccceaa2607 | ||
|
|
358a50ecc7 | ||
|
|
6aabae849d | ||
|
|
60cbd9b37d | ||
|
|
df2ee8273d | ||
|
|
8bbee0aff8 | ||
|
|
01ea7f07f5 | ||
|
|
ba928b389b | ||
|
|
39ba0f86f6 | ||
|
|
4d523d1ca4 | ||
|
|
2f60fe795c | ||
|
|
c3560b0ef0 | ||
|
|
804b4dc07d | ||
|
|
bb87774ba9 | ||
|
|
f76ab977d1 | ||
|
|
8003252cf3 | ||
|
|
95c0d0f5cf | ||
|
|
3dcc6ea28f | ||
|
|
85c708b075 | ||
|
|
278c51e26b | ||
|
|
7c0e60d191 | ||
|
|
d1d10f90b9 | ||
|
|
3b601979f4 | ||
|
|
69cc09e76d | ||
|
|
ef28e32e04 | ||
|
|
9954eaf469 | ||
|
|
5ce5f53ed8 | ||
|
|
8e36a7b450 | ||
|
|
f15754386b | ||
|
|
6cc90ac7fe | ||
|
|
3c7f10d657 | ||
|
|
21c84865b9 | ||
|
|
853e823a8d | ||
|
|
6d74355fc0 | ||
|
|
987914e748 | ||
|
|
6e66ee0c99 | ||
|
|
4f7145319f | ||
|
|
ab2656cde6 | ||
|
|
27468d9f0c | ||
|
|
bb8462af2a | ||
|
|
e1df150a37 | ||
|
|
bee4f31323 | ||
|
|
df3a1ef84d | ||
|
|
242b13fff0 | ||
|
|
c3cc9de5b5 | ||
|
|
72e16b88f7 | ||
|
|
f1f0227dca | ||
|
|
63fe26f9cb | ||
|
|
02fcb7b089 | ||
|
|
9db9db4f5e | ||
|
|
e5ce250304 | ||
|
|
a5756c0b4d | ||
|
|
0be3d8a4fa | ||
|
|
017ab1ae84 | ||
|
|
c622e2437d | ||
|
|
3a1f20d438 | ||
|
|
2a9b64e53e | ||
|
|
225745a282 | ||
|
|
83680c46e8 | ||
|
|
7484b80fb2 | ||
|
|
77674ac8d2 | ||
|
|
564067602f | ||
|
|
26778c58c9 | ||
|
|
77cd24fa42 | ||
|
|
4feea0e784 | ||
|
|
681faa415f | ||
|
|
3b54484832 | ||
|
|
fa2513d934 | ||
|
|
5e2be5e926 | ||
|
|
4c071b0a1d | ||
|
|
9e7949d9eb | ||
|
|
29843a9812 | ||
|
|
48a08f27a7 | ||
|
|
4f1a0849ec | ||
|
|
6676c3fd37 | ||
|
|
6daa45ff05 | ||
|
|
28f95a89df | ||
|
|
44d11dae34 | ||
|
|
a1b39ba99b | ||
|
|
abcfb8f10c | ||
|
|
33f49c7632 | ||
|
|
d800a33ded | ||
|
|
f587e13bdc |
@@ -11,27 +11,16 @@
|
||||
</properties>
|
||||
-->
|
||||
</resources>
|
||||
|
||||
|
||||
<server protocol="HTTP" host="0.0.0.0" port="6060" root="root">
|
||||
<!--
|
||||
<request>
|
||||
<remoteaddr value="request.headers.X-RemoteAddress"/>
|
||||
</request>
|
||||
<response>
|
||||
<addheader name="X-Node" value="system.property.APP_NODE" />
|
||||
<addheader name="Access-Control-Allow-Origin" value="request.headers.Origin" />
|
||||
<setheader name="Access-Control-Allow-Credentials" value="true"/>
|
||||
</response>
|
||||
-->
|
||||
|
||||
<server protocol="HTTP" host="0.0.0.0" port="6060" root="root">
|
||||
|
||||
<services autoload="true"/>
|
||||
<servlets path="/pipes" autoload="true" >
|
||||
<!--
|
||||
<resource-servlet>
|
||||
<caches limit="0"/>
|
||||
<rewrite type="location" match="^/([^-]+)(-[^-\.]+)+\.html(.*)" forward="/$1.html"/>
|
||||
</resource-servlet>
|
||||
-->
|
||||
</servlets>
|
||||
|
||||
<!-- base指定的自定义HttpServlet子类必须标记@HttpUserType, 不设置base则视为没有当前用户信息设置 -->
|
||||
<rest path="/pipes" base="org.redkale.net.http.HttpServlet"/>
|
||||
|
||||
<servlets path="/pipes" autoload="true" />
|
||||
|
||||
</server>
|
||||
|
||||
</application>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
handlers = java.util.logging.ConsoleHandler
|
||||
|
||||
############################################################
|
||||
.level = FINE
|
||||
.level = FINER
|
||||
|
||||
java.level = INFO
|
||||
javax.level = INFO
|
||||
@@ -12,12 +12,13 @@ sun.level = INFO
|
||||
jdk.level = INFO
|
||||
|
||||
|
||||
java.util.logging.FileHandler.level = FINE
|
||||
java.util.logging.FileHandler.level = FINER
|
||||
#10M
|
||||
java.util.logging.FileHandler.limit = 10485760
|
||||
java.util.logging.FileHandler.count = 10000
|
||||
java.util.logging.FileHandler.encoding = UTF-8
|
||||
java.util.logging.FileHandler.pattern = ${APP_HOME}/logs-%m/log-%d.log
|
||||
java.util.logging.FileHandler.unusual = ${APP_HOME}/logs-%m/log-warnerr-%u.log
|
||||
java.util.logging.FileHandler.append = true
|
||||
|
||||
java.util.logging.ConsoleHandler.level = FINE
|
||||
java.util.logging.ConsoleHandler.level = FINER
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
所有服务所需的资源
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
【节点全局唯一】
|
||||
transport节点只能有一个,用于配置所有Transport的池参数,没配置该节点将自动创建一个。
|
||||
@@ -38,8 +39,9 @@
|
||||
bufferPoolSize: ByteBuffer池的大小,默认: <group>节点数*CPU核数*8
|
||||
-->
|
||||
<transport bufferCapacity="8K" bufferPoolSize="32" threads="32"/>
|
||||
|
||||
<!--
|
||||
一个组包含多个NODE, 同一Service服务可以由多个进程提供,这些进程称为一个GROUP,且同一GROUP内的进程必须在同一机房或局域网内
|
||||
一个组包含多个node, 同一Service服务可以由多个进程提供,这些进程称为一个GROUP,且同一GROUP内的进程必须在同一机房或局域网内
|
||||
一个group节点对应一个 Transport 对象。
|
||||
name: 服务组ID,长度不能超过11个字节. 默认为空字符串。 注意: name不能包含$符号。
|
||||
protocol:值范围:UDP TCP, 默认TCP
|
||||
@@ -57,6 +59,18 @@
|
||||
-->
|
||||
<node addr="127.0.0.1" port="7070"/>
|
||||
</group>
|
||||
|
||||
<!--
|
||||
全局的数据源设置, 可以是CacheSource、DataSource, JDBC的DataSource通常通过persistence.xml配置,此处多用于CacheSource的配置
|
||||
name: 资源名,用于依赖注入。
|
||||
value:类名,必须是CacheSource或DataSource的子类,且必须实现Service接口。
|
||||
groups: 指定groups。
|
||||
xxx: 其他属性与子节点通过Service.init方法传入的AnyValue获取。
|
||||
-->
|
||||
<source name="redis" value="org.redkalex.cache.RedisCacheSource" xxx="16">
|
||||
<node addr="127.0.0.1" port="7070"/>
|
||||
</source>
|
||||
|
||||
<!--
|
||||
【节点全局唯一】
|
||||
全局的参数配置, 可以通过@Resource(name="property.xxxxxx") 进行注入<property>的信息, 被注解的字段类型只能是String、primitive class
|
||||
@@ -82,8 +96,8 @@
|
||||
|
||||
</resources>
|
||||
<!--
|
||||
protocol: required server所启动的协议,Redkale内置的有HTTP、SNCP,SNCP使用TCP实现;
|
||||
name: 服务的名称,用于监控识别,一个配置文件中的server.name不能重复,命名规则: 字母、数字、下划线、减号
|
||||
protocol: required server所启动的协议,Redkale内置的有HTTP、SNCP、WATCH,SNCP使用TCP实现;
|
||||
name: 服务的名称,用于监控识别,一个配置文件中的server.name不能重复,命名规则: 字母、数字、下划线
|
||||
host: 服务所占address , 默认: 0.0.0.0
|
||||
port: required 服务所占端口
|
||||
root: 如果是web类型服务,则包含页面 默认:{APP_HOME}/root
|
||||
@@ -93,7 +107,7 @@
|
||||
backlog: 默认10K
|
||||
threads: 线程总数, 默认: CPU核数*16
|
||||
maxbody: request.body最大值, 默认: 64K
|
||||
bufferCapacity: ByteBuffer的初始化大小, 默认: 8K; 如果是HTTP协议则默认: 16K + 8B (兼容HTTP 2.0)
|
||||
bufferCapacity: ByteBuffer的初始化大小, 默认: 8K; 如果是HTTP协议则默认: 16K + 16B (兼容HTTP 2.0、WebSocket)
|
||||
bufferPoolSize: ByteBuffer池的大小,默认: CPU核数*512
|
||||
responsePoolSize: Response池的大小,默认: CPU核数*256
|
||||
readTimeoutSecond: 读操作超时秒数, 默认0, 表示永久不超时
|
||||
@@ -131,21 +145,46 @@
|
||||
</service>
|
||||
</services>
|
||||
|
||||
<!--
|
||||
加载所有的Filter服务;
|
||||
autoload="true" 默认值.
|
||||
autoload="false" 需要显著的指定Filter类
|
||||
includes: 当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
excludes: 当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
-->
|
||||
<filters autoload="true" includes="" excludes="">
|
||||
|
||||
<!-- 显著加载指定的Filter类 -->
|
||||
<filter value="com.xxx.XXX1Filter"/>
|
||||
|
||||
<!-- 给Filter增加配置属性 -->
|
||||
<filter value="com.xxx.XXX12Filter">
|
||||
<!-- property节点值在 public void init(AnyValue conf) 方法中可以通过 AnyValue properties = conf.getAnyValue("properties");获取 -->
|
||||
<property name="xxxxxx" value="XXXXXXXX"/>
|
||||
<property name="xxxxxx" value="XXXXXXXX"/>
|
||||
</filter>
|
||||
</filters>
|
||||
|
||||
<!--
|
||||
REST的核心配置项
|
||||
当Server为HTTP协议时, rest节点才有效。存在[rest]节点则Server启动时会加载REST服务, 节点可以多个
|
||||
base: REST服务的BaseServlet,必须是 org.redkale.net.http.RestHttpServlet 的子类,该属性值默认值为 org.redkale.net.http.DefaultRestServlet。
|
||||
当Server为HTTP协议时, rest节点才有效。存在[rest]节点则Server启动时会加载REST服务, 节点可以多个,(WATCH协议不需要设置,系统会自动生成)
|
||||
path: servlet的ContextPath前缀 默认为空
|
||||
base: REST服务的BaseServlet,必须是 org.redkale.net.http.HttpServlet 的子类,且子类必须标记@HttpUserType。
|
||||
autoload:默认值"true" 默认值. 加载当前server所能使用的Servce对象;
|
||||
mustsign:默认值"true" 是否只加载标记为RestService的Service类,默认只加载标记RestService且ignore=false的Service
|
||||
includes:当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
excludes:当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
-->
|
||||
<rest base="org.redkale.net.http.DefaultRestServlet" mustsign="true" autoload="true" includes="" excludes="">
|
||||
<rest path="/pipes" base="org.redkale.net.http.HttpServlet" autoload="true" includes="" excludes="">
|
||||
<!--
|
||||
value: Service类名,列出的表示必须被加载的Service对象
|
||||
ignore: 是否忽略,设置为true则不会加载该Service对象,默认值为false
|
||||
-->
|
||||
<service value="com.xxx.XXXXService"/>
|
||||
<!--
|
||||
value: WebSocket类名,列出的表示必须被加载且标记为@RestWebSocket的WebSocket对象
|
||||
ignore: 是否忽略,设置为true则不会加载该RestWebSocket对象,默认值为false
|
||||
-->
|
||||
<websocket value="com.xxx.XXXXRestWebSocket"/>
|
||||
</rest>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -17,7 +17,7 @@ java.util.logging.FileHandler.count = 100
|
||||
java.util.logging.FileHandler.encoding = UTF-8
|
||||
java.util.logging.FileHandler.pattern = ${APP_HOME}/logs-%m/log-%u.log
|
||||
#java.util.logging.FileHandler.unusual \u5c5e\u6027\u8868\u793a\u5c06 WARNING\u3001SEVERE \u7ea7\u522b\u7684\u65e5\u5fd7\u590d\u5236\u5199\u5165\u5355\u72ec\u7684\u6587\u4ef6\u4e2d
|
||||
#java.util.logging.FileHandler.unusual = ${APP_HOME}/logs-%m/log-error-%u.log
|
||||
java.util.logging.FileHandler.unusual = ${APP_HOME}/logs-%m/log-warnerr-%u.log
|
||||
java.util.logging.FileHandler.append = true
|
||||
|
||||
#java.util.logging.ConsoleHandler.level = FINE
|
||||
|
||||
@@ -23,17 +23,18 @@ import org.redkale.util.*;
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class ApiDocs extends HttpBaseServlet {
|
||||
public final class ApiDocsService {
|
||||
|
||||
private final Application app; //Application全局对象
|
||||
|
||||
public ApiDocs(Application app) {
|
||||
public ApiDocsService(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
List<Map> serverList = new ArrayList<>();
|
||||
|
||||
Field __prefix = HttpServlet.class.getDeclaredField("_prefix");
|
||||
__prefix.setAccessible(true);
|
||||
Map<String, Map<String, Map<String, Object>>> typesmap = new LinkedHashMap<>();
|
||||
for (NodeServer node : app.servers) {
|
||||
if (!(node instanceof NodeHttpServer)) continue;
|
||||
@@ -51,7 +52,7 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
continue;
|
||||
}
|
||||
final Map<String, Object> servletmap = new LinkedHashMap<>();
|
||||
String prefix = _prefix(servlet);
|
||||
String prefix = (String) __prefix.get(servlet);
|
||||
String[] urlregs = ws.value();
|
||||
if (prefix != null && !prefix.isEmpty()) {
|
||||
for (int i = 0; i < urlregs.length; i++) {
|
||||
@@ -72,14 +73,14 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
if (Modifier.isAbstract(clz.getModifiers())) break;
|
||||
for (Method method : clz.getMethods()) {
|
||||
if (method.getParameterCount() != 2) continue;
|
||||
WebMapping action = method.getAnnotation(WebMapping.class);
|
||||
HttpMapping action = method.getAnnotation(HttpMapping.class);
|
||||
if (action == null) continue;
|
||||
if (!action.inherited() && selfClz != clz) continue; //忽略不被继承的方法
|
||||
final Map<String, Object> mappingmap = new LinkedHashMap<>();
|
||||
if (actionurls.contains(action.url())) continue;
|
||||
mappingmap.put("url", prefix + action.url());
|
||||
actionurls.add(action.url());
|
||||
mappingmap.put("auth", method.getAnnotation(AuthIgnore.class) == null);
|
||||
mappingmap.put("auth", action.auth());
|
||||
mappingmap.put("actionid", action.actionid());
|
||||
mappingmap.put("comment", action.comment());
|
||||
List<Map> paramsList = new ArrayList<>();
|
||||
@@ -112,7 +113,7 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
}
|
||||
fieldmap.put("primary", !filter && (field.getAnnotation(Id.class) != null));
|
||||
fieldmap.put("updatable", (filter || col == null || col.updatable()));
|
||||
if (servlet.getClass().getAnnotation(Rest.RestDynamic.class) != null) {
|
||||
if (servlet.getClass().getAnnotation(Rest.RestDyn.class) != null) {
|
||||
if (field.getAnnotation(RestAddress.class) != null) continue;
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
typesmap.put(rtype.getName(), typemap);
|
||||
}
|
||||
mappingmap.put("results", results);
|
||||
for (WebParam param : method.getAnnotationsByType(WebParam.class)) {
|
||||
for (HttpParam param : method.getAnnotationsByType(HttpParam.class)) {
|
||||
final Map<String, Object> parammap = new LinkedHashMap<>();
|
||||
final boolean isarray = param.type().isArray();
|
||||
final Class ptype = isarray ? param.type().getComponentType() : param.type();
|
||||
@@ -161,7 +162,7 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
fieldmap.put("primary", !filter && (field.getAnnotation(Id.class) != null));
|
||||
fieldmap.put("updatable", (filter || col == null || col.updatable()));
|
||||
|
||||
if (servlet.getClass().getAnnotation(Rest.RestDynamic.class) != null) {
|
||||
if (servlet.getClass().getAnnotation(Rest.RestDyn.class) != null) {
|
||||
if (field.getAnnotation(RestAddress.class) != null) continue;
|
||||
}
|
||||
|
||||
@@ -196,7 +197,7 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
if (doctemplate.isFile() && doctemplate.canRead()) {
|
||||
in = new FileInputStream(doctemplate);
|
||||
}
|
||||
if (in == null) in = ApiDocs.class.getResourceAsStream("apidoc-template.html");
|
||||
if (in == null) in = ApiDocsService.class.getResourceAsStream("apidoc-template.html");
|
||||
String content = Utility.read(in).replace("'${content}'", json);
|
||||
in.close();
|
||||
FileOutputStream outhtml = new FileOutputStream(new File(app.getHome(), "apidoc.html"));
|
||||
@@ -204,8 +205,4 @@ public class ApiDocs extends HttpBaseServlet {
|
||||
outhtml.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(int moduleid, int actionid, HttpRequest request, HttpResponse response, HttpServlet next) throws IOException {
|
||||
next.execute(request, response);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import org.redkale.net.TransportGroupInfo;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
@@ -15,18 +16,19 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.Resource;
|
||||
import javax.xml.parsers.*;
|
||||
import org.redkale.boot.ClassFilter.FilterEntry;
|
||||
import org.redkale.convert.bson.BsonFactory;
|
||||
import org.redkale.convert.json.JsonFactory;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.http.MimeType;
|
||||
import org.redkale.net.sncp.SncpClient;
|
||||
import org.redkale.net.sncp.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.source.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.WatchFactory;
|
||||
import org.redkale.watch.*;
|
||||
import org.w3c.dom.*;
|
||||
|
||||
/**
|
||||
@@ -35,8 +37,8 @@ import org.w3c.dom.*;
|
||||
* <pre>
|
||||
* 程序启动执行步骤:
|
||||
* 1、读取application.xml
|
||||
* 2、进行classpath扫描动态加载Service与Servlet
|
||||
* 3、优先加载所有SNCP协议的服务,再加载其他协议服务
|
||||
* 2、进行classpath扫描动态加载Service、WebSocket与Servlet
|
||||
* 3、优先加载所有SNCP协议的服务,再加载其他协议服务, 最后加载WATCH协议的服务
|
||||
* 4、最后进行Service、Servlet与其他资源之间的依赖注入
|
||||
* </pre>
|
||||
* <p>
|
||||
@@ -74,26 +76,20 @@ public final class Application {
|
||||
public static final String RESNAME_APP_ADDR = "APP_ADDR";
|
||||
|
||||
/**
|
||||
* 当前Service的IP地址+端口 类型: SocketAddress、InetSocketAddress、String
|
||||
* 当前Service所属的SNCP Server的地址 类型: SocketAddress、InetSocketAddress、String <br>
|
||||
*/
|
||||
public static final String RESNAME_SERVER_ADDR = "SERVER_ADDR";
|
||||
public static final String RESNAME_SNCP_ADDR = "SNCP_ADDR";
|
||||
|
||||
/**
|
||||
* 当前SNCP Server所属的组 类型: String
|
||||
* 当前Service所属的SNCP Server所属的组 类型: String<br>
|
||||
*/
|
||||
public static final String RESNAME_SERVER_GROUP = "SERVER_GROUP";
|
||||
public static final String RESNAME_SNCP_GROUP = "SNCP_GROUP";
|
||||
|
||||
/**
|
||||
* 当前Server的ROOT目录 类型:String、File、Path
|
||||
* "SERVER_ROOT" 当前Server的ROOT目录类型:String、File、Path
|
||||
*/
|
||||
public static final String RESNAME_SERVER_ROOT = Server.RESNAME_SERVER_ROOT;
|
||||
|
||||
//每个地址对应的Group名
|
||||
final Map<InetSocketAddress, String> globalNodes = new HashMap<>();
|
||||
|
||||
//协议地址的Group集合
|
||||
final Map<String, GroupInfo> globalGroups = new HashMap<>();
|
||||
|
||||
//本地IP地址
|
||||
final InetAddress localAddress;
|
||||
|
||||
@@ -106,14 +102,8 @@ public final class Application {
|
||||
//NodeServer 资源
|
||||
final List<NodeServer> servers = new CopyOnWriteArrayList<>();
|
||||
|
||||
//传输端的ByteBuffer对象池
|
||||
final ObjectPool<ByteBuffer> transportBufferPool;
|
||||
|
||||
//传输端的线程池
|
||||
final ExecutorService transportExecutor;
|
||||
|
||||
//传输端的ChannelGroup
|
||||
final AsynchronousChannelGroup transportChannelGroup;
|
||||
//传输端的TransportFactory
|
||||
final TransportFactory transportFactory;
|
||||
|
||||
//全局根ResourceFactory
|
||||
final ResourceFactory resourceFactory = ResourceFactory.root();
|
||||
@@ -124,13 +114,15 @@ public final class Application {
|
||||
//临时计数器
|
||||
CountDownLatch servicecdl; //会出现两次赋值
|
||||
|
||||
//是否启动了WATCH协议服务
|
||||
boolean watching;
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//是否用于main方法运行
|
||||
private final boolean singletonrun;
|
||||
|
||||
//根WatchFactory
|
||||
private final WatchFactory watchFactory = WatchFactory.root();
|
||||
|
||||
//private final WatchFactory watchFactory = WatchFactory.root();
|
||||
//进程根目录
|
||||
private final File home;
|
||||
|
||||
@@ -143,6 +135,9 @@ public final class Application {
|
||||
//Server启动的计数器,用于确保所有Server都启动完后再进行下一步处理
|
||||
private final CountDownLatch serversLatch;
|
||||
|
||||
//根ClassLoader
|
||||
private final NodeClassLoader classLoader;
|
||||
|
||||
private Application(final AnyValue config) {
|
||||
this(false, config);
|
||||
}
|
||||
@@ -232,7 +227,7 @@ public final class Application {
|
||||
}
|
||||
this.logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
this.serversLatch = new CountDownLatch(config.getAnyValues("server").length + 1);
|
||||
logger.log(Level.INFO, "------------------------------- Redkale -------------------------------");
|
||||
logger.log(Level.INFO, "------------------------------- Redkale " + Redkale.getDotedVersion() + " -------------------------------");
|
||||
//------------------配置 <transport> 节点 ------------------
|
||||
ObjectPool<ByteBuffer> transportPool = null;
|
||||
ExecutorService transportExec = null;
|
||||
@@ -244,8 +239,8 @@ public final class Application {
|
||||
if (groupsize > 0 && transportConf == null) transportConf = new DefaultAnyValue();
|
||||
if (transportConf != null) {
|
||||
//--------------transportBufferPool-----------
|
||||
AtomicLong createBufferCounter = watchFactory == null ? new AtomicLong() : watchFactory.createWatchNumber(Transport.class.getSimpleName() + ".Buffer.creatCounter");
|
||||
AtomicLong cycleBufferCounter = watchFactory == null ? new AtomicLong() : watchFactory.createWatchNumber(Transport.class.getSimpleName() + ".Buffer.cycleCounter");
|
||||
AtomicLong createBufferCounter = new AtomicLong();
|
||||
AtomicLong cycleBufferCounter = new AtomicLong();
|
||||
final int bufferCapacity = transportConf.getIntValue("bufferCapacity", 8 * 1024);
|
||||
final int bufferPoolSize = transportConf.getIntValue("bufferPoolSize", groupsize * Runtime.getRuntime().availableProcessors() * 8);
|
||||
final int threads = transportConf.getIntValue("threads", groupsize * Runtime.getRuntime().availableProcessors() * 8);
|
||||
@@ -271,17 +266,25 @@ public final class Application {
|
||||
logger.log(Level.INFO, Transport.class.getSimpleName() + " configure bufferCapacity = " + bufferCapacity + "; bufferPoolSize = " + bufferPoolSize + "; threads = " + threads + ";");
|
||||
}
|
||||
}
|
||||
this.transportBufferPool = transportPool;
|
||||
this.transportExecutor = transportExec;
|
||||
this.transportChannelGroup = transportGroup;
|
||||
this.transportFactory = new TransportFactory(transportExec, transportPool, transportGroup);
|
||||
this.classLoader = new NodeClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
Thread.currentThread().setContextClassLoader(this.classLoader);
|
||||
}
|
||||
|
||||
public ResourceFactory getResourceFactory() {
|
||||
return resourceFactory;
|
||||
}
|
||||
|
||||
public WatchFactory getWatchFactory() {
|
||||
return watchFactory;
|
||||
public TransportFactory getTransportFactory() {
|
||||
return transportFactory;
|
||||
}
|
||||
|
||||
public NodeClassLoader getNodeClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
public List<NodeServer> getNodeServers() {
|
||||
return new ArrayList<>(servers);
|
||||
}
|
||||
|
||||
public File getHome() {
|
||||
@@ -292,10 +295,6 @@ public final class Application {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
private void initLogging() {
|
||||
|
||||
}
|
||||
|
||||
public void init() throws Exception {
|
||||
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "" + Runtime.getRuntime().availableProcessors() * 4);
|
||||
System.setProperty("convert.bson.tiny", "true");
|
||||
@@ -312,7 +311,7 @@ public final class Application {
|
||||
String lib = config.getValue("lib", "").trim().replace("${APP_HOME}", homepath);
|
||||
lib = lib.isEmpty() ? (homepath + "/conf") : (lib + ";" + homepath + "/conf");
|
||||
Server.loadLib(logger, lib);
|
||||
initLogging();
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
final AnyValue resources = config.getAnyValue("resources");
|
||||
if (resources != null) {
|
||||
@@ -355,6 +354,67 @@ public final class Application {
|
||||
this.resourceFactory.register(JsonFactory.root());
|
||||
this.resourceFactory.register(BsonFactory.root().getConvert());
|
||||
this.resourceFactory.register(JsonFactory.root().getConvert());
|
||||
//只有WatchService才能加载Application、WatchFactory
|
||||
final Application application = this;
|
||||
this.resourceFactory.register(new ResourceFactory.ResourceLoader() {
|
||||
|
||||
@Override
|
||||
public void load(ResourceFactory rf, final Object src, String resourceName, Field field, final Object attachment) {
|
||||
try {
|
||||
Resource res = field.getAnnotation(Resource.class);
|
||||
if (res == null) return;
|
||||
if (!(src instanceof WatchService) || Sncp.isRemote((Service) src)) return; //远程模式不得注入
|
||||
Class type = field.getType();
|
||||
if (type == Application.class) {
|
||||
field.set(src, application);
|
||||
} else if (type == TransportFactory.class) {
|
||||
field.set(src, application.transportFactory);
|
||||
} else if (type == NodeSncpServer.class) {
|
||||
NodeServer server = null;
|
||||
for (NodeServer ns : application.getNodeServers()) {
|
||||
if (ns.getClass() == NodeSncpServer.class) continue;
|
||||
if (res.name().equals(ns.server.getName())) {
|
||||
server = ns;
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.set(src, server);
|
||||
} else if (type == NodeHttpServer.class) {
|
||||
NodeServer server = null;
|
||||
for (NodeServer ns : application.getNodeServers()) {
|
||||
if (ns.getClass() == NodeHttpServer.class) continue;
|
||||
if (res.name().equals(ns.server.getName())) {
|
||||
server = ns;
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.set(src, server);
|
||||
} else if (type == NodeWatchServer.class) {
|
||||
NodeServer server = null;
|
||||
for (NodeServer ns : application.getNodeServers()) {
|
||||
if (ns.getClass() == NodeWatchServer.class) continue;
|
||||
if (res.name().equals(ns.server.getName())) {
|
||||
server = ns;
|
||||
break;
|
||||
}
|
||||
}
|
||||
field.set(src, server);
|
||||
}
|
||||
// if (type == WatchFactory.class) {
|
||||
// field.set(src, application.watchFactory);
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Resource inject error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean autoNone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}, Application.class, TransportFactory.class, NodeSncpServer.class, NodeHttpServer.class, NodeWatchServer.class);
|
||||
//--------------------------------------------------------------------------
|
||||
initResources();
|
||||
}
|
||||
|
||||
@@ -363,25 +423,18 @@ public final class Application {
|
||||
final AnyValue resources = config.getAnyValue("resources");
|
||||
if (resources != null) {
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
for (AnyValue conf : resources.getAnyValues("group")) {
|
||||
final String group = conf.getValue("name", "");
|
||||
final String protocol = conf.getValue("protocol", Transport.DEFAULT_PROTOCOL).toUpperCase();
|
||||
if (!"TCP".equalsIgnoreCase(protocol) && !"UDP".equalsIgnoreCase(protocol)) {
|
||||
throw new RuntimeException("Not supported Transport Protocol " + conf.getValue("protocol"));
|
||||
}
|
||||
GroupInfo ginfo = globalGroups.get(group);
|
||||
if (ginfo == null) {
|
||||
ginfo = new GroupInfo(group, protocol, conf.getValue("subprotocol", ""), new LinkedHashSet<>());
|
||||
globalGroups.put(group, ginfo);
|
||||
}
|
||||
TransportGroupInfo ginfo = new TransportGroupInfo(group, protocol, conf.getValue("subprotocol", ""), new LinkedHashSet<>());
|
||||
for (AnyValue node : conf.getAnyValues("node")) {
|
||||
final InetSocketAddress addr = new InetSocketAddress(node.getValue("addr"), node.getIntValue("port"));
|
||||
ginfo.addrs.add(addr);
|
||||
String oldgroup = globalNodes.get(addr);
|
||||
if (oldgroup != null) throw new RuntimeException(addr + " had one more group " + (globalNodes.get(addr)));
|
||||
globalNodes.put(addr, group);
|
||||
ginfo.putAddress(addr);
|
||||
}
|
||||
transportFactory.addGroupInfo(ginfo);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
@@ -431,7 +484,7 @@ public final class Application {
|
||||
}
|
||||
} else if ("APIDOC".equalsIgnoreCase(new String(bytes))) {
|
||||
try {
|
||||
new ApiDocs(application).run();
|
||||
new ApiDocsService(application).run();
|
||||
buffer.clear();
|
||||
buffer.put("APIDOC OK".getBytes());
|
||||
buffer.flip();
|
||||
@@ -476,7 +529,7 @@ public final class Application {
|
||||
final Application application = Application.create(true);
|
||||
application.init();
|
||||
application.start();
|
||||
new ApiDocs(application).run();
|
||||
new ApiDocsService(application).run();
|
||||
logger.info("APIDOC OK");
|
||||
return;
|
||||
}
|
||||
@@ -490,20 +543,25 @@ public final class Application {
|
||||
CountDownLatch timecd = new CountDownLatch(entrys.length);
|
||||
final List<AnyValue> sncps = new ArrayList<>();
|
||||
final List<AnyValue> others = new ArrayList<>();
|
||||
final List<AnyValue> watchs = new ArrayList<>();
|
||||
for (final AnyValue entry : entrys) {
|
||||
if (entry.getValue("protocol", "").toUpperCase().startsWith("SNCP")) {
|
||||
sncps.add(entry);
|
||||
} else if (entry.getValue("protocol", "").toUpperCase().startsWith("WATCH")) {
|
||||
watchs.add(entry);
|
||||
} else {
|
||||
others.add(entry);
|
||||
}
|
||||
}
|
||||
this.watching = !watchs.isEmpty();
|
||||
//单向SNCP服务不需要对等group
|
||||
//if (!sncps.isEmpty() && globalNodes.isEmpty()) throw new RuntimeException("found SNCP Server node but not found <group> node info.");
|
||||
|
||||
runServers(timecd, sncps); //必须确保sncp都启动后再启动其他协议
|
||||
runServers(timecd, others);
|
||||
runServers(timecd, watchs); //必须在所有server都启动后再启动
|
||||
timecd.await();
|
||||
logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms\r\n");
|
||||
if (!singletonrun) this.serversLatch.await();
|
||||
}
|
||||
|
||||
@@ -530,13 +588,22 @@ public final class Application {
|
||||
NodeServer server = null;
|
||||
if ("SNCP".equals(protocol)) {
|
||||
server = NodeSncpServer.createNodeServer(Application.this, serconf);
|
||||
} else if ("WATCH".equalsIgnoreCase(protocol)) {
|
||||
DefaultAnyValue serconf2 = (DefaultAnyValue) serconf;
|
||||
DefaultAnyValue rest = (DefaultAnyValue) serconf2.getAnyValue("rest");
|
||||
if (rest == null) {
|
||||
rest = new DefaultAnyValue();
|
||||
serconf2.addValue("rest", rest);
|
||||
}
|
||||
rest.setValue("base", WatchServlet.class.getName());
|
||||
server = new NodeWatchServer(Application.this, serconf);
|
||||
} else if ("HTTP".equalsIgnoreCase(protocol)) {
|
||||
server = new NodeHttpServer(Application.this, serconf);
|
||||
} else {
|
||||
if (!inited.get()) {
|
||||
synchronized (nodeClasses) {
|
||||
if (!inited.getAndSet(true)) { //加载自定义的协议,如:SOCKS
|
||||
ClassFilter profilter = new ClassFilter(NodeProtocol.class, NodeServer.class);
|
||||
ClassFilter profilter = new ClassFilter(NodeProtocol.class, NodeServer.class, (Class[]) null);
|
||||
ClassFilter.Loader.load(home, serconf.getValue("excludelibs", "").split(";"), profilter);
|
||||
final Set<FilterEntry<NodeServer>> entrys = profilter.getFilterEntrys();
|
||||
for (FilterEntry<NodeServer> entry : entrys) {
|
||||
@@ -546,7 +613,9 @@ public final class Application {
|
||||
p = p.toUpperCase();
|
||||
if ("SNCP".equals(p) || "HTTP".equals(p)) continue;
|
||||
final Class<? extends NodeServer> old = nodeClasses.get(p);
|
||||
if (old != null && old != type) throw new RuntimeException("Protocol(" + p + ") had NodeServer-Class(" + old.getName() + ") but repeat NodeServer-Class(" + type.getName() + ")");
|
||||
if (old != null && old != type) {
|
||||
throw new RuntimeException("Protocol(" + p + ") had NodeServer-Class(" + old.getName() + ") but repeat NodeServer-Class(" + type.getName() + ")");
|
||||
}
|
||||
nodeClasses.put(p, type);
|
||||
}
|
||||
}
|
||||
@@ -623,17 +692,6 @@ public final class Application {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
Set<String> findSncpGroups(Transport sameGroupTransport, Collection<Transport> diffGroupTransports) {
|
||||
Set<String> gs = new HashSet<>();
|
||||
if (sameGroupTransport != null) gs.add(sameGroupTransport.getName());
|
||||
if (diffGroupTransports != null) {
|
||||
for (Transport t : diffGroupTransports) {
|
||||
gs.add(t.getName());
|
||||
}
|
||||
}
|
||||
return gs;
|
||||
}
|
||||
|
||||
NodeSncpServer findNodeSncpServer(final InetSocketAddress sncpAddr) {
|
||||
for (NodeServer node : servers) {
|
||||
if (node.isSNCP() && sncpAddr.equals(node.getSncpAddress())) {
|
||||
@@ -643,11 +701,6 @@ public final class Application {
|
||||
return null;
|
||||
}
|
||||
|
||||
GroupInfo findGroupInfo(String group) {
|
||||
if (group == null) return null;
|
||||
return globalGroups.get(group);
|
||||
}
|
||||
|
||||
private void shutdown() throws Exception {
|
||||
servers.stream().forEach((server) -> {
|
||||
try {
|
||||
@@ -660,26 +713,22 @@ public final class Application {
|
||||
});
|
||||
|
||||
for (DataSource source : dataSources) {
|
||||
if (source == null) continue;
|
||||
try {
|
||||
source.getClass().getMethod("close").invoke(source);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.FINER, "close DataSource erroneous", e);
|
||||
logger.log(Level.FINER, source.getClass() + " close DataSource erroneous", e);
|
||||
}
|
||||
}
|
||||
for (CacheSource source : cacheSources) {
|
||||
if (source == null) continue;
|
||||
try {
|
||||
source.getClass().getMethod("close").invoke(source);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.FINER, "close CacheSource erroneous", e);
|
||||
}
|
||||
}
|
||||
if (this.transportChannelGroup != null) {
|
||||
try {
|
||||
this.transportChannelGroup.shutdownNow();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.FINER, "close transportChannelGroup erroneous", e);
|
||||
logger.log(Level.FINER, source.getClass() + " close CacheSource erroneous", e);
|
||||
}
|
||||
}
|
||||
this.transportFactory.shutdownNow();
|
||||
}
|
||||
|
||||
private static AnyValue load(final InputStream in0) {
|
||||
|
||||
@@ -40,6 +40,8 @@ public final class ClassFilter<T> {
|
||||
|
||||
private Class superClass; //符合的父类型。不为空时,扫描结果的class必须是superClass的子类
|
||||
|
||||
private Class[] excludeSuperClasses; //不符合的父类型。
|
||||
|
||||
private Class<? extends Annotation> annotationClass;//符合的注解。不为空时,扫描结果的class必须包含该注解
|
||||
|
||||
private Pattern[] includePatterns; //符合的classname正则表达式
|
||||
@@ -56,18 +58,19 @@ public final class ClassFilter<T> {
|
||||
|
||||
private AnyValue conf; //基本配置信息, 当符合条件时将conf的属性赋值到FilterEntry中去。
|
||||
|
||||
public ClassFilter(Class<? extends Annotation> annotationClass, Class superClass) {
|
||||
this(annotationClass, superClass, null);
|
||||
public ClassFilter(Class<? extends Annotation> annotationClass, Class superClass, Class[] excludeSuperClasses) {
|
||||
this(annotationClass, superClass, excludeSuperClasses, null);
|
||||
}
|
||||
|
||||
public ClassFilter(Class<? extends Annotation> annotationClass, Class superClass, AnyValue conf) {
|
||||
public ClassFilter(Class<? extends Annotation> annotationClass, Class superClass, Class[] excludeSuperClasses, AnyValue conf) {
|
||||
this.annotationClass = annotationClass;
|
||||
this.superClass = superClass;
|
||||
this.excludeSuperClasses = excludeSuperClasses;
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
public static ClassFilter create(String includeregs, String excluderegs, Set<String> includeValues, Set<String> excludeValues) {
|
||||
ClassFilter filter = new ClassFilter(null, null);
|
||||
public static ClassFilter create(Class[] excludeSuperClasses, String includeregs, String excluderegs, Set<String> includeValues, Set<String> excludeValues) {
|
||||
ClassFilter filter = new ClassFilter(null, null, excludeSuperClasses);
|
||||
filter.setIncludePatterns(includeregs == null ? null : includeregs.split(";"));
|
||||
filter.setExcludePatterns(excluderegs == null ? null : excluderegs.split(";"));
|
||||
filter.setPrivilegeIncludes(includeValues);
|
||||
@@ -247,7 +250,13 @@ public final class ClassFilter<T> {
|
||||
public boolean accept(AnyValue property, Class clazz, boolean autoscan) {
|
||||
if (this.refused || !Modifier.isPublic(clazz.getModifiers())) return false;
|
||||
if (annotationClass != null && clazz.getAnnotation(annotationClass) == null) return false;
|
||||
return superClass == null || (clazz != superClass && superClass.isAssignableFrom(clazz));
|
||||
boolean rs = superClass == null || (clazz != superClass && superClass.isAssignableFrom(clazz));
|
||||
if (rs && this.excludeSuperClasses != null && this.excludeSuperClasses.length > 0) {
|
||||
for (Class c : this.excludeSuperClasses) {
|
||||
if (c != null && (clazz == c || c.isAssignableFrom(clazz))) return false;
|
||||
}
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public static Pattern[] toPattern(String[] regs) {
|
||||
@@ -269,6 +278,18 @@ public final class ClassFilter<T> {
|
||||
this.superClass = superClass;
|
||||
}
|
||||
|
||||
public Class getSuperClass() {
|
||||
return superClass;
|
||||
}
|
||||
|
||||
public Class[] getExcludeSuperClasses() {
|
||||
return excludeSuperClasses;
|
||||
}
|
||||
|
||||
public void setExcludeSuperClasses(Class[] excludeSuperClasses) {
|
||||
this.excludeSuperClasses = excludeSuperClasses;
|
||||
}
|
||||
|
||||
public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
|
||||
this.annotationClass = annotationClass;
|
||||
}
|
||||
@@ -293,10 +314,6 @@ public final class ClassFilter<T> {
|
||||
return annotationClass;
|
||||
}
|
||||
|
||||
public Class getSuperClass() {
|
||||
return superClass;
|
||||
}
|
||||
|
||||
public boolean isRefused() {
|
||||
return refused;
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
|
||||
/**
|
||||
* 协议地址组合对象, 对应application.xml 中 resources->group 节点信息
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class GroupInfo {
|
||||
|
||||
protected String name; //地址
|
||||
|
||||
protected String protocol; //协议 取值范围: TCP、UDP
|
||||
|
||||
protected String subprotocol; //子协议,预留使用
|
||||
|
||||
protected Set<InetSocketAddress> addrs; //地址列表, 对应 resources->group->node节点信息
|
||||
|
||||
public GroupInfo() {
|
||||
}
|
||||
|
||||
public GroupInfo(String name, String protocol, String subprotocol, Set<InetSocketAddress> addrs) {
|
||||
this.name = name;
|
||||
this.protocol = protocol;
|
||||
this.subprotocol = subprotocol;
|
||||
this.addrs = addrs;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getSubprotocol() {
|
||||
return subprotocol;
|
||||
}
|
||||
|
||||
public void setSubprotocol(String subprotocol) {
|
||||
this.subprotocol = subprotocol;
|
||||
}
|
||||
|
||||
public Set<InetSocketAddress> getAddrs() {
|
||||
return addrs;
|
||||
}
|
||||
|
||||
public Set<InetSocketAddress> copyAddrs() {
|
||||
return addrs == null ? null : new LinkedHashSet<>(addrs);
|
||||
}
|
||||
|
||||
public void setAddrs(Set<InetSocketAddress> addrs) {
|
||||
this.addrs = addrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonConvert.root().convertTo(this);
|
||||
}
|
||||
}
|
||||
28
src/org/redkale/boot/NodeClassLoader.java
Normal file
28
src/org/redkale/boot/NodeClassLoader.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class NodeClassLoader extends URLClassLoader {
|
||||
|
||||
public NodeClassLoader(ClassLoader parent) {
|
||||
super(new URL[0], parent);
|
||||
}
|
||||
|
||||
public Class<?> loadClass(String name, byte[] b) {
|
||||
return defineClass(name, b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addURL(URL url) {
|
||||
super.addURL(url);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
@@ -17,6 +18,7 @@ import org.redkale.net.sncp.Sncp;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.*;
|
||||
|
||||
/**
|
||||
* HTTP Server节点的配置Server
|
||||
@@ -29,7 +31,7 @@ import org.redkale.util.*;
|
||||
@NodeProtocol({"HTTP"})
|
||||
public class NodeHttpServer extends NodeServer {
|
||||
|
||||
protected final boolean rest; //是否加载REST服务, 为true加载rest节点信息并将所有可REST化的Service生成RestHttpServlet
|
||||
protected final boolean rest; //是否加载REST服务, 为true加载rest节点信息并将所有可REST化的Service生成RestServlet
|
||||
|
||||
protected final HttpServer httpServer;
|
||||
|
||||
@@ -40,7 +42,11 @@ public class NodeHttpServer extends NodeServer {
|
||||
}
|
||||
|
||||
private static Server createServer(Application application, AnyValue serconf) {
|
||||
return new HttpServer(application.getStartTime(), application.getWatchFactory());
|
||||
return new HttpServer(application.getStartTime());
|
||||
}
|
||||
|
||||
public HttpServer getHttpServer() {
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,22 +54,42 @@ public class NodeHttpServer extends NodeServer {
|
||||
return httpServer == null ? null : httpServer.getSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Service> createServiceClassFilter() {
|
||||
return createClassFilter(this.sncpGroup, null, Service.class, new Class[]{org.redkale.watch.WatchService.class}, Annotation.class, "services", "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Filter> createFilterClassFilter() {
|
||||
return createClassFilter(null, null, HttpFilter.class, new Class[]{WatchFilter.class}, null, "filters", "filter");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Servlet> createServletClassFilter() {
|
||||
return createClassFilter(null, WebServlet.class, HttpServlet.class, null, "servlets", "servlet");
|
||||
return createClassFilter(null, WebServlet.class, HttpServlet.class, new Class[]{WatchServlet.class}, null, "servlets", "servlet");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadServlet(ClassFilter<? extends Servlet> servletFilter) throws Exception {
|
||||
if (httpServer != null) loadHttpServlet(this.serverConf.getAnyValue("servlets"), servletFilter);
|
||||
protected ClassFilter createOtherClassFilter() {
|
||||
return createClassFilter(null, RestWebSocket.class, WebSocket.class, null, null, "rest", "websocket");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadService(ClassFilter serviceFilter) throws Exception {
|
||||
super.loadService(serviceFilter);
|
||||
protected void loadService(ClassFilter<? extends Service> serviceFilter, ClassFilter otherFilter) throws Exception {
|
||||
super.loadService(serviceFilter, otherFilter);
|
||||
initWebSocketService();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFilter(ClassFilter<? extends Filter> filterFilter, ClassFilter otherFilter) throws Exception {
|
||||
if (httpServer != null) loadHttpFilter(this.serverConf.getAnyValue("filters"), filterFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadServlet(ClassFilter<? extends Servlet> servletFilter, ClassFilter otherFilter) throws Exception {
|
||||
if (httpServer != null) loadHttpServlet(servletFilter, otherFilter);
|
||||
}
|
||||
|
||||
private void initWebSocketService() {
|
||||
final NodeServer self = this;
|
||||
final ResourceFactory regFactory = application.getResourceFactory();
|
||||
@@ -71,14 +97,23 @@ public class NodeHttpServer extends NodeServer {
|
||||
try {
|
||||
if (field.getAnnotation(Resource.class) == null) return;
|
||||
if (!(src instanceof WebSocketServlet)) return;
|
||||
ResourceFactory.ResourceLoader loader = null;
|
||||
ResourceFactory sncpResFactory = null;
|
||||
for (NodeServer ns : application.servers) {
|
||||
if (!ns.isSNCP()) continue;
|
||||
sncpResFactory = ns.resourceFactory;
|
||||
loader = sncpResFactory.findLoader(WebSocketNode.class, field);
|
||||
if (loader != null) break;
|
||||
}
|
||||
if (loader != null) loader.load(sncpResFactory, src, resourceName, field, attachment);
|
||||
synchronized (regFactory) {
|
||||
Service nodeService = (Service) rf.find(resourceName, WebSocketNode.class);
|
||||
if (nodeService == null) {
|
||||
nodeService = Sncp.createLocalService(resourceName, getExecutor(), application.getResourceFactory(), WebSocketNodeService.class, (InetSocketAddress) null, (String) null, (Set<String>) null, (AnyValue) null, (Transport) null, (Collection<Transport>) null);
|
||||
nodeService = Sncp.createLocalService(resourceName, WebSocketNodeService.class, application.getResourceFactory(), application.getTransportFactory(), (InetSocketAddress) null, (Set<String>) null, (AnyValue) null);
|
||||
regFactory.register(resourceName, WebSocketNode.class, nodeService);
|
||||
resourceFactory.inject(nodeService, self);
|
||||
logger.fine("[" + Thread.currentThread().getName() + "] Load Service " + nodeService);
|
||||
}
|
||||
resourceFactory.inject(nodeService, self);
|
||||
logger.fine("[" + Thread.currentThread().getName() + "] Load Service " + nodeService);
|
||||
field.set(src, nodeService);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -87,11 +122,31 @@ public class NodeHttpServer extends NodeServer {
|
||||
}, WebSocketNode.class);
|
||||
}
|
||||
|
||||
protected void loadHttpServlet(final AnyValue servletsConf, final ClassFilter<? extends Servlet> filter) throws Exception {
|
||||
protected void loadHttpFilter(final AnyValue filtersConf, final ClassFilter<? extends Filter> classFilter) throws Exception {
|
||||
final StringBuilder sb = logger.isLoggable(Level.INFO) ? new StringBuilder() : null;
|
||||
final String prefix = servletsConf == null ? "" : servletsConf.getValue("path", "");
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
List<FilterEntry<? extends Servlet>> list = new ArrayList(filter.getFilterEntrys());
|
||||
List<FilterEntry<? extends Filter>> list = new ArrayList(classFilter.getFilterEntrys());
|
||||
for (FilterEntry<? extends Filter> en : list) {
|
||||
Class<HttpFilter> clazz = (Class<HttpFilter>) en.getType();
|
||||
if (Modifier.isAbstract(clazz.getModifiers())) continue;
|
||||
final HttpFilter filter = clazz.newInstance();
|
||||
resourceFactory.inject(filter, this);
|
||||
DefaultAnyValue filterConf = (DefaultAnyValue) en.getProperty();
|
||||
this.httpServer.addHttpFilter(filter, filterConf);
|
||||
if (sb != null) sb.append(threadName).append(" Load ").append(clazz.getName()).append(LINE_SEPARATOR);
|
||||
}
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString());
|
||||
}
|
||||
|
||||
protected void loadHttpServlet(final ClassFilter<? extends Servlet> servletFilter, ClassFilter<? extends WebSocket> webSocketFilter) throws Exception {
|
||||
final AnyValue servletsConf = this.serverConf.getAnyValue("servlets");
|
||||
final StringBuilder sb = logger.isLoggable(Level.INFO) ? new StringBuilder() : null;
|
||||
String prefix0 = servletsConf == null ? "" : servletsConf.getValue("path", "");
|
||||
if (!prefix0.isEmpty() && prefix0.charAt(prefix0.length() - 1) == '/') prefix0 = prefix0.substring(0, prefix0.length() - 1);
|
||||
if (!prefix0.isEmpty() && prefix0.charAt(0) != '/') prefix0 = '/' + prefix0;
|
||||
final String prefix = prefix0;
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
List<FilterEntry<? extends Servlet>> list = new ArrayList(servletFilter.getFilterEntrys());
|
||||
list.sort((FilterEntry<? extends Servlet> o1, FilterEntry<? extends Servlet> o2) -> { //必须保证WebSocketServlet优先加载, 因为要确保其他的HttpServlet可以注入本地模式的WebSocketNode
|
||||
boolean ws1 = WebSocketServlet.class.isAssignableFrom(o1.getType());
|
||||
boolean ws2 = WebSocketServlet.class.isAssignableFrom(o2.getType());
|
||||
@@ -132,67 +187,127 @@ public class NodeHttpServer extends NodeServer {
|
||||
}
|
||||
}
|
||||
if (rest && serverConf != null) {
|
||||
final List<Object> restedObjects = new ArrayList<>();
|
||||
for (AnyValue restConf : serverConf.getAnyValues("rest")) {
|
||||
loadRestServlet(prefix, restConf, sb);
|
||||
loadRestServlet(webSocketFilter, restConf, restedObjects, sb);
|
||||
}
|
||||
}
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString());
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString().trim());
|
||||
}
|
||||
|
||||
protected void loadRestServlet(final String prefix, final AnyValue restConf, final StringBuilder sb) throws Exception {
|
||||
protected void loadRestServlet(final ClassFilter<? extends WebSocket> webSocketFilter, final AnyValue restConf, final List<Object> restedObjects, final StringBuilder sb) throws Exception {
|
||||
if (!rest) return;
|
||||
if (restConf == null) return; //不存在REST服务
|
||||
|
||||
String prefix0 = restConf.getValue("path", "");
|
||||
if (!prefix0.isEmpty() && prefix0.charAt(prefix0.length() - 1) == '/') prefix0 = prefix0.substring(0, prefix0.length() - 1);
|
||||
if (!prefix0.isEmpty() && prefix0.charAt(0) != '/') prefix0 = '/' + prefix0;
|
||||
final String prefix = prefix0;
|
||||
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
final List<AbstractMap.SimpleEntry<String, String[]>> ss = sb == null ? null : new ArrayList<>();
|
||||
|
||||
final Class baseServletClass = Class.forName(restConf.getValue("base", DefaultRestServlet.class.getName()));
|
||||
|
||||
final boolean autoload = restConf.getBoolValue("autoload", true);
|
||||
final boolean mustsign = restConf.getBoolValue("mustsign", true); //是否只加载标记@RestService的Service类
|
||||
{ //加载RestService
|
||||
String userTypeStr = restConf.getValue("usertype");
|
||||
final Class userType = userTypeStr == null ? null : Class.forName(userTypeStr);
|
||||
final Class baseServletType = Class.forName(restConf.getValue("base", HttpServlet.class.getName()));
|
||||
final Set<String> includeValues = new HashSet<>();
|
||||
final Set<String> excludeValues = new HashSet<>();
|
||||
for (AnyValue item : restConf.getAnyValues("service")) {
|
||||
if (item.getBoolValue("ignore", false)) {
|
||||
excludeValues.add(item.getValue("value", ""));
|
||||
} else {
|
||||
includeValues.add(item.getValue("value", ""));
|
||||
}
|
||||
}
|
||||
|
||||
final Set<String> includeValues = new HashSet<>();
|
||||
final Set<String> excludeValues = new HashSet<>();
|
||||
for (AnyValue item : restConf.getAnyValues("service")) {
|
||||
if (item.getBoolValue("ignore", false)) {
|
||||
excludeValues.add(item.getValue("value", ""));
|
||||
} else {
|
||||
includeValues.add(item.getValue("value", ""));
|
||||
final ClassFilter restFilter = ClassFilter.create(null, restConf.getValue("includes", ""), restConf.getValue("excludes", ""), includeValues, excludeValues);
|
||||
final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
super.interceptorServices.forEach((service) -> {
|
||||
final Class stype = Sncp.getServiceType(service);
|
||||
final String name = Sncp.getResourceName(service);
|
||||
RestService rs = (RestService) stype.getAnnotation(RestService.class);
|
||||
if (rs == null || rs.ignore()) return;
|
||||
|
||||
final String stypename = stype.getName();
|
||||
if (!autoload && !includeValues.contains(stypename)) return;
|
||||
if (!restFilter.accept(stypename)) return;
|
||||
if (restedObjects.contains(service)) {
|
||||
logger.log(Level.WARNING, stype.getName() + " repeat create rest servlet, so ignore");
|
||||
return;
|
||||
}
|
||||
restedObjects.add(service); //避免重复创建Rest对象
|
||||
HttpServlet servlet = httpServer.addRestServlet(service, userType, baseServletType, prefix);
|
||||
if (servlet == null) return; //没有HttpMapping方法的HttpServlet调用Rest.createRestServlet就会返回null
|
||||
resourceFactory.inject(servlet, NodeHttpServer.this);
|
||||
if (finest) logger.finest(threadName + " Create RestServlet(resource.name='" + name + "') = " + servlet);
|
||||
if (ss != null) {
|
||||
String[] mappings = servlet.getClass().getAnnotation(WebServlet.class).value();
|
||||
for (int i = 0; i < mappings.length; i++) {
|
||||
mappings[i] = prefix + mappings[i];
|
||||
}
|
||||
ss.add(new AbstractMap.SimpleEntry<>(servlet.getClass().getName(), mappings));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (webSocketFilter != null) { //加载RestWebSocket
|
||||
final Set<String> includeValues = new HashSet<>();
|
||||
final Set<String> excludeValues = new HashSet<>();
|
||||
for (AnyValue item : restConf.getAnyValues("websocket")) {
|
||||
if (item.getBoolValue("ignore", false)) {
|
||||
excludeValues.add(item.getValue("value", ""));
|
||||
} else {
|
||||
includeValues.add(item.getValue("value", ""));
|
||||
}
|
||||
}
|
||||
|
||||
final ClassFilter restFilter = ClassFilter.create(null, restConf.getValue("includes", ""), restConf.getValue("excludes", ""), includeValues, excludeValues);
|
||||
final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
|
||||
List<FilterEntry<? extends WebSocket>> list = new ArrayList(webSocketFilter.getFilterEntrys());
|
||||
for (FilterEntry<? extends WebSocket> en : list) {
|
||||
Class<WebSocket> clazz = (Class<WebSocket>) en.getType();
|
||||
if (Modifier.isAbstract(clazz.getModifiers())) {
|
||||
logger.log(Level.FINE, clazz.getName() + " cannot abstract on rest websocket, so ignore");
|
||||
continue;
|
||||
}
|
||||
if (Modifier.isFinal(clazz.getModifiers())) {
|
||||
logger.log(Level.FINE, clazz.getName() + " cannot final on rest websocket, so ignore");
|
||||
continue;
|
||||
}
|
||||
final Class<? extends WebSocket> stype = en.getType();
|
||||
RestWebSocket rs = stype.getAnnotation(RestWebSocket.class);
|
||||
if (rs == null || rs.ignore()) return;
|
||||
|
||||
final String stypename = stype.getName();
|
||||
if (!autoload && !includeValues.contains(stypename)) return;
|
||||
if (!restFilter.accept(stypename)) return;
|
||||
if (restedObjects.contains(stype)) {
|
||||
logger.log(Level.WARNING, stype.getName() + " repeat create rest websocket, so ignore");
|
||||
return;
|
||||
}
|
||||
restedObjects.add(stype); //避免重复创建Rest对象
|
||||
HttpServlet servlet = httpServer.addRestWebSocketServlet(stype, prefix, en.getProperty());
|
||||
if (servlet == null) return; //没有RestOnMessage方法的HttpServlet调用Rest.createRestWebSocketServlet就会返回null
|
||||
resourceFactory.inject(servlet, NodeHttpServer.this);
|
||||
if (finest) logger.finest(threadName + " " + stype.getName() + " create RestWebSocketServlet " + servlet);
|
||||
if (ss != null) {
|
||||
String[] mappings = servlet.getClass().getAnnotation(WebServlet.class).value();
|
||||
for (int i = 0; i < mappings.length; i++) {
|
||||
mappings[i] = prefix + mappings[i];
|
||||
}
|
||||
ss.add(new AbstractMap.SimpleEntry<>(servlet.getClass().getName(), mappings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final ClassFilter restFilter = ClassFilter.create(restConf.getValue("includes", ""), restConf.getValue("excludes", ""), includeValues, excludeValues);
|
||||
|
||||
super.interceptorServices.forEach((service) -> {
|
||||
final Class stype = Sncp.getServiceType(service);
|
||||
final String name = Sncp.getResourceName(service);
|
||||
RestService rs = (RestService) stype.getAnnotation(RestService.class);
|
||||
if (rs != null && rs.ignore()) return;
|
||||
if (mustsign && rs == null) return;
|
||||
if (stype.getAnnotation(LocalService.class) != null && rs == null) return;
|
||||
|
||||
final String stypename = stype.getName();
|
||||
if (!autoload && !includeValues.contains(stypename)) return;
|
||||
if (!restFilter.accept(stypename)) return;
|
||||
|
||||
RestHttpServlet servlet = httpServer.addRestServlet(name, stype, service, baseServletClass, prefix, (AnyValue) null);
|
||||
resourceFactory.inject(servlet, NodeHttpServer.this);
|
||||
if (finest) logger.finest(threadName + " Create RestServlet(resource.name='" + name + "') = " + servlet);
|
||||
if (ss != null) {
|
||||
String[] mappings = servlet.getClass().getAnnotation(WebServlet.class).value();
|
||||
for (int i = 0; i < mappings.length; i++) {
|
||||
mappings[i] = prefix + mappings[i];
|
||||
}
|
||||
ss.add(new AbstractMap.SimpleEntry<>(servlet.getClass().getName(), mappings));
|
||||
}
|
||||
});
|
||||
//输出信息
|
||||
if (ss != null && sb != null) {
|
||||
if (ss != null && !ss.isEmpty() && sb != null) {
|
||||
Collections.sort(ss, (AbstractMap.SimpleEntry<String, String[]> o1, AbstractMap.SimpleEntry<String, String[]> o2) -> o1.getKey().compareTo(o2.getKey()));
|
||||
int max = 0;
|
||||
for (AbstractMap.SimpleEntry<String, String[]> as : ss) {
|
||||
if (as.getKey().length() > max) max = as.getKey().length();
|
||||
}
|
||||
sb.append(threadName).append(" ").append(LINE_SEPARATOR);
|
||||
for (AbstractMap.SimpleEntry<String, String[]> as : ss) {
|
||||
sb.append(threadName).append(" Load ").append(as.getKey());
|
||||
for (int i = 0; i < max - as.getKey().length(); i++) {
|
||||
|
||||
@@ -8,24 +8,24 @@ package org.redkale.boot;
|
||||
import java.io.*;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.net.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Resource;
|
||||
import javax.persistence.Transient;
|
||||
import static org.redkale.boot.Application.*;
|
||||
import org.redkale.boot.ClassFilter.FilterEntry;
|
||||
import org.redkale.net.Filter;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.http.WebSocketNode;
|
||||
import org.redkale.net.http.WebSocketServlet;
|
||||
import org.redkale.net.sncp.*;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.source.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
|
||||
/**
|
||||
* Server节点的初始化配置类
|
||||
@@ -44,15 +44,6 @@ public abstract class NodeServer {
|
||||
//日志输出对象
|
||||
protected final Logger logger;
|
||||
|
||||
//日志是否为FINE级别
|
||||
protected final boolean fine;
|
||||
|
||||
//日志是否为FINER级别
|
||||
protected final boolean finer;
|
||||
|
||||
//日志是否为FINEST级别
|
||||
protected final boolean finest;
|
||||
|
||||
//进程主类
|
||||
protected final Application application;
|
||||
|
||||
@@ -62,8 +53,11 @@ public abstract class NodeServer {
|
||||
//当前Server对象
|
||||
protected final Server server;
|
||||
|
||||
//ClassLoader
|
||||
protected final NodeClassLoader classLoader;
|
||||
|
||||
//当前Server的SNCP协议的组
|
||||
private String sncpGroup = null;
|
||||
protected String sncpGroup = null;
|
||||
|
||||
//SNCP服务的地址, 非SNCP为null
|
||||
private InetSocketAddress sncpAddress;
|
||||
@@ -95,9 +89,8 @@ public abstract class NodeServer {
|
||||
this.resourceFactory = application.getResourceFactory().createChild();
|
||||
this.server = server;
|
||||
this.logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
this.fine = logger.isLoggable(Level.FINE);
|
||||
this.finer = logger.isLoggable(Level.FINER);
|
||||
this.finest = logger.isLoggable(Level.FINEST);
|
||||
this.classLoader = new NodeClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
Thread.currentThread().setContextClassLoader(this.classLoader);
|
||||
}
|
||||
|
||||
protected Consumer<Runnable> getExecutor() throws Exception {
|
||||
@@ -140,13 +133,17 @@ public abstract class NodeServer {
|
||||
if (isSNCP()) { // SNCP协议
|
||||
String host = this.serverConf.getValue("host", "0.0.0.0").replace("0.0.0.0", "");
|
||||
this.sncpAddress = new InetSocketAddress(host.isEmpty() ? application.localAddress.getHostAddress() : host, this.serverConf.getIntValue("port"));
|
||||
this.sncpGroup = application.globalNodes.get(this.sncpAddress);
|
||||
this.sncpGroup = application.transportFactory.findGroupName(this.sncpAddress);
|
||||
//单向SNCP服务不需要对等group
|
||||
//if (this.sncpGroup == null) throw new RuntimeException("Server (" + String.valueOf(config).replaceAll("\\s+", " ") + ") not found <group> info");
|
||||
}
|
||||
//单点服务不会有 sncpAddress、sncpGroup
|
||||
if (this.sncpAddress != null) this.resourceFactory.register(RESNAME_SERVER_ADDR, this.sncpAddress);
|
||||
if (this.sncpGroup != null) this.resourceFactory.register(RESNAME_SERVER_GROUP, this.sncpGroup);
|
||||
if (this.sncpAddress != null) {
|
||||
this.resourceFactory.register(RESNAME_SNCP_ADDR, this.sncpAddress);
|
||||
this.resourceFactory.register(RESNAME_SNCP_ADDR, SocketAddress.class, this.sncpAddress);
|
||||
this.resourceFactory.register(RESNAME_SNCP_ADDR, String.class, this.sncpAddress.getHostString() + ":" + this.sncpAddress.getPort());
|
||||
}
|
||||
if (this.sncpGroup != null) this.resourceFactory.register(RESNAME_SNCP_GROUP, this.sncpGroup);
|
||||
{
|
||||
//设置root文件夹
|
||||
String webroot = this.serverConf.getValue("root", "root");
|
||||
@@ -173,28 +170,53 @@ public abstract class NodeServer {
|
||||
this.interceptor = (NodeInterceptor) clazz.newInstance();
|
||||
}
|
||||
|
||||
ClassFilter<Servlet> servletFilter = createServletClassFilter();
|
||||
ClassFilter<Service> serviceFilter = createServiceClassFilter();
|
||||
ClassFilter<Filter> filterFilter = createFilterClassFilter();
|
||||
ClassFilter<Servlet> servletFilter = createServletClassFilter();
|
||||
ClassFilter otherFilter = createOtherClassFilter();
|
||||
long s = System.currentTimeMillis();
|
||||
if (servletFilter == null) {
|
||||
ClassFilter.Loader.load(application.getHome(), serverConf.getValue("excludelibs", "").split(";"), serviceFilter);
|
||||
} else {
|
||||
ClassFilter.Loader.load(application.getHome(), serverConf.getValue("excludelibs", "").split(";"), serviceFilter, servletFilter);
|
||||
}
|
||||
ClassFilter.Loader.load(application.getHome(), serverConf.getValue("excludelibs", "").split(";"), serviceFilter, filterFilter, servletFilter, otherFilter);
|
||||
long e = System.currentTimeMillis() - s;
|
||||
logger.info(this.getClass().getSimpleName() + " load filter class in " + e + " ms");
|
||||
loadService(serviceFilter); //必须在servlet之前
|
||||
loadServlet(servletFilter);
|
||||
loadService(serviceFilter, otherFilter); //必须在servlet之前
|
||||
loadFilter(filterFilter, otherFilter);
|
||||
loadServlet(servletFilter, otherFilter);
|
||||
|
||||
if (this.interceptor != null) this.resourceFactory.inject(this.interceptor);
|
||||
}
|
||||
|
||||
protected abstract void loadServlet(ClassFilter<? extends Servlet> servletFilter) throws Exception;
|
||||
protected abstract void loadFilter(ClassFilter<? extends Filter> filterFilter, ClassFilter otherFilter) throws Exception;
|
||||
|
||||
protected abstract void loadServlet(ClassFilter<? extends Servlet> servletFilter, ClassFilter otherFilter) throws Exception;
|
||||
|
||||
private void initResource() {
|
||||
final NodeServer self = this;
|
||||
//---------------------------------------------------------------------------------------------
|
||||
final ResourceFactory appResFactory = application.getResourceFactory();
|
||||
final TransportFactory appTranFactory = application.getTransportFactory();
|
||||
final AnyValue resources = application.config.getAnyValue("resources");
|
||||
final Map<String, AnyValue> cacheResource = new HashMap<>();
|
||||
//final Map<String, AnyValue> dataResources = new HashMap<>();
|
||||
if (resources != null) {
|
||||
for (AnyValue sourceConf : resources.getAnyValues("source")) {
|
||||
try {
|
||||
Class type = Class.forName(sourceConf.getValue("value"));
|
||||
if (!Service.class.isAssignableFrom(type)) {
|
||||
logger.log(Level.SEVERE, "load application source resource, but not Service error: " + sourceConf);
|
||||
} else if (CacheSource.class.isAssignableFrom(type)) {
|
||||
cacheResource.put(sourceConf.getValue("name", ""), sourceConf);
|
||||
} else if (DataSource.class.isAssignableFrom(type)) {
|
||||
//dataResources.put(sourceConf.getValue("name", ""), sourceConf);
|
||||
//暂时不支持DataSource通过<resources>设置
|
||||
logger.log(Level.SEVERE, "load application source resource, but not CacheSource error: " + sourceConf);
|
||||
} else {
|
||||
logger.log(Level.SEVERE, "load application source resource, but not CacheSource error: " + sourceConf);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "load application source resource error: " + sourceConf, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------- 注册Resource --------------------------------------------------------
|
||||
resourceFactory.register((ResourceFactory rf, final Object src, String resourceName, Field field, final Object attachment) -> {
|
||||
try {
|
||||
@@ -204,7 +226,6 @@ public abstract class NodeServer {
|
||||
Class type = field.getType();
|
||||
if (type != AnyValue.class && type != AnyValue[].class) return;
|
||||
Object resource = null;
|
||||
final AnyValue resources = application.config.getAnyValue("resources");
|
||||
final AnyValue properties = resources == null ? null : resources.getAnyValue("properties");
|
||||
if (properties != null && type == AnyValue.class) {
|
||||
resource = properties.getAnyValue(res.name().substring("properties.".length()));
|
||||
@@ -229,13 +250,13 @@ public abstract class NodeServer {
|
||||
appResFactory.register(resourceName, DataSource.class, source);
|
||||
|
||||
SncpClient client = Sncp.getSncpClient((Service) src);
|
||||
Transport sameGroupTransport = Sncp.getSameGroupTransport((Service) src);
|
||||
List<Transport> diffGroupTransports = Arrays.asList(Sncp.getDiffGroupTransports((Service) src));
|
||||
final InetSocketAddress sncpAddr = client == null ? null : client.getClientAddress();
|
||||
if ((src instanceof DataSource) && sncpAddr != null && resourceFactory.find(resourceName, DataCacheListener.class) == null) { //只有DataSourceService 才能赋值 DataCacheListener
|
||||
final NodeSncpServer sncpServer = application.findNodeSncpServer(sncpAddr);
|
||||
Set<String> gs = application.findSncpGroups(sameGroupTransport, diffGroupTransports);
|
||||
Service cacheListenerService = Sncp.createLocalService(resourceName, getExecutor(), appResFactory, DataCacheListenerService.class, sncpAddr, sncpServer.getSncpGroup(), gs, Sncp.getConf((Service) src), sameGroupTransport, diffGroupTransports);
|
||||
final Set<String> groups = new HashSet<>();
|
||||
if (client != null && client.getSameGroup() != null) groups.add(client.getSameGroup());
|
||||
if (client != null && client.getDiffGroups() != null) groups.addAll(client.getDiffGroups());
|
||||
Service cacheListenerService = Sncp.createLocalService(resourceName, DataCacheListenerService.class, appResFactory, appTranFactory, sncpAddr, groups, Sncp.getConf((Service) src));
|
||||
appResFactory.register(resourceName, DataCacheListener.class, cacheListenerService);
|
||||
localServices.add(cacheListenerService);
|
||||
sncpServer.consumerAccept(cacheListenerService);
|
||||
@@ -244,56 +265,69 @@ public abstract class NodeServer {
|
||||
}
|
||||
field.set(src, source);
|
||||
rf.inject(source, self); // 给其可能包含@Resource的字段赋值;
|
||||
//NodeServer.this.watchFactory.inject(src);
|
||||
if (source instanceof Service) ((Service) source).init(null);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "DataSource inject error", e);
|
||||
}
|
||||
}, DataSource.class);
|
||||
|
||||
//------------------------------------- 注册CacheSource --------------------------------------------------------
|
||||
resourceFactory.register((ResourceFactory rf, final Object src, final String resourceName, Field field, final Object attachment) -> {
|
||||
try {
|
||||
if (field.getAnnotation(Resource.class) == null) return;
|
||||
if ((src instanceof Service) && Sncp.isRemote((Service) src)) return; //远程模式不需要注入 CacheSource
|
||||
final Service srcService = (Service) src;
|
||||
SncpClient client = Sncp.getSncpClient(srcService);
|
||||
Transport sameGroupTransport = Sncp.getSameGroupTransport(srcService);
|
||||
Transport[] dts = Sncp.getDiffGroupTransports((Service) src);
|
||||
List<Transport> diffGroupTransports = dts == null ? new ArrayList<>() : Arrays.asList(dts);
|
||||
final InetSocketAddress sncpAddr = client == null ? null : client.getClientAddress();
|
||||
final CacheMemorySource source = Sncp.createLocalService(resourceName, getExecutor(), appResFactory, CacheMemorySource.class, sncpAddr, Sncp.getSncpGroup(srcService), Sncp.getGroups(srcService), Sncp.getConf(srcService), sameGroupTransport, diffGroupTransports);
|
||||
Type genericType = field.getGenericType();
|
||||
ParameterizedType pt = (genericType instanceof ParameterizedType) ? (ParameterizedType) genericType : null;
|
||||
Type valType = pt == null ? null : pt.getActualTypeArguments()[1];
|
||||
source.setStoreType(pt == null ? Serializable.class : (Class) pt.getActualTypeArguments()[0], valType instanceof Class ? (Class) valType : Object.class);
|
||||
if (field.getAnnotation(Transient.class) != null) source.setNeedStore(false); //必须在setStoreType之后
|
||||
application.cacheSources.add(source);
|
||||
appResFactory.register(resourceName, genericType, source);
|
||||
appResFactory.register(resourceName, CacheSource.class, source);
|
||||
field.set(src, source);
|
||||
rf.inject(source, self); //
|
||||
((Service) source).init(null);
|
||||
resourceFactory.register(new ResourceFactory.ResourceLoader() {
|
||||
public void load(ResourceFactory rf, final Object src, final String resourceName, Field field, final Object attachment) {
|
||||
try {
|
||||
if (field.getAnnotation(Resource.class) == null) return;
|
||||
if ((src instanceof Service) && Sncp.isRemote((Service) src)) return; //远程模式不需要注入 CacheSource
|
||||
final Service srcService = (Service) src;
|
||||
SncpClient client = Sncp.getSncpClient(srcService);
|
||||
final InetSocketAddress sncpAddr = client == null ? null : client.getClientAddress();
|
||||
final AnyValue sourceConf = cacheResource.get(resourceName);
|
||||
final Class sourceType = sourceConf == null ? CacheMemorySource.class : Class.forName(sourceConf.getValue("type"));
|
||||
final Set<String> groups = new HashSet<>();
|
||||
if (client != null && client.getSameGroup() != null) groups.add(client.getSameGroup());
|
||||
if (client != null && client.getDiffGroups() != null) groups.addAll(client.getDiffGroups());
|
||||
final CacheSource source = (CacheSource) Sncp.createLocalService(resourceName, sourceType, appResFactory, appTranFactory, sncpAddr, groups, Sncp.getConf(srcService));
|
||||
Type genericType = field.getGenericType();
|
||||
ParameterizedType pt = (genericType instanceof ParameterizedType) ? (ParameterizedType) genericType : null;
|
||||
Type valType = pt == null ? null : pt.getActualTypeArguments()[1];
|
||||
if (sourceType == CacheMemorySource.class) {
|
||||
CacheMemorySource memorySource = (CacheMemorySource) source;
|
||||
memorySource.setStoreType(pt == null ? Serializable.class : (Class) pt.getActualTypeArguments()[0], valType instanceof Class ? (Class) valType : Object.class);
|
||||
if (field.getAnnotation(Transient.class) != null) memorySource.setNeedStore(false); //必须在setStoreType之后
|
||||
}
|
||||
application.cacheSources.add(source);
|
||||
appResFactory.register(resourceName, genericType, source);
|
||||
appResFactory.register(resourceName, CacheSource.class, source);
|
||||
field.set(src, source);
|
||||
rf.inject(source, self); //
|
||||
if (source instanceof Service) ((Service) source).init(sourceConf);
|
||||
|
||||
if ((src instanceof WebSocketNodeService) && sncpAddr != null) { //只有WebSocketNodeService的服务才需要给SNCP服务注入CacheMemorySource
|
||||
NodeSncpServer sncpServer = application.findNodeSncpServer(sncpAddr);
|
||||
Set<String> gs = application.findSncpGroups(sameGroupTransport, diffGroupTransports);
|
||||
sncpServer.getSncpServer().addSncpServlet((Service) source);
|
||||
logger.info("[" + Thread.currentThread().getName() + "] Load Service " + source);
|
||||
if ((src instanceof WebSocketNodeService) && sncpAddr != null) { //只有WebSocketNodeService的服务才需要给SNCP服务注入CacheMemorySource
|
||||
NodeSncpServer sncpServer = application.findNodeSncpServer(sncpAddr);
|
||||
sncpServer.getSncpServer().addSncpServlet((Service) source);
|
||||
//logger.info("[" + Thread.currentThread().getName() + "] Load Service " + source);
|
||||
}
|
||||
logger.info("[" + Thread.currentThread().getName() + "] Load Source " + source);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "DataSource inject error", e);
|
||||
}
|
||||
logger.info("[" + Thread.currentThread().getName() + "] Load Source " + source);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "DataSource inject error", e);
|
||||
}
|
||||
|
||||
public boolean autoNone() {
|
||||
return false;
|
||||
}
|
||||
}, CacheSource.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void loadService(ClassFilter serviceFilter) throws Exception {
|
||||
protected void loadService(ClassFilter<? extends Service> serviceFilter, ClassFilter otherFilter) throws Exception {
|
||||
if (serviceFilter == null) return;
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
final Set<FilterEntry<Service>> entrys = serviceFilter.getAllFilterEntrys();
|
||||
final Set<FilterEntry<? extends Service>> entrys = (Set) serviceFilter.getAllFilterEntrys();
|
||||
ResourceFactory regFactory = isSNCP() ? application.getResourceFactory() : resourceFactory;
|
||||
|
||||
for (FilterEntry<Service> entry : entrys) { //service实现类
|
||||
final ResourceFactory appResourceFactory = application.getResourceFactory();
|
||||
final TransportFactory appTransportFactory = application.getTransportFactory();
|
||||
for (FilterEntry<? extends Service> entry : entrys) { //service实现类
|
||||
final Class<? extends Service> serviceImplClass = entry.getType();
|
||||
if (Modifier.isFinal(serviceImplClass.getModifiers())) continue; //修饰final的类跳过
|
||||
if (!Modifier.isPublic(serviceImplClass.getModifiers())) continue;
|
||||
@@ -302,7 +336,7 @@ public abstract class NodeServer {
|
||||
if (DataSource.class.isAssignableFrom(serviceImplClass)) continue;
|
||||
if (CacheSource.class.isAssignableFrom(serviceImplClass)) continue;
|
||||
if (DataCacheListener.class.isAssignableFrom(serviceImplClass)) continue;
|
||||
if (WebSocketNode.class.isAssignableFrom(serviceImplClass)) continue;
|
||||
//if (WebSocketNode.class.isAssignableFrom(serviceImplClass)) continue;
|
||||
}
|
||||
if (entry.getName().contains("$")) throw new RuntimeException("<name> value cannot contains '$' in " + entry.getProperty());
|
||||
Service oldother = resourceFactory.find(entry.getName(), serviceImplClass);
|
||||
@@ -316,30 +350,29 @@ public abstract class NodeServer {
|
||||
final boolean localed = (this.sncpAddress == null && entry.isEmptyGroups() && !serviceImplClass.isInterface() && !Modifier.isAbstract(serviceImplClass.getModifiers())) //非SNCP的Server,通常是单点服务
|
||||
|| groups.contains(this.sncpGroup) //本地IP含在内的
|
||||
|| (this.sncpGroup == null && entry.isEmptyGroups()) //空的SNCP配置
|
||||
|| serviceImplClass.getAnnotation(LocalService.class) != null;//本地模式
|
||||
|| serviceImplClass.getAnnotation(Local.class) != null;//本地模式
|
||||
if (localed && (serviceImplClass.isInterface() || Modifier.isAbstract(serviceImplClass.getModifiers()))) continue; //本地模式不能实例化接口和抽象类的Service类
|
||||
final BiConsumer<ResourceFactory, Boolean> runner = (ResourceFactory rf, Boolean needinject) -> {
|
||||
final ResourceFactory.ResourceLoader resourceLoader = (ResourceFactory rf, final Object src, final String resourceName, Field field, final Object attachment) -> {
|
||||
try {
|
||||
Service service;
|
||||
if (localed) { //本地模式
|
||||
service = Sncp.createLocalService(entry.getName(), getExecutor(), application.getResourceFactory(), serviceImplClass,
|
||||
NodeServer.this.sncpAddress, NodeServer.this.sncpGroup, groups, entry.getProperty(), loadTransport(NodeServer.this.sncpGroup), loadTransports(groups));
|
||||
boolean ws = src instanceof WebSocketServlet;
|
||||
if (ws || localed) { //本地模式
|
||||
service = Sncp.createLocalService(resourceName, serviceImplClass, appResourceFactory, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
|
||||
} else {
|
||||
service = Sncp.createRemoteService(entry.getName(), getExecutor(), serviceImplClass, NodeServer.this.sncpAddress, null, groups, entry.getProperty(), loadTransport(groups));
|
||||
service = Sncp.createRemoteService(resourceName, serviceImplClass, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
|
||||
}
|
||||
if (SncpClient.parseMethod(serviceImplClass).isEmpty()) return; //class没有可用的方法, 通常为BaseService
|
||||
//final ServiceWrapper wrapper = new ServiceWrapper(serviceImplClass, service, entry.getName(), localed ? NodeServer.this.sncpGroup : null, groups, entry.getProperty());
|
||||
for (final Class restype : Sncp.getResourceTypes(service)) {
|
||||
if (resourceFactory.find(entry.getName(), restype) == null) {
|
||||
regFactory.register(entry.getName(), restype, service);
|
||||
if (needinject) rf.inject(service); //动态加载的Service也存在按需加载的注入资源
|
||||
} else if (isSNCP() && !entry.isAutoload()) {
|
||||
throw new RuntimeException(restype.getSimpleName() + "(class:" + serviceImplClass.getName() + ", name:" + entry.getName() + ", group:" + groups + ") is repeat.");
|
||||
}
|
||||
|
||||
final Class restype = Sncp.getResourceType(service);
|
||||
if (rf.find(resourceName, restype) == null) {
|
||||
regFactory.register(resourceName, restype, service);
|
||||
} else if (isSNCP() && !entry.isAutoload()) {
|
||||
throw new RuntimeException(restype.getSimpleName() + "(class:" + serviceImplClass.getName() + ", name:" + resourceName + ", group:" + groups + ") is repeat.");
|
||||
}
|
||||
if (Sncp.isRemote(service)) {
|
||||
remoteServices.add(service);
|
||||
} else {
|
||||
if (field != null) rf.inject(service); //动态加载的Service也存在按需加载的注入资源
|
||||
localServices.add(service);
|
||||
interceptorServices.add(service);
|
||||
if (consumer != null) consumer.accept(service);
|
||||
@@ -351,16 +384,10 @@ public abstract class NodeServer {
|
||||
}
|
||||
};
|
||||
if (entry.isExpect()) {
|
||||
ResourceFactory.ResourceLoader resourceLoader = (ResourceFactory rf, final Object src, final String resourceName, Field field, final Object attachment) -> {
|
||||
runner.accept(rf, true);
|
||||
};
|
||||
ResourceType rty = entry.getType().getAnnotation(ResourceType.class);
|
||||
Class[] resTypes = rty == null ? new Class[]{} : rty.value();
|
||||
for (final Class restype : resTypes) {
|
||||
resourceFactory.register(resourceLoader, restype);
|
||||
}
|
||||
resourceFactory.register(resourceLoader, rty == null ? entry.getType() : rty.value());
|
||||
} else {
|
||||
runner.accept(resourceFactory, false);
|
||||
resourceLoader.load(resourceFactory, null, entry.getName(), null, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -387,7 +414,7 @@ public abstract class NodeServer {
|
||||
//----------------- init -----------------
|
||||
List<Service> swlist = new ArrayList<>(localServices);
|
||||
Collections.sort(swlist, (o1, o2) -> {
|
||||
int rs = Sncp.getResourceTypes(o1)[0].getName().compareTo(Sncp.getResourceTypes(o2)[0].getName());
|
||||
int rs = Sncp.getResourceType(o1).getName().compareTo(Sncp.getResourceType(o2).getName());
|
||||
if (rs == 0) rs = Sncp.getResourceName(o1).compareTo(Sncp.getResourceName(o2));
|
||||
return rs;
|
||||
});
|
||||
@@ -419,88 +446,93 @@ public abstract class NodeServer {
|
||||
|
||||
private void calcMaxLength(Service y) { //计算toString中的长度
|
||||
maxNameLength = Math.max(maxNameLength, Sncp.getResourceName(y).length());
|
||||
StringBuilder s = new StringBuilder();
|
||||
Class[] types = Sncp.getResourceTypes(y);
|
||||
if (types.length == 1) {
|
||||
s.append(types[0].getName());
|
||||
} else {
|
||||
s.append('[');
|
||||
s.append(Arrays.asList(types).stream().map((Class t) -> t.getName()).collect(Collectors.joining(",")));
|
||||
s.append(']');
|
||||
}
|
||||
maxClassNameLength = Math.max(maxClassNameLength, s.length() + 1);
|
||||
maxClassNameLength = Math.max(maxClassNameLength, Sncp.getResourceType(y).getName().length() + 1);
|
||||
}
|
||||
|
||||
protected List<Transport> loadTransports(final HashSet<String> groups) {
|
||||
if (groups == null) return null;
|
||||
final List<Transport> transports = new ArrayList<>();
|
||||
for (String group : groups) {
|
||||
if (this.sncpGroup == null || !this.sncpGroup.equals(group)) {
|
||||
transports.add(loadTransport(group));
|
||||
}
|
||||
}
|
||||
return transports;
|
||||
}
|
||||
|
||||
protected Transport loadTransport(final HashSet<String> groups) {
|
||||
if (groups == null || groups.isEmpty()) return null;
|
||||
final String groupid = new ArrayList<>(groups).stream().sorted().collect(Collectors.joining(";")); //按字母排列顺序
|
||||
Transport transport = application.resourceFactory.find(groupid, Transport.class);
|
||||
if (transport != null) return transport;
|
||||
final List<Transport> transports = new ArrayList<>();
|
||||
for (String group : groups) {
|
||||
transports.add(loadTransport(group));
|
||||
}
|
||||
Set<InetSocketAddress> addrs = new HashSet();
|
||||
transports.forEach(t -> addrs.addAll(Arrays.asList(t.getRemoteAddresses())));
|
||||
Transport first = transports.get(0);
|
||||
GroupInfo ginfo = application.findGroupInfo(first.getName());
|
||||
Transport newTransport = new Transport(groupid, ginfo.getProtocol(), application.getWatchFactory(),
|
||||
ginfo.getSubprotocol(), application.transportBufferPool, application.transportChannelGroup, this.sncpAddress, addrs);
|
||||
synchronized (application.resourceFactory) {
|
||||
transport = application.resourceFactory.find(groupid, Transport.class);
|
||||
if (transport == null) {
|
||||
transport = newTransport;
|
||||
application.resourceFactory.register(groupid, transport);
|
||||
}
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
|
||||
protected Transport loadTransport(final String group) {
|
||||
if (group == null) return null;
|
||||
Transport transport;
|
||||
synchronized (application.resourceFactory) {
|
||||
transport = application.resourceFactory.find(group, Transport.class);
|
||||
if (transport != null) {
|
||||
if (this.sncpAddress != null && !this.sncpAddress.equals(transport.getClientAddress())) {
|
||||
throw new RuntimeException(transport + "repeat create on newClientAddress = " + this.sncpAddress + ", oldClientAddress = " + transport.getClientAddress());
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
GroupInfo ginfo = application.findGroupInfo(group);
|
||||
Set<InetSocketAddress> addrs = ginfo.copyAddrs();
|
||||
if (addrs == null) throw new RuntimeException("Not found <group> = " + group + " on <resources> ");
|
||||
transport = new Transport(group, ginfo.getProtocol(), application.getWatchFactory(),
|
||||
ginfo.getSubprotocol(), application.transportBufferPool, application.transportChannelGroup, this.sncpAddress, addrs);
|
||||
application.resourceFactory.register(group, transport);
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
/*
|
||||
* protected List<Transport> loadTransports(final HashSet<String> groups) {
|
||||
* if (groups == null) return null;
|
||||
* final List<Transport> transports = new ArrayList<>();
|
||||
* for (String group : groups) {
|
||||
* if (this.sncpGroup == null || !this.sncpGroup.equals(group)) {
|
||||
* transports.add(loadTransport(group));
|
||||
* }
|
||||
* }
|
||||
* return transports;
|
||||
* }
|
||||
* protected Transport loadTransport(final HashSet<String> groups) {
|
||||
* if (groups == null || groups.isEmpty()) return null;
|
||||
* final String groupid = new ArrayList<>(groups).stream().sorted().collect(Collectors.joining(";")); //按字母排列顺序
|
||||
* Transport transport = application.resourceFactory.find(groupid, Transport.class);
|
||||
* if (transport != null) return transport;
|
||||
* final List<Transport> transports = new ArrayList<>();
|
||||
* for (String group : groups) {
|
||||
* transports.add(loadTransport(group));
|
||||
* }
|
||||
* Set<InetSocketAddress> addrs = new HashSet();
|
||||
* transports.forEach(t -> addrs.addAll(Arrays.asList(t.getRemoteAddresses())));
|
||||
* Transport first = transports.get(0);
|
||||
* TransportGroupInfo ginfo = application.transportFactory.findGroupInfo(first.getName());
|
||||
* Transport newTransport = new Transport(groupid, ginfo.getProtocol(),
|
||||
* ginfo.getSubprotocol(), application.transportBufferPool, application.transportChannelGroup, this.sncpAddress, addrs);
|
||||
* synchronized (application.resourceFactory) {
|
||||
* transport = application.resourceFactory.find(groupid, Transport.class);
|
||||
* if (transport == null) {
|
||||
* transport = newTransport;
|
||||
* application.resourceFactory.register(groupid, transport);
|
||||
* }
|
||||
* }
|
||||
* return transport;
|
||||
* }
|
||||
*
|
||||
* protected Transport loadTransport(final String group) {
|
||||
* if (group == null) return null;
|
||||
* Transport transport;
|
||||
* synchronized (application.resourceFactory) {
|
||||
* transport = application.resourceFactory.find(group, Transport.class);
|
||||
* if (transport != null) {
|
||||
* if (this.sncpAddress != null && !this.sncpAddress.equals(transport.getClientAddress())) {
|
||||
* throw new RuntimeException(transport + "repeat create on newClientAddress = " + this.sncpAddress + ", oldClientAddress = " + transport.getClientAddress());
|
||||
* }
|
||||
* return transport;
|
||||
* }
|
||||
* TransportGroupInfo ginfo = application.findGroupInfo(group);
|
||||
* Set<InetSocketAddress> addrs = ginfo.copyAddresses();
|
||||
* if (addrs == null) throw new RuntimeException("Not found <group> = " + group + " on <resources> ");
|
||||
* transport = new Transport(group, ginfo.getProtocol(), ginfo.getSubprotocol(), application.transportBufferPool, application.transportChannelGroup, this.sncpAddress, addrs);
|
||||
* application.resourceFactory.register(group, transport);
|
||||
* }
|
||||
* return transport;
|
||||
* }
|
||||
*/
|
||||
protected abstract ClassFilter<Filter> createFilterClassFilter();
|
||||
|
||||
protected abstract ClassFilter<Servlet> createServletClassFilter();
|
||||
|
||||
protected ClassFilter createOtherClassFilter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ClassFilter<Service> createServiceClassFilter() {
|
||||
return createClassFilter(this.sncpGroup, null, Service.class, Annotation.class, "services", "service");
|
||||
return createClassFilter(this.sncpGroup, null, Service.class, (!isSNCP() || application.watching) ? null : new Class[]{org.redkale.watch.WatchService.class}, Annotation.class, "services", "service");
|
||||
}
|
||||
|
||||
protected ClassFilter createClassFilter(final String localGroup, Class<? extends Annotation> ref,
|
||||
Class inter, Class<? extends Annotation> ref2, String properties, String property) {
|
||||
ClassFilter cf = new ClassFilter(ref, inter, null);
|
||||
if (properties == null && properties == null) return cf;
|
||||
if (this.serverConf == null) return cf;
|
||||
Class inter, Class[] excludeSuperClasses, Class<? extends Annotation> ref2, String properties, String property) {
|
||||
ClassFilter cf = new ClassFilter(ref, inter, excludeSuperClasses, null);
|
||||
if (properties == null && properties == null) {
|
||||
cf.setRefused(true);
|
||||
return cf;
|
||||
}
|
||||
if (this.serverConf == null) {
|
||||
cf.setRefused(true);
|
||||
return cf;
|
||||
}
|
||||
AnyValue[] proplist = this.serverConf.getAnyValues(properties);
|
||||
if (proplist == null || proplist.length < 1) return cf;
|
||||
if (proplist == null || proplist.length < 1) {
|
||||
cf.setRefused(true);
|
||||
return cf;
|
||||
}
|
||||
cf = null;
|
||||
for (AnyValue list : proplist) {
|
||||
DefaultAnyValue prop = null;
|
||||
@@ -514,20 +546,20 @@ public abstract class NodeServer {
|
||||
prop = new AnyValue.DefaultAnyValue();
|
||||
prop.addValue("groups", sc);
|
||||
}
|
||||
ClassFilter filter = new ClassFilter(ref, inter, prop);
|
||||
for (AnyValue av : list.getAnyValues(property)) { // <service> 或 <servlet> 节点
|
||||
ClassFilter filter = new ClassFilter(ref, inter, excludeSuperClasses, prop);
|
||||
for (AnyValue av : list.getAnyValues(property)) { // <service>、<filter>、<servlet> 节点
|
||||
final AnyValue[] items = av.getAnyValues("property");
|
||||
if (av instanceof DefaultAnyValue && items.length > 0) { //存在 <property>节点
|
||||
DefaultAnyValue dav = DefaultAnyValue.create();
|
||||
final AnyValue.Entry<String>[] strings = av.getStringEntrys();
|
||||
if (strings != null) { //将<service>或<servlet>节点的属性值传给dav
|
||||
if (strings != null) { //将<service>、<filter>、<servlet>节点的属性值传给dav
|
||||
for (AnyValue.Entry<String> en : strings) {
|
||||
dav.addValue(en.name, en.getValue());
|
||||
}
|
||||
}
|
||||
final AnyValue.Entry<AnyValue>[] anys = av.getAnyEntrys();
|
||||
if (anys != null) {
|
||||
for (AnyValue.Entry<AnyValue> en : anys) { //将<service>或<servlet>节点的非property属性节点传给dav
|
||||
for (AnyValue.Entry<AnyValue> en : anys) { //将<service>、<filter>、<servlet>节点的非property属性节点传给dav
|
||||
if (!"property".equals(en.name)) dav.addValue(en.name, en.getValue());
|
||||
}
|
||||
}
|
||||
@@ -561,6 +593,14 @@ public abstract class NodeServer {
|
||||
return false;
|
||||
}
|
||||
|
||||
public ResourceFactory getResourceFactory() {
|
||||
return resourceFactory;
|
||||
}
|
||||
|
||||
public NodeClassLoader getNodeClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
public InetSocketAddress getSncpAddress() {
|
||||
return sncpAddress;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.logging.Level;
|
||||
import org.redkale.boot.ClassFilter.FilterEntry;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.sncp.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
|
||||
/**
|
||||
* SNCP Server节点的配置Server
|
||||
@@ -41,7 +44,7 @@ public class NodeSncpServer extends NodeServer {
|
||||
}
|
||||
|
||||
private static Server createServer(Application application, AnyValue serconf) {
|
||||
return new SncpServer(application.getStartTime(), application.getWatchFactory());
|
||||
return new SncpServer(application.getStartTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,7 +81,33 @@ public class NodeSncpServer extends NodeServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadServlet(ClassFilter<? extends Servlet> servletFilter) throws Exception {
|
||||
protected void loadFilter(ClassFilter<? extends Filter> filterFilter, ClassFilter otherFilter) throws Exception {
|
||||
if (sncpServer != null) loadSncpFilter(this.serverConf.getAnyValue("fliters"), filterFilter);
|
||||
}
|
||||
|
||||
protected void loadSncpFilter(final AnyValue servletsConf, final ClassFilter<? extends Filter> classFilter) throws Exception {
|
||||
final StringBuilder sb = logger.isLoggable(Level.INFO) ? new StringBuilder() : null;
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
List<FilterEntry<? extends Filter>> list = new ArrayList(classFilter.getFilterEntrys());
|
||||
for (FilterEntry<? extends Filter> en : list) {
|
||||
Class<SncpFilter> clazz = (Class<SncpFilter>) en.getType();
|
||||
if (Modifier.isAbstract(clazz.getModifiers())) continue;
|
||||
final SncpFilter filter = clazz.newInstance();
|
||||
resourceFactory.inject(filter, this);
|
||||
DefaultAnyValue filterConf = (DefaultAnyValue) en.getProperty();
|
||||
this.sncpServer.addSncpFilter(filter, filterConf);
|
||||
if (sb != null) sb.append(threadName).append(" Load ").append(clazz.getName()).append(LINE_SEPARATOR);
|
||||
}
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadServlet(ClassFilter<? extends Servlet> servletFilter, ClassFilter otherFilter) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Filter> createFilterClassFilter() {
|
||||
return createClassFilter(null, null, SncpFilter.class, null, null, "filters", "filter");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
40
src/org/redkale/boot/NodeWatchServer.java
Normal file
40
src/org/redkale/boot/NodeWatchServer.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.http.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.AnyValue;
|
||||
import org.redkale.watch.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@NodeProtocol({"WATCH"})
|
||||
public class NodeWatchServer extends NodeHttpServer {
|
||||
|
||||
public NodeWatchServer(Application application, AnyValue serconf) {
|
||||
super(application, serconf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Service> createServiceClassFilter() {
|
||||
return createClassFilter(this.sncpGroup, null, WatchService.class, null, Annotation.class, "services", "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Filter> createFilterClassFilter() {
|
||||
return createClassFilter(null, null, WatchFilter.class, null, null, "filters", "filter");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Servlet> createServletClassFilter() {
|
||||
return createClassFilter(null, WebServlet.class, WatchServlet.class, null, null, "servlets", "servlet");
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,11 @@ public final class CollectionDecoder<T> implements Decodeable<Reader, Collection
|
||||
this.creator = factory.loadCreator((Class) pt.getRawType());
|
||||
factory.register(type, this);
|
||||
this.decoder = factory.loadDecoder(this.componentType);
|
||||
} else if(factory.isReversible()){
|
||||
this.componentType = Object.class;
|
||||
this.creator = factory.loadCreator(Object.class);
|
||||
factory.register(type, this);
|
||||
this.decoder = factory.loadDecoder(this.componentType);
|
||||
} else {
|
||||
throw new ConvertException("collectiondecoder not support the type (" + type + ")");
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
*/
|
||||
package org.redkale.convert;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.*;
|
||||
import java.math.BigInteger;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import org.redkale.convert.ext.InetAddressSimpledCoder.InetSocketAddressSimpledCoder;
|
||||
import org.redkale.convert.ext.*;
|
||||
import org.redkale.util.*;
|
||||
@@ -91,11 +94,13 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
|
||||
this.register(Class.class, TypeSimpledCoder.instance);
|
||||
this.register(InetSocketAddress.class, InetSocketAddressSimpledCoder.instance);
|
||||
this.register(Pattern.class, PatternSimpledCoder.instance);
|
||||
this.register(File.class, FileSimpledCoder.instance);
|
||||
this.register(CompletionHandler.class, CompletionHandlerSimpledCoder.instance);
|
||||
this.register(AsyncHandler.class, AsyncHandlerSimpledCoder.instance);
|
||||
this.register(URL.class, URLSimpledCoder.instance);
|
||||
this.register(URI.class, URISimpledCoder.instance);
|
||||
//---------------------------------------------------------
|
||||
this.register(ByteBuffer.class, ByteBufferSimpledCoder.instance);
|
||||
this.register(boolean[].class, BoolArraySimpledCoder.instance);
|
||||
this.register(byte[].class, ByteArraySimpledCoder.instance);
|
||||
this.register(short[].class, ShortArraySimpledCoder.instance);
|
||||
@@ -184,12 +189,68 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
|
||||
}
|
||||
|
||||
final String getEntityAlias(Class clazz) {
|
||||
if (clazz == String.class) return "A";
|
||||
if (clazz == int.class) return "I";
|
||||
if (clazz == Integer.class) return "i";
|
||||
if (clazz == long.class) return "J";
|
||||
if (clazz == Long.class) return "j";
|
||||
if (clazz == byte.class) return "B";
|
||||
if (clazz == Byte.class) return "b";
|
||||
if (clazz == boolean.class) return "Z";
|
||||
if (clazz == Boolean.class) return "z";
|
||||
if (clazz == short.class) return "S";
|
||||
if (clazz == Short.class) return "s";
|
||||
if (clazz == char.class) return "C";
|
||||
if (clazz == Character.class) return "c";
|
||||
if (clazz == float.class) return "F";
|
||||
if (clazz == Float.class) return "f";
|
||||
if (clazz == double.class) return "D";
|
||||
if (clazz == Double.class) return "d";
|
||||
|
||||
if (clazz == String[].class) return "[A";
|
||||
if (clazz == int[].class) return "[I";
|
||||
if (clazz == long[].class) return "[J";
|
||||
if (clazz == byte[].class) return "[B";
|
||||
if (clazz == boolean[].class) return "[Z";
|
||||
if (clazz == short[].class) return "[S";
|
||||
if (clazz == char[].class) return "[C";
|
||||
if (clazz == float[].class) return "[F";
|
||||
if (clazz == double[].class) return "[D";
|
||||
|
||||
ConvertEntity ce = (ConvertEntity) clazz.getAnnotation(ConvertEntity.class);
|
||||
if (ce != null && findEntityAlias(ce.value()) == null) entitys.put(ce.value(), clazz);
|
||||
return ce == null ? clazz.getName() : ce.value();
|
||||
}
|
||||
|
||||
final Class getEntityAlias(String name) {
|
||||
if ("A".equals(name)) return String.class;
|
||||
if ("I".equals(name)) return int.class;
|
||||
if ("i".equals(name)) return Integer.class;
|
||||
if ("J".equals(name)) return long.class;
|
||||
if ("j".equals(name)) return Long.class;
|
||||
if ("B".equals(name)) return byte.class;
|
||||
if ("b".equals(name)) return Byte.class;
|
||||
if ("Z".equals(name)) return boolean.class;
|
||||
if ("z".equals(name)) return Boolean.class;
|
||||
if ("S".equals(name)) return short.class;
|
||||
if ("s".equals(name)) return Short.class;
|
||||
if ("C".equals(name)) return char.class;
|
||||
if ("c".equals(name)) return Character.class;
|
||||
if ("F".equals(name)) return float.class;
|
||||
if ("f".equals(name)) return Float.class;
|
||||
if ("D".equals(name)) return double.class;
|
||||
if ("d".equals(name)) return Double.class;
|
||||
|
||||
if ("[A".equals(name)) return String[].class;
|
||||
if ("[I".equals(name)) return int[].class;
|
||||
if ("[J".equals(name)) return long[].class;
|
||||
if ("[B".equals(name)) return byte[].class;
|
||||
if ("[Z".equals(name)) return boolean[].class;
|
||||
if ("[S".equals(name)) return short[].class;
|
||||
if ("[C".equals(name)) return char[].class;
|
||||
if ("[F".equals(name)) return float[].class;
|
||||
if ("[D".equals(name)) return double[].class;
|
||||
|
||||
Class clazz = findEntityAlias(name);
|
||||
try {
|
||||
return clazz == null ? Class.forName(name) : clazz;
|
||||
@@ -393,6 +454,8 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
|
||||
decoder = new ArrayDecoder(this, type);
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
decoder = new CollectionDecoder(this, type);
|
||||
} else if (Stream.class.isAssignableFrom(clazz)) {
|
||||
decoder = new StreamDecoder(this, type);
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
decoder = new MapDecoder(this, type);
|
||||
} else if (clazz == Object.class) {
|
||||
@@ -476,6 +539,8 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
|
||||
encoder = new ArrayEncoder(this, type);
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
encoder = new CollectionEncoder(this, type);
|
||||
} else if (Stream.class.isAssignableFrom(clazz)) {
|
||||
encoder = new StreamEncoder(this, type);
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
encoder = new MapEncoder(this, type);
|
||||
} else if (clazz == Object.class) {
|
||||
|
||||
25
src/org/redkale/convert/ConvertMask.java
Normal file
25
src/org/redkale/convert/ConvertMask.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.convert;
|
||||
|
||||
/**
|
||||
* Mask接口
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public interface ConvertMask {
|
||||
|
||||
default byte mask(byte value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
default byte unmask(byte value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
94
src/org/redkale/convert/StreamDecoder.java
Normal file
94
src/org/redkale/convert/StreamDecoder.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.convert;
|
||||
|
||||
import org.redkale.util.Creator;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Stream的反序列化操作类 <br>
|
||||
* 支持一定程度的泛型。 <br>
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <T> 反解析的集合元素类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class StreamDecoder<T> implements Decodeable<Reader, Stream<T>> {
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final Type componentType;
|
||||
|
||||
protected Creator<Stream<T>> creator;
|
||||
|
||||
protected final Decodeable<Reader, T> decoder;
|
||||
|
||||
private boolean inited = false;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
public StreamDecoder(final ConvertFactory factory, final Type type) {
|
||||
this.type = type;
|
||||
try {
|
||||
if (type instanceof ParameterizedType) {
|
||||
final ParameterizedType pt = (ParameterizedType) type;
|
||||
this.componentType = pt.getActualTypeArguments()[0];
|
||||
this.creator = factory.loadCreator((Class) pt.getRawType());
|
||||
factory.register(type, this);
|
||||
this.decoder = factory.loadDecoder(this.componentType);
|
||||
} else {
|
||||
throw new ConvertException("StreamDecoder not support the type (" + type + ")");
|
||||
}
|
||||
} finally {
|
||||
inited = true;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> convertFrom(Reader in) {
|
||||
final int len = in.readArrayB();
|
||||
if (len == Reader.SIGN_NULL) return null;
|
||||
if (this.decoder == null) {
|
||||
if (!this.inited) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final Decodeable<Reader, T> localdecoder = this.decoder;
|
||||
final List<T> result = new ArrayList();
|
||||
if (len == Reader.SIGN_NOLENGTH) {
|
||||
while (in.hasNext()) {
|
||||
result.add(localdecoder.convertFrom(in));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < len; i++) {
|
||||
result.add(localdecoder.convertFrom(in));
|
||||
}
|
||||
}
|
||||
in.readArrayE();
|
||||
return result.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
90
src/org/redkale/convert/StreamEncoder.java
Normal file
90
src/org/redkale/convert/StreamEncoder.java
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.convert;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Stream的序列化操作类 <br>
|
||||
* 支持一定程度的泛型。 <br>
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <T> 序列化的集合元素类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class StreamEncoder<T> implements Encodeable<Writer, Stream<T>> {
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final Encodeable<Writer, Object> encoder;
|
||||
|
||||
private boolean inited = false;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
public StreamEncoder(final ConvertFactory factory, final Type type) {
|
||||
this.type = type;
|
||||
try {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type t = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
if (t instanceof TypeVariable) {
|
||||
this.encoder = factory.getAnyEncoder();
|
||||
} else {
|
||||
this.encoder = factory.loadEncoder(t);
|
||||
}
|
||||
} else {
|
||||
this.encoder = factory.getAnyEncoder();
|
||||
}
|
||||
} finally {
|
||||
inited = true;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convertTo(Writer out, Stream<T> value) {
|
||||
if (value == null) {
|
||||
out.writeNull();
|
||||
return;
|
||||
}
|
||||
Object[] array = value.toArray();
|
||||
if (array.length == 0) {
|
||||
out.writeArrayB(0);
|
||||
out.writeArrayE();
|
||||
return;
|
||||
}
|
||||
if (this.encoder == null) {
|
||||
if (!this.inited) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.writeArrayB(array.length);
|
||||
boolean first = true;
|
||||
for (Object v : array) {
|
||||
if (!first) out.writeArrayMark();
|
||||
encoder.convertTo(out, v);
|
||||
if (first) first = false;
|
||||
}
|
||||
out.writeArrayE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,10 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
|
||||
private ByteBuffer currentBuffer;
|
||||
|
||||
protected BsonByteBufferReader(ByteBuffer... buffers) {
|
||||
protected ConvertMask mask;
|
||||
|
||||
protected BsonByteBufferReader(ConvertMask mask, ByteBuffer... buffers) {
|
||||
this.mask = mask;
|
||||
this.buffers = buffers;
|
||||
if (buffers != null && buffers.length > 0) this.currentBuffer = buffers[currentIndex];
|
||||
}
|
||||
@@ -36,12 +39,13 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
this.currentIndex = 0;
|
||||
this.currentBuffer = null;
|
||||
this.buffers = null;
|
||||
this.mask = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte currentByte() {
|
||||
return currentBuffer.get(currentBuffer.position());
|
||||
return mask == null ? currentBuffer.get(currentBuffer.position()) : mask.unmask(currentBuffer.get(currentBuffer.position()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,13 +71,13 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
public byte readByte() {
|
||||
if (this.currentBuffer.hasRemaining()) {
|
||||
this.position++;
|
||||
return this.currentBuffer.get();
|
||||
return mask == null ? this.currentBuffer.get() : mask.unmask(this.currentBuffer.get());
|
||||
}
|
||||
for (;;) {
|
||||
this.currentBuffer = this.buffers[++this.currentIndex];
|
||||
if (this.currentBuffer.hasRemaining()) {
|
||||
this.position++;
|
||||
return this.currentBuffer.get();
|
||||
return mask == null ? this.currentBuffer.get() : mask.unmask(this.currentBuffer.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +88,11 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
int remain = this.currentBuffer.remaining();
|
||||
if (remain >= 2) {
|
||||
this.position += 2;
|
||||
return this.currentBuffer.getChar();
|
||||
if (mask == null) {
|
||||
return this.currentBuffer.getChar();
|
||||
} else {
|
||||
return (char) ((0xff00 & (mask.unmask(this.currentBuffer.get()) << 8)) | (0xff & mask.unmask(this.currentBuffer.get())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return (char) ((0xff00 & (readByte() << 8)) | (0xff & readByte()));
|
||||
@@ -96,7 +104,11 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
int remain = this.currentBuffer.remaining();
|
||||
if (remain >= 2) {
|
||||
this.position += 2;
|
||||
return this.currentBuffer.getShort();
|
||||
if (mask == null) {
|
||||
return this.currentBuffer.getShort();
|
||||
} else {
|
||||
return (short) ((0xff00 & (mask.unmask(this.currentBuffer.get()) << 8)) | (0xff & mask.unmask(this.currentBuffer.get())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return (short) ((0xff00 & (readByte() << 8)) | (0xff & readByte()));
|
||||
@@ -108,7 +120,14 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
int remain = this.currentBuffer.remaining();
|
||||
if (remain >= 4) {
|
||||
this.position += 4;
|
||||
return this.currentBuffer.getInt();
|
||||
if (mask == null) {
|
||||
return this.currentBuffer.getInt();
|
||||
} else {
|
||||
return ((mask.unmask(this.currentBuffer.get()) & 0xff) << 24)
|
||||
| ((mask.unmask(this.currentBuffer.get()) & 0xff) << 16)
|
||||
| ((mask.unmask(this.currentBuffer.get()) & 0xff) << 8)
|
||||
| (mask.unmask(this.currentBuffer.get()) & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ((readByte() & 0xff) << 24) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 8) | (readByte() & 0xff);
|
||||
@@ -120,7 +139,18 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
int remain = this.currentBuffer.remaining();
|
||||
if (remain >= 8) {
|
||||
this.position += 8;
|
||||
return this.currentBuffer.getLong();
|
||||
if (mask == null) {
|
||||
return this.currentBuffer.getLong();
|
||||
} else {
|
||||
return ((((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 56)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 48)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 40)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 32)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 24)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 16)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff) << 8)
|
||||
| (((long) mask.unmask(this.currentBuffer.get()) & 0xff)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ((((long) readByte() & 0xff) << 56)
|
||||
@@ -150,9 +180,19 @@ public class BsonByteBufferReader extends BsonReader {
|
||||
if (remain >= len) {
|
||||
this.position += len;
|
||||
this.currentBuffer.get(bs, pos, len);
|
||||
if (mask != null) {
|
||||
for (int i = pos, end = pos + len; i < end; i++) {
|
||||
bs[i] = mask.unmask(bs[i]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.currentBuffer.get(bs, pos, remain);
|
||||
if (mask != null) {
|
||||
for (int i = pos, end = pos + remain; i < end; i++) {
|
||||
bs[i] = mask.unmask(bs[i]);
|
||||
}
|
||||
}
|
||||
this.position += remain;
|
||||
this.currentBuffer = this.buffers[++this.currentIndex];
|
||||
read(bs, pos + remain);
|
||||
|
||||
@@ -61,7 +61,7 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
|
||||
//------------------------------ reader -----------------------------------------------------------
|
||||
public BsonReader pollBsonReader(final ByteBuffer... buffers) {
|
||||
return new BsonByteBufferReader(buffers);
|
||||
return new BsonByteBufferReader((ConvertMask) null, buffers);
|
||||
}
|
||||
|
||||
public BsonReader pollBsonReader(final InputStream in) {
|
||||
@@ -116,7 +116,12 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
|
||||
public <T> T convertFrom(final Type type, final ByteBuffer... buffers) {
|
||||
if (type == null || buffers.length < 1) return null;
|
||||
return (T) factory.loadDecoder(type).convertFrom(new BsonByteBufferReader(buffers));
|
||||
return (T) factory.loadDecoder(type).convertFrom(new BsonByteBufferReader((ConvertMask) null, buffers));
|
||||
}
|
||||
|
||||
public <T> T convertFrom(final Type type, final ConvertMask mask, final ByteBuffer... buffers) {
|
||||
if (type == null || buffers.length < 1) return null;
|
||||
return (T) factory.loadDecoder(type).convertFrom(new BsonByteBufferReader(mask, buffers));
|
||||
}
|
||||
|
||||
public <T> T convertFrom(final Type type, final BsonReader reader) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.io.*;
|
||||
import org.redkale.convert.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
@@ -21,6 +21,7 @@ class BsonStreamReader extends BsonByteBufferReader {
|
||||
private byte currByte;
|
||||
|
||||
protected BsonStreamReader(InputStream in) {
|
||||
super((ConvertMask) null);
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
|
||||
70
src/org/redkale/convert/ext/ByteBufferSimpledCoder.java
Normal file
70
src/org/redkale/convert/ext/ByteBufferSimpledCoder.java
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.convert.ext;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.redkale.convert.Reader;
|
||||
import org.redkale.convert.SimpledCoder;
|
||||
import org.redkale.convert.Writer;
|
||||
|
||||
/**
|
||||
* ByteBuffer 的SimpledCoder实现
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <R> Reader输入的子类型
|
||||
* @param <W> Writer输出的子类型
|
||||
*/
|
||||
public final class ByteBufferSimpledCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, ByteBuffer> {
|
||||
|
||||
public static final ByteBufferSimpledCoder instance = new ByteBufferSimpledCoder();
|
||||
|
||||
@Override
|
||||
public void convertTo(W out, ByteBuffer value) {
|
||||
if (value == null) {
|
||||
out.writeNull();
|
||||
return;
|
||||
}
|
||||
out.writeArrayB(value.remaining());
|
||||
boolean flag = false;
|
||||
for (byte v : value.array()) {
|
||||
if (flag) out.writeArrayMark();
|
||||
out.writeByte(v);
|
||||
flag = true;
|
||||
}
|
||||
out.writeArrayE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer convertFrom(R in) {
|
||||
int len = in.readArrayB();
|
||||
if (len == Reader.SIGN_NULL) return null;
|
||||
if (len == Reader.SIGN_NOLENGTH) {
|
||||
int size = 0;
|
||||
byte[] data = new byte[8];
|
||||
while (in.hasNext()) {
|
||||
if (size >= data.length) {
|
||||
byte[] newdata = new byte[data.length + 4];
|
||||
System.arraycopy(data, 0, newdata, 0, size);
|
||||
data = newdata;
|
||||
}
|
||||
data[size++] = in.readByte();
|
||||
}
|
||||
in.readArrayE();
|
||||
return ByteBuffer.wrap(data, 0, size);
|
||||
} else {
|
||||
byte[] values = new byte[len];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = in.readByte();
|
||||
}
|
||||
in.readArrayE();
|
||||
return ByteBuffer.wrap(values);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
src/org/redkale/convert/ext/FileSimpledCoder.java
Normal file
41
src/org/redkale/convert/ext/FileSimpledCoder.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.convert.ext;
|
||||
|
||||
import java.io.File;
|
||||
import org.redkale.convert.*;
|
||||
|
||||
/**
|
||||
* 文件 的SimpledCoder实现
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <R> Reader输入的子类型
|
||||
* @param <W> Writer输出的子类型
|
||||
*/
|
||||
public class FileSimpledCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, File> {
|
||||
|
||||
public static final PatternSimpledCoder instance = new PatternSimpledCoder();
|
||||
|
||||
@Override
|
||||
public void convertTo(W out, File value) {
|
||||
if (value == null) {
|
||||
out.writeNull();
|
||||
} else {
|
||||
out.writeString(value.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File convertFrom(R in) {
|
||||
String value = in.readString();
|
||||
if (value == null) return null;
|
||||
return new File(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,10 @@ public class JsonByteBufferReader extends JsonReader {
|
||||
|
||||
private ByteBuffer currentBuffer;
|
||||
|
||||
protected JsonByteBufferReader(ByteBuffer... buffers) {
|
||||
protected ConvertMask mask;
|
||||
|
||||
protected JsonByteBufferReader(ConvertMask mask, ByteBuffer... buffers) {
|
||||
this.mask = mask;
|
||||
this.buffers = buffers;
|
||||
if (buffers != null && buffers.length > 0) this.currentBuffer = buffers[currentIndex];
|
||||
}
|
||||
@@ -41,19 +44,20 @@ public class JsonByteBufferReader extends JsonReader {
|
||||
this.currentChar = 0;
|
||||
this.currentBuffer = null;
|
||||
this.buffers = null;
|
||||
this.mask = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected byte nextByte() {
|
||||
if (this.currentBuffer.hasRemaining()) {
|
||||
this.position++;
|
||||
return this.currentBuffer.get();
|
||||
return mask == null ? this.currentBuffer.get() : mask.unmask(this.currentBuffer.get());
|
||||
}
|
||||
for (;;) {
|
||||
this.currentBuffer = this.buffers[++this.currentIndex];
|
||||
if (this.currentBuffer.hasRemaining()) {
|
||||
this.position++;
|
||||
return this.currentBuffer.get();
|
||||
return mask == null ? this.currentBuffer.get() : mask.unmask(this.currentBuffer.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
|
||||
//------------------------------ reader -----------------------------------------------------------
|
||||
public JsonReader pollJsonReader(final ByteBuffer... buffers) {
|
||||
return new JsonByteBufferReader(buffers);
|
||||
return new JsonByteBufferReader((ConvertMask) null, buffers);
|
||||
}
|
||||
|
||||
public JsonReader pollJsonReader(final InputStream in) {
|
||||
@@ -111,7 +111,12 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
|
||||
public <T> T convertFrom(final Type type, final ByteBuffer... buffers) {
|
||||
if (type == null || buffers == null || buffers.length == 0) return null;
|
||||
return (T) factory.loadDecoder(type).convertFrom(new JsonByteBufferReader(buffers));
|
||||
return (T) factory.loadDecoder(type).convertFrom(new JsonByteBufferReader((ConvertMask) null, buffers));
|
||||
}
|
||||
|
||||
public <T> T convertFrom(final Type type, final ConvertMask mask, final ByteBuffer... buffers) {
|
||||
if (type == null || buffers == null || buffers.length == 0) return null;
|
||||
return (T) factory.loadDecoder(type).convertFrom(new JsonByteBufferReader(mask, buffers));
|
||||
}
|
||||
|
||||
public <T> T convertFrom(final Type type, final JsonReader reader) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.redkale.convert.*;
|
||||
/**
|
||||
*
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
class JsonStreamReader extends JsonByteBufferReader {
|
||||
@@ -19,6 +19,7 @@ class JsonStreamReader extends JsonByteBufferReader {
|
||||
private InputStream in;
|
||||
|
||||
protected JsonStreamReader(InputStream in) {
|
||||
super((ConvertMask) null);
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -25,6 +26,12 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
|
||||
protected Object subobject; //用于存储绑定在Connection上的对象, 同attributes, 只绑定单个对象时尽量使用subobject而非attributes
|
||||
|
||||
//关闭数
|
||||
AtomicLong closedCounter = new AtomicLong();
|
||||
|
||||
//在线数
|
||||
AtomicLong livingCounter = new AtomicLong();
|
||||
|
||||
public abstract boolean isTCP();
|
||||
|
||||
public abstract SocketAddress getRemoteAddress();
|
||||
@@ -43,7 +50,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
write(srcs, 0, srcs.length, attachment, handler);
|
||||
}
|
||||
|
||||
protected abstract <A> void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler<Integer, ? super A> handler);
|
||||
public abstract <A> void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler<Integer, ? super A> handler);
|
||||
|
||||
public void dispose() {//同close, 只是去掉throws IOException
|
||||
try {
|
||||
@@ -54,6 +61,14 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closedCounter != null) {
|
||||
closedCounter.incrementAndGet();
|
||||
closedCounter = null;
|
||||
}
|
||||
if (livingCounter != null) {
|
||||
livingCounter.decrementAndGet();
|
||||
livingCounter = null;
|
||||
}
|
||||
if (attributes == null) return;
|
||||
try {
|
||||
for (Object obj : attributes.values()) {
|
||||
@@ -107,11 +122,12 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
* @param group 连接AsynchronousChannelGroup
|
||||
* @param readTimeoutSecond0 读取超时秒数
|
||||
* @param writeTimeoutSecond0 写入超时秒数
|
||||
*
|
||||
* @return 连接
|
||||
* @throws java.io.IOException 异常
|
||||
*/
|
||||
public static AsyncConnection create(final String protocol, final AsynchronousChannelGroup group, final SocketAddress address,
|
||||
final int readTimeoutSecond0, final int writeTimeoutSecond0) throws IOException {
|
||||
final int readTimeoutSecond0, final int writeTimeoutSecond0) throws IOException {
|
||||
if ("TCP".equalsIgnoreCase(protocol)) {
|
||||
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(group);
|
||||
try {
|
||||
@@ -143,7 +159,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
private final boolean client;
|
||||
|
||||
public BIOUDPAsyncConnection(final DatagramChannel ch, SocketAddress addr,
|
||||
final boolean client0, final int readTimeoutSecond0, final int writeTimeoutSecond0) {
|
||||
final boolean client0, final int readTimeoutSecond0, final int writeTimeoutSecond0) {
|
||||
this.channel = ch;
|
||||
this.client = client0;
|
||||
this.readTimeoutSecond = readTimeoutSecond0;
|
||||
@@ -186,7 +202,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <A> void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler<Integer, ? super A> handler) {
|
||||
public <A> void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler<Integer, ? super A> handler) {
|
||||
try {
|
||||
int rs = 0;
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
@@ -213,7 +229,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
public Future<Integer> read(ByteBuffer dst) {
|
||||
try {
|
||||
int rs = channel.read(dst);
|
||||
return new SimpleFuture(rs);
|
||||
return CompletableFuture.completedFuture(rs);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -233,7 +249,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
public Future<Integer> write(ByteBuffer src) {
|
||||
try {
|
||||
int rs = channel.send(src, remoteAddress);
|
||||
return new SimpleFuture(rs);
|
||||
return CompletableFuture.completedFuture(rs);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -257,45 +273,10 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
}
|
||||
|
||||
public static AsyncConnection create(final DatagramChannel ch, SocketAddress addr,
|
||||
final boolean client0, final int readTimeoutSecond0, final int writeTimeoutSecond0) {
|
||||
final boolean client0, final int readTimeoutSecond0, final int writeTimeoutSecond0) {
|
||||
return new BIOUDPAsyncConnection(ch, addr, client0, readTimeoutSecond0, writeTimeoutSecond0);
|
||||
}
|
||||
|
||||
private static class SimpleFuture implements Future<Integer> {
|
||||
|
||||
private final int rs;
|
||||
|
||||
public SimpleFuture(int rs) {
|
||||
this.rs = rs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get() throws InterruptedException, ExecutionException {
|
||||
return rs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return rs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class BIOTCPAsyncConnection extends AsyncConnection {
|
||||
|
||||
private int readTimeoutSecond;
|
||||
@@ -372,7 +353,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <A> void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler<Integer, ? super A> handler) {
|
||||
public <A> void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler<Integer, ? super A> handler) {
|
||||
try {
|
||||
int rs = 0;
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
@@ -398,7 +379,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
public Future<Integer> read(ByteBuffer dst) {
|
||||
try {
|
||||
int rs = readChannel.read(dst);
|
||||
return new SimpleFuture(rs);
|
||||
return CompletableFuture.completedFuture(rs);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -418,7 +399,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
public Future<Integer> write(ByteBuffer src) {
|
||||
try {
|
||||
int rs = writeChannel.write(src);
|
||||
return new SimpleFuture(rs);
|
||||
return CompletableFuture.completedFuture(rs);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -440,6 +421,7 @@ public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCl
|
||||
* 通常用于 ssl socket
|
||||
*
|
||||
* @param socket Socket对象
|
||||
*
|
||||
* @return 连接对象
|
||||
*/
|
||||
public static AsyncConnection create(final Socket socket) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import java.util.logging.*;
|
||||
import org.redkale.convert.bson.*;
|
||||
import org.redkale.convert.json.*;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.*;
|
||||
|
||||
/**
|
||||
* 服务器上下文对象
|
||||
@@ -70,12 +69,8 @@ public class Context {
|
||||
//JSON操作工厂
|
||||
protected final JsonFactory jsonFactory;
|
||||
|
||||
//监控对象
|
||||
protected final WatchFactory watch;
|
||||
|
||||
public Context(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool, ObjectPool<Response> responsePool,
|
||||
final int maxbody, Charset charset, InetSocketAddress address, final PrepareServlet prepare, final WatchFactory watch,
|
||||
final int readTimeoutSecond, final int writeTimeoutSecond) {
|
||||
final int maxbody, Charset charset, InetSocketAddress address, final PrepareServlet prepare, final int readTimeoutSecond, final int writeTimeoutSecond) {
|
||||
this.serverStartTime = serverStartTime;
|
||||
this.logger = logger;
|
||||
this.executor = executor;
|
||||
@@ -86,7 +81,6 @@ public class Context {
|
||||
this.charset = UTF8.equals(charset) ? null : charset;
|
||||
this.address = address;
|
||||
this.prepare = prepare;
|
||||
this.watch = watch;
|
||||
this.readTimeoutSecond = readTimeoutSecond;
|
||||
this.writeTimeoutSecond = writeTimeoutSecond;
|
||||
this.jsonFactory = JsonFactory.root();
|
||||
@@ -133,6 +127,13 @@ public class Context {
|
||||
bufferPool.offer(buffer);
|
||||
}
|
||||
|
||||
public void offerBuffer(ByteBuffer... buffers) {
|
||||
if (buffers == null) return;
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
bufferPool.offer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
55
src/org/redkale/net/Filter.java
Normal file
55
src/org/redkale/net/Filter.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
* 协议拦截器类
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <C> Context的子类型
|
||||
* @param <R> Request的子类型
|
||||
* @param <P> Response的子类型
|
||||
*/
|
||||
public abstract class Filter<C extends Context, R extends Request<C>, P extends Response<C, R>> implements Comparable, Resourcable {
|
||||
|
||||
AnyValue _conf; //当前Filter的配置
|
||||
|
||||
Filter<C, R, P> _next; //下一个Filter
|
||||
|
||||
public void init(C context, AnyValue config) {
|
||||
}
|
||||
|
||||
public abstract void doFilter(R request, P response) throws IOException;
|
||||
|
||||
public void destroy(C context, AnyValue config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 值越小越靠前执行
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if (!(o instanceof Filter)) return 1;
|
||||
return this.getIndex() - ((Filter) o).getIndex();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
@@ -42,6 +43,10 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
|
||||
private Map<K, S> mappings = new HashMap<>();
|
||||
|
||||
private final List<Filter<C, R, P>> filters = new ArrayList<>();
|
||||
|
||||
protected Filter<C, R, P> headFilter;
|
||||
|
||||
protected void putServlet(S servlet) {
|
||||
synchronized (lock1) {
|
||||
Set<S> newservlets = new HashSet<>(servlets);
|
||||
@@ -50,10 +55,42 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
}
|
||||
}
|
||||
|
||||
protected void putMapping(K key, S value) {
|
||||
protected void removeServlet(S servlet) {
|
||||
synchronized (lock1) {
|
||||
Set<S> newservlets = new HashSet<>(servlets);
|
||||
newservlets.remove(servlet);
|
||||
this.servlets = newservlets;
|
||||
}
|
||||
}
|
||||
|
||||
protected void putMapping(K key, S servlet) {
|
||||
synchronized (lock2) {
|
||||
Map<K, S> newmappings = new HashMap<>(mappings);
|
||||
newmappings.put(key, value);
|
||||
newmappings.put(key, servlet);
|
||||
this.mappings = newmappings;
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeMapping(K key) {
|
||||
synchronized (lock2) {
|
||||
if (mappings.containsKey(key)) {
|
||||
Map<K, S> newmappings = new HashMap<>(mappings);
|
||||
newmappings.remove(key);
|
||||
this.mappings = newmappings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeMapping(S servlet) {
|
||||
synchronized (lock2) {
|
||||
List<K> keys = new ArrayList<>();
|
||||
Map<K, S> newmappings = new HashMap<>(mappings);
|
||||
for (Map.Entry<K, S> en : newmappings.entrySet()) {
|
||||
if (en.getValue().equals(servlet)) {
|
||||
keys.add(en.getKey());
|
||||
}
|
||||
}
|
||||
for (K key : keys) newmappings.remove(key);
|
||||
this.mappings = newmappings;
|
||||
}
|
||||
}
|
||||
@@ -62,6 +99,72 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
return mappings.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(C context, AnyValue config) {
|
||||
synchronized (filters) {
|
||||
if (!filters.isEmpty()) {
|
||||
Collections.sort(filters);
|
||||
for (Filter<C, R, P> filter : filters) {
|
||||
filter.init(context, config);
|
||||
}
|
||||
this.headFilter = filters.get(0);
|
||||
Filter<C, R, P> filter = this.headFilter;
|
||||
for (int i = 1; i < filters.size(); i++) {
|
||||
filter._next = filters.get(i);
|
||||
filter = filter._next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(C context, AnyValue config) {
|
||||
synchronized (filters) {
|
||||
if (!filters.isEmpty()) {
|
||||
for (Filter filter : filters) {
|
||||
filter.destroy(context, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addFilter(Filter<C, R, P> filter, AnyValue conf) {
|
||||
filter._conf = conf;
|
||||
synchronized (filters) {
|
||||
this.filters.add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public Filter<C, R, P> removeFilter(Class<? extends Filter<C, R, P>> filterClass) {
|
||||
return removeFilter(f -> filterClass.equals(f.getClass()));
|
||||
}
|
||||
|
||||
public Filter<C, R, P> removeFilter(String filterName) {
|
||||
return removeFilter(f -> filterName.equals(f.resourceName()));
|
||||
}
|
||||
|
||||
public Filter<C, R, P> removeFilter(Predicate<Filter<C, R, P>> predicate) {
|
||||
if (this.headFilter == null || predicate == null) return null;
|
||||
synchronized (filters) {
|
||||
Filter filter = this.headFilter;
|
||||
Filter prev = null;
|
||||
do {
|
||||
if (predicate.test(filter)) break;
|
||||
prev = filter;
|
||||
} while ((filter = filter._next) != null);
|
||||
if (filter != null) {
|
||||
if (prev == null) {
|
||||
this.headFilter = filter._next;
|
||||
} else {
|
||||
prev._next = filter._next;
|
||||
}
|
||||
filter._next = null;
|
||||
this.filters.remove(filter);
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void addServlet(S servlet, Object attachment, AnyValue conf, K... mappings);
|
||||
|
||||
public final void prepare(final ByteBuffer buffer, final R request, final P response) throws IOException {
|
||||
@@ -74,7 +177,9 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
} else if (rs == 0) {
|
||||
response.context.offerBuffer(buffer);
|
||||
request.prepare();
|
||||
this.execute(request, response);
|
||||
response.filter = this.headFilter;
|
||||
response.servlet = this;
|
||||
response.nextEvent();
|
||||
} else {
|
||||
buffer.clear();
|
||||
final AtomicInteger ai = new AtomicInteger(rs);
|
||||
@@ -91,7 +196,9 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
response.context.offerBuffer(buffer);
|
||||
request.prepare();
|
||||
try {
|
||||
execute(request, response);
|
||||
response.filter = PrepareServlet.this.headFilter;
|
||||
response.servlet = PrepareServlet.this;
|
||||
response.nextEvent();
|
||||
} catch (Exception e) {
|
||||
illRequestCounter.incrementAndGet();
|
||||
response.finish(true);
|
||||
|
||||
@@ -11,15 +11,27 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 协议底层Server
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class ProtocolServer {
|
||||
|
||||
//创建数
|
||||
protected final AtomicLong createCounter = new AtomicLong();
|
||||
|
||||
//关闭数
|
||||
protected final AtomicLong closedCounter = new AtomicLong();
|
||||
|
||||
//在线数
|
||||
protected final AtomicLong livingCounter = new AtomicLong();
|
||||
|
||||
public abstract void open() throws IOException;
|
||||
|
||||
public abstract void bind(SocketAddress local, int backlog) throws IOException;
|
||||
@@ -34,6 +46,18 @@ public abstract class ProtocolServer {
|
||||
|
||||
public abstract AsynchronousChannelGroup getChannelGroup();
|
||||
|
||||
public long getCreateCount() {
|
||||
return createCounter.longValue();
|
||||
}
|
||||
|
||||
public long getClosedCount() {
|
||||
return closedCounter.longValue();
|
||||
}
|
||||
|
||||
public long getLivingCount() {
|
||||
return livingCounter.longValue();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
public static ProtocolServer create(String protocol, Context context) {
|
||||
if ("TCP".equalsIgnoreCase(protocol)) return new ProtocolTCPServer(context);
|
||||
@@ -117,6 +141,20 @@ public abstract class ProtocolServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreateCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getClosedCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLivingCount() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProtocolTCPServer extends ProtocolServer {
|
||||
@@ -160,7 +198,12 @@ public abstract class ProtocolServer {
|
||||
@Override
|
||||
public void completed(final AsynchronousSocketChannel channel, Void attachment) {
|
||||
serchannel.accept(null, this);
|
||||
context.submitAsync(new PrepareRunner(context, AsyncConnection.create(channel, null, context.readTimeoutSecond, context.writeTimeoutSecond), null));
|
||||
createCounter.incrementAndGet();
|
||||
livingCounter.incrementAndGet();
|
||||
AsyncConnection conn = AsyncConnection.create(channel, null, context.readTimeoutSecond, context.writeTimeoutSecond);
|
||||
conn.livingCounter = livingCounter;
|
||||
conn.closedCounter = closedCounter;
|
||||
context.submitAsync(new PrepareRunner(context, conn, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.redkale.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -34,6 +35,10 @@ public abstract class Response<C extends Context, R extends Request<C>> {
|
||||
|
||||
protected BiConsumer<R, Response<C, R>> recycleListener;
|
||||
|
||||
protected Filter<C, R, ? extends Response<C, R>> filter;
|
||||
|
||||
protected Servlet<C, R, ? extends Response<C, R>> servlet;
|
||||
|
||||
private final CompletionHandler finishHandler = new CompletionHandler<Integer, ByteBuffer>() {
|
||||
|
||||
@Override
|
||||
@@ -114,6 +119,8 @@ public abstract class Response<C extends Context, R extends Request<C>> {
|
||||
recycleListener = null;
|
||||
}
|
||||
this.output = null;
|
||||
this.filter = null;
|
||||
this.servlet = null;
|
||||
request.recycle();
|
||||
if (channel != null) {
|
||||
if (keepAlive) {
|
||||
@@ -140,10 +147,45 @@ public abstract class Response<C extends Context, R extends Request<C>> {
|
||||
this.request.createtime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
protected void setFilter(Filter<C, R, Response<C, R>> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
protected void thenEvent(Servlet servlet) {
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void nextEvent() throws IOException {
|
||||
if (this.filter != null) {
|
||||
Filter runner = this.filter;
|
||||
this.filter = this.filter._next;
|
||||
runner.doFilter(request, this);
|
||||
return;
|
||||
}
|
||||
if (this.servlet != null) {
|
||||
Servlet s = this.servlet;
|
||||
this.servlet = null;
|
||||
s.execute(request, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 public void recycleListener(BiConsumer recycleListener) 代替
|
||||
*
|
||||
* @param recycleListener BiConsumer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRecycleListener(BiConsumer<R, Response<C, R>> recycleListener) {
|
||||
this.recycleListener = recycleListener;
|
||||
}
|
||||
|
||||
public void recycleListener(BiConsumer<R, Response<C, R>> recycleListener) {
|
||||
this.recycleListener = recycleListener;
|
||||
}
|
||||
|
||||
public Object getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
import org.redkale.util.AnyValue;
|
||||
import org.redkale.watch.WatchFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,9 +38,6 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
//服务的启动时间
|
||||
protected final long serverStartTime;
|
||||
|
||||
//监控对象
|
||||
protected final WatchFactory watch;
|
||||
|
||||
//服务的名称
|
||||
protected String name;
|
||||
|
||||
@@ -93,11 +89,10 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
//IO写入 的超时秒数,小于1视为不设置
|
||||
protected int writeTimeoutSecond;
|
||||
|
||||
protected Server(long serverStartTime, String protocol, PrepareServlet<K, C, R, P, S> servlet, final WatchFactory watch) {
|
||||
protected Server(long serverStartTime, String protocol, PrepareServlet<K, C, R, P, S> servlet) {
|
||||
this.serverStartTime = serverStartTime;
|
||||
this.protocol = protocol;
|
||||
this.prepare = servlet;
|
||||
this.watch = watch;
|
||||
}
|
||||
|
||||
public void init(final AnyValue config) throws Exception {
|
||||
@@ -109,7 +104,8 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
this.readTimeoutSecond = config.getIntValue("readTimeoutSecond", 0);
|
||||
this.writeTimeoutSecond = config.getIntValue("writeTimeoutSecond", 0);
|
||||
this.maxbody = config.getIntValue("maxbody", 64 * 1024);
|
||||
this.bufferCapacity = config.getIntValue("bufferCapacity", 8 * 1024);
|
||||
int bufCapacity = config.getIntValue("bufferCapacity", 8 * 1024);
|
||||
this.bufferCapacity = bufCapacity < 256 ? 256 : bufCapacity;
|
||||
this.threads = config.getIntValue("threads", Runtime.getRuntime().availableProcessors() * 16);
|
||||
this.bufferPoolSize = config.getIntValue("bufferPoolSize", Runtime.getRuntime().availableProcessors() * 512);
|
||||
this.responsePoolSize = config.getIntValue("responsePoolSize", Runtime.getRuntime().availableProcessors() * 256);
|
||||
@@ -161,7 +157,6 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
public void start() throws IOException {
|
||||
this.context = this.createContext();
|
||||
this.prepare.init(this.context, config);
|
||||
if (this.watch != null) this.watch.inject(this.prepare);
|
||||
this.serverChannel = ProtocolServer.create(this.protocol, context);
|
||||
this.serverChannel.open();
|
||||
if (this.serverChannel.supportedOptions().contains(StandardSocketOptions.TCP_NODELAY)) {
|
||||
@@ -190,6 +185,30 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
logger.info(this.getClass().getSimpleName() + " shutdown in " + e + " ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁Servlet
|
||||
*
|
||||
* @param servlet Servlet
|
||||
*/
|
||||
public void destroyServlet(S servlet) {
|
||||
servlet.destroy(context, this.prepare.getServletConf(servlet));
|
||||
}
|
||||
|
||||
//创建数
|
||||
public long getCreateConnectionCount() {
|
||||
return serverChannel == null ? -1 : serverChannel.getCreateCount();
|
||||
}
|
||||
|
||||
//关闭数
|
||||
public long getClosedConnectionCount() {
|
||||
return serverChannel == null ? -1 : serverChannel.getClosedCount();
|
||||
}
|
||||
|
||||
//在线数
|
||||
public long getLivingConnectionCount() {
|
||||
return serverChannel == null ? -1 : serverChannel.getLivingCount();
|
||||
}
|
||||
|
||||
protected Format createFormat() {
|
||||
String sf = "0";
|
||||
if (this.threads > 10) sf = "00";
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.redkale.util.ObjectPool;
|
||||
import org.redkale.watch.WatchFactory;
|
||||
|
||||
/**
|
||||
* 传输客户端
|
||||
@@ -50,8 +49,6 @@ public final class Transport {
|
||||
|
||||
protected final String protocol;
|
||||
|
||||
protected final WatchFactory watch;
|
||||
|
||||
protected final AsynchronousChannelGroup group;
|
||||
|
||||
protected final InetSocketAddress clientAddress;
|
||||
@@ -62,15 +59,14 @@ public final class Transport {
|
||||
|
||||
protected final ConcurrentHashMap<SocketAddress, BlockingQueue<AsyncConnection>> connPool = new ConcurrentHashMap<>();
|
||||
|
||||
public Transport(String name, WatchFactory watch, String subprotocol, final ObjectPool<ByteBuffer> transportBufferPool,
|
||||
public Transport(String name, String subprotocol, final ObjectPool<ByteBuffer> transportBufferPool,
|
||||
final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress, final Collection<InetSocketAddress> addresses) {
|
||||
this(name, DEFAULT_PROTOCOL, watch, subprotocol, transportBufferPool, transportChannelGroup, clientAddress, addresses);
|
||||
this(name, DEFAULT_PROTOCOL, subprotocol, transportBufferPool, transportChannelGroup, clientAddress, addresses);
|
||||
}
|
||||
|
||||
public Transport(String name, String protocol, WatchFactory watch, String subprotocol, final ObjectPool<ByteBuffer> transportBufferPool,
|
||||
public Transport(String name, String protocol, String subprotocol, final ObjectPool<ByteBuffer> transportBufferPool,
|
||||
final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress, final Collection<InetSocketAddress> addresses) {
|
||||
this.name = name;
|
||||
this.watch = watch;
|
||||
this.subprotocol = subprotocol == null ? "" : subprotocol.trim();
|
||||
this.protocol = protocol;
|
||||
this.tcp = "TCP".equalsIgnoreCase(protocol);
|
||||
@@ -92,7 +88,7 @@ public final class Transport {
|
||||
if (first == null) throw new NullPointerException("Collection<Transport> is null or empty");
|
||||
//必须按字母排列顺序确保,相同内容的transport列表组合的name相同,而不会因为list的顺序不同产生不同的name
|
||||
this.name = tmpgroup.stream().sorted().collect(Collectors.joining(";"));
|
||||
this.watch = first.watch;
|
||||
//this.watch = first.watch;
|
||||
this.subprotocol = first.subprotocol;
|
||||
this.protocol = first.protocol;
|
||||
this.tcp = "TCP".equalsIgnoreCase(first.protocol);
|
||||
|
||||
165
src/org/redkale/net/TransportFactory.java
Normal file
165
src/org/redkale/net/TransportFactory.java
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousChannelGroup;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.ObjectPool;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class TransportFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(TransportFactory.class.getSimpleName());
|
||||
|
||||
//传输端的线程池
|
||||
protected final ExecutorService executor;
|
||||
|
||||
//传输端的ByteBuffer对象池
|
||||
protected final ObjectPool<ByteBuffer> bufferPool;
|
||||
|
||||
//传输端的ChannelGroup
|
||||
protected final AsynchronousChannelGroup channelGroup;
|
||||
|
||||
//每个地址对应的Group名
|
||||
protected final Map<InetSocketAddress, String> groupAddrs = new HashMap<>();
|
||||
|
||||
//协议地址的Group集合
|
||||
protected final Map<String, TransportGroupInfo> groupInfos = new HashMap<>();
|
||||
|
||||
protected final List<WeakReference<Service>> services = new CopyOnWriteArrayList<>();
|
||||
|
||||
public TransportFactory(ExecutorService executor, ObjectPool<ByteBuffer> bufferPool, AsynchronousChannelGroup channelGroup) {
|
||||
this.executor = executor;
|
||||
this.bufferPool = bufferPool;
|
||||
this.channelGroup = channelGroup;
|
||||
}
|
||||
|
||||
public String findGroupName(InetSocketAddress addr) {
|
||||
if (addr == null) return null;
|
||||
return groupAddrs.get(addr);
|
||||
}
|
||||
|
||||
public TransportGroupInfo findGroupInfo2(String group) {
|
||||
if (group == null) return null;
|
||||
return groupInfos.get(group);
|
||||
}
|
||||
|
||||
public TransportFactory addGroupInfo(String name, InetSocketAddress... addrs) {
|
||||
addGroupInfo(new TransportGroupInfo(name, addrs));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransportFactory addGroupInfo(String name, Set<InetSocketAddress> addrs) {
|
||||
addGroupInfo(new TransportGroupInfo(name, addrs));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean addGroupInfo(TransportGroupInfo info) {
|
||||
if (info == null) throw new RuntimeException("TransportGroupInfo can not null");
|
||||
if (info.addresses == null) throw new RuntimeException("TransportGroupInfo.addresses can not null");
|
||||
if (!checkName(info.name)) throw new RuntimeException("Transport.group.name only 0-9 a-z A-Z _ cannot begin 0-9");
|
||||
TransportGroupInfo old = groupInfos.get(info.name);
|
||||
if (old != null && !old.protocol.equals(info.protocol)) throw new RuntimeException("Transport.group.name repeat but protocol is different");
|
||||
if (old != null && !old.subprotocol.equals(info.subprotocol)) throw new RuntimeException("Transport.group.name repeat but subprotocol is different");
|
||||
for (InetSocketAddress addr : info.addresses) {
|
||||
if (!groupAddrs.getOrDefault(addr, info.name).equals(info.name)) throw new RuntimeException(addr + " repeat but different group.name");
|
||||
}
|
||||
if (old == null) {
|
||||
groupInfos.put(info.name, info);
|
||||
} else {
|
||||
old.putAddress(info.addresses);
|
||||
}
|
||||
for (InetSocketAddress addr : info.addresses) {
|
||||
groupAddrs.put(addr, info.name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Transport loadSameGroupTransport(InetSocketAddress sncpAddress) {
|
||||
return loadTransport(groupAddrs.get(sncpAddress), sncpAddress);
|
||||
}
|
||||
|
||||
public Transport[] loadDiffGroupTransports(InetSocketAddress sncpAddress, final Set<String> diffGroups) {
|
||||
if (diffGroups == null) return null;
|
||||
final String sncpGroup = groupAddrs.get(sncpAddress);
|
||||
final List<Transport> transports = new ArrayList<>();
|
||||
for (String group : diffGroups) {
|
||||
if (sncpGroup == null || !sncpGroup.equals(group)) {
|
||||
transports.add(loadTransport(group, sncpAddress));
|
||||
}
|
||||
}
|
||||
return transports.toArray(new Transport[transports.size()]);
|
||||
}
|
||||
|
||||
public Transport loadRemoteTransport(InetSocketAddress sncpAddress, final Set<String> groups) {
|
||||
if (groups == null) return null;
|
||||
Set<InetSocketAddress> addresses = new HashSet<>();
|
||||
TransportGroupInfo info = null;
|
||||
for (String group : groups) {
|
||||
info = groupInfos.get(group);
|
||||
if (info == null) continue;
|
||||
addresses.addAll(info.addresses);
|
||||
}
|
||||
if (info == null) return null;
|
||||
if (sncpAddress != null) addresses.remove(sncpAddress);
|
||||
return new Transport("remotes", info.protocol, info.subprotocol, this.bufferPool, this.channelGroup, sncpAddress, addresses);
|
||||
}
|
||||
|
||||
private Transport loadTransport(final String groupName, InetSocketAddress sncpAddress) {
|
||||
if (groupName == null) return null;
|
||||
TransportGroupInfo info = groupInfos.get(groupName);
|
||||
if (info == null) return null;
|
||||
return new Transport(groupName, info.protocol, info.subprotocol, this.bufferPool, this.channelGroup, sncpAddress, info.addresses);
|
||||
}
|
||||
|
||||
public ExecutorService getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public void addSncpService(Service service) {
|
||||
if (service == null) return;
|
||||
services.add(new WeakReference<>(service));
|
||||
}
|
||||
|
||||
public List<Service> getServices() {
|
||||
List<Service> rs = new ArrayList<>();
|
||||
for (WeakReference<Service> ref : services) {
|
||||
Service service = ref.get();
|
||||
if (service != null) rs.add(service);
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public void shutdownNow() {
|
||||
try {
|
||||
this.channelGroup.shutdownNow();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.FINER, "close transportChannelGroup erroneous", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkName(String name) { //不能含特殊字符
|
||||
if (name.isEmpty()) return false;
|
||||
if (name.charAt(0) >= '0' && name.charAt(0) <= '9') return false;
|
||||
for (char ch : name.toCharArray()) {
|
||||
if (!((ch >= '0' && ch <= '9') || ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) { //不能含特殊字符
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
113
src/org/redkale/net/TransportGroupInfo.java
Normal file
113
src/org/redkale/net/TransportGroupInfo.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.util.Utility;
|
||||
|
||||
/**
|
||||
* 协议地址组合对象, 对应application.xml 中 resources->group 节点信息
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class TransportGroupInfo {
|
||||
|
||||
protected String name; //地址
|
||||
|
||||
protected String protocol; //协议 取值范围: TCP、UDP
|
||||
|
||||
protected String subprotocol; //子协议,预留使用
|
||||
|
||||
protected Set<InetSocketAddress> addresses; //地址列表, 对应 resources->group->node节点信息
|
||||
|
||||
public TransportGroupInfo() {
|
||||
}
|
||||
|
||||
public TransportGroupInfo(String name, InetSocketAddress... addrs) {
|
||||
this(name, "TCP", "", Utility.ofSet(addrs));
|
||||
}
|
||||
|
||||
public TransportGroupInfo(String name, Set<InetSocketAddress> addrs) {
|
||||
this(name, "TCP", "", addrs);
|
||||
}
|
||||
|
||||
public TransportGroupInfo(String name, String protocol, String subprotocol, InetSocketAddress... addrs) {
|
||||
this(name, protocol, subprotocol, Utility.ofSet(addrs));
|
||||
}
|
||||
|
||||
public TransportGroupInfo(String name, String protocol, String subprotocol, Set<InetSocketAddress> addrs) {
|
||||
Objects.requireNonNull(name, "Transport.group.name can not null");
|
||||
this.name = name;
|
||||
this.protocol = protocol == null ? "TCP" : protocol;
|
||||
this.subprotocol = subprotocol == null ? "" : subprotocol;
|
||||
this.addresses = addrs;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
Objects.requireNonNull(name, "Transport.group.name can not null");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol == null ? "TCP" : protocol;
|
||||
}
|
||||
|
||||
public String getSubprotocol() {
|
||||
return subprotocol;
|
||||
}
|
||||
|
||||
public void setSubprotocol(String subprotocol) {
|
||||
this.subprotocol = subprotocol == null ? "" : subprotocol;
|
||||
this.subprotocol = subprotocol;
|
||||
}
|
||||
|
||||
public Set<InetSocketAddress> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public Set<InetSocketAddress> copyAddresses() {
|
||||
return addresses == null ? null : new LinkedHashSet<>(addresses);
|
||||
}
|
||||
|
||||
public void setAddresses(Set<InetSocketAddress> addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public boolean containsAddress(InetSocketAddress addr) {
|
||||
if (this.addresses == null) return false;
|
||||
return this.addresses.contains(addr);
|
||||
}
|
||||
|
||||
public void putAddress(InetSocketAddress addr) {
|
||||
if (addr == null) return;
|
||||
if (this.addresses == null) this.addresses = new HashSet<>();
|
||||
this.addresses.add(addr);
|
||||
}
|
||||
|
||||
public void putAddress(Set<InetSocketAddress> addrs) {
|
||||
if (addrs == null) return;
|
||||
if (this.addresses == null) this.addresses = new HashSet<>();
|
||||
this.addresses.addAll(addrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonConvert.root().convertTo(this);
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@ package org.redkale.net.http;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 默认Servlet, 没有配置RestHttpServlet实现类则使用该默认类
|
||||
* 默认Servlet, 没有配置RestServlet实现类则使用该默认类
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultRestServlet extends RestHttpServlet<Object> {
|
||||
|
||||
@Override
|
||||
@@ -21,9 +23,4 @@ public class DefaultRestServlet extends RestHttpServlet<Object> {
|
||||
return new Object();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(int moduleid, int actionid, HttpRequest request, HttpResponse response, HttpServlet next) throws IOException {
|
||||
next.execute(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,31 +22,31 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import org.redkale.service.RetResult;
|
||||
|
||||
/**
|
||||
* 直接只用HttpServlet代替, 将在1.9版本移除
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class HttpBaseServlet extends HttpServlet {
|
||||
|
||||
public static final int RET_SERVER_ERROR = 1800_0001;
|
||||
|
||||
public static final int RET_METHOD_ERROR = 1800_0002;
|
||||
|
||||
/**
|
||||
* 配合 HttpBaseServlet 使用。
|
||||
* 当标记为 @AuthIgnore 的方法在执行execute之前不会调用authenticate 方法。
|
||||
* 使用 org.redkale.util.AuthIgnore 代替
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD, TYPE})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface AuthIgnore {
|
||||
|
||||
}
|
||||
@@ -66,18 +66,20 @@ public abstract class HttpBaseServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配合 @WebMapping 使用。
|
||||
* 用于对@WebMapping方法中参数描述
|
||||
*
|
||||
* 使用 org.redkale.net.http.HttpParam 代替
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Repeatable(WebParams.class)
|
||||
@Deprecated
|
||||
protected @interface WebParam {
|
||||
|
||||
String name(); //参数名
|
||||
@@ -102,16 +104,17 @@ public abstract class HttpBaseServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 WebMapping 替代。
|
||||
* 使用 org.redkale.net.http.HttpMapping 替代。
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Deprecated
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface WebAction {
|
||||
|
||||
int actionid() default 0;
|
||||
@@ -130,17 +133,18 @@ public abstract class HttpBaseServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配合 HttpBaseServlet 使用。
|
||||
* 用于对@WebServlet对应的url进行细分。 其url必须是包含WebServlet中定义的前缀, 且不能是正则表达式
|
||||
* 使用 org.redkale.net.http.HttpMapping 替代。
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface WebMapping {
|
||||
|
||||
int actionid() default 0;
|
||||
@@ -159,18 +163,15 @@ public abstract class HttpBaseServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配合 HttpBaseServlet 使用。
|
||||
* 当标记为 @HttpCacheable 的方法使用response.finish的参数将被缓存一段时间(默认值 seconds=15秒)。
|
||||
* 通常情况下 @HttpCacheable 需要与 @AuthIgnore 一起使用,没有标记@AuthIgnore的方法一般输出的结果与当前用户信息有关。
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
* 使用 org.redkale.net.http.HttpMapping.cacheseconds 替代。
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface HttpCacheable {
|
||||
|
||||
/**
|
||||
@@ -186,7 +187,7 @@ public abstract class HttpBaseServlet extends HttpServlet {
|
||||
private final HttpServlet authSuccessServlet = new HttpServlet() {
|
||||
@Override
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
Entry entry = (Entry) request.attachment;
|
||||
Entry entry = (Entry) request.getAttribute("_redkale_entry");
|
||||
if (entry.cacheseconds > 0) {//有缓存设置
|
||||
CacheEntry ce = entry.cache.get(request.getRequestURI());
|
||||
if (ce != null && ce.time + entry.cacheseconds > System.currentTimeMillis()) { //缓存有效
|
||||
@@ -211,11 +212,11 @@ public abstract class HttpBaseServlet extends HttpServlet {
|
||||
response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error"));
|
||||
return;
|
||||
}
|
||||
request.attachment = entry;
|
||||
request.setAttribute("_redkale_entry", entry);
|
||||
if (entry.ignore) {
|
||||
authSuccessServlet.execute(request, response);
|
||||
} else {
|
||||
authenticate(entry.moduleid, entry.actionid, request, response, authSuccessServlet);
|
||||
HttpBaseServlet.this.authenticate(entry.moduleid, entry.actionid, request, response, authSuccessServlet);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import java.nio.charset.*;
|
||||
import java.security.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.*;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.*;
|
||||
|
||||
/**
|
||||
* HTTP服务的上下文对象
|
||||
@@ -27,11 +28,13 @@ public class HttpContext extends Context {
|
||||
|
||||
protected final SecureRandom random = new SecureRandom();
|
||||
|
||||
protected final ConcurrentHashMap<Class, Creator> asyncHandlerCreators = new ConcurrentHashMap<>();
|
||||
|
||||
public HttpContext(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool,
|
||||
ObjectPool<Response> responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare,
|
||||
WatchFactory watch, int readTimeoutSecond, int writeTimeoutSecond) {
|
||||
int readTimeoutSecond, int writeTimeoutSecond) {
|
||||
super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset,
|
||||
address, prepare, watch, readTimeoutSecond, writeTimeoutSecond);
|
||||
address, prepare, readTimeoutSecond, writeTimeoutSecond);
|
||||
|
||||
random.setSeed(Math.abs(System.nanoTime()));
|
||||
}
|
||||
@@ -42,10 +45,6 @@ public class HttpContext extends Context {
|
||||
return new String(Utility.binToHex(bytes));
|
||||
}
|
||||
|
||||
protected WatchFactory getWatchFactory() {
|
||||
return watch;
|
||||
}
|
||||
|
||||
protected ExecutorService getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
@@ -54,4 +53,113 @@ public class HttpContext extends Context {
|
||||
return responsePool;
|
||||
}
|
||||
|
||||
protected <H extends AsyncHandler> Creator<H> loadAsyncHandlerCreator(Class<H> handlerClass) {
|
||||
Creator<H> creator = asyncHandlerCreators.get(handlerClass);
|
||||
if (creator == null) {
|
||||
creator = createAsyncHandlerCreator(handlerClass);
|
||||
asyncHandlerCreators.put(handlerClass, creator);
|
||||
}
|
||||
return creator;
|
||||
}
|
||||
|
||||
private <H extends AsyncHandler> Creator<H> createAsyncHandlerCreator(Class<H> handlerClass) {
|
||||
//生成规则与SncpAsyncHandler.Factory 很类似
|
||||
//-------------------------------------------------------------
|
||||
final boolean handlerinterface = handlerClass.isInterface();
|
||||
final String handlerClassName = handlerClass.getName().replace('.', '/');
|
||||
final String handlerName = AsyncHandler.class.getName().replace('.', '/');
|
||||
final String handlerDesc = Type.getDescriptor(AsyncHandler.class);
|
||||
final String newDynName = handlerClass.getName().replace('.', '/') + "_Dync" + AsyncHandler.class.getSimpleName() + "_" + (System.currentTimeMillis() % 10000);
|
||||
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||
FieldVisitor fv;
|
||||
AsmMethodVisitor mv;
|
||||
AnnotationVisitor av0;
|
||||
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, newDynName, null, handlerinterface ? "java/lang/Object" : handlerClassName, handlerinterface ? new String[]{handlerClassName} : new String[]{handlerName});
|
||||
|
||||
{ //handler 属性
|
||||
fv = cw.visitField(ACC_PRIVATE, "handler", handlerDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{//构造方法
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "<init>", "(" + handlerDesc + ")V", null, null));
|
||||
//mv.setDebug(true);
|
||||
{
|
||||
av0 = mv.visitAnnotation("Ljava/beans/ConstructorProperties;", true);
|
||||
{
|
||||
AnnotationVisitor av1 = av0.visitArray("value");
|
||||
av1.visit(null, "handler");
|
||||
av1.visitEnd();
|
||||
}
|
||||
av0.visitEnd();
|
||||
}
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, handlerinterface ? "java/lang/Object" : handlerClassName, "<init>", "()V", false);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitFieldInsn(PUTFIELD, newDynName, "handler", handlerDesc);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(2, 2);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
for (java.lang.reflect.Method method : handlerClass.getMethods()) { //
|
||||
if ("completed".equals(method.getName()) && method.getParameterCount() == 2) {
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "completed", Type.getMethodDescriptor(method), null, null));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, "handler", handlerDesc);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, handlerName, "completed", "(Ljava/lang/Object;Ljava/lang/Object;)V", true);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
} else if ("failed".equals(method.getName()) && method.getParameterCount() == 2) {
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "failed", Type.getMethodDescriptor(method), null, null));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, "handler", handlerDesc);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, handlerName, "failed", "(Ljava/lang/Throwable;Ljava/lang/Object;)V", true);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
} else if (handlerinterface || java.lang.reflect.Modifier.isAbstract(method.getModifiers())) {
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, null));
|
||||
Class returnType = method.getReturnType();
|
||||
if (returnType == void.class) {
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 1);
|
||||
} else if (returnType.isPrimitive()) {
|
||||
mv.visitInsn(ICONST_0);
|
||||
if (returnType == long.class) {
|
||||
mv.visitInsn(LRETURN);
|
||||
mv.visitMaxs(2, 1);
|
||||
} else if (returnType == float.class) {
|
||||
mv.visitInsn(FRETURN);
|
||||
mv.visitMaxs(2, 1);
|
||||
} else if (returnType == double.class) {
|
||||
mv.visitInsn(DRETURN);
|
||||
mv.visitMaxs(2, 1);
|
||||
} else {
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
}
|
||||
} else {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
}
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
cw.visitEnd();
|
||||
byte[] bytes = cw.toByteArray();
|
||||
Class<AsyncHandler> newHandlerClazz = (Class<AsyncHandler>) new ClassLoader(handlerClass.getClassLoader()) {
|
||||
public final Class<?> loadClass(String name, byte[] b) {
|
||||
return defineClass(name, b, 0, b.length);
|
||||
}
|
||||
}.loadClass(newDynName.replace('/', '.'), bytes);
|
||||
return (Creator<H>) Creator.create(newHandlerClazz);
|
||||
}
|
||||
}
|
||||
|
||||
20
src/org/redkale/net/http/HttpFilter.java
Normal file
20
src/org/redkale/net/http/HttpFilter.java
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import org.redkale.net.Filter;
|
||||
|
||||
/**
|
||||
* HTTP 过滤器 <br>
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class HttpFilter extends Filter<HttpContext, HttpRequest, HttpResponse> {
|
||||
|
||||
}
|
||||
85
src/org/redkale/net/http/HttpMapping.java
Normal file
85
src/org/redkale/net/http/HttpMapping.java
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 配合 HttpServlet 使用。
|
||||
* 用于对@WebServlet对应的url进行细分。 其url必须是包含WebServlet中定义的前缀, 且不能是正则表达式
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface HttpMapping {
|
||||
|
||||
/**
|
||||
* 操作ID值,鉴权时用到
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int actionid() default 0;
|
||||
|
||||
String url();
|
||||
|
||||
/**
|
||||
* 结果缓存的秒数, 为0表示不缓存 <br>
|
||||
* * 当值大于0,将被缓存一段时间(默认值 seconds=15秒)。 <br>
|
||||
* 通常情况下需要 auth() == true 才使用,没有标记auth==true方法一般输出的结果与当前用户信息有关。 <br>
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int cacheseconds() default 0;
|
||||
|
||||
/**
|
||||
* 是否鉴权,默认需要鉴权 <br>
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
boolean auth() default true;
|
||||
|
||||
/**
|
||||
* 允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法
|
||||
*
|
||||
* @return String[]
|
||||
*/
|
||||
String[] methods() default {};
|
||||
|
||||
/**
|
||||
* 是否能被继承, 当 HttpServlet 被继承后该方法是否能被子类继承
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
boolean inherited() default true;
|
||||
|
||||
/**
|
||||
* 输出结果的数据类型
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String result() default "Object";
|
||||
|
||||
/**
|
||||
* 输出结果的数据类型集合,由于结果类型可能是泛型而注解的参数值不支持泛型,因此加入明细数据类型集合
|
||||
*
|
||||
* @return Class[]
|
||||
*/
|
||||
Class[] results() default {};
|
||||
|
||||
/**
|
||||
* 备注描述
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
61
src/org/redkale/net/http/HttpParam.java
Normal file
61
src/org/redkale/net/http/HttpParam.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 配合 @HttpMapping 使用。
|
||||
* 用于对@HttpMapping方法中参数描述
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Repeatable(HttpParam.HttpParams.class)
|
||||
public @interface HttpParam {
|
||||
|
||||
String name(); //参数名
|
||||
|
||||
Class type(); //参数的数据类型
|
||||
|
||||
String comment() default ""; //备注描述
|
||||
|
||||
HttpParamSourceType src() default HttpParamSourceType.PARAMETER; //参数来源类型
|
||||
|
||||
int radix() default 10; //转换数字byte/short/int/long时所用的进制数, 默认10进制
|
||||
|
||||
boolean required() default true; //参数是否必传
|
||||
|
||||
/**
|
||||
* 配合 @HttpParam 使用。
|
||||
* 用于对@HttpParam中参数的来源类型
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public static enum HttpParamSourceType {
|
||||
|
||||
PARAMETER, HEADER, COOKIE;
|
||||
}
|
||||
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@interface HttpParams {
|
||||
|
||||
HttpParam[] value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,13 +8,13 @@ package org.redkale.net.http;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.function.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.regex.*;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.http.Rest.RestDynSourceType;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.*;
|
||||
|
||||
/**
|
||||
* HTTP Servlet的总入口,请求在HttpPrepareServlet中进行分流。 <br>
|
||||
@@ -29,29 +29,180 @@ public class HttpPrepareServlet extends PrepareServlet<String, HttpContext, Http
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
protected SimpleEntry<Predicate<String>, HttpServlet>[] regArray = new SimpleEntry[0];
|
||||
|
||||
protected HttpServlet resourceHttpServlet = new HttpResourceServlet();
|
||||
|
||||
protected MappingEntry[] regArray = null; //regArray 包含 regWsArray
|
||||
|
||||
protected MappingEntry[] regWsArray = null;
|
||||
|
||||
protected Map<String, WebSocketServlet> wsmappings = new HashMap<>(); //super.mappings 包含 wsmappings
|
||||
|
||||
protected final Map<String, Class> allMapStrings = new HashMap<>();
|
||||
|
||||
private final Object excludeLock = new Object();
|
||||
|
||||
private Map<String, BiPredicate<String, String>> forbidURIMaps; //禁用的URL的正则表达式, 必须与 forbidURIPredicates 保持一致
|
||||
|
||||
private BiPredicate<String, String>[] forbidURIPredicates; //禁用的URL的Predicate, 必须与 forbidURIMaps 保持一致
|
||||
|
||||
private List<HttpServlet> removeHttpServlet(final Predicate<MappingEntry> predicateEntry, final Predicate<Map.Entry<String, WebSocketServlet>> predicateFilter) {
|
||||
List<HttpServlet> servlets = new ArrayList<>();
|
||||
synchronized (allMapStrings) {
|
||||
List<String> keys = new ArrayList<>();
|
||||
if (regArray != null) {
|
||||
for (MappingEntry me : regArray) {
|
||||
if (predicateEntry.test(me)) {
|
||||
servlets.add(me.servlet);
|
||||
keys.add(me.mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (regWsArray != null) {
|
||||
for (MappingEntry me : regWsArray) {
|
||||
if (predicateEntry.test(me)) {
|
||||
servlets.add(me.servlet);
|
||||
keys.add(me.mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, WebSocketServlet> newwsmappings = new HashMap<>();
|
||||
for (Map.Entry<String, WebSocketServlet> en : wsmappings.entrySet()) {
|
||||
if (predicateFilter.test(en)) {
|
||||
servlets.add(en.getValue());
|
||||
keys.add(en.getKey());
|
||||
} else {
|
||||
newwsmappings.put(en.getKey(), en.getValue());
|
||||
}
|
||||
}
|
||||
if (newwsmappings.size() != wsmappings.size()) this.wsmappings = newwsmappings;
|
||||
if (!keys.isEmpty()) {
|
||||
this.regArray = Utility.remove(this.regArray, predicateEntry);
|
||||
this.regWsArray = Utility.remove(this.regWsArray, predicateEntry);
|
||||
for (HttpServlet rs : servlets) {
|
||||
super.removeServlet(rs);
|
||||
}
|
||||
for (String key : keys) {
|
||||
super.removeMapping(key);
|
||||
allMapStrings.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return servlets;
|
||||
}
|
||||
|
||||
public HttpServlet removeHttpServlet(final HttpServlet servlet) {
|
||||
Predicate<MappingEntry> predicateEntry = (t) -> t.servlet == servlet;
|
||||
Predicate<Map.Entry<String, WebSocketServlet>> predicateFilter = (t) -> t.getValue() == servlet;
|
||||
removeHttpServlet(predicateEntry, predicateFilter);
|
||||
return servlet;
|
||||
}
|
||||
|
||||
public <T extends HttpServlet> HttpServlet removeHttpServlet(Service service) {
|
||||
Predicate<MappingEntry> predicateEntry = (t) -> {
|
||||
if (!Rest.isRestDyn(t.servlet)) return false;
|
||||
Service s = Rest.getService(t.servlet);
|
||||
if (s == service) return true;
|
||||
if (s != null) return false;
|
||||
Map<String, Service> map = Rest.getServiceMap(t.servlet);
|
||||
if (map == null) return false;
|
||||
boolean rs = map.values().contains(service);
|
||||
if (rs && map.size() == 1) return true;
|
||||
if (rs && map.size() > 1) {
|
||||
String key = null;
|
||||
for (Map.Entry<String, Service> en : map.entrySet()) {
|
||||
if (en.getValue() == service) {
|
||||
key = en.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (key != null) map.remove(key);
|
||||
return false; //还有其他Resouce.name 的Service
|
||||
}
|
||||
return rs;
|
||||
};
|
||||
Predicate<Map.Entry<String, WebSocketServlet>> predicateFilter = null;
|
||||
List<HttpServlet> list = removeHttpServlet(predicateEntry, predicateFilter);
|
||||
return list == null || list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
public <T extends WebSocket> HttpServlet removeHttpServlet(Class<T> websocketOrServletType) {
|
||||
Predicate<MappingEntry> predicateEntry = (t) -> {
|
||||
Class type = t.servlet.getClass();
|
||||
if (type == websocketOrServletType) return true;
|
||||
RestDynSourceType rdt = (RestDynSourceType) type.getAnnotation(RestDynSourceType.class);
|
||||
return (rdt != null && rdt.value() == websocketOrServletType);
|
||||
};
|
||||
Predicate<Map.Entry<String, WebSocketServlet>> predicateFilter = (t) -> {
|
||||
Class type = t.getValue().getClass();
|
||||
if (type == websocketOrServletType) return true;
|
||||
RestDynSourceType rdt = (RestDynSourceType) type.getAnnotation(RestDynSourceType.class);
|
||||
return (rdt != null && rdt.value() == websocketOrServletType);
|
||||
};
|
||||
List<HttpServlet> list = removeHttpServlet(predicateEntry, predicateFilter);
|
||||
return list == null || list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
public boolean addForbidURIReg(final String urlreg) {
|
||||
if (urlreg == null || urlreg.isEmpty()) return false;
|
||||
synchronized (excludeLock) {
|
||||
if (forbidURIMaps != null && forbidURIMaps.containsKey(urlreg)) return false;
|
||||
if (forbidURIMaps == null) forbidURIMaps = new HashMap<>();
|
||||
String mapping = urlreg;
|
||||
if (Utility.contains(mapping, '.', '*', '{', '[', '(', '|', '^', '$', '+', '?', '\\')) { //是否是正则表达式))
|
||||
if (mapping.endsWith("/*")) {
|
||||
mapping = mapping.substring(0, mapping.length() - 1) + ".*";
|
||||
} else {
|
||||
mapping = mapping + "$";
|
||||
}
|
||||
}
|
||||
final String reg = mapping;
|
||||
final boolean begin = mapping.charAt(0) == '^';
|
||||
final Predicate regPredicate = Pattern.compile(reg).asPredicate();
|
||||
BiPredicate<String, String> predicate = (prefix, uri) -> {
|
||||
return begin || prefix.isEmpty() ? regPredicate.test(uri) : uri.matches(prefix + reg);
|
||||
};
|
||||
forbidURIMaps.put(urlreg, predicate);
|
||||
forbidURIPredicates = Utility.append(forbidURIPredicates, predicate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeForbidURIReg(final String urlreg) {
|
||||
if (urlreg == null || urlreg.isEmpty()) return false;
|
||||
synchronized (excludeLock) {
|
||||
if (forbidURIMaps == null || forbidURIPredicates == null || !forbidURIMaps.containsKey(urlreg)) return false;
|
||||
BiPredicate<String, String> predicate = forbidURIMaps.get(urlreg);
|
||||
forbidURIMaps.remove(urlreg);
|
||||
int index = -1;
|
||||
for (int i = 0; i < forbidURIPredicates.length; i++) {
|
||||
if (forbidURIPredicates[i] == predicate) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index > -1) {
|
||||
if (forbidURIPredicates.length == 1) {
|
||||
forbidURIPredicates = null;
|
||||
} else {
|
||||
int newlen = forbidURIPredicates.length - 1;
|
||||
BiPredicate[] news = new BiPredicate[newlen];
|
||||
System.arraycopy(forbidURIPredicates, 0, news, 0, index);
|
||||
System.arraycopy(forbidURIPredicates, index + 1, news, index, newlen - index);
|
||||
forbidURIPredicates = news;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(HttpContext context, AnyValue config) {
|
||||
super.init(context, config); //必须要执行
|
||||
Collection<HttpServlet> servlets = getServlets();
|
||||
servlets.forEach(s -> {
|
||||
if (s instanceof WebSocketServlet) {
|
||||
((WebSocketServlet) s).preInit(context, getServletConf(s));
|
||||
} else if (s instanceof HttpBaseServlet) {
|
||||
((HttpBaseServlet) s).preInit(context, getServletConf(s));
|
||||
}
|
||||
s.preInit(context, getServletConf(s));
|
||||
s.init(context, getServletConf(s));
|
||||
});
|
||||
final WatchFactory watch = context.getWatchFactory();
|
||||
if (watch != null) {
|
||||
servlets.forEach(s -> {
|
||||
watch.inject(s);
|
||||
});
|
||||
}
|
||||
AnyValue resConfig = config.getAnyValue("resource-servlet");
|
||||
if ((resConfig instanceof DefaultAnyValue) && resConfig.getValue("webroot", "").isEmpty()) {
|
||||
((DefaultAnyValue) resConfig).addValue("webroot", config.getValue("root"));
|
||||
@@ -83,17 +234,48 @@ public class HttpPrepareServlet extends PrepareServlet<String, HttpContext, Http
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
try {
|
||||
final String uri = request.getRequestURI();
|
||||
Servlet<HttpContext, HttpRequest, HttpResponse> servlet = mappingServlet(uri);
|
||||
if (servlet == null && this.regArray != null) {
|
||||
for (SimpleEntry<Predicate<String>, HttpServlet> en : regArray) {
|
||||
if (en.getKey().test(uri)) {
|
||||
servlet = en.getValue();
|
||||
HttpServlet servlet;
|
||||
if (request.isWebSocket()) {
|
||||
servlet = wsmappings.get(uri);
|
||||
if (servlet == null && this.regWsArray != null) {
|
||||
for (MappingEntry en : regWsArray) {
|
||||
if (en.predicate.test(uri)) {
|
||||
servlet = en.servlet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (servlet == null) {
|
||||
response.finish(500, null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
servlet = mappingServlet(uri);
|
||||
if (servlet == null && this.regArray != null) {
|
||||
for (MappingEntry en : regArray) {
|
||||
if (en.predicate.test(uri)) {
|
||||
servlet = en.servlet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//找不到匹配的HttpServlet则使用静态资源HttpResourceServlet
|
||||
if (servlet == null) servlet = this.resourceHttpServlet;
|
||||
}
|
||||
boolean forbid = false;
|
||||
BiPredicate<String, String>[] forbidUrlPredicates = this.forbidURIPredicates;
|
||||
if (forbidUrlPredicates != null && forbidUrlPredicates.length > 0) {
|
||||
for (BiPredicate<String, String> predicate : forbidUrlPredicates) {
|
||||
if (predicate != null && predicate.test(servlet._prefix, uri)) {
|
||||
forbid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//找不到匹配的HttpServlet则使用静态资源HttpResourceServlet
|
||||
if (servlet == null) servlet = this.resourceHttpServlet;
|
||||
if (forbid) {
|
||||
response.finish(403, response.getHttpCode(403));
|
||||
return;
|
||||
}
|
||||
servlet.execute(request, response);
|
||||
} catch (Exception e) {
|
||||
request.getContext().getLogger().log(Level.WARNING, "Servlet occur, forece to close channel. request = " + request, e);
|
||||
@@ -132,14 +314,28 @@ public class HttpPrepareServlet extends PrepareServlet<String, HttpContext, Http
|
||||
mapping = mapping + "$";
|
||||
}
|
||||
if (regArray == null) {
|
||||
regArray = new SimpleEntry[1];
|
||||
regArray[0] = new SimpleEntry<>(Pattern.compile(mapping).asPredicate(), servlet);
|
||||
regArray = new MappingEntry[1];
|
||||
regArray[0] = new MappingEntry(mapping, Pattern.compile(mapping).asPredicate(), servlet);
|
||||
} else {
|
||||
regArray = Arrays.copyOf(regArray, regArray.length + 1);
|
||||
regArray[regArray.length - 1] = new SimpleEntry<>(Pattern.compile(mapping).asPredicate(), servlet);
|
||||
regArray[regArray.length - 1] = new MappingEntry(mapping, Pattern.compile(mapping).asPredicate(), servlet);
|
||||
}
|
||||
if (servlet instanceof WebSocketServlet) {
|
||||
if (regWsArray == null) {
|
||||
regWsArray = new MappingEntry[1];
|
||||
regWsArray[0] = new MappingEntry(mapping, Pattern.compile(mapping).asPredicate(), (WebSocketServlet) servlet);
|
||||
} else {
|
||||
regWsArray = Arrays.copyOf(regWsArray, regWsArray.length + 1);
|
||||
regWsArray[regWsArray.length - 1] = new MappingEntry(mapping, Pattern.compile(mapping).asPredicate(), (WebSocketServlet) servlet);
|
||||
}
|
||||
}
|
||||
} else if (mapping != null && !mapping.isEmpty()) {
|
||||
putMapping(mapping, servlet);
|
||||
if (servlet instanceof WebSocketServlet) {
|
||||
Map<String, WebSocketServlet> newmappings = new HashMap<>(wsmappings);
|
||||
newmappings.put(mapping, (WebSocketServlet) servlet);
|
||||
this.wsmappings = newmappings;
|
||||
}
|
||||
}
|
||||
if (this.allMapStrings.containsKey(mapping)) {
|
||||
Class old = this.allMapStrings.get(mapping);
|
||||
@@ -175,16 +371,31 @@ public class HttpPrepareServlet extends PrepareServlet<String, HttpContext, Http
|
||||
|
||||
@Override
|
||||
public void destroy(HttpContext context, AnyValue config) {
|
||||
super.destroy(context, config); //必须要执行
|
||||
this.resourceHttpServlet.destroy(context, config);
|
||||
getServlets().forEach(s -> {
|
||||
s.destroy(context, getServletConf(s));
|
||||
if (s instanceof WebSocketServlet) {
|
||||
((WebSocketServlet) s).postDestroy(context, getServletConf(s));
|
||||
} else if (s instanceof HttpBaseServlet) {
|
||||
((HttpBaseServlet) s).postDestroy(context, getServletConf(s));
|
||||
}
|
||||
s.postDestroy(context, getServletConf(s));
|
||||
});
|
||||
this.allMapStrings.clear();
|
||||
this.wsmappings.clear();
|
||||
this.regArray = null;
|
||||
this.regWsArray = null;
|
||||
}
|
||||
|
||||
protected static class MappingEntry {
|
||||
|
||||
public final String mapping;
|
||||
|
||||
public final Predicate<String> predicate;
|
||||
|
||||
public final HttpServlet servlet;
|
||||
|
||||
public MappingEntry(String mapping, Predicate<String> predicate, HttpServlet servlet) {
|
||||
this.mapping = mapping;
|
||||
this.predicate = predicate;
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.nio.channels.Channels;
|
||||
import java.nio.charset.Charset;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import org.redkale.util.ByteArray;
|
||||
|
||||
/**
|
||||
* Http请求包 与javax.servlet.http.HttpServletRequest 基本类似。 <br>
|
||||
@@ -34,6 +34,7 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
|
||||
protected static final String SESSIONID_NAME = "JSESSIONID";
|
||||
|
||||
@Comment("Method GET/POST/...")
|
||||
private String method;
|
||||
|
||||
private String protocol;
|
||||
@@ -48,7 +49,8 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
|
||||
private String connection;
|
||||
|
||||
protected String cookiestr;
|
||||
@Comment("原始的cookie字符串,解析后值赋给HttpCookie[] cookies")
|
||||
protected String cookie;
|
||||
|
||||
private HttpCookie[] cookies;
|
||||
|
||||
@@ -64,15 +66,25 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
|
||||
protected boolean boundary = false;
|
||||
|
||||
protected int moduleid;
|
||||
|
||||
protected int actionid;
|
||||
|
||||
protected Object currentUser;
|
||||
|
||||
private final String remoteAddrHeader;
|
||||
|
||||
Object attachment; //供 HttpBaseServlet传递Entry使用
|
||||
Object attachment; //仅供HttpServlet传递Entry使用
|
||||
|
||||
public HttpRequest(HttpContext context, String remoteAddrHeader) {
|
||||
super(context);
|
||||
this.remoteAddrHeader = remoteAddrHeader;
|
||||
}
|
||||
|
||||
protected boolean isWebSocket() {
|
||||
return connection != null && connection.contains("Upgrade") && "GET".equalsIgnoreCase(method) && "websocket".equalsIgnoreCase(getHeader("Upgrade"));
|
||||
}
|
||||
|
||||
protected void setKeepAlive(boolean keepAlive) {
|
||||
this.keepAlive = keepAlive;
|
||||
}
|
||||
@@ -131,10 +143,10 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
this.host = value;
|
||||
break;
|
||||
case "Cookie":
|
||||
if (this.cookiestr == null || this.cookiestr.isEmpty()) {
|
||||
this.cookiestr = value;
|
||||
if (this.cookie == null || this.cookie.isEmpty()) {
|
||||
this.cookie = value;
|
||||
} else {
|
||||
this.cookiestr += ";" + value;
|
||||
this.cookie += ";" + value;
|
||||
}
|
||||
break;
|
||||
case "Connection":
|
||||
@@ -232,6 +244,51 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
return super.removeProperty(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前用户信息, 通常在HttpServlet.preExecute方法里设置currentUser <br>
|
||||
* 数据类型由@HttpUserType指定
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param user 用户信息
|
||||
*
|
||||
* @return HttpRequest
|
||||
*/
|
||||
public <T> HttpRequest setCurrentUser(T user) {
|
||||
this.currentUser = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息<br>
|
||||
* 数据类型由@HttpUserType指定
|
||||
*
|
||||
* @param <T> @HttpUserType指定的用户信息类型
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T currentUser() {
|
||||
return (T) this.currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块ID,来自@HttpServlet.moduleid()
|
||||
*
|
||||
* @return 模块ID
|
||||
*/
|
||||
public int getModuleid() {
|
||||
return this.moduleid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作ID,来自@HttpMapping.actionid()
|
||||
*
|
||||
* @return 模块ID
|
||||
*/
|
||||
public int getActionid() {
|
||||
return this.actionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端地址IP
|
||||
*
|
||||
@@ -278,6 +335,44 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
return array.toString(UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求内容的JavaBean对象
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param type 类型
|
||||
*
|
||||
* @return 内容
|
||||
*/
|
||||
public <T> T getBodyJson(java.lang.reflect.Type type) {
|
||||
String str = array.toString(UTF8);
|
||||
if (str == null || str.isEmpty()) return null;
|
||||
return context.getJsonConvert().convertFrom(type, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求内容的JavaBean对象
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param convert JsonConvert
|
||||
* @param type 类型
|
||||
*
|
||||
* @return 内容
|
||||
*/
|
||||
public <T> T getBodyJson(JsonConvert convert, java.lang.reflect.Type type) {
|
||||
String str = array.toString(UTF8);
|
||||
if (str == null || str.isEmpty()) return null;
|
||||
return convert.convertFrom(type, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求内容的byte[]
|
||||
*
|
||||
* @return 内容
|
||||
*/
|
||||
public byte[] getBody() {
|
||||
return array.getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接获取body对象
|
||||
*
|
||||
@@ -291,7 +386,7 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
public String toString() {
|
||||
parseBody();
|
||||
return this.getClass().getSimpleName() + "{\r\n method: " + this.method + ", \r\n requestURI: " + this.requestURI
|
||||
+ ", \r\n remoteAddr: " + this.getRemoteAddr() + ", \r\n cookies: " + this.cookiestr + ", \r\n contentType: " + this.contentType
|
||||
+ ", \r\n remoteAddr: " + this.getRemoteAddr() + ", \r\n cookies: " + this.cookie + ", \r\n contentType: " + this.contentType
|
||||
+ ", \r\n connection: " + this.connection + ", \r\n protocol: " + this.protocol + ", \r\n host: " + this.host
|
||||
+ ", \r\n contentLength: " + this.contentLength + ", \r\n bodyLength: " + this.array.size() + (this.boundary || this.array.isEmpty() ? "" : (", \r\n bodyContent: " + this.getBodyUTF8()))
|
||||
+ ", \r\n params: " + this.params.toString(4) + ", \r\n header: " + this.header.toString(4) + "\r\n}";
|
||||
@@ -325,7 +420,7 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
|
||||
@Override
|
||||
protected void recycle() {
|
||||
this.cookiestr = null;
|
||||
this.cookie = null;
|
||||
this.cookies = null;
|
||||
this.newsessionid = null;
|
||||
this.method = null;
|
||||
@@ -337,6 +432,9 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
this.contentLength = -1;
|
||||
this.boundary = false;
|
||||
this.bodyparsed = false;
|
||||
this.moduleid = 0;
|
||||
this.actionid = 0;
|
||||
this.currentUser = null;
|
||||
|
||||
this.attachment = null;
|
||||
|
||||
@@ -397,7 +495,7 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
* @return cookie对象数组
|
||||
*/
|
||||
public HttpCookie[] getCookies() {
|
||||
if (this.cookies == null) this.cookies = parseCookies(this.cookiestr);
|
||||
if (this.cookies == null) this.cookies = parseCookies(this.cookie);
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
|
||||
protected static final byte[] LINE = new byte[]{'\r', '\n'};
|
||||
|
||||
protected static final byte[] serverNameBytes = ("Server: redkale/" + Redkale.getDotedVersion() + "\r\n").getBytes();
|
||||
|
||||
private static final Set<OpenOption> options = new HashSet<>();
|
||||
|
||||
private static final DateFormat GMT_DATE_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.ENGLISH);
|
||||
@@ -178,6 +180,11 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
return v == null ? defValue : v;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thenEvent(Servlet servlet) {
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加Cookie值
|
||||
*
|
||||
@@ -203,7 +210,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AsyncHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出
|
||||
* 创建AsyncHandler实例
|
||||
*
|
||||
* @return AsyncHandler
|
||||
*/
|
||||
@@ -222,6 +229,21 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AsyncHandler子类的实例 <br>
|
||||
*
|
||||
* 传入的AsyncHandler子类必须是public,且保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数。
|
||||
*
|
||||
* @param <H> 泛型
|
||||
* @param handlerClass AsyncHandler子类
|
||||
*
|
||||
* @return AsyncHandler AsyncHandler
|
||||
*/
|
||||
public <H extends AsyncHandler> H createAsyncHandler(Class<H> handlerClass) {
|
||||
if (handlerClass == null || handlerClass == AsyncHandler.class) return (H) createAsyncHandler();
|
||||
return context.loadAsyncHandlerCreator(handlerClass).create(createAsyncHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以JSON格式输出
|
||||
*
|
||||
@@ -360,6 +382,8 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
}
|
||||
if (v instanceof CharSequence) {
|
||||
finish(v.toString());
|
||||
} else if (v instanceof HttpResult) {
|
||||
finishJson(convert, (HttpResult) v);
|
||||
} else if (v instanceof org.redkale.service.RetResult) {
|
||||
finishJson(convert, (org.redkale.service.RetResult) v);
|
||||
} else {
|
||||
@@ -368,6 +392,44 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将HttpResult的结果对象以JSON格式输出
|
||||
*
|
||||
* @param result HttpResult对象
|
||||
*/
|
||||
public void finishJson(final HttpResult result) {
|
||||
finishJson(request.getJsonConvert(), result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将HttpResult的结果对象以JSON格式输出
|
||||
*
|
||||
* @param convert 指定的JsonConvert
|
||||
* @param result HttpResult对象
|
||||
*/
|
||||
public void finishJson(final JsonConvert convert, final HttpResult result) {
|
||||
if (output == null) {
|
||||
finish("");
|
||||
return;
|
||||
}
|
||||
if (result.getContentType() != null) setContentType(result.getContentType());
|
||||
addHeader(result.getHeaders()).addCookie(result.getCookies()).setStatus(result.getStatus() < 1 ? 200 : result.getStatus());
|
||||
if (result.getResult() instanceof File) {
|
||||
try {
|
||||
finish((File) result.getResult());
|
||||
} catch (IOException e) {
|
||||
getContext().getLogger().log(Level.WARNING, "HttpServlet finishJson HttpResult File occur, forece to close channel. request = " + getRequest(), e);
|
||||
finish(500, null);
|
||||
}
|
||||
} else if (result.getResult() instanceof String) {
|
||||
finish((String) result.getResult());
|
||||
} else if (result.getResult() == null) {
|
||||
finish(result.getMessage());
|
||||
} else {
|
||||
finishJson(result.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定字符串以响应结果输出
|
||||
*
|
||||
@@ -698,6 +760,8 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
if (!this.request.isKeepAlive()) {
|
||||
buffer.put("Connection: close\r\n".getBytes());
|
||||
}
|
||||
buffer.put(serverNameBytes);
|
||||
|
||||
if (this.defaultAddHeaders != null) {
|
||||
for (String[] headers : this.defaultAddHeaders) {
|
||||
if (headers.length > 3) {
|
||||
|
||||
128
src/org/redkale/net/http/HttpResult.java
Normal file
128
src/org/redkale/net/http/HttpResult.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <T> 结果对象的类型
|
||||
*/
|
||||
public class HttpResult<T> {
|
||||
|
||||
public static final String SESSIONID_COOKIENAME = HttpRequest.SESSIONID_NAME;
|
||||
|
||||
private Map<String, String> headers;
|
||||
|
||||
private List<HttpCookie> cookies;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private T result;
|
||||
|
||||
private int status = 0; //不设置则为 200
|
||||
|
||||
private String message;
|
||||
|
||||
public HttpResult() {
|
||||
}
|
||||
|
||||
public HttpResult(T result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public HttpResult<T> header(String name, Serializable value) {
|
||||
if (this.headers == null) this.headers = new HashMap<>();
|
||||
this.headers.put(name, String.valueOf(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResult<T> cookie(HttpCookie cookie) {
|
||||
if (this.cookies == null) this.cookies = new ArrayList<>();
|
||||
this.cookies.add(cookie);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResult<T> contentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResult<T> result(T result) {
|
||||
this.result = result;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResult<T> status(int status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResult<T> message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public List<HttpCookie> getCookies() {
|
||||
return cookies;
|
||||
}
|
||||
|
||||
public void setCookies(List<HttpCookie> cookies) {
|
||||
this.cookies = cookies;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(T result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonConvert.root().convertTo(this);
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.sncp.Sncp;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.WatchFactory;
|
||||
|
||||
/**
|
||||
* Http服务器
|
||||
@@ -23,14 +23,14 @@ import org.redkale.watch.WatchFactory;
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class HttpServer extends Server<String, HttpContext, HttpRequest, HttpResponse, HttpServlet> {
|
||||
public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpResponse, HttpServlet> {
|
||||
|
||||
public HttpServer() {
|
||||
this(System.currentTimeMillis(), null);
|
||||
this(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public HttpServer(long serverStartTime, final WatchFactory watch) {
|
||||
super(serverStartTime, "TCP", new HttpPrepareServlet(), watch);
|
||||
public HttpServer(long serverStartTime) {
|
||||
super(serverStartTime, "TCP", new HttpPrepareServlet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,6 +47,87 @@ public final class HttpServer extends Server<String, HttpContext, HttpRequest, H
|
||||
return ((HttpPrepareServlet) this.prepare).resourceHttpServlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除HttpFilter
|
||||
*
|
||||
* @param filterName HttpFilter名称
|
||||
*
|
||||
* @return HttpFilter
|
||||
*/
|
||||
public HttpFilter removeFilter(String filterName) {
|
||||
return (HttpFilter) this.prepare.removeFilter(filterName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除HttpServlet
|
||||
*
|
||||
* @param service Service
|
||||
*
|
||||
* @return HttpServlet
|
||||
*/
|
||||
public HttpServlet removeHttpServlet(Service service) {
|
||||
return ((HttpPrepareServlet) this.prepare).removeHttpServlet(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除HttpServlet
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param websocketOrServletType Class
|
||||
*
|
||||
* @return HttpServlet
|
||||
*/
|
||||
public <T extends WebSocket> HttpServlet removeHttpServlet(Class<T> websocketOrServletType) {
|
||||
return ((HttpPrepareServlet) this.prepare).removeHttpServlet(websocketOrServletType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 屏蔽请求URL的正则表达式
|
||||
*
|
||||
* @param urlreg 正则表达式
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean addForbidURIReg(final String urlreg) {
|
||||
return ((HttpPrepareServlet) this.prepare).addForbidURIReg(urlreg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除屏蔽请求URL的正则表达式
|
||||
*
|
||||
* @param urlreg 正则表达式
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean removeForbidURIReg(final String urlreg) {
|
||||
return ((HttpPrepareServlet) this.prepare).removeForbidURIReg(urlreg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除HttpFilter
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param filterClass HttpFilter类
|
||||
*
|
||||
* @return HttpFilter
|
||||
*/
|
||||
public <T extends HttpFilter> T removeFilter(Class<T> filterClass) {
|
||||
return (T) this.prepare.removeFilter(filterClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加HttpFilter
|
||||
*
|
||||
* @param filter HttpFilter
|
||||
* @param conf AnyValue
|
||||
*
|
||||
* @return HttpServer
|
||||
*/
|
||||
public HttpServer addHttpFilter(HttpFilter filter, AnyValue conf) {
|
||||
this.prepare.addFilter(filter, conf);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加HttpServlet
|
||||
*
|
||||
@@ -90,46 +171,63 @@ public final class HttpServer extends Server<String, HttpContext, HttpRequest, H
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加RestHttpServlet
|
||||
* 添加WebSocketServlet
|
||||
*
|
||||
* @param <S> Service
|
||||
* @param <T> RestHttpServlet
|
||||
* @param name Service的资源名
|
||||
* @param serviceType Service的类型
|
||||
* @param service Service对象
|
||||
* @param baseServletClass RestHttpServlet基类
|
||||
* @param prefix url前缀
|
||||
* @param <S> WebSocket
|
||||
* @param <T> HttpServlet
|
||||
* @param webSocketType WebSocket的类型
|
||||
* @param prefix url前缀
|
||||
* @param conf 配置信息
|
||||
*
|
||||
* @return RestHttpServlet
|
||||
* @return RestServlet
|
||||
*/
|
||||
public <S extends Service, T extends RestHttpServlet> RestHttpServlet addRestServlet(String name, Class<S> serviceType, S service, Class<T> baseServletClass, String prefix) {
|
||||
return addRestServlet(name, serviceType, service, baseServletClass, prefix, null);
|
||||
public <S extends WebSocket, T extends HttpServlet> T addRestWebSocketServlet(final Class<S> webSocketType, final String prefix, final AnyValue conf) {
|
||||
T servlet = Rest.createRestWebSocketServlet(webSocketType);
|
||||
if (servlet != null) this.prepare.addServlet(servlet, prefix, conf);
|
||||
return servlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加RestHttpServlet
|
||||
* 添加RestServlet
|
||||
*
|
||||
* @param <S> Service
|
||||
* @param <T> RestHttpServlet
|
||||
* @param name Service的资源名
|
||||
* @param serviceType Service的类型
|
||||
* @param service Service对象
|
||||
* @param baseServletClass RestHttpServlet基类
|
||||
* @param prefix url前缀
|
||||
* @param conf 配置信息
|
||||
* @param <S> Service
|
||||
* @param <T> HttpServlet
|
||||
* @param service Service对象
|
||||
* @param userType 用户数据类型
|
||||
* @param baseServletType RestServlet基类
|
||||
* @param prefix url前缀
|
||||
*
|
||||
* @return RestHttpServlet
|
||||
* @return RestServlet
|
||||
*/
|
||||
public <S extends Service, T extends RestHttpServlet> RestHttpServlet addRestServlet(
|
||||
final String name, Class<S> serviceType, final S service, final Class<T> baseServletClass, final String prefix, AnyValue conf) {
|
||||
RestHttpServlet servlet = null;
|
||||
public <S extends Service, T extends HttpServlet> T addRestServlet(final S service, final Class userType, final Class<T> baseServletType, final String prefix) {
|
||||
return addRestServlet(null, service, userType, baseServletType, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加RestServlet
|
||||
*
|
||||
* @param <S> Service
|
||||
* @param <T> HttpServlet
|
||||
* @param name 资源名
|
||||
* @param service Service对象
|
||||
* @param userType 用户数据类型
|
||||
* @param baseServletType RestServlet基类
|
||||
* @param prefix url前缀
|
||||
*
|
||||
* @return RestServlet
|
||||
*/
|
||||
public <S extends Service, T extends HttpServlet> T addRestServlet(final String name, final S service, final Class userType, final Class<T> baseServletType, final String prefix) {
|
||||
T servlet = null;
|
||||
final boolean sncp = Sncp.isSncpDyn(service);
|
||||
final String resname = name == null ? (sncp ? Sncp.getResourceName(service) : "") : name;
|
||||
final Class<S> serviceType = Sncp.getServiceType(service);
|
||||
for (final HttpServlet item : ((HttpPrepareServlet) this.prepare).getServlets()) {
|
||||
if (!(item instanceof RestHttpServlet)) continue;
|
||||
if (item.getClass().getAnnotation(Rest.RestDynamic.class) == null) continue;
|
||||
if (!(item instanceof HttpServlet)) continue;
|
||||
if (item.getClass().getAnnotation(Rest.RestDyn.class) == null) continue;
|
||||
try {
|
||||
Field field = item.getClass().getDeclaredField(Rest.REST_SERVICE_FIELD_NAME);
|
||||
if (serviceType.equals(field.getType())) {
|
||||
servlet = (RestHttpServlet) item;
|
||||
servlet = (T) item;
|
||||
break;
|
||||
}
|
||||
} catch (NoSuchFieldException | SecurityException e) {
|
||||
@@ -138,7 +236,8 @@ public final class HttpServer extends Server<String, HttpContext, HttpRequest, H
|
||||
}
|
||||
}
|
||||
final boolean first = servlet == null;
|
||||
if (servlet == null) servlet = Rest.createRestServlet(baseServletClass, serviceType);
|
||||
if (servlet == null) servlet = Rest.createRestServlet(userType, baseServletType, serviceType);
|
||||
if (servlet == null) return null; //没有HttpMapping方法的HttpServlet调用Rest.createRestServlet就会返回null
|
||||
try { //若提供动态变更Service服务功能,则改Rest服务无法做出相应更新
|
||||
Field field = servlet.getClass().getDeclaredField(Rest.REST_SERVICE_FIELD_NAME);
|
||||
field.setAccessible(true);
|
||||
@@ -147,21 +246,21 @@ public final class HttpServer extends Server<String, HttpContext, HttpRequest, H
|
||||
mapfield.setAccessible(true);
|
||||
|
||||
Service firstService = (Service) field.get(servlet);
|
||||
if (name.isEmpty()) {
|
||||
if (resname.isEmpty()) {
|
||||
field.set(servlet, service);
|
||||
firstService = service;
|
||||
}
|
||||
Map map = (Map) mapfield.get(servlet);
|
||||
if (map == null && !name.isEmpty()) map = new HashMap();
|
||||
if (map == null && !resname.isEmpty()) map = new HashMap();
|
||||
if (map != null) {
|
||||
map.put(name, service);
|
||||
map.put(resname, service);
|
||||
if (firstService != null) map.put("", firstService);
|
||||
}
|
||||
mapfield.set(servlet, map);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(serviceType + " generate rest servlet error", e);
|
||||
}
|
||||
if (first) this.prepare.addServlet(servlet, prefix, conf);
|
||||
if (first) this.prepare.addServlet(servlet, prefix, sncp ? Sncp.getConf(service) : null);
|
||||
return servlet;
|
||||
}
|
||||
|
||||
@@ -169,8 +268,8 @@ public final class HttpServer extends Server<String, HttpContext, HttpRequest, H
|
||||
@SuppressWarnings("unchecked")
|
||||
protected HttpContext createContext() {
|
||||
final int port = this.address.getPort();
|
||||
AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Buffer.creatCounter");
|
||||
AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Buffer.cycleCounter");
|
||||
AtomicLong createBufferCounter = new AtomicLong();
|
||||
AtomicLong cycleBufferCounter = new AtomicLong();
|
||||
this.bufferCapacity = Math.max(this.bufferCapacity, 16 * 1024 + 16); //兼容 HTTP 2.0;
|
||||
final int rcapacity = this.bufferCapacity;
|
||||
ObjectPool<ByteBuffer> bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, this.bufferPoolSize,
|
||||
@@ -249,11 +348,11 @@ public final class HttpServer extends Server<String, HttpContext, HttpRequest, H
|
||||
final String[][] setHeaders = defaultSetHeaders.isEmpty() ? null : defaultSetHeaders.toArray(new String[defaultSetHeaders.size()][]);
|
||||
final HttpCookie defCookie = defaultCookie;
|
||||
final String addrHeader = remoteAddrHeader;
|
||||
AtomicLong createResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Response.creatCounter");
|
||||
AtomicLong cycleResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Response.cycleCounter");
|
||||
AtomicLong createResponseCounter = new AtomicLong();
|
||||
AtomicLong cycleResponseCounter = new AtomicLong();
|
||||
ObjectPool<Response> responsePool = HttpResponse.createPool(createResponseCounter, cycleResponseCounter, this.responsePoolSize, null);
|
||||
HttpContext httpcontext = new HttpContext(this.serverStartTime, this.logger, executor, rcapacity, bufferPool, responsePool,
|
||||
this.maxbody, this.charset, this.address, this.prepare, this.watch, this.readTimeoutSecond, this.writeTimeoutSecond);
|
||||
this.maxbody, this.charset, this.address, this.prepare, this.readTimeoutSecond, this.writeTimeoutSecond);
|
||||
responsePool.setCreator((Object... params) -> new HttpResponse(httpcontext, new HttpRequest(httpcontext, addrHeader), addHeaders, setHeaders, defCookie));
|
||||
return httpcontext;
|
||||
}
|
||||
|
||||
@@ -5,17 +5,362 @@
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import org.redkale.net.Servlet;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.service.RetResult;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
* HTTP版的Servlet, 执行顺序 execute --> preExecute --> authenticate --> HttpMapping对应的方法
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class HttpServlet extends Servlet<HttpContext, HttpRequest, HttpResponse> {
|
||||
public class HttpServlet extends Servlet<HttpContext, HttpRequest, HttpResponse> {
|
||||
|
||||
public static final int RET_SERVER_ERROR = 1800_0001;
|
||||
|
||||
public static final int RET_METHOD_ERROR = 1800_0002;
|
||||
|
||||
String _prefix = ""; //当前HttpServlet的path前缀
|
||||
|
||||
private Map.Entry<String, Entry>[] mappings;
|
||||
|
||||
//这里不能直接使用HttpServlet,会造成死循环初始化HttpServlet
|
||||
private final Servlet<HttpContext, HttpRequest, HttpResponse> authSuccessServlet = new Servlet<HttpContext, HttpRequest, HttpResponse>() {
|
||||
@Override
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
Entry entry = (Entry) request.attachment;
|
||||
if (entry.cacheseconds > 0) {//有缓存设置
|
||||
CacheEntry ce = entry.cache.get(request.getRequestURI());
|
||||
if (ce != null && ce.time + entry.cacheseconds > System.currentTimeMillis()) { //缓存有效
|
||||
response.setStatus(ce.status);
|
||||
response.setContentType(ce.contentType);
|
||||
response.finish(ce.getBuffers());
|
||||
return;
|
||||
}
|
||||
response.setBufferHandler(entry.cacheHandler);
|
||||
}
|
||||
entry.servlet.execute(request, response);
|
||||
}
|
||||
};
|
||||
|
||||
//preExecute运行完后执行的Servlet
|
||||
private final Servlet<HttpContext, HttpRequest, HttpResponse> preSuccessServlet = new Servlet<HttpContext, HttpRequest, HttpResponse>() {
|
||||
@Override
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
for (Map.Entry<String, Entry> en : mappings) {
|
||||
if (request.getRequestURI().startsWith(en.getKey())) {
|
||||
Entry entry = en.getValue();
|
||||
if (!entry.checkMethod(request.getMethod())) {
|
||||
response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error"));
|
||||
return;
|
||||
}
|
||||
request.attachment = entry;
|
||||
request.moduleid = entry.moduleid;
|
||||
request.actionid = entry.actionid;
|
||||
if (entry.ignore) {
|
||||
authSuccessServlet.execute(request, response);
|
||||
} else {
|
||||
response.thenEvent(authSuccessServlet);
|
||||
authenticate(request, response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")");
|
||||
}
|
||||
};
|
||||
|
||||
void preInit(HttpContext context, AnyValue config) {
|
||||
String path = _prefix == null ? "" : _prefix;
|
||||
WebServlet ws = this.getClass().getAnnotation(WebServlet.class);
|
||||
if (ws != null && !ws.repair()) path = "";
|
||||
HashMap<String, Entry> map = load();
|
||||
this.mappings = new Map.Entry[map.size()];
|
||||
int i = -1;
|
||||
for (Map.Entry<String, Entry> en : map.entrySet()) {
|
||||
mappings[++i] = new AbstractMap.SimpleEntry<>(path + en.getKey(), en.getValue());
|
||||
}
|
||||
//必须要倒排序, /query /query1 /query12 确保含子集的优先匹配 /query12 /query1 /query
|
||||
Arrays.sort(mappings, (o1, o2) -> o2.getKey().compareTo(o1.getKey()));
|
||||
}
|
||||
|
||||
void postDestroy(HttpContext context, AnyValue config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 预执行方法,在execute方法之前运行,设置当前用户信息,或者加入常规统计和基础检测,例如 : <br>
|
||||
* <blockquote><pre>
|
||||
* @Override
|
||||
* public void preExecute(final HttpRequest request, final HttpResponse response) throws IOException {
|
||||
* //设置当前用户信息
|
||||
* final String sessionid = request.getSessionid(false);
|
||||
* if (sessionid != null) request.setCurrentUser(userService.current(sessionid));
|
||||
*
|
||||
* if (finer) response.recycleListener((req, resp) -> { //记录处理时间比较长的请求
|
||||
* long e = System.currentTimeMillis() - ((HttpRequest) req).getCreatetime();
|
||||
* if (e > 200) logger.finer("http-execute-cost-time: " + e + " ms. request = " + req);
|
||||
* });
|
||||
* response.nextEvent();
|
||||
* }
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
*
|
||||
* @param request HttpRequest
|
||||
* @param response HttpResponse
|
||||
*
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
protected void preExecute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
response.nextEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户登录或权限验证, 注解为@HttpMapping.auth == true 的方法会执行authenticate方法, 若验证成功则必须调用response.nextEvent();进行下一步操作, 例如: <br>
|
||||
* <blockquote><pre>
|
||||
* @Override
|
||||
* public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
|
||||
* UserInfo info = request.currentUser();
|
||||
* if (info == null) {
|
||||
* response.finishJson(RET_UNLOGIN);
|
||||
* return;
|
||||
* } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
|
||||
* response.finishJson(RET_AUTHILLEGAL);
|
||||
* return;
|
||||
* }
|
||||
* response.nextEvent();
|
||||
* }
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* @param request HttpRequest
|
||||
* @param response HttpResponse
|
||||
*
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
protected void authenticate(HttpRequest request, HttpResponse response) throws IOException {
|
||||
response.nextEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
response.thenEvent(preSuccessServlet);
|
||||
preExecute(request, response);
|
||||
}
|
||||
|
||||
private HashMap<String, Entry> load() {
|
||||
WebServlet module = this.getClass().getAnnotation(WebServlet.class);
|
||||
final int serviceid = module == null ? 0 : module.moduleid();
|
||||
final HashMap<String, Entry> map = new HashMap<>();
|
||||
HashMap<String, Class> nameset = new HashMap<>();
|
||||
final Class selfClz = this.getClass();
|
||||
Class clz = this.getClass();
|
||||
do {
|
||||
if (java.lang.reflect.Modifier.isAbstract(clz.getModifiers())) break;
|
||||
for (final Method method : clz.getMethods()) {
|
||||
//-----------------------------------------------
|
||||
String methodname = method.getName();
|
||||
if ("service".equals(methodname) || "preExecute".equals(methodname) || "execute".equals(methodname) || "authenticate".equals(methodname)) continue;
|
||||
//-----------------------------------------------
|
||||
Class[] paramTypes = method.getParameterTypes();
|
||||
if (paramTypes.length != 2 || paramTypes[0] != HttpRequest.class
|
||||
|| paramTypes[1] != HttpResponse.class) continue;
|
||||
//-----------------------------------------------
|
||||
Class[] exps = method.getExceptionTypes();
|
||||
if (exps.length > 0 && (exps.length != 1 || exps[0] != IOException.class)) continue;
|
||||
//-----------------------------------------------
|
||||
|
||||
final HttpMapping mapping = method.getAnnotation(HttpMapping.class);
|
||||
if (mapping == null) continue;
|
||||
final boolean inherited = mapping.inherited();
|
||||
if (!inherited && selfClz != clz) continue; //忽略不被继承的方法
|
||||
final int actionid = mapping.actionid();
|
||||
final String name = mapping.url().trim();
|
||||
final String[] methods = mapping.methods();
|
||||
if (nameset.containsKey(name)) {
|
||||
if (nameset.get(name) != clz) continue;
|
||||
throw new RuntimeException(this.getClass().getSimpleName() + " have two same " + HttpMapping.class.getSimpleName() + "(" + name + ")");
|
||||
}
|
||||
nameset.put(name, clz);
|
||||
map.put(name, new Entry(serviceid, actionid, name, methods, method, createHttpServlet(method)));
|
||||
}
|
||||
} while ((clz = clz.getSuperclass()) != HttpServlet.class);
|
||||
return map;
|
||||
}
|
||||
|
||||
private HttpServlet createHttpServlet(final Method method) {
|
||||
//------------------------------------------------------------------------------
|
||||
final String supDynName = HttpServlet.class.getName().replace('.', '/');
|
||||
final String interName = this.getClass().getName().replace('.', '/');
|
||||
final String interDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(this.getClass());
|
||||
final String requestSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Request.class);
|
||||
final String responseSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Response.class);
|
||||
final String requestDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpRequest.class);
|
||||
final String responseDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpResponse.class);
|
||||
String newDynName = interName + "_Dyn_" + method.getName();
|
||||
int i = 0;
|
||||
for (;;) {
|
||||
try {
|
||||
Class.forName(newDynName.replace('/', '.'));
|
||||
newDynName += "_" + (++i);
|
||||
} catch (Throwable ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
|
||||
FieldVisitor fv;
|
||||
MethodVisitor mv;
|
||||
AnnotationVisitor av0;
|
||||
final String factfield = "_factServlet";
|
||||
cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null);
|
||||
{
|
||||
fv = cw.visitField(ACC_PUBLIC, factfield, interDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{ //构造函数
|
||||
mv = (cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null));
|
||||
//mv.setDebug(true);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, supDynName, "<init>", "()V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
{
|
||||
mv = (cw.visitMethod(ACC_PUBLIC, "execute", "(" + requestDesc + responseDesc + ")V", null, new String[]{"java/io/IOException"}));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, factfield, interDesc);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, interName, method.getName(), "(" + requestDesc + responseDesc + ")V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
{
|
||||
mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "execute", "(" + requestSupDesc + responseSupDesc + ")V", null, new String[]{"java/io/IOException"});
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitTypeInsn(CHECKCAST, HttpRequest.class.getName().replace('.', '/'));
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitTypeInsn(CHECKCAST, HttpResponse.class.getName().replace('.', '/'));
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "execute", "(" + requestDesc + responseDesc + ")V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
cw.visitEnd();
|
||||
//------------------------------------------------------------------------------
|
||||
byte[] bytes = cw.toByteArray();
|
||||
Class<?> newClazz = new ClassLoader(this.getClass().getClassLoader()) {
|
||||
public final Class<?> loadClass(String name, byte[] b) {
|
||||
return defineClass(name, b, 0, b.length);
|
||||
}
|
||||
}.loadClass(newDynName.replace('/', '.'), bytes);
|
||||
try {
|
||||
HttpServlet instance = (HttpServlet) newClazz.newInstance();
|
||||
instance.getClass().getField(factfield).set(instance, this);
|
||||
return instance;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Entry {
|
||||
|
||||
public Entry(int moduleid, int actionid, String name, String[] methods, Method method, HttpServlet servlet) {
|
||||
this.moduleid = moduleid;
|
||||
this.actionid = actionid;
|
||||
this.name = name;
|
||||
this.methods = methods;
|
||||
this.method = method;
|
||||
this.servlet = servlet;
|
||||
HttpMapping mapping = method.getAnnotation(HttpMapping.class);
|
||||
this.ignore = mapping == null || !mapping.auth();
|
||||
this.cacheseconds = mapping == null ? 0 : mapping.cacheseconds();
|
||||
this.cache = cacheseconds > 0 ? new ConcurrentHashMap() : null;
|
||||
this.cacheHandler = cacheseconds > 0 ? (HttpResponse response, ByteBuffer[] buffers) -> {
|
||||
int status = response.getStatus();
|
||||
if (status != 200) return null;
|
||||
CacheEntry ce = new CacheEntry(response.getStatus(), response.getContentType(), buffers);
|
||||
cache.put(response.getRequest().getRequestURI(), ce);
|
||||
return ce.getBuffers();
|
||||
} : null;
|
||||
}
|
||||
|
||||
public boolean isNeedCheck() {
|
||||
return this.moduleid != 0 || this.actionid != 0;
|
||||
}
|
||||
|
||||
public boolean checkMethod(final String reqMethod) {
|
||||
if (methods.length == 0) return true;
|
||||
for (String m : methods) {
|
||||
if (reqMethod.equalsIgnoreCase(m)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final HttpResponse.BufferHandler cacheHandler;
|
||||
|
||||
public final ConcurrentHashMap<String, CacheEntry> cache;
|
||||
|
||||
public final int cacheseconds;
|
||||
|
||||
public final boolean ignore;
|
||||
|
||||
public final int moduleid;
|
||||
|
||||
public final int actionid;
|
||||
|
||||
public final String name;
|
||||
|
||||
public final String[] methods;
|
||||
|
||||
public final Method method;
|
||||
|
||||
public final HttpServlet servlet;
|
||||
}
|
||||
|
||||
private static final class CacheEntry {
|
||||
|
||||
public final long time = System.currentTimeMillis();
|
||||
|
||||
private final ByteBuffer[] buffers;
|
||||
|
||||
private final int status;
|
||||
|
||||
private final String contentType;
|
||||
|
||||
public CacheEntry(int status, String contentType, ByteBuffer[] bufs) {
|
||||
this.status = status;
|
||||
this.contentType = contentType;
|
||||
final ByteBuffer[] newBuffers = new ByteBuffer[bufs.length];
|
||||
for (int i = 0; i < newBuffers.length; i++) {
|
||||
newBuffers[i] = bufs[i].duplicate().asReadOnlyBuffer();
|
||||
}
|
||||
this.buffers = newBuffers;
|
||||
}
|
||||
|
||||
public ByteBuffer[] getBuffers() {
|
||||
final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length];
|
||||
for (int i = 0; i < newBuffers.length; i++) {
|
||||
newBuffers[i] = buffers[i].duplicate();
|
||||
}
|
||||
return newBuffers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.*;
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* RestMapping 的多用类
|
||||
* 配合 HttpServlet 使用。
|
||||
* 用于指定HttpRequest.currentUser的数据类型。<br>
|
||||
* 注意: 数据类型是JavaBean,则必须要用javax.persistence.Id标记主键字段,用于确定用户ID
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
@@ -18,9 +21,10 @@ import static java.lang.annotation.RetentionPolicy.*;
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Target({TYPE})
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestMappings {
|
||||
public @interface HttpUserType {
|
||||
|
||||
Class value();
|
||||
|
||||
RestMapping[] value();
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public final class MultiContext {
|
||||
|
||||
public MultiContext(final Charset charsetName, final String contentType, final DefaultAnyValue params, final InputStream in, String fielnameRegex) {
|
||||
this.charset = charsetName == null ? UTF8 : charsetName;
|
||||
this.contentType = contentType.trim();
|
||||
this.contentType = contentType == null ? "" : contentType.trim();
|
||||
this.parameters = params;
|
||||
this.boundary = parseBoundary(this.contentType);
|
||||
this.endboundarray = ("--" + this.boundary + "--").getBytes();
|
||||
@@ -87,10 +87,111 @@ public final class MultiContext {
|
||||
return this.boundary != null;
|
||||
}
|
||||
|
||||
//或被 REST 用到
|
||||
/**
|
||||
* 获取第一个文件的二进制
|
||||
*
|
||||
* @param max 可接收的文件大小最大值
|
||||
* @param filenameReg 可接收的文件名正则表达式
|
||||
* @param contentTypeReg 可接收的ContentType正则表达式
|
||||
*
|
||||
* @return 二进制文件
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public byte[] partsFirstBytes(final long max, final String filenameReg, final String contentTypeReg) throws IOException {
|
||||
if (!isMultipart()) return null;
|
||||
byte[] tmpfile = null;
|
||||
boolean has = false;
|
||||
for (MultiPart part : parts()) {
|
||||
if (has) continue;//不遍历完后面getParameter可能获取不到值
|
||||
has = true;
|
||||
if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue;
|
||||
if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue;
|
||||
tmpfile = part.getContentBytes(max < 1 ? Long.MAX_VALUE : max);
|
||||
}
|
||||
return tmpfile;
|
||||
}
|
||||
|
||||
//或被 REST 用到
|
||||
/**
|
||||
* 获取第一个文件
|
||||
*
|
||||
* @param home 进程目录
|
||||
* @param max 可接收的文件大小最大值
|
||||
* @param filenameReg 可接收的文件名正则表达式
|
||||
* @param contentTypeReg 可接收的ContentType正则表达式
|
||||
*
|
||||
* @return 文件
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public File partsFirstFile(final File home, final long max, final String filenameReg, final String contentTypeReg) throws IOException {
|
||||
if (!isMultipart()) return null;
|
||||
File tmpfile = null;
|
||||
boolean has = false;
|
||||
for (MultiPart part : parts()) {
|
||||
if (has) continue; //不遍历完后面getParameter可能获取不到值
|
||||
has = true;
|
||||
if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue;
|
||||
if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue;
|
||||
String name = part.getFilename();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos > 0) {
|
||||
int pos2 = name.lastIndexOf('.', pos - 1);
|
||||
if (pos2 >= 0) pos = pos2;
|
||||
}
|
||||
File file = new File(home, "tmp/redkale_" + System.nanoTime() + (pos > 0 ? name.substring(pos) : name));
|
||||
file.getParentFile().mkdirs();
|
||||
boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file);
|
||||
if (!rs) {
|
||||
file.delete();
|
||||
} else {
|
||||
tmpfile = file;
|
||||
}
|
||||
}
|
||||
return tmpfile;
|
||||
}
|
||||
|
||||
//或被 REST 用到
|
||||
/**
|
||||
* 获取所有文件
|
||||
*
|
||||
* @param home 进程目录
|
||||
* @param max 可接收的文件大小最大值
|
||||
* @param filenameReg 可接收的文件名正则表达式
|
||||
* @param contentTypeReg 可接收的ContentType正则表达式
|
||||
*
|
||||
* @return 文件列表
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public File[] partsFiles(final File home, final long max, final String filenameReg, final String contentTypeReg) throws IOException {
|
||||
if (!isMultipart()) return null;
|
||||
List<File> files = null;
|
||||
for (MultiPart part : parts()) {
|
||||
if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue;
|
||||
if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue;
|
||||
String name = part.getFilename();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos > 0) {
|
||||
int pos2 = name.lastIndexOf('.', pos - 1);
|
||||
if (pos2 >= 0) pos = pos2;
|
||||
}
|
||||
File file = new File(home, "tmp/redkale_" + System.nanoTime() + (pos > 0 ? name.substring(pos) : name));
|
||||
file.getParentFile().mkdirs();
|
||||
boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file);
|
||||
if (!rs) {
|
||||
file.delete();
|
||||
continue;
|
||||
}
|
||||
if (files == null) files = new ArrayList<>();
|
||||
files.add(file);
|
||||
}
|
||||
return files == null ? null : files.toArray(new File[files.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传文件信息列表
|
||||
*
|
||||
* @return Iterable
|
||||
* @return Iterable
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public Iterable<MultiPart> parts() throws IOException {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,4 +25,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestAddress {
|
||||
|
||||
/**
|
||||
* 备注描述, 对应@HttpParam.comment
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
|
||||
34
src/org/redkale/net/http/RestBody.java
Normal file
34
src/org/redkale/net/http/RestBody.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 只能注解于RestService类的方法的String/byte[]/JavaBean参数或参数内的String/byte[]/JavaBean字段
|
||||
* <p>
|
||||
* 用于获取HTTP请求端的请求内容UTF-8编码字符串、byte[]、JavaBean
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({PARAMETER, FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestBody {
|
||||
|
||||
/**
|
||||
* 备注描述, 对应@HttpParam.comment
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 只能注解于Service类的方法的String参数或参数内的String字段
|
||||
* 只能注解于RestService类的方法的String参数或参数内的String字段
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
|
||||
@@ -10,7 +10,7 @@ import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 只能注解于Service类的方法的参数或参数内的String字段
|
||||
* 只能注解于RestService类的方法的参数或参数内的String字段
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
|
||||
@@ -6,180 +6,21 @@
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
* 使用 RestServlet 代替
|
||||
*
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @deprecated
|
||||
* @param <T> 当前用户对象类型
|
||||
*/
|
||||
public abstract class RestHttpServlet<T> extends HttpBaseServlet {
|
||||
@Deprecated
|
||||
public class RestHttpServlet<T> extends HttpServlet {
|
||||
|
||||
protected abstract T currentUser(HttpRequest req) throws IOException;
|
||||
|
||||
protected void finishJson(final HttpResponse response, CompletableFuture<RestOutput> future) throws IOException {
|
||||
future.whenComplete((output, e) -> {
|
||||
if (e != null) {
|
||||
response.getContext().getLogger().log(Level.WARNING, "Servlet occur, forece to close channel. request = " + response.getRequest(), e);
|
||||
response.finish(500, null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
finishJson(response, output);
|
||||
} catch (IOException ioe) {
|
||||
response.getContext().getLogger().log(Level.WARNING, "Servlet finish RestOutput occur, forece to close channel. request = " + response.getRequest(), ioe);
|
||||
response.finish(500, null);
|
||||
}
|
||||
});
|
||||
protected T currentUser(HttpRequest req) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void finishJson(final HttpResponse response, RestOutput output) throws IOException {
|
||||
if (output == null) {
|
||||
response.finishJson(output);
|
||||
return;
|
||||
}
|
||||
if (output.getContentType() != null) response.setContentType(output.getContentType());
|
||||
response.addHeader(output.getHeaders());
|
||||
response.addCookie(output.getCookies());
|
||||
response.setStatus(output.getStatus() < 1 ? 200 : output.getStatus());
|
||||
if (output.getResult() instanceof File) {
|
||||
response.finish((File) output.getResult());
|
||||
} else if (output.getResult() instanceof String) {
|
||||
response.finish((String) output.getResult());
|
||||
} else if (output.getResult() == null) {
|
||||
response.finish(output.getMessage());
|
||||
} else {
|
||||
response.finishJson(output.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AsyncHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出 <br>
|
||||
*
|
||||
* 传入的AsyncHandler子类必须是public,且保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数。
|
||||
*
|
||||
* @param <H> AsyncHandler泛型
|
||||
* @param response HttpResponse
|
||||
* @param handlerClass Class
|
||||
*
|
||||
* @return AsyncHandler
|
||||
*/
|
||||
protected final <H extends AsyncHandler> H createAsyncHandler(HttpResponse response, final Class<H> handlerClass) {
|
||||
if (handlerClass == null || handlerClass == AsyncHandler.class) return (H) response.createAsyncHandler();
|
||||
Creator<H> creator = creators.get(handlerClass);
|
||||
if (creator == null) {
|
||||
creator = createCreator(handlerClass);
|
||||
creators.put(handlerClass, creator);
|
||||
}
|
||||
return (H) creator.create(response.createAsyncHandler());
|
||||
}
|
||||
|
||||
private static final ConcurrentHashMap<Class, Creator> creators = new ConcurrentHashMap<>();
|
||||
|
||||
private static <H extends AsyncHandler> Creator<H> createCreator(Class<H> handlerClass) {
|
||||
//生成规则与SncpAsyncHandler.Factory 很类似
|
||||
//-------------------------------------------------------------
|
||||
final boolean handlerinterface = handlerClass.isInterface();
|
||||
final String handlerClassName = handlerClass.getName().replace('.', '/');
|
||||
final String handlerName = AsyncHandler.class.getName().replace('.', '/');
|
||||
final String handlerDesc = Type.getDescriptor(AsyncHandler.class);
|
||||
final String newDynName = handlerClass.getName().replace('.', '/') + "_Dync" + AsyncHandler.class.getSimpleName() + "_" + (System.currentTimeMillis() % 10000);
|
||||
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||
FieldVisitor fv;
|
||||
AsmMethodVisitor mv;
|
||||
AnnotationVisitor av0;
|
||||
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, newDynName, null, handlerinterface ? "java/lang/Object" : handlerClassName, handlerinterface ? new String[]{handlerClassName} : new String[]{handlerName});
|
||||
|
||||
{ //handler 属性
|
||||
fv = cw.visitField(ACC_PRIVATE, "handler", handlerDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{//构造方法
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "<init>", "(" + handlerDesc + ")V", null, null));
|
||||
//mv.setDebug(true);
|
||||
{
|
||||
av0 = mv.visitAnnotation("Ljava/beans/ConstructorProperties;", true);
|
||||
{
|
||||
AnnotationVisitor av1 = av0.visitArray("value");
|
||||
av1.visit(null, "handler");
|
||||
av1.visitEnd();
|
||||
}
|
||||
av0.visitEnd();
|
||||
}
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, handlerinterface ? "java/lang/Object" : handlerClassName, "<init>", "()V", false);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitFieldInsn(PUTFIELD, newDynName, "handler", handlerDesc);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(2, 2);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
for (java.lang.reflect.Method method : handlerClass.getMethods()) { //
|
||||
if ("completed".equals(method.getName()) && method.getParameterCount() == 2) {
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "completed", Type.getMethodDescriptor(method), null, null));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, "handler", handlerDesc);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, handlerName, "completed", "(Ljava/lang/Object;Ljava/lang/Object;)V", true);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
} else if ("failed".equals(method.getName()) && method.getParameterCount() == 2) {
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "failed", Type.getMethodDescriptor(method), null, null));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, "handler", handlerDesc);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, handlerName, "failed", "(Ljava/lang/Throwable;Ljava/lang/Object;)V", true);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
} else if (handlerinterface || java.lang.reflect.Modifier.isAbstract(method.getModifiers())) {
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, null));
|
||||
Class returnType = method.getReturnType();
|
||||
if (returnType == void.class) {
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 1);
|
||||
} else if (returnType.isPrimitive()) {
|
||||
mv.visitInsn(ICONST_0);
|
||||
if (returnType == long.class) {
|
||||
mv.visitInsn(LRETURN);
|
||||
mv.visitMaxs(2, 1);
|
||||
} else if (returnType == float.class) {
|
||||
mv.visitInsn(FRETURN);
|
||||
mv.visitMaxs(2, 1);
|
||||
} else if (returnType == double.class) {
|
||||
mv.visitInsn(DRETURN);
|
||||
mv.visitMaxs(2, 1);
|
||||
} else {
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
}
|
||||
} else {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
}
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
cw.visitEnd();
|
||||
byte[] bytes = cw.toByteArray();
|
||||
Class<AsyncHandler> newHandlerClazz = (Class<AsyncHandler>) new ClassLoader(handlerClass.getClassLoader()) {
|
||||
public final Class<?> loadClass(String name, byte[] b) {
|
||||
return defineClass(name, b, 0, b.length);
|
||||
}
|
||||
}.loadClass(newDynName.replace('/', '.'), bytes);
|
||||
return (Creator<H>) Creator.create(newHandlerClazz);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ import static java.lang.annotation.RetentionPolicy.*;
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Repeatable(RestMappings.class)
|
||||
@Repeatable(RestMapping.RestMappings.class)
|
||||
public @interface RestMapping {
|
||||
|
||||
/**
|
||||
* 是否屏蔽该方法的转换
|
||||
* 是否屏蔽该方法进行HttpMapping转换
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
@@ -40,38 +40,46 @@ public @interface RestMapping {
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 备注描述, 对应@WebMapping.comment
|
||||
* 备注描述, 对应@HttpMapping.comment
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
|
||||
/**
|
||||
* 是否鉴权,默认不鉴权, 对应@AuthIgnore
|
||||
* 是否鉴权,默认需要鉴权, 对应@HttpMapping.auth
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
boolean auth() default false;
|
||||
boolean auth() default true;
|
||||
|
||||
/**
|
||||
* 操作ID值,鉴权时用到, 对应@WebMapping.actionid
|
||||
* 操作ID值,鉴权时用到, 对应@HttpMapping.actionid
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int actionid() default 0;
|
||||
|
||||
/**
|
||||
* 结果缓存的秒数, 为0表示不缓存, 对应@HttpCacheable.seconds
|
||||
* 结果缓存的秒数, 为0表示不缓存, 对应@HttpMapping.cacheseconds
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int cacheseconds() default 0;
|
||||
|
||||
/**
|
||||
* 允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法, 对应@WebMapping.methods
|
||||
* 允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法, 对应@HttpMapping.methods
|
||||
*
|
||||
* @return String[]
|
||||
*/
|
||||
String[] methods() default {};
|
||||
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@interface RestMappings {
|
||||
|
||||
RestMapping[] value();
|
||||
}
|
||||
}
|
||||
|
||||
39
src/org/redkale/net/http/RestOnMessage.java
Normal file
39
src/org/redkale/net/http/RestOnMessage.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 标记在RestWebSocket的接收消息方法上
|
||||
*
|
||||
* <br><p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestOnMessage {
|
||||
|
||||
/**
|
||||
* 请求的方法名, 不能含特殊字符,不能以数字开头(能作为变量名)
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* 备注描述
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
@@ -10,13 +10,16 @@ import java.net.HttpCookie;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 使用 HttpResult 代替
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
* @param <T> 结果对象的类型
|
||||
*/
|
||||
@Deprecated
|
||||
public class RestOutput<T> {
|
||||
|
||||
private Map<String, String> headers;
|
||||
|
||||
@@ -29,6 +29,13 @@ public @interface RestService {
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 目录名, 不能含特殊字符, 只能小写字母+数字,且不能以数字开头
|
||||
*
|
||||
* @return 目录名
|
||||
*/
|
||||
String catalog() default "";
|
||||
|
||||
/**
|
||||
* 模块ID值,鉴权时用到, 对应@WebServlet.moduleid
|
||||
*
|
||||
@@ -36,6 +43,13 @@ public @interface RestService {
|
||||
*/
|
||||
int moduleid() default 0;
|
||||
|
||||
/**
|
||||
* 没有标记@RestMapping的方法是否转换, 默认为false
|
||||
*
|
||||
* @return 默认false
|
||||
*/
|
||||
boolean automapping() default false;
|
||||
|
||||
/**
|
||||
* 是否屏蔽该类的转换
|
||||
*
|
||||
|
||||
33
src/org/redkale/net/http/RestURI.java
Normal file
33
src/org/redkale/net/http/RestURI.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 只能注解于Service类的方法的String参数或参数内的String字段
|
||||
* <p>
|
||||
* 用于获取HTTP请求URL HttpRequest.getRequestURI
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({PARAMETER, FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestURI {
|
||||
|
||||
/**
|
||||
* 备注描述, 对应@HttpParam.comment
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
55
src/org/redkale/net/http/RestUploadFile.java
Normal file
55
src/org/redkale/net/http/RestUploadFile.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
*
|
||||
* 依附在RestService类的方法的参数上, 用于接收上传文件 <br>
|
||||
* 只能标记在byte[]/File/File[] 类型的参数上 <br>
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({PARAMETER, FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestUploadFile {
|
||||
|
||||
/**
|
||||
* 可接收的文件大小最大值, 小于1表示无大小限制
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
long maxLength() default 0;
|
||||
|
||||
/**
|
||||
* 可接收的文件名正则表达式, 为空表示接收任何文件 <br>
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String fileNameReg() default "";
|
||||
|
||||
/**
|
||||
* 可接收的ContentType正则表达式, 为空表示接收任何文件类型 <br>
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String contentTypeReg() default "";
|
||||
|
||||
/**
|
||||
* 备注描述, 对应@HttpParam.comment
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
74
src/org/redkale/net/http/RestWebSocket.java
Normal file
74
src/org/redkale/net/http/RestWebSocket.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 只能依附在WebSocket类上,name默认为Service的类名小写并去掉Service字样及后面的字符串 (如HelloWebSocket/HelloWebSocketImpl,的默认路径为 hello)。 <br>
|
||||
* <b>注意: </b> 被标记@RestWebSocket的WebSocket不能被修饰为abstract或final,且其内部标记为@Resource的字段只能是protected或public,且必须要有一个protected或public的空参数构造函数。 <br>
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({TYPE})
|
||||
@Retention(RUNTIME)
|
||||
public @interface RestWebSocket {
|
||||
|
||||
/**
|
||||
* 模块名, 只能是模块名,不能含特殊字符, 只能小写字母+数字,且不能以数字开头
|
||||
*
|
||||
* @return 模块名
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 目录名, 不能含特殊字符, 只能小写字母+数字,且不能以数字开头
|
||||
*
|
||||
* @return 目录名
|
||||
*/
|
||||
String catalog() default "";
|
||||
|
||||
/**
|
||||
* 是否单用户单连接, 默认单用户单连接
|
||||
*
|
||||
* @return 是否单用户单连接
|
||||
*/
|
||||
boolean single() default true;
|
||||
|
||||
/**
|
||||
* WebScoket服务器给客户端进行ping操作的间隔时间, 单位: 秒, 默认值:60秒
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int liveinterval() default 60;
|
||||
|
||||
/**
|
||||
* 是否屏蔽该类的转换
|
||||
*
|
||||
* @return 默认false
|
||||
*/
|
||||
boolean ignore() default false;
|
||||
|
||||
/**
|
||||
* 同@WebServlet的repair属性
|
||||
*
|
||||
* @return 默认true
|
||||
*/
|
||||
boolean repair() default true;
|
||||
|
||||
/**
|
||||
* 备注描述
|
||||
*
|
||||
* @return 备注描述
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import org.redkale.net.http.WebSocketPacket.FrameType;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Stream;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.util.Comment;
|
||||
@@ -20,7 +21,7 @@ import org.redkale.util.Comment;
|
||||
* WebSocket 有两种模式:
|
||||
* 1) 普通模式: 协议上符合HTML5规范, 其流程顺序如下:
|
||||
* 1.1 onOpen 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
|
||||
* 1.2 createGroupid 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
|
||||
* 1.2 createUserid 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
|
||||
* 1.3 onConnected WebSocket成功连接后在准备接收数据前回调此方法。
|
||||
* 1.4 onMessage/onFragment+ WebSocket接收到消息后回调此消息类方法。
|
||||
* 1.5 onClose WebSocket被关闭后回调此方法。
|
||||
@@ -28,7 +29,7 @@ import org.redkale.util.Comment;
|
||||
*
|
||||
* 2) 原始二进制模式: 此模式有别于HTML5规范,可以视为原始的TCP连接。通常用于音频视频通讯场景。其流程顺序如下:
|
||||
* 2.1 onOpen 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
|
||||
* 2.2 createGroupid 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
|
||||
* 2.2 createWebSocketid 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
|
||||
* 2.3 onRead WebSocket成功连接后回调此方法, 由此方法处理原始的TCP连接, 需要业务代码去控制WebSocket的关闭。
|
||||
* 二进制模式下 以上方法都应该被重载。
|
||||
* </pre></blockquote>
|
||||
@@ -36,8 +37,10 @@ import org.redkale.util.Comment;
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <G> Groupid的泛型
|
||||
* @param <T> Message的泛型
|
||||
*/
|
||||
public abstract class WebSocket {
|
||||
public abstract class WebSocket<G extends Serializable, T> {
|
||||
|
||||
@Comment("消息不合法")
|
||||
public static final int RETCODE_SEND_ILLPACKET = 1 << 1; //2
|
||||
@@ -67,11 +70,9 @@ public abstract class WebSocket {
|
||||
|
||||
WebSocketEngine _engine; //不可能为空
|
||||
|
||||
WebSocketGroup _group; //不可能为空
|
||||
String _sessionid; //不可能为空
|
||||
|
||||
Serializable _sessionid; //不可能为空
|
||||
|
||||
Serializable _groupid; //不可能为空
|
||||
G _userid; //不可能为空
|
||||
|
||||
SocketAddress _remoteAddress;//不可能为空
|
||||
|
||||
@@ -79,62 +80,27 @@ public abstract class WebSocket {
|
||||
|
||||
JsonConvert _jsonConvert; //不可能为空
|
||||
|
||||
private final long createtime = System.currentTimeMillis();
|
||||
java.lang.reflect.Type _messageTextType; //不可能为空
|
||||
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
private long createtime = System.currentTimeMillis();
|
||||
|
||||
private Map<String, Object> attributes = new HashMap<>(); //非线程安全
|
||||
|
||||
protected WebSocket() {
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
/**
|
||||
* 给自身发送消息体, 包含二进制/文本
|
||||
*
|
||||
* @param packet WebSocketPacket
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(WebSocketPacket packet) {
|
||||
int rs = RETCODE_WSOCKET_CLOSED;
|
||||
if (this._runner != null) rs = this._runner.sendMessage(packet);
|
||||
if (_engine.finest) _engine.logger.finest("wsgroupid:" + getGroupid() + " send websocket result is " + rs + " on " + this + " by message(" + packet + ")");
|
||||
return rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送单一的文本消息
|
||||
*
|
||||
* @param text 不可为空
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(String text) {
|
||||
return send(text, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送文本消息
|
||||
*
|
||||
* @param text 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(String text, boolean last) {
|
||||
return send(new WebSocketPacket(text, last));
|
||||
}
|
||||
|
||||
public final int sendPing() {
|
||||
public final CompletableFuture<Integer> sendPing() {
|
||||
//if (_engine.finest) _engine.logger.finest(this + " on "+_engine.getEngineid()+" ping...");
|
||||
return send(WebSocketPacket.DEFAULT_PING_PACKET);
|
||||
return sendPacket(WebSocketPacket.DEFAULT_PING_PACKET);
|
||||
}
|
||||
|
||||
public final int sendPing(byte[] data) {
|
||||
return send(new WebSocketPacket(FrameType.PING, data));
|
||||
public final CompletableFuture<Integer> sendPing(byte[] data) {
|
||||
return sendPacket(new WebSocketPacket(FrameType.PING, data));
|
||||
}
|
||||
|
||||
public final int sendPong(byte[] data) {
|
||||
return send(new WebSocketPacket(FrameType.PONG, data));
|
||||
public final CompletableFuture<Integer> sendPong(byte[] data) {
|
||||
return sendPacket(new WebSocketPacket(FrameType.PONG, data));
|
||||
}
|
||||
|
||||
public final long getCreatetime() {
|
||||
@@ -142,251 +108,176 @@ public abstract class WebSocket {
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送单一的二进制消息
|
||||
* 给自身发送消息, 消息类型是String或byte[]或可JavaBean对象
|
||||
*
|
||||
* @param data byte[]
|
||||
* @param message 不可为空, 只能是String或byte[]或可JavaBean对象
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(byte[] data) {
|
||||
return send(data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送二进制消息
|
||||
*
|
||||
* @param data 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(byte[] data, boolean last) {
|
||||
return send(new WebSocketPacket(data, last));
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息, 消息类型是String或byte[]或可JSON化对象
|
||||
*
|
||||
* @param message 不可为空, 只能是String或byte[]或可JSON化对象
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(Object message) {
|
||||
public final CompletableFuture<Integer> send(Object message) {
|
||||
return send(message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息, 消息类型是String或byte[]或可JSON化对象
|
||||
* 给自身发送消息, 消息类型是String或byte[]或可JavaBean对象
|
||||
*
|
||||
* @param message 不可为空, 只能是String或byte[]或可JSON化对象
|
||||
* @param message 不可为空, 只能是String或byte[]或可JavaBean对象
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final int send(Object message, boolean last) {
|
||||
if (message == null || message instanceof CharSequence || message instanceof byte[]) {
|
||||
return send(new WebSocketPacket((Serializable) message, last));
|
||||
} else {
|
||||
return send(new WebSocketPacket(_jsonConvert.convertTo(message), last));
|
||||
public final CompletableFuture<Integer> send(Object message, boolean last) {
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> {
|
||||
if (json == null || json instanceof CharSequence || json instanceof byte[]) {
|
||||
return sendPacket(new WebSocketPacket((Serializable) json, last));
|
||||
} else if (message instanceof WebSocketPacket) {
|
||||
return sendPacket((WebSocketPacket) message);
|
||||
} else {
|
||||
return sendPacket(new WebSocketPacket(_jsonConvert, json, last));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (message == null || message instanceof CharSequence || message instanceof byte[]) {
|
||||
return sendPacket(new WebSocketPacket((Serializable) message, last));
|
||||
} else if (message instanceof WebSocketPacket) {
|
||||
return sendPacket((WebSocketPacket) message);
|
||||
} else {
|
||||
return sendPacket(new WebSocketPacket(_jsonConvert, message, last));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息, 消息类型是JavaBean对象
|
||||
*
|
||||
* @param convert JsonConvert
|
||||
* @param message 不可为空, 只能是JSON对象
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final CompletableFuture<Integer> send(JsonConvert convert, Object message) {
|
||||
return send(convert, message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息, 消息类型是JavaBean对象
|
||||
*
|
||||
* @param convert JsonConvert
|
||||
* @param message 不可为空, 只能是JavaBean对象
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final CompletableFuture<Integer> send(JsonConvert convert, Object message, boolean last) {
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> sendPacket(new WebSocketPacket(convert == null ? _jsonConvert : convert, json, last)));
|
||||
}
|
||||
return sendPacket(new WebSocketPacket(convert == null ? _jsonConvert : convert, message, last));
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息体, 包含二进制/文本
|
||||
*
|
||||
* @param packet WebSocketPacket
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
CompletableFuture<Integer> sendPacket(WebSocketPacket packet) {
|
||||
CompletableFuture<Integer> rs = this._runner.sendMessage(packet);
|
||||
if (_engine.finest) _engine.logger.finest("userid:" + userid() + " send websocket message(" + packet + ")" + " on " + this);
|
||||
return rs == null ? CompletableFuture.completedFuture(RETCODE_WSOCKET_CLOSED) : rs;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息
|
||||
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param text 不可为空
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendEachMessage(Serializable groupid, String text) {
|
||||
return sendEachMessage(groupid, text, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param data 不可为空
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendEachMessage(Serializable groupid, byte[] data) {
|
||||
return sendEachMessage(groupid, data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下所有WebSocket节点发送可JSON化对象消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param message 不可为空
|
||||
* @param userids Serializable[]
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendEachMessage(Serializable groupid, Object message) {
|
||||
return sendEachMessage(groupid, message, true);
|
||||
public final CompletableFuture<Integer> sendMessage(Object message, G... userids) {
|
||||
return sendMessage(message, true, userids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息
|
||||
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param text 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendEachMessage(Serializable groupid, String text, boolean last) {
|
||||
return sendMessage(groupid, false, text, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param data 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendEachMessage(Serializable groupid, byte[] data, boolean last) {
|
||||
return sendMessage(groupid, false, data, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下所有WebSocket节点发送可JSON化对象消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param message 不可为空
|
||||
* @param last 是否最后一条
|
||||
* @param userids Serializable[]
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendEachMessage(Serializable groupid, Object message, boolean last) {
|
||||
return sendMessage(groupid, false, message, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送文本消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param text 不可为空
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendRecentMessage(Serializable groupid, String text) {
|
||||
return sendRecentMessage(groupid, text, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送二进制消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param data 不可为空
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendRecentMessage(Serializable groupid, byte[] data) {
|
||||
return sendRecentMessage(groupid, data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送可JSON化对象消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param message 不可为空
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendRecentMessage(Serializable groupid, Object message) {
|
||||
return sendMessage(groupid, true, message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送文本消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param text 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendRecentMessage(Serializable groupid, String text, boolean last) {
|
||||
return sendMessage(groupid, true, text, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送二进制消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param data 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendRecentMessage(Serializable groupid, byte[] data, boolean last) {
|
||||
return sendMessage(groupid, true, data, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送可JSON化对象消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param message 不可为空
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示异常
|
||||
*/
|
||||
public final int sendRecentMessage(Serializable groupid, Object message, boolean last) {
|
||||
return sendMessage(groupid, true, message, last);
|
||||
}
|
||||
|
||||
private int sendMessage(Serializable groupid, boolean recent, String text, boolean last) {
|
||||
if (_engine.node == null) return RETCODE_NODESERVICE_NULL;
|
||||
int rs = _engine.node.sendMessage(groupid, recent, text, last);
|
||||
if (_engine.finest) _engine.logger.finest("wsgroupid:" + groupid + " " + (recent ? "recent " : "") + "send websocket result is " + rs + " on " + this + " by message(" + text + ")");
|
||||
return rs;
|
||||
}
|
||||
|
||||
private int sendMessage(Serializable groupid, boolean recent, byte[] data, boolean last) {
|
||||
if (_engine.node == null) return RETCODE_NODESERVICE_NULL;
|
||||
int rs = _engine.node.sendMessage(groupid, recent, data, last);
|
||||
if (_engine.finest) _engine.logger.finest("wsgroupid:" + groupid + " " + (recent ? "recent " : "") + "send websocket result is " + rs + " on " + this + " by message(byte[" + data.length + "])");
|
||||
return rs;
|
||||
}
|
||||
|
||||
private int sendMessage(Serializable groupid, boolean recent, Object message, boolean last) {
|
||||
if (_engine.node == null) return RETCODE_NODESERVICE_NULL;
|
||||
int rs = _engine.node.sendMessage(groupid, recent, message, last);
|
||||
if (_engine.finest) _engine.logger.finest("wsgroupid:" + groupid + " " + (recent ? "recent " : "") + "send websocket result is " + rs + " on " + this + " by message(" + _jsonConvert.convertTo(message) + ")");
|
||||
public final CompletableFuture<Integer> sendMessage(Object message, boolean last, G... userids) {
|
||||
if (_engine.node == null) return CompletableFuture.completedFuture(RETCODE_NODESERVICE_NULL);
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> _engine.node.sendMessage(json, last, userids));
|
||||
}
|
||||
CompletableFuture<Integer> rs = _engine.node.sendMessage(message, last, userids);
|
||||
if (_engine.finest) _engine.logger.finest("userids:" + Arrays.toString(userids) + " send websocket message(" + _jsonConvert.convertTo(message) + ")");
|
||||
return rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定groupid在线用户的节点地址列表
|
||||
* 广播消息, 给所有人发消息
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param message 消息内容
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示部分发送异常
|
||||
*/
|
||||
public final CompletableFuture<Integer> broadcastMessage(final Object message) {
|
||||
return broadcastMessage(message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播消息, 给所有人发消息
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示部分发送异常
|
||||
*/
|
||||
public final CompletableFuture<Integer> broadcastMessage(final Object message, final boolean last) {
|
||||
if (_engine.node == null) return CompletableFuture.completedFuture(RETCODE_NODESERVICE_NULL);
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> _engine.node.broadcastMessage(json, last));
|
||||
}
|
||||
CompletableFuture<Integer> rs = _engine.node.broadcastMessage(message, last);
|
||||
if (_engine.finest) _engine.logger.finest("broadcast send websocket message(" + _jsonConvert.convertTo(message) + ")");
|
||||
return rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户在线的SNCP节点地址列表,不是分布式则返回元素数量为1,且元素值为null的列表<br>
|
||||
* InetSocketAddress 为 SNCP节点地址
|
||||
*
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return 地址列表
|
||||
*/
|
||||
protected final Collection<InetSocketAddress> getOnlineNodes(Serializable groupid) {
|
||||
return _engine.node.getOnlineNodes(groupid);
|
||||
public CompletableFuture<Collection<InetSocketAddress>> getRpcNodeAddresses(final Serializable userid) {
|
||||
if (_engine.node == null) return CompletableFuture.completedFuture(null);
|
||||
return _engine.node.getRpcNodeAddresses(userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定groupid在线用户的详细连接信息
|
||||
* 获取在线用户的详细连接信息 <br>
|
||||
* Map.key 为 SNCP节点地址, 含值为null的key表示没有分布式
|
||||
* Map.value 为 用户客户端的IP
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return 地址集合
|
||||
*/
|
||||
protected final Map<InetSocketAddress, List<String>> getOnlineRemoteAddress(Serializable groupid) {
|
||||
return _engine.node.getOnlineRemoteAddress(groupid);
|
||||
public CompletableFuture<Map<InetSocketAddress, List<String>>> getRpcNodeWebSocketAddresses(final Serializable userid) {
|
||||
if (_engine.node == null) return CompletableFuture.completedFuture(null);
|
||||
return _engine.node.getRpcNodeWebSocketAddresses(userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前WebSocket下的属性
|
||||
* 获取当前WebSocket下的属性,非线程安全
|
||||
*
|
||||
* @param <T> 属性值的类型
|
||||
* @param name 属性名
|
||||
@@ -395,11 +286,11 @@ public abstract class WebSocket {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final <T> T getAttribute(String name) {
|
||||
return (T) attributes.get(name);
|
||||
return attributes == null ? null : (T) attributes.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移出当前WebSocket下的属性
|
||||
* 移出当前WebSocket下的属性,非线程安全
|
||||
*
|
||||
* @param <T> 属性值的类型
|
||||
* @param name 属性名
|
||||
@@ -407,26 +298,27 @@ public abstract class WebSocket {
|
||||
* @return 属性值
|
||||
*/
|
||||
public final <T> T removeAttribute(String name) {
|
||||
return (T) attributes.remove(name);
|
||||
return attributes == null ? null : (T) attributes.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给当前WebSocket下的增加属性
|
||||
* 给当前WebSocket下的增加属性,非线程安全
|
||||
*
|
||||
* @param name 属性值
|
||||
* @param value 属性值
|
||||
*/
|
||||
public final void setAttribute(String name, Object value) {
|
||||
if (attributes == null) attributes = new HashMap<>();
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前WebSocket所属的groupid
|
||||
* 获取当前WebSocket所属的userid
|
||||
*
|
||||
* @return groupid
|
||||
* @return userid
|
||||
*/
|
||||
public final Serializable getGroupid() {
|
||||
return _groupid;
|
||||
public final G userid() {
|
||||
return _userid;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,7 +326,7 @@ public abstract class WebSocket {
|
||||
*
|
||||
* @return sessionid
|
||||
*/
|
||||
public final Serializable getSessionid() {
|
||||
public final String getSessionid() {
|
||||
return _sessionid;
|
||||
}
|
||||
|
||||
@@ -458,52 +350,56 @@ public abstract class WebSocket {
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/**
|
||||
* 获取当前WebSocket所属的WebSocketGroup, 不会为null
|
||||
* 获取指定userid的WebSocket数组, 没有返回null <br>
|
||||
* 此方法用于单用户多连接模式
|
||||
*
|
||||
* @return WebSocketGroup
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return WebSocket集合
|
||||
*/
|
||||
protected final WebSocketGroup getWebSocketGroup() {
|
||||
return _group;
|
||||
protected final Stream<WebSocket> getLocalWebSockets(G userid) {
|
||||
return _engine.getLocalWebSockets(userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定groupid的WebSocketGroup, 没有返回null
|
||||
* 获取指定userid的WebSocket数组, 没有返回null<br>
|
||||
* 此方法用于单用户单连接模式
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return WebSocketGroup
|
||||
* @return WebSocket
|
||||
*/
|
||||
protected final WebSocketGroup getWebSocketGroup(Serializable groupid) {
|
||||
return _engine.getWebSocketGroup(groupid);
|
||||
protected final WebSocket findLocalWebSocket(G userid) {
|
||||
return _engine.findLocalWebSocket(userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前进程节点所有在线的WebSocketGroup
|
||||
* 获取当前进程节点所有在线的WebSocket
|
||||
*
|
||||
* @return WebSocketGroup列表
|
||||
*/
|
||||
protected final Collection<WebSocketGroup> getWebSocketGroups() {
|
||||
return _engine.getWebSocketGroups();
|
||||
protected final Collection<WebSocket> getLocalWebSockets() {
|
||||
return _engine.getLocalWebSockets();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/**
|
||||
* 返回sessionid, null表示连接不合法或异常,默认实现是request.getSessionid(false),通常需要重写该方法
|
||||
* 返回sessionid, null表示连接不合法或异常,默认实现是request.sessionid(true),通常需要重写该方法
|
||||
*
|
||||
* @param request HttpRequest
|
||||
*
|
||||
* @return sessionid
|
||||
*/
|
||||
public Serializable onOpen(final HttpRequest request) {
|
||||
return request.getSessionid(false);
|
||||
protected CompletableFuture<String> onOpen(final HttpRequest request) {
|
||||
return CompletableFuture.completedFuture(request.getSessionid(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建groupid, null表示异常, 必须实现该方法, 通常为用户ID为groupid
|
||||
* 创建userid, null表示异常, 必须实现该方法
|
||||
*
|
||||
* @return groupid
|
||||
* @return userid
|
||||
*/
|
||||
protected abstract Serializable createGroupid();
|
||||
protected abstract CompletableFuture<G> createUserid();
|
||||
|
||||
/**
|
||||
* 标记为WebSocketBinary才需要重写此方法
|
||||
@@ -513,30 +409,64 @@ public abstract class WebSocket {
|
||||
public void onRead(AsyncConnection channel) {
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSokcet连接成功后的回调方法
|
||||
*/
|
||||
public void onConnected() {
|
||||
}
|
||||
|
||||
public void onMessage(String text) {
|
||||
}
|
||||
|
||||
/**
|
||||
* ping后的回调方法
|
||||
*
|
||||
* @param bytes 数据
|
||||
*/
|
||||
public void onPing(byte[] bytes) {
|
||||
}
|
||||
|
||||
/**
|
||||
* pong后的回调方法
|
||||
*
|
||||
* @param bytes 数据
|
||||
*/
|
||||
public void onPong(byte[] bytes) {
|
||||
}
|
||||
|
||||
public void onMessage(byte[] bytes) {
|
||||
/**
|
||||
* 接收到消息的回调方法
|
||||
*
|
||||
* @param message 消息
|
||||
* @param last 是否最后一条
|
||||
*/
|
||||
public void onMessage(T message, boolean last) {
|
||||
}
|
||||
|
||||
public void onFragment(String text, boolean last) {
|
||||
}
|
||||
|
||||
public void onFragment(byte[] bytes, boolean last) {
|
||||
/**
|
||||
* 接收到二进制消息的回调方法
|
||||
*
|
||||
* @param bytes 消息
|
||||
* @param last 是否最后一条
|
||||
*/
|
||||
public void onMessage(byte[] bytes, boolean last) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭的回调方法,调用此方法时WebSocket已经被关闭
|
||||
*
|
||||
* @param code 结果码,非0表示非正常关闭
|
||||
* @param reason 关闭原因
|
||||
*/
|
||||
public void onClose(int code, String reason) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一次发送消息的时间
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
public long getLastSendTime() {
|
||||
return this._runner == null ? 0 : this._runner.lastSendTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显式地关闭WebSocket
|
||||
*/
|
||||
@@ -546,6 +476,6 @@ public abstract class WebSocket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ws" + Objects.hashCode(this) + "@" + _remoteAddr;
|
||||
return this.userid() + "@" + _remoteAddr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.stream.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import static org.redkale.net.http.WebSocket.RETCODE_GROUP_EMPTY;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
@@ -22,79 +25,223 @@ import org.redkale.util.*;
|
||||
*/
|
||||
public final class WebSocketEngine {
|
||||
|
||||
//全局自增长ID
|
||||
private static final AtomicInteger sequence = new AtomicInteger();
|
||||
|
||||
//Engine自增长序号ID
|
||||
private final int index;
|
||||
|
||||
//当前WebSocket对应的Engine
|
||||
private final String engineid;
|
||||
|
||||
//当前WebSocket对应的Node
|
||||
protected final WebSocketNode node;
|
||||
|
||||
private final Map<Serializable, WebSocketGroup> containers = new ConcurrentHashMap<>();
|
||||
//HttpContext
|
||||
protected final HttpContext context;
|
||||
|
||||
//JsonConvert
|
||||
protected final JsonConvert convert;
|
||||
|
||||
protected final boolean single; //是否单用户单连接
|
||||
|
||||
//在线用户ID对应的WebSocket组,用于单用户单连接模式
|
||||
private final Map<Serializable, WebSocket> websockets = new ConcurrentHashMap<>();
|
||||
|
||||
//在线用户ID对应的WebSocket组,用于单用户多连接模式
|
||||
private final Map<Serializable, List<WebSocket>> websockets2 = new ConcurrentHashMap<>();
|
||||
|
||||
//用于PING的定时器
|
||||
private ScheduledThreadPoolExecutor scheduler;
|
||||
|
||||
//日志
|
||||
protected final Logger logger;
|
||||
|
||||
//FINEST日志级别
|
||||
protected final boolean finest;
|
||||
|
||||
protected WebSocketEngine(String engineid, WebSocketNode node, Logger logger) {
|
||||
private int liveinterval;
|
||||
|
||||
protected WebSocketEngine(String engineid, boolean single, HttpContext context, int liveinterval, WebSocketNode node, Logger logger) {
|
||||
this.engineid = engineid;
|
||||
this.single = single;
|
||||
this.context = context;
|
||||
this.convert = context.getJsonConvert();
|
||||
this.node = node;
|
||||
this.liveinterval = liveinterval;
|
||||
this.logger = logger;
|
||||
this.index = sequence.getAndIncrement();
|
||||
this.finest = logger.isLoggable(Level.FINEST);
|
||||
}
|
||||
|
||||
void init(AnyValue conf) {
|
||||
final int liveinterval = conf == null ? DEFAILT_LIVEINTERVAL : conf.getIntValue("liveinterval", DEFAILT_LIVEINTERVAL);
|
||||
if (liveinterval <= 0) return;
|
||||
final int interval = conf == null ? (liveinterval < 0 ? DEFAILT_LIVEINTERVAL : liveinterval) : conf.getIntValue("liveinterval", (liveinterval < 0 ? DEFAILT_LIVEINTERVAL : liveinterval));
|
||||
if (interval <= 0) return;
|
||||
if (scheduler != null) return;
|
||||
this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> {
|
||||
final Thread t = new Thread(r, engineid + "-WebSocket-LiveInterval-Thread");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
long delay = (liveinterval - System.currentTimeMillis() / 1000 % liveinterval) + index * 5;
|
||||
long delay = (interval - System.currentTimeMillis() / 1000 % interval) + index * 5;
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
getWebSocketGroups().stream().forEach(x -> x.sendEachPing());
|
||||
}, delay, liveinterval, TimeUnit.SECONDS);
|
||||
if (finest) logger.finest(this.getClass().getSimpleName() + "(" + engineid + ")" + " start keeplive(delay:" + delay + ", interval:" + liveinterval + "s) scheduler executor");
|
||||
getLocalWebSockets().forEach(x -> x.sendPing());
|
||||
}, delay, interval, TimeUnit.SECONDS);
|
||||
if (finest) logger.finest(this.getClass().getSimpleName() + "(" + engineid + ")" + " start keeplive(delay:" + delay + ", interval:" + interval + "s) scheduler executor");
|
||||
}
|
||||
|
||||
void destroy(AnyValue conf) {
|
||||
if (scheduler != null) scheduler.shutdownNow();
|
||||
}
|
||||
|
||||
void add(WebSocket socket) {
|
||||
WebSocketGroup group = containers.get(socket._groupid);
|
||||
if (group == null) {
|
||||
group = new WebSocketGroup(socket._groupid);
|
||||
containers.putIfAbsent(socket._groupid, group);
|
||||
if (single) {
|
||||
websockets.put(socket._userid, socket);
|
||||
} else { //非线程安全, 在常规场景中无需锁
|
||||
List<WebSocket> list = websockets2.get(socket._userid);
|
||||
if (list == null) {
|
||||
list = new CopyOnWriteArrayList<>();
|
||||
websockets2.put(socket._userid, list);
|
||||
}
|
||||
list.add(socket);
|
||||
}
|
||||
group.add(socket);
|
||||
if (node != null) node.connect(socket._groupid, engineid, socket.toString());
|
||||
if (node != null) node.connect(socket._userid);
|
||||
}
|
||||
|
||||
void remove(WebSocket socket) {
|
||||
final WebSocketGroup group = containers.get(socket._groupid);
|
||||
if (group == null) {
|
||||
if (node != null) node.disconnect(socket._groupid, engineid);
|
||||
return;
|
||||
}
|
||||
group.remove(socket);
|
||||
if (group.isEmpty()) {
|
||||
containers.remove(socket._groupid);
|
||||
if (node != null) node.disconnect(socket._groupid, engineid);
|
||||
Serializable userid = socket._userid;
|
||||
if (single) {
|
||||
websockets.remove(userid);
|
||||
if (node != null) node.disconnect(userid);
|
||||
} else { //非线程安全, 在常规场景中无需锁
|
||||
List<WebSocket> list = websockets2.get(userid);
|
||||
if (list != null) {
|
||||
list.remove(socket);
|
||||
if (list.isEmpty()) {
|
||||
websockets2.remove(userid);
|
||||
if (node != null) node.disconnect(userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collection<WebSocketGroup> getWebSocketGroups() {
|
||||
return containers.values();
|
||||
public CompletableFuture<Integer> broadcastMessage(final Object message, final boolean last) {
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> broadcastMessage(json, last));
|
||||
}
|
||||
final boolean more = (!(message instanceof WebSocketPacket) || ((WebSocketPacket) message).sendBuffers == null);
|
||||
if (more) {
|
||||
final WebSocketPacket packet = (message instanceof WebSocketPacket) ? (WebSocketPacket) message
|
||||
: ((message == null || message instanceof CharSequence || message instanceof byte[])
|
||||
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.convert, message, last));
|
||||
packet.setSendBuffers(packet.encode(context.getBufferSupplier()));
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
for (WebSocket websocket : websockets.values()) {
|
||||
future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
} else {
|
||||
for (List<WebSocket> list : websockets2.values()) {
|
||||
for (WebSocket websocket : list) {
|
||||
future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (future != null) future = future.whenComplete((rs, ex) -> context.offerBuffer(packet.sendBuffers));
|
||||
return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
|
||||
} else {
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
for (WebSocket websocket : websockets.values()) {
|
||||
future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
} else {
|
||||
for (List<WebSocket> list : websockets2.values()) {
|
||||
for (WebSocket websocket : list) {
|
||||
future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketGroup getWebSocketGroup(Serializable groupid) {
|
||||
return containers.get(groupid);
|
||||
public CompletableFuture<Integer> sendMessage(final Object message, final boolean last, final Serializable... userids) {
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> sendMessage(json, last, userids));
|
||||
}
|
||||
final boolean more = (!(message instanceof WebSocketPacket) || ((WebSocketPacket) message).sendBuffers == null) && userids.length > 1;
|
||||
if (more) {
|
||||
final WebSocketPacket packet = (message instanceof WebSocketPacket) ? (WebSocketPacket) message
|
||||
: ((message == null || message instanceof CharSequence || message instanceof byte[])
|
||||
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.convert, message, last));
|
||||
packet.setSendBuffers(packet.encode(context.getBufferSupplier()));
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
for (Serializable userid : userids) {
|
||||
WebSocket websocket = websockets.get(userid);
|
||||
if (websocket == null) continue;
|
||||
future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
} else {
|
||||
for (Serializable userid : userids) {
|
||||
List<WebSocket> list = websockets2.get(userid);
|
||||
if (list == null) continue;
|
||||
for (WebSocket websocket : list) {
|
||||
future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (future != null) future = future.whenComplete((rs, ex) -> context.offerBuffer(packet.sendBuffers));
|
||||
return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
|
||||
} else {
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
for (Serializable userid : userids) {
|
||||
WebSocket websocket = websockets.get(userid);
|
||||
if (websocket == null) continue;
|
||||
future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
} else {
|
||||
for (Serializable userid : userids) {
|
||||
List<WebSocket> list = websockets2.get(userid);
|
||||
if (list == null) continue;
|
||||
for (WebSocket websocket : list) {
|
||||
future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (scheduler != null) scheduler.shutdownNow();
|
||||
Collection<WebSocket> getLocalWebSockets() {
|
||||
if (single) return websockets.values();
|
||||
List<WebSocket> list = new ArrayList<>();
|
||||
websockets2.values().forEach(x -> list.addAll(x));
|
||||
return list;
|
||||
}
|
||||
|
||||
//适用于单用户单连接模式
|
||||
public WebSocket findLocalWebSocket(Serializable userid) {
|
||||
if (single) return websockets.get(userid);
|
||||
List<WebSocket> list = websockets2.get(userid);
|
||||
return (list == null || list.isEmpty()) ? null : list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
//适用于单用户多连接模式
|
||||
public Stream<WebSocket> getLocalWebSockets(Serializable userid) {
|
||||
if (single) {
|
||||
WebSocket websocket = websockets.get(userid);
|
||||
return websocket == null ? Stream.empty() : Stream.of(websocket);
|
||||
} else {
|
||||
List<WebSocket> list = websockets2.get(userid);
|
||||
return list == null ? Stream.empty() : list.stream();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean existsLocalWebSocket(Serializable userid) {
|
||||
return single ? websockets.containsKey(userid) : websockets2.containsKey(userid);
|
||||
}
|
||||
|
||||
public String getEngineid() {
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class WebSocketGroup {
|
||||
|
||||
private final Serializable groupid;
|
||||
|
||||
private WebSocket recentWebSocket;
|
||||
|
||||
private final List<WebSocket> list = new CopyOnWriteArrayList<>();
|
||||
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
WebSocketGroup(Serializable groupid) {
|
||||
this.groupid = groupid;
|
||||
}
|
||||
|
||||
public Serializable getGroupid() {
|
||||
return groupid;
|
||||
}
|
||||
|
||||
public Stream<WebSocket> getWebSockets() {
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
void remove(WebSocket socket) {
|
||||
list.remove(socket);
|
||||
}
|
||||
|
||||
void add(WebSocket socket) {
|
||||
socket._group = this;
|
||||
this.recentWebSocket = socket;
|
||||
list.add(socket);
|
||||
}
|
||||
|
||||
void setRecentWebSocket(WebSocket socket) {
|
||||
this.recentWebSocket = socket;
|
||||
}
|
||||
|
||||
public final boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public final int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 最近发送消息的WebSocket
|
||||
*
|
||||
* @return WebSocket
|
||||
*/
|
||||
public final WebSocket getRecentWebSocket() {
|
||||
return recentWebSocket;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public final <T> T getAttribute(String name) {
|
||||
return (T) attributes.get(name);
|
||||
}
|
||||
|
||||
public final void removeAttribute(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
public final void setAttribute(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
public final int send(boolean recent, Object message, boolean last) {
|
||||
if (recent) {
|
||||
return recentWebSocket.send(message, last);
|
||||
} else {
|
||||
return sendEach(message, last);
|
||||
}
|
||||
}
|
||||
|
||||
public final int sendEach(Object message) {
|
||||
return sendEach(message, true);
|
||||
}
|
||||
|
||||
public final int sendEach(WebSocketPacket packet) {
|
||||
int rs = 0;
|
||||
for (WebSocket s : list) {
|
||||
rs |= s.send(packet);
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public final int sendEachPing() {
|
||||
int rs = 0;
|
||||
for (WebSocket s : list) {
|
||||
rs |= s.sendPing();
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public final int sendRecent(Object message) {
|
||||
return sendRecent(message, true);
|
||||
}
|
||||
|
||||
public final int sendRecent(WebSocketPacket packet) {
|
||||
return recentWebSocket.send(packet);
|
||||
}
|
||||
|
||||
public final int sendEach(Object message, boolean last) {
|
||||
if (message != null && !(message instanceof byte[]) && !(message instanceof CharSequence)) {
|
||||
message = recentWebSocket._jsonConvert.convertTo(message);
|
||||
}
|
||||
int rs = 0;
|
||||
for (WebSocket s : list) {
|
||||
rs |= s.send(message, last);
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public final int sendRecent(Object message, boolean last) {
|
||||
return recentWebSocket.send(message, last);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{groupid: " + groupid + ", list.size: " + (list == null ? -1 : list.size()) + "}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,230 +30,206 @@ public abstract class WebSocketNode {
|
||||
|
||||
protected final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
|
||||
@Resource(name = Application.RESNAME_SERVER_ADDR)
|
||||
//"SNCP_ADDR" 如果不是分布式(没有SNCP) 值为null
|
||||
@Resource(name = Application.RESNAME_SNCP_ADDR)
|
||||
protected InetSocketAddress localSncpAddress; //为SncpServer的服务address
|
||||
|
||||
//如果不是分布式(没有SNCP) 值为null
|
||||
@RpcRemote
|
||||
protected WebSocketNode remoteNode;
|
||||
|
||||
//存放所有用户分布在节点上的队列信息,Set<InetSocketAddress> 为 sncpnode 的集合
|
||||
@Resource(name = "$")
|
||||
protected CacheSource<Serializable, InetSocketAddress> sncpNodes;
|
||||
//存放所有用户分布在节点上的队列信息,Set<InetSocketAddress> 为 sncpnode 的集合, key: groupid
|
||||
//集合包含 localSncpAddress
|
||||
//如果不是分布式(没有SNCP),sncpNodeAddresses 将不会被用到
|
||||
@Resource(name = "$_nodes")
|
||||
protected CacheSource<Serializable, InetSocketAddress> sncpNodeAddresses;
|
||||
|
||||
//存放本地节点上所有在线用户的队列信息,Set<String> 为 engineid 的集合
|
||||
protected final ConcurrentHashMap<Serializable, Set<String>> localNodes = new ConcurrentHashMap();
|
||||
|
||||
protected final ConcurrentHashMap<String, WebSocketEngine> engines = new ConcurrentHashMap();
|
||||
//当前节点的本地WebSocketEngine
|
||||
protected WebSocketEngine localEngine;
|
||||
|
||||
public void init(AnyValue conf) {
|
||||
|
||||
}
|
||||
|
||||
public void destroy(AnyValue conf) {
|
||||
HashMap<Serializable, Set<String>> nodes = new HashMap<>(localNodes);
|
||||
nodes.forEach((k, v) -> {
|
||||
new HashSet<>(v).forEach(e -> {
|
||||
if (engines.containsKey(e)) disconnect(k, e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract List<String> getOnlineRemoteAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable groupid);
|
||||
public final void postDestroy(AnyValue conf) {
|
||||
if (this.localEngine == null) return;
|
||||
//关掉所有本地本地WebSocket
|
||||
this.localEngine.getLocalWebSockets().forEach(g -> disconnect(g.userid()));
|
||||
if (sncpNodeAddresses != null && localSncpAddress != null) sncpNodeAddresses.removeSetItem("redkale_sncpnodes", localSncpAddress);
|
||||
}
|
||||
|
||||
protected abstract int sendMessage(@RpcTargetAddress InetSocketAddress targetAddress, Serializable groupid, boolean recent, Object message, boolean last);
|
||||
protected abstract CompletableFuture<List<String>> getWebSocketAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable userid);
|
||||
|
||||
protected abstract void connect(Serializable groupid, InetSocketAddress addr);
|
||||
protected abstract CompletableFuture<Integer> sendMessage(@RpcTargetAddress InetSocketAddress targetAddress, Object message, boolean last, Serializable userid);
|
||||
|
||||
protected abstract void disconnect(Serializable groupid, InetSocketAddress addr);
|
||||
protected abstract CompletableFuture<Integer> broadcastMessage(@RpcTargetAddress InetSocketAddress targetAddress, Object message, boolean last);
|
||||
|
||||
protected abstract CompletableFuture<Void> connect(Serializable userid, InetSocketAddress addr);
|
||||
|
||||
protected abstract CompletableFuture<Void> disconnect(Serializable userid, InetSocketAddress addr);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
protected List<String> remoteOnlineRemoteAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable groupid) {
|
||||
if (remoteNode == null) return null;
|
||||
final CompletableFuture<Void> connect(final Serializable userid) {
|
||||
if (finest) logger.finest(localSncpAddress + " receive websocket connect event (" + userid + " on " + this.localEngine.getEngineid() + ").");
|
||||
return connect(userid, localSncpAddress);
|
||||
}
|
||||
|
||||
final CompletableFuture<Void> disconnect(final Serializable userid) {
|
||||
if (finest) logger.finest(localSncpAddress + " receive websocket disconnect event (" + userid + " on " + this.localEngine.getEngineid() + ").");
|
||||
return disconnect(userid, localSncpAddress);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
/**
|
||||
* 获取目标地址 <br>
|
||||
* 该方法仅供内部调用
|
||||
*
|
||||
* @param targetAddress InetSocketAddress
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return 客户端地址列表
|
||||
*/
|
||||
protected CompletableFuture<List<String>> remoteWebSocketAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable userid) {
|
||||
if (remoteNode == null) return CompletableFuture.completedFuture(null);
|
||||
try {
|
||||
return remoteNode.getOnlineRemoteAddresses(targetAddress, groupid);
|
||||
return remoteNode.getWebSocketAddresses(targetAddress, userid);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "remote " + targetAddress + " websocket getOnlineRemoteAddresses error", e);
|
||||
return null;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户的节点地址列表
|
||||
* 获取用户在线的SNCP节点地址列表,不是分布式则返回元素数量为1,且元素值为null的列表<br>
|
||||
* InetSocketAddress 为 SNCP节点地址
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return 地址列表
|
||||
*/
|
||||
public Collection<InetSocketAddress> getOnlineNodes(final Serializable groupid) {
|
||||
return sncpNodes == null ? null : sncpNodes.getCollection(groupid);
|
||||
public CompletableFuture<Collection<InetSocketAddress>> getRpcNodeAddresses(final Serializable userid) {
|
||||
if (this.sncpNodeAddresses != null) return this.sncpNodeAddresses.getCollectionAsync(userid);
|
||||
List<InetSocketAddress> rs = new ArrayList<>();
|
||||
rs.add(this.localSncpAddress);
|
||||
return CompletableFuture.completedFuture(rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户的详细连接信息
|
||||
* 获取在线用户的详细连接信息 <br>
|
||||
* Map.key 为 SNCP节点地址, 含值为null的key表示没有分布式
|
||||
* Map.value 为 用户客户端的IP
|
||||
*
|
||||
* @param groupid groupid
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return 地址集合
|
||||
*/
|
||||
public Map<InetSocketAddress, List<String>> getOnlineRemoteAddress(final Serializable groupid) {
|
||||
Collection<InetSocketAddress> nodes = getOnlineNodes(groupid);
|
||||
if (nodes == null) return null;
|
||||
final Map<InetSocketAddress, List<String>> map = new HashMap();
|
||||
for (InetSocketAddress nodeAddress : nodes) {
|
||||
List<String> list = getOnlineRemoteAddresses(nodeAddress, groupid);
|
||||
if (list == null) list = new ArrayList();
|
||||
map.put(nodeAddress, list);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public final void connect(Serializable groupid, String engineid, String wsinfo) {
|
||||
if (finest) logger.finest(localSncpAddress + " receive websocket connect event (" + groupid + " on " + engineid + ").");
|
||||
Set<String> engineids = localNodes.get(groupid);
|
||||
if (engineids == null) {
|
||||
engineids = new CopyOnWriteArraySet<>();
|
||||
localNodes.putIfAbsent(groupid, engineids);
|
||||
}
|
||||
if (localSncpAddress != null && engineids.isEmpty()) connect(groupid, localSncpAddress);
|
||||
engineids.add(engineid);
|
||||
}
|
||||
|
||||
public final void disconnect(Serializable groupid, String engineid) {
|
||||
if (finest) logger.finest(localSncpAddress + " receive websocket disconnect event (" + groupid + " on " + engineid + ").");
|
||||
Set<String> engineids = localNodes.get(groupid);
|
||||
if (engineids == null || engineids.isEmpty()) return;
|
||||
engineids.remove(engineid);
|
||||
if (engineids.isEmpty()) {
|
||||
localNodes.remove(groupid);
|
||||
if (localSncpAddress != null) disconnect(groupid, localSncpAddress);
|
||||
}
|
||||
}
|
||||
|
||||
final void putWebSocketEngine(WebSocketEngine engine) {
|
||||
engines.put(engine.getEngineid(), engine);
|
||||
}
|
||||
|
||||
public final int sendMessage(Serializable groupid, boolean recent, Object message, boolean last) {
|
||||
final Set<String> engineids = localNodes.get(groupid);
|
||||
if (finest) logger.finest("websocket want send message {groupid:" + groupid + ", content:'" + message + "'} from locale node to " + engineids);
|
||||
int rscode = RETCODE_GROUP_EMPTY;
|
||||
if (engineids != null && !engineids.isEmpty()) {
|
||||
for (String engineid : engineids) {
|
||||
final WebSocketEngine engine = engines.get(engineid);
|
||||
if (engine != null) { //在本地
|
||||
final WebSocketGroup group = engine.getWebSocketGroup(groupid);
|
||||
if (group == null || group.isEmpty()) {
|
||||
engineids.remove(engineid);
|
||||
if (finest) logger.finest("websocket want send message {engineid:'" + engineid + "', groupid:" + groupid + ", content:'" + message + "'} but websocket group is empty ");
|
||||
rscode = RETCODE_GROUP_EMPTY;
|
||||
break;
|
||||
}
|
||||
rscode = group.send(recent, message, last);
|
||||
}
|
||||
public CompletableFuture<Map<InetSocketAddress, List<String>>> getRpcNodeWebSocketAddresses(final Serializable userid) {
|
||||
CompletableFuture<Collection<InetSocketAddress>> sncpFuture = getRpcNodeAddresses(userid);
|
||||
return sncpFuture.thenCompose((Collection<InetSocketAddress> addrs) -> {
|
||||
if (finest) logger.finest("websocket found userid:" + userid + " on " + addrs);
|
||||
if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(new HashMap<>());
|
||||
CompletableFuture<Map<InetSocketAddress, List<String>>> future = null;
|
||||
for (final InetSocketAddress nodeAddress : addrs) {
|
||||
CompletableFuture<Map<InetSocketAddress, List<String>>> mapFuture = getWebSocketAddresses(nodeAddress, userid)
|
||||
.thenCompose((List<String> list) -> CompletableFuture.completedFuture(Utility.ofMap(nodeAddress, list)));
|
||||
future = future == null ? mapFuture : future.thenCombine(mapFuture, (a, b) -> Utility.merge(a, b));
|
||||
}
|
||||
}
|
||||
if ((recent && rscode == 0) || remoteNode == null || sncpNodes == null) {
|
||||
if (finest) {
|
||||
if ((recent && rscode == 0)) {
|
||||
logger.finest("websocket want send recent message success");
|
||||
} else {
|
||||
logger.finest("websocket remote node is null");
|
||||
}
|
||||
}
|
||||
return rscode;
|
||||
}
|
||||
//-----------------------发送远程的-----------------------------
|
||||
Collection<InetSocketAddress> addrs = sncpNodes.getCollection(groupid);
|
||||
if (finest) logger.finest("websocket found groupid:" + groupid + " on " + addrs);
|
||||
if (addrs != null && !addrs.isEmpty()) { //对方连接在远程节点(包含本地节点),所以正常情况下addrs不会为空。
|
||||
if (recent) {
|
||||
InetSocketAddress one = null;
|
||||
for (InetSocketAddress addr : addrs) {
|
||||
one = addr;
|
||||
}
|
||||
rscode = remoteNode.sendMessage(one, groupid, recent, message, last);
|
||||
} else {
|
||||
for (InetSocketAddress addr : addrs) {
|
||||
if (!addr.equals(localSncpAddress)) {
|
||||
rscode |= remoteNode.sendMessage(addr, groupid, recent, message, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rscode = RETCODE_GROUP_EMPTY;
|
||||
}
|
||||
return rscode;
|
||||
return future == null ? CompletableFuture.completedFuture(new HashMap<>()) : future;
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
public final int sendEachMessage(Serializable groupid, String text) {
|
||||
return sendMessage(groupid, false, (Object) text, true);
|
||||
public final CompletableFuture<Integer> sendMessage(Object message, final Serializable... userids) {
|
||||
return sendMessage(message, true, userids);
|
||||
}
|
||||
|
||||
public final int sendEachMessage(Serializable groupid, String text, boolean last) {
|
||||
return sendMessage(groupid, false, (Object) text, last);
|
||||
/**
|
||||
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
|
||||
* 如果当前WebSocketNode是远程模式,此方法只发送远程连接
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @param last 是否最后一条
|
||||
* @param userids Serializable[]
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示部分发送异常
|
||||
*/
|
||||
//最近连接发送逻辑还没有理清楚
|
||||
public final CompletableFuture<Integer> sendMessage(final Object message, final boolean last, final Serializable... userids) {
|
||||
if (userids == null || userids.length < 1) return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
|
||||
if (this.localEngine != null && this.sncpNodeAddresses == null) { //本地模式且没有分布式
|
||||
return this.localEngine.sendMessage(message, last, userids);
|
||||
}
|
||||
CompletableFuture<Integer> future = null;
|
||||
for (Serializable userid : userids) {
|
||||
future = future == null ? sendOneMessage(message, last, userid)
|
||||
: future.thenCombine(sendOneMessage(message, last, userid), (a, b) -> a | b);
|
||||
}
|
||||
return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
|
||||
}
|
||||
|
||||
public final int sendRecentMessage(Serializable groupid, String text) {
|
||||
return sendMessage(groupid, true, (Object) text, true);
|
||||
/**
|
||||
* 广播消息, 给所有人发消息
|
||||
*
|
||||
* @param message 消息内容
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示部分发送异常
|
||||
*/
|
||||
public final CompletableFuture<Integer> broadcastMessage(final Object message) {
|
||||
return broadcastMessage(message, true);
|
||||
}
|
||||
|
||||
public final int sendRecentMessage(Serializable groupid, String text, boolean last) {
|
||||
return sendMessage(groupid, true, (Object) text, last);
|
||||
/**
|
||||
* 广播消息, 给所有人发消息
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示部分发送异常
|
||||
*/
|
||||
public final CompletableFuture<Integer> broadcastMessage(final Object message, final boolean last) {
|
||||
if (this.localEngine != null && this.sncpNodeAddresses == null) { //本地模式且没有分布式
|
||||
return this.localEngine.broadcastMessage(message, last);
|
||||
}
|
||||
CompletableFuture<Integer> localFuture = this.localEngine == null ? null : this.localEngine.broadcastMessage(message, last);
|
||||
CompletableFuture<Collection<InetSocketAddress>> addrsFuture = sncpNodeAddresses.getCollectionAsync("redkale_sncpnodes");
|
||||
CompletableFuture<Integer> remoteFuture = addrsFuture.thenCompose((Collection<InetSocketAddress> addrs) -> {
|
||||
if (finest) logger.finest("websocket broadcast message on " + addrs);
|
||||
if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
|
||||
CompletableFuture<Integer> future = null;
|
||||
for (InetSocketAddress addr : addrs) {
|
||||
if (addr == null || addr.equals(localSncpAddress)) continue;
|
||||
future = future == null ? remoteNode.broadcastMessage(addr, message, last)
|
||||
: future.thenCombine(remoteNode.broadcastMessage(addr, message, last), (a, b) -> a | b);
|
||||
}
|
||||
return future == null ? CompletableFuture.completedFuture(0) : future;
|
||||
});
|
||||
return localFuture == null ? remoteFuture : localFuture.thenCombine(remoteFuture, (a, b) -> a | b);
|
||||
}
|
||||
|
||||
public final int sendMessage(Serializable groupid, boolean recent, String text) {
|
||||
return sendMessage(groupid, recent, (Object) text, true);
|
||||
private CompletableFuture<Integer> sendOneMessage(final Object message, final boolean last, final Serializable userid) {
|
||||
if (finest) logger.finest("websocket want send message {userid:" + userid + ", content:'" + message + "'} from locale node to locale engine");
|
||||
CompletableFuture<Integer> localFuture = null;
|
||||
if (this.localEngine != null) localFuture = localEngine.sendMessage(message, last, userid);
|
||||
if (this.sncpNodeAddresses == null || this.remoteNode == null) {
|
||||
if (finest) logger.finest("websocket remote node is null");
|
||||
//没有CacheSource就不会有分布式节点
|
||||
return localFuture == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : localFuture;
|
||||
}
|
||||
//远程节点发送消息
|
||||
CompletableFuture<Collection<InetSocketAddress>> addrsFuture = sncpNodeAddresses.getCollectionAsync(userid);
|
||||
CompletableFuture<Integer> remoteFuture = addrsFuture.thenCompose((Collection<InetSocketAddress> addrs) -> {
|
||||
if (finest) logger.finest("websocket found userid:" + userid + " on " + addrs);
|
||||
if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
|
||||
CompletableFuture<Integer> future = null;
|
||||
for (InetSocketAddress addr : addrs) {
|
||||
if (addr == null || addr.equals(localSncpAddress)) continue;
|
||||
future = future == null ? remoteNode.sendMessage(addr, message, last, userid)
|
||||
: future.thenCombine(remoteNode.sendMessage(addr, message, last, userid), (a, b) -> a | b);
|
||||
}
|
||||
return future == null ? CompletableFuture.completedFuture(0) : future;
|
||||
});
|
||||
return localFuture == null ? remoteFuture : localFuture.thenCombine(remoteFuture, (a, b) -> a | b);
|
||||
}
|
||||
|
||||
public final int sendMessage(Serializable groupid, boolean recent, String text, boolean last) {
|
||||
return sendMessage(groupid, recent, (Object) text, last);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
public final int sendEachMessage(Serializable groupid, byte[] data) {
|
||||
return sendMessage(groupid, false, (Object) data, true);
|
||||
}
|
||||
|
||||
public final int sendEachMessage(Serializable groupid, byte[] data, boolean last) {
|
||||
return sendMessage(groupid, false, (Object) data, last);
|
||||
}
|
||||
|
||||
public final int sendRecentMessage(Serializable groupid, byte[] data) {
|
||||
return sendMessage(groupid, true, (Object) data, true);
|
||||
}
|
||||
|
||||
public final int sendRecentMessage(Serializable groupid, byte[] data, boolean last) {
|
||||
return sendMessage(groupid, true, (Object) data, last);
|
||||
}
|
||||
|
||||
public final int sendMessage(Serializable groupid, boolean recent, byte[] data) {
|
||||
return sendMessage(groupid, recent, data, true);
|
||||
}
|
||||
|
||||
public final int sendMessage(Serializable groupid, boolean recent, byte[] data, boolean last) {
|
||||
return sendMessage(groupid, recent, (Object) data, last);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
public final int sendEachMessage(Serializable groupid, Object message) {
|
||||
return sendMessage(groupid, false, message, true);
|
||||
}
|
||||
|
||||
public final int sendEachMessage(Serializable groupid, Object message, boolean last) {
|
||||
return sendMessage(groupid, false, message, last);
|
||||
}
|
||||
|
||||
public final int sendRecentMessage(Serializable groupid, Object message) {
|
||||
return sendMessage(groupid, true, message, true);
|
||||
}
|
||||
|
||||
public final int sendRecentMessage(Serializable groupid, Object message, boolean last) {
|
||||
return sendMessage(groupid, true, message, last);
|
||||
}
|
||||
|
||||
public final int sendMessage(Serializable groupid, boolean recent, Object message) {
|
||||
return sendMessage(groupid, recent, message, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,10 +7,17 @@ package org.redkale.net.http;
|
||||
|
||||
import org.redkale.util.Utility;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.*;
|
||||
import org.redkale.convert.ConvertMask;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class WebSocketPacket {
|
||||
@@ -51,6 +58,16 @@ public final class WebSocketPacket {
|
||||
|
||||
protected boolean last = true;
|
||||
|
||||
protected Object sendJson;
|
||||
|
||||
JsonConvert sendConvert;
|
||||
|
||||
ByteBuffer[] sendBuffers;
|
||||
|
||||
ConvertMask receiveMasker;
|
||||
|
||||
ByteBuffer[] receiveBuffers;
|
||||
|
||||
public WebSocketPacket() {
|
||||
}
|
||||
|
||||
@@ -76,6 +93,31 @@ public final class WebSocketPacket {
|
||||
this.last = fin;
|
||||
}
|
||||
|
||||
public WebSocketPacket(JsonConvert convert, Object json, boolean fin) {
|
||||
this.type = FrameType.TEXT;
|
||||
this.sendConvert = convert;
|
||||
this.sendJson = json;
|
||||
this.last = fin;
|
||||
}
|
||||
|
||||
WebSocketPacket(ByteBuffer[] sendBuffers, FrameType type, boolean fin) {
|
||||
this.type = type;
|
||||
this.last = fin;
|
||||
this.setSendBuffers(sendBuffers);
|
||||
}
|
||||
|
||||
void setSendBuffers(ByteBuffer[] sendBuffers) {
|
||||
this.sendBuffers = sendBuffers;
|
||||
}
|
||||
|
||||
ByteBuffer[] duplicateSendBuffers() {
|
||||
ByteBuffer[] rs = new ByteBuffer[this.sendBuffers.length];
|
||||
for (int i = 0; i < this.sendBuffers.length; i++) {
|
||||
rs[i] = this.sendBuffers[i].duplicate();
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public WebSocketPacket(byte[] data) {
|
||||
this(FrameType.BINARY, data, true);
|
||||
}
|
||||
@@ -120,4 +162,226 @@ public final class WebSocketPacket {
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + (payload != null ? (", payload=" + payload) : "") + (bytes != null ? (", bytes=[" + bytes.length + ']') : "") + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息编码
|
||||
*
|
||||
* @param supplier Supplier
|
||||
*
|
||||
* @return ByteBuffer[]
|
||||
*/
|
||||
ByteBuffer[] encode(final Supplier<ByteBuffer> supplier) {
|
||||
final byte opcode = (byte) (this.type.getValue() | 0x80);
|
||||
if (this.sendConvert != null) {
|
||||
Supplier<ByteBuffer> newsupplier = new Supplier<ByteBuffer>() {
|
||||
|
||||
private ByteBuffer buf = supplier.get();
|
||||
|
||||
@Override
|
||||
public ByteBuffer get() {
|
||||
if (buf != null) {
|
||||
ByteBuffer rs = buf;
|
||||
rs.position(6);
|
||||
this.buf = null;
|
||||
return rs;
|
||||
}
|
||||
return supplier.get();
|
||||
}
|
||||
};
|
||||
ByteBuffer[] buffers = this.sendConvert.convertTo(newsupplier, sendJson);
|
||||
int len = 0;
|
||||
for (ByteBuffer buf : buffers) {
|
||||
len += buf.remaining();
|
||||
}
|
||||
int contentLength = len - 6;
|
||||
ByteBuffer firstbuf = buffers[0];
|
||||
if (contentLength <= 0x7D) { //125
|
||||
firstbuf.put(4, opcode);
|
||||
firstbuf.put(5, (byte) contentLength);
|
||||
firstbuf.position(4);
|
||||
} else if (contentLength <= 0xFFFF) {
|
||||
firstbuf.put(2, opcode);
|
||||
firstbuf.put(3, (byte) 0x7E); //126
|
||||
firstbuf.putChar(4, (char) contentLength);
|
||||
firstbuf.position(2);
|
||||
} else {
|
||||
firstbuf.put(0, opcode);
|
||||
firstbuf.put(1, (byte) 0x7F); //127
|
||||
firstbuf.putInt(2, contentLength);
|
||||
}
|
||||
return buffers;
|
||||
}
|
||||
|
||||
ByteBuffer buffer = supplier.get(); //确保ByteBuffer的capacity不能小于128
|
||||
final byte[] content = getContent();
|
||||
final int len = content.length;
|
||||
if (len <= 0x7D) { //125
|
||||
buffer.put(opcode);
|
||||
buffer.put((byte) len);
|
||||
buffer.put(content);
|
||||
buffer.flip();
|
||||
return new ByteBuffer[]{buffer};
|
||||
}
|
||||
if (len <= 0xFFFF) { // 65535
|
||||
buffer.put(opcode);
|
||||
buffer.put((byte) 0x7E); //126
|
||||
buffer.putChar((char) len);
|
||||
} else {
|
||||
buffer.put(opcode);
|
||||
buffer.put((byte) 0x7F); //127
|
||||
buffer.putInt(len);
|
||||
}
|
||||
int start = buffer.remaining();
|
||||
int pend = len - buffer.remaining();
|
||||
if (pend <= 0) {
|
||||
buffer.put(content);
|
||||
buffer.flip();
|
||||
return new ByteBuffer[]{buffer};
|
||||
}
|
||||
buffer.put(content, 0, buffer.remaining());
|
||||
buffer.flip();
|
||||
final int capacity = buffer.capacity();
|
||||
final ByteBuffer[] buffers = new ByteBuffer[(pend / capacity) + 1 + ((pend % capacity) > 0 ? 1 : 0)];
|
||||
buffers[0] = buffer;
|
||||
for (int i = 1; i < buffers.length; i++) {
|
||||
ByteBuffer buf = supplier.get();
|
||||
buf.put(content, start, Math.min(pend, capacity));
|
||||
buf.flip();
|
||||
buffers[i] = buf;
|
||||
start += capacity;
|
||||
pend -= capacity;
|
||||
}
|
||||
return buffers;
|
||||
}
|
||||
|
||||
// public static void main(String[] args) throws Throwable {
|
||||
// byte[] mask = new byte[]{(byte) 0x8f, (byte) 0xf8, (byte) 0x6d, (byte) 0x94};
|
||||
// ByteBuffer buffer = ByteBuffer.wrap(new byte[]{(byte) 0x67, (byte) 0x47, (byte) 0xf4, (byte) 0x70, (byte) 0x37, (byte) 0x52, (byte) 0x8b, (byte) 0x0c, (byte) 0x20, (byte) 0x1e, (byte) 0xdb, (byte) 0x1c, (byte) 0x69, (byte) 0x79, (byte) 0xc2});
|
||||
// ConvertMask masker = new ConvertMask() {
|
||||
// private int index = 0;
|
||||
//
|
||||
// public byte unmask(byte value) {
|
||||
// return (byte) (value ^ mask[index++ % 4]);
|
||||
// }
|
||||
// };
|
||||
// String rs = JsonConvert.root().convertFrom(String.class, masker, buffer);
|
||||
// System.out.println(rs);
|
||||
// }
|
||||
/**
|
||||
* 消息解码 <br>
|
||||
*
|
||||
* 0 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
* |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
* |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
* |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
* | |1|2|3| |K| | |
|
||||
* +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
* | Extended payload length continued, if payload len == 127 |
|
||||
* + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
* | |Masking-key, if MASK set to 1 |
|
||||
* +-------------------------------+-------------------------------+
|
||||
* | Masking-key (continued) | Payload Data |
|
||||
* +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
* : Payload Data continued :
|
||||
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
* | Payload Data continued |
|
||||
* +-----------------------------------------------------------------------+
|
||||
*
|
||||
* @param buffer
|
||||
* @param exbuffers
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
WebSocketPacket decode(final Logger logger, final ByteBuffer buffer, ByteBuffer... exbuffers) {
|
||||
final boolean debug = false; //调试开关
|
||||
if (debug) {
|
||||
int remain = buffer.remaining();
|
||||
if (exbuffers != null) {
|
||||
for (ByteBuffer b : exbuffers) {
|
||||
remain += b == null ? 0 : b.remaining();
|
||||
}
|
||||
}
|
||||
logger.log(Level.FINEST, "read websocket message's length = " + remain);
|
||||
}
|
||||
if (buffer.remaining() < 2) return null;
|
||||
byte opcode = buffer.get();
|
||||
this.last = (opcode & 0b1000_0000) != 0;
|
||||
this.type = FrameType.valueOf(opcode & 0xF);
|
||||
if (type == FrameType.CLOSE) {
|
||||
if (debug) logger.log(Level.FINEST, " receive close command from websocket client");
|
||||
return this;
|
||||
}
|
||||
final boolean checkrsv = false;//暂时不校验
|
||||
if (checkrsv && (opcode & 0b0111_0000) != 0) {
|
||||
if (debug) logger.log(Level.FINE, "rsv1 rsv2 rsv3 must be 0, but not (" + opcode + ")");
|
||||
return null; //rsv1 rsv2 rsv3 must be 0
|
||||
}
|
||||
//0x00 表示一个后续帧
|
||||
//0x01 表示一个文本帧
|
||||
//0x02 表示一个二进制帧
|
||||
//0x03-07 为以后的非控制帧保留
|
||||
//0x8 表示一个连接关闭
|
||||
//0x9 表示一个ping
|
||||
//0xA 表示一个pong
|
||||
//0x0B-0F 为以后的控制帧保留
|
||||
final boolean control = (opcode & 0b0000_1000) != 0; //是否控制帧
|
||||
byte lengthCode = buffer.get();
|
||||
final boolean masked = (lengthCode & 0x80) == 0x80;
|
||||
if (masked) lengthCode ^= 0x80; //mask
|
||||
int length;
|
||||
if (lengthCode <= 0x7D) { //125
|
||||
length = lengthCode;
|
||||
} else {
|
||||
if (control) {
|
||||
if (debug) logger.log(Level.FINE, " receive control command from websocket client");
|
||||
return null;
|
||||
}
|
||||
if (lengthCode == 0x7E) {//0x7E=126
|
||||
length = (int) buffer.getChar();
|
||||
} else {
|
||||
length = buffer.getInt();
|
||||
}
|
||||
}
|
||||
if (masked) {
|
||||
final byte[] masks = new byte[4];
|
||||
buffer.get(masks);
|
||||
this.receiveMasker = new ConvertMask() {
|
||||
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public byte unmask(byte value) {
|
||||
return (byte) (value ^ masks[index++ % 4]);
|
||||
}
|
||||
};
|
||||
}
|
||||
this.receiveBuffers = Utility.append(new ByteBuffer[]{buffer}, exbuffers);
|
||||
return this;
|
||||
}
|
||||
|
||||
byte[] getReceiveBytes() {
|
||||
if (this.receiveBuffers.length == 0) return new byte[0];
|
||||
if (this.receiveBuffers.length == 1 && this.receiveBuffers[0].remaining() == 0) return new byte[0];
|
||||
|
||||
int count = 0;
|
||||
for (ByteBuffer buf : this.receiveBuffers) {
|
||||
count += buf.remaining();
|
||||
}
|
||||
byte[] bs = new byte[count];
|
||||
int index = 0;
|
||||
for (ByteBuffer buf : this.receiveBuffers) {
|
||||
int r = buf.remaining();
|
||||
buf.get(bs, index, r);
|
||||
index += r;
|
||||
}
|
||||
ConvertMask mask = this.receiveMasker;
|
||||
if (mask != null) {
|
||||
for (int i = 0; i < bs.length; i++) {
|
||||
bs[i] = mask.unmask(bs[i]);
|
||||
}
|
||||
}
|
||||
return bs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,22 @@ import static org.redkale.net.http.WebSocket.*;
|
||||
import org.redkale.net.http.WebSocketPacket.FrameType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
|
||||
/**
|
||||
* WebSocket的消息接收发送器, 一个WebSocket对应一个WebSocketRunner
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class WebSocketRunner implements Runnable {
|
||||
class WebSocketRunner implements Runnable {
|
||||
|
||||
private final WebSocketEngine engine;
|
||||
|
||||
@@ -34,34 +38,34 @@ public class WebSocketRunner implements Runnable {
|
||||
|
||||
private ByteBuffer readBuffer;
|
||||
|
||||
private ByteBuffer writeBuffer;
|
||||
|
||||
protected boolean closed = false;
|
||||
protected volatile boolean closed = false;
|
||||
|
||||
private AtomicBoolean writing = new AtomicBoolean();
|
||||
|
||||
private final Coder coder = new Coder();
|
||||
|
||||
private final BlockingQueue<byte[]> queue = new ArrayBlockingQueue(1024);
|
||||
private final BlockingQueue<QueueEntry> queue = new ArrayBlockingQueue(1024);
|
||||
|
||||
private final boolean wsbinary;
|
||||
|
||||
public WebSocketRunner(Context context, WebSocket webSocket, AsyncConnection channel, final boolean wsbinary) {
|
||||
private final BiConsumer<WebSocket, Object> restMessageConsumer; //主要供RestWebSocket使用
|
||||
|
||||
protected long lastSendTime;
|
||||
|
||||
protected final JsonConvert convert;
|
||||
|
||||
WebSocketRunner(Context context, WebSocket webSocket, BiConsumer<WebSocket, Object> messageConsumer, AsyncConnection channel, final boolean wsbinary) {
|
||||
this.context = context;
|
||||
this.engine = webSocket._engine;
|
||||
this.webSocket = webSocket;
|
||||
this.restMessageConsumer = messageConsumer;
|
||||
this.channel = channel;
|
||||
this.wsbinary = wsbinary;
|
||||
webSocket._runner = this;
|
||||
this.coder.logger = context.getLogger();
|
||||
this.coder.debugable = false;//context.getLogger().isLoggable(Level.FINEST);
|
||||
this.readBuffer = context.pollBuffer();
|
||||
this.writeBuffer = context.pollBuffer();
|
||||
this.convert = context.getJsonConvert();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean debug = this.coder.debugable;
|
||||
final boolean debug = true;
|
||||
try {
|
||||
webSocket.onConnected();
|
||||
channel.setReadTimeoutSecond(300); //读取超时5分钟
|
||||
@@ -93,48 +97,90 @@ public class WebSocketRunner implements Runnable {
|
||||
return;
|
||||
}
|
||||
readBuffer.flip();
|
||||
|
||||
ByteBuffer[] exBuffers = null;
|
||||
if (!readBuffers.isEmpty()) {
|
||||
exBuffers = readBuffers.toArray(new ByteBuffer[readBuffers.size()]);
|
||||
readBuffers.clear();
|
||||
recentExBuffer = null;
|
||||
for (ByteBuffer b : exBuffers) {
|
||||
b.flip();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ByteBuffer[] exBuffers = null;
|
||||
if (!readBuffers.isEmpty()) {
|
||||
exBuffers = readBuffers.toArray(new ByteBuffer[readBuffers.size()]);
|
||||
readBuffers.clear();
|
||||
recentExBuffer = null;
|
||||
for (ByteBuffer b : exBuffers) {
|
||||
b.flip();
|
||||
}
|
||||
}
|
||||
WebSocketPacket packet = coder.decode(readBuffer, exBuffers);
|
||||
if (exBuffers != null) {
|
||||
for (ByteBuffer b : exBuffers) {
|
||||
context.offerBuffer(b);
|
||||
}
|
||||
}
|
||||
WebSocketPacket packet = new WebSocketPacket().decode(context.getLogger(), readBuffer, exBuffers);
|
||||
|
||||
if (packet == null) {
|
||||
failed(null, attachment1);
|
||||
if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on decode WebSocketPacket, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds");
|
||||
return;
|
||||
}
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
webSocket._group.setRecentWebSocket(webSocket);
|
||||
try {
|
||||
if (packet.type == FrameType.TEXT) {
|
||||
webSocket.onMessage(packet.getPayload());
|
||||
} else if (packet.type == FrameType.BINARY) {
|
||||
webSocket.onMessage(packet.getBytes());
|
||||
} else if (packet.type == FrameType.PONG) {
|
||||
webSocket.onPong(packet.getBytes());
|
||||
} else if (packet.type == FrameType.PING) {
|
||||
webSocket.onPing(packet.getBytes());
|
||||
|
||||
if (packet.type == FrameType.TEXT) {
|
||||
Object message = convert.convertFrom(webSocket._messageTextType, packet.receiveMasker, packet.receiveBuffers);
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
try {
|
||||
if (restMessageConsumer != null) { //主要供RestWebSocket使用
|
||||
restMessageConsumer.accept(webSocket, message);
|
||||
} else {
|
||||
webSocket.onMessage(message, packet.last);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onTextMessage error (" + packet + ")", e);
|
||||
}
|
||||
} else if (packet.type == FrameType.BINARY) {
|
||||
byte[] message = packet.getReceiveBytes();
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
try {
|
||||
webSocket.onMessage(message, packet.last);
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e);
|
||||
}
|
||||
} else if (packet.type == FrameType.PONG) {
|
||||
byte[] message = packet.getReceiveBytes();
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
try {
|
||||
webSocket.onPong(message);
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onPong error (" + packet + ")", e);
|
||||
}
|
||||
} else if (packet.type == FrameType.PING) {
|
||||
byte[] message = packet.getReceiveBytes();
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
try {
|
||||
webSocket.onPing(message);
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onPing error (" + packet + ")", e);
|
||||
}
|
||||
} else {
|
||||
context.getLogger().log(Level.WARNING, "WebSocketRunner onMessage by unknown FrameType : " + packet);
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.INFO, "WebSocket onMessage error (" + packet + ")", e);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
closeRunner();
|
||||
if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read WebSocketPacket, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t);
|
||||
} finally {
|
||||
if (exBuffers != null) {
|
||||
for (ByteBuffer b : exBuffers) {
|
||||
context.offerBuffer(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,54 +202,66 @@ public class WebSocketRunner implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
public int sendMessage(WebSocketPacket packet) {
|
||||
if (packet == null) return RETCODE_SEND_ILLPACKET;
|
||||
if (closed) return RETCODE_WSOCKET_CLOSED;
|
||||
final boolean debug = this.coder.debugable;
|
||||
public CompletableFuture<Integer> sendMessage(WebSocketPacket packet) {
|
||||
if (packet == null) return CompletableFuture.completedFuture(RETCODE_SEND_ILLPACKET);
|
||||
if (closed) return CompletableFuture.completedFuture(RETCODE_WSOCKET_CLOSED);
|
||||
boolean debug = true;
|
||||
//System.out.println("推送消息");
|
||||
final byte[] bytes = coder.encode(packet);
|
||||
if (debug) context.getLogger().log(Level.FINEST, "send web socket message's length = " + bytes.length);
|
||||
if (debug) context.getLogger().log(Level.FINEST, "send web socket message: " + packet);
|
||||
final CompletableFuture<Integer> futureResult = new CompletableFuture<>();
|
||||
if (writing.getAndSet(true)) {
|
||||
queue.add(bytes);
|
||||
return 0;
|
||||
}
|
||||
if (writeBuffer == null) return RETCODE_ILLEGALBUFFER;
|
||||
ByteBuffer sendBuffer = null;
|
||||
if (bytes.length <= writeBuffer.capacity()) {
|
||||
writeBuffer.clear();
|
||||
writeBuffer.put(bytes);
|
||||
writeBuffer.flip();
|
||||
sendBuffer = writeBuffer;
|
||||
} else {
|
||||
sendBuffer = ByteBuffer.wrap(bytes);
|
||||
queue.add(new QueueEntry(futureResult, packet));
|
||||
return futureResult;
|
||||
}
|
||||
ByteBuffer[] buffers = packet.sendBuffers != null ? packet.duplicateSendBuffers() : packet.encode(this.context.getBufferSupplier());
|
||||
try {
|
||||
channel.write(sendBuffer, sendBuffer, new CompletionHandler<Integer, ByteBuffer>() {
|
||||
this.lastSendTime = System.currentTimeMillis();
|
||||
channel.write(buffers, buffers, new CompletionHandler<Integer, ByteBuffer[]>() {
|
||||
|
||||
private CompletableFuture<Integer> future = futureResult;
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, ByteBuffer attachment) {
|
||||
if (attachment == null || closed) return;
|
||||
try {
|
||||
if (attachment.hasRemaining()) {
|
||||
if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner write completed reemaining: " + attachment.remaining());
|
||||
channel.write(attachment, attachment, this);
|
||||
return;
|
||||
}
|
||||
byte[] bs = queue.poll();
|
||||
if (bs != null && writeBuffer != null) {
|
||||
ByteBuffer sendBuffer;
|
||||
if (bs.length <= writeBuffer.capacity()) {
|
||||
writeBuffer.clear();
|
||||
writeBuffer.put(bs);
|
||||
writeBuffer.flip();
|
||||
sendBuffer = writeBuffer;
|
||||
} else {
|
||||
sendBuffer = ByteBuffer.wrap(bs);
|
||||
public void completed(Integer result, ByteBuffer[] attachments) {
|
||||
if (attachments == null || closed) {
|
||||
if (future != null) {
|
||||
future.complete(RETCODE_WSOCKET_CLOSED);
|
||||
future = null;
|
||||
if (attachments != null) {
|
||||
for (ByteBuffer buf : attachments) {
|
||||
context.offerBuffer(buf);
|
||||
}
|
||||
}
|
||||
channel.write(sendBuffer, sendBuffer, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int index = -1;
|
||||
for (int i = 0; i < attachments.length; i++) {
|
||||
if (attachments[i].hasRemaining()) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
channel.write(attachments, index, attachments.length - index, attachments, this);
|
||||
return;
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
if (future != null) {
|
||||
future.complete(0);
|
||||
future = null;
|
||||
if (attachments != null) {
|
||||
for (ByteBuffer buf : attachments) {
|
||||
context.offerBuffer(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
QueueEntry entry = queue.poll();
|
||||
if (entry != null) {
|
||||
future = entry.future;
|
||||
ByteBuffer[] buffers = packet.sendBuffers != null ? packet.duplicateSendBuffers() : packet.encode(context.getBufferSupplier());
|
||||
lastSendTime = System.currentTimeMillis();
|
||||
channel.write(buffers, buffers, this);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
closeRunner();
|
||||
context.getLogger().log(Level.WARNING, "WebSocket sendMessage abort on rewrite, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e);
|
||||
@@ -212,7 +270,7 @@ public class WebSocketRunner implements Runnable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, ByteBuffer attachment) {
|
||||
public void failed(Throwable exc, ByteBuffer[] attachments) {
|
||||
writing.set(false);
|
||||
closeRunner();
|
||||
if (exc != null) {
|
||||
@@ -220,13 +278,13 @@ public class WebSocketRunner implements Runnable {
|
||||
}
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
} catch (Exception t) {
|
||||
writing.set(false);
|
||||
closeRunner();
|
||||
context.getLogger().log(Level.FINE, "WebSocket sendMessage abort, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t);
|
||||
return RETCODE_SENDEXCEPTION;
|
||||
futureResult.complete(RETCODE_SENDEXCEPTION);
|
||||
}
|
||||
return futureResult;
|
||||
}
|
||||
|
||||
public void closeRunner() {
|
||||
@@ -239,286 +297,21 @@ public class WebSocketRunner implements Runnable {
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
context.offerBuffer(readBuffer);
|
||||
context.offerBuffer(writeBuffer);
|
||||
readBuffer = null;
|
||||
writeBuffer = null;
|
||||
engine.remove(webSocket);
|
||||
webSocket.onClose(0, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Masker {
|
||||
private static final class QueueEntry {
|
||||
|
||||
public static final int MASK_SIZE = 4;
|
||||
public final CompletableFuture<Integer> future;
|
||||
|
||||
private ByteBuffer buffer;
|
||||
public final WebSocketPacket packet;
|
||||
|
||||
private ByteBuffer[] exbuffers;
|
||||
|
||||
private byte[] mask;
|
||||
|
||||
private int index = 0;
|
||||
|
||||
public Masker(ByteBuffer buffer, ByteBuffer... exbuffers) {
|
||||
this.buffer = buffer;
|
||||
this.exbuffers = exbuffers == null || exbuffers.length == 0 ? null : exbuffers;
|
||||
}
|
||||
|
||||
public Masker() {
|
||||
generateMask();
|
||||
}
|
||||
|
||||
public int remaining() {
|
||||
int r = buffer.remaining();
|
||||
if (exbuffers != null) {
|
||||
for (ByteBuffer b : exbuffers) {
|
||||
r += b.remaining();
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public byte get() {
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
public byte[] get(final int size) {
|
||||
byte[] bytes = new byte[size];
|
||||
if (buffer.remaining() >= size) {
|
||||
buffer.get(bytes);
|
||||
} else { //必须有 exbuffers
|
||||
int offset = buffer.remaining();
|
||||
buffer.get(bytes, 0, buffer.remaining());
|
||||
for (ByteBuffer b : exbuffers) {
|
||||
b.get(bytes, offset, b.remaining());
|
||||
offset += b.remaining();
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public byte unmask() {
|
||||
final byte b = get();
|
||||
return mask == null ? b : (byte) (b ^ mask[index++ % MASK_SIZE]);
|
||||
}
|
||||
|
||||
public byte[] unmask(int count) {
|
||||
byte[] bytes = get(count);
|
||||
if (mask != null) {
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] ^= mask[index++ % MASK_SIZE];
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void generateMask() {
|
||||
mask = new byte[MASK_SIZE];
|
||||
new SecureRandom().nextBytes(mask);
|
||||
}
|
||||
|
||||
public void mask(byte[] bytes, int location, byte b) {
|
||||
bytes[location] = mask == null ? b : (byte) (b ^ mask[index++ % MASK_SIZE]);
|
||||
}
|
||||
|
||||
public void mask(byte[] target, int location, byte[] bytes) {
|
||||
if (bytes != null && target != null) {
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
target[location + i] = mask == null ? bytes[i] : (byte) (bytes[i] ^ mask[index++ % MASK_SIZE]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] maskAndPrepend(byte[] packet) {
|
||||
byte[] masked = new byte[packet.length + MASK_SIZE];
|
||||
System.arraycopy(getMask(), 0, masked, 0, MASK_SIZE);
|
||||
mask(masked, MASK_SIZE, packet);
|
||||
return masked;
|
||||
}
|
||||
|
||||
public void setBuffer(ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public byte[] getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
public void readMask() {
|
||||
mask = get(MASK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Coder {
|
||||
|
||||
protected byte inFragmentedType;
|
||||
|
||||
protected byte outFragmentedType;
|
||||
|
||||
protected final boolean maskData = false;
|
||||
|
||||
protected boolean processingFragment;
|
||||
|
||||
private boolean debugable;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
/**
|
||||
* 0 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
* |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
* |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
* |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
* | |1|2|3| |K| | |
|
||||
* +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
* | Extended payload length continued, if payload len == 127 |
|
||||
* + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
* | |Masking-key, if MASK set to 1 |
|
||||
* +-------------------------------+-------------------------------+
|
||||
* | Masking-key (continued) | Payload Data |
|
||||
* +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
* : Payload Data continued :
|
||||
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
* | Payload Data continued |
|
||||
* +-----------------------------------------------------------------------+
|
||||
*
|
||||
* @param buffer
|
||||
* @param exbuffers
|
||||
* @return
|
||||
*/
|
||||
public WebSocketPacket decode(final ByteBuffer buffer, ByteBuffer... exbuffers) {
|
||||
final boolean debug = this.debugable;
|
||||
if (debug) {
|
||||
int remain = buffer.remaining();
|
||||
if (exbuffers != null) {
|
||||
for (ByteBuffer b : exbuffers) {
|
||||
remain += b == null ? 0 : b.remaining();
|
||||
}
|
||||
}
|
||||
logger.log(Level.FINEST, "read web socket message's length = " + remain);
|
||||
}
|
||||
if (buffer.remaining() < 2) return null;
|
||||
byte opcode = buffer.get();
|
||||
final boolean last = (opcode & 0b1000000) != 0;
|
||||
final boolean checkrsv = false;//暂时不校验
|
||||
if (checkrsv && (opcode & 0b01110000) != 0) {
|
||||
if (debug) logger.log(Level.FINE, "rsv1 rsv2 rsv3 must be 0, but not (" + opcode + ")");
|
||||
return null; //rsv1 rsv2 rsv3 must be 0
|
||||
}
|
||||
//0x00 表示一个后续帧
|
||||
//0x01 表示一个文本帧
|
||||
//0x02 表示一个二进制帧
|
||||
//0x03-07 为以后的非控制帧保留
|
||||
//0x8 表示一个连接关闭
|
||||
//0x9 表示一个ping
|
||||
//0xA 表示一个pong
|
||||
//0x0B-0F 为以后的控制帧保留
|
||||
final boolean control = (opcode & 0x08) == 0x08; //是否控制帧
|
||||
//final boolean continuation = opcode == 0;
|
||||
FrameType type = FrameType.valueOf(opcode & 0xf);
|
||||
if (type == FrameType.CLOSE) {
|
||||
if (debug) logger.log(Level.FINEST, " receive close command from websocket client");
|
||||
return null;
|
||||
}
|
||||
byte lengthCode = buffer.get();
|
||||
final Masker masker = new Masker(buffer, exbuffers);
|
||||
final boolean masked = (lengthCode & 0x80) == 0x80;
|
||||
if (masked) lengthCode ^= 0x80; //mask
|
||||
int length;
|
||||
if (lengthCode <= 125) {
|
||||
length = lengthCode;
|
||||
} else {
|
||||
if (control) {
|
||||
if (debug) logger.log(Level.FINE, " receive control command from websocket client");
|
||||
return null;
|
||||
}
|
||||
|
||||
final int lengthBytes = lengthCode == 126 ? 2 : 8;
|
||||
if (buffer.remaining() < lengthBytes) {
|
||||
if (debug) logger.log(Level.FINE, " read illegal message length from websocket, expect " + lengthBytes + " but " + buffer.remaining());
|
||||
return null;
|
||||
}
|
||||
length = toInt(masker.unmask(lengthBytes));
|
||||
}
|
||||
if (masked) {
|
||||
if (buffer.remaining() < Masker.MASK_SIZE) {
|
||||
if (debug) logger.log(Level.FINE, " read illegal masker length from websocket, expect " + Masker.MASK_SIZE + " but " + buffer.remaining());
|
||||
return null;
|
||||
}
|
||||
masker.readMask();
|
||||
}
|
||||
if (masker.remaining() < length) {
|
||||
if (debug) logger.log(Level.FINE, " read illegal remaining length from websocket, expect " + length + " but " + masker.remaining());
|
||||
return null;
|
||||
}
|
||||
final byte[] data = masker.unmask(length);
|
||||
if (data.length != length) {
|
||||
if (debug) logger.log(Level.FINE, " read illegal unmask length from websocket, expect " + length + " but " + data.length);
|
||||
return null;
|
||||
}
|
||||
return new WebSocketPacket(type, data, last);
|
||||
}
|
||||
|
||||
public byte[] encode(WebSocketPacket frame) {
|
||||
byte opcode = (byte) (frame.type.getValue() | 0x80);
|
||||
final byte[] bytes = frame.getContent();
|
||||
final byte[] lengthBytes = encodeLength(bytes.length);
|
||||
|
||||
int length = 1 + lengthBytes.length + bytes.length + (maskData ? Masker.MASK_SIZE : 0);
|
||||
int payloadStart = 1 + lengthBytes.length + (maskData ? Masker.MASK_SIZE : 0);
|
||||
final byte[] packet = new byte[length];
|
||||
packet[0] = opcode;
|
||||
System.arraycopy(lengthBytes, 0, packet, 1, lengthBytes.length);
|
||||
if (maskData) {
|
||||
Masker masker = new Masker();
|
||||
packet[1] |= 0x80;
|
||||
masker.mask(packet, payloadStart, bytes);
|
||||
System.arraycopy(masker.getMask(), 0, packet, payloadStart - Masker.MASK_SIZE, Masker.MASK_SIZE);
|
||||
} else {
|
||||
System.arraycopy(bytes, 0, packet, payloadStart, bytes.length);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static byte[] encodeLength(final int length) {
|
||||
byte[] lengthBytes;
|
||||
if (length <= 125) {
|
||||
lengthBytes = new byte[1];
|
||||
lengthBytes[0] = (byte) length;
|
||||
} else {
|
||||
byte[] b = toArray(length);
|
||||
if (length <= 0xFFFF) {
|
||||
lengthBytes = new byte[3];
|
||||
lengthBytes[0] = 126;
|
||||
System.arraycopy(b, 6, lengthBytes, 1, 2);
|
||||
} else {
|
||||
lengthBytes = new byte[9];
|
||||
lengthBytes[0] = 127;
|
||||
System.arraycopy(b, 0, lengthBytes, 1, 8);
|
||||
}
|
||||
}
|
||||
return lengthBytes;
|
||||
}
|
||||
|
||||
private static byte[] toArray(long length) {
|
||||
long value = length;
|
||||
byte[] b = new byte[8];
|
||||
for (int i = 7; i >= 0 && value > 0; i--) {
|
||||
b[i] = (byte) (value & 0xFF);
|
||||
value >>= 8;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private static int toInt(byte[] bytes) {
|
||||
int value = 0;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
value <<= 8;
|
||||
value ^= (int) bytes[i] & 0xFF;
|
||||
}
|
||||
return value;
|
||||
public QueueEntry(CompletableFuture<Integer> future, WebSocketPacket packet) {
|
||||
this.future = future;
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
package org.redkale.net.http;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.service.WebSocketNodeService;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
@@ -28,10 +31,7 @@ import org.redkale.util.*;
|
||||
* / \
|
||||
* / \
|
||||
* / \
|
||||
* WebSocketGroup1 WebSocketGroup2
|
||||
* / \ / \
|
||||
* / \ / \
|
||||
* WebSocket1 WebSocket2 WebSocket3 WebSocket4
|
||||
* WebSocket1 WebSocket2
|
||||
*
|
||||
* </pre></blockquote>
|
||||
*
|
||||
@@ -52,6 +52,159 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
|
||||
private final MessageDigest digest = getMessageDigest();
|
||||
|
||||
@Comment("是否用于二进制流传输")
|
||||
protected final boolean wsbinary = getClass().getAnnotation(WebSocketBinary.class) != null;
|
||||
|
||||
private final BiConsumer<WebSocket, Object> restMessageConsumer = createRestOnMessageConsumer();
|
||||
|
||||
protected Type messageTextType; //RestWebSocket时会被修改
|
||||
|
||||
protected boolean single = true; //是否单用户单连接
|
||||
|
||||
protected int liveinterval = DEFAILT_LIVEINTERVAL;
|
||||
|
||||
@Resource
|
||||
protected JsonConvert jsonConvert; //Rest.createRestWebSocketServlet 需要过滤掉已有的@Resource
|
||||
|
||||
@Resource(name = "$")
|
||||
protected WebSocketNode node; //Rest.createRestWebSocketServlet 需要过滤掉已有的@Resource
|
||||
|
||||
protected WebSocketServlet() {
|
||||
Type msgtype = String.class;
|
||||
try {
|
||||
for (Method method : this.getClass().getDeclaredMethods()) {
|
||||
if (!method.getName().equals("createWebSocket")) continue;
|
||||
if (method.getParameterCount() > 0) continue;
|
||||
Type rt = method.getGenericReturnType();
|
||||
if (rt instanceof ParameterizedType) {
|
||||
msgtype = ((ParameterizedType) rt).getActualTypeArguments()[1];
|
||||
}
|
||||
if (msgtype == Object.class) msgtype = String.class;
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warning(this.getClass().getName() + " not designate text message type on createWebSocket Method");
|
||||
}
|
||||
this.messageTextType = msgtype;
|
||||
}
|
||||
|
||||
@Override
|
||||
final void preInit(HttpContext context, AnyValue conf) {
|
||||
InetSocketAddress addr = context.getServerAddress();
|
||||
if (this.node == null) this.node = createWebSocketNode();
|
||||
if (this.node == null) { //没有部署SNCP,即不是分布式
|
||||
this.node = new WebSocketNodeService();
|
||||
if (logger.isLoggable(Level.WARNING)) logger.warning("Not found WebSocketNode, create a default value for " + getClass().getName());
|
||||
}
|
||||
//存在WebSocketServlet,则此WebSocketNode必须是本地模式Service
|
||||
this.node.localEngine = new WebSocketEngine("WebSocketEngine-" + addr.getHostString() + ":" + addr.getPort() + "-[" + resourceName() + "]", this.single, context, liveinterval, this.node, logger);
|
||||
this.node.init(conf);
|
||||
this.node.localEngine.init(conf);
|
||||
}
|
||||
|
||||
@Override
|
||||
final void postDestroy(HttpContext context, AnyValue conf) {
|
||||
this.node.postDestroy(conf);
|
||||
super.destroy(context, conf);
|
||||
this.node.localEngine.destroy(conf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceName() {
|
||||
return this.getClass().getSimpleName().replace("_Dyn", "").toLowerCase().replaceAll("websocket.*$", "").replaceAll("servlet.*$", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void execute(final HttpRequest request, final HttpResponse response) throws IOException {
|
||||
final boolean debug = logger.isLoggable(Level.FINEST);
|
||||
if (!request.isWebSocket()) {
|
||||
if (debug) logger.finest("WebSocket connect abort, (Not GET Method) or (Connection != Upgrade) or (Upgrade != websocket). request=" + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
final String key = request.getHeader("Sec-WebSocket-Key");
|
||||
if (key == null) {
|
||||
if (debug) logger.finest("WebSocket connect abort, Not found Sec-WebSocket-Key header. request=" + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
final WebSocket webSocket = this.createWebSocket();
|
||||
webSocket._engine = this.node.localEngine;
|
||||
webSocket._messageTextType = this.messageTextType;
|
||||
webSocket._jsonConvert = jsonConvert;
|
||||
webSocket._remoteAddress = request.getRemoteAddress();
|
||||
webSocket._remoteAddr = request.getRemoteAddr();
|
||||
initRestWebSocket(webSocket);
|
||||
CompletableFuture<String> sessionFuture = webSocket.onOpen(request);
|
||||
if (sessionFuture == null) {
|
||||
if (debug) logger.finest("WebSocket connect abort, Not found sessionid. request=" + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
sessionFuture.whenComplete((sessionid, ex) -> {
|
||||
if (sessionid == null || ex != null) {
|
||||
if (debug || ex != null) logger.log(ex == null ? Level.FINEST : Level.FINE, "WebSocket connect abort, Not found sessionid or occur error. request=" + request, ex);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
webSocket._sessionid = sessionid;
|
||||
request.setKeepAlive(true);
|
||||
byte[] bytes = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes();
|
||||
synchronized (digest) {
|
||||
bytes = digest.digest(bytes);
|
||||
}
|
||||
response.setStatus(101);
|
||||
response.setHeader("Connection", "Upgrade");
|
||||
response.addHeader("Upgrade", "websocket");
|
||||
response.addHeader("Sec-WebSocket-Accept", Base64.getEncoder().encodeToString(bytes));
|
||||
response.sendBody((ByteBuffer) null, null, new AsyncHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
HttpContext context = response.getContext();
|
||||
CompletableFuture<Serializable> userFuture = webSocket.createUserid();
|
||||
if (userFuture == null) {
|
||||
if (debug) logger.finest("WebSocket connect abort, Create userid abort. request = " + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
userFuture.whenComplete((userid, ex2) -> {
|
||||
if (userid == null || ex2 != null) {
|
||||
if (debug || ex2 != null) logger.log(ex2 == null ? Level.FINEST : Level.FINE, "WebSocket connect abort, Create userid abort. request = " + request, ex2);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
webSocket._userid = userid;
|
||||
WebSocketServlet.this.node.localEngine.add(webSocket);
|
||||
WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer, response.removeChannel(), wsbinary);
|
||||
webSocket._runner = runner;
|
||||
context.runAsync(runner);
|
||||
response.finish(true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
logger.log(Level.FINEST, "WebSocket connect abort, Response send abort. request = " + request, exc);
|
||||
response.finish(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract <G extends Serializable, T> WebSocket<G, T> createWebSocket();
|
||||
|
||||
protected WebSocketNode createWebSocketNode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void initRestWebSocket(WebSocket websocket) { //设置WebSocket中的@Resource资源
|
||||
}
|
||||
|
||||
protected BiConsumer<WebSocket, Object> createRestOnMessageConsumer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MessageDigest getMessageDigest() {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-1");
|
||||
@@ -60,107 +213,4 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("是否用于二进制流传输")
|
||||
protected final boolean wsbinary = getClass().getAnnotation(WebSocketBinary.class) != null;
|
||||
|
||||
@Resource
|
||||
protected JsonConvert jsonConvert;
|
||||
|
||||
@Resource(name = "$")
|
||||
protected WebSocketNode node;
|
||||
|
||||
protected WebSocketEngine engine;
|
||||
|
||||
public final void preInit(HttpContext context, AnyValue conf) {
|
||||
InetSocketAddress addr = context.getServerAddress();
|
||||
this.engine = new WebSocketEngine(addr.getHostString() + ":" + addr.getPort() + "-[" + resourceName() + "]", this.node, logger);
|
||||
if (this.node == null) this.node = createWebSocketNode();
|
||||
if (this.node == null) {
|
||||
this.node = new WebSocketNodeService();
|
||||
if (logger.isLoggable(Level.WARNING)) logger.warning("Not found WebSocketNode, create a default value for " + getClass().getName());
|
||||
}
|
||||
this.node.putWebSocketEngine(engine);
|
||||
this.node.init(conf);
|
||||
this.engine.init(conf);
|
||||
}
|
||||
|
||||
public final void postDestroy(HttpContext context, AnyValue conf) {
|
||||
this.node.destroy(conf);
|
||||
super.destroy(context, conf);
|
||||
engine.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceName() {
|
||||
return this.getClass().getSimpleName().replace("Servlet", "").replace("WebSocket", "").toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void execute(final HttpRequest request, final HttpResponse response) throws IOException {
|
||||
final boolean debug = logger.isLoggable(Level.FINEST);
|
||||
if (!"GET".equalsIgnoreCase(request.getMethod())
|
||||
|| !request.getConnection().contains("Upgrade")
|
||||
|| !"websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) {
|
||||
if (debug) logger.finest("WebSocket connect abort, (Not GET Method) or (Connection != Upgrade) or (Upgrade != websocket). request=" + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
String key = request.getHeader("Sec-WebSocket-Key");
|
||||
if (key == null) {
|
||||
if (debug) logger.finest("WebSocket connect abort, Not found Sec-WebSocket-Key header. request=" + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
final WebSocket webSocket = this.createWebSocket();
|
||||
webSocket._engine = engine;
|
||||
webSocket._jsonConvert = jsonConvert;
|
||||
webSocket._remoteAddress = request.getRemoteAddress();
|
||||
webSocket._remoteAddr = request.getRemoteAddr();
|
||||
Serializable sessionid = webSocket.onOpen(request);
|
||||
if (sessionid == null) {
|
||||
if (debug) logger.finest("WebSocket connect abort, Not found sessionid. request=" + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
webSocket._sessionid = sessionid;
|
||||
request.setKeepAlive(true);
|
||||
byte[] bytes = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes();
|
||||
synchronized (digest) {
|
||||
bytes = digest.digest(bytes);
|
||||
}
|
||||
key = Base64.getEncoder().encodeToString(bytes);
|
||||
response.setStatus(101);
|
||||
response.setHeader("Connection", "Upgrade");
|
||||
response.addHeader("Upgrade", "websocket");
|
||||
response.addHeader("Sec-WebSocket-Accept", key);
|
||||
response.sendBody((ByteBuffer) null, null, new AsyncHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
HttpContext context = response.getContext();
|
||||
Serializable groupid = webSocket.createGroupid();
|
||||
if (groupid == null) {
|
||||
if (debug) logger.finest("WebSocket connect abort, Create groupid abort. request = " + request);
|
||||
response.finish(true);
|
||||
return;
|
||||
}
|
||||
webSocket._groupid = groupid;
|
||||
engine.add(webSocket);
|
||||
context.runAsync(new WebSocketRunner(context, webSocket, response.removeChannel(), wsbinary));
|
||||
response.finish(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
logger.log(Level.FINEST, "WebSocket connect abort, Response send abort. request = " + request, exc);
|
||||
response.finish(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected WebSocketNode createWebSocketNode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract WebSocket createWebSocket();
|
||||
}
|
||||
|
||||
@@ -10,16 +10,12 @@ import java.lang.reflect.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Resource;
|
||||
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import org.redkale.convert.bson.BsonConvert;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.net.Transport;
|
||||
import org.redkale.net.TransportFactory;
|
||||
import org.redkale.net.sncp.SncpClient.SncpAction;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.*;
|
||||
@@ -93,6 +89,10 @@ public abstract class Sncp {
|
||||
return dyn != null && dyn.remote();
|
||||
}
|
||||
|
||||
public static boolean isSncpDyn(Service service) {
|
||||
return service.getClass().getAnnotation(SncpDyn.class) != null;
|
||||
}
|
||||
|
||||
public static String getResourceName(Service service) {
|
||||
if (service == null) return null;
|
||||
Resource res = service.getClass().getAnnotation(Resource.class);
|
||||
@@ -100,42 +100,14 @@ public abstract class Sncp {
|
||||
}
|
||||
|
||||
public static Class getServiceType(Service service) {
|
||||
if (service == null) return null;
|
||||
try {
|
||||
Field ts = service.getClass().getDeclaredField(FIELDPREFIX + "_service_type");
|
||||
ts.setAccessible(true);
|
||||
return (Class) ts.get(service);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(service + " not found " + FIELDPREFIX + "_service_type");
|
||||
}
|
||||
ResourceType rt = service.getClass().getAnnotation(ResourceType.class);
|
||||
return rt == null ? service.getClass() : rt.value();
|
||||
}
|
||||
|
||||
public static Class[] getResourceTypes(Service service) {
|
||||
public static Class getResourceType(Service service) {
|
||||
if (service == null) return null;
|
||||
ResourceType types = service.getClass().getAnnotation(ResourceType.class);
|
||||
return types == null ? new Class[]{getServiceType(service)} : types.value();
|
||||
}
|
||||
|
||||
public static String getSncpGroup(Service service) {
|
||||
if (service == null) return null;
|
||||
try {
|
||||
Field ts = service.getClass().getDeclaredField(FIELDPREFIX + "_sncpGroup");
|
||||
ts.setAccessible(true);
|
||||
return (String) ts.get(service);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(service + " not found " + FIELDPREFIX + "_sncpGroup");
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> getGroups(Service service) {
|
||||
if (service == null) return null;
|
||||
try {
|
||||
Field ts = service.getClass().getDeclaredField(FIELDPREFIX + "_groups");
|
||||
ts.setAccessible(true);
|
||||
return (Set) ts.get(service);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(service + " not found " + FIELDPREFIX + "_groups");
|
||||
}
|
||||
ResourceType type = service.getClass().getAnnotation(ResourceType.class);
|
||||
return type == null ? getServiceType(service) : type.value();
|
||||
}
|
||||
|
||||
public static AnyValue getConf(Service service) {
|
||||
@@ -160,28 +132,6 @@ public abstract class Sncp {
|
||||
}
|
||||
}
|
||||
|
||||
public static Transport getSameGroupTransport(Service service) {
|
||||
if (service == null) return null;
|
||||
try {
|
||||
Field ts = service.getClass().getDeclaredField(FIELDPREFIX + "_sameGroupTransport");
|
||||
ts.setAccessible(true);
|
||||
return (Transport) ts.get(service);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(service + " not found " + FIELDPREFIX + "_sameGroupTransport");
|
||||
}
|
||||
}
|
||||
|
||||
public static Transport[] getDiffGroupTransports(Service service) {
|
||||
if (service == null) return null;
|
||||
try {
|
||||
Field ts = service.getClass().getDeclaredField(FIELDPREFIX + "_diffGroupTransports");
|
||||
ts.setAccessible(true);
|
||||
return (Transport[]) ts.get(service);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(service + " not found " + FIELDPREFIX + "_diffGroupTransports");
|
||||
}
|
||||
}
|
||||
|
||||
static void checkAsyncModifier(Class param, Method method) {
|
||||
if (param == AsyncHandler.class) return;
|
||||
if (Modifier.isFinal(param.getModifiers())) {
|
||||
@@ -224,20 +174,10 @@ public abstract class Sncp {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(isRemote(service) ? "RemoteService" : "LocalService ");
|
||||
int len;
|
||||
Class[] types = getResourceTypes(service);
|
||||
Class type = getResourceType(service);
|
||||
String name = getResourceName(service);
|
||||
if (types.length == 1) {
|
||||
sb.append("(type= ").append(types[0].getName());
|
||||
len = maxClassNameLength - types[0].getName().length();
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append('[');
|
||||
s.append(Arrays.asList(types).stream().map((Class t) -> t.getName()).collect(Collectors.joining(",")));
|
||||
s.append(']');
|
||||
sb.append("(types=").append(s);
|
||||
len = maxClassNameLength - s.length();
|
||||
}
|
||||
|
||||
sb.append("(type= ").append(type.getName());
|
||||
len = maxClassNameLength - type.getName().length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
@@ -272,31 +212,13 @@ public abstract class Sncp {
|
||||
* <blockquote><pre>
|
||||
* @Resource(name = "")
|
||||
* @SncpDyn(remote = false)
|
||||
* @ResourceType({TestService.class})
|
||||
* @ResourceType(TestService.class)
|
||||
* public final class _DynLocalTestService extends TestService{
|
||||
*
|
||||
* private static final Class _redkale_service_type = TestService.class;
|
||||
*
|
||||
* @Resource
|
||||
* private BsonConvert _redkale_bsonConvert;
|
||||
*
|
||||
* @Resource
|
||||
* private JsonConvert _redkale_jsonConvert;
|
||||
*
|
||||
* private AnyValue _redkale_conf;
|
||||
*
|
||||
* private String _redkale_sncpGroup; //自身的组节点名 可能为null
|
||||
*
|
||||
* private Set<String> groups; //所有的组节点,包含自身
|
||||
*
|
||||
* private Transport _redkale_sameGroupTransport;
|
||||
*
|
||||
* private Transport[] _redkale_diffGroupTransports;
|
||||
*
|
||||
* private SncpClient _redkale_client;
|
||||
*
|
||||
* private String _redkale_selfstring;
|
||||
*
|
||||
* @Override
|
||||
* public String toString() {
|
||||
* return _redkale_selfstring == null ? super.toString() : _redkale_selfstring;
|
||||
@@ -311,8 +233,8 @@ public abstract class Sncp {
|
||||
* public void _redkale_createSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, TestBean bean){
|
||||
* if(selfrunnable) super.createSomeThing(bean);
|
||||
* if (_redkale_client== null) return;
|
||||
* if (samerunnable) _redkale_client.remoteSameGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_sameGroupTransport, 0, true, false, false, bean);
|
||||
* if (diffrunnable) _redkale_client.remoteDiffGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_diffGroupTransports, 0, true, true, false, bean);
|
||||
* if (samerunnable) _redkale_client.remoteSameGroup(0, true, false, false, bean);
|
||||
* if (diffrunnable) _redkale_client.remoteDiffGroup(0, true, true, false, bean);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
@@ -324,8 +246,8 @@ public abstract class Sncp {
|
||||
* public String _redkale_updateSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, String id){
|
||||
* String rs = super.updateSomeThing(id);
|
||||
* if (_redkale_client== null) return rs;
|
||||
* if (samerunnable) _redkale_client.remoteSameGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_sameGroupTransport, 1, true, false, false, id);
|
||||
* if (diffrunnable) _redkale_client.remoteDiffGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_diffGroupTransports, 1, true, true, false, id);
|
||||
* if (samerunnable) _redkale_client.remoteSameGroup(1, true, false, false, id);
|
||||
* if (diffrunnable) _redkale_client.remoteDiffGroup(1, true, true, false, id);
|
||||
* return rs;
|
||||
* }
|
||||
* }
|
||||
@@ -350,13 +272,8 @@ public abstract class Sncp {
|
||||
final String supDynName = serviceImplClass.getName().replace('.', '/');
|
||||
final String clientName = SncpClient.class.getName().replace('.', '/');
|
||||
final String clientDesc = Type.getDescriptor(SncpClient.class);
|
||||
final String bsonConvertDesc = Type.getDescriptor(BsonConvert.class);
|
||||
final String jsonConvertDesc = Type.getDescriptor(JsonConvert.class);
|
||||
final String stringDesc = Type.getDescriptor(String.class);
|
||||
final String anyValueDesc = Type.getDescriptor(AnyValue.class);
|
||||
final String sncpDynDesc = Type.getDescriptor(SncpDyn.class);
|
||||
final String transportDesc = Type.getDescriptor(Transport.class);
|
||||
final String transportsDesc = Type.getDescriptor(Transport[].class);
|
||||
ClassLoader loader = Sncp.class.getClassLoader();
|
||||
String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + LOCALPREFIX + serviceImplClass.getSimpleName();
|
||||
if (!name.isEmpty()) {
|
||||
@@ -396,73 +313,18 @@ public abstract class Sncp {
|
||||
}
|
||||
{
|
||||
av0 = cw.visitAnnotation(Type.getDescriptor(ResourceType.class), true);
|
||||
{
|
||||
AnnotationVisitor av1 = av0.visitArray("value");
|
||||
ResourceType rty = serviceImplClass.getAnnotation(ResourceType.class);
|
||||
if (rty == null) {
|
||||
av1.visit(null, Type.getType(Type.getDescriptor(serviceImplClass)));
|
||||
} else {
|
||||
for (Class cl : rty.value()) {
|
||||
av1.visit(null, Type.getType(Type.getDescriptor(cl)));
|
||||
}
|
||||
}
|
||||
av1.visitEnd();
|
||||
}
|
||||
ResourceType rty = serviceImplClass.getAnnotation(ResourceType.class);
|
||||
av0.visit("value", Type.getType(Type.getDescriptor(rty == null ? serviceImplClass : rty.value())));
|
||||
av0.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, FIELDPREFIX + "_service_type", "Ljava/lang/Class;", null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_bsonConvert", bsonConvertDesc, null, null);
|
||||
av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true);
|
||||
av0.visitEnd();
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_jsonConvert", jsonConvertDesc, null, null);
|
||||
av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true);
|
||||
av0.visitEnd();
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_conf", anyValueDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_sncpGroup", stringDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_groups", "Ljava/util/Set;", "Ljava/util/Set<Ljava/lang/String;>;", null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_sameGroupTransport", transportDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_diffGroupTransports", transportsDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_client", clientDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_selfstring", "Ljava/lang/String;", null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{//静态构造函数
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null));
|
||||
mv.visitLdcInsn(Type.getType(Type.getDescriptor(serviceImplClass)));
|
||||
mv.visitFieldInsn(PUTSTATIC, newDynName, FIELDPREFIX + "_service_type", "Ljava/lang/Class;");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
{ //构造函数
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null));
|
||||
//mv.setDebug(true);
|
||||
@@ -475,16 +337,18 @@ public abstract class Sncp {
|
||||
{ // toString()
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_selfstring", "Ljava/lang/String;");
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
Label l1 = new Label();
|
||||
mv.visitJumpInsn(IFNONNULL, l1);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
|
||||
Label l2 = new Label();
|
||||
mv.visitJumpInsn(GOTO, l2);
|
||||
mv.visitLabel(l1);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_selfstring", "Ljava/lang/String;");
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, "toSimpleString", "()Ljava/lang/String;", false);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
@@ -660,14 +524,7 @@ public abstract class Sncp {
|
||||
|
||||
mv.visitVarInsn(ALOAD, 0);//调用 _client
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
mv.visitVarInsn(ALOAD, 0); //传递 _bsonConvert
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_bsonConvert", bsonConvertDesc);
|
||||
mv.visitVarInsn(ALOAD, 0); //传递 _jsonConvert
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_jsonConvert", jsonConvertDesc);
|
||||
mv.visitVarInsn(ALOAD, 0); //传递 _sameGroupTransport
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_sameGroupTransport", transportDesc);
|
||||
|
||||
final int preparams = 3; //调用selfrunnable之前的参数个数; _client/_bsonConvert/_jsonConvert/_sameGroupTransport
|
||||
final int preparams = 3; //调用selfrunnable之前的参数个数; _client
|
||||
|
||||
if (index <= 5) { //第几个 SncpAction
|
||||
mv.visitInsn(ICONST_0 + index);
|
||||
@@ -727,7 +584,7 @@ public abstract class Sncp {
|
||||
}
|
||||
mv.visitInsn(AASTORE);
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, mrun.async() ? "asyncRemoteSameGroup" : "remoteSameGroup", "(" + bsonConvertDesc + jsonConvertDesc + transportDesc + "I[Ljava/lang/Object;)V", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, mrun.async() ? "asyncRemoteSameGroup" : "remoteSameGroup", "(I[Ljava/lang/Object;)V", false);
|
||||
mv.visitLabel(sameLabel);
|
||||
//---------------------------- 调用diffrun ---------------------------------
|
||||
mv.visitVarInsn(ILOAD, 3); //读取 diffrunnable
|
||||
@@ -736,12 +593,6 @@ public abstract class Sncp {
|
||||
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_bsonConvert", bsonConvertDesc);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_jsonConvert", jsonConvertDesc);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_diffGroupTransports", transportsDesc);
|
||||
|
||||
if (index <= 5) { //第几个 SncpAction
|
||||
mv.visitInsn(ICONST_0 + index);
|
||||
@@ -801,7 +652,7 @@ public abstract class Sncp {
|
||||
}
|
||||
mv.visitInsn(AASTORE);
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, mrun.async() ? "asyncRemoteDiffGroup" : "remoteDiffGroup", "(" + bsonConvertDesc + jsonConvertDesc + transportsDesc + "I[Ljava/lang/Object;)V", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, mrun.async() ? "asyncRemoteDiffGroup" : "remoteDiffGroup", "(I[Ljava/lang/Object;)V", false);
|
||||
mv.visitLabel(diffLabel);
|
||||
|
||||
if (returnType == void.class) {
|
||||
@@ -885,46 +736,40 @@ public abstract class Sncp {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Service> T createSimpleLocalService(final String name, final Class<T> serviceImplClass, final InetSocketAddress clientAddress, final Transport sameGroupTransport) {
|
||||
return createLocalService(name, null, ResourceFactory.root(), serviceImplClass, clientAddress, null, new HashSet<>(), null, sameGroupTransport, null);
|
||||
public static <T extends Service> T createSimpleLocalService(final Class<T> serviceImplClass,
|
||||
final TransportFactory transportFactory, final InetSocketAddress clientSncpAddress, final String... groups) {
|
||||
return createLocalService("", serviceImplClass, ResourceFactory.root(), transportFactory, clientSncpAddress, Utility.ofSet(groups), null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 创建本地模式Service实例
|
||||
*
|
||||
* @param <T> Service泛型
|
||||
* @param name 资源名
|
||||
* @param executor 线程池
|
||||
* @param resourceFactory 资源容器
|
||||
* @param serviceImplClass Service类
|
||||
* @param clientAddress 本地IP地址
|
||||
* @param sncpGroup 自身的组节点名 可能为null
|
||||
* @param groups 所有的组节点,包含自身
|
||||
* @param conf 启动配置项
|
||||
* @param sameGroupTransport 同组的通信组件
|
||||
* @param diffGroupTransports 异组的通信组件列表
|
||||
* @param <T> Service泛型
|
||||
* @param name 资源名
|
||||
* @param serviceImplClass Service类
|
||||
* @param resourceFactory ResourceFactory
|
||||
* @param transportFactory TransportFactory
|
||||
* @param clientSncpAddress 本地IP地址
|
||||
* @param groups 所有的组节点,包含自身
|
||||
* @param conf 启动配置项
|
||||
*
|
||||
* @return Service的本地模式实例
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Service> T createLocalService(
|
||||
final String name,
|
||||
final Consumer<Runnable> executor,
|
||||
final ResourceFactory resourceFactory,
|
||||
final Class<T> serviceImplClass,
|
||||
final InetSocketAddress clientAddress,
|
||||
final String sncpGroup,
|
||||
final ResourceFactory resourceFactory,
|
||||
final TransportFactory transportFactory,
|
||||
final InetSocketAddress clientSncpAddress,
|
||||
final Set<String> groups,
|
||||
final AnyValue conf,
|
||||
final Transport sameGroupTransport,
|
||||
final Collection<Transport> diffGroupTransports) {
|
||||
final AnyValue conf) {
|
||||
try {
|
||||
final Class newClazz = createLocalServiceClass(name, serviceImplClass);
|
||||
T rs = (T) newClazz.newInstance();
|
||||
//--------------------------------------
|
||||
Service remoteService = null;
|
||||
Transport remoteTransport = null;
|
||||
{
|
||||
Class loop = newClazz;
|
||||
do {
|
||||
@@ -934,25 +779,8 @@ public abstract class Sncp {
|
||||
if (field.getAnnotation(RpcRemote.class) == null) continue;
|
||||
if (!field.getType().isAssignableFrom(newClazz)) continue;
|
||||
field.setAccessible(true);
|
||||
if (remoteTransport == null) {
|
||||
List<Transport> list = new ArrayList<>();
|
||||
if (sameGroupTransport != null) list.add(sameGroupTransport);
|
||||
if (diffGroupTransports != null) list.addAll(diffGroupTransports);
|
||||
if (!list.isEmpty()) {
|
||||
Transport tmp = new Transport(list);
|
||||
synchronized (resourceFactory) {
|
||||
Transport old = resourceFactory.find(tmp.getName(), Transport.class);
|
||||
if (old != null) {
|
||||
remoteTransport = old;
|
||||
} else {
|
||||
remoteTransport = tmp;
|
||||
resourceFactory.register(tmp.getName(), tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (remoteService == null && remoteTransport != null) {
|
||||
remoteService = createRemoteService(name, executor, serviceImplClass, clientAddress, sncpGroup, groups, conf, remoteTransport);
|
||||
if (remoteService == null && clientSncpAddress != null) {
|
||||
remoteService = createRemoteService(name, serviceImplClass, transportFactory, clientSncpAddress, groups, conf);
|
||||
}
|
||||
if (remoteService != null) field.set(rs, remoteService);
|
||||
}
|
||||
@@ -963,137 +791,73 @@ public abstract class Sncp {
|
||||
try {
|
||||
Field e = newClazz.getDeclaredField(FIELDPREFIX + "_client");
|
||||
e.setAccessible(true);
|
||||
client = new SncpClient(name, serviceImplClass, rs, executor, false, newClazz, clientAddress);
|
||||
client = new SncpClient(name, serviceImplClass, rs, transportFactory, false, newClazz, clientSncpAddress);
|
||||
Set<String> diffGroups = groups == null ? new HashSet<>() : new HashSet<>(groups);
|
||||
String sameGroup = transportFactory.findGroupName(clientSncpAddress);
|
||||
if (sameGroup != null) diffGroups.remove(sameGroup);
|
||||
client.setSameGroup(sameGroup);
|
||||
client.setDiffGroups(diffGroups);
|
||||
client.setSameGroupTransport(transportFactory.loadSameGroupTransport(clientSncpAddress));
|
||||
client.setDiffGroupTransports(transportFactory.loadDiffGroupTransports(clientSncpAddress, diffGroups));
|
||||
e.set(rs, client);
|
||||
transportFactory.addSncpService(rs);
|
||||
} catch (NoSuchFieldException ne) {
|
||||
ne.printStackTrace();
|
||||
}
|
||||
}
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(newClazz.getName()).append("{name = '").append(name).append("'");
|
||||
if (client != null) {
|
||||
sb.append(", serviceid = ").append(client.getServiceid());
|
||||
sb.append(", serviceversion = ").append(client.getServiceversion());
|
||||
sb.append(", action.size = ").append(client.getActionCount());
|
||||
// List<String> groups = new ArrayList<>();
|
||||
// if (sameGroupTransport != null) groups.add(sameGroupTransport.getName());
|
||||
// if (diffGroupTransports != null) {
|
||||
// for (Transport t : diffGroupTransports) {
|
||||
// groups.add(t.getName());
|
||||
// }
|
||||
// }
|
||||
sb.append(", address = ").append(clientAddress).append(", groups = ").append(groups);
|
||||
sb.append(", sameaddrs = ").append(sameGroupTransport == null ? null : Arrays.asList(sameGroupTransport.getRemoteAddresses()));
|
||||
|
||||
List<InetSocketAddress> addrs = new ArrayList<>();
|
||||
if (diffGroupTransports != null) {
|
||||
for (Transport t : diffGroupTransports) {
|
||||
addrs.addAll(Arrays.asList(t.getRemoteAddresses()));
|
||||
}
|
||||
}
|
||||
sb.append(", diffaddrs = ").append(addrs);
|
||||
} else {
|
||||
sb.append(", ").append(RpcMultiRun.class.getSimpleName().toLowerCase()).append(" = false");
|
||||
}
|
||||
sb.append("}");
|
||||
Field s = newClazz.getDeclaredField(FIELDPREFIX + "_selfstring");
|
||||
s.setAccessible(true);
|
||||
s.set(rs, sb.toString());
|
||||
}
|
||||
if (client == null) return rs;
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_sncpGroup");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, sncpGroup);
|
||||
}
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_groups");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, groups);
|
||||
}
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_conf");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, conf);
|
||||
}
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_sameGroupTransport");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, sameGroupTransport);
|
||||
}
|
||||
if (diffGroupTransports != null) {
|
||||
Field t = newClazz.getDeclaredField(FIELDPREFIX + "_diffGroupTransports");
|
||||
t.setAccessible(true);
|
||||
t.set(rs, diffGroupTransports.toArray(new Transport[diffGroupTransports.size()]));
|
||||
}
|
||||
return rs;
|
||||
} catch (RuntimeException rex) {
|
||||
throw rex;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static <T extends Service> T createSimpleRemoteService(final String name, final Class<T> serviceTypeOrImplClass, final InetSocketAddress clientAddress, final Transport transport) {
|
||||
return createRemoteService(name, null, serviceTypeOrImplClass, clientAddress, (String) null, new HashSet<>(), (AnyValue) null, transport);
|
||||
public static <T extends Service> T createSimpleRemoteService(final Class<T> serviceImplClass,
|
||||
final TransportFactory transportFactory, final InetSocketAddress clientSncpAddress, final String... groups) {
|
||||
return createRemoteService("", serviceImplClass, transportFactory, clientSncpAddress, Utility.ofSet(groups), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <blockquote><pre>
|
||||
* @Resource(name = "")
|
||||
* @SncpDyn(remote = true)
|
||||
* @ResourceType({TestService.class})
|
||||
* @ResourceType(TestService.class)
|
||||
* public final class _DynRemoteTestService extends TestService{
|
||||
*
|
||||
* private static final Class _redkale_service_type = TestService.class;
|
||||
*
|
||||
* @Resource
|
||||
* private BsonConvert _redkale_bsonConvert;
|
||||
*
|
||||
* @Resource
|
||||
* private JsonConvert _redkale_jsonConvert;
|
||||
*
|
||||
* private String _redkale_sncpGroup; //自身的组节点名 可能为null
|
||||
*
|
||||
* private Set<String> groups; //所有的组节点,包含自身
|
||||
*
|
||||
* private AnyValue _redkale_conf;
|
||||
*
|
||||
* private Transport _redkale_transport;
|
||||
*
|
||||
* private SncpClient _redkale_client;
|
||||
*
|
||||
* private String _redkale_selfstring;
|
||||
*
|
||||
* @Override
|
||||
* public String toString() {
|
||||
* return _redkale_selfstring == null ? super.toString() : _redkale_selfstring;
|
||||
* }
|
||||
*
|
||||
* @SncpDyn(remote = false, index = 0)
|
||||
* public void _redkale_createSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, TestBean bean){
|
||||
* _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 0, selfrunnable, samerunnable, diffrunnable, bean);
|
||||
* _redkale_client.remote(0, selfrunnable, samerunnable, diffrunnable, bean);
|
||||
* }
|
||||
*
|
||||
* @SncpDyn(remote = false, index = 1)
|
||||
* public String _redkale_updateSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, String id){
|
||||
* return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 1, selfrunnable, samerunnable, diffrunnable, id);
|
||||
* return _redkale_client.remote(1, selfrunnable, samerunnable, diffrunnable, id);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void createSomeThing(TestBean bean){
|
||||
* _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 2, bean);
|
||||
* _redkale_client.remote(2, bean);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public String findSomeThing(){
|
||||
* return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 3);
|
||||
* return _redkale_client.remote(3);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public String updateSomeThing(String id){
|
||||
* return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 4, id);
|
||||
* return _redkale_client.remote(4, id);
|
||||
* }
|
||||
* }
|
||||
* </pre></blockquote>
|
||||
@@ -1102,26 +866,23 @@ public abstract class Sncp {
|
||||
*
|
||||
* @param <T> Service泛型
|
||||
* @param name 资源名
|
||||
* @param executor 线程池
|
||||
* @param serviceTypeOrImplClass Service类
|
||||
* @param transportFactory TransportFactory
|
||||
* @param clientAddress 本地IP地址
|
||||
* @param sncpGroup 自身的组节点名 可能为null
|
||||
* @param groups 所有的组节点,包含自身
|
||||
* @param conf 启动配置项
|
||||
* @param transport 通信组件
|
||||
*
|
||||
* @return Service的远程模式实例
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
public static <T extends Service> T createRemoteService(
|
||||
final String name,
|
||||
final Consumer<Runnable> executor,
|
||||
final Class<T> serviceTypeOrImplClass,
|
||||
final TransportFactory transportFactory,
|
||||
final InetSocketAddress clientAddress,
|
||||
final String sncpGroup,
|
||||
final Set<String> groups,
|
||||
final AnyValue conf,
|
||||
final Transport transport) {
|
||||
final AnyValue conf) {
|
||||
if (serviceTypeOrImplClass == null) return null;
|
||||
if (!Service.class.isAssignableFrom(serviceTypeOrImplClass)) return null;
|
||||
int mod = serviceTypeOrImplClass.getModifiers();
|
||||
@@ -1131,36 +892,19 @@ public abstract class Sncp {
|
||||
final String clientName = SncpClient.class.getName().replace('.', '/');
|
||||
final String clientDesc = Type.getDescriptor(SncpClient.class);
|
||||
final String sncpDynDesc = Type.getDescriptor(SncpDyn.class);
|
||||
final String bsonConvertDesc = Type.getDescriptor(BsonConvert.class);
|
||||
final String jsonConvertDesc = Type.getDescriptor(JsonConvert.class);
|
||||
final String transportDesc = Type.getDescriptor(Transport.class);
|
||||
final String stringDesc = Type.getDescriptor(String.class);
|
||||
final String anyValueDesc = Type.getDescriptor(AnyValue.class);
|
||||
ClassLoader loader = Sncp.class.getClassLoader();
|
||||
String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + REMOTEPREFIX + serviceTypeOrImplClass.getSimpleName();
|
||||
try {
|
||||
Class newClazz = Class.forName(newDynName.replace('/', '.'));
|
||||
T rs = (T) newClazz.newInstance();
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, executor, true, realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, transportFactory, true, realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
client.setRemoteGroups(groups);
|
||||
client.setRemoteGroupTransport(transportFactory.loadRemoteTransport(clientAddress, groups));
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_client");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, client);
|
||||
Field t = newClazz.getDeclaredField(FIELDPREFIX + "_transport");
|
||||
t.setAccessible(true);
|
||||
t.set(rs, transport);
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(newClazz.getName()).append("{name = '").append(name);
|
||||
sb.append("', serviceid = '").append(client.getServiceid());
|
||||
sb.append("', serviceversion = ").append(client.getServiceversion());
|
||||
sb.append(", action.size = ").append(client.getActionCount());
|
||||
sb.append(", address = ").append(clientAddress).append(", groups = ").append(transport == null ? null : transport.getName());
|
||||
sb.append(", remoteaddrs = ").append(transport == null ? null : Arrays.asList(transport.getRemoteAddresses()));
|
||||
sb.append("}");
|
||||
Field s = newClazz.getDeclaredField(FIELDPREFIX + "_selfstring");
|
||||
s.setAccessible(true);
|
||||
s.set(rs, sb.toString());
|
||||
}
|
||||
transportFactory.addSncpService(rs);
|
||||
return rs;
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
@@ -1178,18 +922,8 @@ public abstract class Sncp {
|
||||
}
|
||||
{
|
||||
av0 = cw.visitAnnotation(Type.getDescriptor(ResourceType.class), true);
|
||||
{
|
||||
AnnotationVisitor av1 = av0.visitArray("value");
|
||||
ResourceType rty = serviceTypeOrImplClass.getAnnotation(ResourceType.class);
|
||||
if (rty == null) {
|
||||
av1.visit(null, Type.getType(Type.getDescriptor(serviceTypeOrImplClass)));
|
||||
} else {
|
||||
for (Class cl : rty.value()) {
|
||||
av1.visit(null, Type.getType(Type.getDescriptor(cl)));
|
||||
}
|
||||
}
|
||||
av1.visitEnd();
|
||||
}
|
||||
ResourceType rty = serviceTypeOrImplClass.getAnnotation(ResourceType.class);
|
||||
av0.visit("value", Type.getType(Type.getDescriptor(rty == null ? serviceTypeOrImplClass : rty.value())));
|
||||
av0.visitEnd();
|
||||
}
|
||||
{
|
||||
@@ -1203,54 +937,14 @@ public abstract class Sncp {
|
||||
visitAnnotation(cw.visitAnnotation(Type.getDescriptor(ann.annotationType()), true), ann);
|
||||
}
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, FIELDPREFIX + "_service_type", "Ljava/lang/Class;", null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_bsonConvert", bsonConvertDesc, null, null);
|
||||
av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true);
|
||||
av0.visitEnd();
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_jsonConvert", jsonConvertDesc, null, null);
|
||||
av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true);
|
||||
av0.visitEnd();
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_sncpGroup", stringDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_groups", "Ljava/util/Set;", "Ljava/util/Set<Ljava/lang/String;>;", null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_conf", anyValueDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_transport", transportDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_client", clientDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{
|
||||
fv = cw.visitField(ACC_PRIVATE, FIELDPREFIX + "_selfstring", "Ljava/lang/String;", null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
{//静态构造函数
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null));
|
||||
mv.visitLdcInsn(Type.getType(Type.getDescriptor(serviceTypeOrImplClass)));
|
||||
mv.visitFieldInsn(PUTSTATIC, newDynName, FIELDPREFIX + "_service_type", "Ljava/lang/Class;");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
{ //构造函数
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null));
|
||||
//mv.setDebug(true);
|
||||
@@ -1275,16 +969,18 @@ public abstract class Sncp {
|
||||
{ // toString()
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_selfstring", "Ljava/lang/String;");
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
Label l1 = new Label();
|
||||
mv.visitJumpInsn(IFNONNULL, l1);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
|
||||
Label l2 = new Label();
|
||||
mv.visitJumpInsn(GOTO, l2);
|
||||
mv.visitLabel(l1);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_selfstring", "Ljava/lang/String;");
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, "toSimpleString", "()Ljava/lang/String;", false);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
@@ -1307,12 +1003,7 @@ public abstract class Sncp {
|
||||
}
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_client", clientDesc);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_bsonConvert", bsonConvertDesc);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_jsonConvert", jsonConvertDesc);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, FIELDPREFIX + "_transport", transportDesc);
|
||||
|
||||
if (index <= 5) {
|
||||
mv.visitInsn(ICONST_0 + index);
|
||||
} else {
|
||||
@@ -1357,7 +1048,7 @@ public abstract class Sncp {
|
||||
}
|
||||
}
|
||||
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, "remote", "(" + bsonConvertDesc + jsonConvertDesc + transportDesc + "I[Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, clientName, "remote", "(I[Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
//mv.visitMethodInsn(INVOKEVIRTUAL, convertName, "convertFrom", convertFromDesc, false);
|
||||
if (method.getGenericReturnType() == void.class) {
|
||||
mv.visitInsn(POP);
|
||||
@@ -1400,7 +1091,9 @@ public abstract class Sncp {
|
||||
}.loadClass(newDynName.replace('/', '.'), bytes);
|
||||
try {
|
||||
T rs = (T) newClazz.newInstance();
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, executor, true, realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, transportFactory, true, realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
client.setRemoteGroups(groups);
|
||||
client.setRemoteGroupTransport(transportFactory.loadRemoteTransport(clientAddress, groups));
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_client");
|
||||
c.setAccessible(true);
|
||||
@@ -1411,34 +1104,7 @@ public abstract class Sncp {
|
||||
c.setAccessible(true);
|
||||
c.set(rs, conf);
|
||||
}
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_sncpGroup");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, sncpGroup);
|
||||
}
|
||||
{
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_groups");
|
||||
c.setAccessible(true);
|
||||
c.set(rs, groups);
|
||||
}
|
||||
{
|
||||
Field t = newClazz.getDeclaredField(FIELDPREFIX + "_transport");
|
||||
t.setAccessible(true);
|
||||
t.set(rs, transport);
|
||||
}
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(newClazz.getName()).append("{name = '").append(name);
|
||||
sb.append("', serviceid = ").append(client.getServiceid());
|
||||
sb.append(", serviceversion = ").append(client.getServiceversion());
|
||||
sb.append(", action.size = ").append(client.getActionCount());
|
||||
sb.append(", address = ").append(clientAddress).append(", groups = ").append(transport == null ? null : transport.getName());
|
||||
sb.append(", remotes = ").append(transport == null ? null : Arrays.asList(transport.getRemoteAddresses()));
|
||||
sb.append("}");
|
||||
Field s = newClazz.getDeclaredField(FIELDPREFIX + "_selfstring");
|
||||
s.setAccessible(true);
|
||||
s.set(rs, sb.toString());
|
||||
}
|
||||
transportFactory.addSncpService(rs);
|
||||
return rs;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.*;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.convert.bson.*;
|
||||
import org.redkale.convert.json.*;
|
||||
import org.redkale.net.*;
|
||||
@@ -31,111 +31,6 @@ import org.redkale.service.RpcCall;
|
||||
*/
|
||||
public final class SncpClient {
|
||||
|
||||
protected static final class SncpAction {
|
||||
|
||||
protected final DLong actionid;
|
||||
|
||||
protected final Method method;
|
||||
|
||||
protected final Type resultTypes; //void 必须设为 null
|
||||
|
||||
protected final Type[] paramTypes;
|
||||
|
||||
protected final Class[] paramClass;
|
||||
|
||||
protected final Attribute[] paramAttrs; // 为null表示无RpcCall处理,index=0固定为null, 其他为参数标记的RpcCall回调方法
|
||||
|
||||
protected final int handlerFuncParamIndex;
|
||||
|
||||
protected final int handlerAttachParamIndex;
|
||||
|
||||
protected final int addressTargetParamIndex;
|
||||
|
||||
protected final int addressSourceParamIndex;
|
||||
|
||||
protected final boolean boolReturnTypeFuture; // 返回结果类型是否为 CompletableFuture
|
||||
|
||||
protected final Creator<? extends CompletableFuture> futureCreator;
|
||||
|
||||
public SncpAction(final Class clazz, Method method, DLong actionid) {
|
||||
this.actionid = actionid == null ? Sncp.hash(method) : actionid;
|
||||
Type rt = method.getGenericReturnType();
|
||||
if (rt instanceof TypeVariable) {
|
||||
TypeVariable tv = (TypeVariable) rt;
|
||||
if (tv.getBounds().length == 1) rt = tv.getBounds()[0];
|
||||
}
|
||||
this.resultTypes = rt == void.class ? null : rt;
|
||||
this.boolReturnTypeFuture = CompletableFuture.class.isAssignableFrom(method.getReturnType());
|
||||
this.futureCreator = boolReturnTypeFuture ? Creator.create((Class<? extends CompletableFuture>) method.getReturnType()) : null;
|
||||
this.paramTypes = method.getGenericParameterTypes();
|
||||
this.paramClass = method.getParameterTypes();
|
||||
this.method = method;
|
||||
Annotation[][] anns = method.getParameterAnnotations();
|
||||
int targetAddrIndex = -1;
|
||||
int sourceAddrIndex = -1;
|
||||
int handlerAttachIndex = -1;
|
||||
int handlerFuncIndex = -1;
|
||||
boolean hasattr = false;
|
||||
Attribute[] atts = new Attribute[paramTypes.length + 1];
|
||||
if (anns.length > 0) {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
if (AsyncHandler.class.isAssignableFrom(params[i])) {
|
||||
if (boolReturnTypeFuture) {
|
||||
throw new RuntimeException(method + " have both AsyncHandler and CompletableFuture");
|
||||
}
|
||||
if (handlerFuncIndex >= 0) {
|
||||
throw new RuntimeException(method + " have more than one AsyncHandler type parameter");
|
||||
}
|
||||
Sncp.checkAsyncModifier(params[i], method);
|
||||
handlerFuncIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < anns.length; i++) {
|
||||
if (anns[i].length > 0) {
|
||||
for (Annotation ann : anns[i]) {
|
||||
if (ann.annotationType() == RpcAttachment.class) {
|
||||
if (handlerAttachIndex >= 0) {
|
||||
throw new RuntimeException(method + " have more than one @RpcAttachment parameter");
|
||||
}
|
||||
handlerAttachIndex = i;
|
||||
} else if (ann.annotationType() == RpcTargetAddress.class && SocketAddress.class.isAssignableFrom(params[i])) {
|
||||
targetAddrIndex = i;
|
||||
} else if (ann.annotationType() == RpcSourceAddress.class && SocketAddress.class.isAssignableFrom(params[i])) {
|
||||
sourceAddrIndex = i;
|
||||
}
|
||||
}
|
||||
for (Annotation ann : anns[i]) {
|
||||
if (ann.annotationType() == RpcCall.class) {
|
||||
try {
|
||||
atts[i + 1] = ((RpcCall) ann).value().newInstance();
|
||||
hasattr = true;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, RpcCall.class.getSimpleName() + ".attribute cannot a newInstance for" + method, e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.addressTargetParamIndex = targetAddrIndex;
|
||||
this.addressSourceParamIndex = sourceAddrIndex;
|
||||
this.handlerFuncParamIndex = handlerFuncIndex;
|
||||
this.handlerAttachParamIndex = handlerAttachIndex;
|
||||
this.paramAttrs = hasattr ? atts : null;
|
||||
if (this.handlerFuncParamIndex >= 0 && method.getReturnType() != void.class) {
|
||||
throw new RuntimeException(method + " have AsyncHandler type parameter but return type is not void");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + actionid + "," + (method == null ? "null" : method.getName()) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(SncpClient.class.getSimpleName());
|
||||
|
||||
protected final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
@@ -160,19 +55,43 @@ public final class SncpClient {
|
||||
|
||||
protected final SncpAction[] actions;
|
||||
|
||||
protected final Consumer<Runnable> executor;
|
||||
protected final ExecutorService executor;
|
||||
|
||||
public <T extends Service> SncpClient(final String serviceName, final Class<T> serviceTypeOrImplClass, final T service, final Consumer<Runnable> executor,
|
||||
@Resource
|
||||
protected JsonConvert jsonConvert;
|
||||
|
||||
@Resource
|
||||
protected BsonConvert bsonConvert;
|
||||
|
||||
//远程模式
|
||||
protected Set<String> remoteGroups;
|
||||
|
||||
//远程模式
|
||||
protected Transport remoteGroupTransport;
|
||||
|
||||
//本地模式
|
||||
protected String sameGroup;
|
||||
|
||||
//本地模式
|
||||
protected Transport sameGroupTransport;
|
||||
|
||||
//本地模式
|
||||
protected Set<String> diffGroups;
|
||||
|
||||
//本地模式
|
||||
protected Transport[] diffGroupTransports;
|
||||
|
||||
public <T extends Service> SncpClient(final String serviceName, final Class<T> serviceTypeOrImplClass, final T service, final TransportFactory factory,
|
||||
final boolean remote, final Class serviceClass, final InetSocketAddress clientAddress) {
|
||||
this.remote = remote;
|
||||
this.executor = executor;
|
||||
this.executor = factory.getExecutor();
|
||||
this.serviceClass = serviceClass;
|
||||
this.serviceversion = 0;
|
||||
this.clientAddress = clientAddress;
|
||||
this.name = serviceName;
|
||||
Class tn = serviceTypeOrImplClass;
|
||||
ResourceType rt = (ResourceType) tn.getAnnotation(ResourceType.class);
|
||||
if (rt != null && rt.value().length > 0) tn = rt.value()[0];
|
||||
if (rt != null) tn = rt.value();
|
||||
this.serviceid = Sncp.hash(tn.getName() + ':' + serviceName);
|
||||
final List<SncpAction> methodens = new ArrayList<>();
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -209,6 +128,54 @@ public final class SncpClient {
|
||||
return actions.length;
|
||||
}
|
||||
|
||||
public Set<String> getRemoteGroups() {
|
||||
return remoteGroups;
|
||||
}
|
||||
|
||||
public void setRemoteGroups(Set<String> remoteGroups) {
|
||||
this.remoteGroups = remoteGroups;
|
||||
}
|
||||
|
||||
public Transport getRemoteGroupTransport() {
|
||||
return remoteGroupTransport;
|
||||
}
|
||||
|
||||
public void setRemoteGroupTransport(Transport remoteGroupTransport) {
|
||||
this.remoteGroupTransport = remoteGroupTransport;
|
||||
}
|
||||
|
||||
public String getSameGroup() {
|
||||
return sameGroup;
|
||||
}
|
||||
|
||||
public void setSameGroup(String sameGroup) {
|
||||
this.sameGroup = sameGroup;
|
||||
}
|
||||
|
||||
public Transport getSameGroupTransport() {
|
||||
return sameGroupTransport;
|
||||
}
|
||||
|
||||
public void setSameGroupTransport(Transport sameGroupTransport) {
|
||||
this.sameGroupTransport = sameGroupTransport;
|
||||
}
|
||||
|
||||
public Set<String> getDiffGroups() {
|
||||
return diffGroups;
|
||||
}
|
||||
|
||||
public void setDiffGroups(Set<String> diffGroups) {
|
||||
this.diffGroups = diffGroups;
|
||||
}
|
||||
|
||||
public Transport[] getDiffGroupTransports() {
|
||||
return diffGroupTransports;
|
||||
}
|
||||
|
||||
public void setDiffGroupTransports(Transport[] diffGroupTransports) {
|
||||
this.diffGroupTransports = diffGroupTransports;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String service = serviceClass.getName();
|
||||
@@ -218,6 +185,26 @@ public final class SncpClient {
|
||||
+ ", actions.size = " + actions.length + ")";
|
||||
}
|
||||
|
||||
public String toSimpleString() { //给Sncp产生的Service用
|
||||
String service = serviceClass.getName();
|
||||
if (remote) service = service.replace(Sncp.LOCALPREFIX, Sncp.REMOTEPREFIX);
|
||||
List<InetSocketAddress> diffaddrs = new ArrayList<>();
|
||||
if (diffGroupTransports != null && diffGroupTransports.length > 1) {
|
||||
for (Transport t : diffGroupTransports) {
|
||||
diffaddrs.addAll(Utility.ofList(t.getRemoteAddresses()));
|
||||
}
|
||||
}
|
||||
return service + "(name = '" + name + "', serviceid = " + serviceid + ", serviceversion = " + serviceversion
|
||||
+ ", clientaddr = " + (clientAddress == null ? "" : (clientAddress.getHostString() + ":" + clientAddress.getPort()))
|
||||
+ ((sameGroup == null || sameGroup.isEmpty()) ? "" : ", sameGroup = " + sameGroup)
|
||||
+ (sameGroupTransport == null ? "" : ", sameGroupTransport = " + Arrays.toString(sameGroupTransport.getRemoteAddresses()))
|
||||
+ ((diffGroups == null || diffGroups.isEmpty()) ? "" : ", diffGroups = " + diffGroups)
|
||||
+ ((diffGroupTransports == null || diffGroupTransports.length < 1) ? "" : ", diffGroupTransports = " + diffaddrs)
|
||||
+ ((remoteGroups == null || remoteGroups.isEmpty()) ? "" : ", remoteGroups = " + remoteGroups)
|
||||
+ (remoteGroupTransport == null ? "" : ", remoteGroupTransport = " + Arrays.toString(remoteGroupTransport.getRemoteAddresses()))
|
||||
+ ", actions.size = " + actions.length + ")";
|
||||
}
|
||||
|
||||
public static List<Method> parseMethod(final Class serviceClass) {
|
||||
final List<Method> list = new ArrayList<>();
|
||||
final List<Method> multis = new ArrayList<>();
|
||||
@@ -228,6 +215,7 @@ public final class SncpClient {
|
||||
final int mod = method.getModifiers();
|
||||
if (Modifier.isStatic(mod)) continue;
|
||||
if (Modifier.isFinal(mod)) continue;
|
||||
if (method.getAnnotation(Local.class) != null) continue;
|
||||
if (method.getName().equals("getClass") || method.getName().equals("toString")) continue;
|
||||
if (method.getName().equals("equals") || method.getName().equals("hashCode")) continue;
|
||||
if (method.getName().equals("notify") || method.getName().equals("notifyAll") || method.getName().equals("wait")) continue;
|
||||
@@ -259,53 +247,53 @@ public final class SncpClient {
|
||||
return multis;
|
||||
}
|
||||
|
||||
public void remoteSameGroup(final BsonConvert bsonConvert, final JsonConvert jsonConvert, Transport transport, final int index, final Object... params) {
|
||||
if (transport == null) return;
|
||||
public void remoteSameGroup(final int index, final Object... params) {
|
||||
if (sameGroupTransport == null) return;
|
||||
final SncpAction action = actions[index];
|
||||
if (action.handlerFuncParamIndex >= 0) params[action.handlerFuncParamIndex] = null; //不能让远程调用handler,因为之前本地方法已经调用过了
|
||||
for (InetSocketAddress addr : transport.getRemoteAddresses()) {
|
||||
remote0(null, bsonConvert, jsonConvert, transport, addr, action, params);
|
||||
for (InetSocketAddress addr : sameGroupTransport.getRemoteAddresses()) {
|
||||
remote0(null, sameGroupTransport, addr, action, params);
|
||||
}
|
||||
}
|
||||
|
||||
public void asyncRemoteSameGroup(final BsonConvert bsonConvert, final JsonConvert jsonConvert, Transport transport, final int index, final Object... params) {
|
||||
if (transport == null) return;
|
||||
public void asyncRemoteSameGroup(final int index, final Object... params) {
|
||||
if (sameGroupTransport == null) return;
|
||||
if (executor != null) {
|
||||
executor.accept(() -> {
|
||||
remoteSameGroup(bsonConvert, jsonConvert, transport, index, params);
|
||||
executor.execute(() -> {
|
||||
remoteSameGroup(index, params);
|
||||
});
|
||||
} else {
|
||||
remoteSameGroup(bsonConvert, jsonConvert, transport, index, params);
|
||||
remoteSameGroup(index, params);
|
||||
}
|
||||
}
|
||||
|
||||
public void remoteDiffGroup(final BsonConvert bsonConvert, final JsonConvert jsonConvert, Transport[] transports, final int index, final Object... params) {
|
||||
if (transports == null || transports.length < 1) return;
|
||||
public void remoteDiffGroup(final int index, final Object... params) {
|
||||
if (diffGroupTransports == null || diffGroupTransports.length < 1) return;
|
||||
final SncpAction action = actions[index];
|
||||
if (action.handlerFuncParamIndex >= 0) params[action.handlerFuncParamIndex] = null; //不能让远程调用handler,因为之前本地方法已经调用过了
|
||||
for (Transport transport : transports) {
|
||||
remote0(null, bsonConvert, jsonConvert, transport, null, action, params);
|
||||
for (Transport transport : diffGroupTransports) {
|
||||
remote0(null, transport, null, action, params);
|
||||
}
|
||||
}
|
||||
|
||||
public void asyncRemoteDiffGroup(final BsonConvert bsonConvert, final JsonConvert jsonConvert, Transport[] transports, final int index, final Object... params) {
|
||||
if (transports == null || transports.length < 1) return;
|
||||
public void asyncRemoteDiffGroup(final int index, final Object... params) {
|
||||
if (diffGroupTransports == null || diffGroupTransports.length < 1) return;
|
||||
if (executor != null) {
|
||||
executor.accept(() -> {
|
||||
remoteDiffGroup(bsonConvert, jsonConvert, transports, index, params);
|
||||
executor.execute(() -> {
|
||||
remoteDiffGroup(index, params);
|
||||
});
|
||||
} else {
|
||||
remoteDiffGroup(bsonConvert, jsonConvert, transports, index, params);
|
||||
remoteDiffGroup(index, params);
|
||||
}
|
||||
}
|
||||
|
||||
//只给远程模式调用的
|
||||
public <T> T remote(final BsonConvert bsonConvert, final JsonConvert jsonConvert, Transport transport, final int index, final Object... params) {
|
||||
public <T> T remote(final int index, final Object... params) {
|
||||
final SncpAction action = actions[index];
|
||||
final AsyncHandler handlerFunc = action.handlerFuncParamIndex >= 0 ? (AsyncHandler) params[action.handlerFuncParamIndex] : null;
|
||||
if (action.handlerFuncParamIndex >= 0) params[action.handlerFuncParamIndex] = null;
|
||||
final BsonReader reader = bsonConvert.pollBsonReader();
|
||||
CompletableFuture<byte[]> future = remote0(handlerFunc, bsonConvert, jsonConvert, transport, null, action, params);
|
||||
CompletableFuture<byte[]> future = remote0(handlerFunc, remoteGroupTransport, null, action, params);
|
||||
if (action.boolReturnTypeFuture) {
|
||||
CompletableFuture result = action.futureCreator.create();
|
||||
future.whenComplete((v, e) -> {
|
||||
@@ -323,6 +311,8 @@ public final class SncpClient {
|
||||
|
||||
result.complete(rs);
|
||||
}
|
||||
} catch (Exception exp) {
|
||||
result.completeExceptionally(exp);
|
||||
} finally {
|
||||
bsonConvert.offerBsonReader(reader);
|
||||
}
|
||||
@@ -346,27 +336,7 @@ public final class SncpClient {
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void remote(final BsonConvert bsonConvert, final JsonConvert jsonConvert, Transport[] transports, final int index, final Object... params) {
|
||||
if (transports == null || transports.length < 1) return;
|
||||
remote(bsonConvert, jsonConvert, transports[0], index, params);
|
||||
for (int i = 1; i < transports.length; i++) {
|
||||
remote0(null, bsonConvert, jsonConvert, transports[i], null, actions[index], params);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<byte[]> remote0(final AsyncHandler handler, final BsonConvert bsonConvert, final JsonConvert jsonConvert, final Transport transport, final SocketAddress addr0, final SncpAction action, final Object... params) {
|
||||
if ("rest".equalsIgnoreCase(transport.getSubprotocol())) {
|
||||
return remoteRest0(handler, jsonConvert, transport, addr0, action, params);
|
||||
}
|
||||
return remoteSncp0(handler, bsonConvert, transport, addr0, action, params);
|
||||
}
|
||||
|
||||
//尚未实现
|
||||
private CompletableFuture<byte[]> remoteRest0(final AsyncHandler handler, final JsonConvert jsonConvert, final Transport transport, final SocketAddress addr0, final SncpAction action, final Object... params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompletableFuture<byte[]> remoteSncp0(final AsyncHandler handler, final BsonConvert bsonConvert, final Transport transport, final SocketAddress addr0, final SncpAction action, final Object... params) {
|
||||
private CompletableFuture<byte[]> remote0(final AsyncHandler handler, final Transport transport, final SocketAddress addr0, final SncpAction action, final Object... params) {
|
||||
final Type[] myparamtypes = action.paramTypes;
|
||||
final Class[] myparamclass = action.paramClass;
|
||||
if (action.addressSourceParamIndex >= 0) params[action.addressSourceParamIndex] = this.clientAddress;
|
||||
@@ -379,16 +349,23 @@ public final class SncpClient {
|
||||
final long seqid = System.nanoTime();
|
||||
final DLong actionid = action.actionid;
|
||||
final SocketAddress addr = addr0 == null ? (action.addressTargetParamIndex >= 0 ? (SocketAddress) params[action.addressTargetParamIndex] : null) : addr0;
|
||||
final AsyncConnection conn = transport.pollConnection(addr);
|
||||
if (conn == null || !conn.isOpen()) {
|
||||
logger.log(Level.SEVERE, action.method + " sncp (params: " + convert.convertTo(params) + ") cannot connect " + (conn == null ? addr : conn.getRemoteAddress()));
|
||||
throw new RuntimeException("sncp " + (conn == null ? addr : conn.getRemoteAddress()) + " cannot connect");
|
||||
final CompletableFuture<byte[]> future = new CompletableFuture();
|
||||
AsyncConnection conn0;
|
||||
try {
|
||||
conn0 = transport.pollConnection(addr);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
return future;
|
||||
}
|
||||
if (conn0 == null || !conn0.isOpen()) {
|
||||
future.completeExceptionally(new RuntimeException("sncp " + (conn0 == null ? addr : conn0.getRemoteAddress()) + " cannot connect"));
|
||||
return future;
|
||||
}
|
||||
final AsyncConnection conn = conn0;
|
||||
final ByteBuffer[] sendBuffers = writer.toBuffers();
|
||||
fillHeader(sendBuffers[0], seqid, actionid, reqBodyLength);
|
||||
|
||||
final ByteBuffer buffer = transport.pollBuffer();
|
||||
final CompletableFuture<byte[]> future = new CompletableFuture();
|
||||
conn.write(sendBuffers, sendBuffers, new CompletionHandler<Integer, ByteBuffer[]>() {
|
||||
|
||||
@Override
|
||||
@@ -545,4 +522,108 @@ public final class SncpClient {
|
||||
buffer.position(currentpos);
|
||||
}
|
||||
|
||||
protected static final class SncpAction {
|
||||
|
||||
protected final DLong actionid;
|
||||
|
||||
protected final Method method;
|
||||
|
||||
protected final Type resultTypes; //void 必须设为 null
|
||||
|
||||
protected final Type[] paramTypes;
|
||||
|
||||
protected final Class[] paramClass;
|
||||
|
||||
protected final Attribute[] paramAttrs; // 为null表示无RpcCall处理,index=0固定为null, 其他为参数标记的RpcCall回调方法
|
||||
|
||||
protected final int handlerFuncParamIndex;
|
||||
|
||||
protected final int handlerAttachParamIndex;
|
||||
|
||||
protected final int addressTargetParamIndex;
|
||||
|
||||
protected final int addressSourceParamIndex;
|
||||
|
||||
protected final boolean boolReturnTypeFuture; // 返回结果类型是否为 CompletableFuture
|
||||
|
||||
protected final Creator<? extends CompletableFuture> futureCreator;
|
||||
|
||||
public SncpAction(final Class clazz, Method method, DLong actionid) {
|
||||
this.actionid = actionid == null ? Sncp.hash(method) : actionid;
|
||||
Type rt = method.getGenericReturnType();
|
||||
if (rt instanceof TypeVariable) {
|
||||
TypeVariable tv = (TypeVariable) rt;
|
||||
if (tv.getBounds().length == 1) rt = tv.getBounds()[0];
|
||||
}
|
||||
this.resultTypes = rt == void.class ? null : rt;
|
||||
this.boolReturnTypeFuture = CompletableFuture.class.isAssignableFrom(method.getReturnType());
|
||||
this.futureCreator = boolReturnTypeFuture ? Creator.create((Class<? extends CompletableFuture>) method.getReturnType()) : null;
|
||||
this.paramTypes = method.getGenericParameterTypes();
|
||||
this.paramClass = method.getParameterTypes();
|
||||
this.method = method;
|
||||
Annotation[][] anns = method.getParameterAnnotations();
|
||||
int targetAddrIndex = -1;
|
||||
int sourceAddrIndex = -1;
|
||||
int handlerAttachIndex = -1;
|
||||
int handlerFuncIndex = -1;
|
||||
boolean hasattr = false;
|
||||
Attribute[] atts = new Attribute[paramTypes.length + 1];
|
||||
if (anns.length > 0) {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
if (AsyncHandler.class.isAssignableFrom(params[i])) {
|
||||
if (boolReturnTypeFuture) {
|
||||
throw new RuntimeException(method + " have both AsyncHandler and CompletableFuture");
|
||||
}
|
||||
if (handlerFuncIndex >= 0) {
|
||||
throw new RuntimeException(method + " have more than one AsyncHandler type parameter");
|
||||
}
|
||||
Sncp.checkAsyncModifier(params[i], method);
|
||||
handlerFuncIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < anns.length; i++) {
|
||||
if (anns[i].length > 0) {
|
||||
for (Annotation ann : anns[i]) {
|
||||
if (ann.annotationType() == RpcAttachment.class) {
|
||||
if (handlerAttachIndex >= 0) {
|
||||
throw new RuntimeException(method + " have more than one @RpcAttachment parameter");
|
||||
}
|
||||
handlerAttachIndex = i;
|
||||
} else if (ann.annotationType() == RpcTargetAddress.class && SocketAddress.class.isAssignableFrom(params[i])) {
|
||||
targetAddrIndex = i;
|
||||
} else if (ann.annotationType() == RpcSourceAddress.class && SocketAddress.class.isAssignableFrom(params[i])) {
|
||||
sourceAddrIndex = i;
|
||||
}
|
||||
}
|
||||
for (Annotation ann : anns[i]) {
|
||||
if (ann.annotationType() == RpcCall.class) {
|
||||
try {
|
||||
atts[i + 1] = ((RpcCall) ann).value().newInstance();
|
||||
hasattr = true;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, RpcCall.class.getSimpleName() + ".attribute cannot a newInstance for" + method, e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.addressTargetParamIndex = targetAddrIndex;
|
||||
this.addressSourceParamIndex = sourceAddrIndex;
|
||||
this.handlerFuncParamIndex = handlerFuncIndex;
|
||||
this.handlerAttachParamIndex = handlerAttachIndex;
|
||||
this.paramAttrs = hasattr ? atts : null;
|
||||
if (this.handlerFuncParamIndex >= 0 && method.getReturnType() != void.class) {
|
||||
throw new RuntimeException(method + " have AsyncHandler type parameter but return type is not void");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + actionid + "," + (method == null ? "null" : method.getName()) + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.util.ObjectPool;
|
||||
import org.redkale.watch.WatchFactory;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -23,9 +22,9 @@ import org.redkale.watch.WatchFactory;
|
||||
public class SncpContext extends Context {
|
||||
|
||||
public SncpContext(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool,
|
||||
ObjectPool<Response> responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare,
|
||||
WatchFactory watch, int readTimeoutSecond, int writeTimeoutSecond) {
|
||||
ObjectPool<Response> responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare,
|
||||
int readTimeoutSecond, int writeTimeoutSecond) {
|
||||
super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset,
|
||||
address, prepare, watch, readTimeoutSecond, writeTimeoutSecond);
|
||||
address, prepare, readTimeoutSecond, writeTimeoutSecond);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,19 +42,14 @@ public final class SncpDynServlet extends SncpServlet {
|
||||
|
||||
private final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
|
||||
private final Class<? extends Service> type;
|
||||
|
||||
private final String serviceName;
|
||||
|
||||
private final DLong serviceid;
|
||||
|
||||
private final HashMap<DLong, SncpServletAction> actions = new HashMap<>();
|
||||
|
||||
private Supplier<ByteBuffer> bufferSupplier;
|
||||
|
||||
public SncpDynServlet(final BsonConvert convert, final String serviceName, final Class<? extends Service> type, final Service service) {
|
||||
this.serviceName = serviceName;
|
||||
this.type = type;
|
||||
public SncpDynServlet(final BsonConvert convert, final String serviceName, final Class serviceOrSourceType, final Service service) {
|
||||
super(serviceName, serviceOrSourceType, service);
|
||||
this.serviceid = Sncp.hash(type.getName() + ':' + serviceName);
|
||||
Set<DLong> actionids = new HashSet<>();
|
||||
for (java.lang.reflect.Method method : service.getClass().getMethods()) {
|
||||
|
||||
16
src/org/redkale/net/sncp/SncpFilter.java
Normal file
16
src/org/redkale/net/sncp/SncpFilter.java
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.net.sncp;
|
||||
|
||||
import org.redkale.net.Filter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class SncpFilter extends Filter<SncpContext, SncpRequest, SncpResponse> {
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.redkale.util.AnyValue;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
@@ -21,17 +22,37 @@ import org.redkale.util.*;
|
||||
*/
|
||||
public class SncpPrepareServlet extends PrepareServlet<DLong, SncpContext, SncpRequest, SncpResponse, SncpServlet> {
|
||||
|
||||
private final Object sncplock = new Object();
|
||||
|
||||
private static final ByteBuffer pongBuffer = ByteBuffer.wrap("PONG".getBytes()).asReadOnlyBuffer();
|
||||
|
||||
@Override
|
||||
public void addServlet(SncpServlet servlet, Object attachment, AnyValue conf, DLong... mappings) {
|
||||
addServlet((SncpServlet) servlet, conf);
|
||||
synchronized (sncplock) {
|
||||
for (SncpServlet s : getServlets()) {
|
||||
if (s.service == servlet.service) throw new RuntimeException(s.service + " repeat addSncpServlet");
|
||||
}
|
||||
setServletConf(servlet, conf);
|
||||
putMapping(servlet.getServiceid(), servlet);
|
||||
putServlet(servlet);
|
||||
}
|
||||
}
|
||||
|
||||
public void addServlet(SncpServlet servlet, AnyValue conf) {
|
||||
setServletConf(servlet, conf);
|
||||
putMapping(servlet.getServiceid(), servlet);
|
||||
putServlet(servlet);
|
||||
public <T> SncpServlet removeSncpServlet(Service service) {
|
||||
SncpServlet rs = null;
|
||||
synchronized (sncplock) {
|
||||
for (SncpServlet servlet : getServlets()) {
|
||||
if (servlet.service == service) {
|
||||
rs = servlet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rs != null) {
|
||||
removeMapping(rs);
|
||||
removeServlet(rs);
|
||||
}
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public List<SncpServlet> getSncpServlets() {
|
||||
@@ -40,11 +61,13 @@ public class SncpPrepareServlet extends PrepareServlet<DLong, SncpContext, SncpR
|
||||
|
||||
@Override
|
||||
public void init(SncpContext context, AnyValue config) {
|
||||
super.init(context, config); //必须要执行
|
||||
getServlets().forEach(s -> s.init(context, getServletConf(s)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(SncpContext context, AnyValue config) {
|
||||
super.destroy(context, config); //必须要执行
|
||||
getServlets().forEach(s -> s.destroy(context, getServletConf(s)));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.redkale.convert.bson.*;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.watch.*;
|
||||
|
||||
/**
|
||||
* Service Node Communicate Protocol
|
||||
@@ -23,14 +22,14 @@ import org.redkale.watch.*;
|
||||
* @author zhangjx
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class SncpServer extends Server<DLong, SncpContext, SncpRequest, SncpResponse, SncpServlet> {
|
||||
public class SncpServer extends Server<DLong, SncpContext, SncpRequest, SncpResponse, SncpServlet> {
|
||||
|
||||
public SncpServer() {
|
||||
this(System.currentTimeMillis(), null);
|
||||
this(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public SncpServer(long serverStartTime, final WatchFactory watch) {
|
||||
super(serverStartTime, "TCP", new SncpPrepareServlet(), watch);
|
||||
public SncpServer(long serverStartTime) {
|
||||
super(serverStartTime, "TCP", new SncpPrepareServlet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,18 +37,58 @@ public final class SncpServer extends Server<DLong, SncpContext, SncpRequest, Sn
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除SncpFilter
|
||||
*
|
||||
* @param filterName SncpFilter名称
|
||||
*
|
||||
* @return SncpFilter
|
||||
*/
|
||||
public SncpFilter removeFilter(String filterName) {
|
||||
return (SncpFilter) this.prepare.removeFilter(filterName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除SncpFilter
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param filterClass SncpFilter类
|
||||
*
|
||||
* @return SncpFilter
|
||||
*/
|
||||
public <T extends SncpFilter> T removeFilter(Class<T> filterClass) {
|
||||
return (T) this.prepare.removeFilter(filterClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加SncpFilter
|
||||
*
|
||||
* @param filter SncpFilter
|
||||
* @param conf AnyValue
|
||||
*
|
||||
* @return SncpServer
|
||||
*/
|
||||
public SncpServer addSncpFilter(SncpFilter filter, AnyValue conf) {
|
||||
this.prepare.addFilter(filter, conf);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除SncpServlet
|
||||
*
|
||||
* @param sncpService Service
|
||||
*
|
||||
* @return SncpServlet
|
||||
*/
|
||||
public SncpServlet removeSncpServlet(Service sncpService) {
|
||||
return ((SncpPrepareServlet) this.prepare).removeSncpServlet(sncpService);
|
||||
}
|
||||
|
||||
public void addSncpServlet(Service sncpService) {
|
||||
for (Class type : Sncp.getResourceTypes(sncpService)) {
|
||||
SncpDynServlet sds = new SncpDynServlet(BsonFactory.root().getConvert(), Sncp.getResourceName(sncpService), type, sncpService);
|
||||
this.prepare.addServlet(sds, null, Sncp.getConf(sncpService));
|
||||
}
|
||||
SncpDynServlet sds = new SncpDynServlet(BsonFactory.root().getConvert(), Sncp.getResourceName(sncpService), Sncp.getResourceType(sncpService), sncpService);
|
||||
this.prepare.addServlet(sds, null, Sncp.getConf(sncpService));
|
||||
}
|
||||
|
||||
public <T extends Service> void addSncpServlet(Class<T> serviceTypeClass, String name, T service, AnyValue conf) {
|
||||
SncpDynServlet sds = new SncpDynServlet(BsonFactory.root().getConvert(), name, serviceTypeClass, service);
|
||||
this.prepare.addServlet(sds, null, conf);
|
||||
}
|
||||
|
||||
|
||||
public List<SncpServlet> getSncpServlets() {
|
||||
return ((SncpPrepareServlet) this.prepare).getSncpServlets();
|
||||
}
|
||||
@@ -58,8 +97,8 @@ public final class SncpServer extends Server<DLong, SncpContext, SncpRequest, Sn
|
||||
@SuppressWarnings("unchecked")
|
||||
protected SncpContext createContext() {
|
||||
final int port = this.address.getPort();
|
||||
AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Buffer.creatCounter");
|
||||
AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Buffer.cycleCounter");
|
||||
AtomicLong createBufferCounter = new AtomicLong();
|
||||
AtomicLong cycleBufferCounter = new AtomicLong();
|
||||
final int rcapacity = Math.max(this.bufferCapacity, 4 * 1024);
|
||||
ObjectPool<ByteBuffer> bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, this.bufferPoolSize,
|
||||
(Object... params) -> ByteBuffer.allocateDirect(rcapacity), null, (e) -> {
|
||||
@@ -67,11 +106,11 @@ public final class SncpServer extends Server<DLong, SncpContext, SncpRequest, Sn
|
||||
e.clear();
|
||||
return true;
|
||||
});
|
||||
AtomicLong createResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Response.creatCounter");
|
||||
AtomicLong cycleResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Response.cycleCounter");
|
||||
AtomicLong createResponseCounter = new AtomicLong();
|
||||
AtomicLong cycleResponseCounter = new AtomicLong();
|
||||
ObjectPool<Response> responsePool = SncpResponse.createPool(createResponseCounter, cycleResponseCounter, this.responsePoolSize, null);
|
||||
SncpContext sncpcontext = new SncpContext(this.serverStartTime, this.logger, executor, rcapacity, bufferPool, responsePool,
|
||||
this.maxbody, this.charset, this.address, this.prepare, this.watch, this.readTimeoutSecond, this.writeTimeoutSecond);
|
||||
this.maxbody, this.charset, this.address, this.prepare, this.readTimeoutSecond, this.writeTimeoutSecond);
|
||||
responsePool.setCreator((Object... params) -> new SncpResponse(sncpcontext, new SncpRequest(sncpcontext)));
|
||||
return sncpcontext;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.redkale.net.sncp;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.*;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
@@ -19,6 +20,26 @@ import org.redkale.util.*;
|
||||
*/
|
||||
public abstract class SncpServlet extends Servlet<SncpContext, SncpRequest, SncpResponse> implements Comparable<SncpServlet> {
|
||||
|
||||
protected final Class type;
|
||||
|
||||
protected final String serviceName;
|
||||
|
||||
protected final Service service;
|
||||
|
||||
protected SncpServlet(String serviceName, Class serviceOrSourceType, Service service) {
|
||||
this.type = serviceOrSourceType;
|
||||
this.service = service;
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public Service getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public abstract DLong getServiceid();
|
||||
|
||||
protected ExecutorService getExecutor() {
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.redkale.util.*;
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
@ResourceType({DataCacheListenerService.class, DataCacheListener.class})
|
||||
@ResourceType(DataCacheListener.class)
|
||||
public class DataCacheListenerService implements DataCacheListener, Service {
|
||||
|
||||
@Resource(name = "$")
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
package org.redkale.service;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 本地模式注解。
|
||||
* 声明为LocalService的Service只能以本地模式存在, 即使配置文件中配置成远程模式也将被忽略。
|
||||
* 声明为Local的Service只能以本地模式存在, 即使配置文件中配置成远程模式也将被忽略。
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
@@ -20,9 +20,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({TYPE})
|
||||
@Target({TYPE, METHOD, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
public @interface LocalService {
|
||||
public @interface Local {
|
||||
|
||||
String comment() default ""; //备注描述
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import org.redkale.util.*;
|
||||
* <blockquote><pre>
|
||||
* 异步方法:
|
||||
* Service编写异步方法:
|
||||
* 1、异步方法有且仅有一个类型为AsyncHandler的参数。若参数类型为AsyncHandler子类,必须保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数。
|
||||
* 2、异步方法返回类型必须是void。
|
||||
* 1、异步方法有且仅有一个类型为AsyncHandler的参数, 返回类型必须是void。若参数类型为AsyncHandler子类,必须保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数。
|
||||
* 2、异步方法返回类型是CompletableFuture。
|
||||
* 例如:
|
||||
* public void insertRecord(AsyncHandler<Integer, Record> handler, String name, @RpcAttachment Record record);
|
||||
*
|
||||
|
||||
@@ -9,6 +9,7 @@ import static org.redkale.net.http.WebSocket.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.redkale.net.http.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
@@ -20,7 +21,7 @@ import org.redkale.util.*;
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
@ResourceType({WebSocketNodeService.class, WebSocketNode.class})
|
||||
@ResourceType(WebSocketNode.class)
|
||||
public class WebSocketNodeService extends WebSocketNode implements Service {
|
||||
|
||||
@Override
|
||||
@@ -34,49 +35,56 @@ public class WebSocketNodeService extends WebSocketNode implements Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOnlineRemoteAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable groupid) {
|
||||
if (localSncpAddress == null || !localSncpAddress.equals(targetAddress)) return remoteOnlineRemoteAddresses(targetAddress, groupid);
|
||||
final Set<String> engineids = localNodes.get(groupid);
|
||||
if (engineids == null || engineids.isEmpty()) return null;
|
||||
final List<String> rs = new ArrayList<>();
|
||||
for (String engineid : engineids) {
|
||||
final WebSocketEngine engine = engines.get(engineid);
|
||||
if (engine == null) continue;
|
||||
final WebSocketGroup group = engine.getWebSocketGroup(groupid);
|
||||
group.getWebSockets().forEach(x -> rs.add("ws" + Objects.hashCode(x) + '@' + x.getRemoteAddr()));
|
||||
}
|
||||
return rs;
|
||||
public CompletableFuture<List<String>> getWebSocketAddresses(final @RpcTargetAddress InetSocketAddress targetAddress, final Serializable groupid) {
|
||||
if (localSncpAddress == null || !localSncpAddress.equals(targetAddress)) return remoteWebSocketAddresses(targetAddress, groupid);
|
||||
if (this.localEngine == null) return CompletableFuture.completedFuture(new ArrayList<>());
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final List<String> rs = new ArrayList<>();
|
||||
this.localEngine.getLocalWebSockets(groupid).forEach(x -> rs.add(x.getRemoteAddr()));
|
||||
return rs;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendMessage(@RpcTargetAddress InetSocketAddress addr, Serializable groupid, boolean recent, Object message, boolean last) {
|
||||
final Set<String> engineids = localNodes.get(groupid);
|
||||
if (engineids == null || engineids.isEmpty()) return RETCODE_GROUP_EMPTY;
|
||||
int code = RETCODE_GROUP_EMPTY;
|
||||
for (String engineid : engineids) {
|
||||
final WebSocketEngine engine = engines.get(engineid);
|
||||
if (engine != null) { //在本地
|
||||
final WebSocketGroup group = engine.getWebSocketGroup(groupid);
|
||||
if (group == null || group.isEmpty()) {
|
||||
if (finest) logger.finest("receive websocket message {engineid:'" + engineid + "', groupid:" + groupid + ", content:'" + message + "'} from " + addr + " but send result is " + RETCODE_GROUP_EMPTY);
|
||||
return RETCODE_GROUP_EMPTY;
|
||||
}
|
||||
code = group.send(recent, message, last);
|
||||
if (finest) logger.finest("websocket node send message (" + message + ") from " + addr + " result is " + code);
|
||||
}
|
||||
}
|
||||
return code;
|
||||
public CompletableFuture<Integer> sendMessage(@RpcTargetAddress InetSocketAddress addr, Object message, boolean last, Serializable userid) {
|
||||
if (this.localEngine == null) return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
|
||||
return this.localEngine.sendMessage(message, last, userid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Serializable groupid, InetSocketAddress addr) {
|
||||
sncpNodes.appendSetItem(groupid, addr);
|
||||
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + groupid + " connect from " + addr);
|
||||
public CompletableFuture<Integer> broadcastMessage(@RpcTargetAddress InetSocketAddress addr, Object message, boolean last) {
|
||||
if (this.localEngine == null) return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
|
||||
return this.localEngine.broadcastMessage(message, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当用户连接到节点,需要更新到CacheSource
|
||||
*
|
||||
* @param userid String
|
||||
* @param sncpAddr InetSocketAddress
|
||||
*
|
||||
* @return 无返回值
|
||||
*/
|
||||
@Override
|
||||
public void disconnect(Serializable groupid, InetSocketAddress addr) {
|
||||
sncpNodes.removeSetItem(groupid, addr);
|
||||
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + groupid + " disconnect from " + addr);
|
||||
public CompletableFuture<Void> connect(Serializable userid, InetSocketAddress sncpAddr) {
|
||||
CompletableFuture<Void> future = sncpNodeAddresses.appendSetItemAsync(userid, sncpAddr);
|
||||
future = future.thenAccept((a) -> sncpNodeAddresses.appendSetItemAsync("redkale_sncpnodes", sncpAddr));
|
||||
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " connect from " + sncpAddr);
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当用户从一个节点断掉了所有的连接,需要从CacheSource中删除
|
||||
*
|
||||
* @param userid String
|
||||
* @param sncpAddr InetSocketAddress
|
||||
*
|
||||
* @return 无返回值
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Void> disconnect(Serializable userid, InetSocketAddress sncpAddr) {
|
||||
CompletableFuture<Void> future = sncpNodeAddresses.removeSetItemAsync(userid, sncpAddr);
|
||||
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " disconnect from " + sncpAddr);
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
package org.redkale.source;
|
||||
|
||||
import java.beans.ConstructorProperties;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
@@ -13,7 +12,6 @@ import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.convert.ConvertColumn;
|
||||
import org.redkale.convert.json.*;
|
||||
import org.redkale.net.sncp.Sncp;
|
||||
import org.redkale.service.*;
|
||||
@@ -29,9 +27,9 @@ import org.redkale.util.*;
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@LocalService
|
||||
@Local
|
||||
@AutoLoad(false)
|
||||
@ResourceType({CacheSource.class})
|
||||
@ResourceType(CacheSource.class)
|
||||
public class CacheMemorySource<K extends Serializable, V extends Object> extends AbstractService implements CacheSource<K, V>, Service, AutoCloseable, Resourcable {
|
||||
|
||||
@Resource(name = "APP_HOME")
|
||||
@@ -56,7 +54,10 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
|
||||
private final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
protected final ConcurrentHashMap<K, CacheEntry<K, ?>> container = new ConcurrentHashMap<>();
|
||||
protected final ConcurrentHashMap<K, CacheEntry<K, Object>> container = new ConcurrentHashMap<>();
|
||||
|
||||
@RpcRemote
|
||||
protected CacheSource<K, V> remoteSource;
|
||||
|
||||
public CacheMemorySource() {
|
||||
}
|
||||
@@ -125,32 +126,51 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
boolean datasync = false; //是否从远程同步过数据
|
||||
//----------同步数据……-----------
|
||||
// TODO
|
||||
if (!this.needStore) return;
|
||||
try {
|
||||
File store = new File(home, "cache/" + resourceName());
|
||||
if (!store.isFile() || !store.canRead()) return;
|
||||
LineNumberReader reader = new LineNumberReader(new FileReader(store));
|
||||
if (this.keyType == null) this.keyType = Serializable.class;
|
||||
if (this.objValueType == null) {
|
||||
this.objValueType = Object.class;
|
||||
this.setValueType = TypeToken.createParameterizedType(null, CopyOnWriteArraySet.class, this.objValueType);
|
||||
this.listValueType = TypeToken.createParameterizedType(null, ConcurrentLinkedQueue.class, this.objValueType);
|
||||
if (this.needStore) {
|
||||
try {
|
||||
File store = new File(home, "cache/" + resourceName());
|
||||
if (!store.isFile() || !store.canRead()) return;
|
||||
LineNumberReader reader = new LineNumberReader(new FileReader(store));
|
||||
if (this.keyType == null) this.keyType = Serializable.class;
|
||||
if (this.objValueType == null) {
|
||||
this.objValueType = Object.class;
|
||||
this.setValueType = TypeToken.createParameterizedType(null, CopyOnWriteArraySet.class, this.objValueType);
|
||||
this.listValueType = TypeToken.createParameterizedType(null, ConcurrentLinkedQueue.class, this.objValueType);
|
||||
}
|
||||
final Type storeObjType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, objValueType);
|
||||
final Type storeSetType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, setValueType);
|
||||
final Type storeListType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, listValueType);
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) continue;
|
||||
CacheEntry<K, Object> entry = convert.convertFrom(line.startsWith(CacheEntry.JSON_SET_KEY) ? storeSetType : (line.startsWith(CacheEntry.JSON_LIST_KEY) ? storeListType : storeObjType), line);
|
||||
if (entry.isExpired()) continue;
|
||||
if (datasync && container.containsKey(entry.key)) continue; //已经同步了
|
||||
container.put(entry.key, entry);
|
||||
}
|
||||
reader.close();
|
||||
store.delete();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, CacheSource.class.getSimpleName() + "(" + resourceName() + ") load store file error ", e);
|
||||
}
|
||||
final Type storeObjType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, objValueType);
|
||||
final Type storeSetType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, setValueType);
|
||||
final Type storeListType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, listValueType);
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) continue;
|
||||
CacheEntry<K, ?> entry = convert.convertFrom(line.startsWith(CacheEntry.JSON_SET_KEY) ? storeSetType : (line.startsWith(CacheEntry.JSON_LIST_KEY) ? storeListType : storeObjType), line);
|
||||
if (entry.isExpired()) continue;
|
||||
if (datasync && container.containsKey(entry.key)) continue; //已经同步了
|
||||
container.put(entry.key, entry);
|
||||
}
|
||||
reader.close();
|
||||
store.delete();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, CacheSource.class.getSimpleName() + "(" + resourceName() + ") load store file error ", e);
|
||||
}
|
||||
if (remoteSource != null && !Sncp.isRemote(this)) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
CompletableFuture<List<CacheEntry<K, Object>>> listFuture = remoteSource.queryListAsync();
|
||||
listFuture.whenComplete((list, exp) -> {
|
||||
if (exp != null) {
|
||||
logger.log(Level.FINEST, CacheSource.class.getSimpleName() + "(" + resourceName() + ") queryListAsync error", exp);
|
||||
} else {
|
||||
for (CacheEntry<K, Object> entry : list) {
|
||||
container.put(entry.key, entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.FINEST, CacheSource.class.getSimpleName() + "(" + resourceName() + ") queryListAsync error, maybe remote node connot connect ", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +196,7 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
final Type storeObjType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, objValueType);
|
||||
final Type storeSetType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, setValueType);
|
||||
final Type storeListType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, listValueType);
|
||||
Collection<CacheEntry<K, ?>> entrys = (Collection<CacheEntry<K, ?>>) container.values();
|
||||
Collection<CacheEntry<K, Object>> entrys = container.values();
|
||||
for (CacheEntry entry : entrys) {
|
||||
stream.println(convert.convertTo(entry.isSetCacheType() ? storeSetType : (entry.isListCacheType() ? storeListType : storeObjType), entry));
|
||||
}
|
||||
@@ -200,26 +220,14 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.supplyAsync(() -> exists(key), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void existsAsync(final AsyncHandler<Boolean, K> handler, @RpcAttachment final K key) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
boolean rs = exists(key);
|
||||
if (handler != null) handler.completed(rs, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key) {
|
||||
if (key == null) return null;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null || entry.isExpired() || entry.value == null) return null;
|
||||
if (entry.isListCacheType()) return (V) new ArrayList((Collection) entry.value);
|
||||
if (entry.isSetCacheType()) return (V) new HashSet((Collection) entry.value);
|
||||
return (V) entry.getValue();
|
||||
if (entry == null || entry.isExpired()) return null;
|
||||
if (entry.isListCacheType()) return (V) (entry.listValue == null ? null : new ArrayList(entry.listValue));
|
||||
if (entry.isSetCacheType()) return (V) (entry.setValue == null ? null : new HashSet(entry.setValue));
|
||||
return (V) entry.objectValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,29 +235,17 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.supplyAsync(() -> get(key), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAsync(final AsyncHandler<V, K> handler, @RpcAttachment final K key) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
V rs = get(key);
|
||||
if (handler != null) handler.completed(rs, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public V getAndRefresh(K key, final int expireSeconds) {
|
||||
if (key == null) return null;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null || entry.isExpired() || entry.value == null) return null;
|
||||
if (entry == null || entry.isExpired()) return null;
|
||||
entry.lastAccessed = (int) (System.currentTimeMillis() / 1000);
|
||||
entry.expireSeconds = expireSeconds;
|
||||
if (entry.isListCacheType()) return (V) new ArrayList((Collection) entry.value);
|
||||
if (entry.isSetCacheType()) return (V) new HashSet((Collection) entry.value);
|
||||
return (V) entry.getValue();
|
||||
if (entry.isListCacheType()) return (V) (entry.listValue == null ? null : new ArrayList(entry.listValue));
|
||||
if (entry.isSetCacheType()) return (V) (entry.setValue == null ? null : new HashSet(entry.setValue));
|
||||
return (V) entry.objectValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -257,18 +253,6 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.supplyAsync(() -> getAndRefresh(key, expireSeconds), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAndRefreshAsync(final AsyncHandler<V, K> handler, @RpcAttachment final K key, final int expireSeconds) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
V rs = getAndRefresh(key, expireSeconds);
|
||||
if (handler != null) handler.completed(rs, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void refresh(K key, final int expireSeconds) {
|
||||
@@ -284,29 +268,17 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> refresh(key, expireSeconds), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final int expireSeconds) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
refresh(key, expireSeconds);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void set(K key, V value) {
|
||||
if (key == null) return;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null) {
|
||||
entry = new CacheEntry(CacheEntryType.OBJECT, key, value);
|
||||
entry = new CacheEntry(CacheEntryType.OBJECT, key, value, null, null);
|
||||
container.putIfAbsent(key, entry);
|
||||
} else {
|
||||
entry.expireSeconds = 0;
|
||||
entry.value = value;
|
||||
entry.objectValue = value;
|
||||
entry.lastAccessed = (int) (System.currentTimeMillis() / 1000);
|
||||
}
|
||||
}
|
||||
@@ -316,30 +288,18 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> set(key, value), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final V value) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
set(key, value);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void set(int expireSeconds, K key, V value) {
|
||||
if (key == null) return;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null) {
|
||||
entry = new CacheEntry(CacheEntryType.OBJECT, expireSeconds, key, value);
|
||||
entry = new CacheEntry(CacheEntryType.OBJECT, expireSeconds, key, value, null, null);
|
||||
container.putIfAbsent(key, entry);
|
||||
} else {
|
||||
if (expireSeconds > 0) entry.expireSeconds = expireSeconds;
|
||||
entry.lastAccessed = (int) (System.currentTimeMillis() / 1000);
|
||||
entry.value = value;
|
||||
entry.objectValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,18 +308,6 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> set(expireSeconds, key, value), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsync(final AsyncHandler<Void, K> handler, final int expireSeconds, @RpcAttachment final K key, final V value) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
set(expireSeconds, key, value);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void setExpireSeconds(K key, int expireSeconds) {
|
||||
@@ -374,18 +322,6 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> setExpireSeconds(key, expireSeconds), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpireSecondsAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final int expireSeconds) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
setExpireSeconds(key, expireSeconds);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void remove(K key) {
|
||||
@@ -398,18 +334,6 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> remove(key), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
remove(key);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> getCollection(final K key) {
|
||||
return (Collection<V>) get(key);
|
||||
@@ -421,15 +345,14 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCollectionAsync(final AsyncHandler<Collection<V>, K> handler, @RpcAttachment final K key) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
Collection<V> rs = getCollection(key);
|
||||
if (handler != null) handler.completed(rs, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
public long getCollectionSize(final K key) {
|
||||
Collection<V> collection = (Collection<V>) get(key);
|
||||
return collection == null ? 0 : collection.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Long> getCollectionSizeAsync(final K key) {
|
||||
return CompletableFuture.supplyAsync(() -> getCollectionSize(key), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -442,31 +365,19 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.supplyAsync(() -> getCollectionAndRefresh(key, expireSeconds), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCollectionAndRefreshAsync(final AsyncHandler<Collection<V>, K> handler, @RpcAttachment final K key, final int expireSeconds) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
Collection<V> rs = getCollectionAndRefresh(key, expireSeconds);
|
||||
if (handler != null) handler.completed(rs, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void appendListItem(K key, V value) {
|
||||
if (key == null) return;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null || !entry.isListCacheType()) {
|
||||
Collection<V> list = new ConcurrentLinkedQueue<>();
|
||||
entry = new CacheEntry(CacheEntryType.LIST, key, list);
|
||||
if (entry == null || !entry.isListCacheType() || entry.listValue == null) {
|
||||
ConcurrentLinkedQueue<V> list = new ConcurrentLinkedQueue();
|
||||
entry = new CacheEntry(CacheEntryType.LIST, key, null, null, list);
|
||||
CacheEntry old = container.putIfAbsent(key, entry);
|
||||
if (old != null) list = (Collection) old.value;
|
||||
list.add(value);
|
||||
if (old != null) list = old.listValue;
|
||||
if (list != null) list.add(value);
|
||||
} else {
|
||||
((Collection) entry.getValue()).add(value);
|
||||
entry.listValue.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,25 +386,13 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> appendListItem(key, value), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendListItemAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final V value) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
appendListItem(key, value);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void removeListItem(K key, V value) {
|
||||
if (key == null) return;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null || !entry.isListCacheType()) return;
|
||||
((Collection) entry.getValue()).remove(value);
|
||||
if (entry == null || entry.listValue == null) return;
|
||||
entry.listValue.remove(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -501,31 +400,19 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> removeListItem(key, value), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListItemAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final V value) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
removeListItem(key, value);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void appendSetItem(K key, V value) {
|
||||
if (key == null) return;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null || !entry.isSetCacheType()) {
|
||||
Collection<V> set = new CopyOnWriteArraySet();
|
||||
entry = new CacheEntry(CacheEntryType.SET, key, set);
|
||||
if (entry == null || !entry.isSetCacheType() || entry.setValue == null) {
|
||||
CopyOnWriteArraySet<V> set = new CopyOnWriteArraySet();
|
||||
entry = new CacheEntry(CacheEntryType.SET, key, null, set, null);
|
||||
CacheEntry old = container.putIfAbsent(key, entry);
|
||||
if (old != null) set = (Collection) old.value;
|
||||
set.add(value);
|
||||
if (old != null) set = old.setValue;
|
||||
if (set != null) set.add(value);
|
||||
} else {
|
||||
((Collection) entry.getValue()).add(value);
|
||||
entry.setValue.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,25 +421,13 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.runAsync(() -> appendSetItem(key, value), getExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendSetItemAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final V value) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
appendSetItem(key, value);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RpcMultiRun
|
||||
public void removeSetItem(K key, V value) {
|
||||
if (key == null) return;
|
||||
CacheEntry entry = container.get(key);
|
||||
if (entry == null || !(entry.value instanceof Set)) return;
|
||||
((Set) entry.getValue()).remove(value);
|
||||
if (entry == null || entry.setValue == null) return;
|
||||
entry.setValue.remove(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -561,94 +436,23 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSetItemAsync(final AsyncHandler<Void, K> handler, @RpcAttachment final K key, final V value) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
removeSetItem(key, value);
|
||||
if (handler != null) handler.completed(null, key);
|
||||
} catch (Throwable t) {
|
||||
if (handler != null) handler.failed(t, key);
|
||||
}
|
||||
});
|
||||
public List<K> queryKeys() {
|
||||
return new ArrayList<>(container.keySet());
|
||||
}
|
||||
|
||||
public static enum CacheEntryType {
|
||||
OBJECT, SET, LIST;
|
||||
@Override
|
||||
public CompletableFuture<List<CacheEntry<K, Object>>> queryListAsync() {
|
||||
return CompletableFuture.completedFuture(new ArrayList<>(container.values()));
|
||||
}
|
||||
|
||||
public static final class CacheEntry<K extends Serializable, T> {
|
||||
|
||||
public static final String JSON_SET_KEY = "{\"cacheType\":\"" + CacheEntryType.SET + "\"";
|
||||
|
||||
public static final String JSON_LIST_KEY = "{\"cacheType\":\"" + CacheEntryType.LIST + "\"";
|
||||
|
||||
private final CacheEntryType cacheType;
|
||||
|
||||
private final K key;
|
||||
|
||||
//<=0表示永久保存
|
||||
private int expireSeconds;
|
||||
|
||||
private volatile int lastAccessed; //最后刷新时间
|
||||
|
||||
private T value;
|
||||
|
||||
public CacheEntry(CacheEntryType cacheType, K key, T value) {
|
||||
this(cacheType, 0, key, value);
|
||||
}
|
||||
|
||||
public CacheEntry(CacheEntryType cacheType, int expireSeconds, K key, T value) {
|
||||
this(cacheType, expireSeconds, (int) (System.currentTimeMillis() / 1000), key, value);
|
||||
}
|
||||
|
||||
@ConstructorProperties({"cacheType", "expireSeconds", "lastAccessed", "key", "value"})
|
||||
public CacheEntry(CacheEntryType cacheType, int expireSeconds, int lastAccessed, K key, T value) {
|
||||
this.cacheType = cacheType;
|
||||
this.expireSeconds = expireSeconds;
|
||||
this.lastAccessed = lastAccessed;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonFactory.root().getConvert().convertTo(this);
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public boolean isListCacheType() {
|
||||
return cacheType == CacheEntryType.LIST;
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public boolean isSetCacheType() {
|
||||
return cacheType == CacheEntryType.SET;
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public boolean isExpired() {
|
||||
return (expireSeconds > 0 && lastAccessed + expireSeconds < (System.currentTimeMillis() / 1000));
|
||||
}
|
||||
|
||||
public CacheEntryType getCacheType() {
|
||||
return cacheType;
|
||||
}
|
||||
|
||||
public int getExpireSeconds() {
|
||||
return expireSeconds;
|
||||
}
|
||||
|
||||
public int getLastAccessed() {
|
||||
return lastAccessed;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CacheEntry<K, Object>> queryList() {
|
||||
return new ArrayList<>(container.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<K>> queryKeysAsync() {
|
||||
return CompletableFuture.completedFuture(new ArrayList<>(container.keySet()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
*/
|
||||
package org.redkale.source;
|
||||
|
||||
import java.beans.ConstructorProperties;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.redkale.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import org.redkale.convert.ConvertColumn;
|
||||
import org.redkale.convert.json.JsonFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,6 +45,8 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public Collection<V> getCollection(final K key);
|
||||
|
||||
public long getCollectionSize(final K key);
|
||||
|
||||
public Collection<V> getCollectionAndRefresh(final K key, final int expireSeconds);
|
||||
|
||||
public void appendListItem(final K key, final V value);
|
||||
@@ -53,6 +57,10 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public void removeSetItem(final K key, final V value);
|
||||
|
||||
public List<K> queryKeys();
|
||||
|
||||
public List<CacheEntry<K, Object>> queryList();
|
||||
|
||||
//---------------------- CompletableFuture 异步版 ---------------------------------
|
||||
public CompletableFuture<Boolean> existsAsync(final K key);
|
||||
|
||||
@@ -72,6 +80,8 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public CompletableFuture<Collection<V>> getCollectionAsync(final K key);
|
||||
|
||||
public CompletableFuture<Long> getCollectionSizeAsync(final K key);
|
||||
|
||||
public CompletableFuture<Collection<V>> getCollectionAndRefreshAsync(final K key, final int expireSeconds);
|
||||
|
||||
public CompletableFuture<Void> appendListItemAsync(final K key, final V value);
|
||||
@@ -82,55 +92,105 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public CompletableFuture<Void> removeSetItemAsync(final K key, final V value);
|
||||
|
||||
public CompletableFuture<List<K>> queryKeysAsync();
|
||||
|
||||
public CompletableFuture<List<CacheEntry<K, Object>>> queryListAsync();
|
||||
|
||||
default CompletableFuture<Boolean> isOpenAsync() {
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
//---------------------- AsyncHandler 异步版 ---------------------------------
|
||||
@Deprecated
|
||||
public void existsAsync(final AsyncHandler<Boolean, K> handler, final K key);
|
||||
public static enum CacheEntryType {
|
||||
OBJECT, SET, LIST;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void getAsync(final AsyncHandler<V, K> handler, final K key);
|
||||
public static final class CacheEntry<K extends Serializable, T> {
|
||||
|
||||
@Deprecated
|
||||
public void getAndRefreshAsync(final AsyncHandler<V, K> handler, final K key, final int expireSeconds);
|
||||
static final String JSON_SET_KEY = "{\"cacheType\":\"" + CacheEntryType.SET + "\"";
|
||||
|
||||
@Deprecated
|
||||
public void refreshAsync(final AsyncHandler<Void, K> handler, final K key, final int expireSeconds);
|
||||
static final String JSON_LIST_KEY = "{\"cacheType\":\"" + CacheEntryType.LIST + "\"";
|
||||
|
||||
@Deprecated
|
||||
public void setAsync(final AsyncHandler<Void, K> handler, final K key, final V value);
|
||||
final CacheEntryType cacheType;
|
||||
|
||||
@Deprecated
|
||||
public void setAsync(final AsyncHandler<Void, K> handler, final int expireSeconds, final K key, final V value);
|
||||
final K key;
|
||||
|
||||
@Deprecated
|
||||
public void setExpireSecondsAsync(final AsyncHandler<Void, K> handler, final K key, final int expireSeconds);
|
||||
//<=0表示永久保存
|
||||
int expireSeconds;
|
||||
|
||||
@Deprecated
|
||||
public void removeAsync(final AsyncHandler<Void, K> handler, final K key);
|
||||
volatile int lastAccessed; //最后刷新时间
|
||||
|
||||
@Deprecated
|
||||
public void getCollectionAsync(final AsyncHandler<Collection<V>, K> handler, final K key);
|
||||
T objectValue;
|
||||
|
||||
@Deprecated
|
||||
public void getCollectionAndRefreshAsync(final AsyncHandler<Collection<V>, K> handler, final K key, final int expireSeconds);
|
||||
CopyOnWriteArraySet<T> setValue;
|
||||
|
||||
@Deprecated
|
||||
public void appendListItemAsync(final AsyncHandler<Void, K> handler, final K key, final V value);
|
||||
ConcurrentLinkedQueue<T> listValue;
|
||||
|
||||
@Deprecated
|
||||
public void removeListItemAsync(final AsyncHandler<Void, K> handler, final K key, final V value);
|
||||
public CacheEntry(CacheEntryType cacheType, K key, T objectValue, CopyOnWriteArraySet<T> setValue, ConcurrentLinkedQueue<T> listValue) {
|
||||
this(cacheType, 0, key, objectValue, setValue, listValue);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void appendSetItemAsync(final AsyncHandler<Void, K> handler, final K key, final V value);
|
||||
public CacheEntry(CacheEntryType cacheType, int expireSeconds, K key, T objectValue, CopyOnWriteArraySet<T> setValue, ConcurrentLinkedQueue<T> listValue) {
|
||||
this(cacheType, expireSeconds, (int) (System.currentTimeMillis() / 1000), key, objectValue, setValue, listValue);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void removeSetItemAsync(final AsyncHandler<Void, K> handler, final K key, final V value);
|
||||
@ConstructorProperties({"cacheType", "expireSeconds", "lastAccessed", "key", "objectValue", "setValue", "listValue"})
|
||||
public CacheEntry(CacheEntryType cacheType, int expireSeconds, int lastAccessed, K key, T objectValue, CopyOnWriteArraySet<T> setValue, ConcurrentLinkedQueue<T> listValue) {
|
||||
this.cacheType = cacheType;
|
||||
this.expireSeconds = expireSeconds;
|
||||
this.lastAccessed = lastAccessed;
|
||||
this.key = key;
|
||||
this.objectValue = objectValue;
|
||||
this.setValue = setValue;
|
||||
this.listValue = listValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonFactory.root().getConvert().convertTo(this);
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public boolean isListCacheType() {
|
||||
return cacheType == CacheEntryType.LIST;
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public boolean isSetCacheType() {
|
||||
return cacheType == CacheEntryType.SET;
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public boolean isExpired() {
|
||||
return (expireSeconds > 0 && lastAccessed + expireSeconds < (System.currentTimeMillis() / 1000));
|
||||
}
|
||||
|
||||
public CacheEntryType getCacheType() {
|
||||
return cacheType;
|
||||
}
|
||||
|
||||
public int getExpireSeconds() {
|
||||
return expireSeconds;
|
||||
}
|
||||
|
||||
public int getLastAccessed() {
|
||||
return lastAccessed;
|
||||
}
|
||||
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public T getObjectValue() {
|
||||
return objectValue;
|
||||
}
|
||||
|
||||
public CopyOnWriteArraySet<T> getSetValue() {
|
||||
return setValue;
|
||||
}
|
||||
|
||||
public ConcurrentLinkedQueue<T> getListValue() {
|
||||
return listValue;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default void isOpenAsync(final AsyncHandler<Boolean, Void> handler) {
|
||||
if (handler != null) handler.completed(Boolean.TRUE, null);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,15 @@ public final class EntityInfo<T> {
|
||||
//Entity构建器
|
||||
private final Creator<T> creator;
|
||||
|
||||
//Entity构建器参数
|
||||
private final String[] constructorParameters;
|
||||
|
||||
//Entity构建器参数Attribute
|
||||
private final Attribute<T, Serializable>[] constructorAttributes;
|
||||
|
||||
//Entity构建器参数Attribute
|
||||
private final Attribute<T, Serializable>[] unconstructorAttributes;
|
||||
|
||||
//主键
|
||||
final Attribute<T, Serializable> primary;
|
||||
|
||||
@@ -202,6 +211,13 @@ public final class EntityInfo<T> {
|
||||
this.tableStrategy = dts;
|
||||
|
||||
this.creator = Creator.create(type);
|
||||
Creator.ConstructorParameters cp = null;
|
||||
try {
|
||||
cp = this.creator.getClass().getMethod("create", Object[].class).getAnnotation(Creator.ConstructorParameters.class);
|
||||
} catch (Exception e) {
|
||||
logger.severe(type + " cannot find ConstructorParameters Creator", e);
|
||||
}
|
||||
this.constructorParameters = (cp == null || cp.value().length < 1) ? null : cp.value();
|
||||
Attribute idAttr0 = null;
|
||||
Map<String, String> aliasmap0 = null;
|
||||
Class cltmp = type;
|
||||
@@ -271,6 +287,22 @@ public final class EntityInfo<T> {
|
||||
this.queryAttributes = queryattrs.toArray(new Attribute[queryattrs.size()]);
|
||||
this.insertAttributes = insertattrs.toArray(new Attribute[insertattrs.size()]);
|
||||
this.updateAttributes = updateattrs.toArray(new Attribute[updateattrs.size()]);
|
||||
if (this.constructorParameters == null) {
|
||||
this.constructorAttributes = null;
|
||||
this.unconstructorAttributes = null;
|
||||
} else {
|
||||
this.constructorAttributes = new Attribute[this.constructorParameters.length];
|
||||
List<Attribute<T, Serializable>> unconstructorAttrs = new ArrayList<>();
|
||||
for (Attribute<T, Serializable> attr : queryAttributes) {
|
||||
int pos = Arrays.binarySearch(this.constructorParameters, attr.field());
|
||||
if (pos >= 0) {
|
||||
this.constructorAttributes[pos] = attr;
|
||||
} else {
|
||||
unconstructorAttrs.add(attr);
|
||||
}
|
||||
}
|
||||
this.unconstructorAttributes = unconstructorAttrs.toArray(new Attribute[unconstructorAttrs.size()]);
|
||||
}
|
||||
if (table != null) {
|
||||
StringBuilder insertsb = new StringBuilder();
|
||||
StringBuilder insertsb2 = new StringBuilder();
|
||||
@@ -630,61 +662,79 @@ public final class EntityInfo<T> {
|
||||
* @throws SQLException SQLException
|
||||
*/
|
||||
protected T getValue(final SelectColumn sels, final ResultSet set) throws SQLException {
|
||||
T obj = creator.create();
|
||||
for (Attribute<T, Serializable> attr : queryAttributes) {
|
||||
if (sels == null || sels.test(attr.field())) {
|
||||
final Class t = attr.type();
|
||||
Serializable o;
|
||||
if (t == byte[].class) {
|
||||
Blob blob = set.getBlob(this.getSQLColumn(null, attr.field()));
|
||||
if (blob == null) {
|
||||
o = null;
|
||||
} else { //不支持超过2G的数据
|
||||
o = blob.getBytes(1, (int) blob.length());
|
||||
}
|
||||
} else {
|
||||
o = (Serializable) set.getObject(this.getSQLColumn(null, attr.field()));
|
||||
if (t.isPrimitive()) {
|
||||
if (o != null) {
|
||||
if (t == int.class) {
|
||||
o = ((Number) o).intValue();
|
||||
} else if (t == long.class) {
|
||||
o = ((Number) o).longValue();
|
||||
} else if (t == short.class) {
|
||||
o = ((Number) o).shortValue();
|
||||
} else if (t == float.class) {
|
||||
o = ((Number) o).floatValue();
|
||||
} else if (t == double.class) {
|
||||
o = ((Number) o).doubleValue();
|
||||
} else if (t == byte.class) {
|
||||
o = ((Number) o).byteValue();
|
||||
} else if (t == char.class) {
|
||||
o = (char) ((Number) o).intValue();
|
||||
} else if (t == boolean.class) {
|
||||
o = (Boolean) o;
|
||||
}
|
||||
} else if (t == int.class) {
|
||||
o = 0;
|
||||
} else if (t == long.class) {
|
||||
o = 0L;
|
||||
} else if (t == short.class) {
|
||||
o = (short) 0;
|
||||
} else if (t == float.class) {
|
||||
o = 0.0f;
|
||||
} else if (t == double.class) {
|
||||
o = 0.0d;
|
||||
} else if (t == byte.class) {
|
||||
o = (byte) 0;
|
||||
} else if (t == boolean.class) {
|
||||
o = false;
|
||||
} else if (t == char.class) {
|
||||
o = (char) 0;
|
||||
}
|
||||
}
|
||||
T obj;
|
||||
Attribute<T, Serializable>[] attrs = this.queryAttributes;
|
||||
if (this.constructorParameters == null) {
|
||||
obj = creator.create();
|
||||
} else {
|
||||
Object[] cps = new Object[this.constructorParameters.length];
|
||||
for (int i = 0; i < this.constructorAttributes.length; i++) {
|
||||
Attribute<T, Serializable> attr = this.constructorAttributes[i];
|
||||
if (sels == null || sels.test(attr.field())) {
|
||||
cps[i] = getFieldValue(attr, set);
|
||||
}
|
||||
attr.set(obj, o);
|
||||
}
|
||||
obj = creator.create(cps);
|
||||
attrs = this.unconstructorAttributes;
|
||||
}
|
||||
for (Attribute<T, Serializable> attr : attrs) {
|
||||
if (sels == null || sels.test(attr.field())) {
|
||||
attr.set(obj, getFieldValue(attr, set));
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
protected Serializable getFieldValue(Attribute<T, Serializable> attr, final ResultSet set) throws SQLException {
|
||||
final Class t = attr.type();
|
||||
Serializable o;
|
||||
if (t == byte[].class) {
|
||||
Blob blob = set.getBlob(this.getSQLColumn(null, attr.field()));
|
||||
if (blob == null) {
|
||||
o = null;
|
||||
} else { //不支持超过2G的数据
|
||||
o = blob.getBytes(1, (int) blob.length());
|
||||
}
|
||||
} else {
|
||||
o = (Serializable) set.getObject(this.getSQLColumn(null, attr.field()));
|
||||
if (t.isPrimitive()) {
|
||||
if (o != null) {
|
||||
if (t == int.class) {
|
||||
o = ((Number) o).intValue();
|
||||
} else if (t == long.class) {
|
||||
o = ((Number) o).longValue();
|
||||
} else if (t == short.class) {
|
||||
o = ((Number) o).shortValue();
|
||||
} else if (t == float.class) {
|
||||
o = ((Number) o).floatValue();
|
||||
} else if (t == double.class) {
|
||||
o = ((Number) o).doubleValue();
|
||||
} else if (t == byte.class) {
|
||||
o = ((Number) o).byteValue();
|
||||
} else if (t == char.class) {
|
||||
o = (char) ((Number) o).intValue();
|
||||
} else if (t == boolean.class) {
|
||||
o = (Boolean) o;
|
||||
}
|
||||
} else if (t == int.class) {
|
||||
o = 0;
|
||||
} else if (t == long.class) {
|
||||
o = 0L;
|
||||
} else if (t == short.class) {
|
||||
o = (short) 0;
|
||||
} else if (t == float.class) {
|
||||
o = 0.0f;
|
||||
} else if (t == double.class) {
|
||||
o = 0.0d;
|
||||
} else if (t == byte.class) {
|
||||
o = (byte) 0;
|
||||
} else if (t == boolean.class) {
|
||||
o = false;
|
||||
} else if (t == char.class) {
|
||||
o = (char) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,13 @@ public final class FilterNodeBean<T extends FilterBean> implements Comparable<Fi
|
||||
try {
|
||||
getter = cltmp.getMethod(((t == boolean.class || t == Boolean.class) ? "is" : "get") + new String(chars));
|
||||
} catch (Exception ex) {
|
||||
if (!pubmod) continue;
|
||||
if (t == Boolean.class) {
|
||||
try {
|
||||
getter = cltmp.getMethod("get" + new String(chars));
|
||||
} catch (Exception ex2) {
|
||||
if (!pubmod) continue;
|
||||
}
|
||||
} else if (!pubmod) continue;
|
||||
}
|
||||
fields.add(field.getName());
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package org.redkale.source;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.redkale.util.Comment;
|
||||
|
||||
/**
|
||||
* 翻页对象, offset从0开始, limit必须大于0
|
||||
@@ -19,10 +20,13 @@ public final class Flipper implements Serializable, Cloneable {
|
||||
|
||||
public static int DEFAULT_LIMIT = 20;
|
||||
|
||||
@Comment("每页多少行")
|
||||
private int limit = DEFAULT_LIMIT;
|
||||
|
||||
@Comment("记录行的偏移量,从0开始")
|
||||
private int offset = 0;
|
||||
|
||||
@Comment("排序字段, 可多字段排序")
|
||||
private String sort = "";
|
||||
|
||||
public Flipper() {
|
||||
|
||||
30
src/org/redkale/util/Redkale.java
Normal file
30
src/org/redkale/util/Redkale.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.util;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class Redkale {
|
||||
|
||||
private Redkale() {
|
||||
}
|
||||
|
||||
public static String getDotedVersion() {
|
||||
return "1.8.0-rc1";
|
||||
}
|
||||
|
||||
public static int getMajorVersion() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static int getMinorVersion() {
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.*;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
@@ -19,7 +18,7 @@ import javax.annotation.Resource;
|
||||
* 依赖注入功能主类 <br>
|
||||
*
|
||||
* 如果@Resource(name = "$") 表示资源name采用所属对象的name <br>
|
||||
* 如果没有@Resource 则会取对象的getResourceName()方法值(若存在)
|
||||
* 如果没有@Resource且对象实现了Resourcable, 则会取对象的resourceName()方法值
|
||||
* <blockquote><pre>
|
||||
* name规则:
|
||||
* 1: "$"有特殊含义, 不能表示"$"资源本身
|
||||
@@ -41,6 +40,8 @@ public final class ResourceFactory {
|
||||
|
||||
private static final ResourceFactory instance = new ResourceFactory(null);
|
||||
|
||||
private final List<WeakReference<ResourceFactory>> chidren = new CopyOnWriteArrayList<>();
|
||||
|
||||
private final ConcurrentHashMap<Type, ResourceLoader> loadermap = new ConcurrentHashMap();
|
||||
|
||||
private final ConcurrentHashMap<Type, ConcurrentHashMap<String, ResourceEntry>> store = new ConcurrentHashMap();
|
||||
@@ -49,113 +50,301 @@ public final class ResourceFactory {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根ResourceFactory
|
||||
*
|
||||
* @return ResourceFactory
|
||||
*/
|
||||
public static ResourceFactory root() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ResourceFactory子节点
|
||||
*
|
||||
* @return ResourceFactory
|
||||
*/
|
||||
public ResourceFactory createChild() {
|
||||
return new ResourceFactory(this);
|
||||
ResourceFactory child = new ResourceFactory(this);
|
||||
this.chidren.add(new WeakReference<>(child));
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有ResourceFactory子节点
|
||||
*
|
||||
* @return List
|
||||
*/
|
||||
public List<ResourceFactory> getChildren() {
|
||||
List<ResourceFactory> result = new ArrayList<>();
|
||||
for (WeakReference<ResourceFactory> ref : chidren) {
|
||||
ResourceFactory rf = ref.get();
|
||||
if (rf != null) result.add(rf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空当前ResourceFactory注入资源
|
||||
*
|
||||
*/
|
||||
public void release() {
|
||||
this.store.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查资源名是否合法
|
||||
* <blockquote><pre>
|
||||
* name规则:
|
||||
* 1: "$"有特殊含义, 不能表示"$"资源本身
|
||||
* 2: 只能是字母、数字、(短横)-、(下划线)_、点(.)的组合
|
||||
* </pre></blockquote>
|
||||
*
|
||||
* @param name String
|
||||
*/
|
||||
public void checkName(String name) {
|
||||
if (name == null || (!name.isEmpty() && !name.matches("^[a-zA-Z0-9_;\\-\\.\\[\\]\\(\\)]+$"))) {
|
||||
throw new IllegalArgumentException("Resource.name(" + name + ") contains illegal character, must be (a-z,A-Z,0-9,_,.,(,),-,[,])");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象指定类型且name=""注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param clazz 资源类型
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final Class<? extends A> clazz, final A rs) {
|
||||
return register(true, clazz, rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象指定类型且name=""注入到资源池中
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param clazz 资源类型
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final boolean autoSync, final Class<? extends A> clazz, final A rs) {
|
||||
return register(autoSync, "", clazz, rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以name=""注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final A rs) {
|
||||
return register(true, rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以name=""注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final boolean autoSync, final A rs) {
|
||||
if (rs == null) return null;
|
||||
return (A) register(autoSync, "", rs);
|
||||
}
|
||||
|
||||
public void register(final Type clazz, final ResourceLoader rs) {
|
||||
if (clazz == null || rs == null) return;
|
||||
loadermap.put(clazz, rs);
|
||||
}
|
||||
|
||||
public void register(final ResourceLoader rs, final Type... clazzs) {
|
||||
if (clazzs == null || rs == null) return;
|
||||
for (Type clazz : clazzs) {
|
||||
loadermap.put(clazz, rs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将boolean对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final boolean value) {
|
||||
register(true, name, boolean.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将boolean对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final boolean value) {
|
||||
register(autoSync, name, boolean.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将byte对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final byte value) {
|
||||
register(true, name, byte.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将byte对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final byte value) {
|
||||
register(autoSync, name, byte.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将short对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final short value) {
|
||||
register(true, name, short.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将short对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final short value) {
|
||||
register(autoSync, name, short.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将int对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final int value) {
|
||||
register(true, name, int.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将int对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final int value) {
|
||||
register(autoSync, name, int.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将float对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final float value) {
|
||||
register(true, name, float.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将float对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final float value) {
|
||||
register(autoSync, name, float.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将long对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final long value) {
|
||||
register(true, name, long.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将long对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final long value) {
|
||||
register(autoSync, name, long.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将double对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final String name, final double value) {
|
||||
register(true, name, double.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将double对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param value 资源值
|
||||
*
|
||||
*/
|
||||
public void register(final boolean autoSync, final String name, final double value) {
|
||||
register(autoSync, name, double.class, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以指定资源名注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param name 资源名
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final String name, final A rs) {
|
||||
return register(true, name, rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以指定资源名注入到资源池中
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final boolean autoSync, final String name, final A rs) {
|
||||
checkName(name);
|
||||
final Class<?> claz = rs.getClass();
|
||||
@@ -164,22 +353,51 @@ public final class ResourceFactory {
|
||||
return (A) register(autoSync, name, claz, rs);
|
||||
} else {
|
||||
A old = null;
|
||||
for (Class cl : rtype.value()) {
|
||||
A t = (A) register(autoSync, name, cl, rs);
|
||||
if (t != null) old = t;
|
||||
}
|
||||
A t = (A) register(autoSync, name, rtype.value(), rs);
|
||||
if (t != null) old = t;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以指定资源名和类型注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param name 资源名
|
||||
* @param clazz 资源类型
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final String name, final Class<? extends A> clazz, final A rs) {
|
||||
return register(true, name, clazz, rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以指定资源名和类型注入到资源池中,并同步已被注入的资源
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param name 资源名
|
||||
* @param clazz 资源类型
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final String name, final Type clazz, final A rs) {
|
||||
return register(true, name, clazz, rs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以指定资源名和类型注入到资源池中
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param autoSync 是否同步已被注入的资源
|
||||
* @param name 资源名
|
||||
* @param clazz 资源类型
|
||||
* @param rs 资源对象
|
||||
*
|
||||
* @return 旧资源对象
|
||||
*/
|
||||
public <A> A register(final boolean autoSync, final String name, final Type clazz, final A rs) {
|
||||
checkName(name);
|
||||
ConcurrentHashMap<String, ResourceEntry> map = this.store.get(clazz);
|
||||
@@ -201,6 +419,21 @@ public final class ResourceFactory {
|
||||
return re == null ? null : (A) re.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含指定资源名和资源类型的资源对象
|
||||
*
|
||||
* @param <A> 泛型
|
||||
* @param recursive 是否遍历父节点
|
||||
* @param name 资源名
|
||||
* @param clazz 资源类型
|
||||
*
|
||||
* @return 是否存在
|
||||
*/
|
||||
public <A> boolean contains(boolean recursive, String name, Class<? extends A> clazz) {
|
||||
Map<String, ResourceEntry> map = this.store.get(clazz);
|
||||
return map == null ? ((recursive && parent != null) ? parent.contains(recursive, name, clazz) : false) : map.containsKey(name);
|
||||
}
|
||||
|
||||
public <A> A find(Class<? extends A> clazz) {
|
||||
return find("", clazz);
|
||||
}
|
||||
@@ -210,6 +443,23 @@ public final class ResourceFactory {
|
||||
return re == null ? null : (A) re.value;
|
||||
}
|
||||
|
||||
public <A> A find(String name, Class<? extends A> clazz) {
|
||||
ResourceEntry<A> re = findEntry(name, clazz);
|
||||
return re == null ? null : re.value;
|
||||
}
|
||||
|
||||
public <A> A findChild(final String name, final Class<? extends A> clazz) {
|
||||
A rs = find(name, clazz);
|
||||
if (rs != null) return rs;
|
||||
for (Map.Entry<Type, ConcurrentHashMap<String, ResourceEntry>> en : this.store.entrySet()) {
|
||||
if (!(en.getKey() instanceof Class)) continue;
|
||||
if (!clazz.isAssignableFrom((Class) en.getKey())) continue;
|
||||
ResourceEntry v = en.getValue().get(name);
|
||||
if (v != null) return (A) v.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResourceEntry findEntry(String name, Type clazz) {
|
||||
Map<String, ResourceEntry> map = this.store.get(clazz);
|
||||
if (map != null) {
|
||||
@@ -239,11 +489,6 @@ public final class ResourceFactory {
|
||||
return list;
|
||||
}
|
||||
|
||||
public <A> A find(String name, Class<? extends A> clazz) {
|
||||
ResourceEntry<A> re = findEntry(name, clazz);
|
||||
return re == null ? null : re.value;
|
||||
}
|
||||
|
||||
private <A> ResourceEntry<A> findEntry(String name, Class<? extends A> clazz) {
|
||||
Map<String, ResourceEntry> map = this.store.get(clazz);
|
||||
if (map != null) {
|
||||
@@ -254,32 +499,6 @@ public final class ResourceFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
public <A> A findChild(final String name, final Class<? extends A> clazz) {
|
||||
A rs = find(name, clazz);
|
||||
if (rs != null) return rs;
|
||||
for (Map.Entry<Type, ConcurrentHashMap<String, ResourceEntry>> en : this.store.entrySet()) { //不用forEach为兼容JDK 6
|
||||
if (!(en.getKey() instanceof Class)) continue;
|
||||
if (!clazz.isAssignableFrom((Class) en.getKey())) continue;
|
||||
ResourceEntry v = en.getValue().get(name);
|
||||
if (v != null) return (A) v.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private <A> void load(final Pattern reg, Class<? extends A> clazz, final A exclude, final Map<String, A> result) {
|
||||
ConcurrentHashMap<String, ResourceEntry> map = this.store.get(clazz);
|
||||
if (map != null) {
|
||||
for (Map.Entry<String, ResourceEntry> en : map.entrySet()) { // 不用forEach为兼容JDK 6
|
||||
String x = en.getKey();
|
||||
ResourceEntry re = en.getValue();
|
||||
if (re == null) continue;
|
||||
Object y = re.value;
|
||||
if (y != exclude && reg.matcher(x).find() && result.get(x) == null) result.put(x, (A) y);
|
||||
}
|
||||
}
|
||||
if (parent != null) parent.load(reg, clazz, exclude, result);
|
||||
}
|
||||
|
||||
public <T> boolean inject(final Object src) {
|
||||
return inject(src, null);
|
||||
}
|
||||
@@ -334,10 +553,11 @@ public final class ResourceFactory {
|
||||
logger.log(Level.SEVERE, src.getClass().getName() + " not found @Resource on Class or not implements Resourcable");
|
||||
}
|
||||
} else {
|
||||
tname = res.name();
|
||||
tname = tname.replace(RESOURCE_PARENT_NAME, res.name());
|
||||
}
|
||||
|
||||
}
|
||||
boolean autoregnull = true;
|
||||
final String rcname = tname;
|
||||
ResourceEntry re = findEntry(rcname, genctype);
|
||||
if (re == null) {
|
||||
@@ -348,13 +568,32 @@ public final class ResourceFactory {
|
||||
}
|
||||
}
|
||||
if (re == null) {
|
||||
ResourceLoader it = findLoader(field.getGenericType(), field);
|
||||
ResourceLoader it = findLoader(genctype, field);
|
||||
if (it != null) {
|
||||
it.load(this, src, rcname, field, attachment);
|
||||
autoregnull = it.autoNone();
|
||||
re = findEntry(rcname, genctype);
|
||||
}
|
||||
}
|
||||
if (re == null) {
|
||||
if (re == null && genctype != classtype) {
|
||||
re = findEntry(rcname, classtype);
|
||||
if (re == null) {
|
||||
if (rcname.startsWith("property.")) {
|
||||
re = findEntry(rcname, String.class);
|
||||
} else {
|
||||
re = findEntry(rcname, classtype);
|
||||
}
|
||||
}
|
||||
if (re == null) {
|
||||
ResourceLoader it = findLoader(classtype, field);
|
||||
if (it != null) {
|
||||
it.load(this, src, rcname, field, attachment);
|
||||
autoregnull = it.autoNone();
|
||||
re = findEntry(rcname, classtype);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (re == null && autoregnull) {
|
||||
register(rcname, genctype, null); //自动注入null的值
|
||||
re = findEntry(rcname, genctype);
|
||||
}
|
||||
@@ -389,14 +628,22 @@ public final class ResourceFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public void register(final ResourceLoader rs, final Type... clazzs) {
|
||||
if (clazzs == null || rs == null) return;
|
||||
for (Type clazz : clazzs) {
|
||||
loadermap.put(clazz, rs);
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceLoader findMatchLoader(Type ft, Field field) {
|
||||
ResourceLoader it = this.loadermap.get(ft);
|
||||
if (it == null) it = this.loadermap.get(field.getType());
|
||||
if (it == null && field != null) it = this.loadermap.get(field.getType());
|
||||
if (it != null) return it;
|
||||
return parent == null ? null : parent.findMatchLoader(ft, field);
|
||||
}
|
||||
|
||||
private ResourceLoader findRegxLoader(Type ft, Field field) {
|
||||
if (field == null) return null;
|
||||
Class c = field.getType();
|
||||
for (Map.Entry<Type, ResourceLoader> en : this.loadermap.entrySet()) {
|
||||
Type t = en.getKey();
|
||||
@@ -406,7 +653,7 @@ public final class ResourceFactory {
|
||||
return parent == null ? null : parent.findRegxLoader(ft, field);
|
||||
}
|
||||
|
||||
private ResourceLoader findLoader(Type ft, Field field) {
|
||||
public ResourceLoader findLoader(Type ft, Field field) {
|
||||
ResourceLoader it = this.findMatchLoader(ft, field);
|
||||
return it == null ? findRegxLoader(ft, field) : it;
|
||||
}
|
||||
@@ -477,7 +724,7 @@ public final class ResourceFactory {
|
||||
|
||||
private static class ResourceElement<T> {
|
||||
|
||||
private static final HashMap<Class, Method> listenerMethods = new HashMap<>(); //不使用ConcurrentHashMap是因为value不能存null
|
||||
private static final HashMap<String, Method> listenerMethods = new HashMap<>(); //不使用ConcurrentHashMap是因为value不能存null
|
||||
|
||||
public final WeakReference<T> dest;
|
||||
|
||||
@@ -493,24 +740,27 @@ public final class ResourceFactory {
|
||||
this.fieldType = field.getType();
|
||||
Class t = dest.getClass();
|
||||
String tn = t.getName();
|
||||
this.listener = tn.startsWith("java.") || tn.startsWith("javax.") ? null : findListener(t);
|
||||
this.listener = tn.startsWith("java.") || tn.startsWith("javax.") ? null : findListener(t, field.getType());
|
||||
}
|
||||
|
||||
private static synchronized Method findListener(Class clazz) {
|
||||
private static synchronized Method findListener(Class clazz, Class fieldType) {
|
||||
Class loop = clazz;
|
||||
Method m = null;
|
||||
Method m = listenerMethods.get(clazz.getName() + "-" + fieldType.getName());
|
||||
if (m != null) return m;
|
||||
do {
|
||||
for (Method method : loop.getDeclaredMethods()) {
|
||||
if (method.getAnnotation(ResourceListener.class) != null
|
||||
&& method.getParameterCount() == 3
|
||||
&& String.class.isAssignableFrom(method.getParameterTypes()[0])) {
|
||||
&& String.class.isAssignableFrom(method.getParameterTypes()[0])
|
||||
&& method.getParameterTypes()[1] == method.getParameterTypes()[2]
|
||||
&& method.getParameterTypes()[1].isAssignableFrom(fieldType)) {
|
||||
m = method;
|
||||
m.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ((loop = loop.getSuperclass()) != Object.class);
|
||||
listenerMethods.put(clazz, m);
|
||||
listenerMethods.put(clazz.getName() + "-" + fieldType.getName(), m);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@@ -519,6 +769,11 @@ public final class ResourceFactory {
|
||||
public static interface ResourceLoader {
|
||||
|
||||
public void load(ResourceFactory factory, Object src, String resourceName, Field field, Object attachment);
|
||||
|
||||
// 返回true 表示调用ResourceLoader之后资源仍不存在,则会在ResourceFactory里注入默认值null,返回false表示资源不存在下次仍会调用ResourceLoader自行处理
|
||||
default boolean autoNone() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,5 +25,5 @@ import java.lang.annotation.*;
|
||||
@Retention(RUNTIME)
|
||||
public @interface ResourceType {
|
||||
|
||||
Class[] value();
|
||||
Class value();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.*;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import javax.net.ssl.*;
|
||||
|
||||
@@ -108,15 +109,38 @@ public final class Utility {
|
||||
* 将多个key:value对应值组合成一个Map,items长度必须是偶数, 参数个数若是奇数的话,最后一个会被忽略
|
||||
* 类似 JDK9中的 Map.of 方法
|
||||
*
|
||||
* @param <K> 泛型
|
||||
* @param <V> 泛型
|
||||
* @param items 键值对
|
||||
*
|
||||
* @return Map
|
||||
*/
|
||||
public static Map<Object, Object> ofMap(Object... items) {
|
||||
HashMap<Object, Object> map = new LinkedHashMap<>();
|
||||
public static <K, V> Map<K, V> ofMap(Object... items) {
|
||||
HashMap<K, V> map = new LinkedHashMap<>();
|
||||
int len = items.length / 2;
|
||||
for (int i = 0; i < len; i++) {
|
||||
map.put(items[i * 2], items[i * 2 + 1]);
|
||||
map.put((K) items[i * 2], (V) items[i * 2 + 1]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多个Map合并成一个Map
|
||||
*
|
||||
* @param <K> 泛型
|
||||
* @param <V> 泛型
|
||||
* @param maps Map
|
||||
*
|
||||
* @return Map
|
||||
*/
|
||||
public static <K, V> Map<K, V> merge(Map<K, V>... maps) {
|
||||
Map<K, V> map = null;
|
||||
for (Map<K, V> m : maps) {
|
||||
if (map == null) {
|
||||
map = m;
|
||||
} else if (m != null) {
|
||||
map.putAll(m);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -216,6 +240,7 @@ public final class Utility {
|
||||
*/
|
||||
public static <T> T[] append(final T[] array, final T... objs) {
|
||||
if (array == null || array.length == 0) return objs;
|
||||
if (objs == null || objs.length == 0) return array;
|
||||
final T[] news = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length + objs.length);
|
||||
System.arraycopy(array, 0, news, 0, array.length);
|
||||
System.arraycopy(objs, 0, news, array.length, objs.length);
|
||||
@@ -252,6 +277,30 @@ public final class Utility {
|
||||
return news;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将符合条件的元素从数组中删除
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param array 原数组
|
||||
* @param filter Predicate
|
||||
*
|
||||
* @return 新数组
|
||||
*/
|
||||
public static <T> T[] remove(final T[] array, final Predicate filter) {
|
||||
if (array == null || array.length == 0 || filter == null) return array;
|
||||
final T[] news = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length);
|
||||
int index = 0;
|
||||
for (int i = 0; i < news.length; i++) {
|
||||
if (!filter.test(array[i])) {
|
||||
news[index++] = array[i];
|
||||
}
|
||||
}
|
||||
if (index == array.length) return array;
|
||||
final T[] rs = (T[]) Array.newInstance(array.getClass().getComponentType(), index);
|
||||
System.arraycopy(news, 0, rs, 0, index);
|
||||
return rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否包含指定的字符,包含返回true
|
||||
*
|
||||
@@ -856,6 +905,84 @@ public final class Utility {
|
||||
return buffer2;
|
||||
}
|
||||
|
||||
public static String getTypeDescriptor(java.lang.reflect.Type type) {
|
||||
if (type == null) return null;
|
||||
if (type instanceof Class) {
|
||||
Class d = (Class) type;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (true) {
|
||||
if (d.isPrimitive()) {
|
||||
char car;
|
||||
if (d == Integer.TYPE) {
|
||||
car = 'I';
|
||||
} else if (d == Void.TYPE) {
|
||||
car = 'V';
|
||||
} else if (d == Boolean.TYPE) {
|
||||
car = 'Z';
|
||||
} else if (d == Byte.TYPE) {
|
||||
car = 'B';
|
||||
} else if (d == Character.TYPE) {
|
||||
car = 'C';
|
||||
} else if (d == Short.TYPE) {
|
||||
car = 'S';
|
||||
} else if (d == Double.TYPE) {
|
||||
car = 'D';
|
||||
} else if (d == Float.TYPE) {
|
||||
car = 'F';
|
||||
} else /* if (d == Long.TYPE) */ {
|
||||
car = 'J';
|
||||
}
|
||||
return sb.append(car).toString();
|
||||
} else if (d.isArray()) {
|
||||
sb.append('[');
|
||||
d = d.getComponentType();
|
||||
} else {
|
||||
sb.append('L');
|
||||
String name = d.getName();
|
||||
int len = name.length();
|
||||
for (int i = 0; i < len; ++i) {
|
||||
char car = name.charAt(i);
|
||||
sb.append(car == '.' ? '/' : car);
|
||||
}
|
||||
return sb.append(';').toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type instanceof ParameterizedType) {// 例如: Map<String, Serializable>
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String raw = getTypeDescriptor(pt.getRawType());
|
||||
sb.append(raw.substring(0, raw.length() - 1)).append('<');
|
||||
for (java.lang.reflect.Type item : pt.getActualTypeArguments()) {
|
||||
sb.append(getTypeDescriptor(item));
|
||||
}
|
||||
return sb.append(">;").toString();
|
||||
}
|
||||
if (type instanceof WildcardType) { // 例如: <? extends Serializable>
|
||||
final WildcardType wt = (WildcardType) type;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
java.lang.reflect.Type[] us = wt.getUpperBounds();
|
||||
java.lang.reflect.Type[] ls = wt.getLowerBounds();
|
||||
if (ls.length < 1) {
|
||||
if (us.length == 1 && us[0] == Object.class) {
|
||||
sb.append('*');
|
||||
} else {
|
||||
for (java.lang.reflect.Type f : us) {
|
||||
sb.append('+');
|
||||
sb.append(getTypeDescriptor(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (java.lang.reflect.Type f : ls) {
|
||||
sb.append('-');
|
||||
sb.append(getTypeDescriptor(f));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
//TypeVariable 不支持
|
||||
return null;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
public static javax.net.ssl.SSLContext getDefaultSSLContext() {
|
||||
return DEFAULTSSL_CONTEXT;
|
||||
@@ -1149,23 +1276,41 @@ public final class Utility {
|
||||
return read(in, "UTF-8");
|
||||
}
|
||||
|
||||
public static String readThenClose(InputStream in) throws IOException {
|
||||
return read(in, "UTF-8", true);
|
||||
}
|
||||
|
||||
public static String read(InputStream in, String charsetName) throws IOException {
|
||||
return read(in, charsetName, false);
|
||||
}
|
||||
|
||||
private static String read(InputStream in, String charsetName, boolean close) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
byte[] bytes = new byte[1024];
|
||||
int pos;
|
||||
while ((pos = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, pos);
|
||||
}
|
||||
if (close) in.close();
|
||||
return charsetName == null ? out.toString() : out.toString(charsetName);
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream readStream(InputStream in) throws IOException {
|
||||
return readStream(in, false);
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream readStreamThenClose(InputStream in) throws IOException {
|
||||
return readStream(in, true);
|
||||
}
|
||||
|
||||
private static ByteArrayOutputStream readStream(InputStream in, boolean close) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
byte[] bytes = new byte[1024];
|
||||
int pos;
|
||||
while ((pos = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, pos);
|
||||
}
|
||||
if (close) in.close();
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1173,17 +1318,6 @@ public final class Utility {
|
||||
return readStream(in).toByteArray();
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream readStreamThenClose(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
byte[] bytes = new byte[1024];
|
||||
int pos;
|
||||
while ((pos = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, pos);
|
||||
}
|
||||
in.close();
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] readBytesThenClose(InputStream in) throws IOException {
|
||||
return readStreamThenClose(in).toByteArray();
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class WatchFactory {
|
||||
|
||||
private static final WatchFactory instance = new WatchFactory(null);
|
||||
|
||||
private final List<WeakReference<WatchNode>> beans = new CopyOnWriteArrayList<>();
|
||||
|
||||
private final WatchFactory parent;
|
||||
|
||||
private WatchFactory(WatchFactory parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void register(WatchNode bean) {
|
||||
if (bean != null) beans.add(new WeakReference<>(bean));
|
||||
}
|
||||
|
||||
public static WatchFactory root() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public WatchFactory createChild() {
|
||||
return new WatchFactory(this);
|
||||
}
|
||||
|
||||
public WatchNumber createWatchNumber(String name) {
|
||||
return createWatchNumber(name, "", false, 0);
|
||||
}
|
||||
|
||||
public WatchNumber createWatchNumber(String name, boolean interval) {
|
||||
return createWatchNumber(name, "", interval, 0);
|
||||
}
|
||||
|
||||
public WatchNumber createWatchNumber(String name, String description) {
|
||||
return createWatchNumber(name, description, false, 0);
|
||||
}
|
||||
|
||||
public WatchNumber createWatchNumber(String name, String description, long v) {
|
||||
return createWatchNumber(name, description, false, 0);
|
||||
}
|
||||
|
||||
public WatchNumber createWatchNumber(String name, String description, boolean interval) {
|
||||
return createWatchNumber(name, description, interval, 0);
|
||||
}
|
||||
|
||||
public WatchNumber createWatchNumber(String name, String description, boolean interval, long v) {
|
||||
WatchNumber bean = new WatchNumber(name, description, interval, v);
|
||||
register(bean);
|
||||
return bean;
|
||||
}
|
||||
|
||||
public void register(String name, LongSupplier supplier) {
|
||||
register(name, "", supplier);
|
||||
}
|
||||
|
||||
public void register(String name, String description, LongSupplier supplier) {
|
||||
register(new WatchSupplier(name, description, supplier));
|
||||
}
|
||||
|
||||
public boolean inject(final Object src) {
|
||||
return inject(src, new ArrayList<>());
|
||||
}
|
||||
|
||||
private boolean inject(final Object src, final List<Object> list) {
|
||||
if (src == null) return false;
|
||||
try {
|
||||
list.add(src);
|
||||
Class clazz = src.getClass();
|
||||
do {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (Modifier.isFinal(field.getModifiers())) continue;
|
||||
field.setAccessible(true);
|
||||
final Class type = field.getType();
|
||||
Watchable wo = field.getAnnotation(Watchable.class);
|
||||
|
||||
}
|
||||
} while ((clazz = clazz.getSuperclass()) != Object.class);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,14 @@
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import org.redkale.net.http.HttpFilter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
public interface WatchNode {
|
||||
public abstract class WatchFilter extends HttpFilter {
|
||||
|
||||
public String getName();
|
||||
|
||||
public String getDescription();
|
||||
|
||||
public long getValue();
|
||||
|
||||
public boolean isInterval();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import java.beans.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class WatchNumber extends AtomicLong implements WatchNode {
|
||||
|
||||
private final boolean interval;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String description;
|
||||
|
||||
@ConstructorProperties({"name", "description", "interval", "value"})
|
||||
protected WatchNumber(String name, String description, boolean interval, long value) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.interval = interval;
|
||||
this.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getValue() {
|
||||
return super.longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
}
|
||||
20
src/org/redkale/watch/WatchService.java
Normal file
20
src/org/redkale/watch/WatchService.java
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import org.redkale.service.*;
|
||||
|
||||
/**
|
||||
* 只给WATCH协议的Server才能加载的Service,其他协议的Server均不能自动加载WatchService
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public interface WatchService extends Service {
|
||||
|
||||
}
|
||||
19
src/org/redkale/watch/WatchServlet.java
Normal file
19
src/org/redkale/watch/WatchServlet.java
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import org.redkale.net.http.HttpServlet;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class WatchServlet extends HttpServlet {
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class WatchSupplier implements WatchNode {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final LongSupplier supplier;
|
||||
|
||||
WatchSupplier(String name, String description, LongSupplier supplier) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getValue() {
|
||||
return supplier == null ? Long.MIN_VALUE : supplier.getAsLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterval() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.redkale.watch;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 该注解只能放在field类型为Collection, Map, 或者java.util.concurrent.atomic.AtomicXXX的Number类);
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface Watchable {
|
||||
|
||||
String name();
|
||||
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* 该值指明是不是只收集阶段数据, 而且被注解的字段只能被赋予java.util.concurrent.atomic.AtomicXXX的Number类型字段。
|
||||
* 例如收集每分钟的注册用户数, 就需要将interval设置true。
|
||||
*
|
||||
* @return 是否收集
|
||||
*/
|
||||
boolean interval() default false;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user