89 Commits
1.8.0 ... 1.8.4

Author SHA1 Message Date
Redkale
f633e72c5f 2017-10-25 09:26:57 +08:00
Redkale
b8c85284ab 2017-10-25 09:21:48 +08:00
Redkale
a3af6749f6 暂时屏蔽Persist功能 2017-10-25 09:21:00 +08:00
Redkale
9128dffe35 新增Persist注解, 用于Service成员变量数据的临时缓存 2017-10-24 17:30:51 +08:00
Redkale
9273f2917e 2017-10-24 15:23:43 +08:00
Redkale
4d9d09af8c 2017-10-24 10:28:21 +08:00
Redkale
33beb60efe 2017-10-23 11:38:11 +08:00
Redkale
129bcf2f78 2017-10-22 16:01:20 +08:00
Redkale
f6c7dde28f Server增加setThreads方法,可以动态修改线程池的大小 2017-10-22 15:39:00 +08:00
Redkale
8fcd33b511 Copy一份javax.annotation.Resource源码到Redkale中,因JDK9中java.base模块中不包含javax.annotation.Resource 2017-10-22 13:01:03 +08:00
Redkale
66ec26e0ce 2017-10-22 11:14:49 +08:00
Redkale
969f7ada82 2017-10-22 11:04:00 +08:00
Redkale
7e37889372 增加Priority功能, 可以对Service、Filter、HttpServlet的加载顺序进行优先级设置,同时删掉Filter的getIndex方法 2017-10-22 11:00:09 +08:00
Redkale
c819d4d45b WATCH协议服务器默认host改为127.0.0.1 2017-10-22 10:13:24 +08:00
Redkale
6dc59b7abc 2017-10-21 11:07:16 +08:00
Redkale
60b24fa1ae 2017-10-20 10:58:31 +08:00
Redkale
b784993110 WebSocket的sendMessage系列方法增加Stream userids参数 2017-10-20 10:45:56 +08:00
Redkale
cc150a2cc6 @RestUploadFile标记的File对象可以获取到用户上传的文件名 2017-10-20 09:41:34 +08:00
Redkale
f6b407aa44 WebSocket识别Single模式,Single模式下重复登陆时默认会关闭之前的WebSocket连接 2017-10-18 10:47:43 +08:00
Redkale
a69d813bf5 WebSocket增加forceCloseWebSocket系列方法 2017-10-18 10:05:10 +08:00
Redkale
d1eff6144d convertMapTo方法识别CompletableFuture 2017-10-18 09:16:55 +08:00
Redkale
99589387d8 RetResult增加Map<String, String> attach成员变量 2017-10-17 09:42:32 +08:00
Redkale
e0041235fe WebSocket增加sendMap方法 2017-10-16 11:19:58 +08:00
Redkale
84b4eee7b5 重载WebSocket的sendMessage和broadcastMessage系列方法,增加Convert参数 2017-10-16 10:48:14 +08:00
Redkale
96c0a9bfe4 2017-10-15 22:22:31 +08:00
Redkale
89ad976744 增加BigDecimal的序列化和反序列化 2017-10-15 22:01:16 +08:00
Redkale
0eedc2c180 Redkale 1.8.4 开始 2017-10-15 21:59:59 +08:00
Redkale
8b3658143a 修复HttpResponse.finishFile传输偶尔异常的BUG 2017-09-25 11:40:49 +08:00
Redkale
6d69ff546b 2017-09-13 11:10:24 +08:00
Redkale
9555e3c9b9 2017-09-13 10:43:34 +08:00
Redkale
744634dbdd ResourceFactory 增加 public <A> List<A> query(final BiPredicate<String, A> predicate) 方法 2017-09-12 19:34:50 +08:00
Redkale
de5ee844c4 Convert 增加 convertMapTo 系列方法, HttpResponse增加 finishMapJson 系列方法 2017-09-09 22:36:35 +08:00
Redkale
ae73cee357 2017-09-09 13:24:19 +08:00
Redkale
d1cf9be8d7 AnyEncoder增加convertMapTo方法 2017-09-09 13:23:35 +08:00
Redkale
43ae77ab33 2017-09-01 11:13:22 +08:00
Redkale
182a75cfad 修复创建RestServlet时方法内含try/catch导致获取方法参数名错位的BUG 2017-09-01 10:51:41 +08:00
Redkale
222dc0edce 2017-08-28 21:54:48 +08:00
Redkale
c7a81513fe Redkale 1.8.3 开始 2017-08-28 19:20:51 +08:00
Redkale
4931c66868 2017-08-28 18:11:55 +08:00
Redkale
fcff1c3a4b 修复ByteBuffer的缓存区域会重复的BUG 2017-08-28 17:50:09 +08:00
Redkale
2005bf7e3b 2017-08-27 17:18:13 +08:00
Redkale
cb07a38f04 2017-08-27 14:37:06 +08:00
Redkale
6085cd5eef 2017-08-25 12:54:23 +08:00
Redkale
086275c135 2017-08-22 15:55:25 +08:00
Redkale
a449a96ef9 2017-08-22 15:10:17 +08:00
Redkale
bc3209a09c 2017-08-21 14:40:36 +08:00
Redkale
63d1ef985d 2017-08-18 14:48:21 +08:00
Redkale
24505564c8 2017-08-14 16:22:46 +08:00
Redkale
93a7bd63cf WebSocket 增加 onOccurException 方法 2017-08-12 07:56:42 +08:00
Redkale
0b9b5baa49 HTTP服务增加OPTIONS配置项功能 2017-08-10 16:19:58 +08:00
Redkale
5c4100e762 2017-08-10 15:05:46 +08:00
Redkale
eb861014c4 2017-08-10 10:24:37 +08:00
Redkale
e62f7ea63d 2017-08-06 12:25:56 +08:00
Redkale
d6df2055b2 2017-08-03 14:30:03 +08:00
Redkale
570aac947a 2017-08-03 12:16:14 +08:00
Redkale
1fdc33b565 2017-08-03 10:21:16 +08:00
Redkale
af8d0e978e 2017-07-27 16:52:00 +08:00
Redkale
c58022a81e Update README.md 2017-07-25 11:32:57 +08:00
Redkale
f4cf828993 Update README.md 2017-07-25 11:31:23 +08:00
Redkale
c4dc0de5fe Redkale 1.8.2 开始 2017-07-25 11:24:04 +08:00
Redkale
44507a97a6 兼容Oracle的复制表结构 2017-07-25 11:05:49 +08:00
Redkale
f4a7f1cff6 修复负载均衡无法正确切换节点的BUG 2017-07-24 18:29:40 +08:00
Redkale
a5fcb45a88 2017-07-24 17:34:07 +08:00
Redkale
bc8b68526d 2017-07-24 17:32:45 +08:00
Redkale
180f201dc0 修复多个<services>节点且其下指定service时无法获取所有service实例的BUG 2017-07-24 17:20:16 +08:00
Redkale
9ab315a405 2017-07-24 16:10:13 +08:00
Redkale
27b4742b6d 增加 TransportStrategy 功能 2017-07-21 16:07:13 +08:00
Redkale
702220d18e 2017-07-19 10:51:17 +08:00
Redkale
414489da8e 2017-07-15 22:24:10 +08:00
Redkale
77057df25d 2017-07-11 11:59:12 +08:00
Redkale
2f98cd1ab5 2017-07-11 11:29:57 +08:00
Redkale
8809fe8ec9 2017-07-11 10:09:49 +08:00
Redkale
f9702a9517 WebSocket时request.keepAlive设置为false 2017-07-11 10:07:42 +08:00
Redkale
29e46b9b68 暂时屏蔽304 2017-07-11 09:47:40 +08:00
Redkale
f838e35413 返回304的响应中增加Content-Length值 2017-07-11 09:30:27 +08:00
Redkale
f3bb77c49b 增加获取最后一次ping的时间点 2017-07-10 22:46:16 +08:00
Redkale
12fa033e15 增加判断用户是否在线和获取在线用户总数接口 2017-07-05 15:57:22 +08:00
Redkale
f4abfafea2 2017-07-05 15:42:54 +08:00
Redkale
0918af71d2 CacheSource增加getKeySize方法 2017-07-05 15:18:16 +08:00
Redkale
275befa330 private方法改成protected,方便重载 2017-07-05 10:28:55 +08:00
Redkale
ab4cd8bcb6 2017-07-02 17:30:08 +08:00
Redkale
36c109b32f 2017-07-02 17:29:22 +08:00
Redkale
73a915665d 2017-07-02 17:00:59 +08:00
Redkale
bd6d71c94a 修复@RestWebSocket对应的WebSocketServlet.resourceName没有重载的BUG 2017-07-01 16:31:00 +08:00
Redkale
842e93507c WebSocketEngine.broadcastMessage 增加 Predicate<WebSocket> 参数 2017-07-01 15:46:51 +08:00
Redkale
76df1108d7 WebSocketEngine.broadcastMessage 增加 Predicate<WebSocket> 参数 2017-07-01 15:38:47 +08:00
Redkale
941d09cde2 2017-07-01 15:29:26 +08:00
Redkale
9dd3e1da07 允许本地模式的Service通过@Resource可以获取Application、ResourceFactory等资源 2017-06-30 13:35:44 +08:00
Redkale
2bf73245ec Redkale 1.8.1 开始 2017-06-26 09:40:30 +08:00
71 changed files with 1842 additions and 335 deletions

View File

@@ -22,3 +22,7 @@
&nbsp;&nbsp;&nbsp;由于RedKale使用了JDK 8 内置的ASM包所以需要在源码工程中的编译器选项中加入 <b>-XDignore.symbol.file=true</b>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<h5>详情请访问:&nbsp;&nbsp;&nbsp;&nbsp;<a href='https://redkale.org' target='_blank'>https://redkale.org</a></h5>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<h5>基本文档:&nbsp;&nbsp;&nbsp;&nbsp;<a href='https://redkale.org/articles.html' target='_blank'>https://redkale.org/articles.html</a></h5>
&nbsp;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -10,6 +10,16 @@
<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"/>
<filters autoload="true"/>

View File

@@ -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

View File

@@ -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&amp;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>

View File

@@ -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"/>
@@ -210,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=""/>
<defcookie domain="" path=""/>
<addheader name="Access-Control-Allow-Origin" value="request.headers.Origin" />
<setheader name="Access-Control-Allow-Credentials" value="true"/>
<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>
<!--

View File

@@ -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

View File

@@ -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&amp;autoReconnectForPools=true&amp;characterEncoding=utf8 -->
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbim?characterEncoding=utf8"/>

View File

@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 值越大,优先级越高
*
* @since Common Annotations 1.2
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Priority {
int value();
}

View File

@@ -0,0 +1,32 @@
/*
* 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 javax.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @since Common Annotations 1.0
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
public enum AuthenticationType {
CONTAINER,
APPLICATION
}
public String name() default "";
public Class<?> type() default Object.class;
public AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
public boolean shareable() default true;
public String description() default "";
public String mappedName() default "";
public String lookup() default "";
}

View File

@@ -32,6 +32,7 @@ import org.redkale.util.AnyValue.DefaultAnyValue;
import org.redkale.util.*;
import org.redkale.watch.*;
import org.w3c.dom.*;
import sun.misc.Signal;
/**
*
@@ -232,12 +233,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;
@@ -246,9 +249,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;
@@ -257,6 +260,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);
@@ -271,8 +278,21 @@ 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 RedkaleClassLoader(Thread.currentThread().getContextClassLoader());
if (transportGroup == null) {
final AtomicInteger counter = new AtomicInteger();
transportExec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 8, (Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("Transport-Thread-" + counter.incrementAndGet());
return t;
});
try {
transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
this.transportFactory = new TransportFactory(transportExec, transportPool, transportGroup, strategy);
Thread.currentThread().setContextClassLoader(this.classLoader);
this.serverClassLoader = new RedkaleClassLoader(this.classLoader);
}
@@ -379,7 +399,7 @@ 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);
@@ -431,7 +451,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();
}
@@ -588,10 +608,47 @@ public final class Application {
runServers(timecd, others);
runServers(timecd, watchs); //必须在所有服务都启动后再启动WATCH服务
timecd.await();
if (!singletonrun) signalHandle();
if (!singletonrun) clearPersistData();
logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms\r\n");
if (!singletonrun) this.serversLatch.await();
}
private void clearPersistData() {
File cachedir = new File(home, "cache");
if (!cachedir.isDirectory()) return;
for (File file : cachedir.listFiles()) {
if (file.getName().startsWith("persist-")) file.delete();
}
}
private void signalHandle() {
//http://www.comptechdoc.org/os/linux/programming/linux_pgsignals.html
String[] sigs = new String[]{"HUP", "TERM", "INT", "QUIT", "KILL", "TSTP", "USR1", "USR2", "STOP"};
List<sun.misc.Signal> list = new ArrayList<>();
for (String sig : sigs) {
try {
list.add(new sun.misc.Signal(sig));
} catch (Exception e) {
}
}
sun.misc.SignalHandler handler = new sun.misc.SignalHandler() {
private volatile boolean runed;
@Override
public void handle(Signal sig) {
if (runed) return;
runed = true;
logger.info(Application.this.getClass().getSimpleName() + " stoped\r\n");
System.exit(0);
}
};
for (Signal sig : list) {
Signal.handle(sig, handler);
}
}
@SuppressWarnings("unchecked")
private void runServers(CountDownLatch timecd, final List<AnyValue> serconfs) throws Exception {
this.servicecdl = new CountDownLatch(serconfs.size());
@@ -758,6 +815,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) {

View File

@@ -99,7 +99,11 @@ public final class ClassFilter<T> {
* @return Set&lt;FilterEntry&lt;T&gt;&gt;
*/
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;
}
/**
@@ -108,7 +112,11 @@ public final class ClassFilter<T> {
* @return Set&lt;FilterEntry&lt;T&gt;&gt;
*/
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;
}
/**
@@ -118,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;
}
@@ -183,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);
}
}
}

View File

@@ -22,6 +22,7 @@ import java.util.logging.Formatter;
*
* @author zhangjx
*/
@SuppressWarnings("unchecked")
public class LogFileHandler extends Handler {
/**

View File

@@ -10,7 +10,7 @@ import java.lang.reflect.*;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.logging.Level;
import javax.annotation.Resource;
import javax.annotation.*;
import org.redkale.boot.ClassFilter.FilterEntry;
import org.redkale.net.*;
import org.redkale.net.http.*;
@@ -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;
@@ -150,7 +152,12 @@ public class NodeHttpServer extends NodeServer {
list.sort((FilterEntry<? extends Servlet> o1, FilterEntry<? extends Servlet> o2) -> { //必须保证WebSocketServlet优先加载 因为要确保其他的HttpServlet可以注入本地模式的WebSocketNode
boolean ws1 = WebSocketServlet.class.isAssignableFrom(o1.getType());
boolean ws2 = WebSocketServlet.class.isAssignableFrom(o2.getType());
if (ws1 == ws2) return o1.getType().getName().compareTo(o2.getType().getName());
if (ws1 == ws2) {
Priority p1 = o1.getType().getAnnotation(Priority.class);
Priority p2 = o2.getType().getAnnotation(Priority.class);
int v = (p2 == null ? 0 : p2.value()) - (p1 == null ? 0 : p1.value());
return v == 0 ? o1.getType().getName().compareTo(o2.getType().getName()) : 0;
}
return ws1 ? -1 : 1;
});
final List<AbstractMap.SimpleEntry<String, String[]>> ss = sb == null ? null : new ArrayList<>();
@@ -195,6 +202,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服务

View File

@@ -15,10 +15,11 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;
import javax.annotation.Resource;
import javax.annotation.*;
import javax.persistence.Transient;
import static org.redkale.boot.Application.*;
import org.redkale.boot.ClassFilter.FilterEntry;
import org.redkale.convert.bson.*;
import org.redkale.net.Filter;
import org.redkale.net.*;
import org.redkale.net.http.WebSocketServlet;
@@ -56,7 +57,7 @@ public abstract class NodeServer {
//ClassLoader
protected RedkaleClassLoader serverClassLoader;
protected final Thread serverThread;
//当前Server的SNCP协议的组
@@ -108,7 +109,7 @@ public abstract class NodeServer {
public void init(AnyValue config) throws Exception {
this.serverConf = config == null ? AnyValue.create() : config;
if (isSNCP()) { // SNCP协议
String host = this.serverConf.getValue("host", "0.0.0.0").replace("0.0.0.0", "");
String host = this.serverConf.getValue("host", isWATCH() ? "127.0.0.1" : "0.0.0.0").replace("0.0.0.0", "");
this.sncpAddress = new InetSocketAddress(host.isEmpty() ? application.localAddress.getHostAddress() : host, this.serverConf.getIntValue("port"));
this.sncpGroup = application.transportFactory.findGroupName(this.sncpAddress);
//单向SNCP服务不需要对等group
@@ -173,7 +174,7 @@ 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 {
@@ -183,9 +184,7 @@ public abstract class NodeServer {
} 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);
}
@@ -258,23 +257,32 @@ 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 : serverClassLoader.loadClass(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(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];
if (sourceType == CacheMemorySource.class) {
CacheMemorySource memorySource = (CacheMemorySource) source;
memorySource.setStoreType(pt == null ? Serializable.class : (Class) pt.getActualTypeArguments()[0], valType instanceof Class ? (Class) valType : Object.class);
if (field.getAnnotation(Transient.class) != null) memorySource.setNeedStore(false); //必须在setStoreType之后
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];
if (sourceType == CacheMemorySource.class) {
CacheMemorySource memorySource = (CacheMemorySource) source;
memorySource.setStoreType(pt == null ? Serializable.class : (Class) pt.getActualTypeArguments()[0], valType instanceof Class ? (Class) valType : Object.class);
if (field.getAnnotation(Transient.class) != null) memorySource.setNeedStore(false); //必须在setStoreType之后
}
application.cacheSources.add((CacheSource) source);
appResFactory.register(resourceName, genericType, source);
appResFactory.register(resourceName, CacheSource.class, source);
}
application.cacheSources.add(source);
appResFactory.register(resourceName, genericType, source);
appResFactory.register(resourceName, CacheSource.class, source);
field.set(src, source);
rf.inject(source, self); //
if (source instanceof Service) ((Service) source).init(sourceConf);
@@ -338,7 +346,7 @@ public abstract class NodeServer {
} else {
service = Sncp.createRemoteService(serverClassLoader, resourceName, serviceImplClass, appTransportFactory, NodeServer.this.sncpAddress, groups, entry.getProperty());
}
if (SncpClient.parseMethod(serviceImplClass).isEmpty()) return; //class没有可用的方法 通常为BaseService
if (SncpClient.parseMethod(serviceImplClass).isEmpty() && serviceImplClass.getAnnotation(Priority.class) == null) return; //class没有可用的方法且没有标记启动优先级的 通常为BaseService
final Class restype = Sncp.getResourceType(service);
if (rf.find(resourceName, restype) == null) {
@@ -391,15 +399,20 @@ public abstract class NodeServer {
//----------------- init -----------------
List<Service> swlist = new ArrayList<>(localServices);
Collections.sort(swlist, (o1, o2) -> {
Priority p1 = o1.getClass().getAnnotation(Priority.class);
Priority p2 = o2.getClass().getAnnotation(Priority.class);
int v = (p2 == null ? 0 : p2.value()) - (p1 == null ? 0 : p1.value());
if (v != 0) return v;
int rs = Sncp.getResourceType(o1).getName().compareTo(Sncp.getResourceType(o2).getName());
if (rs == 0) rs = Sncp.getResourceName(o1).compareTo(Sncp.getResourceName(o2));
return rs;
});
localServices.clear();
localServices.addAll(swlist);
//this.loadPersistData();
final List<String> slist = sb == null ? null : new CopyOnWriteArrayList<>();
CountDownLatch clds = new CountDownLatch(localServices.size());
localServices.parallelStream().forEach(y -> {
localServices.stream().forEach(y -> {
try {
long s = System.currentTimeMillis();
y.init(Sncp.getConf(y));
@@ -413,7 +426,6 @@ public abstract class NodeServer {
clds.await();
if (slist != null && sb != null) {
List<String> wlist = new ArrayList<>(slist); //直接使用CopyOnWriteArrayList偶尔会出现莫名的异常(CopyOnWriteArrayList源码1185行)
Collections.sort(wlist);
for (String s : wlist) {
sb.append(s);
}
@@ -426,6 +438,125 @@ public abstract class NodeServer {
maxClassNameLength = Math.max(maxClassNameLength, Sncp.getResourceType(y).getName().length() + 1);
}
//尚未完整实现, 先屏蔽, 单个Service在多个Server中存在的情况下进行缓存的方案还未考虑清楚
@SuppressWarnings("unchecked")
private void loadPersistData() throws Exception {
File home = application.getHome();
if (home == null || !home.isDirectory()) return;
File cachedir = new File(home, "cache");
if (!cachedir.isDirectory()) return;
int port = this.server.getSocketAddress().getPort();
final String prefix = "persist-" + port + "-";
final BsonConvert convert = BsonFactory.create().skipAllIgnore(true).getConvert();
synchronized (this.application) {
for (final File file : cachedir.listFiles((dir, name) -> name.startsWith(prefix))) {
if (!file.getName().endsWith(".bat")) continue;
String classAndResname = file.getName().substring(prefix.length(), file.getName().length() - 4); //去掉尾部的.bat
int pos = classAndResname.indexOf('-');
String servtype = pos > 0 ? classAndResname.substring(0, pos) : classAndResname;
String resname = pos > 0 ? classAndResname.substring(pos + 1) : "";
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
int b;
while ((b = in.read()) != '\n') out.write(b);
final String[] fieldNames = out.toString().split(",");
int timeout = (int) ((System.currentTimeMillis() - file.lastModified()) / 1000);
for (final Service service : this.localServices) {
if (!servtype.equals(Sncp.getResourceType(service).getName())) continue;
if (!resname.equals(Sncp.getResourceName(service))) continue;
for (final String fieldName : fieldNames) {
Field field = null;
Class clzz = service.getClass();
do {
try {
field = clzz.getDeclaredField(fieldName);
break;
} catch (Exception e) {
}
} while ((clzz = clzz.getSuperclass()) != Object.class);
field.setAccessible(true);
Object val = convert.convertFrom(field.getGenericType(), in);
Persist persist = field.getAnnotation(Persist.class);
if (persist.timeout() == 0 || persist.timeout() >= timeout) {
if (Modifier.isFinal(field.getModifiers())) {
if (Map.class.isAssignableFrom(field.getType())) {
((Map) field.get(service)).putAll((Map) val);
} else if (Collection.class.isAssignableFrom(field.getType())) {
((Collection) field.get(service)).addAll((Collection) val);
}
} else {
field.set(service, val);
}
}
if (in.read() != '\n') logger.log(Level.SEVERE, servtype + "'s [" + resname + "] load value error");
}
}
in.close();
}
}
}
//尚未完整实现, 先屏蔽
@SuppressWarnings("unchecked")
private void savePersistData() throws IOException {
File home = application.getHome();
if (home == null || !home.isDirectory()) return;
File cachedir = new File(home, "cache");
int port = this.server.getSocketAddress().getPort();
final String prefix = "persist-" + port + "-";
final BsonConvert convert = BsonFactory.create().skipAllIgnore(true).getConvert();
for (final Service service : this.localServices) {
Class clzz = service.getClass();
final Set<String> fieldNameSet = new HashSet<>();
final List<Field> fields = new ArrayList<>();
final StringBuilder sb = new StringBuilder();
do {
for (Field field : clzz.getDeclaredFields()) {
if (field.getAnnotation(Persist.class) == null) continue;
if (fieldNameSet.contains(field.getName())) continue;
if (Modifier.isStatic(field.getModifiers())) throw new RuntimeException(field + " cannot static on @" + Persist.class.getName() + " in " + clzz.getName());
if (Modifier.isFinal(field.getModifiers()) && !Map.class.isAssignableFrom(field.getType()) && !Collection.class.isAssignableFrom(field.getType())) {
throw new RuntimeException(field + " cannot final on @" + Persist.class.getName() + " in " + clzz.getName());
}
fieldNameSet.add(field.getName());
field.setAccessible(true);
try {
if (field.get(service) == null) continue;
} catch (Exception e) {
logger.log(Level.SEVERE, field + " get value error", e);
continue;
}
fields.add(field);
if (sb.length() > 0) sb.append(',');
sb.append(field.getName());
}
} while ((clzz = clzz.getSuperclass()) != Object.class);
if (fields.isEmpty()) continue; //没有数据需要缓存
// synchronized (this.application.localServices) {
// if (this.application.localServices.contains(service)) continue;
// this.application.localServices.add(service);
// }
if (!cachedir.isDirectory()) cachedir.mkdirs();
String resname = Sncp.getResourceName(service);
FileOutputStream out = new FileOutputStream(new File(cachedir, prefix + Sncp.getResourceType(service).getName() + (resname.isEmpty() ? "" : ("-" + resname)) + ".bat"));
out.write(sb.toString().getBytes());
out.write('\n');
for (Field field : fields) {
Object val = null;
try {
val = field.get(service);
} catch (Exception e) {
logger.log(Level.SEVERE, field + " save value error", e);
}
convert.convertTo(out, field.getGenericType(), val);
out.write('\n');
}
out.close();
}
}
protected abstract ClassFilter<Filter> createFilterClassFilter();
protected abstract ClassFilter<Servlet> createServletClassFilter();
@@ -435,7 +566,7 @@ 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,
@@ -531,7 +662,7 @@ public abstract class NodeServer {
public void setServerClassLoader(RedkaleClassLoader serverClassLoader) {
Objects.requireNonNull(this.serverClassLoader);
this.serverClassLoader = serverClassLoader;
this.serverThread.setContextClassLoader(serverClassLoader);
this.serverThread.setContextClassLoader(serverClassLoader);
}
public InetSocketAddress getSncpAddress() {

View File

@@ -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

View File

@@ -6,6 +6,7 @@
package org.redkale.convert;
import java.lang.reflect.Type;
import java.util.concurrent.CompletableFuture;
/**
* 对不明类型的对象进行序列化; BSON序列化时将对象的类名写入WriterJSON则不写入。
@@ -35,6 +36,28 @@ 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();
Object val = values[i + 1];
if (val instanceof CompletableFuture) {
this.convertTo(out, (T) ((CompletableFuture) val).join());
} else {
this.convertTo(out, (T) val);
}
}
out.writeMapE();
}
}
@Override
public Type getType() {
return Object.class;

View File

@@ -0,0 +1,36 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkale.convert;
import java.lang.reflect.Type;
/**
* 二进制序列化/反序列化操作类
*
* <p>
* 详情见: https://redkale.org
*
* @author zhangjx
* @param <R> Reader输入的子类
* @param <W> Writer输出的子类
*/
public abstract class BinaryConvert<R extends Reader, W extends Writer> extends Convert<R, W> {
protected BinaryConvert(ConvertFactory<R, W> factory) {
super(factory);
}
@Override
public final boolean isBinary() {
return true;
}
public abstract byte[] convertTo(final Object value);
public abstract byte[] convertTo(final Type type, final Object value);
public abstract byte[] convertMapTo(final Object... values);
}

View File

@@ -40,4 +40,7 @@ public abstract class Convert<R extends Reader, W extends Writer> {
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);
}

View File

@@ -7,7 +7,7 @@ package org.redkale.convert;
import java.io.File;
import java.lang.reflect.*;
import java.math.BigInteger;
import java.math.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.CompletionHandler;
@@ -89,6 +89,7 @@ public abstract class ConvertFactory<R extends Reader, W extends Writer> {
this.register(CharSequence.class, CharSequenceSimpledCoder.instance);
this.register(java.util.Date.class, DateSimpledCoder.instance);
this.register(BigInteger.class, BigIntegerSimpledCoder.instance);
this.register(BigDecimal.class, BigDecimalSimpledCoder.instance);
this.register(InetAddress.class, InetAddressSimpledCoder.instance);
this.register(DLong.class, DLongSimpledCoder.instance);
this.register(Class.class, TypeSimpledCoder.instance);

View File

@@ -0,0 +1,36 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkale.convert;
import java.lang.reflect.Type;
/**
* 文本序列化/反序列化操作类
*
* <p>
* 详情见: https://redkale.org
*
* @author zhangjx
* @param <R> Reader输入的子类
* @param <W> Writer输出的子类
*/
public abstract class TextConvert<R extends Reader, W extends Writer> extends Convert<R, W> {
protected TextConvert(ConvertFactory<R, W> factory) {
super(factory);
}
@Override
public final boolean isBinary() {
return false;
}
public abstract String convertTo(final Object value);
public abstract String convertTo(final Type type, final Object value);
public abstract String convertMapTo(final Object... values);
}

View File

@@ -37,7 +37,7 @@ import org.redkale.util.*;
*
* @author zhangjx
*/
public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
public final class BsonConvert extends BinaryConvert<BsonReader, BsonWriter> {
private static final ObjectPool<BsonReader> readerPool = BsonReader.createPool(Integer.getInteger("convert.bson.pool.size", 16));
@@ -59,11 +59,6 @@ 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);
@@ -104,6 +99,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();
@@ -114,23 +110,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")
@@ -139,6 +139,7 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
}
//------------------------------ convertTo -----------------------------------------------------------
@Override
public byte[] convertTo(final Object value) {
if (value == null) {
final BsonWriter out = writerPool.get().tiny(tiny);
@@ -150,6 +151,7 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
return convertTo(value.getClass(), value);
}
@Override
public byte[] convertTo(final Type type, final Object value) {
if (type == null) return null;
final BsonWriter out = writerPool.get().tiny(tiny);
@@ -159,6 +161,16 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
return result;
}
@Override
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();
@@ -176,6 +188,14 @@ 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;
@@ -200,6 +220,18 @@ public final class BsonConvert extends Convert<BsonReader, BsonWriter> {
return out.toBuffers();
}
@Override
public ByteBuffer[] convertMapTo(final Supplier<ByteBuffer> supplier, final Object... values) {
if (supplier == null) return null;
BsonByteBufferWriter out = new BsonByteBufferWriter(tiny, supplier);
if (values == null) {
out.writeNull();
} else {
((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values);
}
return out.toBuffers();
}
public void convertTo(final BsonWriter writer, final Object value) {
if (value == null) {
writer.writeNull();
@@ -213,6 +245,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);
@@ -225,4 +265,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;
}
}

View File

@@ -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;

View File

@@ -0,0 +1,44 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkale.convert.ext;
import org.redkale.convert.SimpledCoder;
import org.redkale.convert.Writer;
import org.redkale.convert.Reader;
import java.math.BigDecimal;
import org.redkale.util.Utility;
/**
* BigDecimal 的SimpledCoder实现
*
* <p>
* 详情见: https://redkale.org
*
* @author zhangjx
* @param <R> Reader输入的子类型
* @param <W> Writer输出的子类型
*/
public final class BigDecimalSimpledCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, BigDecimal> {
public static final BigDecimalSimpledCoder instance = new BigDecimalSimpledCoder();
@Override
public void convertTo(W out, BigDecimal value) {
if (value == null) {
out.writeNull();
return;
}
out.writeSmallString(value.toString());
}
@Override
public BigDecimal convertFrom(R in) {
String value = in.readSmallString();
if (value == null) return null;
return new BigDecimal(Utility.charArray(value));
}
}

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -21,7 +21,7 @@ import org.redkale.util.*;
* @author zhangjx
*/
@SuppressWarnings("unchecked")
public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
public final class JsonConvert extends TextConvert<JsonReader, JsonWriter> {
public static final Type TYPE_MAP_STRING_STRING = new TypeToken<java.util.LinkedHashMap<String, String>>() {
}.getType();
@@ -46,11 +46,6 @@ 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);
@@ -134,11 +129,13 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
}
//------------------------------ convertTo -----------------------------------------------------------
@Override
public String convertTo(final Object value) {
if (value == null) return "null";
return convertTo(value.getClass(), value);
}
@Override
public String convertTo(final Type type, final Object value) {
if (type == null) return null;
if (value == null) return "null";
@@ -149,6 +146,16 @@ public final class JsonConvert extends Convert<JsonReader, JsonWriter> {
return result;
}
@Override
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();
@@ -166,6 +173,14 @@ 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;
@@ -190,6 +205,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();
@@ -207,6 +234,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);
@@ -218,4 +253,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;
}
}

View File

@@ -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"));

View File

@@ -31,7 +31,7 @@ public class Context {
protected final long serverStartTime;
//Server的线程池
protected final ExecutorService executor;
protected final ThreadPoolExecutor executor;
//ByteBuffer的容量默认8K
protected final int bufferCapacity;
@@ -69,7 +69,7 @@ public class Context {
//JSON操作工厂
protected final JsonFactory jsonFactory;
public Context(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool, ObjectPool<Response> responsePool,
public Context(long serverStartTime, Logger logger, ThreadPoolExecutor executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool, ObjectPool<Response> responsePool,
final int maxbody, Charset charset, InetSocketAddress address, final PrepareServlet prepare, final int readTimeoutSecond, final int writeTimeoutSecond) {
this.serverStartTime = serverStartTime;
this.logger = logger;

View File

@@ -6,6 +6,7 @@
package org.redkale.net;
import java.io.IOException;
import javax.annotation.Priority;
import org.redkale.util.*;
/**
@@ -33,18 +34,11 @@ public abstract class Filter<C extends Context, R extends Request<C>, P extends
public void destroy(C context, AnyValue config) {
}
/**
* 值越小越靠前执行
*
* @return int
*/
public int getIndex() {
return 0;
}
@Override
public final int compareTo(Object o) {
if (!(o instanceof Filter)) return 1;
return this.getIndex() - ((Filter) o).getIndex();
Priority p1 = this.getClass().getAnnotation(Priority.class);
Priority p2 = o.getClass().getAnnotation(Priority.class);
return (p2 == null ? 0 : p2.value()) - (p1 == null ? 0 : p1.value());
}
}

View File

@@ -118,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()) {
@@ -136,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()) {
@@ -146,6 +148,7 @@ 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) {
@@ -176,6 +179,7 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
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) {
@@ -198,10 +202,12 @@ public abstract class PrepareServlet<K extends Serializable, C extends Context,
}
}
@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 {

View File

@@ -51,6 +51,7 @@ public abstract class Request<C extends Context> {
* 返回值Integer.MIN_VALUE: 帧数据; -1数据不合法 0解析完毕 &gt;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() {

View File

@@ -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;
}

View File

@@ -71,7 +71,7 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
protected int threads;
//线程池
protected ExecutorService executor;
protected ThreadPoolExecutor executor;
//ByteBuffer池大小
protected int bufferPoolSize;
@@ -99,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);
@@ -113,13 +113,31 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
final AtomicInteger counter = new AtomicInteger();
final Format f = createFormat();
final String n = name;
this.executor = Executors.newFixedThreadPool(threads, (Runnable r) -> {
this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threads, (Runnable r) -> {
Thread t = new WorkThread(executor, r);
t.setName(n + "-ServletThread-" + f.format(counter.incrementAndGet()));
return t;
});
}
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);
}
@@ -148,6 +166,13 @@ public abstract class Server<K extends Serializable, C extends Context, R extend
return this.context;
}
public void setThreads(int threads) {
int oldthreads = this.threads;
this.context.executor.setCorePoolSize(threads);
this.threads = threads;
logger.info("[" + Thread.currentThread().getName() + "] " + this.getClass().getSimpleName() + " change threads from " + oldthreads + " to " + threads);
}
@SuppressWarnings("unchecked")
public void addServlet(S servlet, final Object attachment, AnyValue conf, K... mappings) {
this.prepare.addServlet(servlet, attachment, conf, mappings);

View File

@@ -11,6 +11,8 @@ import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;
import org.redkale.convert.*;
import org.redkale.convert.json.JsonConvert;
import org.redkale.util.*;
/**
@@ -52,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;
@@ -72,32 +79,38 @@ public final class Transport {
this.group = transportChannelGroup;
this.bufferPool = transportBufferPool;
this.clientAddress = clientAddress;
this.strategy = strategy;
updateRemoteAddresses(addresses);
}
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.remoteAddres == null) {
this.remoteAddres = new InetSocketAddress[]{addr};
if (this.transportAddres == null) {
this.transportAddres = new TransportAddress[]{new TransportAddress(addr)};
} else {
for (InetSocketAddress i : this.remoteAddres) {
if (addr.equals(i)) return false;
for (TransportAddress i : this.transportAddres) {
if (addr.equals(i.address)) return false;
}
this.remoteAddres = Utility.append(remoteAddres, addr);
this.transportAddres = Utility.append(transportAddres, new TransportAddress(addr));
}
return true;
}
@@ -105,9 +118,9 @@ public final class Transport {
public final boolean removeRemoteAddresses(InetSocketAddress addr) {
if (addr == null) return false;
if (this.remoteAddres == null) return false;
if (this.transportAddres == null) return false;
synchronized (this) {
this.remoteAddres = Utility.remove(remoteAddres, addr);
this.transportAddres = Utility.remove(transportAddres, new TransportAddress(addr));
}
return true;
}
@@ -128,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() {
@@ -158,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 {
@@ -194,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);
@@ -256,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);
}
}
}

View File

@@ -43,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) {
@@ -127,14 +136,14 @@ public class TransportFactory {
}
if (info == null) return null;
if (sncpAddress != null) addresses.remove(sncpAddress);
return new Transport(groups.stream().sorted().collect(Collectors.joining(";")), 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() {

View 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);
}

View File

@@ -30,7 +30,7 @@ public class HttpContext extends Context {
protected final ConcurrentHashMap<Class, Creator> asyncHandlerCreators = new ConcurrentHashMap<>();
public HttpContext(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool,
public HttpContext(long serverStartTime, Logger logger, ThreadPoolExecutor executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool,
ObjectPool<Response> responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare,
int readTimeoutSecond, int writeTimeoutSecond) {
super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset,
@@ -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 很类似
//-------------------------------------------------------------

View File

@@ -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) {

View File

@@ -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;
}
/**
* 获取所有参数名
*

View File

@@ -192,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);

View File

@@ -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));
}
if (count > max) {
attachment.limit((int) (attachment.position() + max - count));
}
attachment.flip();
channel.write(attachment, attachment, this);
if (attachment.hasRemaining()) {
channel.write(attachment, attachment, this);
} else {
failed(null, attachment);
}
}
}

View File

@@ -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;
@@ -216,6 +217,7 @@ public class HttpServer extends Server<String, HttpContext, HttpRequest, HttpRes
*
* @return RestServlet
*/
@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);
@@ -231,8 +233,7 @@ 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;
@@ -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;
}

View File

@@ -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);

View File

@@ -133,17 +133,13 @@ public final class MultiContext {
has = true;
if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue;
if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue;
String name = part.getFilename();
int pos = name.lastIndexOf('.');
if (pos > 0) {
int pos2 = name.lastIndexOf('.', pos - 1);
if (pos2 >= 0) pos = pos2;
}
File file = new File(home, "tmp/redkale_" + System.nanoTime() + (pos > 0 ? name.substring(pos) : name));
file.getParentFile().mkdirs();
File file = new File(home, "tmp/redkale_" + System.nanoTime() + "/" + part.getFilename());
File parent = file.getParentFile();
parent.mkdirs();
boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file);
if (!rs) {
file.delete();
parent.delete();
} else {
tmpfile = file;
}
@@ -169,17 +165,13 @@ public final class MultiContext {
for (MultiPart part : parts()) {
if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue;
if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue;
String name = part.getFilename();
int pos = name.lastIndexOf('.');
if (pos > 0) {
int pos2 = name.lastIndexOf('.', pos - 1);
if (pos2 >= 0) pos = pos2;
}
File file = new File(home, "tmp/redkale_" + System.nanoTime() + (pos > 0 ? name.substring(pos) : name));
file.getParentFile().mkdirs();
File file = new File(home, "tmp/redkale_" + System.nanoTime() + "/" + part.getFilename());
File parent = file.getParentFile();
parent.mkdirs();
boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file);
if (!rs) {
file.delete();
parent.delete();
continue;
}
if (files == null) files = new ArrayList<>();

View File

@@ -29,6 +29,7 @@ import org.redkale.source.Flipper;
*
* @author zhangjx
*/
@SuppressWarnings("unchecked")
public final class Rest {
public static final String REST_HEADER_RESOURCE_NAME = "rest-resource-name";
@@ -92,7 +93,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);
}
};
}
@@ -121,6 +131,7 @@ public final class Rest {
childFactory.register(rc.type(), true, rc.ignoreColumns());
childFactory.reloadCoder(rc.type());
types.add(rc.type());
childFactory.tiny(rc.tiny());
}
return childFactory.getConvert();
}
@@ -179,15 +190,18 @@ public final class Rest {
//----------------------------------------------------------------------------------------
final Set<Field> resourcesFieldSet = new LinkedHashSet<>();
final ClassLoader loader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
final Set<String> resourcesFieldNameSet = new HashSet<>();
Class clzz = webSocketType;
do {
for (Field field : webSocketType.getDeclaredFields()) {
for (Field field : clzz.getDeclaredFields()) {
if (field.getAnnotation(Resource.class) == null) continue;
if (Modifier.isStatic(webSocketType.getModifiers())) throw new RuntimeException(field + " cannot static on createRestWebSocketServlet");
if (Modifier.isFinal(webSocketType.getModifiers())) throw new RuntimeException(field + " cannot final on createRestWebSocketServlet");
if (!Modifier.isPublic(webSocketType.getModifiers()) && !Modifier.isProtected(webSocketType.getModifiers())) {
if (resourcesFieldNameSet.contains(field.getName())) continue;
if (Modifier.isStatic(field.getModifiers())) throw new RuntimeException(field + " cannot static on createRestWebSocketServlet");
if (Modifier.isFinal(field.getModifiers())) throw new RuntimeException(field + " cannot final on createRestWebSocketServlet");
if (!Modifier.isPublic(field.getModifiers()) && !Modifier.isProtected(field.getModifiers())) {
throw new RuntimeException(field + " must be public or protected on createRestWebSocketServlet");
}
resourcesFieldNameSet.add(field.getName());
resourcesFieldSet.add(field);
}
} while ((clzz = clzz.getSuperclass()) != Object.class);
@@ -328,6 +342,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);

View File

@@ -24,6 +24,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Repeatable(RestConvert.RestConverts.class)
public @interface RestConvert {
boolean tiny() default true;
Class type();
String[] ignoreColumns() default {};

View File

@@ -12,6 +12,7 @@ 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.Convert;
import org.redkale.util.Comment;
@@ -36,6 +37,9 @@ import org.redkale.util.Comment;
*/
public abstract class WebSocket<G extends Serializable, T> {
@Comment("强制关闭结果码")
public static final int CLOSECODE_FORCED = 1;
@Comment("消息不合法")
public static final int RETCODE_SEND_ILLPACKET = 1 << 1; //2
@@ -82,6 +86,8 @@ public abstract class WebSocket<G extends Serializable, T> {
private long createtime = System.currentTimeMillis();
private long pingtime;
private Map<String, Object> attributes = new HashMap<>(); //非线程安全
protected WebSocket() {
@@ -89,11 +95,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));
}
@@ -113,7 +121,18 @@ public abstract class WebSocket<G extends Serializable, T> {
* @return 0表示成功 非0表示错误码
*/
public final CompletableFuture<Integer> send(Object message) {
return send(message, true);
return send(false, message, true);
}
/**
* 给自身发送消息, 消息类型是key-value键值对
*
* @param messages key-value键值对
*
* @return 0表示成功 非0表示错误码
*/
public final CompletableFuture<Integer> sendMap(Object... messages) {
return send(true, messages, true);
}
/**
@@ -125,6 +144,31 @@ public abstract class WebSocket<G extends Serializable, T> {
* @return 0表示成功 非0表示错误码
*/
public final CompletableFuture<Integer> send(Object message, boolean last) {
return send(false, message, last);
}
/**
* 给自身发送消息, 消息类型是key-value键值对
*
* @param last 是否最后一条
* @param messages key-value键值对
*
* @return 0表示成功 非0表示错误码
*/
public final CompletableFuture<Integer> sendMap(boolean last, Object... messages) {
return send(true, messages, last);
}
/**
* 给自身发送消息, 消息类型是Object[]
*
* @param mapconvable 是否convertMapTo
* @param message 不可为空, 只能是String或byte[]或可JavaBean对象或Object[]
* @param last 是否最后一条
*
* @return 0表示成功 非0表示错误码
*/
private CompletableFuture<Integer> send(boolean mapconvable, Object message, boolean last) {
if (message instanceof CompletableFuture) {
return ((CompletableFuture) message).thenCompose((json) -> {
if (json == null || json instanceof CharSequence || json instanceof byte[]) {
@@ -132,7 +176,7 @@ public abstract class WebSocket<G extends Serializable, T> {
} else if (message instanceof WebSocketPacket) {
return sendPacket((WebSocketPacket) message);
} else {
return sendPacket(new WebSocketPacket(getSendConvert(), json, last));
return sendPacket(new WebSocketPacket(getSendConvert(), mapconvable, json, last));
}
});
}
@@ -141,7 +185,7 @@ public abstract class WebSocket<G extends Serializable, T> {
} else if (message instanceof WebSocketPacket) {
return sendPacket((WebSocketPacket) message);
} else {
return sendPacket(new WebSocketPacket(getSendConvert(), message, last));
return sendPacket(new WebSocketPacket(getSendConvert(), mapconvable, message, last));
}
}
@@ -168,9 +212,9 @@ public abstract class WebSocket<G extends Serializable, T> {
*/
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 ? getSendConvert() : convert, json, last)));
return ((CompletableFuture) message).thenCompose((json) -> sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, false, json, last)));
}
return sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, message, last));
return sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, false, message, last));
}
/**
@@ -187,6 +231,18 @@ public abstract class WebSocket<G extends Serializable, T> {
}
//----------------------------------------------------------------
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
* @param message 不可为空
* @param userids Stream
*
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(Object message, Stream<G> userids) {
return sendMessage(message, true, userids);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
@@ -199,6 +255,45 @@ public abstract class WebSocket<G extends Serializable, T> {
return sendMessage(message, true, userids);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
* @param convert Convert
* @param message 不可为空
* @param userids Stream
*
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, Object message, Stream<G> userids) {
return sendMessage(convert, message, true, userids);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
* @param convert Convert
* @param message 不可为空
* @param userids Serializable[]
*
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, Object message, G... userids) {
return sendMessage(convert, message, true, userids);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
* @param message 不可为空
* @param last 是否最后一条
* @param userids Serializable[]
*
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(Object message, boolean last, Stream<G> userids) {
return sendMessage((Convert) null, message, last, userids);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
@@ -209,11 +304,44 @@ public abstract class WebSocket<G extends Serializable, T> {
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(Object message, boolean last, G... userids) {
return sendMessage((Convert) null, message, last, userids);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
* @param convert Convert
* @param message 不可为空
* @param last 是否最后一条
* @param userids Stream
*
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, Object message, boolean last, final Stream<G> userids) {
Object[] array = userids.toArray();
Serializable[] ss = new Serializable[array.length];
for (int i = 0; i < array.length; i++) {
ss[i] = (Serializable) array[i];
}
return sendMessage(convert, message, last, ss);
}
/**
* 给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息
*
* @param convert Convert
* @param message 不可为空
* @param last 是否最后一条
* @param userids Serializable[]
*
* @return 为0表示成功 其他值表示异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, Object message, boolean last, Serializable... userids) {
if (_engine.node == null) return CompletableFuture.completedFuture(RETCODE_NODESERVICE_NULL);
if (message instanceof CompletableFuture) {
return ((CompletableFuture) message).thenCompose((json) -> _engine.node.sendMessage(json, last, userids));
return ((CompletableFuture) message).thenCompose((json) -> _engine.node.sendMessage(convert, json, last, userids));
}
CompletableFuture<Integer> rs = _engine.node.sendMessage(message, last, userids);
CompletableFuture<Integer> rs = _engine.node.sendMessage(convert, message, last, userids);
if (_engine.finest) _engine.logger.finest("userids:" + Arrays.toString(userids) + " send websocket message(" + message + ")");
return rs;
}
@@ -226,7 +354,19 @@ public abstract class WebSocket<G extends Serializable, T> {
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Object message) {
return broadcastMessage(message, true);
return broadcastMessage((Convert) null, message, true);
}
/**
* 广播消息, 给所有人发消息
*
* @param convert Convert
* @param message 消息内容
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message) {
return broadcastMessage(convert, message, true);
}
/**
@@ -238,11 +378,24 @@ public abstract class WebSocket<G extends Serializable, T> {
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Object message, final boolean last) {
return broadcastMessage((Convert) null, message, last);
}
/**
* 广播消息, 给所有人发消息
*
* @param convert Convert
* @param message 消息内容
* @param last 是否最后一条
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message, final boolean last) {
if (_engine.node == null) return CompletableFuture.completedFuture(RETCODE_NODESERVICE_NULL);
if (message instanceof CompletableFuture) {
return ((CompletableFuture) message).thenCompose((json) -> _engine.node.broadcastMessage(json, last));
return ((CompletableFuture) message).thenCompose((json) -> _engine.node.broadcastMessage(convert, json, last));
}
CompletableFuture<Integer> rs = _engine.node.broadcastMessage(message, last);
CompletableFuture<Integer> rs = _engine.node.broadcastMessage(convert, message, last);
if (_engine.finest) _engine.logger.finest("broadcast send websocket message(" + message + ")");
return rs;
}
@@ -274,6 +427,18 @@ public abstract class WebSocket<G extends Serializable, T> {
return _engine.node.getRpcNodeWebSocketAddresses(userid);
}
/**
* 强制关闭用户的所有WebSocket
*
* @param userid Serializable
*
* @return int
*/
@Comment("强制关闭用户的所有WebSocket")
public CompletableFuture<Integer> forceCloseWebSocket(Serializable userid) {
return _engine.node.forceCloseWebSocket(userid);
}
/**
* 获取当前WebSocket下的属性非线程安全
*
@@ -478,6 +643,33 @@ 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);
}
/**
* 当Single模式下用户重复登陆时回调函数 默认处理逻辑关闭之前的WebSocket连接
*
*/
public void onSingleRepeatConnect() {
this._engine.node.forceCloseWebSocket(getUserid());
}
/**
* 获取Logger
*
* @return Logger Logger
*/
public Logger getLogger() {
return this._engine.logger;
}
/**
* 获取最后一次发送消息的时间
*
@@ -487,11 +679,29 @@ 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
*/
public final void close() {
if (this._runner != null) this._runner.closeRunner();
if (this._runner != null) this._runner.closeRunner(CLOSECODE_FORCED);
}
/**
* 是否关闭
*
* @return boolean
*/
public final boolean isClosed() {
return this._runner != null ? this._runner.closed : true;
}
@Override

View File

@@ -10,6 +10,7 @@ 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.Convert;
@@ -23,18 +24,18 @@ 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
@@ -43,23 +44,25 @@ public final class WebSocketEngine {
//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, Convert sendConvert, Logger logger) {
@@ -70,8 +73,8 @@ public final class WebSocketEngine {
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) {
@@ -96,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);
@@ -110,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) {
@@ -127,24 +132,50 @@ public final class WebSocketEngine {
}
}
@Comment("强制关闭本地用户的WebSocket")
public int forceCloseLocalWebSocket(Serializable userid) {
if (single) {
WebSocket ws = websockets.get(userid);
if (ws == null) return 0;
ws.close();
return 1;
}
List<WebSocket> list = websockets2.get(userid);
if (list == null || list.isEmpty()) return 0;
List<WebSocket> list2 = new ArrayList<>(list);
for (WebSocket ws : list2) {
ws.close();
}
return list2.size();
}
@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) {
//此处的WebSocketPacket只能是包含payload或bytes内容的不能包含sendConvert、sendJson、sendBuffers
final WebSocketPacket packet = (message instanceof WebSocketPacket) ? (WebSocketPacket) message
: ((message == null || message instanceof CharSequence || message instanceof byte[])
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.sendConvert, message, last));
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.sendConvert, false, 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);
}
}
@@ -155,11 +186,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);
}
}
@@ -168,15 +201,27 @@ public final class WebSocketEngine {
}
}
@Comment("给指定用户组发送消息")
public CompletableFuture<Integer> sendMessage(final Object message, final boolean last, final Stream<? extends Serializable> userids) {
Object[] array = userids.toArray();
Serializable[] ss = new Serializable[array.length];
for (int i = 0; i < array.length; i++) {
ss[i] = (Serializable) array[i];
}
return sendMessage(message, last, ss);
}
@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));
}
final boolean more = (!(message instanceof WebSocketPacket) || ((WebSocketPacket) message).sendBuffers == null) && userids.length > 1;
if (more) {
//此处的WebSocketPacket只能是包含payload或bytes内容的不能包含sendConvert、sendJson、sendBuffers
final WebSocketPacket packet = (message instanceof WebSocketPacket) ? (WebSocketPacket) message
: ((message == null || message instanceof CharSequence || message instanceof byte[])
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.sendConvert, message, last));
? new WebSocketPacket((Serializable) message, last) : new WebSocketPacket(this.sendConvert, false, message, last));
packet.setSendBuffers(packet.encode(context.getBufferSupplier()));
CompletableFuture<Integer> future = null;
if (single) {
@@ -217,21 +262,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);

View File

@@ -11,8 +11,10 @@ import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.*;
import java.util.stream.Stream;
import javax.annotation.*;
import org.redkale.boot.*;
import org.redkale.convert.*;
import org.redkale.service.*;
import org.redkale.source.*;
import org.redkale.util.*;
@@ -26,6 +28,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);
@@ -57,7 +65,9 @@ public abstract class WebSocketNode {
if (this.localEngine == null) return;
//关掉所有本地本地WebSocket
this.localEngine.getLocalWebSockets().forEach(g -> disconnect(g.getUserid()));
if (sncpNodeAddresses != null && localSncpAddress != null) sncpNodeAddresses.removeSetItem("redkale_sncpnodes", localSncpAddress);
if (sncpNodeAddresses != null && localSncpAddress != null) {
sncpNodeAddresses.removeSetItem(SOURCE_SNCP_NODES_KEY, localSncpAddress);
}
}
protected abstract CompletableFuture<List<String>> getWebSocketAddresses(@RpcTargetAddress InetSocketAddress targetAddress, Serializable userid);
@@ -70,6 +80,8 @@ public abstract class WebSocketNode {
protected abstract CompletableFuture<Void> disconnect(Serializable userid, InetSocketAddress addr);
protected abstract CompletableFuture<Integer> forceCloseWebSocket(Serializable userid, InetSocketAddress addr);
//--------------------------------------------------------------------------------
final CompletableFuture<Void> connect(final Serializable userid) {
if (finest) logger.finest(localSncpAddress + " receive websocket connect event (" + userid + " on " + this.localEngine.getEngineid() + ").");
@@ -140,9 +152,143 @@ 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);
});
}
/**
* 强制关闭用户WebSocket
*
* @param userid Serializable
*
* @return int
*/
public final CompletableFuture<Integer> forceCloseWebSocket(final Serializable userid) {
CompletableFuture<Integer> localFuture = null;
if (this.localEngine != null) localFuture = CompletableFuture.completedFuture(localEngine.forceCloseLocalWebSocket(userid));
if (this.sncpNodeAddresses == null || this.remoteNode == null) {
if (finest) logger.finest("websocket remote node is null");
//没有CacheSource就不会有分布式节点
return localFuture;
}
//远程节点关闭
CompletableFuture<Collection<InetSocketAddress>> addrsFuture = sncpNodeAddresses.getCollectionAsync(userid);
CompletableFuture<Integer> remoteFuture = addrsFuture.thenCompose((Collection<InetSocketAddress> addrs) -> {
if (finest) logger.finest("websocket found userid:" + userid + " on " + addrs);
if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
CompletableFuture<Integer> future = null;
for (InetSocketAddress addr : addrs) {
if (addr == null || addr.equals(localSncpAddress)) continue;
future = future == null ? remoteNode.forceCloseWebSocket(userid, addr)
: future.thenCombine(remoteNode.forceCloseWebSocket(userid, addr), (a, b) -> a + b);
}
return future == null ? CompletableFuture.completedFuture(0) : future;
});
return localFuture.thenCombine(remoteFuture, (a, b) -> a + b);
}
//--------------------------------------------------------------------------------
/**
* 获取本地的WebSocketEngine没有则返回null
*
*
* @return WebSocketEngine
*/
public final WebSocketEngine getLocalWebSocketEngine() {
return this.localEngine;
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param message 消息内容
* @param userids Stream
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(Object message, final Stream<? extends Serializable> userids) {
return sendMessage((Convert) null, message, true, userids);
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param message 消息内容
* @param userids Serializable[]
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(Object message, final Serializable... userids) {
return sendMessage(message, true, userids);
return sendMessage((Convert) null, message, true, userids);
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param convert Convert
* @param message 消息内容
* @param userids Stream
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, Object message, final Stream<? extends Serializable> userids) {
return sendMessage(convert, message, true, userids);
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param convert Convert
* @param message 消息内容
* @param userids Serializable[]
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, Object message, final Serializable... userids) {
return sendMessage(convert, message, true, userids);
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param message 消息内容
* @param last 是否最后一条
* @param userids Stream
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(final Object message, final boolean last, final Stream<? extends Serializable> userids) {
return sendMessage((Convert) null, message, last, userids);
}
/**
@@ -155,9 +301,44 @@ public abstract class WebSocketNode {
*
* @return 为0表示成功 其他值表示部分发送异常
*/
//最近连接发送逻辑还没有理清楚
public final CompletableFuture<Integer> sendMessage(final Object message, final boolean last, final Serializable... userids) {
return sendMessage((Convert) null, message, last, userids);
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param convert Convert
* @param message0 消息内容
* @param last 是否最后一条
* @param userids Stream
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, final Object message0, final boolean last, final Stream<? extends Serializable> userids) {
Object[] array = userids.toArray();
Serializable[] ss = new Serializable[array.length];
for (int i = 0; i < array.length; i++) {
ss[i] = (Serializable) array[i];
}
return sendMessage(convert, message0, last, ss);
}
/**
* 向指定用户发送消息,先发送本地连接,再发送远程连接 <br>
* 如果当前WebSocketNode是远程模式此方法只发送远程连接
*
* @param convert Convert
* @param message0 消息内容
* @param last 是否最后一条
* @param userids Serializable[]
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> sendMessage(final Convert convert, final Object message0, final boolean last, final Serializable... userids) {
if (userids == null || userids.length < 1) return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
final Object message = (convert == null || message0 instanceof WebSocketPacket) ? message0 : ((convert instanceof TextConvert) ? new WebSocketPacket(((TextConvert) convert).convertTo(message0), last) : new WebSocketPacket(((BinaryConvert) convert).convertTo(message0), last));
if (this.localEngine != null && this.sncpNodeAddresses == null) { //本地模式且没有分布式
return this.localEngine.sendMessage(message, last, userids);
}
@@ -177,7 +358,19 @@ public abstract class WebSocketNode {
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Object message) {
return broadcastMessage(message, true);
return broadcastMessage((Convert) null, message, true);
}
/**
* 广播消息, 给所有人发消息
*
* @param convert Convert
* @param message 消息内容
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message) {
return broadcastMessage(convert, message, true);
}
/**
@@ -189,6 +382,20 @@ public abstract class WebSocketNode {
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Object message, final boolean last) {
return broadcastMessage((Convert) null, message, last);
}
/**
* 广播消息, 给所有人发消息
*
* @param convert Convert
* @param message0 消息内容
* @param last 是否最后一条
*
* @return 为0表示成功 其他值表示部分发送异常
*/
public final CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message0, final boolean last) {
final Object message = (convert == null || message0 instanceof WebSocketPacket) ? message0 : ((convert instanceof TextConvert) ? new WebSocketPacket(((TextConvert) convert).convertTo(message0), last) : new WebSocketPacket(((BinaryConvert) convert).convertTo(message0), last));
if (this.localEngine != null && this.sncpNodeAddresses == null) { //本地模式且没有分布式
return this.localEngine.broadcastMessage(message, last);
}

View File

@@ -57,10 +57,12 @@ public final class WebSocketPacket {
protected boolean last = true;
protected Object sendJson;
Object sendJson;
Convert sendConvert;
boolean mapconvable;
ByteBuffer[] sendBuffers;
ConvertMask receiveMasker;
@@ -74,49 +76,12 @@ public final class WebSocketPacket {
this(payload, true);
}
public WebSocketPacket(Serializable message, boolean fin) {
boolean bin = message != null && message.getClass() == byte[].class;
if (bin) {
this.type = FrameType.BINARY;
this.bytes = (byte[]) message;
} else {
this.type = FrameType.TEXT;
this.payload = String.valueOf(message);
}
this.last = fin;
}
public WebSocketPacket(String payload, boolean fin) {
this.type = FrameType.TEXT;
this.payload = payload;
this.last = fin;
}
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;
}
WebSocketPacket(ByteBuffer[] sendBuffers, FrameType type, boolean fin) {
this.type = type;
this.last = fin;
this.setSendBuffers(sendBuffers);
}
void setSendBuffers(ByteBuffer[] sendBuffers) {
this.sendBuffers = sendBuffers;
}
ByteBuffer[] duplicateSendBuffers() {
ByteBuffer[] rs = new ByteBuffer[this.sendBuffers.length];
for (int i = 0; i < this.sendBuffers.length; i++) {
rs[i] = this.sendBuffers[i].duplicate();
}
return rs;
}
public WebSocketPacket(byte[] data) {
this(FrameType.BINARY, data, true);
}
@@ -139,7 +104,46 @@ public final class WebSocketPacket {
this.last = fin;
}
public byte[] getContent() {
public WebSocketPacket(Serializable message, boolean fin) {
boolean bin = message != null && message.getClass() == byte[].class;
if (bin) {
this.type = FrameType.BINARY;
this.bytes = (byte[]) message;
} else {
this.type = FrameType.TEXT;
this.payload = String.valueOf(message);
}
this.last = fin;
}
WebSocketPacket(Convert convert, boolean mapconvable, Object json, boolean fin) {
this.type = (convert == null || !convert.isBinary()) ? FrameType.TEXT : FrameType.BINARY;
this.sendConvert = convert;
this.mapconvable = mapconvable;
this.sendJson = json;
this.last = fin;
if (mapconvable && !(json instanceof Object[])) throw new IllegalArgumentException();
}
WebSocketPacket(ByteBuffer[] sendBuffers, FrameType type, boolean fin) {
this.type = type;
this.last = fin;
this.setSendBuffers(sendBuffers);
}
void setSendBuffers(ByteBuffer[] sendBuffers) {
this.sendBuffers = sendBuffers;
}
ByteBuffer[] duplicateSendBuffers() {
ByteBuffer[] rs = new ByteBuffer[this.sendBuffers.length];
for (int i = 0; i < this.sendBuffers.length; i++) {
rs[i] = this.sendBuffers[i].duplicate().asReadOnlyBuffer(); //必须使用asReadOnlyBuffer 否则会导致ByteBuffer对应的byte[]被ObjectPool回收两次
}
return rs;
}
public byte[] content() {
if (this.type == FrameType.TEXT) return Utility.encodeUTF8(getPayload());
if (this.bytes == null) return new byte[0];
return this.bytes;
@@ -157,6 +161,26 @@ public final class WebSocketPacket {
return last;
}
public FrameType getType() {
return type;
}
public void setType(FrameType type) {
this.type = type;
}
public void setPayload(String payload) {
this.payload = payload;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public void setLast(boolean last) {
this.last = last;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + (payload != null ? (", payload=" + payload) : "") + (bytes != null ? (", bytes=[" + bytes.length + ']') : "") + (sendJson != null ? (", json=" + sendJson) : "") + "]";
@@ -187,7 +211,7 @@ public final class WebSocketPacket {
return supplier.get();
}
};
ByteBuffer[] buffers = this.sendConvert.convertTo(newsupplier, sendJson);
ByteBuffer[] buffers = this.mapconvable ? this.sendConvert.convertMapTo(supplier, (Object[]) sendJson) : this.sendConvert.convertTo(newsupplier, sendJson);
int len = 0;
for (ByteBuffer buf : buffers) {
len += buf.remaining();
@@ -212,7 +236,7 @@ public final class WebSocketPacket {
}
ByteBuffer buffer = supplier.get(); //确保ByteBuffer的capacity不能小于128
final byte[] content = getContent();
final byte[] content = content();
final int len = content.length;
if (len <= 0x7D) { //125
buffer.put(opcode);

View File

@@ -17,6 +17,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.logging.*;
import org.redkale.convert.Convert;
import org.redkale.util.Utility;
/**
* WebSocket的消息接收发送器, 一个WebSocket对应一个WebSocketRunner
@@ -38,7 +39,7 @@ class WebSocketRunner implements Runnable {
private ByteBuffer readBuffer;
protected volatile boolean closed = false;
volatile boolean closed = false;
private AtomicBoolean writing = new AtomicBoolean();
@@ -74,7 +75,7 @@ class WebSocketRunner implements Runnable {
@Override
public void completed(Integer count, Void attachment1) {
if (count < 1 && readBuffers.isEmpty()) {
closeRunner();
closeRunner(0);
if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read buffer count, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds");
return;
}
@@ -99,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");
@@ -121,7 +131,17 @@ class WebSocketRunner implements Runnable {
context.getLogger().log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e);
}
} else {
Object message = textConvert.convertFrom(webSocket._messageTextType, packet.receiveMasker, packet.receiveBuffers);
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);
@@ -150,7 +170,17 @@ class WebSocketRunner implements Runnable {
context.getLogger().log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e);
}
} else {
Object message = binaryConvert.convertFrom(webSocket._messageTextType, packet.receiveMasker, packet.receiveBuffers);
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);
@@ -195,7 +225,7 @@ class WebSocketRunner implements Runnable {
}
}
} catch (Throwable t) {
closeRunner();
closeRunner(0);
if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read WebSocketPacket, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t);
} finally {
if (exBuffers != null) {
@@ -208,18 +238,18 @@ class WebSocketRunner implements Runnable {
@Override
public void failed(Throwable exc, Void attachment2) {
closeRunner();
closeRunner(0);
if (exc != null) {
context.getLogger().log(Level.FINEST, "WebSocketRunner read WebSocketPacket failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc);
}
}
});
} else {
closeRunner();
closeRunner(0);
context.getLogger().log(Level.FINEST, "WebSocketRunner abort by AsyncConnection closed");
}
} catch (Exception e) {
closeRunner();
closeRunner(0);
context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read bytes from channel, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e);
}
}
@@ -285,7 +315,7 @@ class WebSocketRunner implements Runnable {
channel.write(buffers, buffers, this);
}
} catch (Exception e) {
closeRunner();
closeRunner(0);
context.getLogger().log(Level.WARNING, "WebSocket sendMessage abort on rewrite, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e);
}
writing.set(false);
@@ -294,7 +324,7 @@ class WebSocketRunner implements Runnable {
@Override
public void failed(Throwable exc, ByteBuffer[] attachments) {
writing.set(false);
closeRunner();
closeRunner(0);
if (exc != null) {
context.getLogger().log(Level.FINE, "WebSocket sendMessage on CompletionHandler failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc);
}
@@ -302,14 +332,14 @@ class WebSocketRunner implements Runnable {
});
} catch (Exception t) {
writing.set(false);
closeRunner();
closeRunner(0);
context.getLogger().log(Level.FINE, "WebSocket sendMessage abort, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t);
futureResult.complete(RETCODE_SENDEXCEPTION);
}
return futureResult;
}
public void closeRunner() {
public void closeRunner(int code) {
if (closed) return;
synchronized (this) {
if (closed) return;
@@ -321,7 +351,7 @@ class WebSocketRunner implements Runnable {
context.offerBuffer(readBuffer);
readBuffer = null;
engine.remove(webSocket);
webSocket.onClose(0, null);
webSocket.onClose(code, null);
}
}

View File

@@ -186,11 +186,22 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl
return;
}
webSocket._userid = userid;
WebSocketServlet.this.node.localEngine.add(webSocket);
WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer, response.removeChannel());
webSocket._runner = runner;
context.runAsync(runner);
response.finish(true);
if (single) {
WebSocketServlet.this.node.existsWebSocket(userid).whenComplete((rs, ex) -> {
if (rs) webSocket.onSingleRepeatConnect();
WebSocketServlet.this.node.localEngine.add(webSocket);
WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer, response.removeChannel());
webSocket._runner = runner;
context.runAsync(runner);
response.finish(true);
});
} else {
WebSocketServlet.this.node.localEngine.add(webSocket);
WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer, response.removeChannel());
webSocket._runner = runner;
context.runAsync(runner);
response.finish(true);
}
});
}

View File

@@ -266,6 +266,7 @@ public abstract class Sncp {
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;
ResourceFactory.checkResourceName(name);
int mod = serviceImplClass.getModifiers();
if (!java.lang.reflect.Modifier.isPublic(mod)) return serviceImplClass;
if (java.lang.reflect.Modifier.isAbstract(mod)) return serviceImplClass;
@@ -890,6 +891,7 @@ public abstract class Sncp {
final AnyValue conf) {
if (serviceTypeOrImplClass == null) return null;
if (!Service.class.isAssignableFrom(serviceTypeOrImplClass)) return null;
ResourceFactory.checkResourceName(name);
int mod = serviceTypeOrImplClass.getModifiers();
boolean realed = !(java.lang.reflect.Modifier.isAbstract(mod) || serviceTypeOrImplClass.isInterface());
if (!java.lang.reflect.Modifier.isPublic(mod)) return null;

View File

@@ -8,7 +8,7 @@ package org.redkale.net.sncp;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Logger;
import org.redkale.net.*;
import org.redkale.util.ObjectPool;
@@ -21,7 +21,7 @@ import org.redkale.util.ObjectPool;
*/
public class SncpContext extends Context {
public SncpContext(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool,
public SncpContext(long serverStartTime, Logger logger, ThreadPoolExecutor executor, int bufferCapacity, ObjectPool<ByteBuffer> bufferPool,
ObjectPool<Response> responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare,
int readTimeoutSecond, int writeTimeoutSecond) {
super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset,

View File

@@ -0,0 +1,32 @@
/*
* 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.service;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Service类中临时缓存字段 <br>
*
* <b>注意: </b> 被标记字段的数据必须是可序列化和反序列化的, 且字段不能是static的 如果字段类型不是Map或Collection类型则不能修饰为final
*
* <p>
* 详情见: https://redkale.org
*
* @author zhangjx
*/
@Target({FIELD})
@Retention(RUNTIME)
public @interface Persist {
/**
* 临时缓存的超时秒数,超过指定秒数的缓存数据将会被废弃, 0表示不超时 默认超时值为60秒
*
* @return int
*/
int timeout() default 60;
}

View File

@@ -5,6 +5,7 @@
*/
package org.redkale.service;
import java.util.*;
import org.redkale.convert.json.*;
/**
@@ -28,6 +29,8 @@ public class RetResult<T> {
protected T result;
protected Map<String, String> attach;
public RetResult() {
}
@@ -99,6 +102,32 @@ public class RetResult<T> {
return this;
}
/**
* 同 setAttach
*
* @param attach attach
*
* @return RetResult
*/
public RetResult<T> attach(Map<String, String> attach) {
this.attach = attach;
return this;
}
/**
* attach添加元素
*
* @param key String
* @param value String
*
* @return RetResult
*/
public RetResult<T> attach(String key, Object value) {
if (this.attach == null) this.attach = new HashMap<>();
this.attach.put(key, value == null ? null : String.valueOf(value));
return this;
}
/**
* 结果码 0表示成功、 非0表示错误
*
@@ -148,6 +177,24 @@ public class RetResult<T> {
this.result = result;
}
/**
* 结果附件
*
* @return 结果附件
*/
public Map<String, String> getAttach() {
return attach;
}
/**
* 设置结果附件
*
* @param attach Map
*/
public void setAttach(Map<String, String> attach) {
this.attach = attach;
}
@Override
public String toString() {
return JsonConvert.root().convertTo(this);

View File

@@ -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;
}
@@ -87,4 +87,20 @@ public class WebSocketNodeService extends WebSocketNode implements Service {
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " disconnect from " + sncpAddr);
return future;
}
/**
* 强制关闭用户的WebSocket
*
* @param userid String
* @param sncpAddr InetSocketAddress
*
* @return 无返回值
*/
@Override
public CompletableFuture<Integer> forceCloseWebSocket(Serializable userid, InetSocketAddress sncpAddr) {
//不能从sncpNodeAddresses中移除因为engine.forceCloseWebSocket 会调用到disconnect
if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " forceCloseWebSocket from " + sncpAddr);
if (localEngine == null) return CompletableFuture.completedFuture(0);
return CompletableFuture.completedFuture(localEngine.forceCloseLocalWebSocket(userid));
}
}

View File

@@ -348,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());
}
@@ -443,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()));
@@ -458,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());
}
}

View File

@@ -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() {

View File

@@ -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()) {
@@ -447,7 +448,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
}
String sql = "DELETE " + (this.readPool.isMysql() ? "a" : "") + " FROM " + info.getTable(node) + " a" + (join1 == null ? "" : (", " + join1))
+ ((where == null || where.length() == 0) ? (join2 == null ? "" : (" WHERE " + join2))
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2)))) + info.createSQLOrderby(flipper)
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2)))) + info.createSQLOrderby(flipper)
+ ((flipper == null || flipper.getLimit() < 1) ? "" : (" LIMIT " + flipper.getLimit()));
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(info.getType().getSimpleName() + " delete sql=" + sql);
conn.setReadOnly(false);
@@ -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;
@@ -617,7 +618,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
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()) {
@@ -703,7 +704,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
String sql = "UPDATE " + info.getTable(node) + " a " + (join1 == null ? "" : (", " + join1))
+ " SET " + info.getSQLColumn("a", column) + " = ?"
+ ((where == null || where.length() == 0) ? (join2 == null ? "" : (" WHERE " + join2))
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(info.getType().getSimpleName() + " update sql=" + sql);
conn.setReadOnly(false);
Blob blob = conn.createBlob();
@@ -716,7 +717,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
String sql = "UPDATE " + info.getTable(node) + " a " + (join1 == null ? "" : (", " + join1))
+ " SET " + info.getSQLColumn("a", column) + " = " + info.formatToString(value)
+ ((where == null || where.length() == 0) ? (join2 == null ? "" : (" WHERE " + join2))
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(info.getType().getSimpleName() + " update sql=" + sql);
conn.setReadOnly(false);
final Statement stmt = conn.createStatement();
@@ -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();
@@ -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();
@@ -921,7 +922,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
}
String sql = "UPDATE " + info.getTable(node) + " a " + (join1 == null ? "" : (", " + join1)) + " SET " + setsql
+ ((where == null || where.length() == 0) ? (join2 == null ? "" : (" WHERE " + join2))
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
//注LIMIT 仅支持MySQL 且在多表关联式会异常, 该BUG尚未解决
sql += info.createSQLOrderby(flipper) + ((flipper == null || flipper.getLimit() < 1) ? "" : (" LIMIT " + flipper.getLimit()));
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(info.getType().getSimpleName() + " update sql=" + sql);
@@ -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();
@@ -1107,7 +1108,7 @@ public class DataJdbcSource extends AbstractService implements DataSource, Servi
}
String sql = "UPDATE " + info.getTable(node) + " a " + (join1 == null ? "" : (", " + join1)) + " SET " + setsql
+ ((where == null || where.length() == 0) ? (join2 == null ? "" : (" WHERE " + join2))
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
: (" WHERE " + where + (join2 == null ? "" : (" AND " + join2))));
if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(info.getType().getSimpleName() + " update sql=" + sql);
conn.setReadOnly(false);
if (blobs != null) {
@@ -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);

View File

@@ -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";
@@ -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());
}
}
}

View File

@@ -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,7 +193,7 @@ 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 {
@@ -207,7 +206,7 @@ public final class EntityInfo<T> {
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;
@@ -216,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;

View File

@@ -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");

View File

@@ -74,6 +74,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
* @param <T> 字段依附的类
* @param <F> 字段的数据类型
*/
@SuppressWarnings("unchecked")
public interface Attribute<T, F> {
/**

View File

@@ -13,6 +13,7 @@ import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.concurrent.*;
import jdk.internal.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.Type;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
@@ -82,6 +83,7 @@ public interface Creator<T> {
String[] value();
@SuppressWarnings("unchecked")
static class CreatorInner {
static class SimpleClassVisitor extends ClassVisitor {
@@ -108,7 +110,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);
}
};
}
@@ -196,6 +207,8 @@ public interface Creator<T> {
clazz = (Class<T>) HashMap.class;
} else if (clazz.isAssignableFrom(HashSet.class)) {
clazz = (Class<T>) HashSet.class;
} else if (clazz.isAssignableFrom(ConcurrentHashMap.class)) {
clazz = (Class<T>) ConcurrentHashMap.class;
}
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
throw new RuntimeException("[" + clazz + "] is a interface or abstract class, cannot create it's Creator.");

View File

@@ -17,7 +17,7 @@ public final class Redkale {
}
public static String getDotedVersion() {
return "1.8.0";
return "1.8.4";
}
public static int getMajorVersion() {

View File

@@ -9,7 +9,7 @@ import java.lang.ref.WeakReference;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.*;
import java.util.logging.*;
import javax.annotation.Resource;
@@ -96,15 +96,15 @@ public final class ResourceFactory {
* 检查资源名是否合法
* <blockquote><pre>
* name规则:
* 1: "$"有特殊含义, 不能表示"$"资源本身
* 1: "$"有特殊含义, 表示资源本身,"$"不能单独使用
* 2: 只能是字母、数字、(短横)-、(下划线)_、点(.)的组合
* </pre></blockquote>
*
*
* @param name String
*/
public void checkName(String name) {
public static void checkResourceName(String name) {
if (name == null || (!name.isEmpty() && !name.matches("^[a-zA-Z0-9_;\\-\\.\\[\\]\\(\\)]+$"))) {
throw new IllegalArgumentException("Resource.name(" + name + ") contains illegal character, must be (a-z,A-Z,0-9,_,.,(,),-,[,])");
throw new IllegalArgumentException("name(" + name + ") contains illegal character, must be (a-z,A-Z,0-9,_,.,(,),-,[,])");
}
}
@@ -346,7 +346,7 @@ public final class ResourceFactory {
* @return 旧资源对象
*/
public <A> A register(final boolean autoSync, final String name, final A rs) {
checkName(name);
checkResourceName(name);
final Class<?> claz = rs.getClass();
ResourceType rtype = claz.getAnnotation(ResourceType.class);
if (rtype == null) {
@@ -399,7 +399,7 @@ public final class ResourceFactory {
* @return 旧资源对象
*/
public <A> A register(final boolean autoSync, final String name, final Type clazz, final A rs) {
checkName(name);
checkResourceName(name);
ConcurrentHashMap<String, ResourceEntry> map = this.store.get(clazz);
if (map == null) {
synchronized (clazz) {
@@ -471,11 +471,11 @@ public final class ResourceFactory {
}
public <A> List<A> query(Class<? extends A> clazz) {
return query(new ArrayList<A>(), clazz);
return query(new ArrayList<>(), clazz);
}
public <A> List<A> query(Type clazz) {
return query(new ArrayList<A>(), clazz);
return query(new ArrayList<>(), clazz);
}
private <A> List<A> query(final List<A> list, Type clazz) {
@@ -489,6 +489,23 @@ public final class ResourceFactory {
return list;
}
public <A> List<A> query(final BiPredicate<String, Object> predicate) {
return query(new ArrayList<>(), predicate);
}
private <A> List<A> query(final List<A> list, final BiPredicate<String, Object> predicate) {
if (predicate == null) return list;
for (ConcurrentHashMap<String, ResourceEntry> map : this.store.values()) {
for (Map.Entry<String, ResourceEntry> en : map.entrySet()) {
if (predicate.test(en.getKey(), en.getValue().value)) {
list.add((A) en.getValue().value);
}
}
}
if (parent != null) query(list, predicate);
return list;
}
private <A> ResourceEntry<A> findEntry(String name, Class<? extends A> clazz) {
Map<String, ResourceEntry> map = this.store.get(clazz);
if (map != null) {

View File

@@ -6,7 +6,7 @@
package org.redkale.util;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.*;
import java.util.stream.*;
/**
@@ -108,6 +108,15 @@ public class Sheet<T> implements java.io.Serializable, Iterable<T> {
}
}
public <R> Sheet<R> map(Function<T, R> mapper) {
if (this.isEmpty()) return (Sheet) this;
final List<R> list = new ArrayList<>();
for (T item : this.rows) {
list.add(mapper.apply(item));
}
return new Sheet<>(getTotal(), list);
}
public void forEachParallel(final Consumer<? super T> consumer) {
if (consumer != null && this.rows != null && !this.rows.isEmpty()) {
this.rows.parallelStream().forEach(consumer);