Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b3658143a | ||
|
|
6d69ff546b | ||
|
|
9555e3c9b9 | ||
|
|
744634dbdd | ||
|
|
de5ee844c4 | ||
|
|
ae73cee357 | ||
|
|
d1cf9be8d7 | ||
|
|
43ae77ab33 | ||
|
|
182a75cfad | ||
|
|
222dc0edce | ||
|
|
c7a81513fe | ||
|
|
4931c66868 | ||
|
|
fcff1c3a4b | ||
|
|
2005bf7e3b | ||
|
|
cb07a38f04 | ||
|
|
6085cd5eef | ||
|
|
086275c135 | ||
|
|
a449a96ef9 | ||
|
|
bc3209a09c | ||
|
|
63d1ef985d | ||
|
|
24505564c8 | ||
|
|
93a7bd63cf | ||
|
|
0b9b5baa49 | ||
|
|
5c4100e762 | ||
|
|
eb861014c4 | ||
|
|
e62f7ea63d | ||
|
|
d6df2055b2 | ||
|
|
570aac947a | ||
|
|
1fdc33b565 | ||
|
|
af8d0e978e | ||
|
|
c58022a81e | ||
|
|
f4cf828993 | ||
|
|
c4dc0de5fe | ||
|
|
44507a97a6 | ||
|
|
f4a7f1cff6 | ||
|
|
a5fcb45a88 | ||
|
|
bc8b68526d | ||
|
|
180f201dc0 | ||
|
|
9ab315a405 | ||
|
|
27b4742b6d | ||
|
|
702220d18e | ||
|
|
414489da8e | ||
|
|
77057df25d | ||
|
|
2f98cd1ab5 | ||
|
|
8809fe8ec9 | ||
|
|
f9702a9517 | ||
|
|
29e46b9b68 | ||
|
|
f838e35413 | ||
|
|
f3bb77c49b | ||
|
|
12fa033e15 | ||
|
|
f4abfafea2 | ||
|
|
0918af71d2 | ||
|
|
275befa330 | ||
|
|
ab4cd8bcb6 | ||
|
|
36c109b32f | ||
|
|
73a915665d | ||
|
|
bd6d71c94a | ||
|
|
842e93507c | ||
|
|
76df1108d7 | ||
|
|
941d09cde2 | ||
|
|
9dd3e1da07 | ||
|
|
2bf73245ec | ||
|
|
b0ab792f72 | ||
|
|
0df9a940c5 | ||
|
|
fc35fc5abc | ||
|
|
eaa0a99933 | ||
|
|
83bdb97842 | ||
|
|
a71a4d0fed | ||
|
|
835435c220 | ||
|
|
c524ba1797 | ||
|
|
f254b48693 | ||
|
|
922697eb4d | ||
|
|
450e3e3ea2 | ||
|
|
b31f75f4f6 | ||
|
|
1d1f18b046 | ||
|
|
73f942746b | ||
|
|
b6cefe8c2d | ||
|
|
ebc0e4eb41 | ||
|
|
179a7b22ea | ||
|
|
0b87d9a261 | ||
|
|
685a686ead | ||
|
|
568e1cf62d | ||
|
|
1d121bd2ab | ||
|
|
2ca4bdaaec | ||
|
|
878fda30f6 | ||
|
|
abb611382c | ||
|
|
b53510a26f | ||
|
|
2aee84d477 | ||
|
|
0ba2e25f2e | ||
|
|
98e8a7eb05 | ||
|
|
62139efca9 | ||
|
|
2b62cbe455 | ||
|
|
e44602fe3b | ||
|
|
276cb4da92 | ||
|
|
7081f94afc | ||
|
|
2d6cefeb43 | ||
|
|
ea6c703ac6 | ||
|
|
d1f14962fd | ||
|
|
79ca63bf81 | ||
|
|
6421bc2851 | ||
|
|
43f9f50f4c | ||
|
|
5f140a8ce9 | ||
|
|
c0f8cdf902 | ||
|
|
8d66b1b4a7 | ||
|
|
8c7ee4136c | ||
|
|
d9498c9a6c | ||
|
|
08060a8c86 | ||
|
|
2bdf0e4a50 | ||
|
|
eb184df100 | ||
|
|
cf545a731c | ||
|
|
95e18dfd48 | ||
|
|
023a9abdef | ||
|
|
7f3776c224 | ||
|
|
7b15ba33e0 | ||
|
|
a6c105d63d | ||
|
|
10e22b0873 | ||
|
|
53a35e6397 | ||
|
|
b5a3c39f4f | ||
|
|
99381d4842 | ||
|
|
5f2c2a9f2c | ||
|
|
dc3f318949 | ||
|
|
cfae61faea | ||
|
|
dd7626b1a3 | ||
|
|
2171aa1232 | ||
|
|
801ad489d2 | ||
|
|
c88d0b402d | ||
|
|
542bb4353b | ||
|
|
fe1e0a845a | ||
|
|
f320f4c550 |
@@ -22,3 +22,7 @@
|
||||
由于RedKale使用了JDK 8 内置的ASM包,所以需要在源码工程中的编译器选项中加入: <b>-XDignore.symbol.file=true</b>
|
||||
|
||||
<h5>详情请访问: <a href='https://redkale.org' target='_blank'>https://redkale.org</a></h5>
|
||||
|
||||
<h5>基本文档: <a href='https://redkale.org/articles.html' target='_blank'>https://redkale.org/articles.html</a></h5>
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
<directory>${project.basedir}/conf</directory>
|
||||
<outputDirectory>conf</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/libs</directory>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/logs</directory>
|
||||
<outputDirectory>logs</outputDirectory>
|
||||
|
||||
@@ -4,7 +4,7 @@ export LC_ALL="zh_CN.UTF-8"
|
||||
|
||||
APP_HOME=`dirname "$0"`
|
||||
|
||||
if [ ! -a "$APP_HOME"/conf/application.xml ]; then
|
||||
if [ ! -f "$APP_HOME"/conf/application.xml ]; then
|
||||
APP_HOME="$APP_HOME"/..
|
||||
fi
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ export LC_ALL="zh_CN.UTF-8"
|
||||
|
||||
APP_HOME=`dirname "$0"`
|
||||
|
||||
if [ ! -a "$APP_HOME"/conf/application.xml ]; then
|
||||
if [ ! -f "$APP_HOME"/conf/application.xml ]; then
|
||||
APP_HOME="$APP_HOME"/..
|
||||
fi
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@ SET APP_HOME=%~dp0
|
||||
IF NOT EXIST "%APP_HOME%\conf\application.xml" SET APP_HOME=%~dp0..
|
||||
|
||||
java -DAPP_HOME=%APP_HOME% -classpath %APP_HOME%\lib\* org.redkale.boot.Application
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export LC_ALL="zh_CN.UTF-8"
|
||||
|
||||
APP_HOME=`dirname "$0"`
|
||||
|
||||
if [ ! -a "$APP_HOME"/conf/application.xml ]; then
|
||||
if [ ! -f "$APP_HOME"/conf/application.xml ]; then
|
||||
APP_HOME="$APP_HOME"/..
|
||||
fi
|
||||
|
||||
|
||||
@@ -5,19 +5,26 @@
|
||||
<!-- 详细配置说明见: http://redkale.org/redkale.html#redkale_confxml -->
|
||||
|
||||
<resources>
|
||||
<!--
|
||||
<properties>
|
||||
<property name="system.property.convert.json.tiny" value="true"/>
|
||||
</properties>
|
||||
-->
|
||||
|
||||
</resources>
|
||||
|
||||
<server protocol="HTTP" host="0.0.0.0" port="6060" root="root">
|
||||
|
||||
<request>
|
||||
<remoteaddr value="request.headers.X-RemoteAddress"/>
|
||||
</request>
|
||||
|
||||
<response>
|
||||
<defcookie domain="" path=""/>
|
||||
<addheader name="Access-Control-Allow-Origin" value="request.headers.Origin" />
|
||||
<setheader name="Access-Control-Allow-Credentials" value="true"/>
|
||||
</response>
|
||||
|
||||
<services autoload="true"/>
|
||||
|
||||
<!-- base指定的自定义HttpServlet子类必须标记@HttpUserType, 不设置base则视为没有当前用户信息设置 -->
|
||||
<rest path="/pipes" base="org.redkale.net.http.HttpServlet"/>
|
||||
<filters autoload="true"/>
|
||||
|
||||
<rest path="/pipes" /> <!-- base指定的自定义HttpServlet子类必须标记@HttpUserType, 不设置base则视为没有当前用户信息设置 -->
|
||||
|
||||
<servlets path="/pipes" autoload="true" />
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ 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.unusual = ${APP_HOME}/logs-%m/log-warnerr-%d.log
|
||||
java.util.logging.FileHandler.append = true
|
||||
|
||||
java.util.logging.ConsoleHandler.level = FINER
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<persistence-unit name="user.read" transaction-type="RESOURCE_LOCAL">
|
||||
<shared-cache-mode>ALL</shared-cache-mode>
|
||||
<properties>
|
||||
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/user?autoReconnect=true&characterEncoding=utf8"/>
|
||||
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
|
||||
<property name="javax.persistence.jdbc.user" value="root"/>
|
||||
<property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
|
||||
<property name="javax.persistence.jdbc.driver" value="oracle.jdbc.driver.OracleDriver"/>
|
||||
<property name="javax.persistence.jdbc.user" value="system"/>
|
||||
<property name="javax.persistence.jdbc.password" value="1234"/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<EFBFBD><EFBFBD><EFBFBD>н<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>jarĬ<EFBFBD>Ϸ<EFBFBD><EFBFBD>ڴ˴<EFBFBD>
|
||||
<EFBFBD><EFBFBD><EFBFBD>н<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>jarĬ<EFBFBD>Ϸ<EFBFBD><EFBFBD>ڴ˴<EFBFBD>
|
||||
1
libs/readme.txt
Normal file
1
libs/readme.txt
Normal file
@@ -0,0 +1 @@
|
||||
<EFBFBD>Լ<EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD>jarĬ<EFBFBD>Ϸ<EFBFBD><EFBFBD>ڴ˴<EFBFBD>
|
||||
@@ -37,6 +37,7 @@
|
||||
threads: 线程总数, 默认: <group>节点数*CPU核数*8
|
||||
bufferCapacity: ByteBuffer的初始化大小, 默认: 8K;
|
||||
bufferPoolSize: ByteBuffer池的大小,默认: <group>节点数*CPU核数*8
|
||||
strategy: 远程请求的负载均衡策略, 必须是org.redkale.net.TransportStrategy的实现类
|
||||
-->
|
||||
<transport bufferCapacity="8K" bufferPoolSize="32" threads="32"/>
|
||||
|
||||
@@ -96,12 +97,12 @@
|
||||
|
||||
</resources>
|
||||
<!--
|
||||
protocol: required server所启动的协议,Redkale内置的有HTTP、SNCP、WATCH,SNCP使用TCP实现;
|
||||
protocol: required server所启动的协议,Redkale内置的有HTTP、SNCP、WATCH。协议均使用TCP实现; WATCH服务只能存在一个。
|
||||
name: 服务的名称,用于监控识别,一个配置文件中的server.name不能重复,命名规则: 字母、数字、下划线
|
||||
host: 服务所占address , 默认: 0.0.0.0
|
||||
port: required 服务所占端口
|
||||
root: 如果是web类型服务,则包含页面 默认:{APP_HOME}/root
|
||||
lib: server额外的class目录, 默认为空
|
||||
lib: server额外的class目录, 默认为${APP_HOME}/libs/*;
|
||||
excludelibs: 排除lib.path与excludes中的正则表达式匹配的路径, 多个正则表达式用分号;隔开
|
||||
charset: 文本编码, 默认: UTF-8
|
||||
backlog: 默认10K
|
||||
@@ -119,8 +120,7 @@
|
||||
<!--
|
||||
加载所有的Service服务;
|
||||
在同一个进程中同一个name同一类型的Service将共用同一个实例
|
||||
autoload="true" 默认值. 自动加载以下目录(如果存在的话)下所有的Service类:
|
||||
server.lib; server.lib/*; server.classes;
|
||||
autoload="true" 默认值. 自动加载classpath下所有的Service类
|
||||
autoload="false" 需要显著的指定Service类
|
||||
includes: 当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
excludes: 当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
@@ -135,6 +135,7 @@
|
||||
<!--
|
||||
name: 显式指定name,覆盖默认的空字符串值。 注意: name不能包含$符号。
|
||||
groups: 显式指定groups,覆盖<services>节点的groups默认值。
|
||||
ignore: 是否禁用, 默认为false。
|
||||
-->
|
||||
<service value="com.xxx.XXX2Service" name="" groups="xxx;yyy"/>
|
||||
<!-- 给Service增加配置属性 -->
|
||||
@@ -154,6 +155,11 @@
|
||||
-->
|
||||
<filters autoload="true" includes="" excludes="">
|
||||
|
||||
<!--
|
||||
显著加载指定的Filter类
|
||||
value=: Filter类名。必须与Server的协议层相同,HTTP必须是HttpFilter
|
||||
ignore: 是否禁用, 默认为false。
|
||||
-->
|
||||
<!-- 显著加载指定的Filter类 -->
|
||||
<filter value="com.xxx.XXX1Filter"/>
|
||||
|
||||
@@ -205,11 +211,14 @@
|
||||
如果addheader、setheader 的value值以request.parameters.开头则表示从request.parameters中获取对应的parameter值
|
||||
如果addheader、setheader 的value值以request.headers.开头则表示从request.headers中获取对应的header值
|
||||
例如下面例子是在Response输出header时添加两个header(一个addHeader, 一个setHeader)。
|
||||
options 节点: 设置了该节点却auto=true,当request的method=OPTIONS自动设置addheader、setheader并返回200状态码
|
||||
-->
|
||||
<response>
|
||||
<defcookie domain="" path=""/>
|
||||
<addheader name="Access-Control-Allow-Origin" value="request.headers.Origin" />
|
||||
<setheader name="Access-Control-Allow-Headers" value="request.headers.Access-Control-Request-Headers"/>
|
||||
<setheader name="Access-Control-Allow-Credentials" value="true"/>
|
||||
<options auto="true" />
|
||||
</response>
|
||||
|
||||
<!--
|
||||
@@ -240,14 +249,17 @@
|
||||
<!--
|
||||
加载所有的Servlet服务;
|
||||
path: servlet的ContextPath前缀 默认为空
|
||||
autoload="true" 默认值. 自动加载以下目录(如果存在的话)下所有的Servlet类:
|
||||
${APP_HOME}/lib; ${APP_HOME}/root/lib/*; ${APP_HOME}/root/classes;
|
||||
autoload="true" 默认值. 自动加载classpath下所有的Servlet类
|
||||
autoload="false" 需要显著的指定Service类
|
||||
includes: 当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
excludes: 当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
|
||||
-->
|
||||
<servlets path="/pipes" autoload="true" includes="" excludes="">
|
||||
<!-- 显著加载指定的Servlet -->
|
||||
<!--
|
||||
显著加载指定的Servlet类
|
||||
value=: Servlet类名。必须与Server的协议层相同,HTTP必须是HttpServlet
|
||||
ignore: 是否禁用, 默认为false。
|
||||
-->
|
||||
<servlet value="com.xxx.XXX1Servlet" />
|
||||
<servlet value="com.xxx.XXX2Servlet" />
|
||||
<servlet value="com.xxx.XXX3Servlet" >
|
||||
|
||||
@@ -15,9 +15,9 @@ com.sun.level = INFO
|
||||
java.util.logging.FileHandler.limit = 10485760
|
||||
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.pattern = ${APP_HOME}/logs-%m/log-%d.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-warnerr-%u.log
|
||||
java.util.logging.FileHandler.unusual = ${APP_HOME}/logs-%m/log-warnerr-%d.log
|
||||
java.util.logging.FileHandler.append = true
|
||||
|
||||
#java.util.logging.ConsoleHandler.level = FINE
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
<persistence>
|
||||
<!-- 系统基本库 -->
|
||||
<persistence-unit name="demouser">
|
||||
<!-- 为NONE表示不启动缓存,@Cacheable 失效; 非NONE值(通常用ALL)表示开启缓存。 -->
|
||||
<shared-cache-mode>NONE</shared-cache-mode>
|
||||
<properties>
|
||||
<!--
|
||||
DataSource的实现类,没有设置默认为org.redkale.source.DataJdbcSource的实现,使用常规基于JDBC的数据库驱动一般无需设置
|
||||
-->
|
||||
<property name="javax.persistence.datasource" value="org.redkale.source.DataJdbcSource"/>
|
||||
<!--
|
||||
是否开启缓存(标记为@Cacheable的Entity类),值目前只支持两种: ALL: 所有开启缓存。 NONE: 关闭所有缓存
|
||||
-->
|
||||
<property name="javax.persistence.cachemode" value="ALL"/>
|
||||
|
||||
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbuser?characterEncoding=utf8"/>
|
||||
<!--
|
||||
@@ -42,7 +44,6 @@
|
||||
</persistence-unit>
|
||||
<!-- IM消息库 -->
|
||||
<persistence-unit name="demoim">
|
||||
<shared-cache-mode>NONE</shared-cache-mode>
|
||||
<properties>
|
||||
<!-- jdbc:mysql://127.0.0.1:3306/dbim?autoReconnect=true&autoReconnectForPools=true&characterEncoding=utf8 -->
|
||||
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbim?characterEncoding=utf8"/>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import org.redkale.util.RedkaleClassLoader;
|
||||
import org.redkale.net.TransportGroupInfo;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
@@ -19,6 +20,7 @@ import java.util.logging.*;
|
||||
import javax.annotation.Resource;
|
||||
import javax.xml.parsers.*;
|
||||
import org.redkale.boot.ClassFilter.FilterEntry;
|
||||
import org.redkale.convert.Convert;
|
||||
import org.redkale.convert.bson.BsonFactory;
|
||||
import org.redkale.convert.json.JsonFactory;
|
||||
import org.redkale.net.*;
|
||||
@@ -136,7 +138,10 @@ public final class Application {
|
||||
private final CountDownLatch serversLatch;
|
||||
|
||||
//根ClassLoader
|
||||
private final NodeClassLoader classLoader;
|
||||
private final RedkaleClassLoader classLoader;
|
||||
|
||||
//Server根ClassLoader
|
||||
private final RedkaleClassLoader serverClassLoader;
|
||||
|
||||
private Application(final AnyValue config) {
|
||||
this(false, config);
|
||||
@@ -227,12 +232,14 @@ public final class Application {
|
||||
}
|
||||
this.logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
this.serversLatch = new CountDownLatch(config.getAnyValues("server").length + 1);
|
||||
this.classLoader = new RedkaleClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
logger.log(Level.INFO, "------------------------------- Redkale " + Redkale.getDotedVersion() + " -------------------------------");
|
||||
//------------------配置 <transport> 节点 ------------------
|
||||
ObjectPool<ByteBuffer> transportPool = null;
|
||||
ExecutorService transportExec = null;
|
||||
AsynchronousChannelGroup transportGroup = null;
|
||||
final AnyValue resources = config.getAnyValue("resources");
|
||||
TransportStrategy strategy = null;
|
||||
if (resources != null) {
|
||||
AnyValue transportConf = resources.getAnyValue("transport");
|
||||
int groupsize = resources.getAnyValues("group").length;
|
||||
@@ -241,9 +248,9 @@ public final class Application {
|
||||
//--------------transportBufferPool-----------
|
||||
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);
|
||||
final int bufferCapacity = Math.max(parseLenth(transportConf.getValue("bufferCapacity"), 8 * 1024), 4 * 1024);
|
||||
final int bufferPoolSize = parseLenth(transportConf.getValue("bufferPoolSize"), groupsize * Runtime.getRuntime().availableProcessors() * 8);
|
||||
final int threads = parseLenth(transportConf.getValue("threads"), groupsize * Runtime.getRuntime().availableProcessors() * 8);
|
||||
transportPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, bufferPoolSize,
|
||||
(Object... params) -> ByteBuffer.allocateDirect(bufferCapacity), null, (e) -> {
|
||||
if (e == null || e.isReadOnly() || e.capacity() != bufferCapacity) return false;
|
||||
@@ -252,6 +259,10 @@ public final class Application {
|
||||
});
|
||||
//-----------transportChannelGroup--------------
|
||||
try {
|
||||
final String strategyClass = transportConf.getValue("strategy");
|
||||
if (strategyClass != null && !strategyClass.isEmpty()) {
|
||||
strategy = (TransportStrategy) classLoader.loadClass(strategyClass).newInstance();
|
||||
}
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
transportExec = Executors.newFixedThreadPool(threads, (Runnable r) -> {
|
||||
Thread t = new Thread(r);
|
||||
@@ -266,9 +277,9 @@ public final class Application {
|
||||
logger.log(Level.INFO, Transport.class.getSimpleName() + " configure bufferCapacity = " + bufferCapacity + "; bufferPoolSize = " + bufferPoolSize + "; threads = " + threads + ";");
|
||||
}
|
||||
}
|
||||
this.transportFactory = new TransportFactory(transportExec, transportPool, transportGroup);
|
||||
this.classLoader = new NodeClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
this.transportFactory = new TransportFactory(transportExec, transportPool, transportGroup, strategy);
|
||||
Thread.currentThread().setContextClassLoader(this.classLoader);
|
||||
this.serverClassLoader = new RedkaleClassLoader(this.classLoader);
|
||||
}
|
||||
|
||||
public ResourceFactory getResourceFactory() {
|
||||
@@ -279,10 +290,14 @@ public final class Application {
|
||||
return transportFactory;
|
||||
}
|
||||
|
||||
public NodeClassLoader getNodeClassLoader() {
|
||||
public RedkaleClassLoader getClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
public RedkaleClassLoader getServerClassLoader() {
|
||||
return serverClassLoader;
|
||||
}
|
||||
|
||||
public List<NodeServer> getNodeServers() {
|
||||
return new ArrayList<>(servers);
|
||||
}
|
||||
@@ -295,6 +310,10 @@ public final class Application {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public AnyValue getAppConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void init() throws Exception {
|
||||
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "" + Runtime.getRuntime().availableProcessors() * 4);
|
||||
System.setProperty("convert.bson.tiny", "true");
|
||||
@@ -310,7 +329,7 @@ public final class Application {
|
||||
logger.log(Level.INFO, RESNAME_APP_HOME + "= " + homepath + "\r\n" + RESNAME_APP_ADDR + "= " + this.localAddress.getHostAddress());
|
||||
String lib = config.getValue("lib", "").trim().replace("${APP_HOME}", homepath);
|
||||
lib = lib.isEmpty() ? (homepath + "/conf") : (lib + ";" + homepath + "/conf");
|
||||
Server.loadLib(logger, lib);
|
||||
Server.loadLib(classLoader, logger, lib);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
final AnyValue resources = config.getAnyValue("resources");
|
||||
@@ -354,6 +373,8 @@ public final class Application {
|
||||
this.resourceFactory.register(JsonFactory.root());
|
||||
this.resourceFactory.register(BsonFactory.root().getConvert());
|
||||
this.resourceFactory.register(JsonFactory.root().getConvert());
|
||||
this.resourceFactory.register("bsonconvert", Convert.class, BsonFactory.root().getConvert());
|
||||
this.resourceFactory.register("jsonconvert", Convert.class, JsonFactory.root().getConvert());
|
||||
//只有WatchService才能加载Application、WatchFactory
|
||||
final Application application = this;
|
||||
this.resourceFactory.register(new ResourceFactory.ResourceLoader() {
|
||||
@@ -363,10 +384,12 @@ public final class Application {
|
||||
try {
|
||||
Resource res = field.getAnnotation(Resource.class);
|
||||
if (res == null) return;
|
||||
if (!(src instanceof WatchService) || Sncp.isRemote((Service) src)) return; //远程模式不得注入
|
||||
if (Sncp.isRemote((Service) src)) return; //远程模式不得注入
|
||||
Class type = field.getType();
|
||||
if (type == Application.class) {
|
||||
field.set(src, application);
|
||||
} else if (type == ResourceFactory.class) {
|
||||
field.set(src, res.name().equalsIgnoreCase("server") ? rf : (res.name().isEmpty() ? application.resourceFactory : null));
|
||||
} else if (type == TransportFactory.class) {
|
||||
field.set(src, application.transportFactory);
|
||||
} else if (type == NodeSncpServer.class) {
|
||||
@@ -413,7 +436,7 @@ public final class Application {
|
||||
return false;
|
||||
}
|
||||
|
||||
}, Application.class, TransportFactory.class, NodeSncpServer.class, NodeHttpServer.class, NodeWatchServer.class);
|
||||
}, Application.class, ResourceFactory.class, TransportFactory.class, NodeSncpServer.class, NodeHttpServer.class, NodeWatchServer.class);
|
||||
//--------------------------------------------------------------------------
|
||||
initResources();
|
||||
}
|
||||
@@ -440,6 +463,16 @@ public final class Application {
|
||||
//------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void restoreConfig() throws IOException {
|
||||
synchronized (this) {
|
||||
File confFile = new File(this.home, "conf/application.xml");
|
||||
confFile.renameTo(new File(this.home, "conf/application_" + String.format("%1$tY%1$tm%1$td%1$tH%1$tM%1$tS", System.currentTimeMillis()) + ".xml"));
|
||||
final PrintStream ps = new PrintStream(new FileOutputStream(confFile));
|
||||
ps.append(config.toXML("application"));
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void startSelfServer() throws Exception {
|
||||
final Application application = this;
|
||||
new Thread() {
|
||||
@@ -553,13 +586,12 @@ public final class Application {
|
||||
others.add(entry);
|
||||
}
|
||||
}
|
||||
if (watchs.size() > 1) throw new RuntimeException("Found one more WATCH Server");
|
||||
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, sncps); //必须确保SNCP服务都启动后再启动其他服务
|
||||
runServers(timecd, others);
|
||||
runServers(timecd, watchs); //必须在所有server都启动后再启动
|
||||
runServers(timecd, watchs); //必须在所有服务都启动后再启动WATCH服务
|
||||
timecd.await();
|
||||
logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms\r\n");
|
||||
if (!singletonrun) this.serversLatch.await();
|
||||
@@ -603,7 +635,7 @@ public final class Application {
|
||||
if (!inited.get()) {
|
||||
synchronized (nodeClasses) {
|
||||
if (!inited.getAndSet(true)) { //加载自定义的协议,如:SOCKS
|
||||
ClassFilter profilter = new ClassFilter(NodeProtocol.class, NodeServer.class, (Class[]) null);
|
||||
ClassFilter profilter = new ClassFilter(classLoader, 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) {
|
||||
@@ -731,6 +763,15 @@ public final class Application {
|
||||
this.transportFactory.shutdownNow();
|
||||
}
|
||||
|
||||
private static int parseLenth(String value, int defValue) {
|
||||
if (value == null) return defValue;
|
||||
value = value.toUpperCase().replace("B", "");
|
||||
if (value.endsWith("G")) return Integer.decode(value.replace("G", "")) * 1024 * 1024 * 1024;
|
||||
if (value.endsWith("M")) return Integer.decode(value.replace("M", "")) * 1024 * 1024;
|
||||
if (value.endsWith("K")) return Integer.decode(value.replace("K", "")) * 1024;
|
||||
return Integer.decode(value);
|
||||
}
|
||||
|
||||
private static AnyValue load(final InputStream in0) {
|
||||
final DefaultAnyValue any = new DefaultAnyValue();
|
||||
try (final InputStream in = in0) {
|
||||
|
||||
@@ -58,19 +58,22 @@ public final class ClassFilter<T> {
|
||||
|
||||
private AnyValue conf; //基本配置信息, 当符合条件时将conf的属性赋值到FilterEntry中去。
|
||||
|
||||
public ClassFilter(Class<? extends Annotation> annotationClass, Class superClass, Class[] excludeSuperClasses) {
|
||||
this(annotationClass, superClass, excludeSuperClasses, null);
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public ClassFilter(ClassLoader classLoader, Class<? extends Annotation> annotationClass, Class superClass, Class[] excludeSuperClasses) {
|
||||
this(classLoader, annotationClass, superClass, excludeSuperClasses, null);
|
||||
}
|
||||
|
||||
public ClassFilter(Class<? extends Annotation> annotationClass, Class superClass, Class[] excludeSuperClasses, AnyValue conf) {
|
||||
public ClassFilter(ClassLoader classLoader, Class<? extends Annotation> annotationClass, Class superClass, Class[] excludeSuperClasses, AnyValue conf) {
|
||||
this.annotationClass = annotationClass;
|
||||
this.superClass = superClass;
|
||||
this.excludeSuperClasses = excludeSuperClasses;
|
||||
this.conf = conf;
|
||||
this.classLoader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
|
||||
}
|
||||
|
||||
public static ClassFilter create(Class[] excludeSuperClasses, String includeregs, String excluderegs, Set<String> includeValues, Set<String> excludeValues) {
|
||||
ClassFilter filter = new ClassFilter(null, null, excludeSuperClasses);
|
||||
ClassFilter filter = new ClassFilter(null, null, null, excludeSuperClasses);
|
||||
filter.setIncludePatterns(includeregs == null ? null : includeregs.split(";"));
|
||||
filter.setExcludePatterns(excluderegs == null ? null : excluderegs.split(";"));
|
||||
filter.setPrivilegeIncludes(includeValues);
|
||||
@@ -96,7 +99,11 @@ public final class ClassFilter<T> {
|
||||
* @return Set<FilterEntry<T>>
|
||||
*/
|
||||
public final Set<FilterEntry<T>> getFilterEntrys() {
|
||||
return entrys;
|
||||
HashSet<FilterEntry<T>> set = new HashSet<>();
|
||||
set.addAll(entrys);
|
||||
if (ors != null) ors.forEach(f -> set.addAll(f.getFilterEntrys()));
|
||||
if (ands != null) ands.forEach(f -> set.addAll(f.getFilterEntrys()));
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +112,11 @@ public final class ClassFilter<T> {
|
||||
* @return Set<FilterEntry<T>>
|
||||
*/
|
||||
public final Set<FilterEntry<T>> getFilterExpectEntrys() {
|
||||
return expectEntrys;
|
||||
HashSet<FilterEntry<T>> set = new HashSet<>();
|
||||
set.addAll(expectEntrys);
|
||||
if (ors != null) ors.forEach(f -> set.addAll(f.getFilterExpectEntrys()));
|
||||
if (ands != null) ands.forEach(f -> set.addAll(f.getFilterExpectEntrys()));
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,8 +126,8 @@ public final class ClassFilter<T> {
|
||||
*/
|
||||
public final Set<FilterEntry<T>> getAllFilterEntrys() {
|
||||
HashSet<FilterEntry<T>> rs = new HashSet<>();
|
||||
rs.addAll(entrys);
|
||||
rs.addAll(expectEntrys);
|
||||
rs.addAll(getFilterEntrys());
|
||||
rs.addAll(getFilterExpectEntrys());
|
||||
return rs;
|
||||
}
|
||||
|
||||
@@ -156,7 +167,7 @@ public final class ClassFilter<T> {
|
||||
}
|
||||
if (cf == null || clazzname.startsWith("sun.")) return;
|
||||
try {
|
||||
Class clazz = Class.forName(clazzname);
|
||||
Class clazz = classLoader.loadClass(clazzname);
|
||||
if (!cf.accept(property, clazz, autoscan)) return;
|
||||
if (cf.conf != null) {
|
||||
if (property == null) {
|
||||
@@ -180,7 +191,7 @@ public final class ClassFilter<T> {
|
||||
} catch (Throwable cfe) {
|
||||
if (finer && !clazzname.startsWith("sun.") && !clazzname.startsWith("javax.")
|
||||
&& !clazzname.startsWith("com.sun.") && !clazzname.startsWith("jdk.")) {
|
||||
//logger.log(Level.FINEST, ClassFilter.class.getSimpleName() + " filter error", cfe);
|
||||
logger.log(Level.FINEST, ClassFilter.class.getSimpleName() + " filter error", cfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,12 +456,12 @@ public final class ClassFilter<T> {
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public static void load(final File excludeFile, final String[] excludeRegs, final ClassFilter... filters) throws IOException {
|
||||
URLClassLoader loader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
|
||||
RedkaleClassLoader loader = (RedkaleClassLoader) Thread.currentThread().getContextClassLoader();
|
||||
List<URL> urlfiles = new ArrayList<>(2);
|
||||
List<URL> urljares = new ArrayList<>(2);
|
||||
final URL exurl = excludeFile != null ? excludeFile.toURI().toURL() : null;
|
||||
final Pattern[] excludePatterns = toPattern(excludeRegs);
|
||||
for (URL url : loader.getURLs()) {
|
||||
for (URL url : loader.getAllURLs()) {
|
||||
if (exurl != null && exurl.sameFile(url)) continue;
|
||||
if (excludePatterns != null) {
|
||||
boolean skip = false;
|
||||
@@ -482,6 +493,12 @@ public final class ClassFilter<T> {
|
||||
if (entryname.endsWith(".class") && entryname.indexOf('$') < 0) {
|
||||
String classname = entryname.substring(0, entryname.length() - 6);
|
||||
if (classname.startsWith("javax.") || classname.startsWith("com.sun.")) continue;
|
||||
//常见的jar跳过
|
||||
if (classname.startsWith("com.mysql.")) break;
|
||||
if (classname.startsWith("org.mariadb.")) break;
|
||||
if (classname.startsWith("oracle.jdbc.")) break;
|
||||
if (classname.startsWith("org.postgresql.")) break;
|
||||
if (classname.startsWith("com.microsoft.sqlserver.")) break;
|
||||
classes.add(classname);
|
||||
if (debug) debugstr.append(classname).append("\r\n");
|
||||
for (final ClassFilter filter : filters) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.logging.Formatter;
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class LogFileHandler extends Handler {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,28 +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.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class NodeHttpServer extends NodeServer {
|
||||
synchronized (regFactory) {
|
||||
Service nodeService = (Service) rf.find(resourceName, WebSocketNode.class);
|
||||
if (nodeService == null) {
|
||||
nodeService = Sncp.createLocalService(resourceName, WebSocketNodeService.class, application.getResourceFactory(), application.getTransportFactory(), (InetSocketAddress) null, (Set<String>) null, (AnyValue) null);
|
||||
nodeService = Sncp.createLocalService(serverClassLoader, resourceName, WebSocketNodeService.class, application.getResourceFactory(), application.getTransportFactory(), (InetSocketAddress) null, (Set<String>) null, (AnyValue) null);
|
||||
regFactory.register(resourceName, WebSocketNode.class, nodeService);
|
||||
}
|
||||
resourceFactory.inject(nodeService, self);
|
||||
@@ -122,6 +122,7 @@ public class NodeHttpServer extends NodeServer {
|
||||
}, WebSocketNode.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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 threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
@@ -138,6 +139,7 @@ public class NodeHttpServer extends NodeServer {
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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;
|
||||
@@ -195,6 +197,7 @@ public class NodeHttpServer extends NodeServer {
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString().trim());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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服务
|
||||
@@ -210,8 +213,9 @@ public class NodeHttpServer extends NodeServer {
|
||||
final boolean autoload = restConf.getBoolValue("autoload", true);
|
||||
{ //加载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 Class userType = userTypeStr == null ? null : this.serverClassLoader.loadClass(userTypeStr);
|
||||
|
||||
final Class baseServletType = this.serverClassLoader.loadClass(restConf.getValue("base", HttpServlet.class.getName()));
|
||||
final Set<String> includeValues = new HashSet<>();
|
||||
final Set<String> excludeValues = new HashSet<>();
|
||||
for (AnyValue item : restConf.getAnyValues("service")) {
|
||||
@@ -238,14 +242,17 @@ public class NodeHttpServer extends NodeServer {
|
||||
return;
|
||||
}
|
||||
restedObjects.add(service); //避免重复创建Rest对象
|
||||
HttpServlet servlet = httpServer.addRestServlet(service, userType, baseServletType, prefix);
|
||||
HttpServlet servlet = httpServer.addRestServlet(serverClassLoader, service, userType, baseServletType, prefix);
|
||||
if (servlet == null) return; //没有HttpMapping方法的HttpServlet调用Rest.createRestServlet就会返回null
|
||||
String prefix2 = prefix;
|
||||
WebServlet ws = servlet.getClass().getAnnotation(WebServlet.class);
|
||||
if (ws != null && !ws.repair()) prefix2 = "";
|
||||
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];
|
||||
mappings[i] = prefix2 + mappings[i];
|
||||
}
|
||||
ss.add(new AbstractMap.SimpleEntry<>(servlet.getClass().getName(), mappings));
|
||||
}
|
||||
@@ -288,14 +295,17 @@ public class NodeHttpServer extends NodeServer {
|
||||
return;
|
||||
}
|
||||
restedObjects.add(stype); //避免重复创建Rest对象
|
||||
HttpServlet servlet = httpServer.addRestWebSocketServlet(stype, prefix, en.getProperty());
|
||||
HttpServlet servlet = httpServer.addRestWebSocketServlet(serverClassLoader, stype, prefix, en.getProperty());
|
||||
if (servlet == null) return; //没有RestOnMessage方法的HttpServlet调用Rest.createRestWebSocketServlet就会返回null
|
||||
String prefix2 = prefix;
|
||||
WebServlet ws = servlet.getClass().getAnnotation(WebServlet.class);
|
||||
if (ws != null && !ws.repair()) prefix2 = "";
|
||||
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];
|
||||
mappings[i] = prefix2 + mappings[i];
|
||||
}
|
||||
ss.add(new AbstractMap.SimpleEntry<>(servlet.getClass().getName(), mappings));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.redkale.boot;
|
||||
|
||||
import org.redkale.util.RedkaleClassLoader;
|
||||
import java.io.*;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.*;
|
||||
@@ -54,7 +55,9 @@ public abstract class NodeServer {
|
||||
protected final Server server;
|
||||
|
||||
//ClassLoader
|
||||
protected final NodeClassLoader classLoader;
|
||||
protected RedkaleClassLoader serverClassLoader;
|
||||
|
||||
protected final Thread serverThread;
|
||||
|
||||
//当前Server的SNCP协议的组
|
||||
protected String sncpGroup = null;
|
||||
@@ -89,35 +92,9 @@ public abstract class NodeServer {
|
||||
this.resourceFactory = application.getResourceFactory().createChild();
|
||||
this.server = server;
|
||||
this.logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
this.classLoader = new NodeClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
Thread.currentThread().setContextClassLoader(this.classLoader);
|
||||
}
|
||||
|
||||
protected Consumer<Runnable> getExecutor() throws Exception {
|
||||
if (server == null) return null;
|
||||
final Field field = Server.class.getDeclaredField("context");
|
||||
field.setAccessible(true);
|
||||
return new Consumer<Runnable>() {
|
||||
|
||||
private Context context;
|
||||
|
||||
@Override
|
||||
public void accept(Runnable t) {
|
||||
if (context == null && server != null) {
|
||||
try {
|
||||
this.context = (Context) field.get(server);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Server (" + server.getSocketAddress() + ") cannot find Context", e);
|
||||
}
|
||||
}
|
||||
if (context == null) {
|
||||
t.run();
|
||||
} else {
|
||||
context.submitAsync(t);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
this.serverClassLoader = new RedkaleClassLoader(application.getServerClassLoader());
|
||||
Thread.currentThread().setContextClassLoader(this.serverClassLoader);
|
||||
this.serverThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
public static <T extends NodeServer> NodeServer create(Class<T> clazz, Application application, AnyValue serconf) {
|
||||
@@ -156,9 +133,9 @@ public abstract class NodeServer {
|
||||
resourceFactory.register(Server.RESNAME_SERVER_ROOT, File.class, myroot.getCanonicalFile());
|
||||
resourceFactory.register(Server.RESNAME_SERVER_ROOT, Path.class, myroot.toPath());
|
||||
|
||||
final String homepath = myroot.getCanonicalPath();
|
||||
//加入指定的classpath
|
||||
Server.loadLib(logger, this.serverConf.getValue("lib", "").replace("${APP_HOME}", homepath) + ";" + homepath + "/lib/*;" + homepath + "/classes");
|
||||
Server.loadLib(serverClassLoader, logger, this.serverConf.getValue("lib", "${APP_HOME}/libs/*").replace("${APP_HOME}", application.getHome().getPath().replace('\\', '/')));
|
||||
this.serverThread.setContextClassLoader(this.serverClassLoader);
|
||||
}
|
||||
//必须要进行初始化, 构建Service时需要使用Context中的ExecutorService
|
||||
server.init(this.serverConf);
|
||||
@@ -166,7 +143,7 @@ public abstract class NodeServer {
|
||||
initResource(); //给 DataSource、CacheSource 注册依赖注入时的监听回调事件。
|
||||
String interceptorClass = this.serverConf.getValue("interceptor", "");
|
||||
if (!interceptorClass.isEmpty()) {
|
||||
Class clazz = Class.forName(interceptorClass);
|
||||
Class clazz = serverClassLoader.loadClass(interceptorClass);
|
||||
this.interceptor = (NodeInterceptor) clazz.newInstance();
|
||||
}
|
||||
|
||||
@@ -196,19 +173,17 @@ public abstract class NodeServer {
|
||||
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<>();
|
||||
final Map<String, AnyValue> dataResources = new HashMap<>();
|
||||
if (resources != null) {
|
||||
for (AnyValue sourceConf : resources.getAnyValues("source")) {
|
||||
try {
|
||||
Class type = Class.forName(sourceConf.getValue("value"));
|
||||
Class type = serverClassLoader.loadClass(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);
|
||||
dataResources.put(sourceConf.getValue("name", ""), sourceConf);
|
||||
} else {
|
||||
logger.log(Level.SEVERE, "load application source resource, but not CacheSource error: " + sourceConf);
|
||||
}
|
||||
@@ -256,7 +231,7 @@ public abstract class NodeServer {
|
||||
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));
|
||||
Service cacheListenerService = Sncp.createLocalService(serverClassLoader, resourceName, DataCacheListenerService.class, appResFactory, appTranFactory, sncpAddr, groups, Sncp.getConf((Service) src));
|
||||
appResFactory.register(resourceName, DataCacheListener.class, cacheListenerService);
|
||||
localServices.add(cacheListenerService);
|
||||
sncpServer.consumerAccept(cacheListenerService);
|
||||
@@ -281,12 +256,20 @@ public abstract class NodeServer {
|
||||
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));
|
||||
|
||||
AnyValue sourceConf = cacheResource.get(resourceName);
|
||||
if (sourceConf == null) sourceConf = dataResources.get(resourceName);
|
||||
final Class sourceType = sourceConf == null ? CacheMemorySource.class : serverClassLoader.loadClass(sourceConf.getValue("value"));
|
||||
Object source;
|
||||
if (DataSource.class.isAssignableFrom(sourceType)) { // DataSource
|
||||
source = (DataSource) Sncp.createLocalService(serverClassLoader, resourceName, sourceType, appResFactory, appTranFactory, sncpAddr, groups, Sncp.getConf(srcService));
|
||||
application.dataSources.add((DataSource) source);
|
||||
appResFactory.register(resourceName, DataSource.class, source);
|
||||
} else { // CacheSource
|
||||
source = (CacheSource) Sncp.createLocalService(serverClassLoader, 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];
|
||||
@@ -295,9 +278,10 @@ public abstract class NodeServer {
|
||||
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);
|
||||
application.cacheSources.add((CacheSource) 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);
|
||||
@@ -357,9 +341,9 @@ public abstract class NodeServer {
|
||||
Service service;
|
||||
boolean ws = src instanceof WebSocketServlet;
|
||||
if (ws || localed) { //本地模式
|
||||
service = Sncp.createLocalService(resourceName, serviceImplClass, appResourceFactory, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
|
||||
service = Sncp.createLocalService(serverClassLoader, resourceName, serviceImplClass, appResourceFactory, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
|
||||
} else {
|
||||
service = Sncp.createRemoteService(resourceName, serviceImplClass, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
|
||||
service = Sncp.createRemoteService(serverClassLoader, resourceName, serviceImplClass, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
|
||||
}
|
||||
if (SncpClient.parseMethod(serviceImplClass).isEmpty()) return; //class没有可用的方法, 通常为BaseService
|
||||
|
||||
@@ -449,62 +433,6 @@ public abstract class NodeServer {
|
||||
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);
|
||||
* 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();
|
||||
@@ -514,12 +442,12 @@ public abstract class NodeServer {
|
||||
}
|
||||
|
||||
protected ClassFilter<Service> createServiceClassFilter() {
|
||||
return createClassFilter(this.sncpGroup, null, Service.class, (!isSNCP() || application.watching) ? null : new Class[]{org.redkale.watch.WatchService.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[] excludeSuperClasses, Class<? extends Annotation> ref2, String properties, String property) {
|
||||
ClassFilter cf = new ClassFilter(ref, inter, excludeSuperClasses, null);
|
||||
ClassFilter cf = new ClassFilter(this.serverClassLoader, ref, inter, excludeSuperClasses, null);
|
||||
if (properties == null && properties == null) {
|
||||
cf.setRefused(true);
|
||||
return cf;
|
||||
@@ -546,7 +474,7 @@ public abstract class NodeServer {
|
||||
prop = new AnyValue.DefaultAnyValue();
|
||||
prop.addValue("groups", sc);
|
||||
}
|
||||
ClassFilter filter = new ClassFilter(ref, inter, excludeSuperClasses, prop);
|
||||
ClassFilter filter = new ClassFilter(this.serverClassLoader, 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>节点
|
||||
@@ -570,8 +498,10 @@ public abstract class NodeServer {
|
||||
dav.addValue("properties", ps);
|
||||
av = dav;
|
||||
}
|
||||
if (!av.getBoolValue("ignore", false)) {
|
||||
filter.filter(av, av.getValue("value"), false);
|
||||
}
|
||||
}
|
||||
if (list.getBoolValue("autoload", true)) {
|
||||
String includes = list.getValue("includes", "");
|
||||
String excludes = list.getValue("excludes", "");
|
||||
@@ -593,12 +523,22 @@ public abstract class NodeServer {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isWATCH() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public ResourceFactory getResourceFactory() {
|
||||
return resourceFactory;
|
||||
}
|
||||
|
||||
public NodeClassLoader getNodeClassLoader() {
|
||||
return classLoader;
|
||||
public RedkaleClassLoader getServerClassLoader() {
|
||||
return serverClassLoader;
|
||||
}
|
||||
|
||||
public void setServerClassLoader(RedkaleClassLoader serverClassLoader) {
|
||||
Objects.requireNonNull(this.serverClassLoader);
|
||||
this.serverClassLoader = serverClassLoader;
|
||||
this.serverThread.setContextClassLoader(serverClassLoader);
|
||||
}
|
||||
|
||||
public InetSocketAddress getSncpAddress() {
|
||||
|
||||
@@ -85,6 +85,7 @@ public class NodeSncpServer extends NodeServer {
|
||||
if (sncpServer != null) loadSncpFilter(this.serverConf.getAnyValue("fliters"), filterFilter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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() + "] ";
|
||||
@@ -106,8 +107,9 @@ public class NodeSncpServer extends NodeServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected ClassFilter<Filter> createFilterClassFilter() {
|
||||
return createClassFilter(null, null, SncpFilter.class, null, null, "filters", "filter");
|
||||
return createClassFilter(null, null, SncpFilter.class, new Class[]{org.redkale.watch.WatchFilter.class}, null, "filters", "filter");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,4 +37,14 @@ public class NodeWatchServer extends NodeHttpServer {
|
||||
protected ClassFilter<Servlet> createServletClassFilter() {
|
||||
return createClassFilter(null, WebServlet.class, WatchServlet.class, null, null, "servlets", "servlet");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter createOtherClassFilter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWATCH() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
17
src/org/redkale/boot/watch/AbstractWatchService.java
Normal file
17
src/org/redkale/boot/watch/AbstractWatchService.java
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.watch;
|
||||
|
||||
import org.redkale.service.AbstractService;
|
||||
import org.redkale.watch.WatchService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class AbstractWatchService extends AbstractService implements WatchService {
|
||||
|
||||
}
|
||||
50
src/org/redkale/boot/watch/FilterWatchService.java
Normal file
50
src/org/redkale/boot/watch/FilterWatchService.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.watch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.boot.*;
|
||||
import org.redkale.net.http.*;
|
||||
import org.redkale.service.RetResult;
|
||||
import org.redkale.util.Comment;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@RestService(name = "filter", catalog = "watch", repair = false)
|
||||
public class FilterWatchService extends AbstractWatchService {
|
||||
|
||||
@Comment("Filter类名不存在")
|
||||
public static final int RET_FILTER_TYPE_NOT_EXISTS = 1601_0002;
|
||||
|
||||
@Comment("Filter类名不合法")
|
||||
public static final int RET_FILTER_TYPE_ILLEGAL = 1601_0003;
|
||||
|
||||
@Comment("Filter类名已存在")
|
||||
public static final int RET_FILTER_EXISTS = 1601_0004;
|
||||
|
||||
@Comment("Filter的JAR包不存在")
|
||||
public static final int RET_FILTER_JAR_ILLEGAL = 1601_0005;
|
||||
|
||||
@Resource
|
||||
private Application application;
|
||||
|
||||
@RestMapping(name = "addfilter", auth = false, comment = "动态增加Filter")
|
||||
public RetResult addFilter(@RestUploadFile(maxLength = 10 * 1024 * 1024, fileNameReg = "\\.jar$") byte[] jar,
|
||||
@RestParam(name = "server", comment = "Server节点名") final String serverName,
|
||||
@RestParam(name = "type", comment = "Filter类名") final String filterType) throws IOException {
|
||||
if (filterType == null) return new RetResult(RET_FILTER_TYPE_NOT_EXISTS, "Not found Filter Type (" + filterType + ")");
|
||||
if (jar == null) return new RetResult(RET_FILTER_JAR_ILLEGAL, "Not found jar file");
|
||||
List<NodeServer> nodes = application.getNodeServers();
|
||||
for (NodeServer node : nodes) {
|
||||
if (node.getServer().containsFilter(filterType)) return new RetResult(RET_FILTER_EXISTS, "Filter(" + filterType + ") exists");
|
||||
}
|
||||
return RetResult.success();
|
||||
}
|
||||
}
|
||||
17
src/org/redkale/boot/watch/ServerWatchService.java
Normal file
17
src/org/redkale/boot/watch/ServerWatchService.java
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.watch;
|
||||
|
||||
import org.redkale.net.http.RestService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@RestService(name = "server", catalog = "watch", repair = false)
|
||||
public class ServerWatchService extends AbstractWatchService {
|
||||
|
||||
}
|
||||
39
src/org/redkale/boot/watch/ServiceWatchService.java
Normal file
39
src/org/redkale/boot/watch/ServiceWatchService.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.boot.watch;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.boot.Application;
|
||||
import org.redkale.net.TransportFactory;
|
||||
import org.redkale.net.http.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@RestService(name = "service", catalog = "watch", repair = false)
|
||||
public class ServiceWatchService extends AbstractWatchService {
|
||||
|
||||
@Resource
|
||||
private Application application;
|
||||
|
||||
@Resource
|
||||
private TransportFactory transportFactory;
|
||||
|
||||
// @RestMapping(name = "load", auth = false, comment = "动态增加Service")
|
||||
// public RetResult loadService(String type, @RestUploadFile(maxLength = 10 * 1024 * 1024, fileNameReg = "\\.jar$") byte[] jar) {
|
||||
// //待开发
|
||||
// return RetResult.success();
|
||||
// }
|
||||
//
|
||||
// @RestMapping(name = "stop", auth = false, comment = "动态停止Service")
|
||||
// public RetResult stopService(String name, String type) {
|
||||
// //待开发
|
||||
// return RetResult.success();
|
||||
// }
|
||||
}
|
||||
39
src/org/redkale/boot/watch/ServletWatchService.java
Normal file
39
src/org/redkale/boot/watch/ServletWatchService.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.boot.watch;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.boot.Application;
|
||||
import org.redkale.net.TransportFactory;
|
||||
import org.redkale.net.http.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@RestService(name = "servlet", catalog = "watch", repair = false)
|
||||
public class ServletWatchService extends AbstractWatchService {
|
||||
|
||||
@Resource
|
||||
private Application application;
|
||||
|
||||
@Resource
|
||||
private TransportFactory transportFactory;
|
||||
//
|
||||
// @RestMapping(name = "load", auth = false, comment = "动态增加Servlet")
|
||||
// public RetResult loadServlet(String type, @RestUploadFile(maxLength = 10 * 1024 * 1024, fileNameReg = "\\.jar$") byte[] jar) {
|
||||
// //待开发
|
||||
// return RetResult.success();
|
||||
// }
|
||||
//
|
||||
// @RestMapping(name = "stop", auth = false, comment = "动态停止Servlet")
|
||||
// public RetResult stopServlet(String type) {
|
||||
// //待开发
|
||||
// return RetResult.success();
|
||||
// }
|
||||
}
|
||||
26
src/org/redkale/boot/watch/SourceWatchService.java
Normal file
26
src/org/redkale/boot/watch/SourceWatchService.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.watch;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.boot.Application;
|
||||
import org.redkale.net.TransportFactory;
|
||||
import org.redkale.net.http.RestService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@RestService(name = "source", catalog = "watch", repair = false)
|
||||
public class SourceWatchService extends AbstractWatchService {
|
||||
|
||||
@Resource
|
||||
private Application application;
|
||||
|
||||
@Resource
|
||||
private TransportFactory transportFactory;
|
||||
|
||||
}
|
||||
138
src/org/redkale/boot/watch/TransportWatchService.java
Normal file
138
src/org/redkale/boot/watch/TransportWatchService.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.watch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.channels.AsynchronousSocketChannel;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.boot.Application;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.http.*;
|
||||
import org.redkale.net.sncp.*;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.util.AnyValue.DefaultAnyValue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@RestService(name = "transport", catalog = "watch", repair = false)
|
||||
public class TransportWatchService extends AbstractWatchService {
|
||||
|
||||
@Comment("不存在的Group节点")
|
||||
public static final int RET_TRANSPORT_GROUP_NOT_EXISTS = 1606_0001;
|
||||
|
||||
@Comment("非法的Node节点IP地址")
|
||||
public static final int RET_TRANSPORT_ADDR_ILLEGAL = 1606_0002;
|
||||
|
||||
@Comment("Node节点IP地址已存在")
|
||||
public static final int RET_TRANSPORT_ADDR_EXISTS = 1606_0003;
|
||||
|
||||
@Resource
|
||||
private Application application;
|
||||
|
||||
@Resource
|
||||
private TransportFactory transportFactory;
|
||||
|
||||
@RestMapping(name = "listnodes", auth = false, comment = "获取所有Node节点")
|
||||
public List<TransportGroupInfo> listNodes() {
|
||||
return transportFactory.getGroupInfos();
|
||||
}
|
||||
|
||||
@RestMapping(name = "addnode", auth = false, comment = "动态增加指定Group的Node节点")
|
||||
public RetResult addNode(@RestParam(name = "group", comment = "Group节点名") final String group,
|
||||
@RestParam(name = "addr", comment = "节点IP") final String addr,
|
||||
@RestParam(name = "port", comment = "节点端口") final int port) throws IOException {
|
||||
InetSocketAddress address;
|
||||
try {
|
||||
address = new InetSocketAddress(addr, port);
|
||||
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
|
||||
channel.connect(address).get(2, TimeUnit.SECONDS); //连接超时2秒
|
||||
channel.close();
|
||||
} catch (Exception e) {
|
||||
return new RetResult(RET_TRANSPORT_ADDR_ILLEGAL, "InetSocketAddress(addr=" + addr + ", port=" + port + ") is illegal or cannot connect");
|
||||
}
|
||||
if (transportFactory.findGroupName(address) != null) return new RetResult(RET_TRANSPORT_ADDR_ILLEGAL, "InetSocketAddress(addr=" + addr + ", port=" + port + ") is exists");
|
||||
synchronized (this) {
|
||||
if (transportFactory.findGroupInfo(group) == null) {
|
||||
return new RetResult(RET_TRANSPORT_GROUP_NOT_EXISTS, "not found group (" + group + ")");
|
||||
}
|
||||
transportFactory.addGroupInfo(group, address);
|
||||
for (Service service : transportFactory.getServices()) {
|
||||
if (!Sncp.isSncpDyn(service)) continue;
|
||||
SncpClient client = Sncp.getSncpClient(service);
|
||||
if (Sncp.isRemote(service)) {
|
||||
if (client.getRemoteGroups() != null && client.getRemoteGroups().contains(group)) {
|
||||
client.getRemoteGroupTransport().addRemoteAddresses(address);
|
||||
}
|
||||
} else {
|
||||
if (group.equals(client.getSameGroup())) {
|
||||
client.getSameGroupTransport().addRemoteAddresses(address);
|
||||
}
|
||||
if (client.getDiffGroups() != null && client.getDiffGroups().contains(group)) {
|
||||
for (Transport transport : client.getDiffGroupTransports()) {
|
||||
transport.addRemoteAddresses(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DefaultAnyValue node = DefaultAnyValue.create("addr", addr).addValue("port", port);
|
||||
for (AnyValue groupconf : application.getAppConfig().getAnyValue("resources").getAnyValues("group")) {
|
||||
if (group.equals(groupconf.getValue("name"))) {
|
||||
((DefaultAnyValue) groupconf).addValue("node", node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
application.restoreConfig();
|
||||
}
|
||||
return RetResult.success();
|
||||
}
|
||||
|
||||
@RestMapping(name = "removenode", auth = false, comment = "动态删除指定Group的Node节点")
|
||||
public RetResult removeNode(@RestParam(name = "group", comment = "Group节点名") final String group,
|
||||
@RestParam(name = "addr", comment = "节点IP") final String addr,
|
||||
@RestParam(name = "port", comment = "节点端口") final int port) throws IOException {
|
||||
if (group == null) return new RetResult(RET_TRANSPORT_GROUP_NOT_EXISTS, "not found group (" + group + ")");
|
||||
final InetSocketAddress address = new InetSocketAddress(addr, port);
|
||||
if (!group.equals(transportFactory.findGroupName(address))) return new RetResult(RET_TRANSPORT_ADDR_ILLEGAL, "InetSocketAddress(addr=" + addr + ", port=" + port + ") not belong to group(" + group + ")");
|
||||
synchronized (this) {
|
||||
if (transportFactory.findGroupInfo(group) == null) {
|
||||
return new RetResult(RET_TRANSPORT_GROUP_NOT_EXISTS, "not found group (" + group + ")");
|
||||
}
|
||||
transportFactory.removeGroupInfo(group, address);
|
||||
for (Service service : transportFactory.getServices()) {
|
||||
if (!Sncp.isSncpDyn(service)) continue;
|
||||
SncpClient client = Sncp.getSncpClient(service);
|
||||
if (Sncp.isRemote(service)) {
|
||||
if (client.getRemoteGroups() != null && client.getRemoteGroups().contains(group)) {
|
||||
client.getRemoteGroupTransport().removeRemoteAddresses(address);
|
||||
}
|
||||
} else {
|
||||
if (group.equals(client.getSameGroup())) {
|
||||
client.getSameGroupTransport().removeRemoteAddresses(address);
|
||||
}
|
||||
if (client.getDiffGroups() != null && client.getDiffGroups().contains(group)) {
|
||||
for (Transport transport : client.getDiffGroupTransports()) {
|
||||
transport.removeRemoteAddresses(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (AnyValue groupconf : application.getAppConfig().getAnyValue("resources").getAnyValues("group")) {
|
||||
if (group.equals(groupconf.getValue("name"))) {
|
||||
((DefaultAnyValue) groupconf).removeValue("node", DefaultAnyValue.create("addr", addr).addValue("port", port));
|
||||
break;
|
||||
}
|
||||
}
|
||||
application.restoreConfig();
|
||||
}
|
||||
return RetResult.success();
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,23 @@ public final class AnyEncoder<T> implements Encodeable<Writer, T> {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void convertMapTo(final Writer out, final Object... values) {
|
||||
if (values == null) {
|
||||
out.writeNull();
|
||||
} else {
|
||||
int count = values.length - values.length % 2;
|
||||
out.writeMapB(count / 2);
|
||||
for (int i = 0; i < count; i += 2) {
|
||||
if (i > 0) out.writeArrayMark();
|
||||
this.convertTo(out, (T) values[i]);
|
||||
out.writeMapMark();
|
||||
this.convertTo(out, (T) values[i + 1]);
|
||||
}
|
||||
out.writeMapE();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Object.class;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
*/
|
||||
package org.redkale.convert;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 序列化/反序列化操作类
|
||||
*
|
||||
@@ -26,4 +30,17 @@ public abstract class Convert<R extends Reader, W extends Writer> {
|
||||
public ConvertFactory<R, W> getFactory() {
|
||||
return this.factory;
|
||||
}
|
||||
|
||||
public abstract boolean isBinary();
|
||||
|
||||
public abstract <T> T convertFrom(final Type type, final ByteBuffer... buffers);
|
||||
|
||||
public abstract <T> T convertFrom(final Type type, final ConvertMask mask, final ByteBuffer... buffers);
|
||||
|
||||
public abstract ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Object value);
|
||||
|
||||
public abstract ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Type type, final Object value);
|
||||
|
||||
public abstract ByteBuffer[] convertMapTo(final Supplier<ByteBuffer> supplier, final Object... values);
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import static java.lang.annotation.RetentionPolicy.*;
|
||||
@Documented
|
||||
@Target({METHOD, FIELD})
|
||||
@Retention(RUNTIME)
|
||||
@Repeatable(ConvertColumns.class)
|
||||
@Repeatable(ConvertColumn.ConvertColumns.class)
|
||||
public @interface ConvertColumn {
|
||||
|
||||
/**
|
||||
@@ -31,6 +31,13 @@ public @interface ConvertColumn {
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 给字段取个序号ID,值小靠前
|
||||
*
|
||||
* @return 字段排序ID
|
||||
*/
|
||||
int index() default 0;
|
||||
|
||||
/**
|
||||
* 解析/序列化时是否屏蔽该字段
|
||||
*
|
||||
@@ -44,4 +51,21 @@ public @interface ConvertColumn {
|
||||
* @return JSON or BSON or ALL
|
||||
*/
|
||||
ConvertType type() default ConvertType.ALL;
|
||||
|
||||
/**
|
||||
* ConvertColumn 的多用类
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD, FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public static @interface ConvertColumns {
|
||||
|
||||
ConvertColumn[] value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,15 @@ package org.redkale.convert;
|
||||
/**
|
||||
* ConvertColumn 对应的实体类
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class ConvertColumnEntry {
|
||||
|
||||
private int index;
|
||||
|
||||
private String name = "";
|
||||
|
||||
private boolean ignore;
|
||||
@@ -25,6 +29,7 @@ public final class ConvertColumnEntry {
|
||||
public ConvertColumnEntry(ConvertColumn column) {
|
||||
if (column == null) return;
|
||||
this.name = column.name();
|
||||
this.index = column.index();
|
||||
this.ignore = column.ignore();
|
||||
this.convertType = column.type();
|
||||
}
|
||||
@@ -45,6 +50,13 @@ public final class ConvertColumnEntry {
|
||||
this.convertType = convertType;
|
||||
}
|
||||
|
||||
public ConvertColumnEntry(String name, int index, boolean ignore, ConvertType convertType) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
this.ignore = ignore;
|
||||
this.convertType = convertType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name == null ? "" : name;
|
||||
}
|
||||
@@ -69,4 +81,12 @@ public final class ConvertColumnEntry {
|
||||
this.convertType = convertType;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,25 +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.convert;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.*;
|
||||
|
||||
/**
|
||||
* ConvertColumn 的多用类
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD, FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface ConvertColumns {
|
||||
|
||||
ConvertColumn[] value();
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
|
||||
|
||||
public abstract ConvertType getConvertType();
|
||||
|
||||
public abstract boolean isReversible();
|
||||
public abstract boolean isReversible(); //是否可逆的
|
||||
|
||||
public abstract ConvertFactory createChild();
|
||||
|
||||
@@ -253,7 +253,7 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
|
||||
|
||||
Class clazz = findEntityAlias(name);
|
||||
try {
|
||||
return clazz == null ? Class.forName(name) : clazz;
|
||||
return clazz == null ? Thread.currentThread().getContextClassLoader().loadClass(name) : clazz;
|
||||
} catch (Exception ex) {
|
||||
throw new ConvertException("convert entity is " + name, ex);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.redkale.util.Attribute;
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class DeMember<R extends Reader, T, F> implements Comparable<DeMember<R, T, F>> {
|
||||
|
||||
protected int index;
|
||||
|
||||
protected final Attribute<T, F> attribute;
|
||||
|
||||
protected Decodeable<R, F> decoder;
|
||||
@@ -68,9 +70,14 @@ public final class DeMember<R extends Reader, T, F> implements Comparable<DeMemb
|
||||
return this.attribute;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int compareTo(DeMember<R, T, F> o) {
|
||||
if (o == null) return 1;
|
||||
if (o == null) return -1;
|
||||
if (this.index != o.index) return (this.index == 0 ? Integer.MAX_VALUE : this.index) - (o.index == 0 ? Integer.MAX_VALUE : o.index);
|
||||
return this.attribute.field().compareTo(o.attribute.field());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ public final class EnMember<W extends Writer, T, F> implements Comparable<EnMemb
|
||||
//final boolean isnumber;
|
||||
final boolean isbool;
|
||||
|
||||
protected int index;
|
||||
|
||||
public EnMember(Attribute<T, F> attribute, Encodeable<W, F> encoder) {
|
||||
this.attribute = attribute;
|
||||
this.encoder = encoder;
|
||||
@@ -61,9 +63,14 @@ public final class EnMember<W extends Writer, T, F> implements Comparable<EnMemb
|
||||
return attribute.field().equals(name);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int compareTo(EnMember<W, T, F> o) {
|
||||
if (o == null) return 1;
|
||||
if (o == null) return -1;
|
||||
if (this.index != o.index) return (this.index == 0 ? Integer.MAX_VALUE : this.index) - (o.index == 0 ? Integer.MAX_VALUE : o.index);
|
||||
return this.attribute.field().compareTo(o.attribute.field());
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ public final class ObjectDecoder<R extends Reader, T> implements Decodeable<R, T
|
||||
ref = factory.findRef(field);
|
||||
if (ref != null && ref.ignore()) continue;
|
||||
Type t = TypeToken.createClassType(field.getGenericType(), this.type);
|
||||
list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, field, null, null), factory.loadDecoder(t)));
|
||||
DeMember member = new DeMember(ObjectEncoder.createAttribute(factory, clazz, field, null, null), factory.loadDecoder(t));
|
||||
if (ref != null) member.index = ref.getIndex();
|
||||
list.add(member);
|
||||
}
|
||||
final boolean reversible = factory.isReversible();
|
||||
for (final Method method : clazz.getMethods()) {
|
||||
@@ -101,7 +103,9 @@ public final class ObjectDecoder<R extends Reader, T> implements Decodeable<R, T
|
||||
ref = factory.findRef(method);
|
||||
if (ref != null && ref.ignore()) continue;
|
||||
Type t = TypeToken.createClassType(method.getGenericParameterTypes()[0], this.type);
|
||||
list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, null, null, method), factory.loadDecoder(t)));
|
||||
DeMember member = new DeMember(ObjectEncoder.createAttribute(factory, clazz, null, null, method), factory.loadDecoder(t));
|
||||
if (ref != null) member.index = ref.getIndex();
|
||||
list.add(member);
|
||||
}
|
||||
if (cps != null) { //可能存在某些构造函数中的字段名不存在setter方法
|
||||
for (final String constructorField : cps) {
|
||||
|
||||
@@ -69,7 +69,9 @@ public final class ObjectEncoder<W extends Writer, T> implements Encodeable<W, T
|
||||
ref = factory.findRef(field);
|
||||
if (ref != null && ref.ignore()) continue;
|
||||
Type t = TypeToken.createClassType(field.getGenericType(), this.type);
|
||||
list.add(new EnMember(createAttribute(factory, clazz, field, null, null), factory.loadEncoder(t)));
|
||||
EnMember member = new EnMember(createAttribute(factory, clazz, field, null, null), factory.loadEncoder(t));
|
||||
if (ref != null) member.index = ref.getIndex();
|
||||
list.add(member);
|
||||
}
|
||||
for (final Method method : clazz.getMethods()) {
|
||||
if (Modifier.isStatic(method.getModifiers())) continue;
|
||||
@@ -92,7 +94,9 @@ public final class ObjectEncoder<W extends Writer, T> implements Encodeable<W, T
|
||||
ref = factory.findRef(method);
|
||||
if (ref != null && ref.ignore()) continue;
|
||||
Type t = TypeToken.createClassType(method.getGenericReturnType(), this.type);
|
||||
list.add(new EnMember(createAttribute(factory, clazz, null, method, null), factory.loadEncoder(t)));
|
||||
EnMember member = new EnMember(createAttribute(factory, clazz, null, method, null), factory.loadEncoder(t));
|
||||
if (ref != null) member.index = ref.getIndex();
|
||||
list.add(member);
|
||||
}
|
||||
this.members = list.toArray(new EnMember[list.size()]);
|
||||
Arrays.sort(this.members);
|
||||
|
||||
@@ -59,6 +59,11 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
return BsonFactory.root().getConvert();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinary() {
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------ reader -----------------------------------------------------------
|
||||
public BsonReader pollBsonReader(final ByteBuffer... buffers) {
|
||||
return new BsonByteBufferReader((ConvertMask) null, buffers);
|
||||
@@ -99,6 +104,7 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
return convertFrom(type, bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertFrom(final Type type, final byte[] bytes, final int start, final int len) {
|
||||
if (type == null) return null;
|
||||
final BsonReader in = readerPool.get();
|
||||
@@ -109,21 +115,27 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
return rs;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertFrom(final Type type, final InputStream in) {
|
||||
if (type == null || in == null) return null;
|
||||
return (T) factory.loadDecoder(type).convertFrom(new BsonStreamReader(in));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
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((ConvertMask) null, buffers));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
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));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertFrom(final Type type, final BsonReader reader) {
|
||||
if (type == null) return null;
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -152,6 +164,15 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] convertMapTo(final Object... values) {
|
||||
if (values == null) return null;
|
||||
final BsonWriter out = writerPool.get().tiny(tiny);
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
|
||||
byte[] result = out.toArray();
|
||||
writerPool.offer(out);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void convertTo(final OutputStream out, final Object value) {
|
||||
if (value == null) {
|
||||
new BsonStreamWriter(tiny, out).writeNull();
|
||||
@@ -169,6 +190,27 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
}
|
||||
}
|
||||
|
||||
public void convertMapTo(final OutputStream out, final Object... values) {
|
||||
if (values == null) {
|
||||
new BsonStreamWriter(tiny, out).writeNull();
|
||||
} else {
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(new BsonStreamWriter(tiny, out), values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Object value) {
|
||||
if (supplier == null) return null;
|
||||
BsonByteBufferWriter out = new BsonByteBufferWriter(tiny, supplier);
|
||||
if (value == null) {
|
||||
out.writeNull();
|
||||
} else {
|
||||
factory.loadEncoder(value.getClass()).convertTo(out, value);
|
||||
}
|
||||
return out.toBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Type type, final Object value) {
|
||||
if (supplier == null || type == null) return null;
|
||||
BsonByteBufferWriter out = new BsonByteBufferWriter(tiny, supplier);
|
||||
@@ -180,13 +222,14 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
return out.toBuffers();
|
||||
}
|
||||
|
||||
public ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Object value) {
|
||||
@Override
|
||||
public ByteBuffer[] convertMapTo(final Supplier<ByteBuffer> supplier, final Object... values) {
|
||||
if (supplier == null) return null;
|
||||
BsonByteBufferWriter out = new BsonByteBufferWriter(tiny, supplier);
|
||||
if (value == null) {
|
||||
if (values == null) {
|
||||
out.writeNull();
|
||||
} else {
|
||||
factory.loadEncoder(value.getClass()).convertTo(out, value);
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
|
||||
}
|
||||
return out.toBuffers();
|
||||
}
|
||||
@@ -204,6 +247,14 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
factory.loadEncoder(type).convertTo(writer, value);
|
||||
}
|
||||
|
||||
public void convertMapTo(final BsonWriter writer, final Object... values) {
|
||||
if (values == null) {
|
||||
writer.writeNull();
|
||||
} else {
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(writer, values);
|
||||
}
|
||||
}
|
||||
|
||||
public BsonWriter convertToWriter(final Object value) {
|
||||
if (value == null) return null;
|
||||
return convertToWriter(value.getClass(), value);
|
||||
@@ -216,4 +267,9 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
|
||||
return out;
|
||||
}
|
||||
|
||||
public BsonWriter convertMapToWriter(final Object... values) {
|
||||
final BsonWriter out = writerPool.get().tiny(tiny);
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ public class BsonReader extends Reader {
|
||||
* 跳过属性的值
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public final void skipValue() {
|
||||
if (typeval == 0) return;
|
||||
final byte val = this.typeval;
|
||||
|
||||
@@ -32,7 +32,7 @@ public final class DateSimpledCoder<R extends Reader, W extends Writer> extends
|
||||
@Override
|
||||
public Date convertFrom(R in) {
|
||||
long t = in.readLong();
|
||||
return t == 0 ? null : new Date();
|
||||
return t == 0 ? null : new Date(t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.net.*;
|
||||
* @param <R> Reader输入的子类型
|
||||
* @param <W> Writer输出的子类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class InetAddressSimpledCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, InetAddress> {
|
||||
|
||||
public static final InetAddressSimpledCoder instance = new InetAddressSimpledCoder();
|
||||
@@ -50,6 +51,7 @@ public final class InetAddressSimpledCoder<R extends Reader, W extends Writer> e
|
||||
* @param <R> Reader输入的子类型
|
||||
* @param <W> Writer输出的子类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final static class InetSocketAddressSimpledCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, InetSocketAddress> {
|
||||
|
||||
public static final InetSocketAddressSimpledCoder instance = new InetSocketAddressSimpledCoder();
|
||||
|
||||
@@ -37,7 +37,7 @@ public class TypeSimpledCoder<R extends Reader, W extends Writer> extends Simple
|
||||
String str = in.readSmallString();
|
||||
if (str == null) return null;
|
||||
try {
|
||||
return Class.forName(str);
|
||||
return Thread.currentThread().getContextClassLoader().loadClass(str);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public class JsonByteBufferReader extends JsonReader {
|
||||
public final boolean hasNext() {
|
||||
char ch = nextGoodChar();
|
||||
if (ch == ',') return true;
|
||||
if (ch == '}' || ch == ']') return false;
|
||||
if (ch == '}' || ch == ']' || ch == 0) return false;
|
||||
backChar(ch); // { [ 交由 readObjectB 或 readMapB 或 readArrayB 读取
|
||||
return true;
|
||||
}
|
||||
@@ -253,6 +253,7 @@ public class JsonByteBufferReader extends JsonReader {
|
||||
throw new ConvertException("illegal escape(" + c + ") (position = " + this.position + ")");
|
||||
}
|
||||
} else if (ch == ',' || ch == ']' || ch == '}' || ch <= ' ' || ch == ':') { // ch <= ' ' 包含 0
|
||||
backChar(ch);
|
||||
break;
|
||||
} else {
|
||||
sb.append(ch);
|
||||
|
||||
@@ -46,6 +46,11 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
return JsonFactory.root().getConvert();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinary() {
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------ reader -----------------------------------------------------------
|
||||
public JsonReader pollJsonReader(final ByteBuffer... buffers) {
|
||||
return new JsonByteBufferReader((ConvertMask) null, buffers);
|
||||
@@ -109,11 +114,13 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
return (T) factory.loadDecoder(type).convertFrom(new JsonStreamReader(in));
|
||||
}
|
||||
|
||||
@Override
|
||||
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((ConvertMask) null, buffers));
|
||||
}
|
||||
|
||||
@Override
|
||||
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));
|
||||
@@ -142,6 +149,15 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
return result;
|
||||
}
|
||||
|
||||
public String convertMapTo(final Object... values) {
|
||||
if (values == null) return "null";
|
||||
final JsonWriter out = writerPool.get().tiny(tiny);
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
|
||||
String result = out.toString();
|
||||
writerPool.offer(out);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void convertTo(final OutputStream out, final Object value) {
|
||||
if (value == null) {
|
||||
new JsonStreamWriter(tiny, out).writeNull();
|
||||
@@ -159,6 +175,15 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
}
|
||||
}
|
||||
|
||||
public void convertMapTo(final OutputStream out, final Object... values) {
|
||||
if (values == null) {
|
||||
new JsonStreamWriter(tiny, out).writeNull();
|
||||
} else {
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(new JsonStreamWriter(tiny, out), values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Object value) {
|
||||
if (supplier == null) return null;
|
||||
JsonByteBufferWriter out = new JsonByteBufferWriter(tiny, null, supplier);
|
||||
@@ -170,6 +195,7 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
return out.toBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] convertTo(final Supplier<ByteBuffer> supplier, final Type type, final Object value) {
|
||||
if (supplier == null || type == null) return null;
|
||||
JsonByteBufferWriter out = new JsonByteBufferWriter(tiny, null, supplier);
|
||||
@@ -181,6 +207,18 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
return out.toBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] convertMapTo(final Supplier<ByteBuffer> supplier, final Object... values) {
|
||||
if (supplier == null) return null;
|
||||
JsonByteBufferWriter out = new JsonByteBufferWriter(tiny, null, supplier);
|
||||
if (values == null) {
|
||||
out.writeNull();
|
||||
} else {
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
|
||||
}
|
||||
return out.toBuffers();
|
||||
}
|
||||
|
||||
public void convertTo(final JsonWriter writer, final Object value) {
|
||||
if (value == null) {
|
||||
writer.writeNull();
|
||||
@@ -198,6 +236,14 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
}
|
||||
}
|
||||
|
||||
public void convertMapTo(final JsonWriter writer, final Object... values) {
|
||||
if (values == null) {
|
||||
writer.writeNull();
|
||||
} else {
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(writer, values);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonWriter convertToWriter(final Object value) {
|
||||
if (value == null) return null;
|
||||
return convertToWriter(value.getClass(), value);
|
||||
@@ -209,4 +255,10 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
|
||||
factory.loadEncoder(type).convertTo(out, value);
|
||||
return out;
|
||||
}
|
||||
|
||||
public JsonWriter convertMapToWriter(final Object... values) {
|
||||
final JsonWriter out = writerPool.get().tiny(tiny);
|
||||
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.redkale.util.*;
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class JsonFactory extends ConvertFactory<JsonReader, JsonWriter> {
|
||||
|
||||
private static final JsonFactory instance = new JsonFactory(null, Boolean.getBoolean("convert.json.tiny"));
|
||||
|
||||
@@ -404,6 +404,7 @@ public class JsonReader extends Reader {
|
||||
@Override
|
||||
public final DeMember readFieldName(final DeMember[] members) {
|
||||
final String exceptedfield = this.readSmallString();
|
||||
if(exceptedfield == null) return null;
|
||||
final int len = members.length;
|
||||
if (this.fieldIndex >= len) this.fieldIndex = 0;
|
||||
for (int k = this.fieldIndex; k < len; k++) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.redkale.util.*;
|
||||
* @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 {
|
||||
public abstract class Filter<C extends Context, R extends Request<C>, P extends Response<C, R>> implements Comparable {
|
||||
|
||||
AnyValue _conf; //当前Filter的配置
|
||||
|
||||
@@ -33,11 +33,6 @@ public abstract class Filter<C extends Context, R extends Request<C>, P extends
|
||||
public void destroy(C context, AnyValue config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 值越小越靠前执行
|
||||
*
|
||||
@@ -48,7 +43,7 @@ public abstract class Filter<C extends Context, R extends Request<C>, P extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
public final int compareTo(Object o) {
|
||||
if (!(o instanceof Filter)) return 1;
|
||||
return this.getIndex() - ((Filter) o).getIndex();
|
||||
}
|
||||
|
||||
@@ -63,6 +63,24 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsServlet(Class<? extends S> servletClass) {
|
||||
synchronized (lock1) {
|
||||
for (S servlet : new HashSet<>(servlets)) {
|
||||
if (servlet.getClass().equals(servletClass)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsServlet(String servletClassName) {
|
||||
synchronized (lock1) {
|
||||
for (S servlet : new HashSet<>(servlets)) {
|
||||
if (servlet.getClass().getName().equals(servletClassName)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void putMapping(K key, S servlet) {
|
||||
synchronized (lock2) {
|
||||
Map<K, S> newmappings = new HashMap<>(mappings);
|
||||
@@ -100,6 +118,7 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void init(C context, AnyValue config) {
|
||||
synchronized (filters) {
|
||||
if (!filters.isEmpty()) {
|
||||
@@ -118,6 +137,7 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void destroy(C context, AnyValue config) {
|
||||
synchronized (filters) {
|
||||
if (!filters.isEmpty()) {
|
||||
@@ -128,28 +148,45 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addFilter(Filter<C, R, P> filter, AnyValue conf) {
|
||||
filter._conf = conf;
|
||||
synchronized (filters) {
|
||||
this.filters.add(filter);
|
||||
Collections.sort(this.filters);
|
||||
}
|
||||
}
|
||||
|
||||
public Filter<C, R, P> removeFilter(Class<? extends Filter<C, R, P>> filterClass) {
|
||||
public <T extends Filter<C, R, P>> T removeFilter(Class<T> filterClass) {
|
||||
return removeFilter(f -> filterClass.equals(f.getClass()));
|
||||
}
|
||||
|
||||
public Filter<C, R, P> removeFilter(String filterName) {
|
||||
return removeFilter(f -> filterName.equals(f.resourceName()));
|
||||
public boolean containsFilter(Class<? extends Filter> filterClass) {
|
||||
if (this.headFilter == null || filterClass == null) return false;
|
||||
Filter filter = this.headFilter;
|
||||
do {
|
||||
if (filter.getClass().equals(filterClass)) return true;
|
||||
} while ((filter = filter._next) != null);
|
||||
return false;
|
||||
}
|
||||
|
||||
public Filter<C, R, P> removeFilter(Predicate<Filter<C, R, P>> predicate) {
|
||||
public boolean containsFilter(String filterClassName) {
|
||||
if (this.headFilter == null || filterClassName == null) return false;
|
||||
Filter filter = this.headFilter;
|
||||
do {
|
||||
if (filter.getClass().getName().equals(filterClassName)) return true;
|
||||
} while ((filter = filter._next) != null);
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Filter<C, R, P>> T removeFilter(Predicate<T> predicate) {
|
||||
if (this.headFilter == null || predicate == null) return null;
|
||||
synchronized (filters) {
|
||||
Filter filter = this.headFilter;
|
||||
Filter prev = null;
|
||||
do {
|
||||
if (predicate.test(filter)) break;
|
||||
if (predicate.test((T) filter)) break;
|
||||
prev = filter;
|
||||
} while ((filter = filter._next) != null);
|
||||
if (filter != null) {
|
||||
@@ -161,10 +198,16 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
filter._next = null;
|
||||
this.filters.remove(filter);
|
||||
}
|
||||
return filter;
|
||||
return (T) filter;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Filter<C, R, P>> List<T> getFilters() {
|
||||
return (List) new ArrayList<>(filters);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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 {
|
||||
@@ -226,7 +269,7 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
|
||||
servlet._conf = conf;
|
||||
}
|
||||
|
||||
public Set<S> getServlets() {
|
||||
return new LinkedHashSet<>(servlets);
|
||||
public List<S> getServlets() {
|
||||
return new ArrayList<>(servlets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public abstract class Request<C extends Context> {
|
||||
* 返回值:Integer.MIN_VALUE: 帧数据; -1:数据不合法; 0:解析完毕; >0: 需再读取的字节数。
|
||||
*
|
||||
* @param buffer ByteBuffer对象
|
||||
*
|
||||
* @return 缺少的字节数
|
||||
*/
|
||||
protected abstract int readHeader(ByteBuffer buffer);
|
||||
@@ -59,6 +60,7 @@ public abstract class Request<C extends Context> {
|
||||
* 读取buffer,并返回读取的有效数据长度
|
||||
*
|
||||
* @param buffer ByteBuffer对象
|
||||
*
|
||||
* @return 有效数据长度
|
||||
*/
|
||||
protected abstract int readBody(ByteBuffer buffer);
|
||||
@@ -82,8 +84,9 @@ public abstract class Request<C extends Context> {
|
||||
return (T) properties.get(name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T removeProperty(String name) {
|
||||
return (T)properties.remove(name);
|
||||
return (T) properties.remove(name);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getProperties() {
|
||||
@@ -100,8 +103,9 @@ public abstract class Request<C extends Context> {
|
||||
return (T) attributes.get(name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T removeAttribute(String name) {
|
||||
return (T)attributes.remove(name);
|
||||
return (T) attributes.remove(name);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* 协议响应对象
|
||||
@@ -113,8 +114,7 @@ public abstract class Response<C extends Context, R extends Request<C>> {
|
||||
try {
|
||||
recycleListener.accept(request, this);
|
||||
} catch (Exception e) {
|
||||
System.err.println(request);
|
||||
e.printStackTrace();
|
||||
context.logger.log(Level.WARNING, "Response.recycleListener error, request = " + request, e);
|
||||
}
|
||||
recycleListener = null;
|
||||
}
|
||||
@@ -170,18 +170,6 @@ public abstract class Response<C extends Context, R extends Request<C>> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 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;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package org.redkale.net;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.*;
|
||||
@@ -14,7 +13,7 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
import org.redkale.util.AnyValue;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -100,11 +99,11 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
this.config = config;
|
||||
this.address = new InetSocketAddress(config.getValue("host", "0.0.0.0"), config.getIntValue("port", 80));
|
||||
this.charset = Charset.forName(config.getValue("charset", "UTF-8"));
|
||||
this.backlog = config.getIntValue("backlog", 8 * 1024);
|
||||
this.readTimeoutSecond = config.getIntValue("readTimeoutSecond", 0);
|
||||
this.writeTimeoutSecond = config.getIntValue("writeTimeoutSecond", 0);
|
||||
this.maxbody = config.getIntValue("maxbody", 64 * 1024);
|
||||
int bufCapacity = config.getIntValue("bufferCapacity", 8 * 1024);
|
||||
this.backlog = parseLenth(config.getValue("backlog"), 8 * 1024);
|
||||
this.maxbody = parseLenth(config.getValue("maxbody"), 64 * 1024);
|
||||
int bufCapacity = parseLenth(config.getValue("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);
|
||||
@@ -121,6 +120,24 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
});
|
||||
}
|
||||
|
||||
protected static int parseLenth(String value, int defValue) {
|
||||
if (value == null) return defValue;
|
||||
value = value.toUpperCase().replace("B", "");
|
||||
if (value.endsWith("G")) return Integer.decode(value.replace("G", "")) * 1024 * 1024 * 1024;
|
||||
if (value.endsWith("M")) return Integer.decode(value.replace("M", "")) * 1024 * 1024;
|
||||
if (value.endsWith("K")) return Integer.decode(value.replace("K", "")) * 1024;
|
||||
return Integer.decode(value);
|
||||
}
|
||||
|
||||
protected static long parseLenth(String value, long defValue) {
|
||||
if (value == null) return defValue;
|
||||
value = value.toUpperCase().replace("B", "");
|
||||
if (value.endsWith("G")) return Long.decode(value.replace("G", "")) * 1024 * 1024 * 1024;
|
||||
if (value.endsWith("M")) return Long.decode(value.replace("M", "")) * 1024 * 1024;
|
||||
if (value.endsWith("K")) return Long.decode(value.replace("K", "")) * 1024;
|
||||
return Long.decode(value);
|
||||
}
|
||||
|
||||
public void destroy(final AnyValue config) throws Exception {
|
||||
this.prepare.destroy(context, config);
|
||||
}
|
||||
@@ -185,6 +202,52 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
logger.info(this.getClass().getSimpleName() + " shutdown in " + e + " ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在Filter
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param filterClass Filter类
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public <T extends Filter> boolean containsFilter(Class<T> filterClass) {
|
||||
return this.prepare.containsFilter(filterClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在Filter
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @param filterClassName Filter类
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public <T extends Filter> boolean containsFilter(String filterClassName) {
|
||||
return this.prepare.containsFilter(filterClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在Servlet
|
||||
*
|
||||
* @param servletClass Servlet类
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean containsServlet(Class<? extends S> servletClass) {
|
||||
return this.prepare.containsServlet(servletClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在Servlet
|
||||
*
|
||||
* @param servletClassName Servlet类
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean containsServlet(String servletClassName) {
|
||||
return this.prepare.containsServlet(servletClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁Servlet
|
||||
*
|
||||
@@ -217,7 +280,7 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
return new DecimalFormat(sf);
|
||||
}
|
||||
|
||||
public static URL[] loadLib(final Logger logger, final String lib) throws Exception {
|
||||
public static URL[] loadLib(final RedkaleClassLoader classLoader, final Logger logger, final String lib) throws Exception {
|
||||
if (lib == null || lib.isEmpty()) return new URL[0];
|
||||
final Set<URL> set = new HashSet<>();
|
||||
for (String s : lib.split(";")) {
|
||||
@@ -235,17 +298,8 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
|
||||
}
|
||||
}
|
||||
if (set.isEmpty()) return new URL[0];
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
if (cl instanceof URLClassLoader) {
|
||||
URLClassLoader loader = (URLClassLoader) cl;
|
||||
final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
|
||||
method.setAccessible(true);
|
||||
for (URL url : set) {
|
||||
method.invoke(loader, url);
|
||||
//if (logger != null) logger.log(Level.INFO, "Server found ClassPath({0})", url);
|
||||
}
|
||||
} else {
|
||||
Thread.currentThread().setContextClassLoader(new URLClassLoader(set.toArray(new URL[set.size()]), cl));
|
||||
classLoader.addURL(url);
|
||||
}
|
||||
List<URL> list = new ArrayList<>(set);
|
||||
Collections.sort(list, (URL o1, URL o2) -> o1.getFile().compareTo(o2.getFile()));
|
||||
|
||||
@@ -11,8 +11,9 @@ import java.nio.channels.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.redkale.util.ObjectPool;
|
||||
import org.redkale.convert.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
* 传输客户端
|
||||
@@ -53,19 +54,24 @@ public final class Transport {
|
||||
|
||||
protected final InetSocketAddress clientAddress;
|
||||
|
||||
protected InetSocketAddress[] remoteAddres = new InetSocketAddress[0];
|
||||
protected TransportAddress[] transportAddres = new TransportAddress[0];
|
||||
|
||||
protected final ObjectPool<ByteBuffer> bufferPool;
|
||||
|
||||
//负载均衡策略
|
||||
protected final TransportStrategy strategy;
|
||||
|
||||
protected final ConcurrentHashMap<SocketAddress, BlockingQueue<AsyncConnection>> connPool = new ConcurrentHashMap<>();
|
||||
|
||||
public Transport(String name, String subprotocol, final ObjectPool<ByteBuffer> transportBufferPool,
|
||||
final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress, final Collection<InetSocketAddress> addresses) {
|
||||
this(name, DEFAULT_PROTOCOL, subprotocol, transportBufferPool, transportChannelGroup, clientAddress, addresses);
|
||||
final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress,
|
||||
final Collection<InetSocketAddress> addresses, final TransportStrategy strategy) {
|
||||
this(name, DEFAULT_PROTOCOL, subprotocol, transportBufferPool, transportChannelGroup, clientAddress, addresses, strategy);
|
||||
}
|
||||
|
||||
public Transport(String name, String protocol, String subprotocol, final ObjectPool<ByteBuffer> transportBufferPool,
|
||||
final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress, final Collection<InetSocketAddress> addresses) {
|
||||
final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress,
|
||||
final Collection<InetSocketAddress> addresses, final TransportStrategy strategy) {
|
||||
this.name = name;
|
||||
this.subprotocol = subprotocol == null ? "" : subprotocol.trim();
|
||||
this.protocol = protocol;
|
||||
@@ -73,44 +79,50 @@ public final class Transport {
|
||||
this.group = transportChannelGroup;
|
||||
this.bufferPool = transportBufferPool;
|
||||
this.clientAddress = clientAddress;
|
||||
this.strategy = strategy;
|
||||
updateRemoteAddresses(addresses);
|
||||
}
|
||||
|
||||
public Transport(final Collection<Transport> transports) {
|
||||
Transport first = null;
|
||||
List<String> tmpgroup = new ArrayList<>();
|
||||
if (transports != null) {
|
||||
for (Transport t : transports) {
|
||||
if (first == null) first = t;
|
||||
tmpgroup.add(t.name);
|
||||
}
|
||||
}
|
||||
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.subprotocol = first.subprotocol;
|
||||
this.protocol = first.protocol;
|
||||
this.tcp = "TCP".equalsIgnoreCase(first.protocol);
|
||||
this.group = first.group;
|
||||
this.bufferPool = first.bufferPool;
|
||||
this.clientAddress = first.clientAddress;
|
||||
Set<InetSocketAddress> addrs = new HashSet<>();
|
||||
transports.forEach(t -> addrs.addAll(Arrays.asList(t.getRemoteAddresses())));
|
||||
updateRemoteAddresses(addrs);
|
||||
}
|
||||
|
||||
public final InetSocketAddress[] updateRemoteAddresses(final Collection<InetSocketAddress> addresses) {
|
||||
InetSocketAddress[] oldAddresses = this.remoteAddres;
|
||||
List<InetSocketAddress> list = new ArrayList<>();
|
||||
TransportAddress[] oldAddresses = this.transportAddres;
|
||||
List<TransportAddress> list = new ArrayList<>();
|
||||
if (addresses != null) {
|
||||
for (InetSocketAddress addr : addresses) {
|
||||
if (clientAddress != null && clientAddress.equals(addr)) continue;
|
||||
list.add(addr);
|
||||
list.add(new TransportAddress(addr));
|
||||
}
|
||||
}
|
||||
this.remoteAddres = list.toArray(new InetSocketAddress[list.size()]);
|
||||
return oldAddresses;
|
||||
this.transportAddres = list.toArray(new TransportAddress[list.size()]);
|
||||
|
||||
InetSocketAddress[] rs = new InetSocketAddress[oldAddresses.length];
|
||||
for (int i = 0; i < rs.length; i++) {
|
||||
rs[i] = oldAddresses[i].getAddress();
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public final boolean addRemoteAddresses(final InetSocketAddress addr) {
|
||||
if (addr == null) return false;
|
||||
synchronized (this) {
|
||||
if (this.transportAddres == null) {
|
||||
this.transportAddres = new TransportAddress[]{new TransportAddress(addr)};
|
||||
} else {
|
||||
for (TransportAddress i : this.transportAddres) {
|
||||
if (addr.equals(i.address)) return false;
|
||||
}
|
||||
this.transportAddres = Utility.append(transportAddres, new TransportAddress(addr));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean removeRemoteAddresses(InetSocketAddress addr) {
|
||||
if (addr == null) return false;
|
||||
if (this.transportAddres == null) return false;
|
||||
synchronized (this) {
|
||||
this.transportAddres = Utility.remove(transportAddres, new TransportAddress(addr));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -129,13 +141,25 @@ public final class Transport {
|
||||
return clientAddress;
|
||||
}
|
||||
|
||||
public TransportAddress[] getTransportAddresses() {
|
||||
return transportAddres;
|
||||
}
|
||||
|
||||
public InetSocketAddress[] getRemoteAddresses() {
|
||||
return remoteAddres;
|
||||
InetSocketAddress[] rs = new InetSocketAddress[transportAddres.length];
|
||||
for (int i = 0; i < rs.length; i++) {
|
||||
rs[i] = transportAddres[i].getAddress();
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
public ConcurrentHashMap<SocketAddress, BlockingQueue<AsyncConnection>> getAsyncConnectionPool() {
|
||||
return connPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Transport.class.getSimpleName() + "{name = " + name + ", protocol = " + protocol + ", clientAddress = " + clientAddress + ", remoteAddres = " + Arrays.toString(remoteAddres) + "}";
|
||||
return Transport.class.getSimpleName() + "{name = " + name + ", protocol = " + protocol + ", clientAddress = " + clientAddress + ", remoteAddres = " + Arrays.toString(transportAddres) + "}";
|
||||
}
|
||||
|
||||
public ByteBuffer pollBuffer() {
|
||||
@@ -159,32 +183,57 @@ public final class Transport {
|
||||
}
|
||||
|
||||
public AsyncConnection pollConnection(SocketAddress addr) {
|
||||
if (addr == null && remoteAddres.length == 1) addr = remoteAddres[0];
|
||||
if (this.strategy != null) return strategy.pollConnection(addr, this);
|
||||
if (addr == null && this.transportAddres.length == 1) addr = this.transportAddres[0].address;
|
||||
final boolean rand = addr == null;
|
||||
if (rand && remoteAddres.length < 1) throw new RuntimeException("Transport (" + this.name + ") have no remoteAddress list");
|
||||
if (rand && this.transportAddres.length < 1) throw new RuntimeException("Transport (" + this.name + ") have no remoteAddress list");
|
||||
try {
|
||||
if (tcp) {
|
||||
AsynchronousSocketChannel channel = null;
|
||||
if (rand) { //取地址
|
||||
for (int i = 0; i < remoteAddres.length; i++) {
|
||||
addr = remoteAddres[i];
|
||||
BlockingQueue<AsyncConnection> queue = connPool.get(addr);
|
||||
if (queue != null && !queue.isEmpty()) {
|
||||
TransportAddress transportAddr;
|
||||
boolean tryed = false;
|
||||
for (int i = 0; i < transportAddres.length; i++) {
|
||||
transportAddr = transportAddres[i];
|
||||
addr = transportAddr.address;
|
||||
if (!transportAddr.enable) continue;
|
||||
final BlockingQueue<AsyncConnection> queue = transportAddr.conns;
|
||||
if (!queue.isEmpty()) {
|
||||
AsyncConnection conn;
|
||||
while ((conn = queue.poll()) != null) {
|
||||
if (conn.isOpen()) return conn;
|
||||
}
|
||||
}
|
||||
tryed = true;
|
||||
if (channel == null) {
|
||||
channel = AsynchronousSocketChannel.open(group);
|
||||
if (supportTcpNoDelay) channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||
}
|
||||
try {
|
||||
channel.connect(addr).get(2, TimeUnit.SECONDS);
|
||||
transportAddr.enable = true;
|
||||
break;
|
||||
} catch (Exception iex) {
|
||||
iex.printStackTrace();
|
||||
if (i == remoteAddres.length - 1) channel = null;
|
||||
transportAddr.enable = false;
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
if (channel == null && !tryed) {
|
||||
for (int i = 0; i < transportAddres.length; i++) {
|
||||
transportAddr = transportAddres[i];
|
||||
addr = transportAddr.address;
|
||||
if (channel == null) {
|
||||
channel = AsynchronousSocketChannel.open(group);
|
||||
if (supportTcpNoDelay) channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||
}
|
||||
try {
|
||||
channel.connect(addr).get(2, TimeUnit.SECONDS);
|
||||
transportAddr.enable = true;
|
||||
break;
|
||||
} catch (Exception iex) {
|
||||
transportAddr.enable = false;
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -195,7 +244,7 @@ public final class Transport {
|
||||
if (channel == null) return null;
|
||||
return AsyncConnection.create(channel, addr, 3000, 3000);
|
||||
} else { // UDP
|
||||
if (rand) addr = remoteAddres[0];
|
||||
if (rand) addr = this.transportAddres[0].address;
|
||||
DatagramChannel channel = DatagramChannel.open();
|
||||
channel.configureBlocking(true);
|
||||
channel.connect(addr);
|
||||
@@ -257,4 +306,54 @@ public final class Transport {
|
||||
});
|
||||
}
|
||||
|
||||
public static class TransportAddress {
|
||||
|
||||
protected InetSocketAddress address;
|
||||
|
||||
protected volatile boolean enable;
|
||||
|
||||
protected final BlockingQueue<AsyncConnection> conns = new ArrayBlockingQueue<>(MAX_POOL_LIMIT);
|
||||
|
||||
public TransportAddress(InetSocketAddress address) {
|
||||
this.address = address;
|
||||
this.enable = true;
|
||||
}
|
||||
|
||||
@java.beans.ConstructorProperties({"address", "enable"})
|
||||
public TransportAddress(InetSocketAddress address, boolean enable) {
|
||||
this.address = address;
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public InetSocketAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
@ConvertColumn(ignore = true)
|
||||
public BlockingQueue<AsyncConnection> getConns() {
|
||||
return conns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.address.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
final TransportAddress other = (TransportAddress) obj;
|
||||
return this.address.equals(other.address);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return JsonConvert.root().convertTo(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.nio.channels.AsynchronousChannelGroup;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.redkale.service.Service;
|
||||
import org.redkale.util.ObjectPool;
|
||||
|
||||
@@ -42,10 +43,19 @@ public class TransportFactory {
|
||||
|
||||
protected final List<WeakReference<Service>> services = new CopyOnWriteArrayList<>();
|
||||
|
||||
public TransportFactory(ExecutorService executor, ObjectPool<ByteBuffer> bufferPool, AsynchronousChannelGroup channelGroup) {
|
||||
//负载均衡策略
|
||||
protected final TransportStrategy strategy;
|
||||
|
||||
public TransportFactory(ExecutorService executor, ObjectPool<ByteBuffer> bufferPool, AsynchronousChannelGroup channelGroup,
|
||||
final TransportStrategy strategy) {
|
||||
this.executor = executor;
|
||||
this.bufferPool = bufferPool;
|
||||
this.channelGroup = channelGroup;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public TransportFactory(ExecutorService executor, ObjectPool<ByteBuffer> bufferPool, AsynchronousChannelGroup channelGroup) {
|
||||
this(executor, bufferPool, channelGroup, null);
|
||||
}
|
||||
|
||||
public String findGroupName(InetSocketAddress addr) {
|
||||
@@ -53,14 +63,24 @@ public class TransportFactory {
|
||||
return groupAddrs.get(addr);
|
||||
}
|
||||
|
||||
public TransportGroupInfo findGroupInfo2(String group) {
|
||||
public TransportGroupInfo findGroupInfo(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 boolean addGroupInfo(String groupName, InetSocketAddress... addrs) {
|
||||
addGroupInfo(new TransportGroupInfo(groupName, addrs));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeGroupInfo(String groupName, InetSocketAddress addr) {
|
||||
if (groupName == null || groupName.isEmpty() || addr == null) return false;
|
||||
if (!groupName.equals(groupAddrs.get(addr))) return false;
|
||||
TransportGroupInfo group = groupInfos.get(groupName);
|
||||
if (group == null) return false;
|
||||
group.removeAddress(addr);
|
||||
groupAddrs.remove(addr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public TransportFactory addGroupInfo(String name, Set<InetSocketAddress> addrs) {
|
||||
@@ -116,20 +136,24 @@ public class TransportFactory {
|
||||
}
|
||||
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);
|
||||
return new Transport(groups.stream().sorted().collect(Collectors.joining(";")), info.protocol, info.subprotocol, this.bufferPool, this.channelGroup, sncpAddress, addresses, this.strategy);
|
||||
}
|
||||
|
||||
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);
|
||||
return new Transport(groupName, info.protocol, info.subprotocol, this.bufferPool, this.channelGroup, sncpAddress, info.addresses, this.strategy);
|
||||
}
|
||||
|
||||
public ExecutorService getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public List<TransportGroupInfo> getGroupInfos() {
|
||||
return new ArrayList<>(this.groupInfos.values());
|
||||
}
|
||||
|
||||
public void addSncpService(Service service) {
|
||||
if (service == null) return;
|
||||
services.add(new WeakReference<>(service));
|
||||
|
||||
@@ -90,21 +90,35 @@ public class TransportGroupInfo {
|
||||
}
|
||||
|
||||
public boolean containsAddress(InetSocketAddress addr) {
|
||||
synchronized (this) {
|
||||
if (this.addresses == null) return false;
|
||||
return this.addresses.contains(addr);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAddress(InetSocketAddress addr) {
|
||||
if (addr == null) return;
|
||||
synchronized (this) {
|
||||
if (this.addresses == null) return;
|
||||
this.addresses.remove(addr);
|
||||
}
|
||||
}
|
||||
|
||||
public void putAddress(InetSocketAddress addr) {
|
||||
if (addr == null) return;
|
||||
synchronized (this) {
|
||||
if (this.addresses == null) this.addresses = new HashSet<>();
|
||||
this.addresses.add(addr);
|
||||
}
|
||||
}
|
||||
|
||||
public void putAddress(Set<InetSocketAddress> addrs) {
|
||||
if (addrs == null) return;
|
||||
synchronized (this) {
|
||||
if (this.addresses == null) this.addresses = new HashSet<>();
|
||||
this.addresses.addAll(addrs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
21
src/org/redkale/net/TransportStrategy.java
Normal file
21
src/org/redkale/net/TransportStrategy.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.SocketAddress;
|
||||
|
||||
/**
|
||||
* 远程请求的负载均衡策略
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public interface TransportStrategy {
|
||||
|
||||
public AsyncConnection pollConnection(SocketAddress addr, Transport transport);
|
||||
}
|
||||
@@ -1,26 +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.IOException;
|
||||
|
||||
/**
|
||||
* 默认Servlet, 没有配置RestServlet实现类则使用该默认类
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultRestServlet extends RestHttpServlet<Object> {
|
||||
|
||||
@Override
|
||||
protected Object currentUser(HttpRequest req) throws IOException {
|
||||
return new Object();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,538 +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 org.redkale.net.Response;
|
||||
import org.redkale.net.Request;
|
||||
import org.redkale.util.AnyValue;
|
||||
import java.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
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.service.RetResult;
|
||||
|
||||
/**
|
||||
* 直接只用HttpServlet代替, 将在1.9版本移除
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class HttpBaseServlet extends HttpServlet {
|
||||
|
||||
/**
|
||||
* 使用 org.redkale.util.AuthIgnore 代替
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD, TYPE})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface AuthIgnore {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 配合 @WebParam 使用。
|
||||
* 用于对@WebParam中参数的来源类型
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
protected enum ParamSourceType {
|
||||
|
||||
PARAMETER, HEADER, COOKIE;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 使用 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(); //参数名
|
||||
|
||||
Class type(); //参数的数据类型
|
||||
|
||||
String comment() default ""; //备注描述
|
||||
|
||||
ParamSourceType src() default ParamSourceType.PARAMETER; //参数来源类型
|
||||
|
||||
int radix() default 10; //转换数字byte/short/int/long时所用的进制数, 默认10进制
|
||||
|
||||
boolean required() default true; //参数是否必传
|
||||
}
|
||||
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
protected @interface WebParams {
|
||||
|
||||
WebParam[] value();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 org.redkale.net.http.HttpMapping 替代。
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface WebAction {
|
||||
|
||||
int actionid() default 0;
|
||||
|
||||
String url();
|
||||
|
||||
String[] methods() default {};//允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法
|
||||
|
||||
String comment() default ""; //备注描述
|
||||
|
||||
boolean inherited() default true; //是否能被继承, 当 HttpBaseServlet 被继承后该方法是否能被子类继承
|
||||
|
||||
String result() default "Object"; //输出结果的数据类型
|
||||
|
||||
Class[] results() default {}; //输出结果的数据类型集合,由于结果类型可能是泛型而注解的参数值不支持泛型,因此加入明细数据类型集合
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 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;
|
||||
|
||||
String url();
|
||||
|
||||
String[] methods() default {};//允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法
|
||||
|
||||
String comment() default ""; //备注描述
|
||||
|
||||
boolean inherited() default true; //是否能被继承, 当 HttpBaseServlet 被继承后该方法是否能被子类继承
|
||||
|
||||
String result() default "Object"; //输出结果的数据类型
|
||||
|
||||
Class[] results() default {}; //输出结果的数据类型集合,由于结果类型可能是泛型而注解的参数值不支持泛型,因此加入明细数据类型集合
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 org.redkale.net.http.HttpMapping.cacheseconds 替代。
|
||||
*
|
||||
* @deprecated
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Deprecated
|
||||
protected @interface HttpCacheable {
|
||||
|
||||
/**
|
||||
* 超时的秒数
|
||||
*
|
||||
* @return 超时秒数
|
||||
*/
|
||||
int seconds() default 15;
|
||||
}
|
||||
|
||||
private Map.Entry<String, Entry>[] mappings;
|
||||
|
||||
private final HttpServlet authSuccessServlet = new HttpServlet() {
|
||||
@Override
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
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()) { //缓存有效
|
||||
response.setStatus(ce.status);
|
||||
response.setContentType(ce.contentType);
|
||||
response.finish(ce.getBuffers());
|
||||
return;
|
||||
}
|
||||
response.setBufferHandler(entry.cacheHandler);
|
||||
}
|
||||
entry.servlet.execute(request, response);
|
||||
}
|
||||
};
|
||||
|
||||
private final HttpServlet preSuccessServlet = new HttpServlet() {
|
||||
@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.setAttribute("_redkale_entry", entry);
|
||||
if (entry.ignore) {
|
||||
authSuccessServlet.execute(request, response);
|
||||
} else {
|
||||
HttpBaseServlet.this.authenticate(entry.moduleid, entry.actionid, request, response, authSuccessServlet);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 预执行方法,在execute方法之前运行,通常用于常规统计或基础检测,例如 : <br>
|
||||
* <blockquote><pre>
|
||||
* @Override
|
||||
* public void preExecute(final HttpRequest request, final HttpResponse response, HttpServlet next) throws IOException {
|
||||
* if (finer) response.setRecycleListener((req, resp) -> { //记录处理时间比较长的请求
|
||||
* long e = System.currentTimeMillis() - ((HttpRequest) req).getCreatetime();
|
||||
* if (e > 200) logger.finer("http-execute-cost-time: " + e + " ms. request = " + req);
|
||||
* });
|
||||
* next.execute(request, response);
|
||||
* }
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
*
|
||||
* @param request HttpRequest
|
||||
* @param response HttpResponse
|
||||
* @param next HttpServlet
|
||||
*
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public void preExecute(HttpRequest request, HttpResponse response, final HttpServlet next) throws IOException {
|
||||
next.execute(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 public void authenticate(int moduleid, int actionid, HttpRequest request, HttpResponse response, final HttpServlet next) throws IOException 代替
|
||||
*
|
||||
* @param moduleid int
|
||||
* @param actionid int
|
||||
* @param request HttpRequest
|
||||
* @param response HttpResponse
|
||||
*
|
||||
* @return boolean
|
||||
* @throws IOException IOException
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean authenticate(int moduleid, int actionid, HttpRequest request, HttpResponse response) throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户登录或权限验证, 没有注解为@AuthIgnore 的方法会执行authenticate方法, 若验证成功则必须调用next.execute(request, response);进行下一步操作, 例如: <br>
|
||||
* <blockquote><pre>
|
||||
* @Override
|
||||
* public void authenticate(int moduleid, int actionid, HttpRequest request, HttpResponse response, final HttpServlet next) throws IOException {
|
||||
* UserInfo info = currentUser(request);
|
||||
* if (info == null) {
|
||||
* response.finishJson(RET_UNLOGIN);
|
||||
* return;
|
||||
* } else if (!info.checkAuth(module, actionid)) {
|
||||
* response.finishJson(RET_AUTHILLEGAL);
|
||||
* return;
|
||||
* }
|
||||
* next.execute(request, response);
|
||||
* }
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* @param moduleid 模块ID,来自@WebServlet.moduleid()
|
||||
* @param actionid 操作ID,来自@WebMapping.actionid()
|
||||
* @param request HttpRequest
|
||||
* @param response HttpResponse
|
||||
* @param next HttpServlet
|
||||
*
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public abstract void authenticate(int moduleid, int actionid, HttpRequest request, HttpResponse response, final HttpServlet next) throws IOException;
|
||||
|
||||
@Override
|
||||
public final void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
preExecute(request, response, preSuccessServlet);
|
||||
}
|
||||
|
||||
public final 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()));
|
||||
}
|
||||
|
||||
public final void postDestroy(HttpContext context, AnyValue config) {
|
||||
}
|
||||
|
||||
protected void setHeader(HttpRequest request, String name, Serializable value) {
|
||||
request.header.setValue(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
protected void addHeader(HttpRequest request, String name, Serializable value) {
|
||||
request.header.addValue(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
protected String _prefix(HttpServlet servlet) {
|
||||
return servlet._prefix;
|
||||
}
|
||||
|
||||
private HashMap<String, Entry> load() {
|
||||
final boolean typeIgnore = this.getClass().getAnnotation(AuthIgnore.class) != null;
|
||||
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 (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 WebMapping mapping = method.getAnnotation(WebMapping.class);
|
||||
final WebAction action = method.getAnnotation(WebAction.class);
|
||||
if (mapping == null && action == null) continue;
|
||||
final boolean inherited = mapping == null ? action.inherited() : mapping.inherited();
|
||||
if (!inherited && selfClz != clz) continue; //忽略不被继承的方法
|
||||
final int actionid = mapping == null ? action.actionid() : mapping.actionid();
|
||||
final String name = mapping == null ? action.url().trim() : mapping.url().trim();
|
||||
final String[] methods = mapping == null ? action.methods() : mapping.methods();
|
||||
if (nameset.containsKey(name)) {
|
||||
if (nameset.get(name) != clz) continue;
|
||||
throw new RuntimeException(this.getClass().getSimpleName() + " have two same " + WebMapping.class.getSimpleName() + "(" + name + ")");
|
||||
}
|
||||
nameset.put(name, clz);
|
||||
map.put(name, new Entry(typeIgnore, serviceid, actionid, name, methods, method, createHttpServlet(method)));
|
||||
}
|
||||
} while ((clz = clz.getSuperclass()) != HttpBaseServlet.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(boolean typeIgnore, 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;
|
||||
this.ignore = typeIgnore || method.getAnnotation(AuthIgnore.class) != null;
|
||||
HttpCacheable hc = method.getAnnotation(HttpCacheable.class);
|
||||
this.cacheseconds = hc == null ? 0 : hc.seconds() * 1000;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ public class HttpContext extends Context {
|
||||
return responsePool;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <H extends AsyncHandler> Creator<H> loadAsyncHandlerCreator(Class<H> handlerClass) {
|
||||
Creator<H> creator = asyncHandlerCreators.get(handlerClass);
|
||||
if (creator == null) {
|
||||
@@ -62,6 +63,7 @@ public class HttpContext extends Context {
|
||||
return creator;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <H extends AsyncHandler> Creator<H> createAsyncHandlerCreator(Class<H> handlerClass) {
|
||||
//生成规则与SncpAsyncHandler.Factory 很类似
|
||||
//-------------------------------------------------------------
|
||||
|
||||
@@ -222,7 +222,7 @@ public class HttpPrepareServlet extends PrepareServlet<String, HttpContext, Http
|
||||
}
|
||||
String resServlet = resConfig.getValue("servlet", HttpResourceServlet.class.getName());
|
||||
try {
|
||||
this.resourceHttpServlet = (HttpServlet) Class.forName(resServlet).newInstance();
|
||||
this.resourceHttpServlet = (HttpServlet) Thread.currentThread().getContextClassLoader().loadClass(resServlet).newInstance();
|
||||
} catch (Throwable e) {
|
||||
this.resourceHttpServlet = new HttpResourceServlet();
|
||||
logger.log(Level.WARNING, "init HttpResourceSerlvet(" + resServlet + ") error", e);
|
||||
@@ -235,6 +235,10 @@ public class HttpPrepareServlet extends PrepareServlet<String, HttpContext, Http
|
||||
try {
|
||||
final String uri = request.getRequestURI();
|
||||
HttpServlet servlet;
|
||||
if (response.isAutoOptions() && "OPTIONS".equals(request.getMethod())) {
|
||||
response.finish(200, null);
|
||||
return;
|
||||
}
|
||||
if (request.isWebSocket()) {
|
||||
servlet = wsmappings.get(uri);
|
||||
if (servlet == null && this.regWsArray != null) {
|
||||
|
||||
@@ -32,7 +32,7 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
|
||||
protected static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
protected static final String SESSIONID_NAME = "JSESSIONID";
|
||||
public static final String SESSIONID_NAME = "JSESSIONID";
|
||||
|
||||
@Comment("Method GET/POST/...")
|
||||
private String method;
|
||||
@@ -121,7 +121,6 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
} else {
|
||||
this.requestURI = array.toDecodeString(index, offset - index, charset).trim();
|
||||
}
|
||||
if (this.requestURI.contains("../")) return -1;
|
||||
index = ++offset;
|
||||
this.protocol = array.toString(index, array.size() - index, charset).trim();
|
||||
while (readLine(buffer, array)) {
|
||||
@@ -889,6 +888,15 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* 获取请求Header总对象
|
||||
*
|
||||
* @return AnyValue
|
||||
*/
|
||||
public AnyValue getHeaders() {
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的header名
|
||||
*
|
||||
@@ -1087,6 +1095,16 @@ public class HttpRequest extends Request<HttpContext> {
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* 获取请求参数总对象
|
||||
*
|
||||
* @return AnyValue
|
||||
*/
|
||||
public AnyValue getParameters() {
|
||||
parseBody();
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有参数名
|
||||
*
|
||||
|
||||
@@ -162,6 +162,24 @@ public class HttpResourceServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
public void serRoot(String rootstr) {
|
||||
if (rootstr == null) return;
|
||||
try {
|
||||
this.root = new File(rootstr).getCanonicalFile();
|
||||
} catch (IOException ioe) {
|
||||
this.root = new File(rootstr);
|
||||
}
|
||||
}
|
||||
|
||||
public void serRoot(File file) {
|
||||
if (file == null) return;
|
||||
try {
|
||||
this.root = file.getCanonicalFile();
|
||||
} catch (IOException ioe) {
|
||||
this.root = file;
|
||||
}
|
||||
}
|
||||
|
||||
protected static long parseLenth(String value, long defValue) {
|
||||
if (value == null) return defValue;
|
||||
value = value.toUpperCase().replace("B", "");
|
||||
@@ -174,6 +192,11 @@ public class HttpResourceServlet extends HttpServlet {
|
||||
@Override
|
||||
public void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||
String uri = request.getRequestURI();
|
||||
if (uri.contains("../")) {
|
||||
if (finest) logger.log(Level.FINEST, "Not found resource (404) be " + uri + ", request = " + request);
|
||||
response.finish404();
|
||||
return;
|
||||
}
|
||||
if (locationRewrites != null) {
|
||||
for (SimpleEntry<Pattern, String> entry : locationRewrites) {
|
||||
Matcher matcher = entry.getKey().matcher(uri);
|
||||
|
||||
@@ -42,7 +42,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
public ByteBuffer[] execute(final HttpResponse response, final ByteBuffer[] buffers);
|
||||
}
|
||||
|
||||
private static final ByteBuffer buffer304 = ByteBuffer.wrap("HTTP/1.1 304 Not Modified\r\n\r\n".getBytes()).asReadOnlyBuffer();
|
||||
private static final ByteBuffer buffer304 = ByteBuffer.wrap("HTTP/1.1 304 Not Modified\r\nContent-Length:0\r\n\r\n".getBytes()).asReadOnlyBuffer();
|
||||
|
||||
private static final ByteBuffer buffer404 = ByteBuffer.wrap("HTTP/1.1 404 Not Found\r\nContent-Length:0\r\n\r\n".getBytes()).asReadOnlyBuffer();
|
||||
|
||||
@@ -125,17 +125,21 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
|
||||
private final String[][] defaultSetHeaders;
|
||||
|
||||
private final boolean autoOptions;
|
||||
|
||||
private final HttpCookie defcookie;
|
||||
|
||||
public static ObjectPool<Response> createPool(AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator<Response> creator) {
|
||||
return new ObjectPool<>(creatCounter, cycleCounter, max, creator, (x) -> ((HttpResponse) x).prepare(), (x) -> ((HttpResponse) x).recycle());
|
||||
}
|
||||
|
||||
public HttpResponse(HttpContext context, HttpRequest request, String[][] defaultAddHeaders, String[][] defaultSetHeaders, HttpCookie defcookie) {
|
||||
public HttpResponse(HttpContext context, HttpRequest request, String[][] defaultAddHeaders, String[][] defaultSetHeaders,
|
||||
HttpCookie defcookie, boolean autoOptions) {
|
||||
super(context, request);
|
||||
this.defaultAddHeaders = defaultAddHeaders;
|
||||
this.defaultSetHeaders = defaultSetHeaders;
|
||||
this.defcookie = defcookie;
|
||||
this.autoOptions = autoOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,6 +149,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
|
||||
@Override
|
||||
protected boolean recycle() {
|
||||
boolean rs = super.recycle();
|
||||
this.status = 200;
|
||||
this.contentLength = -1;
|
||||
this.contentType = null;
|
||||
@@ -152,7 +157,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
this.headsended = false;
|
||||
this.header.clear();
|
||||
this.bufferHandler = null;
|
||||
return super.recycle();
|
||||
return rs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,10 +186,15 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void thenEvent(Servlet servlet) {
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
protected boolean isAutoOptions() {
|
||||
return this.autoOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加Cookie值
|
||||
*
|
||||
@@ -239,6 +249,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
*
|
||||
* @return AsyncHandler AsyncHandler
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <H extends AsyncHandler> H createAsyncHandler(Class<H> handlerClass) {
|
||||
if (handlerClass == null || handlerClass == AsyncHandler.class) return (H) createAsyncHandler();
|
||||
return context.loadAsyncHandlerCreator(handlerClass).create(createAsyncHandler());
|
||||
@@ -255,6 +266,18 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
finish(request.getJsonConvert().convertTo(context.getBufferSupplier(), obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象数组用Map的形式以JSON格式输出 <br>
|
||||
* 例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3}
|
||||
*
|
||||
* @param objs 输出对象
|
||||
*/
|
||||
public void finishMapJson(final Object... objs) {
|
||||
this.contentType = "text/plain; charset=utf-8";
|
||||
if (this.recycleListener != null) this.output = objs;
|
||||
finish(request.getJsonConvert().convertMapTo(context.getBufferSupplier(), objs));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以JSON格式输出
|
||||
*
|
||||
@@ -267,6 +290,19 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
finish(convert.convertTo(context.getBufferSupplier(), obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象数组用Map的形式以JSON格式输出 <br>
|
||||
* 例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3}
|
||||
*
|
||||
* @param convert 指定的JsonConvert
|
||||
* @param objs 输出对象
|
||||
*/
|
||||
public void finishMapJson(final JsonConvert convert, final Object... objs) {
|
||||
this.contentType = "text/plain; charset=utf-8";
|
||||
if (this.recycleListener != null) this.output = objs;
|
||||
finish(convert.convertMapTo(context.getBufferSupplier(), objs));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象以JSON格式输出
|
||||
*
|
||||
@@ -349,6 +385,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
* @param convert 指定的JsonConvert
|
||||
* @param future 输出对象的句柄
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void finishJson(final JsonConvert convert, final CompletableFuture future) {
|
||||
future.whenComplete((v, e) -> {
|
||||
if (e != null) {
|
||||
@@ -373,6 +410,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
* @param type 指定的类型
|
||||
* @param future 输出对象的句柄
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void finishJson(final JsonConvert convert, final Type type, final CompletableFuture future) {
|
||||
future.whenComplete((v, e) -> {
|
||||
if (e != null) {
|
||||
@@ -407,6 +445,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
* @param convert 指定的JsonConvert
|
||||
* @param result HttpResult对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void finishJson(final JsonConvert convert, final HttpResult result) {
|
||||
if (output == null) {
|
||||
finish("");
|
||||
@@ -439,6 +478,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
if (isClosed()) return;
|
||||
if (this.recycleListener != null) this.output = obj;
|
||||
if (obj == null || obj.isEmpty()) {
|
||||
this.contentLength = 0;
|
||||
final ByteBuffer headbuf = createHeader();
|
||||
headbuf.flip();
|
||||
super.finish(headbuf);
|
||||
@@ -704,8 +744,8 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
final String match = request.getHeader("If-None-Match");
|
||||
final String etag = (file == null ? 0L : file.lastModified()) + "-" + length;
|
||||
if (match != null && etag.equals(match)) {
|
||||
finish304();
|
||||
return;
|
||||
//finish304();
|
||||
//return;
|
||||
}
|
||||
this.contentLength = length;
|
||||
if (filename != null && !filename.isEmpty() && file != null) {
|
||||
@@ -733,18 +773,20 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
ByteBuffer hbuffer = createHeader();
|
||||
hbuffer.flip();
|
||||
if (fileBody == null) {
|
||||
if (this.recycleListener != null) this.output = file;
|
||||
finishFile(hbuffer, file, start, len);
|
||||
} else {
|
||||
if (start >= 0) {
|
||||
fileBody.position((int) start);
|
||||
if (len > 0) fileBody.limit((int) (fileBody.position() + len));
|
||||
}
|
||||
if (this.recycleListener != null) this.output = fileBody;
|
||||
super.finish(hbuffer, fileBody);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishFile(ByteBuffer hbuffer, File file, long offset, long length) throws IOException {
|
||||
this.channel.write(hbuffer, hbuffer, new TransferFileHandler(AsynchronousFileChannel.open(file.toPath(), options, ((HttpContext) context).getExecutor()), offset, length));
|
||||
this.channel.write(hbuffer, hbuffer, new TransferFileHandler(file, offset, length));
|
||||
}
|
||||
|
||||
private ByteBuffer createHeader() {
|
||||
@@ -754,7 +796,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
|
||||
buffer.put(("Content-Type: " + (this.contentType == null ? "text/plain; charset=utf-8" : this.contentType) + "\r\n").getBytes());
|
||||
|
||||
if (this.contentLength > 0) {
|
||||
if (this.contentLength >= 0) {
|
||||
buffer.put(("Content-Length: " + this.contentLength + "\r\n").getBytes());
|
||||
}
|
||||
if (!this.request.isKeepAlive()) {
|
||||
@@ -976,54 +1018,76 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
||||
|
||||
protected final class TransferFileHandler implements AsyncHandler<Integer, ByteBuffer> {
|
||||
|
||||
private final File file;
|
||||
|
||||
private final AsynchronousFileChannel filechannel;
|
||||
|
||||
private final long max; //需要读取的字节数, -1表示读到文件结尾
|
||||
|
||||
private long count;//读取文件的字节数
|
||||
|
||||
private long position = 0;
|
||||
private long readpos = 0;
|
||||
|
||||
private boolean next = false;
|
||||
private boolean hdwrite = true; //写入Header
|
||||
|
||||
private boolean read = true;
|
||||
private boolean read = false;
|
||||
|
||||
public TransferFileHandler(AsynchronousFileChannel channel) {
|
||||
this.filechannel = channel;
|
||||
this.max = -1;
|
||||
public TransferFileHandler(File file) throws IOException {
|
||||
this.file = file;
|
||||
this.filechannel = AsynchronousFileChannel.open(file.toPath(), options, ((HttpContext) context).getExecutor());
|
||||
this.readpos = 0;
|
||||
this.max = file.length();
|
||||
}
|
||||
|
||||
public TransferFileHandler(AsynchronousFileChannel channel, long offset, long len) {
|
||||
this.filechannel = channel;
|
||||
this.position = offset <= 0 ? 0 : offset;
|
||||
this.max = len;
|
||||
public TransferFileHandler(File file, long offset, long len) throws IOException {
|
||||
this.file = file;
|
||||
this.filechannel = AsynchronousFileChannel.open(file.toPath(), options, ((HttpContext) context).getExecutor());
|
||||
this.readpos = offset <= 0 ? 0 : offset;
|
||||
this.max = len <= 0 ? file.length() : len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, ByteBuffer attachment) {
|
||||
if (result < 0 || (max > 0 && count >= max)) {
|
||||
//(Utility.now() + "---" + Thread.currentThread().getName() + "-----------" + file + "-------------------result: " + result + ", max = " + max + ", readpos = " + readpos + ", count = " + count + ", " + (hdwrite ? "正在写Header" : (read ? "准备读" : "准备写")));
|
||||
if (result < 0 || count >= max) {
|
||||
failed(null, attachment);
|
||||
return;
|
||||
}
|
||||
if (hdwrite && attachment.hasRemaining()) { //Header还没写完
|
||||
channel.write(attachment, attachment, this);
|
||||
return;
|
||||
}
|
||||
if (hdwrite) {
|
||||
//(Utility.now() + "---" + Thread.currentThread().getName() + "-----------" + file + "-------------------Header写入完毕, 准备读取文件.");
|
||||
hdwrite = false;
|
||||
read = true;
|
||||
result = 0;
|
||||
}
|
||||
if (read) {
|
||||
count += result;
|
||||
} else {
|
||||
readpos += result;
|
||||
}
|
||||
if (read && attachment.hasRemaining()) { //Buffer还没写完
|
||||
channel.write(attachment, attachment, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (read) {
|
||||
read = false;
|
||||
if (next) {
|
||||
position += result;
|
||||
} else {
|
||||
next = true;
|
||||
}
|
||||
attachment.clear();
|
||||
filechannel.read(attachment, position, attachment, this);
|
||||
filechannel.read(attachment, readpos, attachment, this);
|
||||
} else {
|
||||
read = true;
|
||||
if (max > 0) {
|
||||
count += result;
|
||||
if (count > max) {
|
||||
attachment.limit((int) (attachment.position() + max - count));
|
||||
}
|
||||
}
|
||||
attachment.flip();
|
||||
if (attachment.hasRemaining()) {
|
||||
channel.write(attachment, attachment, this);
|
||||
} else {
|
||||
failed(null, attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ public class HttpResult<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResult<T> cookie(String name, Serializable value) {
|
||||
return cookie(new HttpCookie(name, String.valueOf(value)));
|
||||
}
|
||||
|
||||
public HttpResult<T> cookie(HttpCookie cookie) {
|
||||
if (this.cookies == null) this.cookies = new ArrayList<>();
|
||||
this.cookies.add(cookie);
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.net.HttpCookie;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.net.sncp.Sncp;
|
||||
import org.redkale.service.Service;
|
||||
@@ -38,24 +39,21 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
public List<HttpServlet> getHttpServlets() {
|
||||
return this.prepare.getServlets();
|
||||
}
|
||||
|
||||
public List<HttpFilter> getHttpFilters() {
|
||||
return this.prepare.getFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取静态资源HttpServlet
|
||||
*
|
||||
* @return HttpServlet
|
||||
*/
|
||||
public HttpServlet getResourceServlet() {
|
||||
return ((HttpPrepareServlet) this.prepare).resourceHttpServlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除HttpFilter
|
||||
*
|
||||
* @param filterName HttpFilter名称
|
||||
*
|
||||
* @return HttpFilter
|
||||
*/
|
||||
public HttpFilter removeFilter(String filterName) {
|
||||
return (HttpFilter) this.prepare.removeFilter(filterName);
|
||||
public HttpResourceServlet getResourceServlet() {
|
||||
return (HttpResourceServlet) ((HttpPrepareServlet) this.prepare).resourceHttpServlet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +109,7 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
*
|
||||
* @return HttpFilter
|
||||
*/
|
||||
public <T extends HttpFilter> T removeFilter(Class<T> filterClass) {
|
||||
public <T extends HttpFilter> T removeHttpFilter(Class<T> filterClass) {
|
||||
return (T) this.prepare.removeFilter(filterClass);
|
||||
}
|
||||
|
||||
@@ -175,14 +173,15 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
*
|
||||
* @param <S> WebSocket
|
||||
* @param <T> HttpServlet
|
||||
* @param classLoader ClassLoader
|
||||
* @param webSocketType WebSocket的类型
|
||||
* @param prefix url前缀
|
||||
* @param conf 配置信息
|
||||
*
|
||||
* @return RestServlet
|
||||
*/
|
||||
public <S extends WebSocket, T extends HttpServlet> T addRestWebSocketServlet(final Class<S> webSocketType, final String prefix, final AnyValue conf) {
|
||||
T servlet = Rest.createRestWebSocketServlet(webSocketType);
|
||||
public <S extends WebSocket, T extends HttpServlet> T addRestWebSocketServlet(final ClassLoader classLoader, final Class<S> webSocketType, final String prefix, final AnyValue conf) {
|
||||
T servlet = Rest.createRestWebSocketServlet(classLoader, webSocketType);
|
||||
if (servlet != null) this.prepare.addServlet(servlet, prefix, conf);
|
||||
return servlet;
|
||||
}
|
||||
@@ -192,6 +191,7 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
*
|
||||
* @param <S> Service
|
||||
* @param <T> HttpServlet
|
||||
* @param classLoader ClassLoader
|
||||
* @param service Service对象
|
||||
* @param userType 用户数据类型
|
||||
* @param baseServletType RestServlet基类
|
||||
@@ -199,8 +199,8 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
*
|
||||
* @return RestServlet
|
||||
*/
|
||||
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);
|
||||
public <S extends Service, T extends HttpServlet> T addRestServlet(final ClassLoader classLoader, final S service, final Class userType, final Class<T> baseServletType, final String prefix) {
|
||||
return addRestServlet(classLoader, null, service, userType, baseServletType, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,6 +208,7 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
*
|
||||
* @param <S> Service
|
||||
* @param <T> HttpServlet
|
||||
* @param classLoader ClassLoader
|
||||
* @param name 资源名
|
||||
* @param service Service对象
|
||||
* @param userType 用户数据类型
|
||||
@@ -216,7 +217,8 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
*
|
||||
* @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) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends Service, T extends HttpServlet> T addRestServlet(final ClassLoader classLoader, 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;
|
||||
@@ -231,12 +233,11 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
break;
|
||||
}
|
||||
} catch (NoSuchFieldException | SecurityException e) {
|
||||
System.err.println("serviceType = " + serviceType + ", servletClass = " + item.getClass());
|
||||
e.printStackTrace();
|
||||
logger.log(Level.SEVERE, "serviceType = " + serviceType + ", servletClass = " + item.getClass(), e);
|
||||
}
|
||||
}
|
||||
final boolean first = servlet == null;
|
||||
if (servlet == null) servlet = Rest.createRestServlet(userType, baseServletType, serviceType);
|
||||
if (servlet == null) servlet = Rest.createRestServlet(classLoader, 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);
|
||||
@@ -280,6 +281,8 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
});
|
||||
final List<String[]> defaultAddHeaders = new ArrayList<>();
|
||||
final List<String[]> defaultSetHeaders = new ArrayList<>();
|
||||
boolean autoOptions = false;
|
||||
|
||||
HttpCookie defaultCookie = null;
|
||||
String remoteAddrHeader = null;
|
||||
if (config != null) {
|
||||
@@ -342,10 +345,15 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
defaultCookie.setPath(path);
|
||||
}
|
||||
}
|
||||
AnyValue options = resps == null ? null : resps.getAnyValue("options");
|
||||
autoOptions = options != null && options.getBoolValue("auto", false);
|
||||
}
|
||||
|
||||
}
|
||||
final String[][] addHeaders = defaultAddHeaders.isEmpty() ? null : defaultAddHeaders.toArray(new String[defaultAddHeaders.size()][]);
|
||||
final String[][] setHeaders = defaultSetHeaders.isEmpty() ? null : defaultSetHeaders.toArray(new String[defaultSetHeaders.size()][]);
|
||||
final boolean options = autoOptions;
|
||||
|
||||
final HttpCookie defCookie = defaultCookie;
|
||||
final String addrHeader = remoteAddrHeader;
|
||||
AtomicLong createResponseCounter = new AtomicLong();
|
||||
@@ -353,7 +361,7 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
|
||||
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.readTimeoutSecond, this.writeTimeoutSecond);
|
||||
responsePool.setCreator((Object... params) -> new HttpResponse(httpcontext, new HttpRequest(httpcontext, addrHeader), addHeaders, setHeaders, defCookie));
|
||||
responsePool.setCreator((Object... params) -> new HttpResponse(httpcontext, new HttpRequest(httpcontext, addrHeader), addHeaders, setHeaders, defCookie, options));
|
||||
return httpcontext;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ import org.redkale.util.*;
|
||||
*/
|
||||
public class HttpServlet extends Servlet<HttpContext, HttpRequest, HttpResponse> {
|
||||
|
||||
public static final int RET_SERVER_ERROR = 1800_0001;
|
||||
public static final int RET_SERVER_ERROR = 1200_0001;
|
||||
|
||||
public static final int RET_METHOD_ERROR = 1800_0002;
|
||||
public static final int RET_METHOD_ERROR = 1200_0002;
|
||||
|
||||
String _prefix = ""; //当前HttpServlet的path前缀
|
||||
|
||||
@@ -81,6 +81,7 @@ public class HttpServlet extends Servlet<HttpContext, HttpRequest, HttpResponse>
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void preInit(HttpContext context, AnyValue config) {
|
||||
String path = _prefix == null ? "" : _prefix;
|
||||
WebServlet ws = this.getClass().getAnnotation(WebServlet.class);
|
||||
@@ -214,7 +215,7 @@ public class HttpServlet extends Servlet<HttpContext, HttpRequest, HttpResponse>
|
||||
int i = 0;
|
||||
for (;;) {
|
||||
try {
|
||||
Class.forName(newDynName.replace('/', '.'));
|
||||
Thread.currentThread().getContextClassLoader().loadClass(newDynName.replace('/', '.'));
|
||||
newDynName += "_" + (++i);
|
||||
} catch (Throwable ex) {
|
||||
break;
|
||||
|
||||
@@ -17,7 +17,7 @@ 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 jdk.internal.org.objectweb.asm.Type;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.convert.json.*;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.*;
|
||||
import org.redkale.source.Flipper;
|
||||
@@ -35,6 +35,8 @@ public final class Rest {
|
||||
|
||||
static final String REST_SERVICE_FIELD_NAME = "_redkale_service";
|
||||
|
||||
static final String REST_JSONCONVERT_FIELD_PREFIX = "_redkale_jsonconvert_";
|
||||
|
||||
static final String REST_SERVICEMAP_FIELD_NAME = "_redkale_servicemap"; //如果只有name=""的Service资源,则实例中_servicemap必须为null
|
||||
|
||||
private static final String REST_PARAMTYPES_FIELD_NAME = "_redkale_paramtypes"; //存在泛型的参数数组 Type[][] 第1维度是方法的下标, 第二维度是参数的下标
|
||||
@@ -90,7 +92,16 @@ public final class Rest {
|
||||
return new MethodVisitor(Opcodes.ASM5) {
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {
|
||||
if (index > 0) fieldnames.add(name);
|
||||
if (index < 1) return;
|
||||
int size = fieldnames.size();
|
||||
//index并不会按顺序执行的
|
||||
if (index > size) {
|
||||
for (int i = size; i < index; i++) {
|
||||
fieldnames.add(" ");
|
||||
}
|
||||
fieldnames.set(index - 1, name);
|
||||
}
|
||||
fieldnames.set(index - 1, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -109,6 +120,21 @@ public final class Rest {
|
||||
}
|
||||
}
|
||||
|
||||
static JsonConvert createJsonConvert(RestConvert[] converts) {
|
||||
if (converts == null || converts.length < 1) return JsonConvert.root();
|
||||
final JsonFactory childFactory = JsonFactory.root().createChild();
|
||||
List<Class> types = new ArrayList<>();
|
||||
for (RestConvert rc : converts) {
|
||||
if (types.contains(rc.type())) throw new RuntimeException("@RestConvert type(" + rc.type() + ") repeat");
|
||||
childFactory.register(rc.type(), false, rc.convertColumns());
|
||||
childFactory.register(rc.type(), true, rc.ignoreColumns());
|
||||
childFactory.reloadCoder(rc.type());
|
||||
types.add(rc.type());
|
||||
childFactory.tiny(rc.tiny());
|
||||
}
|
||||
return childFactory.getConvert();
|
||||
}
|
||||
|
||||
static String getWebModuleName(Class<? extends Service> serviceType) {
|
||||
final RestService controller = serviceType.getAnnotation(RestService.class);
|
||||
if (controller == null) return serviceType.getSimpleName().replaceAll("Service.*$", "").toLowerCase();
|
||||
@@ -142,7 +168,7 @@ public final class Rest {
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends HttpServlet> T createRestWebSocketServlet(final Class<? extends WebSocket> webSocketType) {
|
||||
static <T extends HttpServlet> T createRestWebSocketServlet(final ClassLoader classLoader, final Class<? extends WebSocket> webSocketType) {
|
||||
if (webSocketType == null) throw new RuntimeException("Rest WebSocket Class is null on createRestWebSocketServlet");
|
||||
if (Modifier.isAbstract(webSocketType.getModifiers())) throw new RuntimeException("Rest WebSocket Class(" + webSocketType + ") cannot abstract on createRestWebSocketServlet");
|
||||
if (Modifier.isFinal(webSocketType.getModifiers())) throw new RuntimeException("Rest WebSocket Class(" + webSocketType + ") cannot final on createRestWebSocketServlet");
|
||||
@@ -162,6 +188,7 @@ public final class Rest {
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
final Set<Field> resourcesFieldSet = new LinkedHashSet<>();
|
||||
final ClassLoader loader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
|
||||
Class clzz = webSocketType;
|
||||
do {
|
||||
for (Field field : webSocketType.getDeclaredFields()) {
|
||||
@@ -216,7 +243,6 @@ public final class Rest {
|
||||
final String newDynConsumerSimpleName = "_DynRestOnMessageConsumer";
|
||||
final String newDynConsumerFullName = newDynName + "$" + newDynConsumerSimpleName;
|
||||
//----------------------------------------------------------------------------------------
|
||||
ClassLoader loader = Rest.class.getClassLoader();
|
||||
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
|
||||
FieldVisitor fv;
|
||||
@@ -312,6 +338,13 @@ public final class Rest {
|
||||
mv.visitMaxs(2, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
{ //resourceName
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "resourceName", "()Ljava/lang/String;", null, null));
|
||||
mv.visitLdcInsn(rws.name());
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
RestClassLoader newLoader = new RestClassLoader(loader);
|
||||
|
||||
@@ -514,7 +547,7 @@ public final class Rest {
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends HttpServlet> T createRestServlet(final Class userType0, final Class<T> baseServletType, final Class<? extends Service> serviceType) {
|
||||
static <T extends HttpServlet> T createRestServlet(final ClassLoader classLoader, final Class userType0, final Class<T> baseServletType, final Class<? extends Service> serviceType) {
|
||||
if (baseServletType == null || serviceType == null) throw new RuntimeException(" Servlet or Service is null Class on createRestServlet");
|
||||
if (!HttpServlet.class.isAssignableFrom(baseServletType)) throw new RuntimeException(baseServletType + " is not HttpServlet Class on createRestServlet");
|
||||
int mod = baseServletType.getModifiers();
|
||||
@@ -525,6 +558,7 @@ public final class Rest {
|
||||
final String webServletDesc = Type.getDescriptor(WebServlet.class);
|
||||
final String reqDesc = Type.getDescriptor(HttpRequest.class);
|
||||
final String respDesc = Type.getDescriptor(HttpResponse.class);
|
||||
final String convertDesc = Type.getDescriptor(JsonConvert.class);
|
||||
final String retDesc = Type.getDescriptor(RetResult.class);
|
||||
final String futureDesc = Type.getDescriptor(CompletableFuture.class);
|
||||
final String flipperDesc = Type.getDescriptor(Flipper.class);
|
||||
@@ -547,7 +581,7 @@ public final class Rest {
|
||||
final String supDynName = baseServletType.getName().replace('.', '/');
|
||||
final RestService controller = serviceType.getAnnotation(RestService.class);
|
||||
if (controller != null && controller.ignore()) throw new RuntimeException(serviceType + " is ignore Rest Service Class"); //标记为ignore=true不创建Servlet
|
||||
ClassLoader loader = Rest.class.getClassLoader();
|
||||
ClassLoader loader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
|
||||
String newDynName = serviceTypeInternalName.substring(0, serviceTypeInternalName.lastIndexOf('/') + 1) + "_Dyn" + serviceType.getSimpleName().replaceAll("Service.*$", "") + "RestServlet";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -672,6 +706,7 @@ public final class Rest {
|
||||
final Map<String, List<String>> asmParamMap = MethodParamClassVisitor.getMethodParamNames(serviceType);
|
||||
final Map<String, java.lang.reflect.Type> bodyTypes = new HashMap<>();
|
||||
|
||||
final List<RestConvert[]> restConverts = new ArrayList<>();
|
||||
for (final MappingEntry entry : entrys) {
|
||||
RestUploadFile mupload = null;
|
||||
Class muploadType = null;
|
||||
@@ -680,6 +715,9 @@ public final class Rest {
|
||||
final String methodDesc = Type.getMethodDescriptor(method);
|
||||
final Parameter[] params = method.getParameters();
|
||||
|
||||
final RestConvert[] rcs = method.getAnnotationsByType(RestConvert.class);
|
||||
if (rcs != null && rcs.length > 0) restConverts.add(rcs);
|
||||
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, entry.name, "(" + reqDesc + respDesc + ")V", null, new String[]{"java/io/IOException"}));
|
||||
//mv.setDebug(true);
|
||||
mv.debugLine();
|
||||
@@ -725,6 +763,7 @@ public final class Rest {
|
||||
|
||||
RestHeader annhead = param.getAnnotation(RestHeader.class);
|
||||
if (annhead != null) {
|
||||
if (ptype != String.class) throw new RuntimeException("@RestHeader must on String Parameter in " + method);
|
||||
n = annhead.name();
|
||||
radix = annhead.radix();
|
||||
comment = annhead.comment();
|
||||
@@ -801,7 +840,7 @@ public final class Rest {
|
||||
} else if (ptype == Flipper.class) {
|
||||
n = "flipper";
|
||||
} else {
|
||||
n = ("bean" + i);
|
||||
throw new RuntimeException("Parameter " + param.getName() + " not found name by @RestParam in " + method);
|
||||
}
|
||||
}
|
||||
if (annhead == null && anncookie == null && annaddr == null && annbody == null && annfile == null
|
||||
@@ -839,7 +878,7 @@ public final class Rest {
|
||||
}
|
||||
}
|
||||
av0 = mv.visitAnnotation(mappingDesc, true);
|
||||
String url = "/" + defmodulename.toLowerCase() + "/" + entry.name + (reqpath ? "/" : "");
|
||||
String url = (catalog.isEmpty() ? "/" : ("/" + catalog + "/")) + defmodulename.toLowerCase() + "/" + entry.name + (reqpath ? "/" : "");
|
||||
av0.visit("url", url);
|
||||
av0.visit("auth", entry.auth);
|
||||
av0.visit("cacheseconds", entry.cacheseconds);
|
||||
@@ -1488,15 +1527,29 @@ public final class Rest {
|
||||
} else if (RetResult.class.isAssignableFrom(returnType)) {
|
||||
mv.visitVarInsn(ASTORE, maxLocals);
|
||||
mv.visitVarInsn(ALOAD, 2); //response
|
||||
if (rcs != null && rcs.length > 0) {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, REST_JSONCONVERT_FIELD_PREFIX + restConverts.size(), convertDesc);
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + convertDesc + retDesc + ")V", false);
|
||||
} else {
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + retDesc + ")V", false);
|
||||
}
|
||||
mv.visitInsn(RETURN);
|
||||
maxLocals++;
|
||||
} else if (HttpResult.class.isAssignableFrom(returnType)) {
|
||||
mv.visitVarInsn(ASTORE, maxLocals);
|
||||
mv.visitVarInsn(ALOAD, 2); //response
|
||||
if (rcs != null && rcs.length > 0) {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, REST_JSONCONVERT_FIELD_PREFIX + restConverts.size(), convertDesc);
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + convertDesc + httprsDesc + ")V", false);
|
||||
} else {
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + httprsDesc + ")V", false);
|
||||
}
|
||||
mv.visitInsn(RETURN);
|
||||
maxLocals++;
|
||||
} else if (Number.class.isAssignableFrom(returnType) || CharSequence.class.isAssignableFrom(returnType)) { //returnType == String.class 必须放在前面
|
||||
@@ -1510,15 +1563,29 @@ public final class Rest {
|
||||
} else if (CompletableFuture.class.isAssignableFrom(returnType)) {
|
||||
mv.visitVarInsn(ASTORE, maxLocals);
|
||||
mv.visitVarInsn(ALOAD, 2);//response
|
||||
if (rcs != null && rcs.length > 0) {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, REST_JSONCONVERT_FIELD_PREFIX + restConverts.size(), convertDesc);
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + convertDesc + futureDesc + ")V", false);
|
||||
} else {
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + futureDesc + ")V", false);
|
||||
}
|
||||
mv.visitInsn(RETURN);
|
||||
maxLocals++;
|
||||
} else {
|
||||
mv.visitVarInsn(ASTORE, maxLocals);
|
||||
mv.visitVarInsn(ALOAD, 2); //response
|
||||
if (rcs != null && rcs.length > 0) {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, newDynName, REST_JSONCONVERT_FIELD_PREFIX + restConverts.size(), convertDesc);
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(" + convertDesc + "Ljava/lang/Object;)V", false);
|
||||
} else {
|
||||
mv.visitVarInsn(ALOAD, maxLocals);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(Ljava/lang/Object;)V", false);
|
||||
}
|
||||
mv.visitInsn(RETURN);
|
||||
maxLocals++;
|
||||
}
|
||||
@@ -1537,6 +1604,11 @@ public final class Rest {
|
||||
fv.visitEnd();
|
||||
}
|
||||
|
||||
for (int i = 1; i <= restConverts.size(); i++) {
|
||||
fv = cw.visitField(ACC_PRIVATE, REST_JSONCONVERT_FIELD_PREFIX + i, convertDesc, null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
|
||||
//classMap.put("mappings", mappingMaps); //不显示太多信息
|
||||
{ //toString函数
|
||||
mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null));
|
||||
@@ -1561,6 +1633,11 @@ public final class Rest {
|
||||
genField.setAccessible(true);
|
||||
genField.set(obj, en.getValue());
|
||||
}
|
||||
for (int i = 0; i < restConverts.size(); i++) {
|
||||
Field genField = newClazz.getDeclaredField(REST_JSONCONVERT_FIELD_PREFIX + (i + 1));
|
||||
genField.setAccessible(true);
|
||||
genField.set(obj, createJsonConvert(restConverts.get(i)));
|
||||
}
|
||||
Field typesfield = newClazz.getDeclaredField(REST_PARAMTYPES_FIELD_NAME);
|
||||
typesfield.setAccessible(true);
|
||||
java.lang.reflect.Type[][] paramtypeArray = new java.lang.reflect.Type[paramtypes.size()][];
|
||||
@@ -1611,10 +1688,10 @@ public final class Rest {
|
||||
if (mapping == null) mapping = DEFAULT__MAPPING;
|
||||
this.methodidx = methodidx;
|
||||
this.ignore = mapping.ignore();
|
||||
String n = mapping.name().toLowerCase();
|
||||
String n = mapping.name();
|
||||
if (n.isEmpty()) {
|
||||
String t = method.getName().toLowerCase();
|
||||
int pos = t.indexOf(defmodulename.toLowerCase());
|
||||
String t = method.getName();
|
||||
int pos = t.indexOf(defmodulename);
|
||||
n = pos > 0 ? t.substring(0, pos) : t;
|
||||
}
|
||||
this.name = n;
|
||||
|
||||
43
src/org/redkale/net/http/RestConvert.java
Normal file
43
src/org/redkale/net/http/RestConvert.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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实现类的public方法上, 当方法的返回值以JSON输出时对指定类型的转换设定。 <br>
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@Repeatable(RestConvert.RestConverts.class)
|
||||
public @interface RestConvert {
|
||||
|
||||
boolean tiny() default true;
|
||||
|
||||
Class type();
|
||||
|
||||
String[] ignoreColumns() default {};
|
||||
|
||||
String[] convertColumns() default {};
|
||||
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({METHOD})
|
||||
@Retention(RUNTIME)
|
||||
@interface RestConverts {
|
||||
|
||||
RestConvert[] value();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +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.*;
|
||||
|
||||
/**
|
||||
* 使用 RestServlet 代替
|
||||
*
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
* @deprecated
|
||||
* @param <T> 当前用户对象类型
|
||||
*/
|
||||
@Deprecated
|
||||
public class RestHttpServlet<T> extends HttpServlet {
|
||||
|
||||
protected T currentUser(HttpRequest req) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +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.Serializable;
|
||||
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;
|
||||
|
||||
private List<HttpCookie> cookies;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private T result;
|
||||
|
||||
private int status = 0; //不设置则为 200
|
||||
|
||||
private String message;
|
||||
|
||||
public RestOutput() {
|
||||
}
|
||||
|
||||
public RestOutput(T result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public RestOutput<T> addHeader(String name, Serializable value) {
|
||||
if (this.headers == null) this.headers = new HashMap<>();
|
||||
this.headers.put(name, String.valueOf(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestOutput<T> addCookie(HttpCookie cookie) {
|
||||
if (this.cookies == null) this.cookies = new ArrayList<>();
|
||||
this.cookies.add(cookie);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,6 +37,13 @@ public @interface RestWebSocket {
|
||||
*/
|
||||
String catalog() default "";
|
||||
|
||||
/**
|
||||
* 是否为二进制消息, 默认为文本消息
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
boolean binary() default false;
|
||||
|
||||
/**
|
||||
* 是否单用户单连接, 默认单用户单连接
|
||||
*
|
||||
@@ -45,11 +52,11 @@ public @interface RestWebSocket {
|
||||
boolean single() default true;
|
||||
|
||||
/**
|
||||
* WebScoket服务器给客户端进行ping操作的间隔时间, 单位: 秒, 默认值:60秒
|
||||
* WebScoket服务器给客户端进行ping操作的间隔时间, 单位: 秒, 默认值:15秒
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int liveinterval() default 60;
|
||||
int liveinterval() default WebSocketServlet.DEFAILT_LIVEINTERVAL;
|
||||
|
||||
/**
|
||||
* 是否屏蔽该类的转换
|
||||
|
||||
@@ -8,30 +8,25 @@ package org.redkale.net.http;
|
||||
import org.redkale.net.http.WebSocketPacket.FrameType;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.*;
|
||||
import java.util.stream.Stream;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.net.*;
|
||||
import org.redkale.convert.Convert;
|
||||
import org.redkale.util.Comment;
|
||||
|
||||
/**
|
||||
* <blockquote><pre>
|
||||
* 一个WebSocket连接对应一个WebSocket实体,即一个WebSocket会绑定一个TCP连接。
|
||||
* WebSocket 有两种模式:
|
||||
* 1) 普通模式: 协议上符合HTML5规范, 其流程顺序如下:
|
||||
* 协议上符合HTML5规范, 其流程顺序如下:
|
||||
* 1.1 onOpen 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
|
||||
* 1.2 createUserid 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
|
||||
* 1.3 onConnected WebSocket成功连接后在准备接收数据前回调此方法。
|
||||
* 1.4 onMessage/onFragment+ WebSocket接收到消息后回调此消息类方法。
|
||||
* 1.5 onClose WebSocket被关闭后回调此方法。
|
||||
* 普通模式下 以上方法都应该被重载。
|
||||
*
|
||||
* 2) 原始二进制模式: 此模式有别于HTML5规范,可以视为原始的TCP连接。通常用于音频视频通讯场景。其流程顺序如下:
|
||||
* 2.1 onOpen 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
|
||||
* 2.2 createWebSocketid 若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
|
||||
* 2.3 onRead WebSocket成功连接后回调此方法, 由此方法处理原始的TCP连接, 需要业务代码去控制WebSocket的关闭。
|
||||
* 二进制模式下 以上方法都应该被重载。
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
@@ -78,12 +73,18 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
|
||||
String _remoteAddr;//不可能为空
|
||||
|
||||
JsonConvert _jsonConvert; //不可能为空
|
||||
Convert _textConvert; //不可能为空
|
||||
|
||||
Convert _binaryConvert; //可能为空
|
||||
|
||||
Convert _sendConvert; //不可能为空
|
||||
|
||||
java.lang.reflect.Type _messageTextType; //不可能为空
|
||||
|
||||
private long createtime = System.currentTimeMillis();
|
||||
|
||||
private long pingtime;
|
||||
|
||||
private Map<String, Object> attributes = new HashMap<>(); //非线程安全
|
||||
|
||||
protected WebSocket() {
|
||||
@@ -91,11 +92,13 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
|
||||
//----------------------------------------------------------------
|
||||
public final CompletableFuture<Integer> sendPing() {
|
||||
this.pingtime = System.currentTimeMillis();
|
||||
//if (_engine.finest) _engine.logger.finest(this + " on "+_engine.getEngineid()+" ping...");
|
||||
return sendPacket(WebSocketPacket.DEFAULT_PING_PACKET);
|
||||
}
|
||||
|
||||
public final CompletableFuture<Integer> sendPing(byte[] data) {
|
||||
this.pingtime = System.currentTimeMillis();
|
||||
return sendPacket(new WebSocketPacket(FrameType.PING, data));
|
||||
}
|
||||
|
||||
@@ -134,7 +137,7 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
} else if (message instanceof WebSocketPacket) {
|
||||
return sendPacket((WebSocketPacket) message);
|
||||
} else {
|
||||
return sendPacket(new WebSocketPacket(_jsonConvert, json, last));
|
||||
return sendPacket(new WebSocketPacket(getSendConvert(), json, last));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -143,36 +146,36 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
} else if (message instanceof WebSocketPacket) {
|
||||
return sendPacket((WebSocketPacket) message);
|
||||
} else {
|
||||
return sendPacket(new WebSocketPacket(_jsonConvert, message, last));
|
||||
return sendPacket(new WebSocketPacket(getSendConvert(), message, last));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息, 消息类型是JavaBean对象
|
||||
*
|
||||
* @param convert JsonConvert
|
||||
* @param convert Convert
|
||||
* @param message 不可为空, 只能是JSON对象
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final CompletableFuture<Integer> send(JsonConvert convert, Object message) {
|
||||
public final CompletableFuture<Integer> send(Convert convert, Object message) {
|
||||
return send(convert, message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给自身发送消息, 消息类型是JavaBean对象
|
||||
*
|
||||
* @param convert JsonConvert
|
||||
* @param convert Convert
|
||||
* @param message 不可为空, 只能是JavaBean对象
|
||||
* @param last 是否最后一条
|
||||
*
|
||||
* @return 0表示成功, 非0表示错误码
|
||||
*/
|
||||
public final CompletableFuture<Integer> send(JsonConvert convert, Object message, boolean last) {
|
||||
public final CompletableFuture<Integer> send(Convert convert, Object message, boolean last) {
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> sendPacket(new WebSocketPacket(convert == null ? _jsonConvert : convert, json, last)));
|
||||
return ((CompletableFuture) message).thenCompose((json) -> sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, json, last)));
|
||||
}
|
||||
return sendPacket(new WebSocketPacket(convert == null ? _jsonConvert : convert, message, last));
|
||||
return sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, message, last));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +187,7 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
*/
|
||||
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);
|
||||
if (_engine.finest) _engine.logger.finest("userid:" + getUserid() + " send websocket message(" + packet + ")" + " on " + this);
|
||||
return rs == null ? CompletableFuture.completedFuture(RETCODE_WSOCKET_CLOSED) : rs;
|
||||
}
|
||||
|
||||
@@ -216,7 +219,7 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
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) + ")");
|
||||
if (_engine.finest) _engine.logger.finest("userids:" + Arrays.toString(userids) + " send websocket message(" + message + ")");
|
||||
return rs;
|
||||
}
|
||||
|
||||
@@ -245,7 +248,7 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
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) + ")");
|
||||
if (_engine.finest) _engine.logger.finest("broadcast send websocket message(" + message + ")");
|
||||
return rs;
|
||||
}
|
||||
|
||||
@@ -317,7 +320,7 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
*
|
||||
* @return userid
|
||||
*/
|
||||
public final G userid() {
|
||||
public final G getUserid() {
|
||||
return _userid;
|
||||
}
|
||||
|
||||
@@ -348,6 +351,18 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
return _remoteAddr;
|
||||
}
|
||||
|
||||
protected Convert getTextConvert() {
|
||||
return _textConvert;
|
||||
}
|
||||
|
||||
protected Convert getBinaryConvert() {
|
||||
return _binaryConvert;
|
||||
}
|
||||
|
||||
protected Convert getSendConvert() {
|
||||
return _sendConvert;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/**
|
||||
* 获取指定userid的WebSocket数组, 没有返回null <br>
|
||||
@@ -382,6 +397,15 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
return _engine.getLocalWebSockets();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ByteBuffer资源池
|
||||
*
|
||||
* @return Supplier
|
||||
*/
|
||||
protected Supplier<ByteBuffer> getByteBufferSupplier() {
|
||||
return this._runner.context.getBufferSupplier();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/**
|
||||
* 返回sessionid, null表示连接不合法或异常,默认实现是request.sessionid(true),通常需要重写该方法
|
||||
@@ -401,14 +425,6 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
*/
|
||||
protected abstract CompletableFuture<G> createUserid();
|
||||
|
||||
/**
|
||||
* 标记为WebSocketBinary才需要重写此方法
|
||||
*
|
||||
* @param channel 请求连接
|
||||
*/
|
||||
public void onRead(AsyncConnection channel) {
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSokcet连接成功后的回调方法
|
||||
*/
|
||||
@@ -440,6 +456,15 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
public void onMessage(T message, boolean last) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收到文本消息的回调方法
|
||||
*
|
||||
* @param text 消息
|
||||
* @param last 是否最后一条
|
||||
*/
|
||||
public void onMessage(String text, boolean last) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收到二进制消息的回调方法
|
||||
*
|
||||
@@ -458,6 +483,25 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
public void onClose(int code, String reason) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发生异常时调用
|
||||
*
|
||||
* @param t 异常
|
||||
* @param buffers ByteBuffer[]
|
||||
*/
|
||||
public void onOccurException(Throwable t, ByteBuffer[] buffers) {
|
||||
this.getLogger().log(Level.SEVERE, "WebSocket receive or send Message error", t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Logger
|
||||
*
|
||||
* @return Logger Logger
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return this._engine.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一次发送消息的时间
|
||||
*
|
||||
@@ -467,6 +511,15 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
return this._runner == null ? 0 : this._runner.lastSendTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一次发送PING消息的时间
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
public long getLastPingTime() {
|
||||
return this.pingtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显式地关闭WebSocket
|
||||
*/
|
||||
@@ -476,6 +529,6 @@ public abstract class WebSocket<G extends Serializable, T> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.userid() + "@" + _remoteAddr;
|
||||
return this.getUserid() + "@" + _remoteAddr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +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.lang.annotation.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 被标记为 @WebSocketBinary 的WebSocketServlet 将使用原始的TCP传输, 通常用于类似音频/视频传输场景
|
||||
*
|
||||
* <p> 详情见: https://redkale.org
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({TYPE})
|
||||
@Retention(RUNTIME)
|
||||
public @interface WebSocketBinary {
|
||||
|
||||
}
|
||||
@@ -10,9 +10,10 @@ import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.*;
|
||||
import java.util.stream.*;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.convert.Convert;
|
||||
import static org.redkale.net.http.WebSocket.RETCODE_GROUP_EMPTY;
|
||||
import org.redkale.util.*;
|
||||
|
||||
@@ -23,55 +24,57 @@ import org.redkale.util.*;
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class WebSocketEngine {
|
||||
public class WebSocketEngine {
|
||||
|
||||
//全局自增长ID
|
||||
@Comment("全局自增长ID, 为了确保在一个进程里多个WebSocketEngine定时发送ping时不会同时进行")
|
||||
private static final AtomicInteger sequence = new AtomicInteger();
|
||||
|
||||
//Engine自增长序号ID
|
||||
@Comment("Engine自增长序号ID")
|
||||
private final int index;
|
||||
|
||||
//当前WebSocket对应的Engine
|
||||
@Comment("当前WebSocket对应的Engine")
|
||||
private final String engineid;
|
||||
|
||||
//当前WebSocket对应的Node
|
||||
@Comment("当前WebSocket对应的Node")
|
||||
protected final WebSocketNode node;
|
||||
|
||||
//HttpContext
|
||||
protected final HttpContext context;
|
||||
|
||||
//JsonConvert
|
||||
protected final JsonConvert convert;
|
||||
//Convert
|
||||
protected final Convert sendConvert;
|
||||
|
||||
protected final boolean single; //是否单用户单连接
|
||||
@Comment("是否单用户单连接")
|
||||
protected final boolean single;
|
||||
|
||||
//在线用户ID对应的WebSocket组,用于单用户单连接模式
|
||||
@Comment("在线用户ID对应的WebSocket组,用于单用户单连接模式")
|
||||
private final Map<Serializable, WebSocket> websockets = new ConcurrentHashMap<>();
|
||||
|
||||
//在线用户ID对应的WebSocket组,用于单用户多连接模式
|
||||
@Comment("在线用户ID对应的WebSocket组,用于单用户多连接模式")
|
||||
private final Map<Serializable, List<WebSocket>> websockets2 = new ConcurrentHashMap<>();
|
||||
|
||||
//用于PING的定时器
|
||||
@Comment("用于PING的定时器")
|
||||
private ScheduledThreadPoolExecutor scheduler;
|
||||
|
||||
//日志
|
||||
@Comment("日志")
|
||||
protected final Logger logger;
|
||||
|
||||
//FINEST日志级别
|
||||
@Comment("日志级别")
|
||||
protected final boolean finest;
|
||||
|
||||
@Comment("PING的间隔秒数")
|
||||
private int liveinterval;
|
||||
|
||||
protected WebSocketEngine(String engineid, boolean single, HttpContext context, int liveinterval, WebSocketNode node, Logger logger) {
|
||||
protected WebSocketEngine(String engineid, boolean single, HttpContext context, int liveinterval, WebSocketNode node, Convert sendConvert, Logger logger) {
|
||||
this.engineid = engineid;
|
||||
this.single = single;
|
||||
this.context = context;
|
||||
this.convert = context.getJsonConvert();
|
||||
this.sendConvert = sendConvert;
|
||||
this.node = node;
|
||||
this.liveinterval = liveinterval;
|
||||
this.logger = logger;
|
||||
this.index = sequence.getAndIncrement();
|
||||
this.finest = logger.isLoggable(Level.FINEST);
|
||||
this.index = sequence.getAndIncrement();
|
||||
}
|
||||
|
||||
void init(AnyValue conf) {
|
||||
@@ -84,8 +87,10 @@ public final class WebSocketEngine {
|
||||
return t;
|
||||
});
|
||||
long delay = (interval - System.currentTimeMillis() / 1000 % interval) + index * 5;
|
||||
final int intervalms = interval * 1000;
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
getLocalWebSockets().forEach(x -> x.sendPing());
|
||||
long now = System.currentTimeMillis();
|
||||
getLocalWebSockets().stream().filter(x -> (now - x.getLastSendTime()) > intervalms).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");
|
||||
}
|
||||
@@ -94,6 +99,7 @@ public final class WebSocketEngine {
|
||||
if (scheduler != null) scheduler.shutdownNow();
|
||||
}
|
||||
|
||||
@Comment("添加WebSocket")
|
||||
void add(WebSocket socket) {
|
||||
if (single) {
|
||||
websockets.put(socket._userid, socket);
|
||||
@@ -108,6 +114,7 @@ public final class WebSocketEngine {
|
||||
if (node != null) node.connect(socket._userid);
|
||||
}
|
||||
|
||||
@Comment("从WebSocketEngine删除指定WebSocket")
|
||||
void remove(WebSocket socket) {
|
||||
Serializable userid = socket._userid;
|
||||
if (single) {
|
||||
@@ -125,24 +132,32 @@ public final class WebSocketEngine {
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("给所有连接用户发送消息")
|
||||
public CompletableFuture<Integer> broadcastMessage(final Object message, final boolean last) {
|
||||
return broadcastMessage(null, message, last);
|
||||
}
|
||||
|
||||
@Comment("给指定WebSocket连接用户发送消息")
|
||||
public CompletableFuture<Integer> broadcastMessage(final Predicate<WebSocket> predicate, final Object message, final boolean last) {
|
||||
if (message instanceof CompletableFuture) {
|
||||
return ((CompletableFuture) message).thenCompose((json) -> broadcastMessage(json, last));
|
||||
return ((CompletableFuture) message).thenCompose((json) -> broadcastMessage(predicate, 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));
|
||||
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.sendConvert, message, last));
|
||||
packet.setSendBuffers(packet.encode(context.getBufferSupplier()));
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
for (WebSocket websocket : websockets.values()) {
|
||||
if (predicate != null && !predicate.test(websocket)) continue;
|
||||
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) {
|
||||
if (predicate != null && !predicate.test(websocket)) continue;
|
||||
future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
}
|
||||
@@ -153,11 +168,13 @@ public final class WebSocketEngine {
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
for (WebSocket websocket : websockets.values()) {
|
||||
if (predicate != null && !predicate.test(websocket)) continue;
|
||||
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) {
|
||||
if (predicate != null && !predicate.test(websocket)) continue;
|
||||
future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
|
||||
}
|
||||
}
|
||||
@@ -166,6 +183,7 @@ public final class WebSocketEngine {
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("给指定用户组发送消息")
|
||||
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));
|
||||
@@ -174,7 +192,7 @@ public final class WebSocketEngine {
|
||||
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));
|
||||
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.sendConvert, message, last));
|
||||
packet.setSendBuffers(packet.encode(context.getBufferSupplier()));
|
||||
CompletableFuture<Integer> future = null;
|
||||
if (single) {
|
||||
@@ -215,21 +233,33 @@ public final class WebSocketEngine {
|
||||
}
|
||||
}
|
||||
|
||||
Collection<WebSocket> getLocalWebSockets() {
|
||||
@Comment("获取所有连接")
|
||||
public Collection<WebSocket> getLocalWebSockets() {
|
||||
if (single) return websockets.values();
|
||||
List<WebSocket> list = new ArrayList<>();
|
||||
websockets2.values().forEach(x -> list.addAll(x));
|
||||
return list;
|
||||
}
|
||||
|
||||
//适用于单用户单连接模式
|
||||
@Comment("获取当前连接总数")
|
||||
public int getLocalWebSocketSize() {
|
||||
if (single) return websockets.size();
|
||||
return (int) websockets2.values().stream().mapToInt(sublist -> sublist.size()).count();
|
||||
}
|
||||
|
||||
@Comment("获取当前用户总数")
|
||||
public int getLocalUserSize() {
|
||||
return single ? websockets.size() : websockets2.size();
|
||||
}
|
||||
|
||||
@Comment("适用于单用户单连接模式")
|
||||
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);
|
||||
}
|
||||
|
||||
//适用于单用户多连接模式
|
||||
@Comment("适用于单用户多连接模式")
|
||||
public Stream<WebSocket> getLocalWebSockets(Serializable userid) {
|
||||
if (single) {
|
||||
WebSocket websocket = websockets.get(userid);
|
||||
|
||||
@@ -26,6 +26,12 @@ import org.redkale.util.*;
|
||||
*/
|
||||
public abstract class WebSocketNode {
|
||||
|
||||
@Comment("存储当前SNCP节点列表的key")
|
||||
public static final String SOURCE_SNCP_NODES_KEY = "redkale_sncpnodes";
|
||||
|
||||
@Comment("存储当前用户数量的key")
|
||||
public static final String SOURCE_USER_COUNT_KEY = "redkale_usercount";
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
protected final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
@@ -56,8 +62,10 @@ public abstract class WebSocketNode {
|
||||
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);
|
||||
this.localEngine.getLocalWebSockets().forEach(g -> disconnect(g.getUserid()));
|
||||
if (sncpNodeAddresses != null && localSncpAddress != null) {
|
||||
sncpNodeAddresses.removeSetItem(SOURCE_SNCP_NODES_KEY, localSncpAddress);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract CompletableFuture<List<String>> getWebSocketAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable userid);
|
||||
@@ -140,7 +148,55 @@ public abstract class WebSocketNode {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定用户是否WebSocket在线
|
||||
*
|
||||
* @param userid Serializable
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public CompletableFuture<Boolean> existsWebSocket(final Serializable userid) {
|
||||
if (this.localEngine != null && this.sncpNodeAddresses == null) {
|
||||
return CompletableFuture.completedFuture(this.localEngine.existsLocalWebSocket(userid));
|
||||
}
|
||||
return this.sncpNodeAddresses.existsAsync(userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户总数
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public CompletableFuture<Integer> getUserSize() {
|
||||
if (this.localEngine != null && this.sncpNodeAddresses == null) {
|
||||
return CompletableFuture.completedFuture(this.localEngine.getLocalUserSize());
|
||||
}
|
||||
return this.sncpNodeAddresses.getKeySizeAsync().thenCompose(count -> {
|
||||
return sncpNodeAddresses.existsAsync(SOURCE_SNCP_NODES_KEY).thenApply(exists -> exists ? (count - 1) : count);
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
/**
|
||||
* 获取本地的WebSocketEngine,没有则返回null
|
||||
*
|
||||
*
|
||||
* @return WebSocketEngine
|
||||
*/
|
||||
public final WebSocketEngine getLocalWebSocketEngine() {
|
||||
return this.localEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
|
||||
* 如果当前WebSocketNode是远程模式,此方法只发送远程连接
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @param userids Serializable[]
|
||||
*
|
||||
* @return 为0表示成功, 其他值表示部分发送异常
|
||||
*/
|
||||
public final CompletableFuture<Integer> sendMessage(Object message, final Serializable... userids) {
|
||||
return sendMessage(message, true, userids);
|
||||
}
|
||||
@@ -155,7 +211,6 @@ public abstract class WebSocketNode {
|
||||
*
|
||||
* @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) { //本地模式且没有分布式
|
||||
|
||||
@@ -10,8 +10,7 @@ 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;
|
||||
import org.redkale.convert.*;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -60,7 +59,7 @@ public final class WebSocketPacket {
|
||||
|
||||
protected Object sendJson;
|
||||
|
||||
JsonConvert sendConvert;
|
||||
Convert sendConvert;
|
||||
|
||||
ByteBuffer[] sendBuffers;
|
||||
|
||||
@@ -93,8 +92,8 @@ public final class WebSocketPacket {
|
||||
this.last = fin;
|
||||
}
|
||||
|
||||
public WebSocketPacket(JsonConvert convert, Object json, boolean fin) {
|
||||
this.type = FrameType.TEXT;
|
||||
public WebSocketPacket(Convert convert, Object json, boolean fin) {
|
||||
this.type = (convert == null || !convert.isBinary()) ? FrameType.TEXT : FrameType.BINARY;
|
||||
this.sendConvert = convert;
|
||||
this.sendJson = json;
|
||||
this.last = fin;
|
||||
@@ -113,7 +112,7 @@ public final class WebSocketPacket {
|
||||
ByteBuffer[] duplicateSendBuffers() {
|
||||
ByteBuffer[] rs = new ByteBuffer[this.sendBuffers.length];
|
||||
for (int i = 0; i < this.sendBuffers.length; i++) {
|
||||
rs[i] = this.sendBuffers[i].duplicate();
|
||||
rs[i] = this.sendBuffers[i].duplicate().asReadOnlyBuffer(); //必须使用asReadOnlyBuffer, 否则会导致ByteBuffer对应的byte[]被ObjectPool回收两次
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
@@ -160,7 +159,7 @@ public final class WebSocketPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + (payload != null ? (", payload=" + payload) : "") + (bytes != null ? (", bytes=[" + bytes.length + ']') : "") + "]";
|
||||
return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + (payload != null ? (", payload=" + payload) : "") + (bytes != null ? (", bytes=[" + bytes.length + ']') : "") + (sendJson != null ? (", json=" + sendJson) : "") + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,8 @@ 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;
|
||||
import org.redkale.convert.Convert;
|
||||
import org.redkale.util.Utility;
|
||||
|
||||
/**
|
||||
* WebSocket的消息接收发送器, 一个WebSocket对应一个WebSocketRunner
|
||||
@@ -44,23 +45,17 @@ class WebSocketRunner implements Runnable {
|
||||
|
||||
private final BlockingQueue<QueueEntry> queue = new ArrayBlockingQueue(1024);
|
||||
|
||||
private 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) {
|
||||
WebSocketRunner(Context context, WebSocket webSocket, BiConsumer<WebSocket, Object> messageConsumer, AsyncConnection channel) {
|
||||
this.context = context;
|
||||
this.engine = webSocket._engine;
|
||||
this.webSocket = webSocket;
|
||||
this.restMessageConsumer = messageConsumer;
|
||||
this.channel = channel;
|
||||
this.wsbinary = wsbinary;
|
||||
this.readBuffer = context.pollBuffer();
|
||||
this.convert = context.getJsonConvert();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,10 +65,6 @@ class WebSocketRunner implements Runnable {
|
||||
webSocket.onConnected();
|
||||
channel.setReadTimeoutSecond(300); //读取超时5分钟
|
||||
if (channel.isOpen()) {
|
||||
if (wsbinary) {
|
||||
webSocket.onRead(channel);
|
||||
return;
|
||||
}
|
||||
channel.read(readBuffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
private ByteBuffer recentExBuffer;
|
||||
@@ -109,8 +100,17 @@ class WebSocketRunner implements Runnable {
|
||||
}
|
||||
|
||||
try {
|
||||
WebSocketPacket packet = new WebSocketPacket().decode(context.getLogger(), readBuffer, exBuffers);
|
||||
|
||||
WebSocketPacket packet;
|
||||
try {
|
||||
packet = new WebSocketPacket().decode(context.getLogger(), readBuffer, exBuffers);
|
||||
} catch (Exception e) { //接收的消息体解析失败
|
||||
webSocket.onOccurException(e, Utility.append(new ByteBuffer[]{readBuffer}, exBuffers == null ? new ByteBuffer[0] : exBuffers));
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
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");
|
||||
@@ -118,7 +118,30 @@ class WebSocketRunner implements Runnable {
|
||||
}
|
||||
|
||||
if (packet.type == FrameType.TEXT) {
|
||||
Object message = convert.convertFrom(webSocket._messageTextType, packet.receiveMasker, packet.receiveBuffers);
|
||||
Convert textConvert = webSocket.getTextConvert();
|
||||
if (textConvert == null) {
|
||||
byte[] message = packet.getReceiveBytes();
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
try {
|
||||
webSocket.onMessage(new String(message, "UTF-8"), packet.last);
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e);
|
||||
}
|
||||
} else {
|
||||
Object message;
|
||||
try {
|
||||
message = textConvert.convertFrom(webSocket._messageTextType, packet.receiveMasker, packet.receiveBuffers);
|
||||
} catch (Exception e) { //接收的消息体解析失败
|
||||
webSocket.onOccurException(e, packet.receiveBuffers);
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
@@ -132,7 +155,10 @@ class WebSocketRunner implements Runnable {
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onTextMessage error (" + packet + ")", e);
|
||||
}
|
||||
}
|
||||
} else if (packet.type == FrameType.BINARY) {
|
||||
Convert binaryConvert = webSocket.getBinaryConvert();
|
||||
if (binaryConvert == null) {
|
||||
byte[] message = packet.getReceiveBytes();
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
@@ -143,6 +169,32 @@ class WebSocketRunner implements Runnable {
|
||||
} catch (Exception e) {
|
||||
context.getLogger().log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e);
|
||||
}
|
||||
} else {
|
||||
Object message;
|
||||
try {
|
||||
message = binaryConvert.convertFrom(webSocket._messageTextType, packet.receiveMasker, packet.receiveBuffers);
|
||||
} catch (Exception e) { //接收的消息体解析失败
|
||||
webSocket.onOccurException(e, packet.receiveBuffers);
|
||||
if (readBuffer != null) {
|
||||
readBuffer.clear();
|
||||
channel.read(readBuffer, null, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
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.PONG) {
|
||||
byte[] message = packet.getReceiveBytes();
|
||||
if (readBuffer != null) {
|
||||
@@ -207,7 +259,7 @@ class WebSocketRunner implements Runnable {
|
||||
if (closed) return CompletableFuture.completedFuture(RETCODE_WSOCKET_CLOSED);
|
||||
boolean debug = true;
|
||||
//System.out.println("推送消息");
|
||||
if (debug) context.getLogger().log(Level.FINEST, "send web socket message: " + packet);
|
||||
//if (debug) context.getLogger().log(Level.FINEST, "send web socket message: " + packet);
|
||||
final CompletableFuture<Integer> futureResult = new CompletableFuture<>();
|
||||
if (writing.getAndSet(true)) {
|
||||
queue.add(new QueueEntry(futureResult, packet));
|
||||
@@ -258,7 +310,7 @@ class WebSocketRunner implements Runnable {
|
||||
QueueEntry entry = queue.poll();
|
||||
if (entry != null) {
|
||||
future = entry.future;
|
||||
ByteBuffer[] buffers = packet.sendBuffers != null ? packet.duplicateSendBuffers() : packet.encode(context.getBufferSupplier());
|
||||
ByteBuffer[] buffers = entry.packet.sendBuffers != null ? entry.packet.duplicateSendBuffers() : entry.packet.encode(context.getBufferSupplier());
|
||||
lastSendTime = System.currentTimeMillis();
|
||||
channel.write(buffers, buffers, this);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ 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.convert.Convert;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
@@ -46,15 +46,12 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
public static final String WEBPARAM__LIVEINTERVAL = "liveinterval";
|
||||
|
||||
@Comment("WebScoket服务器给客户端进行ping操作的默认间隔时间, 单位: 秒")
|
||||
public static final int DEFAILT_LIVEINTERVAL = 60;
|
||||
public static final int DEFAILT_LIVEINTERVAL = 15;
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
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时会被修改
|
||||
@@ -63,11 +60,20 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
|
||||
protected int liveinterval = DEFAILT_LIVEINTERVAL;
|
||||
|
||||
@Resource
|
||||
protected JsonConvert jsonConvert; //Rest.createRestWebSocketServlet 需要过滤掉已有的@Resource
|
||||
@Resource(name = "jsonconvert")
|
||||
protected Convert jsonConvert;
|
||||
|
||||
@Resource(name = "$_textconvert")
|
||||
protected Convert textConvert;
|
||||
|
||||
@Resource(name = "$_binaryconvert")
|
||||
protected Convert binaryConvert;
|
||||
|
||||
@Resource(name = "$_sendconvert")
|
||||
protected Convert sendConvert;
|
||||
|
||||
@Resource(name = "$")
|
||||
protected WebSocketNode node; //Rest.createRestWebSocketServlet 需要过滤掉已有的@Resource
|
||||
protected WebSocketNode node;
|
||||
|
||||
protected WebSocketServlet() {
|
||||
Type msgtype = String.class;
|
||||
@@ -90,6 +96,9 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
|
||||
@Override
|
||||
final void preInit(HttpContext context, AnyValue conf) {
|
||||
if (this.textConvert == null) this.textConvert = jsonConvert;
|
||||
if (this.binaryConvert == null) this.binaryConvert = jsonConvert;
|
||||
if (this.sendConvert == null) this.sendConvert = jsonConvert;
|
||||
InetSocketAddress addr = context.getServerAddress();
|
||||
if (this.node == null) this.node = createWebSocketNode();
|
||||
if (this.node == null) { //没有部署SNCP,即不是分布式
|
||||
@@ -97,7 +106,7 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
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.localEngine = new WebSocketEngine("WebSocketEngine-" + addr.getHostString() + ":" + addr.getPort() + "-[" + resourceName() + "]", this.single, context, liveinterval, this.node, this.sendConvert, logger);
|
||||
this.node.init(conf);
|
||||
this.node.localEngine.init(conf);
|
||||
}
|
||||
@@ -131,7 +140,9 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
final WebSocket webSocket = this.createWebSocket();
|
||||
webSocket._engine = this.node.localEngine;
|
||||
webSocket._messageTextType = this.messageTextType;
|
||||
webSocket._jsonConvert = jsonConvert;
|
||||
webSocket._textConvert = textConvert;
|
||||
webSocket._binaryConvert = binaryConvert;
|
||||
webSocket._sendConvert = sendConvert;
|
||||
webSocket._remoteAddress = request.getRemoteAddress();
|
||||
webSocket._remoteAddr = request.getRemoteAddr();
|
||||
initRestWebSocket(webSocket);
|
||||
@@ -176,7 +187,7 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
|
||||
}
|
||||
webSocket._userid = userid;
|
||||
WebSocketServlet.this.node.localEngine.add(webSocket);
|
||||
WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer, response.removeChannel(), wsbinary);
|
||||
WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer, response.removeChannel());
|
||||
webSocket._runner = runner;
|
||||
context.runAsync(runner);
|
||||
response.finish(true);
|
||||
|
||||
@@ -256,13 +256,14 @@ public abstract class Sncp {
|
||||
* 创建Service的本地模式Class
|
||||
*
|
||||
* @param <T> Service子类
|
||||
* @param classLoader ClassLoader
|
||||
* @param name 资源名
|
||||
* @param serviceImplClass Service类
|
||||
*
|
||||
* @return Service实例
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static <T extends Service> Class<? extends T> createLocalServiceClass(final String name, final Class<T> serviceImplClass) {
|
||||
protected static <T extends Service> Class<? extends T> createLocalServiceClass(ClassLoader classLoader, final String name, final Class<T> serviceImplClass) {
|
||||
if (serviceImplClass == null) return null;
|
||||
if (!Service.class.isAssignableFrom(serviceImplClass)) return serviceImplClass;
|
||||
int mod = serviceImplClass.getModifiers();
|
||||
@@ -274,7 +275,7 @@ public abstract class Sncp {
|
||||
final String clientDesc = Type.getDescriptor(SncpClient.class);
|
||||
final String anyValueDesc = Type.getDescriptor(AnyValue.class);
|
||||
final String sncpDynDesc = Type.getDescriptor(SncpDyn.class);
|
||||
ClassLoader loader = Sncp.class.getClassLoader();
|
||||
ClassLoader loader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
|
||||
String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + LOCALPREFIX + serviceImplClass.getSimpleName();
|
||||
if (!name.isEmpty()) {
|
||||
boolean normal = true;
|
||||
@@ -285,7 +286,7 @@ public abstract class Sncp {
|
||||
newDynName += "_" + (normal ? name : hash(name));
|
||||
}
|
||||
try {
|
||||
return (Class<T>) Class.forName(newDynName.replace('/', '.'));
|
||||
return (Class<T>) loader.loadClass(newDynName.replace('/', '.'));
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -738,7 +739,7 @@ public abstract class Sncp {
|
||||
|
||||
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);
|
||||
return createLocalService(null, "", serviceImplClass, ResourceFactory.root(), transportFactory, clientSncpAddress, Utility.ofSet(groups), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -746,6 +747,7 @@ public abstract class Sncp {
|
||||
* 创建本地模式Service实例
|
||||
*
|
||||
* @param <T> Service泛型
|
||||
* @param classLoader ClassLoader
|
||||
* @param name 资源名
|
||||
* @param serviceImplClass Service类
|
||||
* @param resourceFactory ResourceFactory
|
||||
@@ -758,6 +760,7 @@ public abstract class Sncp {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Service> T createLocalService(
|
||||
final ClassLoader classLoader,
|
||||
final String name,
|
||||
final Class<T> serviceImplClass,
|
||||
final ResourceFactory resourceFactory,
|
||||
@@ -766,7 +769,7 @@ public abstract class Sncp {
|
||||
final Set<String> groups,
|
||||
final AnyValue conf) {
|
||||
try {
|
||||
final Class newClazz = createLocalServiceClass(name, serviceImplClass);
|
||||
final Class newClazz = createLocalServiceClass(classLoader, name, serviceImplClass);
|
||||
T rs = (T) newClazz.newInstance();
|
||||
//--------------------------------------
|
||||
Service remoteService = null;
|
||||
@@ -780,7 +783,7 @@ public abstract class Sncp {
|
||||
if (!field.getType().isAssignableFrom(newClazz)) continue;
|
||||
field.setAccessible(true);
|
||||
if (remoteService == null && clientSncpAddress != null) {
|
||||
remoteService = createRemoteService(name, serviceImplClass, transportFactory, clientSncpAddress, groups, conf);
|
||||
remoteService = createRemoteService(classLoader, name, serviceImplClass, transportFactory, clientSncpAddress, groups, conf);
|
||||
}
|
||||
if (remoteService != null) field.set(rs, remoteService);
|
||||
}
|
||||
@@ -821,7 +824,7 @@ public abstract class Sncp {
|
||||
|
||||
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);
|
||||
return createRemoteService(null, "", serviceImplClass, transportFactory, clientSncpAddress, Utility.ofSet(groups), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -865,6 +868,7 @@ public abstract class Sncp {
|
||||
* 创建远程模式的Service实例
|
||||
*
|
||||
* @param <T> Service泛型
|
||||
* @param classLoader ClassLoader
|
||||
* @param name 资源名
|
||||
* @param serviceTypeOrImplClass Service类
|
||||
* @param transportFactory TransportFactory
|
||||
@@ -877,6 +881,7 @@ public abstract class Sncp {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
public static <T extends Service> T createRemoteService(
|
||||
final ClassLoader classLoader,
|
||||
final String name,
|
||||
final Class<T> serviceTypeOrImplClass,
|
||||
final TransportFactory transportFactory,
|
||||
@@ -893,12 +898,12 @@ public abstract class Sncp {
|
||||
final String clientDesc = Type.getDescriptor(SncpClient.class);
|
||||
final String sncpDynDesc = Type.getDescriptor(SncpDyn.class);
|
||||
final String anyValueDesc = Type.getDescriptor(AnyValue.class);
|
||||
ClassLoader loader = Sncp.class.getClassLoader();
|
||||
ClassLoader loader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
|
||||
String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + REMOTEPREFIX + serviceTypeOrImplClass.getSimpleName();
|
||||
try {
|
||||
Class newClazz = Class.forName(newDynName.replace('/', '.'));
|
||||
Class newClazz = loader.loadClass(newDynName.replace('/', '.'));
|
||||
T rs = (T) newClazz.newInstance();
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, transportFactory, true, realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, transportFactory, true, realed ? createLocalServiceClass(loader, name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
client.setRemoteGroups(groups);
|
||||
client.setRemoteGroupTransport(transportFactory.loadRemoteTransport(clientAddress, groups));
|
||||
Field c = newClazz.getDeclaredField(FIELDPREFIX + "_client");
|
||||
@@ -987,7 +992,7 @@ public abstract class Sncp {
|
||||
mv.visitEnd();
|
||||
}
|
||||
int i = -1;
|
||||
for (final SncpAction entry : SncpClient.getSncpActions(realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass)) {
|
||||
for (final SncpAction entry : SncpClient.getSncpActions(realed ? createLocalServiceClass(loader, name, serviceTypeOrImplClass) : serviceTypeOrImplClass)) {
|
||||
final int index = ++i;
|
||||
final java.lang.reflect.Method method = entry.method;
|
||||
{
|
||||
@@ -1091,7 +1096,7 @@ public abstract class Sncp {
|
||||
}.loadClass(newDynName.replace('/', '.'), bytes);
|
||||
try {
|
||||
T rs = (T) newClazz.newInstance();
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, transportFactory, true, realed ? createLocalServiceClass(name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
SncpClient client = new SncpClient(name, serviceTypeOrImplClass, rs, transportFactory, true, realed ? createLocalServiceClass(loader, name, serviceTypeOrImplClass) : serviceTypeOrImplClass, clientAddress);
|
||||
client.setRemoteGroups(groups);
|
||||
client.setRemoteGroupTransport(transportFactory.loadRemoteTransport(clientAddress, groups));
|
||||
{
|
||||
|
||||
@@ -310,7 +310,7 @@ public final class SncpDynServlet extends SncpServlet {
|
||||
+ "DynAction" + serviceClass.getSimpleName() + "_" + method.getName() + "_" + actionid;
|
||||
while (true) {
|
||||
try {
|
||||
Class.forName(newDynName.replace('/', '.'));
|
||||
Thread.currentThread().getContextClassLoader().loadClass(newDynName.replace('/', '.'));
|
||||
newDynName += "_";
|
||||
} catch (Throwable ex) {
|
||||
break;
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.redkale.net.PrepareServlet;
|
||||
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.*;
|
||||
|
||||
@@ -55,10 +54,6 @@ public class SncpPrepareServlet extends PrepareServlet<DLong, SncpContext, SncpR
|
||||
return rs;
|
||||
}
|
||||
|
||||
public List<SncpServlet> getSncpServlets() {
|
||||
return new ArrayList<>(getServlets());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(SncpContext context, AnyValue config) {
|
||||
super.init(context, config); //必须要执行
|
||||
|
||||
@@ -37,15 +37,12 @@ public class SncpServer extends Server<DLong, SncpContext, SncpRequest, SncpResp
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除SncpFilter
|
||||
*
|
||||
* @param filterName SncpFilter名称
|
||||
*
|
||||
* @return SncpFilter
|
||||
*/
|
||||
public SncpFilter removeFilter(String filterName) {
|
||||
return (SncpFilter) this.prepare.removeFilter(filterName);
|
||||
public List<SncpServlet> getSncpServlets() {
|
||||
return this.prepare.getServlets();
|
||||
}
|
||||
|
||||
public List<SncpFilter> getSncpFilters() {
|
||||
return this.prepare.getFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +53,7 @@ public class SncpServer extends Server<DLong, SncpContext, SncpRequest, SncpResp
|
||||
*
|
||||
* @return SncpFilter
|
||||
*/
|
||||
public <T extends SncpFilter> T removeFilter(Class<T> filterClass) {
|
||||
public <T extends SncpFilter> T removeSncpFilter(Class<T> filterClass) {
|
||||
return (T) this.prepare.removeFilter(filterClass);
|
||||
}
|
||||
|
||||
@@ -89,10 +86,6 @@ public class SncpServer extends Server<DLong, SncpContext, SncpRequest, SncpResp
|
||||
this.prepare.addServlet(sds, null, Sncp.getConf(sncpService));
|
||||
}
|
||||
|
||||
public List<SncpServlet> getSncpServlets() {
|
||||
return ((SncpPrepareServlet) this.prepare).getSncpServlets();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected SncpContext createContext() {
|
||||
|
||||
@@ -40,6 +40,10 @@ public abstract class SncpServlet extends Servlet<SncpContext, SncpRequest, Sncp
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public Class getServiceType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public abstract DLong getServiceid();
|
||||
|
||||
protected ExecutorService getExecutor() {
|
||||
|
||||
@@ -29,35 +29,47 @@ public @interface RetLabel {
|
||||
|
||||
String value();
|
||||
|
||||
public static abstract class AbstractRetCode {
|
||||
public static class RetCode {
|
||||
|
||||
protected static final Map<Integer, String> rets = new HashMap();
|
||||
protected final Map<Integer, String> rets = new HashMap();
|
||||
|
||||
protected static void load(Class clazz) {
|
||||
rets.putAll(RetLoader.load(clazz));
|
||||
protected final Class clazz;
|
||||
|
||||
protected boolean inited;
|
||||
|
||||
public RetCode(Class clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public static RetResult retResult(int retcode) {
|
||||
public RetResult retResult(int retcode) {
|
||||
if (retcode == 0) return RetResult.success();
|
||||
return new RetResult(retcode, retInfo(retcode));
|
||||
}
|
||||
|
||||
public static RetResult retResult(int retcode, Object... args) {
|
||||
public RetResult retResult(int retcode, Object... args) {
|
||||
if (retcode == 0) return RetResult.success();
|
||||
if (args == null || args.length < 1) return new RetResult(retcode, retInfo(retcode));
|
||||
String info = MessageFormat.format(retInfo(retcode), args);
|
||||
return new RetResult(retcode, info);
|
||||
}
|
||||
|
||||
public static RetResult set(RetResult result, int retcode, Object... args) {
|
||||
public RetResult set(RetResult result, int retcode, Object... args) {
|
||||
if (retcode == 0) return result.retcode(0).retinfo("");
|
||||
if (args == null || args.length < 1) return result.retcode(retcode).retinfo(retInfo(retcode));
|
||||
String info = MessageFormat.format(retInfo(retcode), args);
|
||||
return result.retcode(retcode).retinfo(info);
|
||||
}
|
||||
|
||||
public static String retInfo(int retcode) {
|
||||
public String retInfo(int retcode) {
|
||||
if (retcode == 0) return "成功";
|
||||
if (!this.inited) {
|
||||
synchronized (this) {
|
||||
if (!this.inited) {
|
||||
rets.putAll(RetLoader.load(clazz));
|
||||
}
|
||||
this.inited = true;
|
||||
}
|
||||
}
|
||||
return rets.getOrDefault(retcode, "未知错误");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class WebSocketNodeService extends WebSocketNode implements Service {
|
||||
@Override
|
||||
public CompletableFuture<Void> connect(Serializable userid, InetSocketAddress sncpAddr) {
|
||||
CompletableFuture<Void> future = sncpNodeAddresses.appendSetItemAsync(userid, sncpAddr);
|
||||
future = future.thenAccept((a) -> sncpNodeAddresses.appendSetItemAsync("redkale_sncpnodes", sncpAddr));
|
||||
future = future.thenAccept((a) -> sncpNodeAddresses.appendSetItemAsync(SOURCE_SNCP_NODES_KEY, sncpAddr));
|
||||
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " connect from " + sncpAddr);
|
||||
return future;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.function.Consumer;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.convert.json.*;
|
||||
import org.redkale.net.sncp.Sncp;
|
||||
import org.redkale.net.sncp.*;
|
||||
import org.redkale.service.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
@@ -84,7 +84,7 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
String storeValueStr = prop.getValue("value-type");
|
||||
if (storeKeyStr != null && storeValueStr != null) {
|
||||
try {
|
||||
this.setStoreType(Class.forName(storeKeyStr), Class.forName(storeValueStr));
|
||||
this.setStoreType(Thread.currentThread().getContextClassLoader().loadClass(storeKeyStr), Thread.currentThread().getContextClassLoader().loadClass(storeValueStr));
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.SEVERE, self.getClass().getSimpleName() + " load key & value store class (" + storeKeyStr + ", " + storeValueStr + ") error", e);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
String expireHandlerClass = prop == null ? null : prop.getValue("expirehandler");
|
||||
if (expireHandlerClass != null) {
|
||||
try {
|
||||
this.expireHandler = (Consumer<CacheEntry>) Class.forName(expireHandlerClass).newInstance();
|
||||
this.expireHandler = (Consumer<CacheEntry>) Thread.currentThread().getContextClassLoader().loadClass(expireHandlerClass).newInstance();
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.SEVERE, self.getClass().getSimpleName() + " new expirehandler class (" + expireHandlerClass + ") instance error", e);
|
||||
}
|
||||
@@ -155,6 +155,8 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
}
|
||||
}
|
||||
if (remoteSource != null && !Sncp.isRemote(this)) {
|
||||
SncpClient client = Sncp.getSncpClient((Service) remoteSource);
|
||||
if (client != null && client.getRemoteGroupTransport() != null) {
|
||||
super.runAsync(() -> {
|
||||
try {
|
||||
CompletableFuture<List<CacheEntry<K, Object>>> listFuture = remoteSource.queryListAsync();
|
||||
@@ -173,6 +175,7 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception { //给Application 关闭时调用
|
||||
@@ -345,13 +348,13 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCollectionSize(final K key) {
|
||||
public int 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) {
|
||||
public CompletableFuture<Integer> getCollectionSizeAsync(final K key) {
|
||||
return CompletableFuture.supplyAsync(() -> getCollectionSize(key), getExecutor());
|
||||
}
|
||||
|
||||
@@ -440,6 +443,11 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return new ArrayList<>(container.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeySize() {
|
||||
return container.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<CacheEntry<K, Object>>> queryListAsync() {
|
||||
return CompletableFuture.completedFuture(new ArrayList<>(container.values()));
|
||||
@@ -455,4 +463,8 @@ public class CacheMemorySource<K extends Serializable, V extends Object> extends
|
||||
return CompletableFuture.completedFuture(new ArrayList<>(container.keySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getKeySizeAsync() {
|
||||
return CompletableFuture.completedFuture(container.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public Collection<V> getCollection(final K key);
|
||||
|
||||
public long getCollectionSize(final K key);
|
||||
public int getCollectionSize(final K key);
|
||||
|
||||
public Collection<V> getCollectionAndRefresh(final K key, final int expireSeconds);
|
||||
|
||||
@@ -59,6 +59,8 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public List<K> queryKeys();
|
||||
|
||||
public int getKeySize();
|
||||
|
||||
public List<CacheEntry<K, Object>> queryList();
|
||||
|
||||
//---------------------- CompletableFuture 异步版 ---------------------------------
|
||||
@@ -80,7 +82,7 @@ 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<Integer> getCollectionSizeAsync(final K key);
|
||||
|
||||
public CompletableFuture<Collection<V>> getCollectionAndRefreshAsync(final K key, final int expireSeconds);
|
||||
|
||||
@@ -94,6 +96,8 @@ public interface CacheSource<K extends Serializable, V extends Object> {
|
||||
|
||||
public CompletableFuture<List<K>> queryKeysAsync();
|
||||
|
||||
public CompletableFuture<Integer> getKeySizeAsync();
|
||||
|
||||
public CompletableFuture<List<CacheEntry<K, Object>>> queryListAsync();
|
||||
|
||||
default CompletableFuture<Boolean> isOpenAsync() {
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.function.*;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.Resource;
|
||||
import org.redkale.service.*;
|
||||
import static org.redkale.source.DataSources.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
/**
|
||||
@@ -29,35 +30,35 @@ import org.redkale.util.*;
|
||||
@AutoLoad(false)
|
||||
@SuppressWarnings("unchecked")
|
||||
@ResourceType(DataSource.class)
|
||||
public class DataJdbcSource extends AbstractService implements DataSource, Service, DataCacheListener, Function<Class, EntityInfo>, AutoCloseable, Resourcable {
|
||||
public class DataJdbcSource extends AbstractService implements DataSource, DataCacheListener, Function<Class, EntityInfo>, AutoCloseable, Resourcable {
|
||||
|
||||
private static final Flipper FLIPPER_ONE = new Flipper(1);
|
||||
protected static final Flipper FLIPPER_ONE = new Flipper(1);
|
||||
|
||||
final Logger logger = Logger.getLogger(DataJdbcSource.class.getSimpleName());
|
||||
protected final Logger logger = Logger.getLogger(DataJdbcSource.class.getSimpleName());
|
||||
|
||||
final AtomicBoolean debug = new AtomicBoolean(logger.isLoggable(Level.FINEST));
|
||||
protected final AtomicBoolean debug = new AtomicBoolean(logger.isLoggable(Level.FINEST));
|
||||
|
||||
final String name;
|
||||
protected final String name;
|
||||
|
||||
final URL conf;
|
||||
protected final URL conf;
|
||||
|
||||
final boolean cacheForbidden;
|
||||
protected final boolean cacheForbidden;
|
||||
|
||||
private final PoolJdbcSource readPool;
|
||||
protected final PoolJdbcSource readPool;
|
||||
|
||||
private final PoolJdbcSource writePool;
|
||||
protected final PoolJdbcSource writePool;
|
||||
|
||||
@Resource(name = "$")
|
||||
private DataCacheListener cacheListener;
|
||||
protected DataCacheListener cacheListener;
|
||||
|
||||
private final BiFunction<DataSource, Class, List> fullloader = (s, t) -> querySheet(false, false, t, null, null, (FilterNode) null).list(true);
|
||||
protected final BiFunction<DataSource, Class, List> fullloader = (s, t) -> querySheet(false, false, t, null, null, (FilterNode) null).list(true);
|
||||
|
||||
public DataJdbcSource(String unitName, Properties readprop, Properties writeprop) {
|
||||
this.name = unitName;
|
||||
this.conf = null;
|
||||
this.readPool = new PoolJdbcSource(this, "read", readprop);
|
||||
this.writePool = new PoolJdbcSource(this, "write", writeprop);
|
||||
this.cacheForbidden = "NONE".equalsIgnoreCase(readprop.getProperty("shared-cache-mode"));
|
||||
this.cacheForbidden = "NONE".equalsIgnoreCase(readprop.getProperty(JDBC_CACHE_MODE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,15 +72,15 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
writePool.close();
|
||||
}
|
||||
|
||||
private Connection createReadSQLConnection() {
|
||||
protected Connection createReadSQLConnection() {
|
||||
return readPool.poll();
|
||||
}
|
||||
|
||||
private <T> Connection createWriteSQLConnection() {
|
||||
protected <T> Connection createWriteSQLConnection() {
|
||||
return writePool.poll();
|
||||
}
|
||||
|
||||
private void closeSQLConnection(final Connection sqlconn) {
|
||||
protected void closeSQLConnection(final Connection sqlconn) {
|
||||
if (sqlconn == null) return;
|
||||
try {
|
||||
sqlconn.close();
|
||||
@@ -93,7 +94,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return loadEntityInfo(t);
|
||||
}
|
||||
|
||||
private <T> EntityInfo<T> loadEntityInfo(Class<T> clazz) {
|
||||
protected <T> EntityInfo<T> loadEntityInfo(Class<T> clazz) {
|
||||
return EntityInfo.load(clazz, this.cacheForbidden, this.readPool.props, this, fullloader);
|
||||
}
|
||||
|
||||
@@ -150,7 +151,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.runAsync(() -> insert(values), getExecutor());
|
||||
}
|
||||
|
||||
private <T> void insert(final Connection conn, final EntityInfo<T> info, T... values) {
|
||||
protected <T> void insert(final Connection conn, final EntityInfo<T> info, T... values) {
|
||||
if (values.length == 0) return;
|
||||
try {
|
||||
if (!info.isVirtualEntity()) {
|
||||
@@ -251,7 +252,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
}
|
||||
}
|
||||
|
||||
private <T> PreparedStatement createInsertPreparedStatement(final Connection conn, final String sql,
|
||||
protected <T> PreparedStatement createInsertPreparedStatement(final Connection conn, final String sql,
|
||||
final EntityInfo<T> info, T... values) throws SQLException {
|
||||
Attribute<T, Serializable>[] attrs = info.insertAttributes;
|
||||
final PreparedStatement prestmt = info.autoGenerated ? conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : conn.prepareStatement(sql);
|
||||
@@ -328,7 +329,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> delete(values), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int delete(final Connection conn, final EntityInfo<T> info, T... values) {
|
||||
protected <T> int delete(final Connection conn, final EntityInfo<T> info, T... values) {
|
||||
if (values.length == 0) return -1;
|
||||
final Attribute primary = info.getPrimary();
|
||||
Serializable[] ids = new Serializable[values.length];
|
||||
@@ -358,7 +359,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> delete(clazz, ids), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int delete(final Connection conn, final EntityInfo<T> info, Serializable... keys) {
|
||||
protected <T> int delete(final Connection conn, final EntityInfo<T> info, Serializable... keys) {
|
||||
if (keys.length == 0) return -1;
|
||||
int c = -1;
|
||||
int c2 = 0;
|
||||
@@ -430,7 +431,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> delete(clazz, flipper, node), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int delete(final Connection conn, final EntityInfo<T> info, final Flipper flipper, final FilterNode node) {
|
||||
protected <T> int delete(final Connection conn, final EntityInfo<T> info, final Flipper flipper, final FilterNode node) {
|
||||
int c = -1;
|
||||
try {
|
||||
if (!info.isVirtualEntity()) {
|
||||
@@ -521,7 +522,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> update(values), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int update(final Connection conn, final EntityInfo<T> info, T... values) {
|
||||
protected <T> int update(final Connection conn, final EntityInfo<T> info, T... values) {
|
||||
try {
|
||||
Class clazz = info.getType();
|
||||
int c = -1;
|
||||
@@ -613,11 +614,11 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> CompletableFuture<Integer> updateColumnupdateColumnAsync(final Class<T> clazz, final Serializable id, final String column, final Serializable value) {
|
||||
public <T> CompletableFuture<Integer> updateColumnAsync(final Class<T> clazz, final Serializable id, final String column, final Serializable value) {
|
||||
return CompletableFuture.supplyAsync(() -> updateColumn(clazz, id, column, value), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int updateColumn(Connection conn, final EntityInfo<T> info, Serializable id, String column, final Serializable value) {
|
||||
protected <T> int updateColumn(Connection conn, final EntityInfo<T> info, Serializable id, String column, final Serializable value) {
|
||||
try {
|
||||
int c = -1;
|
||||
if (!info.isVirtualEntity()) {
|
||||
@@ -684,7 +685,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> updateColumn(clazz, column, value, node), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int updateColumn(Connection conn, final EntityInfo<T> info, String column, final Serializable value, FilterNode node) {
|
||||
protected <T> int updateColumn(Connection conn, final EntityInfo<T> info, String column, final Serializable value, FilterNode node) {
|
||||
try {
|
||||
int c = -1;
|
||||
if (!info.isVirtualEntity()) {
|
||||
@@ -766,7 +767,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> updateColumn(clazz, id, values), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int updateColumn(final Connection conn, final EntityInfo<T> info, final Serializable id, final ColumnValue... values) {
|
||||
protected <T> int updateColumn(final Connection conn, final EntityInfo<T> info, final Serializable id, final ColumnValue... values) {
|
||||
if (values == null || values.length < 1) return -1;
|
||||
try {
|
||||
StringBuilder setsql = new StringBuilder();
|
||||
@@ -776,7 +777,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
List<byte[]> blobs = null;
|
||||
for (ColumnValue col : values) {
|
||||
Attribute<T, Serializable> attr = info.getUpdateAttribute(col.getColumn());
|
||||
if (attr == null) continue;
|
||||
if (attr == null) throw new RuntimeException(info.getType() + " cannot found column " + col.getColumn());
|
||||
attrs.add(attr);
|
||||
cols.add(col);
|
||||
if (!virtual) {
|
||||
@@ -882,7 +883,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> updateColumn(clazz, node, flipper, values), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int updateColumn(final Connection conn, final EntityInfo<T> info, final FilterNode node, final Flipper flipper, final ColumnValue... values) {
|
||||
protected <T> int updateColumn(final Connection conn, final EntityInfo<T> info, final FilterNode node, final Flipper flipper, final ColumnValue... values) {
|
||||
if (values == null || values.length < 1) return -1;
|
||||
try {
|
||||
StringBuilder setsql = new StringBuilder();
|
||||
@@ -992,7 +993,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> updateColumn(bean, selects), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int updateColumns(final Connection conn, final EntityInfo<T> info, final T bean, final SelectColumn selects) {
|
||||
protected <T> int updateColumns(final Connection conn, final EntityInfo<T> info, final T bean, final SelectColumn selects) {
|
||||
if (bean == null || selects == null) return -1;
|
||||
try {
|
||||
final Class<T> clazz = (Class<T>) bean.getClass();
|
||||
@@ -1068,7 +1069,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> updateColumn(bean, node, selects), getExecutor());
|
||||
}
|
||||
|
||||
private <T> int updateColumns(final Connection conn, final EntityInfo<T> info, final T bean, final FilterNode node, final SelectColumn selects) {
|
||||
protected <T> int updateColumns(final Connection conn, final EntityInfo<T> info, final T bean, final FilterNode node, final SelectColumn selects) {
|
||||
if (bean == null || node == null || selects == null) return -1;
|
||||
try {
|
||||
final Class<T> clazz = (Class<T>) bean.getClass();
|
||||
@@ -1266,7 +1267,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
for (FilterFuncColumn ffc : columns) {
|
||||
for (String col : ffc.cols()) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(ffc.func.getColumn((col == null || col.isEmpty() ? "*" : ("a." + col))));
|
||||
sb.append(ffc.func.getColumn((col == null || col.isEmpty() ? "*" : info.getSQLColumn("a", col))));
|
||||
}
|
||||
}
|
||||
final String sql = "SELECT " + sb + " FROM " + info.getTable(node) + " a"
|
||||
@@ -1325,7 +1326,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
final Set<String> haset = new HashSet<>();
|
||||
final CharSequence join = node == null ? null : node.createSQLJoin(this, false, joinTabalis, haset, info);
|
||||
final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis);
|
||||
final String sql = "SELECT " + func.getColumn((column == null || column.isEmpty() ? "*" : ("a." + column))) + " FROM " + info.getTable(node) + " a"
|
||||
final String sql = "SELECT " + func.getColumn((column == null || column.isEmpty() ? "*" : info.getSQLColumn("a", column))) + " FROM " + info.getTable(node) + " a"
|
||||
+ (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where));
|
||||
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(entityClass.getSimpleName() + " single sql=" + sql);
|
||||
conn.setReadOnly(true);
|
||||
@@ -1384,7 +1385,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
final Set<String> haset = new HashSet<>();
|
||||
final CharSequence join = node == null ? null : node.createSQLJoin(this, false, joinTabalis, haset, info);
|
||||
final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis);
|
||||
final String sql = "SELECT a." + sqlkey + ", " + func.getColumn((funcColumn == null || funcColumn.isEmpty() ? "*" : ("a." + funcColumn)))
|
||||
final String sql = "SELECT a." + sqlkey + ", " + func.getColumn((funcColumn == null || funcColumn.isEmpty() ? "*" : info.getSQLColumn("a", funcColumn)))
|
||||
+ " FROM " + info.getTable(node) + " a" + (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)) + " GROUP BY a." + sqlkey;
|
||||
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(entityClass.getSimpleName() + " single sql=" + sql);
|
||||
conn.setReadOnly(true);
|
||||
@@ -1877,7 +1878,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> queryColumnSheet(selectedColumn, clazz, flipper, node), getExecutor());
|
||||
}
|
||||
|
||||
private <T, V extends Serializable> Sheet<V> queryColumnSheet(final boolean needtotal, final String selectedColumn, final Class<T> clazz, final Flipper flipper, final FilterNode node) {
|
||||
protected <T, V extends Serializable> Sheet<V> queryColumnSheet(final boolean needtotal, final String selectedColumn, final Class<T> clazz, final Flipper flipper, final FilterNode node) {
|
||||
Sheet<T> sheet = querySheet(true, needtotal, clazz, SelectColumn.createIncludes(selectedColumn), flipper, node);
|
||||
final Sheet<V> rs = new Sheet<>();
|
||||
if (sheet.isEmpty()) return rs;
|
||||
@@ -2083,7 +2084,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return CompletableFuture.supplyAsync(() -> querySheet(clazz, selects, flipper, node), getExecutor());
|
||||
}
|
||||
|
||||
private <T> Sheet<T> querySheet(final boolean readcache, final boolean needtotal, final Class<T> clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) {
|
||||
protected <T> Sheet<T> querySheet(final boolean readcache, final boolean needtotal, final Class<T> clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) {
|
||||
final EntityInfo<T> info = loadEntityInfo(clazz);
|
||||
final EntityCache<T> cache = info.getCache();
|
||||
if (readcache && cache != null && cache.isFullLoaded()) {
|
||||
@@ -2134,7 +2135,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder multisplit(char ch1, char ch2, String split, StringBuilder sb, String str, int from) {
|
||||
protected static StringBuilder multisplit(char ch1, char ch2, String split, StringBuilder sb, String str, int from) {
|
||||
if (str == null) return sb;
|
||||
int pos1 = str.indexOf(ch1, from);
|
||||
if (pos1 < 0) return sb;
|
||||
@@ -2145,7 +2146,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
|
||||
return multisplit(ch1, ch2, split, sb, str, pos2 + 1);
|
||||
}
|
||||
|
||||
private int[] directExecute(final Connection conn, String... sqls) {
|
||||
protected int[] directExecute(final Connection conn, String... sqls) {
|
||||
if (sqls.length == 0) return new int[0];
|
||||
try {
|
||||
conn.setReadOnly(false);
|
||||
|
||||
@@ -198,7 +198,7 @@ public interface DataSource {
|
||||
*
|
||||
* @return 影响的记录条数CompletableFuture
|
||||
*/
|
||||
public <T> CompletableFuture<Integer> updateColumnupdateColumnAsync(final Class<T> clazz, final Serializable id, final String column, final Serializable value);
|
||||
public <T> CompletableFuture<Integer> updateColumnAsync(final Class<T> clazz, final Serializable id, final String column, final Serializable value);
|
||||
|
||||
/**
|
||||
* 更新符合过滤条件记录的单个字段 <br>
|
||||
|
||||
@@ -21,6 +21,8 @@ public final class DataSources {
|
||||
|
||||
public static final String JDBC_DATASOURCE_CLASS = "javax.persistence.datasource";
|
||||
|
||||
public static final String JDBC_CACHE_MODE = "javax.persistence.cachemode";
|
||||
|
||||
public static final String JDBC_CONNECTIONSMAX = "javax.persistence.connections.limit";
|
||||
|
||||
public static final String JDBC_CONTAIN_SQLTEMPLATE = "javax.persistence.contain.sqltemplate";
|
||||
@@ -85,7 +87,7 @@ public final class DataSources {
|
||||
String impl = readprop.getProperty(JDBC_DATASOURCE_CLASS, DataJdbcSource.class.getName());
|
||||
if (DataJdbcSource.class.getName().equals(impl)) return new DataJdbcSource(unitName, readprop, writeprop);
|
||||
try {
|
||||
Class ds = Class.forName(impl);
|
||||
Class ds = Thread.currentThread().getContextClassLoader().loadClass(impl);
|
||||
for (Constructor d : ds.getConstructors()) {
|
||||
Class<?>[] paramtypes = d.getParameterTypes();
|
||||
if (paramtypes.length == 1 && paramtypes[0] == Properties.class) {
|
||||
@@ -104,7 +106,7 @@ public final class DataSources {
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, Properties> loadPersistenceXml(final InputStream in0) {
|
||||
public static Map<String, Properties> loadPersistenceXml(final InputStream in0) {
|
||||
final Map<String, Properties> map = new LinkedHashMap();
|
||||
Properties result = new Properties();
|
||||
boolean flag = false;
|
||||
@@ -122,8 +124,8 @@ public final class DataSources {
|
||||
String value = reader.getAttributeValue(null, "value");
|
||||
if (name == null) continue;
|
||||
result.put(name, value);
|
||||
} else if (flag && "shared-cache-mode".equalsIgnoreCase(reader.getLocalName())) {
|
||||
result.put(reader.getLocalName(), reader.getElementText());
|
||||
} else if (flag && "shared-cache-mode".equalsIgnoreCase(reader.getLocalName())) { //兼容shared-cache-mode属性
|
||||
result.put(JDBC_CACHE_MODE, reader.getElementText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
*/
|
||||
package org.redkale.source;
|
||||
|
||||
import com.sun.istack.internal.logging.Logger;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.*;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.*;
|
||||
import javax.persistence.*;
|
||||
import org.redkale.util.*;
|
||||
|
||||
@@ -32,7 +31,7 @@ public final class EntityInfo<T> {
|
||||
private static final ConcurrentHashMap<Class, EntityInfo> entityInfos = new ConcurrentHashMap<>();
|
||||
|
||||
//日志
|
||||
private static final Logger logger = Logger.getLogger(EntityInfo.class);
|
||||
private static final Logger logger = Logger.getLogger(EntityInfo.class.getSimpleName());
|
||||
|
||||
//Entity类名
|
||||
private final Class<T> type;
|
||||
@@ -194,19 +193,20 @@ public final class EntityInfo<T> {
|
||||
try {
|
||||
loader = type.getAnnotation(VirtualEntity.class).loader().newInstance();
|
||||
} catch (Exception e) {
|
||||
logger.severe(type + " init @VirtualEntity.loader error", e);
|
||||
logger.log(Level.SEVERE, type + " init @VirtualEntity.loader error", e);
|
||||
}
|
||||
this.fullloader = loader;
|
||||
} else {
|
||||
this.fullloader = fullloader;
|
||||
this.table = (t == null) ? type.getSimpleName().toLowerCase() : (t.catalog().isEmpty()) ? t.name() : (t.catalog() + '.' + (t.name().isEmpty() ? type.getSimpleName().toLowerCase() : t.name()));
|
||||
if (t != null && !t.name().isEmpty() && t.name().indexOf('.') >= 0) throw new RuntimeException(type + " have illegal table.name on @Table");
|
||||
this.table = (t == null) ? type.getSimpleName().toLowerCase() : (t.catalog().isEmpty()) ? (t.name().isEmpty() ? type.getSimpleName().toLowerCase() : t.name()) : (t.catalog() + '.' + (t.name().isEmpty() ? type.getSimpleName().toLowerCase() : t.name()));
|
||||
}
|
||||
DistributeTable dt = type.getAnnotation(DistributeTable.class);
|
||||
DistributeTableStrategy dts = null;
|
||||
try {
|
||||
dts = (dt == null) ? null : dt.strategy().newInstance();
|
||||
} catch (Exception e) {
|
||||
logger.severe(type + " init DistributeTableStrategy error", e);
|
||||
logger.log(Level.SEVERE, type + " init DistributeTableStrategy error", e);
|
||||
}
|
||||
this.tableStrategy = dts;
|
||||
|
||||
@@ -215,7 +215,7 @@ public final class EntityInfo<T> {
|
||||
try {
|
||||
cp = this.creator.getClass().getMethod("create", Object[].class).getAnnotation(Creator.ConstructorParameters.class);
|
||||
} catch (Exception e) {
|
||||
logger.severe(type + " cannot find ConstructorParameters Creator", e);
|
||||
logger.log(Level.SEVERE, type + " cannot find ConstructorParameters Creator", e);
|
||||
}
|
||||
this.constructorParameters = (cp == null || cp.value().length < 1) ? null : cp.value();
|
||||
Attribute idAttr0 = null;
|
||||
|
||||
@@ -44,4 +44,11 @@ public @interface FilterJoinColumn {
|
||||
* @return 关联字段
|
||||
*/
|
||||
String[] columns();
|
||||
|
||||
/**
|
||||
* 备注描述
|
||||
*
|
||||
* @return 备注描述
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
|
||||
@@ -85,6 +85,13 @@ public class PoolJdbcSource {
|
||||
if (this.isOracle()) {
|
||||
this.props.setProperty(JDBC_CONTAIN_SQLTEMPLATE, "INSTR(${keystr}, ${column}) > 0");
|
||||
this.props.setProperty(JDBC_NOTCONTAIN_SQLTEMPLATE, "INSTR(${keystr}, ${column}) = 0");
|
||||
if (!this.props.containsKey(JDBC_TABLENOTEXIST_SQLSTATES)) {
|
||||
this.props.setProperty(JDBC_TABLENOTEXIST_SQLSTATES, "42000;42S02");
|
||||
}
|
||||
if (!this.props.containsKey(JDBC_TABLECOPY_SQLTEMPLATE)) {
|
||||
//注意:此语句复制表结构会导致默认值和主键信息的丢失
|
||||
this.props.setProperty(JDBC_TABLECOPY_SQLTEMPLATE, "CREATE TABLE ${newtable} AS SELECT * FROM ${oldtable} WHERE 1=2");
|
||||
}
|
||||
} else if (this.isSqlserver()) {
|
||||
this.props.setProperty(JDBC_CONTAIN_SQLTEMPLATE, "CHARINDEX(${column}, ${keystr}) > 0");
|
||||
this.props.setProperty(JDBC_NOTCONTAIN_SQLTEMPLATE, "CHARINDEX(${column}, ${keystr}) = 0");
|
||||
@@ -129,7 +136,7 @@ public class PoolJdbcSource {
|
||||
case "com.mysql.cj.jdbc.Driver":
|
||||
case "com.mysql.jdbc.Driver":
|
||||
try {
|
||||
Class.forName("com.mysql.cj.jdbc.MysqlConnectionPoolDataSource");
|
||||
Thread.currentThread().getContextClassLoader().loadClass("com.mysql.cj.jdbc.MysqlConnectionPoolDataSource");
|
||||
source = "com.mysql.cj.jdbc.MysqlConnectionPoolDataSource";
|
||||
} catch (Throwable e) {
|
||||
source = "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource";
|
||||
@@ -146,10 +153,10 @@ public class PoolJdbcSource {
|
||||
break;
|
||||
}
|
||||
}
|
||||
final Class clazz = Class.forName(source);
|
||||
final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(source);
|
||||
Object pdsource = clazz.newInstance();
|
||||
if (source.contains(".postgresql.")) {
|
||||
Class driver = Class.forName("org.postgresql.Driver");
|
||||
Class driver = Thread.currentThread().getContextClassLoader().loadClass("org.postgresql.Driver");
|
||||
Properties properties = (Properties) driver.getMethod("parseURL", String.class, Properties.class).invoke(null, url, new Properties());
|
||||
clazz.getMethod("setServerName", String.class).invoke(pdsource, properties.getProperty("PGHOST"));
|
||||
clazz.getMethod("setDatabaseName", String.class).invoke(pdsource, properties.getProperty("PGDBNAME"));
|
||||
|
||||
@@ -302,6 +302,18 @@ public abstract class AnyValue {
|
||||
return this;
|
||||
}
|
||||
|
||||
public DefaultAnyValue removeValue(String name, AnyValue value) {
|
||||
if (name == null || value == null || this.anyEntrys == null) return this;
|
||||
this.anyEntrys = Utility.remove(this.anyEntrys, (t) -> name.equals(((Entry) t).name) && ((Entry) t).getValue().equals(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public DefaultAnyValue removeValue(String name, String value) {
|
||||
if (name == null || value == null || this.stringEntrys == null) return this;
|
||||
this.stringEntrys = Utility.remove(this.stringEntrys, (t) -> name.equals(((Entry) t).name) && ((Entry) t).getValue().equals(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnyValue getAnyValue(String name) {
|
||||
for (Entry<DefaultAnyValue> en : this.anyEntrys) {
|
||||
@@ -533,4 +545,52 @@ public abstract class AnyValue {
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof AnyValue)) return false;
|
||||
AnyValue conf = (AnyValue) other;
|
||||
if (!equals(this.getStringEntrys(), conf.getStringEntrys())) return false;
|
||||
return equals(this.getAnyEntrys(), conf.getAnyEntrys());
|
||||
}
|
||||
|
||||
private static <T> boolean equals(Entry<? extends T>[] entry1, Entry<T>[] entry2) {
|
||||
if ((entry1 == null || entry1.length == 0) && (entry2 == null || entry2.length == 0)) return true;
|
||||
if (entry1.length != entry2.length) return false;
|
||||
for (int i = 0; i < entry1.length; i++) {
|
||||
if (!entry1[i].name.equals(entry2[i].name)) return false;
|
||||
if (!entry1[i].getValue().equals(entry2[i].getValue())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 19 * hash + Arrays.deepHashCode(this.getStringEntrys());
|
||||
hash = 19 * hash + Arrays.deepHashCode(this.getAnyEntrys());
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String toXML(String rootName) {
|
||||
return toXMLString(new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n"), rootName, this, 0).toString();
|
||||
}
|
||||
|
||||
protected static StringBuilder toXMLString(StringBuilder sb, String nodeName, AnyValue conf, int indent) { //indent: 缩进长度
|
||||
if (indent < 0) indent = 0;
|
||||
char[] chars = new char[indent];
|
||||
Arrays.fill(chars, ' ');
|
||||
final String space = new String(chars);
|
||||
Entry<AnyValue>[] anys = conf.getAnyEntrys();
|
||||
sb.append(space).append('<').append(nodeName);
|
||||
for (Entry<String> en : conf.getStringEntrys()) {
|
||||
sb.append(' ').append(en.name).append("=\"").append(en.value).append("\"");
|
||||
}
|
||||
if (anys == null || anys.length == 0) return sb.append("/>\r\n\r\n");
|
||||
sb.append(">\r\n\r\n");
|
||||
for (Entry<AnyValue> en : conf.getAnyEntrys()) {
|
||||
toXMLString(sb, en.name, en.getValue(), indent + 4);
|
||||
}
|
||||
return sb.append(space).append("</").append(nodeName).append(">\r\n\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
* @param <T> 字段依附的类
|
||||
* @param <F> 字段的数据类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface Attribute<T, F> {
|
||||
|
||||
/**
|
||||
@@ -431,7 +432,7 @@ public interface Attribute<T, F> {
|
||||
final String interDesc = Type.getDescriptor(clazz);
|
||||
final String columnDesc = Type.getDescriptor(column);
|
||||
|
||||
ClassLoader loader = Attribute.class.getClassLoader();
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
String newDynName = supDynName + "_Dyn_" + clazz.getSimpleName() + "_"
|
||||
+ fieldname.substring(fieldname.indexOf('.') + 1) + "_" + pcolumn.getSimpleName().replace("[]", "Array");
|
||||
if (String.class.getClassLoader() != clazz.getClassLoader()) {
|
||||
@@ -440,7 +441,7 @@ public interface Attribute<T, F> {
|
||||
+ fieldname.substring(fieldname.indexOf('.') + 1) + "_" + pcolumn.getSimpleName().replace("[]", "Array");
|
||||
}
|
||||
try {
|
||||
return (Attribute) Class.forName(newDynName.replace('/', '.')).newInstance();
|
||||
return (Attribute) loader.loadClass(newDynName.replace('/', '.')).newInstance();
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
//---------------------------------------------------
|
||||
|
||||
@@ -82,6 +82,7 @@ public interface Creator<T> {
|
||||
|
||||
String[] value();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static class CreatorInner {
|
||||
|
||||
static class SimpleClassVisitor extends ClassVisitor {
|
||||
@@ -108,7 +109,16 @@ public interface Creator<T> {
|
||||
return new MethodVisitor(Opcodes.ASM5) {
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {
|
||||
if (index > 0) fieldnames.add(name);
|
||||
if (index < 1) return;
|
||||
int size = fieldnames.size();
|
||||
//index不会按顺序执行的
|
||||
if (index > size) {
|
||||
for (int i = size; i < index; i++) {
|
||||
fieldnames.add(" ");
|
||||
}
|
||||
fieldnames.set(index - 1, name);
|
||||
}
|
||||
fieldnames.set(index - 1, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -214,14 +224,14 @@ public interface Creator<T> {
|
||||
final String supDynName = Creator.class.getName().replace('.', '/');
|
||||
final String interName = clazz.getName().replace('.', '/');
|
||||
final String interDesc = Type.getDescriptor(clazz);
|
||||
ClassLoader loader = Creator.class.getClassLoader();
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
String newDynName = supDynName + "_" + clazz.getSimpleName() + "_" + (System.currentTimeMillis() % 10000);
|
||||
if (String.class.getClassLoader() != clazz.getClassLoader()) {
|
||||
loader = clazz.getClassLoader();
|
||||
newDynName = interName + "_Dyn" + Creator.class.getSimpleName();
|
||||
}
|
||||
try {
|
||||
return (Creator) Class.forName(newDynName.replace('/', '.')).newInstance();
|
||||
return (Creator) loader.loadClass(newDynName.replace('/', '.')).newInstance();
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public final class Redkale {
|
||||
}
|
||||
|
||||
public static String getDotedVersion() {
|
||||
return "1.8.0-rc1";
|
||||
return "1.8.3";
|
||||
}
|
||||
|
||||
public static int getMajorVersion() {
|
||||
|
||||
49
src/org/redkale/util/RedkaleClassLoader.java
Normal file
49
src/org/redkale/util/RedkaleClassLoader.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class RedkaleClassLoader extends URLClassLoader {
|
||||
|
||||
public RedkaleClassLoader(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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL[] getURLs() {
|
||||
return super.getURLs();
|
||||
}
|
||||
|
||||
public URL[] getAllURLs() {
|
||||
ClassLoader loader = this;
|
||||
HashSet<URL> set = new HashSet<>();
|
||||
do {
|
||||
String loaderName = loader.getClass().getName();
|
||||
if (loaderName.startsWith("sun.") && loaderName.contains("ExtClassLoader")) continue;
|
||||
if (loader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) loader).getURLs()) {
|
||||
set.add(url);
|
||||
}
|
||||
}
|
||||
} while ((loader = loader.getParent()) != null);
|
||||
return set.toArray(new URL[set.size()]);
|
||||
}
|
||||
}
|
||||
@@ -35,13 +35,13 @@ public interface Reproduce<D, S> extends BiFunction<D, S, D> {
|
||||
final String destDesc = Type.getDescriptor(destClass);
|
||||
final String srcDesc = Type.getDescriptor(srcClass);
|
||||
String newDynName = supDynName + "Dyn_" + destClass.getSimpleName() + "_" + srcClass.getSimpleName();
|
||||
ClassLoader loader = Reproduce.class.getClassLoader();
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
if (String.class.getClassLoader() != destClass.getClassLoader()) {
|
||||
loader = destClass.getClassLoader();
|
||||
newDynName = destName + "_Dyn" + Reproduce.class.getSimpleName() + "_" + srcClass.getSimpleName();
|
||||
}
|
||||
try {
|
||||
return (Reproduce) Class.forName(newDynName.replace('/', '.')).newInstance();
|
||||
return (Reproduce) loader.loadClass(newDynName.replace('/', '.')).newInstance();
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user