27 Commits
1.8.3 ... 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
32 changed files with 991 additions and 136 deletions

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;
/**
*
@@ -277,6 +278,20 @@ public final class Application {
logger.log(Level.INFO, Transport.class.getSimpleName() + " configure bufferCapacity = " + bufferCapacity + "; bufferPoolSize = " + bufferPoolSize + "; threads = " + threads + ";");
}
}
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);
@@ -593,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());

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.*;
@@ -152,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<>();

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;
@@ -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
@@ -345,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) {
@@ -398,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));
@@ -420,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);
}
@@ -433,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();

View File

@@ -6,6 +6,7 @@
package org.redkale.convert;
import java.lang.reflect.Type;
import java.util.concurrent.CompletableFuture;
/**
* 对不明类型的对象进行序列化; BSON序列化时将对象的类名写入WriterJSON则不写入。
@@ -46,7 +47,12 @@ public final class AnyEncoder<T> implements Encodeable<Writer, T> {
if (i > 0) out.writeArrayMark();
this.convertTo(out, (T) values[i]);
out.writeMapMark();
this.convertTo(out, (T) values[i + 1]);
Object val = values[i + 1];
if (val instanceof CompletableFuture) {
this.convertTo(out, (T) ((CompletableFuture) val).join());
} else {
this.convertTo(out, (T) val);
}
}
out.writeMapE();
}

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

@@ -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);
@@ -144,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);
@@ -155,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);
@@ -164,6 +161,7 @@ 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);

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

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

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

@@ -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;
@@ -113,7 +113,7 @@ 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;
@@ -166,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

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

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

View File

@@ -37,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
@@ -118,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);
}
/**
@@ -130,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[]) {
@@ -137,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));
}
});
}
@@ -146,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));
}
}
@@ -173,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));
}
/**
@@ -192,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对象消息
*
@@ -204,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对象消息
*
@@ -214,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;
}
@@ -231,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);
}
/**
@@ -243,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;
}
@@ -279,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下的属性非线程安全
*
@@ -493,6 +653,14 @@ public abstract class WebSocket<G extends Serializable, T> {
this.getLogger().log(Level.SEVERE, "WebSocket receive or send Message error", t);
}
/**
* 当Single模式下用户重复登陆时回调函数 默认处理逻辑关闭之前的WebSocket连接
*
*/
public void onSingleRepeatConnect() {
this._engine.node.forceCloseWebSocket(getUserid());
}
/**
* 获取Logger
*
@@ -524,7 +692,16 @@ public abstract class WebSocket<G extends Serializable, T> {
* 显式地关闭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

@@ -132,6 +132,23 @@ public 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);
@@ -144,9 +161,10 @@ public class WebSocketEngine {
}
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) {
@@ -183,6 +201,16 @@ public 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) {
@@ -190,9 +218,10 @@ public class WebSocketEngine {
}
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) {

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.*;
@@ -78,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() + ").");
@@ -177,6 +181,37 @@ public abstract class WebSocketNode {
});
}
/**
* 强制关闭用户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
@@ -188,6 +223,19 @@ public abstract class WebSocketNode {
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是远程模式此方法只发送远程连接
@@ -198,7 +246,49 @@ public abstract class WebSocketNode {
* @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);
}
/**
@@ -212,7 +302,43 @@ 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);
}
@@ -232,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);
}
/**
@@ -244,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().asReadOnlyBuffer(); //必须使用asReadOnlyBuffer 否则会导致ByteBuffer对应的byte[]被ObjectPool回收两次
}
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

@@ -39,7 +39,7 @@ class WebSocketRunner implements Runnable {
private ByteBuffer readBuffer;
protected volatile boolean closed = false;
volatile boolean closed = false;
private AtomicBoolean writing = new AtomicBoolean();
@@ -75,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;
}
@@ -225,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) {
@@ -238,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);
}
}
@@ -315,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);
@@ -324,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);
}
@@ -332,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;
@@ -351,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

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

@@ -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.*;
@@ -206,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.3";
return "1.8.4";
}
public static int getMajorVersion() {

View File

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