From 1917ccf35c61ffa4a3295c32558a90c69ead6af3 Mon Sep 17 00:00:00 2001 From: Redkale <8730487+redkale@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:28:57 +0800 Subject: [PATCH] =?UTF-8?q?Redkale=202.3.0=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/start.bat | 2 +- bin/start.sh | 2 +- src/javax/persistence/Cacheable.java | 8 + src/org/redkale/asm/AnnotationVisitor.java | 11 +- src/org/redkale/asm/AnnotationWriter.java | 8 +- src/org/redkale/asm/Attribute.java | 102 ++- src/org/redkale/asm/ByteVector.java | 2 +- src/org/redkale/asm/ClassReader.java | 22 +- src/org/redkale/asm/ClassVisitor.java | 64 +- src/org/redkale/asm/ClassWriter.java | 44 +- src/org/redkale/asm/FieldVisitor.java | 20 +- src/org/redkale/asm/FieldWriter.java | 14 +- src/org/redkale/asm/Frame.java | 8 +- src/org/redkale/asm/Handle.java | 28 + src/org/redkale/asm/Handler.java | 2 +- src/org/redkale/asm/Item.java | 4 +- src/org/redkale/asm/Label.java | 6 +- src/org/redkale/asm/MethodVisitor.java | 138 ++- src/org/redkale/asm/MethodWriter.java | 26 +- src/org/redkale/asm/ModuleVisitor.java | 10 +- src/org/redkale/asm/Opcodes.java | 5 + src/org/redkale/asm/Type.java | 56 +- src/org/redkale/boot/Application.java | 141 ++-- src/org/redkale/boot/NodeHttpServer.java | 2 +- src/org/redkale/boot/NodeServer.java | 28 +- src/org/redkale/boot/NodeSncpServer.java | 2 +- .../boot/watch/ServerWatchService.java | 5 +- .../redkale/cluster/CacheClusterAgent.java | 2 +- src/org/redkale/cluster/ClusterAgent.java | 4 +- src/org/redkale/convert/ArrayEncoder.java | 31 +- src/org/redkale/convert/BinaryConvert.java | 1 - src/org/redkale/convert/Convert.java | 14 +- .../redkale/convert/ConvertBytesHandler.java | 23 + src/org/redkale/convert/ConvertFactory.java | 16 +- src/org/redkale/convert/EnMember.java | 14 + src/org/redkale/convert/Encodeable.java | 5 + src/org/redkale/convert/ObjectEncoder.java | 10 +- src/org/redkale/convert/TextConvert.java | 1 - src/org/redkale/convert/Writer.java | 9 +- .../convert/bson/BsonByteBufferReader.java | 4 +- .../convert/bson/BsonByteBufferWriter.java | 15 + src/org/redkale/convert/bson/BsonConvert.java | 134 ++- src/org/redkale/convert/bson/BsonReader.java | 5 +- src/org/redkale/convert/bson/BsonWriter.java | 51 +- .../convert/ext/ThrowableSimpledCoder.java | 40 + .../convert/json/JsonByteBufferWriter.java | 39 +- .../redkale/convert/json/JsonBytesWriter.java | 389 +++++++++ .../redkale/convert/json/JsonCharsWriter.java | 267 ++++++ src/org/redkale/convert/json/JsonConvert.java | 329 +++----- .../redkale/convert/json/JsonDynEncoder.java | 559 ++++++++++++ src/org/redkale/convert/json/JsonFactory.java | 16 +- src/org/redkale/convert/json/JsonReader.java | 21 +- .../convert/json/JsonStreamWriter.java | 9 +- src/org/redkale/convert/json/JsonWriter.java | 252 +----- .../redkale/mq/HttpMessageClusterClient.java | 207 +++-- src/org/redkale/mq/HttpMessageProcessor.java | 51 +- src/org/redkale/mq/HttpMessageRequest.java | 23 +- src/org/redkale/mq/HttpMessageResponse.java | 74 +- .../redkale/mq/HttpSimpleRequestCoder.java | 3 + src/org/redkale/mq/MessageAgent.java | 30 +- src/org/redkale/mq/MessageClient.java | 10 +- src/org/redkale/mq/MessageProcessor.java | 1 + src/org/redkale/mq/MessageRecord.java | 18 +- src/org/redkale/mq/SncpMessageClient.java | 5 + src/org/redkale/mq/SncpMessageProcessor.java | 26 +- src/org/redkale/mq/SncpMessageRequest.java | 4 +- src/org/redkale/mq/SncpMessageResponse.java | 22 +- ...ection.java => AsyncAioTcpConnection.java} | 25 +- ...er.java => AsyncAioTcpProtocolServer.java} | 47 +- src/org/redkale/net/AsyncConnection.java | 378 +++++---- src/org/redkale/net/AsyncGroup.java | 44 + src/org/redkale/net/AsyncIOGroup.java | 217 +++++ src/org/redkale/net/AsyncIOThread.java | 133 +++ ...er.java => AsyncNioCompletionHandler.java} | 23 +- src/org/redkale/net/AsyncNioConnection.java | 450 ++++++++++ .../redkale/net/AsyncNioTcpConnection.java | 201 +++++ ...er.java => AsyncNioTcpProtocolServer.java} | 70 +- .../redkale/net/AsyncNioUdpConnection.java | 171 ++++ .../net/AsyncNioUdpProtocolServer.java | 169 ++++ .../redkale/net/BioUdpAsyncConnection.java | 178 ---- src/org/redkale/net/BioUdpProtocolServer.java | 131 --- src/org/redkale/net/Context.java | 79 +- .../redkale/net/NioTcpAsyncConnection.java | 473 ----------- src/org/redkale/net/NioTcpPrepareRunner.java | 167 ---- src/org/redkale/net/NioThread.java | 123 --- src/org/redkale/net/NioThreadGroup.java | 87 -- src/org/redkale/net/PrepareRunner.java | 183 ---- src/org/redkale/net/PrepareServlet.java | 16 +- src/org/redkale/net/ProtocolCodec.java | 171 ++++ src/org/redkale/net/ProtocolServer.java | 52 +- src/org/redkale/net/Request.java | 47 +- src/org/redkale/net/Response.java | 251 +++--- src/org/redkale/net/Server.java | 99 +-- src/org/redkale/net/Transport.java | 199 +---- src/org/redkale/net/TransportFactory.java | 143 +--- src/org/redkale/net/WorkThread.java | 32 +- src/org/redkale/net/client/Client.java | 219 +++++ src/org/redkale/net/client/ClientCodec.java | 57 ++ .../redkale/net/client/ClientConnection.java | 273 ++++++ src/org/redkale/net/client/ClientFuture.java | 58 ++ src/org/redkale/net/client/ClientRequest.java | 35 + src/org/redkale/net/client/ClientResult.java | 47 ++ src/org/redkale/net/http/HttpClient.java | 347 ++++++++ src/org/redkale/net/http/HttpContext.java | 11 +- src/org/redkale/net/http/HttpMapping.java | 4 + .../redkale/net/http/HttpPrepareServlet.java | 56 +- src/org/redkale/net/http/HttpRequest.java | 761 +++++++++++++---- src/org/redkale/net/http/HttpResponse.java | 795 +++++++++--------- src/org/redkale/net/http/HttpResult.java | 6 + src/org/redkale/net/http/HttpServer.java | 34 +- src/org/redkale/net/http/HttpServlet.java | 135 +-- .../redkale/net/http/HttpSimpleRequest.java | 31 +- src/org/redkale/net/http/MultiContext.java | 6 +- src/org/redkale/net/http/Rest.java | 89 +- src/org/redkale/net/http/RestMapping.java | 4 + src/org/redkale/net/http/WebSocket.java | 120 ++- src/org/redkale/net/http/WebSocketEngine.java | 227 +++-- src/org/redkale/net/http/WebSocketNode.java | 112 ++- src/org/redkale/net/http/WebSocketPacket.java | 526 +----------- .../net/http/WebSocketReadHandler.java | 363 ++++++++ src/org/redkale/net/http/WebSocketRunner.java | 356 -------- .../redkale/net/http/WebSocketServlet.java | 55 +- .../net/http/WebSocketWriteHandler.java | 132 +++ src/org/redkale/net/sncp/SncpClient.java | 91 +- .../redkale/net/sncp/SncpPrepareServlet.java | 3 +- src/org/redkale/net/sncp/SncpRequest.java | 79 +- src/org/redkale/net/sncp/SncpResponse.java | 71 +- src/org/redkale/net/sncp/SncpServer.java | 31 +- src/org/redkale/service/AbstractService.java | 41 +- src/org/redkale/service/RetLabel.java | 3 +- .../redkale/service/WebSocketNodeService.java | 17 +- src/org/redkale/source/CacheMemorySource.java | 7 +- src/org/redkale/source/DataJdbcSource.java | 3 +- src/org/redkale/source/DataMemorySource.java | 3 +- src/org/redkale/source/DataSource.java | 50 ++ src/org/redkale/source/DataSources.java | 60 +- src/org/redkale/source/DataSqlSource.java | 146 ++-- src/org/redkale/source/EntityCache.java | 68 +- src/org/redkale/source/EntityInfo.java | 87 +- src/org/redkale/source/PoolJdbcSource.java | 29 +- src/org/redkale/source/PoolSource.java | 30 +- src/org/redkale/source/PoolTcpSource.java | 181 ++-- src/org/redkale/source/VirtualEntity.java | 7 +- src/org/redkale/util/ByteArray.java | 341 +++++++- src/org/redkale/util/ByteBufferWriter.java | 50 +- src/org/redkale/util/ByteTuple.java | 24 + src/org/redkale/util/DLong.java | 24 +- src/org/redkale/util/ObjectPool.java | 12 +- src/org/redkale/util/ThreadHashExecutor.java | 116 ++- src/org/redkale/util/Utility.java | 332 ++++++-- test/org/redkale/source/FilterNodeTest.java | 15 +- test/org/redkale/test/asm/AsmCreator.java | 6 +- test/org/redkale/test/convert/Fortune.java | 56 ++ test/org/redkale/test/convert/Message.java | 91 ++ test/org/redkale/test/convert/One.java | 26 + test/org/redkale/test/convert/World.java | 57 ++ .../test/convert/_DyncFortuneJsonEncoder.java | 49 ++ .../test/convert/_DyncMessageJsonEncoder.java | 51 ++ .../test/convert/_DyncWorldJsonEncoder.java | 47 ++ test/org/redkale/test/http/SocketMain.java | 36 + test/org/redkale/test/net/TransportTest.java | 8 +- .../test/rest/_DynHelloRestServlet1.java | 2 +- .../redkale/test/service/ABMainService.java | 14 +- test/org/redkale/test/sncp/SncpTest.java | 44 +- .../test/sncp/SncpTestServiceImpl.java | 7 +- .../redkale/test/source/CacheTestBean.java | 9 +- .../test/websocket/VideoWebSocketServlet.java | 2 +- 167 files changed, 9725 insertions(+), 5413 deletions(-) create mode 100644 src/org/redkale/convert/ConvertBytesHandler.java create mode 100644 src/org/redkale/convert/ext/ThrowableSimpledCoder.java create mode 100644 src/org/redkale/convert/json/JsonBytesWriter.java create mode 100644 src/org/redkale/convert/json/JsonCharsWriter.java create mode 100644 src/org/redkale/convert/json/JsonDynEncoder.java rename src/org/redkale/net/{AioTcpAsyncConnection.java => AsyncAioTcpConnection.java} (92%) rename src/org/redkale/net/{AioTcpProtocolServer.java => AsyncAioTcpProtocolServer.java} (73%) create mode 100644 src/org/redkale/net/AsyncGroup.java create mode 100644 src/org/redkale/net/AsyncIOGroup.java create mode 100644 src/org/redkale/net/AsyncIOThread.java rename src/org/redkale/net/{NioCompletionHandler.java => AsyncNioCompletionHandler.java} (62%) create mode 100644 src/org/redkale/net/AsyncNioConnection.java create mode 100644 src/org/redkale/net/AsyncNioTcpConnection.java rename src/org/redkale/net/{NioTcpProtocolServer.java => AsyncNioTcpProtocolServer.java} (58%) create mode 100644 src/org/redkale/net/AsyncNioUdpConnection.java create mode 100644 src/org/redkale/net/AsyncNioUdpProtocolServer.java delete mode 100644 src/org/redkale/net/BioUdpAsyncConnection.java delete mode 100644 src/org/redkale/net/BioUdpProtocolServer.java delete mode 100644 src/org/redkale/net/NioTcpAsyncConnection.java delete mode 100644 src/org/redkale/net/NioTcpPrepareRunner.java delete mode 100644 src/org/redkale/net/NioThread.java delete mode 100644 src/org/redkale/net/NioThreadGroup.java delete mode 100644 src/org/redkale/net/PrepareRunner.java create mode 100644 src/org/redkale/net/ProtocolCodec.java create mode 100644 src/org/redkale/net/client/Client.java create mode 100644 src/org/redkale/net/client/ClientCodec.java create mode 100644 src/org/redkale/net/client/ClientConnection.java create mode 100644 src/org/redkale/net/client/ClientFuture.java create mode 100644 src/org/redkale/net/client/ClientRequest.java create mode 100644 src/org/redkale/net/client/ClientResult.java create mode 100644 src/org/redkale/net/http/HttpClient.java create mode 100644 src/org/redkale/net/http/WebSocketReadHandler.java delete mode 100644 src/org/redkale/net/http/WebSocketRunner.java create mode 100644 src/org/redkale/net/http/WebSocketWriteHandler.java create mode 100644 src/org/redkale/util/ByteTuple.java create mode 100644 test/org/redkale/test/convert/Fortune.java create mode 100644 test/org/redkale/test/convert/Message.java create mode 100644 test/org/redkale/test/convert/World.java create mode 100644 test/org/redkale/test/convert/_DyncFortuneJsonEncoder.java create mode 100644 test/org/redkale/test/convert/_DyncMessageJsonEncoder.java create mode 100644 test/org/redkale/test/convert/_DyncWorldJsonEncoder.java create mode 100644 test/org/redkale/test/http/SocketMain.java diff --git a/bin/start.bat b/bin/start.bat index 02a0e946a..bcebe18e1 100644 --- a/bin/start.bat +++ b/bin/start.bat @@ -4,5 +4,5 @@ SET APP_HOME=%~dp0 IF NOT EXIST "%APP_HOME%\conf\application.xml" SET APP_HOME=%~dp0.. -java -DAPP_HOME="%APP_HOME%" -classpath "%APP_HOME%"\lib\* org.redkale.boot.Application +java -server -DAPP_HOME="%APP_HOME%" -classpath "%APP_HOME%"\lib\* org.redkale.boot.Application diff --git a/bin/start.sh b/bin/start.sh index ba69b40ee..d73a30358 100644 --- a/bin/start.sh +++ b/bin/start.sh @@ -24,5 +24,5 @@ done export CLASSPATH=$CLASSPATH:$lib echo "$APP_HOME" -nohup java -DAPP_HOME="$APP_HOME" org.redkale.boot.Application > "$APP_HOME"/logs.out & +nohup java -server -DAPP_HOME="$APP_HOME" org.redkale.boot.Application > "$APP_HOME"/logs.out & diff --git a/src/javax/persistence/Cacheable.java b/src/javax/persistence/Cacheable.java index fd5af6c39..45238dd98 100644 --- a/src/javax/persistence/Cacheable.java +++ b/src/javax/persistence/Cacheable.java @@ -51,4 +51,12 @@ public @interface Cacheable { * @return int */ int interval() default 0; + + /** + * DataSource是否直接返回对象的真实引用, 而不是copy一份 + * + * @return boolean + */ + boolean direct() default false; + } diff --git a/src/org/redkale/asm/AnnotationVisitor.java b/src/org/redkale/asm/AnnotationVisitor.java index 1a9238ad2..5b3496de7 100644 --- a/src/org/redkale/asm/AnnotationVisitor.java +++ b/src/org/redkale/asm/AnnotationVisitor.java @@ -60,8 +60,8 @@ package org.redkale.asm; /** * A visitor to visit a Java annotation. The methods of this class must be - * called in the following order: ( visit | visitEnum | - * visitAnnotation | visitArray )* visitEnd. + * called in the following order: ( <tt>visit</tt> | <tt>visitEnum</tt> | + * <tt>visitAnnotation</tt> | <tt>visitArray</tt> )* <tt>visitEnd</tt>. * * @author Eric Bruneton * @author Eugene Kuleshov @@ -102,6 +102,9 @@ public abstract class AnnotationVisitor { * method calls. May be null. */ public AnnotationVisitor(final int api, final AnnotationVisitor av) { + if (api < Opcodes.ASM4 || api > Opcodes.ASM6) { + throw new IllegalArgumentException(); + } this.api = api; this.av = av; } @@ -151,7 +154,7 @@ public abstract class AnnotationVisitor { * @param desc * the class descriptor of the nested annotation class. * @return a visitor to visit the actual nested annotation value, or - * null if this visitor is not interested in visiting this + * <tt>null</tt> if this visitor is not interested in visiting this * nested annotation. The nested annotation value must be fully * visited before calling other methods on this annotation * visitor. @@ -172,7 +175,7 @@ public abstract class AnnotationVisitor { * @param name * the value name. * @return a visitor to visit the actual array value elements, or - * null if this visitor is not interested in visiting these + * <tt>null</tt> if this visitor is not interested in visiting these * values. The 'name' parameters passed to the methods of this * visitor are ignored. All the array values must be visited * before calling other methods on this annotation visitor. diff --git a/src/org/redkale/asm/AnnotationWriter.java b/src/org/redkale/asm/AnnotationWriter.java index 437aeb59e..b9a6d03e5 100644 --- a/src/org/redkale/asm/AnnotationWriter.java +++ b/src/org/redkale/asm/AnnotationWriter.java @@ -77,7 +77,7 @@ final class AnnotationWriter extends AnnotationVisitor { private int size; /** - * true if values are named, false otherwise. Annotation + * <tt>true<tt> if values are named, <tt>false</tt> otherwise. Annotation * writers used for annotation default and annotation arrays use unnamed * values. */ @@ -122,13 +122,13 @@ final class AnnotationWriter extends AnnotationVisitor { * @param cw * the class writer to which this annotation must be added. * @param named - * true if values are named, false otherwise. + * <tt>true<tt> if values are named, <tt>false</tt> otherwise. * @param bv * where the annotation values must be stored. * @param parent * where the number of annotation values must be stored. * @param offset - * where in parent the number of annotation values must + * where in <tt>parent</tt> the number of annotation values must * be stored. */ AnnotationWriter(final ClassWriter cw, final boolean named, @@ -354,7 +354,7 @@ final class AnnotationWriter extends AnnotationVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param out * where the type reference and type path must be put. */ diff --git a/src/org/redkale/asm/Attribute.java b/src/org/redkale/asm/Attribute.java index ac959f499..e2ddb7451 100644 --- a/src/org/redkale/asm/Attribute.java +++ b/src/org/redkale/asm/Attribute.java @@ -58,13 +58,15 @@ */ package org.redkale.asm; +import java.util.Arrays; + /** * A non standard class, field, method or code attribute. * * @author Eric Bruneton * @author Eugene Kuleshov */ -class Attribute { +public class Attribute { /** * The type of this attribute. @@ -77,7 +79,7 @@ class Attribute { byte[] value; /** - * The next attribute in this attribute list. May be null. + * The next attribute in this attribute list. May be <tt>null</tt>. */ Attribute next; @@ -92,19 +94,19 @@ class Attribute { } /** - * Returns true if this type of attribute is unknown. The default - * implementation of this method always returns true. + * Returns <tt>true</tt> if this type of attribute is unknown. The default + * implementation of this method always returns <tt>true</tt>. * - * @return true if this type of attribute is unknown. + * @return <tt>true</tt> if this type of attribute is unknown. */ public boolean isUnknown() { return true; } /** - * Returns true if this type of attribute is a code attribute. + * Returns <tt>true</tt> if this type of attribute is a code attribute. * - * @return true if this type of attribute is a code attribute. + * @return <tt>true</tt> if this type of attribute is a code attribute. */ public boolean isCodeAttribute() { return false; @@ -113,7 +115,7 @@ class Attribute { /** * Returns the labels corresponding to this attribute. * - * @return the labels corresponding to this attribute, or null if + * @return the labels corresponding to this attribute, or <tt>null</tt> if * this attribute is not a code attribute that contains labels. */ protected Label[] getLabels() { @@ -123,7 +125,7 @@ class Attribute { /** * Reads a {@link #type type} attribute. This method must return a * new {@link Attribute} object, of type {@link #type type}, - * corresponding to the len bytes starting at the given offset, in + * corresponding to the <tt>len</tt> bytes starting at the given offset, in * the given class reader. * * @param cr @@ -146,7 +148,7 @@ class Attribute { * containing the type and the length of the attribute, are not * taken into account here. * @param labels - * the labels of the method's code, or null if the + * the labels of the method's code, or <tt>null</tt> if the * attribute to be read is not a code attribute. * @return a new {@link Attribute} object corresponding to the given * bytes. @@ -169,11 +171,11 @@ class Attribute { * class the items that corresponds to this attribute. * @param code * the bytecode of the method corresponding to this code - * attribute, or null if this attribute is not a code + * attribute, or <tt>null</tt> if this attribute is not a code * attributes. * @param len * the length of the bytecode of the method corresponding to this - * code attribute, or null if this attribute is not a + * code attribute, or <tt>null</tt> if this attribute is not a * code attribute. * @param maxStack * the maximum stack size of the method corresponding to this @@ -216,11 +218,11 @@ class Attribute { * byte arrays, with the {@link #write write} method. * @param code * the bytecode of the method corresponding to these code - * attributes, or null if these attributes are not code + * attributes, or <tt>null</tt> if these attributes are not code * attributes. * @param len * the length of the bytecode of the method corresponding to - * these code attributes, or null if these attributes + * these code attributes, or <tt>null</tt> if these attributes * are not code attributes. * @param maxStack * the maximum stack size of the method corresponding to these @@ -254,11 +256,11 @@ class Attribute { * byte arrays, with the {@link #write write} method. * @param code * the bytecode of the method corresponding to these code - * attributes, or null if these attributes are not code + * attributes, or <tt>null</tt> if these attributes are not code * attributes. * @param len * the length of the bytecode of the method corresponding to - * these code attributes, or null if these attributes + * these code attributes, or <tt>null</tt> if these attributes * are not code attributes. * @param maxStack * the maximum stack size of the method corresponding to these @@ -281,4 +283,72 @@ class Attribute { attr = attr.next; } } + + //The stuff below is temporary - once proper support for nestmate attribute has been added, it can be safely removed. + //see also changes in ClassReader.accept. + + public static class NestMembers extends Attribute { + public NestMembers() { + super("NestMembers"); + } + + byte[] bytes; + String[] classes; + + @Override + protected Attribute read(ClassReader cr, int off, int len, char[] buf, int codeOff, Label[] labels) { + int offset = off; + NestMembers a = new NestMembers(); + int size = cr.readShort(off); + a.classes = new String[size]; + off += 2; + for (int i = 0; i < size ; i++) { + a.classes[i] = cr.readClass(off, buf); + off += 2; + } + a.bytes = Arrays.copyOfRange(cr.b, offset, offset + len); + return a; + } + + @Override + protected ByteVector write(ClassWriter cw, byte[] code, int len, int maxStack, int maxLocals) { + ByteVector v = new ByteVector(bytes.length); + v.putShort(classes.length); + for (String s : classes) { + v.putShort(cw.newClass(s)); + } + return v; + } + } + + public static class NestHost extends Attribute { + + byte[] bytes; + String clazz; + + public NestHost() { + super("NestHost"); + } + + @Override + protected Attribute read(ClassReader cr, int off, int len, char[] buf, int codeOff, Label[] labels) { + int offset = off; + NestHost a = new NestHost(); + a.clazz = cr.readClass(off, buf); + a.bytes = Arrays.copyOfRange(cr.b, offset, offset + len); + return a; + } + + @Override + protected ByteVector write(ClassWriter cw, byte[] code, int len, int maxStack, int maxLocals) { + ByteVector v = new ByteVector(bytes.length); + v.putShort(cw.newClass(clazz)); + return v; + } + } + + static final Attribute[] DEFAULT_ATTRIBUTE_PROTOS = new Attribute[] { + new NestMembers(), + new NestHost() + }; } diff --git a/src/org/redkale/asm/ByteVector.java b/src/org/redkale/asm/ByteVector.java index 779707f54..ff215c9d4 100644 --- a/src/org/redkale/asm/ByteVector.java +++ b/src/org/redkale/asm/ByteVector.java @@ -332,7 +332,7 @@ public class ByteVector { * automatically enlarged if necessary. * * @param b - * an array of bytes. May be null to put len + * an array of bytes. May be <tt>null</tt> to put <tt>len</tt> * null bytes into this byte vector. * @param off * index of the fist byte of b that must be copied. diff --git a/src/org/redkale/asm/ClassReader.java b/src/org/redkale/asm/ClassReader.java index 0300a466f..5f28fb50e 100644 --- a/src/org/redkale/asm/ClassReader.java +++ b/src/org/redkale/asm/ClassReader.java @@ -185,9 +185,9 @@ public class ClassReader { public ClassReader(final byte[] b, final int off, final int len) { this.b = b; // checks the class version - if (readShort(off + 6) > Opcodes.V10) { - //throw new IllegalArgumentException(); - } + //if (readShort(off + 6) > Opcodes.V11) { + // throw new IllegalArgumentException(); + //} // parses the constant pool items = new int[readUnsignedShort(off + 8)]; int n = items.length; @@ -205,6 +205,10 @@ public class ClassReader { case ClassWriter.FLOAT: case ClassWriter.NAME_TYPE: case ClassWriter.INDY: + // @@@ ClassWriter.CONDY + // Enables MethodHandles.lookup().defineClass to function correctly + // when it reads the class name + case 17: size = 5; break; case ClassWriter.LONG: @@ -267,7 +271,7 @@ public class ClassReader { * {@link Type#getInternalName() getInternalName}). For interfaces, the * super class is {@link Object}. * - * @return the internal name of super class, or null for + * @return the internal name of super class, or <tt>null</tt> for * {@link Object} class. * * @see ClassVisitor#visit(int, int, String, String, String, String[]) @@ -281,7 +285,7 @@ public class ClassReader { * {@link Type#getInternalName() getInternalName}). * * @return the array of internal names for all implemented interfaces or - * null. + * <tt>null</tt>. * * @see ClassVisitor#visit(int, int, String, String, String, String[]) */ @@ -526,7 +530,7 @@ public class ClassReader { * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. */ public void accept(final ClassVisitor classVisitor, final int flags) { - accept(classVisitor, new Attribute[0], flags); + accept(classVisitor, Attribute.DEFAULT_ATTRIBUTE_PROTOS, flags); } /** @@ -1932,7 +1936,7 @@ public class ClassReader { * @param v * start offset in {@link #b b} of the annotations to be read. * @param visible - * true if the annotations to be read are visible at + * <tt>true</tt> if the annotations to be read are visible at * runtime. */ private void readParameterAnnotations(final MethodVisitor mv, @@ -2474,9 +2478,9 @@ public class ClassReader { * and the length of the attribute, are not taken into account * here. * @param labels - * the labels of the method's code, or null if the + * the labels of the method's code, or <tt>null</tt> if the * attribute to be read is not a code attribute. - * @return the attribute that has been read, or null to skip this + * @return the attribute that has been read, or <tt>null</tt> to skip this * attribute. */ private Attribute readAttribute(final Attribute[] attrs, final String type, diff --git a/src/org/redkale/asm/ClassVisitor.java b/src/org/redkale/asm/ClassVisitor.java index 929415e7f..57b96a95d 100644 --- a/src/org/redkale/asm/ClassVisitor.java +++ b/src/org/redkale/asm/ClassVisitor.java @@ -60,11 +60,11 @@ package org.redkale.asm; /** * A visitor to visit a Java class. The methods of this class must be called in - * the following order: visit [ visitSource ] [ - * visitModule ][ visitOuterClass ] ( visitAnnotation | - * visitTypeAnnotation | visitAttribute )* ( - * visitInnerClass | visitField | visitMethod )* - * visitEnd. + * the following order: <tt>visit</tt> [ <tt>visitSource</tt> ] [ + * <tt>visitModule</tt> ][ <tt>visitOuterClass</tt> ] ( <tt>visitAnnotation</tt> | + * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* ( + * <tt>visitInnerClass</tt> | <tt>visitField</tt> | <tt>visitMethod</tt> )* + * <tt>visitEnd</tt>. * * @author Eric Bruneton */ @@ -104,6 +104,9 @@ public abstract class ClassVisitor { * calls. May be null. */ public ClassVisitor(final int api, final ClassVisitor cv) { + if (api < Opcodes.ASM4 || api > Opcodes.ASM6) { + throw new IllegalArgumentException(); + } this.api = api; this.cv = cv; } @@ -120,18 +123,18 @@ public abstract class ClassVisitor { * the internal name of the class (see * {@link Type#getInternalName() getInternalName}). * @param signature - * the signature of this class. May be null if the class + * the signature of this class. May be <tt>null</tt> if the class * is not a generic one, and does not extend or implement generic * classes or interfaces. * @param superName * the internal of name of the super class (see * {@link Type#getInternalName() getInternalName}). For * interfaces, the super class is {@link Object}. May be - * null, but only for the {@link Object} class. + * <tt>null</tt>, but only for the {@link Object} class. * @param interfaces * the internal names of the class's interfaces (see * {@link Type#getInternalName() getInternalName}). May be - * null. + * <tt>null</tt>. */ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { @@ -145,11 +148,11 @@ public abstract class ClassVisitor { * * @param source * the name of the source file from which the class was compiled. - * May be null. + * May be <tt>null</tt>. * @param debug * additional debug information to compute the correspondance * between source and compiled elements of the class. May be - * null. + * <tt>null</tt>. */ public void visitSource(String source, String debug) { if (cv != null) { @@ -166,7 +169,7 @@ public abstract class ClassVisitor { * and {@code ACC_MANDATED}. * @param version * module version or null. - * @return a visitor to visit the module values, or null if + * @return a visitor to visit the module values, or <tt>null</tt> if * this visitor is not interested in visiting this module. */ public ModuleVisitor visitModule(String name, int access, String version) { @@ -187,11 +190,11 @@ public abstract class ClassVisitor { * internal name of the enclosing class of the class. * @param name * the name of the method that contains the class, or - * null if the class is not enclosed in a method of its + * <tt>null</tt> if the class is not enclosed in a method of its * enclosing class. * @param desc * the descriptor of the method that contains the class, or - * null if the class is not enclosed in a method of its + * <tt>null</tt> if the class is not enclosed in a method of its * enclosing class. */ public void visitOuterClass(String owner, String name, String desc) { @@ -206,8 +209,8 @@ public abstract class ClassVisitor { * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @@ -231,16 +234,19 @@ public abstract class ClassVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (cv != null) { return cv.visitTypeAnnotation(typeRef, typePath, desc, visible); } @@ -269,10 +275,10 @@ public abstract class ClassVisitor { * @param outerName * the internal name of the class to which the inner class * belongs (see {@link Type#getInternalName() getInternalName}). - * May be null for not member classes. + * May be <tt>null</tt> for not member classes. * @param innerName * the (simple) name of the inner class inside its enclosing - * class. May be null for anonymous inner classes. + * class. May be <tt>null</tt> for anonymous inner classes. * @param access * the access flags of the inner class as originally declared in * the enclosing class. @@ -295,20 +301,20 @@ public abstract class ClassVisitor { * @param desc * the field's descriptor (see {@link Type Type}). * @param signature - * the field's signature. May be null if the field's + * the field's signature. May be <tt>null</tt> if the field's * type does not use generic types. * @param value * the field's initial value. This parameter, which may be - * null if the field does not have an initial value, + * <tt>null</tt> if the field does not have an initial value, * must be an {@link Integer}, a {@link Float}, a {@link Long}, a - * {@link Double} or a {@link String} (for int, - * float, long or String fields + * {@link Double} or a {@link String} (for <tt>int</tt>, + * <tt>float</tt>, <tt>long</tt> or <tt>String</tt> fields * respectively). This parameter is only used for static * fields. Its value is ignored for non static fields, which * must be initialized through bytecode instructions in * constructors or methods. * @return a visitor to visit field annotations and attributes, or - * null if this class visitor is not interested in visiting + * <tt>null</tt> if this class visitor is not interested in visiting * these annotations and attributes. */ public FieldVisitor visitField(int access, String name, String desc, @@ -321,7 +327,7 @@ public abstract class ClassVisitor { /** * Visits a method of the class. This method must return a new - * {@link MethodVisitor} instance (or null) each time it is called, + * {@link MethodVisitor} instance (or <tt>null</tt>) each time it is called, * i.e., it should not return a previously returned visitor. * * @param access @@ -333,14 +339,14 @@ public abstract class ClassVisitor { * @param desc * the method's descriptor (see {@link Type Type}). * @param signature - * the method's signature. May be null if the method + * the method's signature. May be <tt>null</tt> if the method * parameters, return type and exceptions do not use generic * types. * @param exceptions * the internal names of the method's exception classes (see * {@link Type#getInternalName() getInternalName}). May be - * null. - * @return an object to visit the byte code of the method, or null + * <tt>null</tt>. + * @return an object to visit the byte code of the method, or <tt>null</tt> * if this class visitor is not interested in visiting the code of * this method. */ diff --git a/src/org/redkale/asm/ClassWriter.java b/src/org/redkale/asm/ClassWriter.java index 8455aa270..25da6f65a 100644 --- a/src/org/redkale/asm/ClassWriter.java +++ b/src/org/redkale/asm/ClassWriter.java @@ -391,8 +391,8 @@ public class ClassWriter extends ClassVisitor { * A type table used to temporarily store internal names that will not * necessarily be stored in the constant pool. This type table is used by * the control flow and data flow analysis algorithm used to compute stack - * map frames from scratch. This array associates to each index i - * the Item whose index is i. All Item objects stored in this array + * map frames from scratch. This array associates to each index <tt>i</tt> + * the Item whose index is <tt>i</tt>. All Item objects stored in this array * are also stored in the {@link #items} hash table. These two arrays allow * to retrieve an Item from its index or, conversely, to get the index of an * Item from its value. Each Item stores an internal name in its @@ -556,7 +556,7 @@ public class ClassWriter extends ClassVisitor { private int compute; /** - * true if some methods have wide forward jumps using ASM pseudo + * <tt>true</tt> if some methods have wide forward jumps using ASM pseudo * instructions, which need to be expanded into sequences of standard * bytecode instructions. In this case the class is re-read and re-written * with a ClassReader -> ClassWriter chain to perform this transformation. @@ -1297,6 +1297,38 @@ public class ClassWriter extends ClassVisitor { return result; } + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @return the index of a new or already existing method type reference + * item. + * + * @deprecated this method is superseded by + * {@link #newHandle(int, String, String, String, boolean)}. + */ + @Deprecated + public int newHandle(final int tag, final String owner, final String name, + final String desc) { + return newHandle(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE); + } + /** * Adds a handle to the constant pool of the class being build. Does nothing * if the constant pool already contains a similar item. This method is @@ -1486,7 +1518,7 @@ public class ClassWriter extends ClassVisitor { * @param desc * the method's descriptor. * @param itf - * true if owner is an interface. + * <tt>true</tt> if <tt>owner</tt> is an interface. * @return a new or already existing method reference item. */ Item newMethodItem(final String owner, final String name, @@ -1515,7 +1547,7 @@ public class ClassWriter extends ClassVisitor { * @param desc * the method's descriptor. * @param itf - * true if owner is an interface. + * <tt>true</tt> if <tt>owner</tt> is an interface. * @return the index of a new or already existing method reference item. */ public int newMethod(final String owner, final String name, @@ -1778,7 +1810,7 @@ public class ClassWriter extends ClassVisitor { * @param key * a constant pool item. * @return the constant pool's hash table item which is equal to the given - * item, or null if there is no such item. + * item, or <tt>null</tt> if there is no such item. */ private Item get(final Item key) { Item i = items[key.hashCode % items.length]; diff --git a/src/org/redkale/asm/FieldVisitor.java b/src/org/redkale/asm/FieldVisitor.java index 7b6029d7a..51a954dfa 100644 --- a/src/org/redkale/asm/FieldVisitor.java +++ b/src/org/redkale/asm/FieldVisitor.java @@ -60,8 +60,8 @@ package org.redkale.asm; /** * A visitor to visit a Java field. The methods of this class must be called in - * the following order: ( visitAnnotation | - * visitTypeAnnotation | visitAttribute )* visitEnd. + * the following order: ( <tt>visitAnnotation</tt> | + * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* <tt>visitEnd</tt>. * * @author Eric Bruneton */ @@ -101,6 +101,9 @@ public abstract class FieldVisitor { * calls. May be null. */ public FieldVisitor(final int api, final FieldVisitor fv) { + if (api < Opcodes.ASM4 || api > Opcodes.ASM6) { + throw new IllegalArgumentException(); + } this.api = api; this.fv = fv; } @@ -111,8 +114,8 @@ public abstract class FieldVisitor { * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @@ -132,16 +135,19 @@ public abstract class FieldVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (fv != null) { return fv.visitTypeAnnotation(typeRef, typePath, desc, visible); } diff --git a/src/org/redkale/asm/FieldWriter.java b/src/org/redkale/asm/FieldWriter.java index 17be5542a..85c68574d 100644 --- a/src/org/redkale/asm/FieldWriter.java +++ b/src/org/redkale/asm/FieldWriter.java @@ -100,28 +100,28 @@ final class FieldWriter extends FieldVisitor { private int value; /** - * The runtime visible annotations of this field. May be null. + * The runtime visible annotations of this field. May be <tt>null</tt>. */ private AnnotationWriter anns; /** - * The runtime invisible annotations of this field. May be null. + * The runtime invisible annotations of this field. May be <tt>null</tt>. */ private AnnotationWriter ianns; /** - * The runtime visible type annotations of this field. May be null. + * The runtime visible type annotations of this field. May be <tt>null</tt>. */ private AnnotationWriter tanns; /** * The runtime invisible type annotations of this field. May be - * null. + * <tt>null</tt>. */ private AnnotationWriter itanns; /** - * The non standard attributes of this field. May be null. + * The non standard attributes of this field. May be <tt>null</tt>. */ private Attribute attrs; @@ -141,9 +141,9 @@ final class FieldWriter extends FieldVisitor { * @param desc * the field's descriptor (see {@link Type}). * @param signature - * the field's signature. May be null. + * the field's signature. May be <tt>null</tt>. * @param value - * the field's constant value. May be null. + * the field's constant value. May be <tt>null</tt>. */ FieldWriter(final ClassWriter cw, final int access, final String name, final String desc, final String signature, final Object value) { diff --git a/src/org/redkale/asm/Frame.java b/src/org/redkale/asm/Frame.java index d843dca72..e5419fc8b 100644 --- a/src/org/redkale/asm/Frame.java +++ b/src/org/redkale/asm/Frame.java @@ -1403,7 +1403,7 @@ class Frame { /** * Merges the input frame of the given basic block with the input and output - * frames of this basic block. Returns true if the input frame of + * frames of this basic block. Returns <tt>true</tt> if the input frame of * the given label has been changed by this operation. * * @param cw @@ -1413,7 +1413,7 @@ class Frame { * @param edge * the kind of the {@link Edge} between this label and 'label'. * See {@link Edge#info}. - * @return true if the input frame of the given label has been + * @return <tt>true</tt> if the input frame of the given label has been * changed by this operation. */ final boolean merge(final ClassWriter cw, final Frame frame, final int edge) { @@ -1511,7 +1511,7 @@ class Frame { /** * Merges the type at the given index in the given type array with the given - * type. Returns true if the type array has been modified by this + * type. Returns <tt>true</tt> if the type array has been modified by this * operation. * * @param cw @@ -1522,7 +1522,7 @@ class Frame { * an array of types. * @param index * the index of the type that must be merged in 'types'. - * @return true if the type array has been modified by this + * @return <tt>true</tt> if the type array has been modified by this * operation. */ private static boolean merge(final ClassWriter cw, int t, diff --git a/src/org/redkale/asm/Handle.java b/src/org/redkale/asm/Handle.java index 259feb217..6a35cf7d9 100644 --- a/src/org/redkale/asm/Handle.java +++ b/src/org/redkale/asm/Handle.java @@ -99,6 +99,34 @@ public final class Handle { */ final boolean itf; + /** + * Constructs a new field or method handle. + * + * @param tag + * the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the class that owns the field or method + * designated by this handle. + * @param name + * the name of the field or method designated by this handle. + * @param desc + * the descriptor of the field or method designated by this + * handle. + * + * @deprecated this constructor has been superseded + * by {@link #Handle(int, String, String, String, boolean)}. + */ + @Deprecated + public Handle(int tag, String owner, String name, String desc) { + this(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE); + } /** * Constructs a new field or method handle. diff --git a/src/org/redkale/asm/Handler.java b/src/org/redkale/asm/Handler.java index 3448caded..e3dc44b98 100644 --- a/src/org/redkale/asm/Handler.java +++ b/src/org/redkale/asm/Handler.java @@ -82,7 +82,7 @@ class Handler { /** * Internal name of the type of exceptions handled by this handler, or - * null to catch any exceptions. + * <tt>null</tt> to catch any exceptions. */ String desc; diff --git a/src/org/redkale/asm/Item.java b/src/org/redkale/asm/Item.java index 5cca74c3a..6c6120ccd 100644 --- a/src/org/redkale/asm/Item.java +++ b/src/org/redkale/asm/Item.java @@ -306,8 +306,8 @@ final class Item { * @param i * the item to be compared to this one. Both items must have the * same {@link #type}. - * @return true if the given item if equal to this one, - * false otherwise. + * @return <tt>true</tt> if the given item if equal to this one, + * <tt>false</tt> otherwise. */ boolean isEqualTo(final Item i) { switch (type) { diff --git a/src/org/redkale/asm/Label.java b/src/org/redkale/asm/Label.java index bbd4b98b0..4c33dd2f6 100644 --- a/src/org/redkale/asm/Label.java +++ b/src/org/redkale/asm/Label.java @@ -325,8 +325,8 @@ public class Label { * the position of first byte of the bytecode instruction that * contains this label. * @param wideOffset - * true if the reference must be stored in 4 bytes, or - * false if it must be stored with 2 bytes. + * <tt>true</tt> if the reference must be stored in 4 bytes, or + * <tt>false</tt> if it must be stored with 2 bytes. * @throws IllegalArgumentException * if this label has not been created by the given code writer. */ @@ -389,7 +389,7 @@ public class Label { * the position of this label in the bytecode. * @param data * the bytecode of the method. - * @return true if a blank that was left for this label was too + * @return <tt>true</tt> if a blank that was left for this label was too * small to store the offset. In such a case the corresponding jump * instruction is replaced with a pseudo instruction (using unused * opcodes) using an unsigned two bytes offset. These pseudo diff --git a/src/org/redkale/asm/MethodVisitor.java b/src/org/redkale/asm/MethodVisitor.java index b35595a54..99f8535dd 100644 --- a/src/org/redkale/asm/MethodVisitor.java +++ b/src/org/redkale/asm/MethodVisitor.java @@ -60,24 +60,24 @@ package org.redkale.asm; /** * A visitor to visit a Java method. The methods of this class must be called in - * the following order: ( visitParameter )* [ - * visitAnnotationDefault ] ( visitAnnotation | - * visitParameterAnnotation visitTypeAnnotation | - * visitAttribute )* [ visitCode ( visitFrame | - * visitXInsn | visitLabel | - * visitInsnAnnotation | visitTryCatchBlock | - * visitTryCatchAnnotation | visitLocalVariable | - * visitLocalVariableAnnotation | visitLineNumber )* - * visitMaxs ] visitEnd. In addition, the - * visitXInsn and visitLabel methods must be called in + * the following order: ( <tt>visitParameter</tt> )* [ + * <tt>visitAnnotationDefault</tt> ] ( <tt>visitAnnotation</tt> | + * <tt>visitParameterAnnotation</tt> <tt>visitTypeAnnotation</tt> | + * <tt>visitAttribute</tt> )* [ <tt>visitCode</tt> ( <tt>visitFrame</tt> | + * <tt>visitXInsn</tt> | <tt>visitLabel</tt> | + * <tt>visitInsnAnnotation</tt> | <tt>visitTryCatchBlock</tt> | + * <tt>visitTryCatchAnnotation</tt> | <tt>visitLocalVariable</tt> | + * <tt>visitLocalVariableAnnotation</tt> | <tt>visitLineNumber</tt> )* + * <tt>visitMaxs</tt> ] <tt>visitEnd</tt>. In addition, the + * <tt>visitXInsn</tt> and <tt>visitLabel</tt> methods must be called in * the sequential order of the bytecode instructions of the visited code, - * visitInsnAnnotation must be called after the annotated - * instruction, visitTryCatchBlock must be called before the + * <tt>visitInsnAnnotation</tt> must be called after the annotated + * instruction, <tt>visitTryCatchBlock</tt> must be called before the * labels passed as arguments have been visited, - * visitTryCatchBlockAnnotation must be called after the + * <tt>visitTryCatchBlockAnnotation</tt> must be called after the * corresponding try catch block has been visited, and the - * visitLocalVariable, visitLocalVariableAnnotation and - * visitLineNumber methods must be called after the labels + * <tt>visitLocalVariable</tt>, <tt>visitLocalVariableAnnotation</tt> and + * <tt>visitLineNumber</tt> methods must be called after the labels * passed as arguments have been visited. * * @author Eric Bruneton @@ -118,6 +118,9 @@ public abstract class MethodVisitor { * calls. May be null. */ public MethodVisitor(final int api, final MethodVisitor mv) { + if (api < Opcodes.ASM4 || api > Opcodes.ASM6) { + throw new IllegalArgumentException(); + } this.api = api; this.mv = mv; } @@ -132,11 +135,14 @@ public abstract class MethodVisitor { * @param name * parameter name or null if none is provided. * @param access - * the parameter's access flags, only ACC_FINAL, - * ACC_SYNTHETIC or/and ACC_MANDATED are + * the parameter's access flags, only <tt>ACC_FINAL</tt>, + * <tt>ACC_SYNTHETIC</tt> or/and <tt>ACC_MANDATED</tt> are * allowed (see {@link Opcodes}). */ public void visitParameter(String name, int access) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (mv != null) { mv.visitParameter(name, access); } @@ -146,7 +152,7 @@ public abstract class MethodVisitor { * Visits the default value of this annotation interface method. * * @return a visitor to the visit the actual default value of this - * annotation interface method, or null if this visitor is + * annotation interface method, or <tt>null</tt> if this visitor is * not interested in visiting this default value. The 'name' * parameters passed to the methods of this annotation visitor are * ignored. Moreover, exacly one visit method must be called on this @@ -165,8 +171,8 @@ public abstract class MethodVisitor { * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @@ -193,16 +199,19 @@ public abstract class MethodVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (mv != null) { return mv.visitTypeAnnotation(typeRef, typePath, desc, visible); } @@ -217,8 +226,8 @@ public abstract class MethodVisitor { * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitParameterAnnotation(int parameter, @@ -444,6 +453,34 @@ public abstract class MethodVisitor { } } + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + */ + @Deprecated + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (api >= Opcodes.ASM5) { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + } /** * Visits a method instruction. A method instruction is an instruction that @@ -465,6 +502,14 @@ public abstract class MethodVisitor { */ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (api < Opcodes.ASM5) { + if (itf != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new IllegalArgumentException( + "INVOKESPECIAL/STATIC on interfaces require ASM 5"); + } + visitMethodInsn(opcode, owner, name, desc); + return; + } if (mv != null) { mv.visitMethodInsn(opcode, owner, name, desc, itf); } @@ -569,7 +614,7 @@ public abstract class MethodVisitor { * the constant to be loaded on the stack. This parameter must be * a non null {@link Integer}, a {@link Float}, a {@link Long}, a * {@link Double}, a {@link String}, a {@link Type} of OBJECT or - * ARRAY sort for .class constants, for classes whose + * ARRAY sort for <tt>.class</tt> constants, for classes whose * version is 49.0, a {@link Type} of METHOD sort or a * {@link Handle} for MethodType and MethodHandle constants, for * classes whose version is 51.0. @@ -604,8 +649,8 @@ public abstract class MethodVisitor { * @param dflt * beginning of the default handler block. * @param labels - * beginnings of the handler blocks. labels[i] is the - * beginning of the handler block for the min + i key. + * beginnings of the handler blocks. <tt>labels[i]</tt> is the + * beginning of the handler block for the <tt>min + i</tt> key. */ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { @@ -622,8 +667,8 @@ public abstract class MethodVisitor { * @param keys * the values of the keys. * @param labels - * beginnings of the handler blocks. labels[i] is the - * beginning of the handler block for the keys[i] key. + * beginnings of the handler blocks. <tt>labels[i]</tt> is the + * beginning of the handler block for the <tt>keys[i]</tt> key. */ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { if (mv != null) { @@ -668,16 +713,19 @@ public abstract class MethodVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (mv != null) { return mv.visitInsnAnnotation(typeRef, typePath, desc, visible); } @@ -699,7 +747,7 @@ public abstract class MethodVisitor { * beginning of the exception handler's code. * @param type * internal name of the type of exceptions handled by the - * handler, or null to catch any exceptions (for + * handler, or <tt>null</tt> to catch any exceptions (for * "finally" blocks). * @throws IllegalArgumentException * if one of the labels has already been visited by this visitor @@ -725,16 +773,19 @@ public abstract class MethodVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (mv != null) { return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible); } @@ -750,7 +801,7 @@ public abstract class MethodVisitor { * the type descriptor of this local variable. * @param signature * the type signature of this local variable. May be - * null if the local variable type does not use generic + * <tt>null</tt> if the local variable type does not use generic * types. * @param start * the first instruction corresponding to the scope of this local @@ -782,7 +833,7 @@ public abstract class MethodVisitor { * @param typePath * the path to the annotated type argument, wildcard bound, array * element type, or static inner type within 'typeRef'. May be - * null if the annotation targets 'typeRef' as a whole. + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. * @param start * the fist instructions corresponding to the continuous ranges * that make the scope of this local variable (inclusive). @@ -796,13 +847,16 @@ public abstract class MethodVisitor { * @param desc * the class descriptor of the annotation class. * @param visible - * true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if * this visitor is not interested in visiting this annotation. */ public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } if (mv != null) { return mv.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible); @@ -819,7 +873,7 @@ public abstract class MethodVisitor { * @param start * the first instruction corresponding to this line number. * @throws IllegalArgumentException - * if start has not already been visited by this + * if <tt>start</tt> has not already been visited by this * visitor (by the {@link #visitLabel visitLabel} method). */ public void visitLineNumber(int line, Label start) { diff --git a/src/org/redkale/asm/MethodWriter.java b/src/org/redkale/asm/MethodWriter.java index 7d3fa5eda..e8fd34a19 100644 --- a/src/org/redkale/asm/MethodWriter.java +++ b/src/org/redkale/asm/MethodWriter.java @@ -218,41 +218,41 @@ class MethodWriter extends MethodVisitor { int[] exceptions; /** - * The annotation default attribute of this method. May be null. + * The annotation default attribute of this method. May be <tt>null</tt>. */ private ByteVector annd; /** - * The runtime visible annotations of this method. May be null. + * The runtime visible annotations of this method. May be <tt>null</tt>. */ private AnnotationWriter anns; /** - * The runtime invisible annotations of this method. May be null. + * The runtime invisible annotations of this method. May be <tt>null</tt>. */ private AnnotationWriter ianns; /** - * The runtime visible type annotations of this method. May be null + * The runtime visible type annotations of this method. May be <tt>null</tt> * . */ private AnnotationWriter tanns; /** * The runtime invisible type annotations of this method. May be - * null. + * <tt>null</tt>. */ private AnnotationWriter itanns; /** * The runtime visible parameter annotations of this method. May be - * null. + * <tt>null</tt>. */ private AnnotationWriter[] panns; /** * The runtime invisible parameter annotations of this method. May be - * null. + * <tt>null</tt>. */ private AnnotationWriter[] ipanns; @@ -381,12 +381,12 @@ class MethodWriter extends MethodVisitor { private int lastCodeOffset; /** - * The runtime visible type annotations of the code. May be null. + * The runtime visible type annotations of the code. May be <tt>null</tt>. */ private AnnotationWriter ctanns; /** - * The runtime invisible type annotations of the code. May be null. + * The runtime invisible type annotations of the code. May be <tt>null</tt>. */ private AnnotationWriter ictanns; @@ -446,7 +446,7 @@ class MethodWriter extends MethodVisitor { * is relative to the beginning of the current basic block, i.e., the true * stack size after the last visited instruction is equal to the * {@link Label#inputStackTop beginStackSize} of the current basic block - * plus stackSize. + * plus <tt>stackSize</tt>. */ private int stackSize; @@ -455,7 +455,7 @@ class MethodWriter extends MethodVisitor { * This size is relative to the beginning of the current basic block, i.e., * the true maximum stack size after the last visited instruction is equal * to the {@link Label#inputStackTop beginStackSize} of the current basic - * block plus stackSize. + * block plus <tt>stackSize</tt>. */ private int maxStackSize; @@ -475,10 +475,10 @@ class MethodWriter extends MethodVisitor { * @param desc * the method's descriptor (see {@link Type}). * @param signature - * the method's signature. May be null. + * the method's signature. May be <tt>null</tt>. * @param exceptions * the internal names of the method's exceptions. May be - * null. + * <tt>null</tt>. * @param compute * Indicates what must be automatically computed (see #compute). */ diff --git a/src/org/redkale/asm/ModuleVisitor.java b/src/org/redkale/asm/ModuleVisitor.java index 455d50123..da743696a 100644 --- a/src/org/redkale/asm/ModuleVisitor.java +++ b/src/org/redkale/asm/ModuleVisitor.java @@ -60,9 +60,9 @@ package org.redkale.asm; /** * A visitor to visit a Java module. The methods of this class must be called in - * the following order: visitMainClass | ( visitPackage | - * visitRequire | visitExport | visitOpen | - * visitUse | visitProvide )* visitEnd. + * the following order: <tt>visitMainClass</tt> | ( <tt>visitPackage</tt> | + * <tt>visitRequire</tt> | <tt>visitExport</tt> | <tt>visitOpen</tt> | + * <tt>visitUse</tt> | <tt>visitProvide</tt> )* <tt>visitEnd</tt>. * * The methods {@link #visitRequire(String, int, String)}, {@link #visitExport(String, int, String...)}, * {@link #visitOpen(String, int, String...)} and {@link #visitPackage(String)} @@ -157,7 +157,7 @@ public abstract class ModuleVisitor { * {@code ACC_MANDATED}. * @param modules the qualified names of the modules that can access to * the public classes of the exported package or - * null. + * <tt>null</tt>. */ public void visitExport(String packaze, int access, String... modules) { if (mv != null) { @@ -174,7 +174,7 @@ public abstract class ModuleVisitor { * {@code ACC_MANDATED}. * @param modules the qualified names of the modules that can use deep * reflection to the classes of the open package or - * null. + * <tt>null</tt>. */ public void visitOpen(String packaze, int access, String... modules) { if (mv != null) { diff --git a/src/org/redkale/asm/Opcodes.java b/src/org/redkale/asm/Opcodes.java index 89459ddd1..30f9efd17 100644 --- a/src/org/redkale/asm/Opcodes.java +++ b/src/org/redkale/asm/Opcodes.java @@ -80,12 +80,17 @@ public interface Opcodes { // versions + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; int V1_5 = 0 << 16 | 49; int V1_6 = 0 << 16 | 50; int V1_7 = 0 << 16 | 51; int V1_8 = 0 << 16 | 52; int V9 = 0 << 16 | 53; int V10 = 0 << 16 | 54; + int V11 = 0 << 16 | 55; // access flags diff --git a/src/org/redkale/asm/Type.java b/src/org/redkale/asm/Type.java index 73d8f45c4..8772c64c7 100644 --- a/src/org/redkale/asm/Type.java +++ b/src/org/redkale/asm/Type.java @@ -71,47 +71,47 @@ import java.lang.reflect.Method; public class Type { /** - * The sort of the void type. See {@link #getSort getSort}. + * The sort of the <tt>void</tt> type. See {@link #getSort getSort}. */ public static final int VOID = 0; /** - * The sort of the boolean type. See {@link #getSort getSort}. + * The sort of the <tt>boolean</tt> type. See {@link #getSort getSort}. */ public static final int BOOLEAN = 1; /** - * The sort of the char type. See {@link #getSort getSort}. + * The sort of the <tt>char</tt> type. See {@link #getSort getSort}. */ public static final int CHAR = 2; /** - * The sort of the byte type. See {@link #getSort getSort}. + * The sort of the <tt>byte</tt> type. See {@link #getSort getSort}. */ public static final int BYTE = 3; /** - * The sort of the short type. See {@link #getSort getSort}. + * The sort of the <tt>short</tt> type. See {@link #getSort getSort}. */ public static final int SHORT = 4; /** - * The sort of the int type. See {@link #getSort getSort}. + * The sort of the <tt>int</tt> type. See {@link #getSort getSort}. */ public static final int INT = 5; /** - * The sort of the float type. See {@link #getSort getSort}. + * The sort of the <tt>float</tt> type. See {@link #getSort getSort}. */ public static final int FLOAT = 6; /** - * The sort of the long type. See {@link #getSort getSort}. + * The sort of the <tt>long</tt> type. See {@link #getSort getSort}. */ public static final int LONG = 7; /** - * The sort of the double type. See {@link #getSort getSort}. + * The sort of the <tt>double</tt> type. See {@link #getSort getSort}. */ public static final int DOUBLE = 8; @@ -131,55 +131,55 @@ public class Type { public static final int METHOD = 11; /** - * The void type. + * The <tt>void</tt> type. */ public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24) | (5 << 16) | (0 << 8) | 0, 1); /** - * The boolean type. + * The <tt>boolean</tt> type. */ public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) | (0 << 16) | (5 << 8) | 1, 1); /** - * The char type. + * The <tt>char</tt> type. */ public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) | (0 << 16) | (6 << 8) | 1, 1); /** - * The byte type. + * The <tt>byte</tt> type. */ public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) | (0 << 16) | (5 << 8) | 1, 1); /** - * The short type. + * The <tt>short</tt> type. */ public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) | (0 << 16) | (7 << 8) | 1, 1); /** - * The int type. + * The <tt>int</tt> type. */ public static final Type INT_TYPE = new Type(INT, null, ('I' << 24) | (0 << 16) | (0 << 8) | 1, 1); /** - * The float type. + * The <tt>float</tt> type. */ public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) | (2 << 16) | (2 << 8) | 1, 1); /** - * The long type. + * The <tt>long</tt> type. */ public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24) | (1 << 16) | (1 << 8) | 2, 1); /** - * The double type. + * The <tt>double</tt> type. */ public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) | (3 << 16) | (3 << 8) | 2, 1); @@ -439,8 +439,8 @@ public class Type { * @return the size of the arguments of the method (plus one for the * implicit this argument), argSize, and the size of its return * value, retSize, packed into a single int i = - * (argSize << 2) | retSize (argSize is therefore equal to - * i >> 2, and retSize to i & 0x03). + * <tt>(argSize << 2) | retSize</tt> (argSize is therefore equal to + * <tt>i >> 2</tt>, and retSize to <tt>i & 0x03</tt>). */ public static int getArgumentsAndReturnSizes(final String desc) { int n = 1; @@ -645,9 +645,9 @@ public class Type { * @return the size of the arguments (plus one for the implicit this * argument), argSize, and the size of the return value, retSize, * packed into a single - * int i = (argSize << 2) | retSize - * (argSize is therefore equal to i >> 2, - * and retSize to i & 0x03). + * int i = <tt>(argSize << 2) | retSize</tt> + * (argSize is therefore equal to <tt>i >> 2</tt>, + * and retSize to <tt>i & 0x03</tt>). */ public int getArgumentsAndReturnSizes() { return getArgumentsAndReturnSizes(getDescriptor()); @@ -838,8 +838,8 @@ public class Type { * Returns the size of values of this type. This method must not be used for * method types. * - * @return the size of values of this type, i.e., 2 for long and - * double, 0 for void and 1 otherwise. + * @return the size of values of this type, i.e., 2 for <tt>long</tt> and + * <tt>double</tt>, 0 for <tt>void</tt> and 1 otherwise. */ public int getSize() { // the size is in byte 0 of 'off' for primitive types (buf == null) @@ -855,8 +855,8 @@ public class Type { * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. * @return an opcode that is similar to the given opcode, but adapted to - * this Java type. For example, if this type is float and - * opcode is IRETURN, this method returns FRETURN. + * this Java type. For example, if this type is <tt>float</tt> and + * <tt>opcode</tt> is IRETURN, this method returns FRETURN. */ public int getOpcode(final int opcode) { if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { @@ -879,7 +879,7 @@ public class Type { * * @param o * the object to be compared to this type. - * @return true if the given object is equal to this type. + * @return <tt>true</tt> if the given object is equal to this type. */ @Override public boolean equals(final Object o) { diff --git a/src/org/redkale/boot/Application.java b/src/org/redkale/boot/Application.java index ed87a89e1..477183b41 100644 --- a/src/org/redkale/boot/Application.java +++ b/src/org/redkale/boot/Application.java @@ -89,6 +89,20 @@ public final class Application { */ public static final String RESNAME_APP_ADDR = "APP_ADDR"; + /** + * 当前进程的work线程池, 类型:Executor、ExecutorService + * + * @since 2.3.0 + */ + public static final String RESNAME_APP_EXECUTOR = "APP_EXECUTOR"; + + /** + * 当前进程的客户端组, 类型:AsyncGroup + * + * @since 2.3.0 + */ + public static final String RESNAME_APP_GROUP = "APP_GROUP"; + /** * 当前Service所属的SNCP Server的地址 类型: SocketAddress、InetSocketAddress、String
*/ @@ -106,8 +120,11 @@ public final class Application { /** * 当前Server的线程池 + * + * @deprecated 2.3.0 使用RESNAME_APP_EXECUTOR */ - public static final String RESNAME_SERVER_EXECUTOR = Server.RESNAME_SERVER_EXECUTOR; + @Deprecated + public static final String RESNAME_SERVER_EXECUTOR2 = Server.RESNAME_SERVER_EXECUTOR2; /** * 当前Server的ResourceFactory @@ -123,6 +140,10 @@ public final class Application { //本地IP地址 final InetSocketAddress localAddress; + //业务逻辑线程池 + //@since 2.3.0 + final ExecutorService workExecutor; + //CacheSource 资源 final List cacheSources = new CopyOnWriteArrayList<>(); @@ -135,6 +156,9 @@ public final class Application { //SNCP传输端的TransportFactory, 注意: 只给SNCP使用 final TransportFactory sncpTransportFactory; + //给客户端使用,包含SNCP客户端、自定义数据库客户端连接池 + final AsyncGroup asyncGroup; + //第三方服务发现管理接口 //@since 2.1.0 final ClusterAgent clusterAgent; @@ -289,9 +313,6 @@ public final class Application { this.classLoader = new RedkaleClassLoader(Thread.currentThread().getContextClassLoader()); logger.log(Level.INFO, "------------------------- Redkale " + Redkale.getDotedVersion() + " -------------------------"); //------------------配置 节点 ------------------ - ObjectPool transportPool = null; - ExecutorService transportExec = null; - AsynchronousChannelGroup transportGroup = null; final AnyValue resources = config.getAnyValue("resources"); TransportStrategy strategy = null; String excludelib0 = null; @@ -301,9 +322,9 @@ public final class Application { int bufferPoolSize = Runtime.getRuntime().availableProcessors() * 8; int readTimeoutSeconds = TransportFactory.DEFAULT_READTIMEOUTSECONDS; int writeTimeoutSeconds = TransportFactory.DEFAULT_WRITETIMEOUTSECONDS; - AtomicLong createBufferCounter = new AtomicLong(); - AtomicLong cycleBufferCounter = new AtomicLong(); + AnyValue executorConf = null; if (resources != null) { + executorConf = resources.getAnyValue("executor"); AnyValue excludelibConf = resources.getAnyValue("excludelibs"); if (excludelibConf != null) excludelib0 = excludelibConf.getValue("value"); AnyValue transportConf = resources.getAnyValue("transport"); @@ -316,31 +337,6 @@ public final class Application { writeTimeoutSeconds = transportConf.getIntValue("writeTimeoutSeconds", writeTimeoutSeconds); final int threads = parseLenth(transportConf.getValue("threads"), groupsize * Runtime.getRuntime().availableProcessors() * 2); bufferPoolSize = parseLenth(transportConf.getValue("bufferPoolSize"), threads * 4); - final int capacity = bufferCapacity; - transportPool = ObjectPool.createSafePool(createBufferCounter, cycleBufferCounter, bufferPoolSize, - (Object... params) -> ByteBuffer.allocateDirect(capacity), null, (e) -> { - if (e == null || e.isReadOnly() || e.capacity() != capacity) return false; - e.clear(); - return true; - }); - //-----------transportChannelGroup-------------- - try { - final String strategyClass = transportConf.getValue("strategy"); - if (strategyClass != null && !strategyClass.isEmpty()) { - strategy = (TransportStrategy) classLoader.loadClass(strategyClass).getDeclaredConstructor().newInstance(); - } - final AtomicInteger counter = new AtomicInteger(); - transportExec = Executors.newFixedThreadPool(threads, (Runnable r) -> { - Thread t = new Thread(r); - t.setDaemon(true); - t.setName("Redkale-Transport-Thread-" + counter.incrementAndGet()); - return t; - }); - transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1); - } catch (Exception e) { - throw new RuntimeException(e); - } - logger.log(Level.INFO, Transport.class.getSimpleName() + " configure bufferCapacity = " + bufferCapacity / 1024 + "K; bufferPoolSize = " + bufferPoolSize + "; threads = " + threads + ";"); } AnyValue clusterConf = resources.getAnyValue("cluster"); @@ -424,31 +420,41 @@ public final class Application { } } } - 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("Redkale-Transport-Thread-" + counter.incrementAndGet()); - return t; - }); - try { - transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1); - } catch (Exception e) { - throw new RuntimeException(e); + + ExecutorService workExecutor0 = null; + if (executorConf != null) { + final AtomicReference workref = new AtomicReference<>(); + int executorThreads = executorConf.getIntValue("threads", Math.max(2, Runtime.getRuntime().availableProcessors())); + boolean executorHash = executorConf.getBoolValue("hash"); + if (executorThreads > 0) { + final AtomicInteger workcounter = new AtomicInteger(); + if (executorHash) { + workExecutor0 = new ThreadHashExecutor(executorThreads, (Runnable r) -> { + int c = workcounter.incrementAndGet(); + String threadname = "Redkale-HashWorkThread-" + (c > 9 ? c : ("0" + c)); + Thread t = new WorkThread(threadname, workref.get(), r); + return t; + }); + } else { + workExecutor0 = Executors.newFixedThreadPool(executorThreads, (Runnable r) -> { + int c = workcounter.incrementAndGet(); + String threadname = "Redkale-WorkThread-" + (c > 9 ? c : ("0" + c)); + Thread t = new WorkThread(threadname, workref.get(), r); + return t; + }); + } + workref.set(workExecutor0); } } - if (transportPool == null) { - final int capacity = bufferCapacity; - transportPool = ObjectPool.createSafePool(createBufferCounter, cycleBufferCounter, bufferPoolSize, - (Object... params) -> ByteBuffer.allocateDirect(capacity), null, (e) -> { - if (e == null || e.isReadOnly() || e.capacity() != capacity) return false; - e.clear(); - return true; - }); - } + this.workExecutor = workExecutor0; + this.resourceFactory.register(RESNAME_APP_EXECUTOR, Executor.class, this.workExecutor); + this.resourceFactory.register(RESNAME_APP_EXECUTOR, ExecutorService.class, this.workExecutor); + + this.asyncGroup = new AsyncIOGroup(this.workExecutor, Runtime.getRuntime().availableProcessors(), bufferCapacity, bufferPoolSize); + this.resourceFactory.register(RESNAME_APP_GROUP, AsyncGroup.class, this.asyncGroup); + this.excludelibs = excludelib0; - this.sncpTransportFactory = TransportFactory.create(transportExec, transportPool, transportGroup, (SSLContext) null, readTimeoutSeconds, writeTimeoutSeconds, strategy); + this.sncpTransportFactory = TransportFactory.create(this.asyncGroup, (SSLContext) null, Transport.DEFAULT_NETPROTOCOL, readTimeoutSeconds, writeTimeoutSeconds, strategy); DefaultAnyValue tarnsportConf = DefaultAnyValue.create(TransportFactory.NAME_POOLMAXCONNS, System.getProperty("net.transport.pool.maxconns", "100")) .addValue(TransportFactory.NAME_PINGINTERVAL, System.getProperty("net.transport.ping.interval", "30")) .addValue(TransportFactory.NAME_CHECKINTERVAL, System.getProperty("net.transport.check.interval", "30")); @@ -470,6 +476,14 @@ public final class Application { return name; } + public ExecutorService getWorkExecutor() { + return workExecutor; + } + + public AsyncGroup getAsyncGroup() { + return asyncGroup; + } + public ResourceFactory getResourceFactory() { return resourceFactory; } @@ -677,7 +691,23 @@ public final class Application { } }, Application.class, ResourceFactory.class, TransportFactory.class, NodeSncpServer.class, NodeHttpServer.class, NodeWatchServer.class); + + //------------------------------------- 注册 HttpClient -------------------------------------------------------- + resourceFactory.register((ResourceFactory rf, final Object src, String resourceName, Field field, final Object attachment) -> { + try { + if (field.getAnnotation(Resource.class) == null) return; + HttpClient httpClient = HttpClient.create(asyncGroup); + field.set(src, httpClient); + rf.inject(httpClient, null); // 给其可能包含@Resource的字段赋值; + rf.register(resourceName, HttpClient.class, httpClient); + } catch (Exception e) { + logger.log(Level.SEVERE, "[" + Thread.currentThread().getName() + "] HttpClient inject error", e); + } + }, HttpClient.class); //-------------------------------------------------------------------------- + if (this.asyncGroup != null) { + ((AsyncIOGroup) this.asyncGroup).start(); + } if (this.clusterAgent != null) { if (logger.isLoggable(Level.FINER)) logger.log(Level.FINER, "ClusterAgent initing"); long s = System.currentTimeMillis(); @@ -759,7 +789,7 @@ public final class Application { //------------------------------------------------------------------------ for (AnyValue conf : resources.getAnyValues("group")) { final String group = conf.getValue("name", ""); - final String protocol = conf.getValue("protocol", Transport.DEFAULT_PROTOCOL).toUpperCase(); + final String protocol = conf.getValue("protocol", Transport.DEFAULT_NETPROTOCOL).toUpperCase(); if (!"TCP".equalsIgnoreCase(protocol) && !"UDP".equalsIgnoreCase(protocol)) { throw new RuntimeException("Not supported Transport Protocol " + conf.getValue("protocol")); } @@ -1004,8 +1034,7 @@ public final class Application { for (final AnyValue serconf : serconfs) { Thread thread = new Thread() { { - String host = serconf.getValue("host", "0.0.0.0").replace("0.0.0.0", "*"); - setName("Redkale-" + serconf.getValue("protocol", "Server").toUpperCase() + "-" + host + ":" + serconf.getIntValue("port") + "-Thread"); + setName("Redkale-" + serconf.getValue("protocol", "Server").toUpperCase().replaceFirst("\\..+", "") + ":" + serconf.getIntValue("port") + "-Thread"); this.setDaemon(true); } diff --git a/src/org/redkale/boot/NodeHttpServer.java b/src/org/redkale/boot/NodeHttpServer.java index 39159545b..27f591a44 100644 --- a/src/org/redkale/boot/NodeHttpServer.java +++ b/src/org/redkale/boot/NodeHttpServer.java @@ -46,7 +46,7 @@ public class NodeHttpServer extends NodeServer { } private static Server createServer(Application application, AnyValue serconf) { - return new HttpServer(application.getStartTime(), application.getResourceFactory().createChild()); + return new HttpServer(application, application.getStartTime(), application.getResourceFactory().createChild()); } public HttpServer getHttpServer() { diff --git a/src/org/redkale/boot/NodeServer.java b/src/org/redkale/boot/NodeServer.java index 05ac10005..3e92576e1 100644 --- a/src/org/redkale/boot/NodeServer.java +++ b/src/org/redkale/boot/NodeServer.java @@ -19,7 +19,6 @@ import java.util.concurrent.*; import java.util.function.*; import java.util.logging.*; import javax.annotation.*; -import javax.persistence.Transient; import static org.redkale.boot.Application.*; import org.redkale.boot.ClassFilter.FilterEntry; import org.redkale.net.Filter; @@ -154,9 +153,10 @@ public abstract class NodeServer { //必须要进行初始化, 构建Service时需要使用Context中的ExecutorService server.init(this.serverConf); //init之后才有Executor - resourceFactory.register(Server.RESNAME_SERVER_EXECUTOR, Executor.class, server.getWorkExecutor()); - resourceFactory.register(Server.RESNAME_SERVER_EXECUTOR, ExecutorService.class, server.getWorkExecutor()); - resourceFactory.register(Server.RESNAME_SERVER_EXECUTOR, ThreadPoolExecutor.class, server.getWorkExecutor()); + //废弃 @since 2.3.0 +// resourceFactory.register(Server.RESNAME_SERVER_EXECUTOR, Executor.class, server.getWorkExecutor()); +// resourceFactory.register(Server.RESNAME_SERVER_EXECUTOR, ExecutorService.class, server.getWorkExecutor()); +// resourceFactory.register(Server.RESNAME_SERVER_EXECUTOR, ThreadPoolExecutor.class, server.getWorkExecutor()); initResource(); //给 DataSource、CacheSource 注册依赖注入时的监听回调事件。 String interceptorClass = this.serverConf.getValue("interceptor", ""); @@ -307,7 +307,6 @@ public abstract class NodeServer { SimpleEntry resEntry = dataResources.get(resourceName); AnyValue sourceConf = resEntry == null ? null : resEntry.getValue(); DataSource source = null; - boolean needinit = true; if (sourceConf != null) { final Class sourceType = resEntry.getKey(); if (sourceType == DataJdbcSource.class) { @@ -331,15 +330,15 @@ public abstract class NodeServer { } if (source == null) { source = DataSources.createDataSource(resourceName); //从persistence.xml配置中创建 - needinit = false; } + application.dataSources.add(source); appResFactory.register(resourceName, DataSource.class, source); field.set(src, source); - rf.inject(source, self); // 给其可能包含@Resource的字段赋值; + rf.inject(source, self); // 给AsyncGroup和其他@Resource的字段赋值; //NodeServer.this.watchFactory.inject(src); - if (source instanceof Service && needinit) ((Service) source).init(sourceConf); + if (source instanceof Service) ((Service) source).init(sourceConf); } catch (Exception e) { logger.log(Level.SEVERE, "[" + Thread.currentThread().getName() + "] DataSource inject to " + src + " error", e); } @@ -383,16 +382,11 @@ public abstract class NodeServer { if (CacheSource.class.isAssignableFrom(sourceType)) { // CacheSource source = Modifier.isFinal(sourceType.getModifiers()) ? sourceType.getConstructor().newInstance() : (CacheSource) Sncp.createLocalService(serverClassLoader, resourceName, sourceType, client == null ? null : client.getMessageAgent(), appResFactory, appSncpTranFactory, sncpAddr, null, Sncp.getConf(srcService)); Type genericType = field.getGenericType(); - ParameterizedType pt = (genericType instanceof ParameterizedType) ? (ParameterizedType) genericType : null; - Type valType = pt == null ? null : pt.getActualTypeArguments()[0]; - if (CacheSource.class.isAssignableFrom(sourceType)) { - CacheSource cacheSource = (CacheSource) source; - cacheSource.initValueType(valType instanceof Class ? (Class) valType : Object.class); - cacheSource.initTransient(field.getAnnotation(Transient.class) != null); //必须在initValueType之后 - } application.cacheSources.add((CacheSource) source); - appResFactory.register(resourceName, genericType, source); appResFactory.register(resourceName, CacheSource.class, source); + if (genericType != CacheSource.class) { + appResFactory.register(resourceName, genericType, source); + } } field.set(src, source); rf.inject(source, self); // @@ -778,7 +772,7 @@ public abstract class NodeServer { public void start() throws IOException { if (interceptor != null) interceptor.preStart(this); - server.start(); + server.start(application); postStartServer(localServices, remoteServices); } diff --git a/src/org/redkale/boot/NodeSncpServer.java b/src/org/redkale/boot/NodeSncpServer.java index e45278e82..c3caa4254 100644 --- a/src/org/redkale/boot/NodeSncpServer.java +++ b/src/org/redkale/boot/NodeSncpServer.java @@ -46,7 +46,7 @@ public class NodeSncpServer extends NodeServer { } private static Server createServer(Application application, AnyValue serconf) { - return new SncpServer(application.getStartTime(), application.getResourceFactory().createChild()); + return new SncpServer(application, application.getStartTime(), serconf, application.getResourceFactory().createChild()); } @Override diff --git a/src/org/redkale/boot/watch/ServerWatchService.java b/src/org/redkale/boot/watch/ServerWatchService.java index f536afd94..80e7476c9 100644 --- a/src/org/redkale/boot/watch/ServerWatchService.java +++ b/src/org/redkale/boot/watch/ServerWatchService.java @@ -61,7 +61,7 @@ public class ServerWatchService extends AbstractWatchService { final Server server = node.getServer(); InetSocketAddress newAddr = new InetSocketAddress(newhost == null || newhost.isEmpty() ? server.getSocketAddress().getHostString() : newhost, newport); try { - server.changeAddress(newAddr); + server.changeAddress(application, newAddr); } catch (IOException e) { e.printStackTrace(); return new RetResult(RET_SERVER_CHANGEPORT_ERROR, "changeaddress error"); @@ -72,7 +72,7 @@ public class ServerWatchService extends AbstractWatchService { private Map formatToMap(NodeServer node) { Server server = node.getServer(); Map rs = new LinkedHashMap<>(); - String protocol = server.getProtocol(); + String protocol = server.getNetprotocol(); if (node instanceof NodeSncpServer) { protocol += "/SNCP"; } else if (node instanceof NodeWatchServer) { @@ -86,7 +86,6 @@ public class ServerWatchService extends AbstractWatchService { rs.put("name", server.getName()); rs.put("protocol", protocol); rs.put("address", server.getSocketAddress()); - rs.put("threads", server.getThreads()); rs.put("backlog", server.getBacklog()); rs.put("bufferCapacity", server.getBufferCapacity()); rs.put("bufferPoolSize", server.getBufferPoolSize()); diff --git a/src/org/redkale/cluster/CacheClusterAgent.java b/src/org/redkale/cluster/CacheClusterAgent.java index a22aae381..9dd1b8146 100644 --- a/src/org/redkale/cluster/CacheClusterAgent.java +++ b/src/org/redkale/cluster/CacheClusterAgent.java @@ -95,7 +95,7 @@ public class CacheClusterAgent extends ClusterAgent implements Resourcable { public void start() { if (this.scheduler == null) { this.scheduler = new ScheduledThreadPoolExecutor(4, (Runnable r) -> { - final Thread t = new Thread(r, CacheClusterAgent.class.getSimpleName() + "-Task-Thread"); + final Thread t = new Thread(r, "Redkale-" + CacheClusterAgent.class.getSimpleName() + "-Task-Thread"); t.setDaemon(true); return t; }); diff --git a/src/org/redkale/cluster/ClusterAgent.java b/src/org/redkale/cluster/ClusterAgent.java index 35df53612..8e2da7d67 100644 --- a/src/org/redkale/cluster/ClusterAgent.java +++ b/src/org/redkale/cluster/ClusterAgent.java @@ -140,6 +140,8 @@ public abstract class ClusterAgent { protected boolean canRegister(String protocol, Service service) { if ("SNCP".equalsIgnoreCase(protocol) && service.getClass().getAnnotation(Local.class) != null) return false; + AutoLoad al = service.getClass().getAnnotation(AutoLoad.class); + if (al != null && !al.value() && service.getClass().getAnnotation(Local.class) != null) return false; if (service instanceof WebSocketNode) { if (((WebSocketNode) service).getLocalWebSocketEngine() == null) return false; } @@ -329,7 +331,7 @@ public abstract class ClusterAgent { this.address = addr; this.serviceref = new WeakReference(service); Server server = ns.getServer(); - this.netprotocol = server instanceof SncpServer ? ((SncpServer) server).getNetprotocol() : Transport.DEFAULT_PROTOCOL; + this.netprotocol = server instanceof SncpServer ? ((SncpServer) server).getNetprotocol() : Transport.DEFAULT_NETPROTOCOL; } @Override diff --git a/src/org/redkale/convert/ArrayEncoder.java b/src/org/redkale/convert/ArrayEncoder.java index 4e8091281..a156779c3 100644 --- a/src/org/redkale/convert/ArrayEncoder.java +++ b/src/org/redkale/convert/ArrayEncoder.java @@ -29,6 +29,8 @@ public class ArrayEncoder implements Encodeable { protected final Encodeable componentEncoder; + protected final boolean subtypefinal; + protected volatile boolean inited = false; protected final Object lock = new Object(); @@ -47,6 +49,7 @@ public class ArrayEncoder implements Encodeable { factory.register(type, this); this.componentEncoder = factory.loadEncoder(this.componentType); this.anyEncoder = factory.getAnyEncoder(); + this.subtypefinal = (this.componentType instanceof Class) && Modifier.isFinal(((Class) this.componentType).getModifiers()); } finally { inited = true; synchronized (lock) { @@ -66,7 +69,7 @@ public class ArrayEncoder implements Encodeable { return; } if (value.length == 0) { - out.writeArrayB(0, this, componentEncoder, value); + out.writeArrayB(0, this, componentEncoder, value); out.writeArrayE(); return; } @@ -81,13 +84,25 @@ public class ArrayEncoder implements Encodeable { } } } - if (out.writeArrayB(value.length, this, componentEncoder, value) < 0) { - final Type comp = this.componentType; - boolean first = true; - for (Object v : value) { - if (!first) out.writeArrayMark(); - writeMemberValue(out, member, ((v != null && (v.getClass() == comp || out.specify() == comp)) ? componentEncoder : anyEncoder), v, first); - if (first) first = false; + Encodeable itemEncoder = this.componentEncoder; + if (subtypefinal) { + if (out.writeArrayB(value.length, this, itemEncoder, value) < 0) { + boolean first = true; + for (Object v : value) { + if (!first) out.writeArrayMark(); + writeMemberValue(out, member, itemEncoder, v, first); + if (first) first = false; + } + } + } else { + if (out.writeArrayB(value.length, this, itemEncoder, value) < 0) { + final Type comp = this.componentType; + boolean first = true; + for (Object v : value) { + if (!first) out.writeArrayMark(); + writeMemberValue(out, member, ((v != null && (v.getClass() == comp || out.specify() == comp)) ? itemEncoder : anyEncoder), v, first); + if (first) first = false; + } } } out.writeArrayE(); diff --git a/src/org/redkale/convert/BinaryConvert.java b/src/org/redkale/convert/BinaryConvert.java index 5019545a2..6db363567 100644 --- a/src/org/redkale/convert/BinaryConvert.java +++ b/src/org/redkale/convert/BinaryConvert.java @@ -32,5 +32,4 @@ public abstract class BinaryConvert extends public abstract byte[] convertTo(final Type type, final Object value); - public abstract byte[] convertMapTo(final Object... values); } diff --git a/src/org/redkale/convert/Convert.java b/src/org/redkale/convert/Convert.java index 3a10c333f..846e1f999 100644 --- a/src/org/redkale/convert/Convert.java +++ b/src/org/redkale/convert/Convert.java @@ -8,7 +8,7 @@ package org.redkale.convert; import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.function.*; -import org.redkale.util.Attribute; +import org.redkale.util.*; /** * 序列化/反序列化操作类 @@ -61,12 +61,16 @@ public abstract class Convert { public abstract byte[] convertToBytes(final Type type, final Object value); + public abstract void convertToBytes(final Object value, final ConvertBytesHandler handler); + + public abstract void convertToBytes(final Type type, final Object value, final ConvertBytesHandler handler); + + public abstract void convertToBytes(final ByteArray array, final Object value); + + public abstract void convertToBytes(final ByteArray array, final Type type, final Object value); + public abstract ByteBuffer[] convertTo(final Supplier supplier, final Object value); public abstract ByteBuffer[] convertTo(final Supplier supplier, final Type type, final Object value); - public abstract byte[] convertMapToBytes(final Object... values); - - public abstract ByteBuffer[] convertMapTo(final Supplier supplier, final Object... values); - } diff --git a/src/org/redkale/convert/ConvertBytesHandler.java b/src/org/redkale/convert/ConvertBytesHandler.java new file mode 100644 index 000000000..320ca0dcf --- /dev/null +++ b/src/org/redkale/convert/ConvertBytesHandler.java @@ -0,0 +1,23 @@ +/* + * 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.util.function.Consumer; + +/** + * + * convertToBytes系列的方法的回调 + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.3.0 + */ +public interface ConvertBytesHandler { + + void completed(byte[] bs, int offset, int length, Consumer callback, A attachment); +} diff --git a/src/org/redkale/convert/ConvertFactory.java b/src/org/redkale/convert/ConvertFactory.java index 2b30a9047..a980c913f 100644 --- a/src/org/redkale/convert/ConvertFactory.java +++ b/src/org/redkale/convert/ConvertFactory.java @@ -42,7 +42,7 @@ public abstract class ConvertFactory { protected Convert convert; - protected boolean tiny; + protected boolean tiny; //String类型值为"",Boolean类型值为false时是否需要输出, 默认为true private final Encodeable anyEncoder = new AnyEncoder(this); @@ -112,6 +112,7 @@ public abstract class ConvertFactory { this.register(InetSocketAddress.class, InetAddressSimpledCoder.InetSocketAddressSimpledCoder.instance); this.register(Pattern.class, PatternSimpledCoder.instance); this.register(File.class, FileSimpledCoder.instance); + this.register(Throwable.class, ThrowableSimpledCoder.instance); this.register(CompletionHandler.class, CompletionHandlerSimpledCoder.instance); this.register(URL.class, URLSimpledCoder.instance); this.register(URI.class, URISimpledCoder.instance); @@ -232,11 +233,15 @@ public abstract class ConvertFactory { return new EnumSimpledCoder(enumClass); } + protected Encodeable createDyncEncoder(Type type) { + return null; + } + protected ObjectDecoder createObjectDecoder(Type type) { return new ObjectDecoder(type); } - protected ObjectEncoder createObjectEncoder(Type type) { + protected ObjectEncoder createObjectEncoder(Type type) { return new ObjectEncoder(type); } @@ -810,8 +815,11 @@ public abstract class ConvertFactory { } } if (simpleCoder == null) { - oe = createObjectEncoder(type); - encoder = oe; + encoder = createDyncEncoder(type); + if (encoder == null) { + oe = createObjectEncoder(type); + encoder = oe; + } } else { encoder = simpleCoder; } diff --git a/src/org/redkale/convert/EnMember.java b/src/org/redkale/convert/EnMember.java index cbba0251f..12edd905f 100644 --- a/src/org/redkale/convert/EnMember.java +++ b/src/org/redkale/convert/EnMember.java @@ -31,6 +31,10 @@ public final class EnMember { //final boolean isnumber; final boolean bool; + final char[] jsonFieldNameChars; + + final byte[] jsonFieldNameBytes; + protected int index; protected int position; //从1开始 @@ -43,6 +47,8 @@ public final class EnMember { Class t = attribute.type(); this.string = CharSequence.class.isAssignableFrom(t); this.bool = t == Boolean.class || t == boolean.class; + this.jsonFieldNameChars = ('"' + attribute.field() + "\":").toCharArray(); + this.jsonFieldNameBytes = ('"' + attribute.field() + "\":").getBytes(); //this.isnumber = Number.class.isAssignableFrom(t) || (!this.isbool && t.isPrimitive()); } @@ -71,6 +77,14 @@ public final class EnMember { return attribute; } + public char[] getJsonFieldNameChars() { + return jsonFieldNameChars; + } + + public byte[] getJsonFieldNameBytes() { + return jsonFieldNameBytes; + } + public Encodeable getEncoder() { return encoder; } diff --git a/src/org/redkale/convert/Encodeable.java b/src/org/redkale/convert/Encodeable.java index a6b591949..136e1458c 100644 --- a/src/org/redkale/convert/Encodeable.java +++ b/src/org/redkale/convert/Encodeable.java @@ -28,4 +28,9 @@ public interface Encodeable { */ public Type getType(); + //是否需要检查Writer.specify + default boolean specifyable() { + return true; + } + } diff --git a/src/org/redkale/convert/ObjectEncoder.java b/src/org/redkale/convert/ObjectEncoder.java index 00f2b6320..5800495ab 100644 --- a/src/org/redkale/convert/ObjectEncoder.java +++ b/src/org/redkale/convert/ObjectEncoder.java @@ -103,7 +103,8 @@ public class ObjectEncoder implements Encodeable { if (factory.isConvertDisabled(method)) continue; if (method.getParameterTypes().length != 0) continue; if (method.getReturnType() == void.class) continue; - if (reversible && (cps == null || !contains(cps, ConvertFactory.readGetSetFieldName(method)))) { + String convertname = ConvertFactory.readGetSetFieldName(method); + if (reversible && (cps == null || !contains(cps, convertname))) { boolean is = method.getName().startsWith("is"); try { clazz.getMethod(method.getName().replaceFirst(is ? "is" : "get", "set"), method.getReturnType()); @@ -114,6 +115,13 @@ public class ObjectEncoder implements Encodeable { ref = factory.findRef(clazz, method); if (ref != null && ref.ignore()) continue; ConvertSmallString small = method.getAnnotation(ConvertSmallString.class); + if (small == null) { + try { + Field f = clazz.getDeclaredField(convertname); + if (f != null) small = f.getAnnotation(ConvertSmallString.class); + } catch (Exception e) { + } + } Encodeable fieldCoder; if (small != null && method.getReturnType() == String.class) { fieldCoder = StringSimpledCoder.SmallStringSimpledCoder.instance; diff --git a/src/org/redkale/convert/TextConvert.java b/src/org/redkale/convert/TextConvert.java index 7d9d0b086..c6f3b095a 100644 --- a/src/org/redkale/convert/TextConvert.java +++ b/src/org/redkale/convert/TextConvert.java @@ -32,5 +32,4 @@ public abstract class TextConvert extends Co public abstract String convertTo(final Type type, final Object value); - public abstract String convertMapTo(final Object... values); } diff --git a/src/org/redkale/convert/Writer.java b/src/org/redkale/convert/Writer.java index 7999962fe..052321df8 100644 --- a/src/org/redkale/convert/Writer.java +++ b/src/org/redkale/convert/Writer.java @@ -133,7 +133,7 @@ public abstract class Writer { } } Attribute attr = member.getAttribute(); - this.writeFieldName(attr.field(), attr.genericType(), member.getPosition()); + this.writeFieldName(member, attr.field(), attr.genericType(), member.getPosition()); member.encoder.convertTo(this, value); this.comma = true; } @@ -160,7 +160,7 @@ public abstract class Writer { if (!((Boolean) value)) return; } } - this.writeFieldName(fieldName, fieldType, fieldPos); + this.writeFieldName(null, fieldName, fieldType, fieldPos); anyEncoder.convertTo(this, value); this.comma = true; } @@ -172,7 +172,7 @@ public abstract class Writer { */ public final void writeFieldName(final EnMember member) { Attribute attr = member.getAttribute(); - this.writeFieldName(attr.field(), attr.genericType(), member.getPosition()); + this.writeFieldName(member, attr.field(), attr.genericType(), member.getPosition()); } /** @@ -233,11 +233,12 @@ public abstract class Writer { /** * 输出一个字段名 * + * @param member EnMember * @param fieldName 字段名称 * @param fieldType 字段类型 * @param fieldPos 字段顺序 */ - public abstract void writeFieldName(String fieldName, Type fieldType, int fieldPos); + public abstract void writeFieldName(EnMember member, String fieldName, Type fieldType, int fieldPos); /** * 写入一个boolean值 diff --git a/src/org/redkale/convert/bson/BsonByteBufferReader.java b/src/org/redkale/convert/bson/BsonByteBufferReader.java index dea6b38c8..77bdfe3b9 100644 --- a/src/org/redkale/convert/bson/BsonByteBufferReader.java +++ b/src/org/redkale/convert/bson/BsonByteBufferReader.java @@ -6,10 +6,10 @@ package org.redkale.convert.bson; import java.nio.*; +import java.nio.charset.StandardCharsets; import org.redkale.convert.*; import static org.redkale.convert.Reader.SIGN_NULL; import org.redkale.convert.ext.ByteSimpledCoder; -import org.redkale.util.*; /** * 以ByteBuffer为数据载体的BsonReader @@ -233,6 +233,6 @@ public class BsonByteBufferReader extends BsonReader { int len = readInt(); if (len == SIGN_NULL) return null; if (len == 0) return ""; - return new String(Utility.decodeUTF8(read(len))); + return new String(read(len), StandardCharsets.UTF_8); } } diff --git a/src/org/redkale/convert/bson/BsonByteBufferWriter.java b/src/org/redkale/convert/bson/BsonByteBufferWriter.java index e0905d884..2058e92ed 100644 --- a/src/org/redkale/convert/bson/BsonByteBufferWriter.java +++ b/src/org/redkale/convert/bson/BsonByteBufferWriter.java @@ -137,4 +137,19 @@ public class BsonByteBufferWriter extends BsonWriter { this.buffers = null; return false; } + + @Override + public byte[] content() { + throw new UnsupportedOperationException("Not supported yet."); //无需实现 + } + + @Override + public int offset() { + throw new UnsupportedOperationException("Not supported yet.");//无需实现 + } + + @Override + public int length() { + throw new UnsupportedOperationException("Not supported yet."); //无需实现 + } } diff --git a/src/org/redkale/convert/bson/BsonConvert.java b/src/org/redkale/convert/bson/BsonConvert.java index 1f19d3745..662ba81af 100644 --- a/src/org/redkale/convert/bson/BsonConvert.java +++ b/src/org/redkale/convert/bson/BsonConvert.java @@ -39,9 +39,9 @@ import org.redkale.util.*; */ public class BsonConvert extends BinaryConvert { - private static final ObjectPool readerPool = BsonReader.createPool(Integer.getInteger("convert.bson.pool.size", Integer.getInteger("convert.pool.size", 16))); + private final ThreadLocal writerPool = ThreadLocal.withInitial(BsonWriter::new); - private static final ObjectPool writerPool = BsonWriter.createPool(Integer.getInteger("convert.bson.pool.size", Integer.getInteger("convert.pool.size", 16))); + private final Consumer offerConsumer = w -> offerBsonWriter(w); private final boolean tiny; @@ -84,11 +84,11 @@ public class BsonConvert extends BinaryConvert { } public BsonReader pollBsonReader() { - return readerPool.get(); + return new BsonReader(); } public void offerBsonReader(final BsonReader in) { - if (in != null) readerPool.accept(in); + //无需回收 } //------------------------------ writer ----------------------------------------------------------- @@ -96,16 +96,25 @@ public class BsonConvert extends BinaryConvert { return configWrite(new BsonByteBufferWriter(tiny, supplier)); } - public BsonWriter pollBsonWriter(final OutputStream out) { + protected BsonWriter pollBsonWriter(final OutputStream out) { return configWrite(new BsonStreamWriter(tiny, out)); } public BsonWriter pollBsonWriter() { - return writerPool.get().tiny(tiny); + BsonWriter writer = writerPool.get(); + if (writer == null) { + writer = new BsonWriter(); + } else { + writerPool.set(null); + } + return configWrite(writer.tiny(tiny)); } public void offerBsonWriter(final BsonWriter out) { - if (out != null) writerPool.accept(out); + if (out != null) { + out.recycle(); + writerPool.set(out); + } } //------------------------------ convertFrom ----------------------------------------------------------- @@ -119,11 +128,9 @@ public class BsonConvert extends BinaryConvert { @SuppressWarnings("unchecked") public T convertFrom(final Type type, final byte[] bytes, final int offset, final int len) { if (type == null) return null; - final BsonReader in = readerPool.get(); - in.setBytes(bytes, offset, len); + final BsonReader in = new BsonReader(bytes, offset, len); @SuppressWarnings("unchecked") T rs = (T) factory.loadDecoder(type).convertFrom(in); - readerPool.accept(in); return rs; } @@ -159,10 +166,10 @@ public class BsonConvert extends BinaryConvert { @Override public byte[] convertTo(final Object value) { if (value == null) { - final BsonWriter out = writerPool.get().tiny(tiny); + final BsonWriter out = pollBsonWriter(); out.writeNull(); byte[] result = out.toArray(); - writerPool.accept(out); + offerBsonWriter(out); return result; } return convertTo(value.getClass(), value); @@ -171,10 +178,10 @@ public class BsonConvert extends BinaryConvert { @Override public byte[] convertTo(final Type type, final Object value) { if (type == null) return null; - final BsonWriter out = writerPool.get().tiny(tiny); - factory.loadEncoder(type).convertTo(out, value); - byte[] result = out.toArray(); - writerPool.accept(out); + final BsonWriter writer = pollBsonWriter(); + factory.loadEncoder(type).convertTo(writer, value); + byte[] result = writer.toArray(); + offerBsonWriter(writer); return result; } @@ -189,13 +196,35 @@ public class BsonConvert extends BinaryConvert { } @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.accept(out); - return result; + public void convertToBytes(final Object value, final ConvertBytesHandler handler) { + convertToBytes(value == null ? null : value.getClass(), value, handler); + } + + @Override + public void convertToBytes(final Type type, final Object value, final ConvertBytesHandler handler) { + final BsonWriter writer = pollBsonWriter(); + if (type == null) { + writer.writeNull(); + } else { + factory.loadEncoder(type).convertTo(writer, value); + } + writer.completed(handler, offerConsumer); + } + + @Override + public void convertToBytes(final ByteArray array, final Object value) { + convertToBytes(array, value == null ? null : value.getClass(), value); + } + + @Override + public void convertToBytes(final ByteArray array, final Type type, final Object value) { + final BsonWriter writer = configWrite(new BsonWriter(array).tiny(tiny)); + if (type == null) { + writer.writeNull(); + } else { + factory.loadEncoder(type).convertTo(writer, value); + } + writer.directTo(array); } public void convertTo(final OutputStream out, final Object value) { @@ -215,14 +244,6 @@ public class BsonConvert extends BinaryConvert { } } - public void convertMapTo(final OutputStream out, final Object... values) { - if (values == null) { - pollBsonWriter(out).writeNull(); - } else { - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(pollBsonWriter(out), values); - } - } - @Override public ByteBuffer[] convertTo(final Supplier supplier, final Object value) { if (supplier == null) return null; @@ -238,36 +259,13 @@ public class BsonConvert extends BinaryConvert { @Override public ByteBuffer[] convertTo(final Supplier supplier, final Type type, final Object value) { if (supplier == null || type == null) return null; - BsonByteBufferWriter out = pollBsonWriter(supplier); + BsonByteBufferWriter writer = pollBsonWriter(supplier); if (value == null) { - out.writeNull(); + writer.writeNull(); } else { - factory.loadEncoder(type).convertTo(out, value); + factory.loadEncoder(type).convertTo(writer, value); } - return out.toBuffers(); - } - - @Override - public byte[] convertMapToBytes(final Object... values) { - BsonWriter out = pollBsonWriter(); - if (values == null) { - out.writeNull(); - } else { - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values); - } - return out.toArray(); - } - - @Override - public ByteBuffer[] convertMapTo(final Supplier supplier, final Object... values) { - if (supplier == null) return null; - BsonByteBufferWriter out = pollBsonWriter(supplier); - if (values == null) { - out.writeNull(); - } else { - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values); - } - return out.toBuffers(); + return writer.toBuffers(); } public void convertTo(final BsonWriter writer, final Object value) { @@ -283,14 +281,6 @@ public class BsonConvert extends BinaryConvert { 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); @@ -298,14 +288,8 @@ public class BsonConvert extends BinaryConvert { public BsonWriter convertToWriter(final Type type, final Object value) { if (type == null) return null; - final BsonWriter out = writerPool.get().tiny(tiny); - factory.loadEncoder(type).convertTo(out, value); - return out; - } - - public BsonWriter convertMapToWriter(final Object... values) { - final BsonWriter out = writerPool.get().tiny(tiny); - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values); - return out; + final BsonWriter writer = writerPool.get().tiny(tiny); + factory.loadEncoder(type).convertTo(writer, value); + return writer; } } diff --git a/src/org/redkale/convert/bson/BsonReader.java b/src/org/redkale/convert/bson/BsonReader.java index ceeb9b640..51c09abfd 100644 --- a/src/org/redkale/convert/bson/BsonReader.java +++ b/src/org/redkale/convert/bson/BsonReader.java @@ -5,6 +5,7 @@ */ package org.redkale.convert.bson; +import java.nio.charset.StandardCharsets; import org.redkale.convert.*; import static org.redkale.convert.Reader.SIGN_NULL; import org.redkale.convert.ext.*; @@ -341,14 +342,14 @@ public class BsonReader extends Reader { int len = readInt(); if (len == SIGN_NULL) return null; if (len == 0) return ""; - String value = new String(Utility.decodeUTF8(content, ++this.position, len)); + String value = new String(content, ++this.position, len, StandardCharsets.UTF_8); this.position += len - 1;//上一行已经++this.position,所以此处要-1 return value; } @Override public ValueType readType() { - throw new UnsupportedOperationException("Not supported yet."); + throw new UnsupportedOperationException("Not supported yet."); } } diff --git a/src/org/redkale/convert/bson/BsonWriter.java b/src/org/redkale/convert/bson/BsonWriter.java index 63ba622f1..0e7a5a20d 100644 --- a/src/org/redkale/convert/bson/BsonWriter.java +++ b/src/org/redkale/convert/bson/BsonWriter.java @@ -7,6 +7,7 @@ package org.redkale.convert.bson; import java.lang.reflect.Type; import java.nio.ByteBuffer; +import java.util.function.Consumer; import org.redkale.convert.*; import org.redkale.convert.ext.ByteSimpledCoder; import org.redkale.util.*; @@ -18,7 +19,7 @@ import org.redkale.util.*; * * @author zhangjx */ -public class BsonWriter extends Writer { +public class BsonWriter extends Writer implements ByteTuple { private static final int defaultSize = Integer.getInteger("convert.bson.writer.buffer.defsize", Integer.getInteger("convert.writer.buffer.defsize", 1024)); @@ -32,6 +33,47 @@ public class BsonWriter extends Writer { return ObjectPool.createSafePool(max, (Object... params) -> new BsonWriter(), null, (t) -> t.recycle()); } + @Override + public byte[] content() { + return content; + } + + @Override + public int offset() { + return 0; + } + + @Override + public int length() { + return count; + } + + /** + * 直接获取全部数据, 实际数据需要根据count长度来截取 + * + * @return byte[] + */ + public byte[] directBytes() { + return content; + } + + /** + * 将本对象的内容引用复制给array + * + * @param array ByteArray + */ + public void directTo(ByteArray array) { + array.directFrom(content, count); + } + + public void completed(ConvertBytesHandler handler, Consumer callback) { + handler.completed(content, 0, count, callback, this); + } + + public ByteArray toByteArray() { + return new ByteArray(this); + } + public byte[] toArray() { if (count == content.length) return content; byte[] newdata = new byte[count]; @@ -55,6 +97,11 @@ public class BsonWriter extends Writer { this.content = new byte[size > 128 ? size : 128]; } + public BsonWriter(ByteArray array) { + this.content = array.content(); + this.count = array.length(); + } + @Override public final boolean tiny() { return tiny; @@ -201,7 +248,7 @@ public class BsonWriter extends Writer { } @Override - public final void writeFieldName(String fieldName, Type fieldType, int fieldPos) { + public final void writeFieldName(EnMember member, String fieldName, Type fieldType, int fieldPos) { writeByte(BsonReader.SIGN_HASNEXT); writeSmallString(fieldName); writeByte(BsonFactory.typeEnum(fieldType)); diff --git a/src/org/redkale/convert/ext/ThrowableSimpledCoder.java b/src/org/redkale/convert/ext/ThrowableSimpledCoder.java new file mode 100644 index 000000000..1c872bcfb --- /dev/null +++ b/src/org/redkale/convert/ext/ThrowableSimpledCoder.java @@ -0,0 +1,40 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.convert.ext; + +import org.redkale.convert.*; + +/** + * 文件 的SimpledCoder实现 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +public class ThrowableSimpledCoder extends SimpledCoder { + + public static final ThrowableSimpledCoder instance = new ThrowableSimpledCoder(); + + @Override + public void convertTo(W out, Throwable value) { + if (value == null) { + out.writeNull(); + } else { + out.writeString(value.toString()); + } + } + + @Override + public Throwable convertFrom(R in) { + String value = in.readString(); + if (value == null) return null; + return new Exception(value); + } + +} diff --git a/src/org/redkale/convert/json/JsonByteBufferWriter.java b/src/org/redkale/convert/json/JsonByteBufferWriter.java index a8b4260a3..52b674512 100644 --- a/src/org/redkale/convert/json/JsonByteBufferWriter.java +++ b/src/org/redkale/convert/json/JsonByteBufferWriter.java @@ -22,6 +22,10 @@ import org.redkale.util.*; */ public class JsonByteBufferWriter extends JsonWriter { + private static final char[] CHARS_TUREVALUE = "true".toCharArray(); + + private static final char[] CHARS_FALSEVALUE = "false".toCharArray(); + protected Charset charset; private final Supplier supplier; @@ -36,7 +40,7 @@ public class JsonByteBufferWriter extends JsonWriter { protected JsonByteBufferWriter(boolean tiny, Charset charset, Supplier supplier) { this.tiny = tiny; - this.charset = StandardCharsets.UTF_8.equals(charset) ? null : charset; + this.charset = charset; this.supplier = supplier; } @@ -56,7 +60,6 @@ public class JsonByteBufferWriter extends JsonWriter { return false; } - @Override public ByteBuffer[] toBuffers() { if (buffers == null) return new ByteBuffer[0]; for (int i = index; i < this.buffers.length; i++) { @@ -66,7 +69,6 @@ public class JsonByteBufferWriter extends JsonWriter { return this.buffers; } - @Override public int count() { if (this.buffers == null) return 0; int len = 0; @@ -111,6 +113,32 @@ public class JsonByteBufferWriter extends JsonWriter { writeTo(-1, false, chs, start, len); } + @Override + public void writeTo(final byte ch) { //只能是 0 - 127 的字符 + expand(1); + this.buffers[index].put(ch); + } + + @Override + public void writeTo(final byte[] chs, final int start, final int len) { //只能是 0 - 127 的字符 + int expandsize = expand(len); + if (expandsize == 0) { // 只需要一个buffer + this.buffers[index].put(chs, start, len); + } else { + ByteBuffer buffer = this.buffers[index]; + int remain = len; + int offset = start; + while (remain > 0) { + int bsize = Math.min(buffer.remaining(), remain); + buffer.put(chs, offset, bsize); + offset += bsize; + remain -= bsize; + if (remain < 1) break; + buffer = nextByteBuffer(); + } + } + } + private void writeTo(int expandsize, final boolean quote, final char[] chs, final int start, final int len) { int byteLength = quote ? 2 : 0; ByteBuffer bb = null; @@ -266,6 +294,11 @@ public class JsonByteBufferWriter extends JsonWriter { } } + @Override + public void writeBoolean(boolean value) { + writeTo(value ? CHARS_TUREVALUE : CHARS_FALSEVALUE); + } + @Override public void writeInt(int value) { writeLatin1To(false, String.valueOf(value)); diff --git a/src/org/redkale/convert/json/JsonBytesWriter.java b/src/org/redkale/convert/json/JsonBytesWriter.java new file mode 100644 index 000000000..5a51ad0e8 --- /dev/null +++ b/src/org/redkale/convert/json/JsonBytesWriter.java @@ -0,0 +1,389 @@ +/* + * 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.json; + +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.function.Consumer; +import org.redkale.convert.*; +import static org.redkale.convert.json.JsonWriter.*; +import org.redkale.util.*; + +/** + * + * writeTo系列的方法输出的字符不能含特殊字符 + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.3.0 + */ +public class JsonBytesWriter extends JsonWriter implements ByteTuple { + + private static final boolean greatejdk8 = Utility.greaterJDK8(); + + private static final byte[] BYTES_TUREVALUE = "true".getBytes(); + + private static final byte[] BYTES_FALSEVALUE = "false".getBytes(); + + private int count; + + private byte[] content; + + public JsonBytesWriter() { + this(defaultSize); + } + + public JsonBytesWriter(int size) { + this.content = new byte[size > 1024 ? size : 1024]; + } + + public JsonBytesWriter(ByteArray array) { + this.content = array.content(); + this.count = array.length(); + } + + public JsonBytesWriter(boolean tiny, ByteArray array) { + this.tiny = tiny; + this.content = array.content(); + this.count = array.length(); + } + + protected byte[] expand(int len) { + int newcount = count + len; + if (newcount <= content.length) return content; + byte[] newdata = new byte[Math.max(content.length * 3 / 2, newcount)]; + System.arraycopy(content, 0, newdata, 0, count); + this.content = newdata; + return newdata; + } + + @Override + public byte[] content() { + return content; + } + + @Override + public int offset() { + return 0; + } + + @Override + public int length() { + return count; + } + + /** + * 将本对象的内容引用复制给array + * + * @param array ByteArray + */ + public void directTo(ByteArray array) { + array.directFrom(content, count); + } + + @Override + public final void writeFieldName(EnMember member, String fieldName, Type fieldType, int fieldPos) { + if (this.comma) writeTo(','); + if (member != null) { + byte[] bs = member.getJsonFieldNameBytes(); + expand(bs.length); + System.arraycopy(bs, 0, content, count, bs.length); + count += bs.length; + } else { + writeLatin1To(true, fieldName); + writeTo(':'); + } + } + + @Override + public void writeTo(final char ch) { //只能是 0 - 127 的字符 + expand(1); + content[count++] = (byte) ch; + } + + @Override + public void writeTo(final char[] chs, final int start, final int len) { //只能是 0 - 127 的字符 + expand(len); + for (int i = 0; i < len; i++) { + content[count + i] = (byte) chs[start + i]; + } + count += len; + } + + @Override + public void writeTo(final byte ch) { //只能是 0 - 127 的字符 + expand(1); + content[count++] = ch; + } + + @Override + public void writeTo(final byte[] chs, final int start, final int len) { //只能是 0 - 127 的字符 + expand(len); + System.arraycopy(chs, start, content, count, len); + count += len; + } + + /** + * 注意: 该String值不能为null且不会进行转义, 只用于不含需要转义字符的字符串,例如enum、double、BigInteger转换的String + * + * @param quote 是否加双引号 + * @param value 非null且不含需要转义的字符的String值 + */ + @Override + public void writeLatin1To(final boolean quote, final String value) { + byte[] bs = Utility.byteArray(value); + int len = bs.length; + expand(len + (quote ? 2 : 0)); + if (quote) content[count++] = '"'; + System.arraycopy(bs, 0, content, count, bs.length); + count += len; + if (quote) content[count++] = '"'; + } + + public JsonBytesWriter clear() { + this.count = 0; + return this; + } + + @Override + public boolean recycle() { + super.recycle(); + this.count = 0; + this.specify = null; + if (this.content != null && this.content.length > defaultSize * 100) { + this.content = new byte[defaultSize]; + } + return true; + } + + /** + * 直接获取全部数据, 实际数据需要根据count长度来截取 + * + * @return byte[] + */ + public byte[] directBytes() { + return content; + } + + public void completed(ConvertBytesHandler handler, Consumer callback) { + handler.completed(content, 0, count, callback, this); + } + + public byte[] toBytes() { + byte[] copy = new byte[count]; + System.arraycopy(content, 0, copy, 0, count); + return copy; + } + + public int count() { + return this.count; + } + + private void writeEscapeLatinString(byte[] value) { + byte[] bytes = expand(value.length * 2 + 2); + int curr = count; + bytes[curr++] = '"'; + for (byte b : value) { + if (b == '"') { + bytes[curr++] = '\\'; + bytes[curr++] = '"'; + } else if (b == '\\') { + bytes[curr++] = '\\'; + bytes[curr++] = '\\'; + } else if (b < 32) { + if (b == '\n') { + bytes[curr++] = '\\'; + bytes[curr++] = 'n'; + } else if (b == '\r') { + bytes[curr++] = '\\'; + bytes[curr++] = 'r'; + } else if (b == '\t') { + bytes[curr++] = '\\'; + bytes[curr++] = 't'; + } else { + bytes[curr++] = b; + } + } else { + bytes[curr++] = b; + } + } + bytes[curr++] = '"'; + count = curr; + } + + @Override + public void writeString(String value) { + if (value == null) { + writeNull(); + return; + } + if (greatejdk8 && Utility.isLatin1(value)) { + writeEscapeLatinString(Utility.byteArray(value)); + return; + } + byte[] bytes = expand(value.length() * 4 + 2); + int curr = count; + bytes[curr++] = '"'; + int len = value.length(); + for (int i = 0; i < len; i++) { + char ch = value.charAt(i); + switch (ch) { + case '\n': + bytes[curr++] = '\\'; + bytes[curr++] = 'n'; + break; + case '\r': + bytes[curr++] = '\\'; + bytes[curr++] = 'r'; + break; + case '\t': + bytes[curr++] = '\\'; + bytes[curr++] = 't'; + break; + case '\\': + bytes[curr++] = '\\'; + bytes[curr++] = '\\'; + break; + case '"': + bytes[curr++] = '\\'; + bytes[curr++] = '"'; + break; + default: + if (ch < 0x80) { + bytes[curr++] = (byte) ch; + } else if (ch < 0x800) { + bytes[curr++] = (byte) (0xc0 | (ch >> 6)); + bytes[curr++] = (byte) (0x80 | (ch & 0x3f)); + } else if (Character.isSurrogate(ch)) { //连取两个 + int uc = Character.toCodePoint(ch, value.charAt(++i)); + bytes[curr++] = (byte) (0xf0 | ((uc >> 18))); + bytes[curr++] = (byte) (0x80 | ((uc >> 12) & 0x3f)); + bytes[curr++] = (byte) (0x80 | ((uc >> 6) & 0x3f)); + bytes[curr++] = (byte) (0x80 | (uc & 0x3f)); + } else { + bytes[curr++] = (byte) (0xe0 | ((ch >> 12))); + bytes[curr++] = (byte) (0x80 | ((ch >> 6) & 0x3f)); + bytes[curr++] = (byte) (0x80 | (ch & 0x3f)); + } + break; + } + } + bytes[curr++] = '"'; + count = curr; + } + + @Override + public String toString() { + return new String(content, 0, count, StandardCharsets.UTF_8); + } + //---------------------------------------------------------------------------------------------- + + @Override + public void writeBoolean(boolean value) { + byte[] bs = value ? BYTES_TUREVALUE : BYTES_FALSEVALUE; + expand(bs.length); + System.arraycopy(bs, 0, content, count, bs.length); + count += bs.length; + } + + @Override + public void writeInt(int value) { + final char sign = value >= 0 ? 0 : '-'; + if (value < 0) value = -value; + int size; + for (int i = 0;; i++) { + if (value <= sizeTable[i]) { + size = i + 1; + break; + } + } + if (sign != 0) size++; //负数 + byte[] bytes = expand(size); + + int q, r; + int charPos = count + size; + + // Generate two digits per iteration + while (value >= 65536) { + q = value / 100; + // really: r = i - (q * 100); + r = value - ((q << 6) + (q << 5) + (q << 2)); + value = q; + bytes[--charPos] = (byte) DigitOnes[r]; + bytes[--charPos] = (byte) DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i <= 65536, i); + for (;;) { + q = (value * 52429) >>> (16 + 3); + r = value - ((q << 3) + (q << 1)); // r = i-(q*10) ... + bytes[--charPos] = (byte) digits[r]; + value = q; + if (value == 0) break; + } + if (sign != 0) bytes[--charPos] = (byte) sign; + count += size; + } + + @Override + public void writeLong(long value) { + final char sign = value >= 0 ? 0 : '-'; + if (value < 0) value = -value; + int size = 19; + long p = 10; + for (int i = 1; i < 19; i++) { + if (value < p) { + size = i; + break; + } + p = 10 * p; + } + if (sign != 0) size++; //负数 + byte[] bytes = expand(size); + + long q; + int r; + int charPos = count + size; + + // Get 2 digits/iteration using longs until quotient fits into an int + while (value > Integer.MAX_VALUE) { + q = value / 100; + // really: r = i - (q * 100); + r = (int) (value - ((q << 6) + (q << 5) + (q << 2))); + value = q; + content[--charPos] = (byte) DigitOnes[r]; + content[--charPos] = (byte) DigitTens[r]; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int) value; + while (i2 >= 65536) { + q2 = i2 / 100; + // really: r = i2 - (q * 100); + r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); + i2 = q2; + bytes[--charPos] = (byte) DigitOnes[r]; + bytes[--charPos] = (byte) DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i2 <= 65536, i2); + for (;;) { + q2 = (i2 * 52429) >>> (16 + 3); + r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... + bytes[--charPos] = (byte) digits[r]; + i2 = q2; + if (i2 == 0) break; + } + if (sign != 0) bytes[--charPos] = (byte) sign; + count += size; + } + +} diff --git a/src/org/redkale/convert/json/JsonCharsWriter.java b/src/org/redkale/convert/json/JsonCharsWriter.java new file mode 100644 index 000000000..6cdb2102d --- /dev/null +++ b/src/org/redkale/convert/json/JsonCharsWriter.java @@ -0,0 +1,267 @@ +/* + * 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.json; + +import java.nio.ByteBuffer; +import org.redkale.util.*; + +/** + * + * writeTo系列的方法输出的字符不能含特殊字符 + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.3.0 + */ +public class JsonCharsWriter extends JsonWriter { + + private static final char[] CHARS_TUREVALUE = "true".toCharArray(); + + private static final char[] CHARS_FALSEVALUE = "false".toCharArray(); + + private int count; + + private char[] content; + + public JsonCharsWriter() { + this(defaultSize); + } + + public JsonCharsWriter(int size) { + this.content = new char[size > 1024 ? size : 1024]; + } + + //----------------------------------------------------------------------- + /** + * 返回指定至少指定长度的缓冲区 + * + * @param len + * + * @return + */ + private char[] expand(int len) { + int newcount = count + len; + if (newcount <= content.length) return content; + char[] newdata = new char[Math.max(content.length * 3 / 2, newcount)]; + System.arraycopy(content, 0, newdata, 0, count); + this.content = newdata; + return newdata; + } + + @Override + public void writeTo(final char ch) { //只能是 0 - 127 的字符 + expand(1); + content[count++] = ch; + } + + @Override + public void writeTo(final char[] chs, final int start, final int len) { //只能是 0 - 127 的字符 + expand(len); + System.arraycopy(chs, start, content, count, len); + count += len; + } + + @Override + public void writeTo(final byte ch) { //只能是 0 - 127 的字符 + expand(1); + content[count++] = (char) ch; + } + + @Override + public void writeTo(final byte[] chs, final int start, final int len) { //只能是 0 - 127 的字符 + expand(len); + for (int i = 0; i < len; i++) { + content[count + i] = (char) chs[start + i]; + } + count += len; + } + + /** + * 注意: 该String值不能为null且不会进行转义, 只用于不含需要转义字符的字符串,例如enum、double、BigInteger转换的String + * + * @param quote 是否加双引号 + * @param value 非null且不含需要转义的字符的String值 + */ + @Override + public void writeLatin1To(final boolean quote, final String value) { + int len = value.length(); + expand(len + (quote ? 2 : 0)); + if (quote) content[count++] = '"'; + value.getChars(0, len, content, count); + count += len; + if (quote) content[count++] = '"'; + } + + @Override + protected boolean recycle() { + super.recycle(); + this.count = 0; + this.specify = null; + if (this.content != null && this.content.length > defaultSize) { + this.content = new char[defaultSize]; + } + return true; + } + + public ByteBuffer[] toBuffers() { + return new ByteBuffer[]{ByteBuffer.wrap(Utility.encodeUTF8(content, 0, count))}; + } + + public byte[] toBytes() { + return Utility.encodeUTF8(content, 0, count); + } + + public int count() { + return this.count; + } + + @Override + public void writeString(String value) { + if (value == null) { + writeNull(); + return; + } + expand(value.length() * 2 + 2); + content[count++] = '"'; + for (char ch : Utility.charArray(value)) { + switch (ch) { + case '\n': + content[count++] = '\\'; + content[count++] = 'n'; + break; + case '\r': + content[count++] = '\\'; + content[count++] = 'r'; + break; + case '\t': + content[count++] = '\\'; + content[count++] = 't'; + break; + case '\\': + content[count++] = '\\'; + content[count++] = ch; + break; + case '"': + content[count++] = '\\'; + content[count++] = ch; + break; + default: + content[count++] = ch; + break; + } + } + content[count++] = '"'; + } + + @Override + public String toString() { + return new String(content, 0, count); + } + + //---------------------------------------------------------------------------------------------- + @Override + public void writeBoolean(boolean value) { + writeTo(value ? CHARS_TUREVALUE : CHARS_FALSEVALUE); + } + + @Override + public void writeInt(int value) { + final char sign = value >= 0 ? 0 : '-'; + if (value < 0) value = -value; + int size; + for (int i = 0;; i++) { + if (value <= sizeTable[i]) { + size = i + 1; + break; + } + } + if (sign != 0) size++; //负数 + expand(size); + + int q, r; + int charPos = count + size; + + // Generate two digits per iteration + while (value >= 65536) { + q = value / 100; + // really: r = i - (q * 100); + r = value - ((q << 6) + (q << 5) + (q << 2)); + value = q; + content[--charPos] = DigitOnes[r]; + content[--charPos] = DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i <= 65536, i); + for (;;) { + q = (value * 52429) >>> (16 + 3); + r = value - ((q << 3) + (q << 1)); // r = i-(q*10) ... + content[--charPos] = digits[r]; + value = q; + if (value == 0) break; + } + if (sign != 0) content[--charPos] = sign; + count += size; + } + + @Override + public void writeLong(long value) { + final char sign = value >= 0 ? 0 : '-'; + if (value < 0) value = -value; + int size = 19; + long p = 10; + for (int i = 1; i < 19; i++) { + if (value < p) { + size = i; + break; + } + p = 10 * p; + } + if (sign != 0) size++; //负数 + expand(size); + + long q; + int r; + int charPos = count + size; + + // Get 2 digits/iteration using longs until quotient fits into an int + while (value > Integer.MAX_VALUE) { + q = value / 100; + // really: r = i - (q * 100); + r = (int) (value - ((q << 6) + (q << 5) + (q << 2))); + value = q; + content[--charPos] = DigitOnes[r]; + content[--charPos] = DigitTens[r]; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int) value; + while (i2 >= 65536) { + q2 = i2 / 100; + // really: r = i2 - (q * 100); + r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); + i2 = q2; + content[--charPos] = DigitOnes[r]; + content[--charPos] = DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i2 <= 65536, i2); + for (;;) { + q2 = (i2 * 52429) >>> (16 + 3); + r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... + content[--charPos] = digits[r]; + i2 = q2; + if (i2 == 0) break; + } + if (sign != 0) content[--charPos] = sign; + count += size; + } + +} diff --git a/src/org/redkale/convert/json/JsonConvert.java b/src/org/redkale/convert/json/JsonConvert.java index 9f13ab628..23c829007 100644 --- a/src/org/redkale/convert/json/JsonConvert.java +++ b/src/org/redkale/convert/json/JsonConvert.java @@ -30,13 +30,18 @@ public class JsonConvert extends TextConvert { public static final Type TYPE_RETRESULT_STRING = new TypeToken>() { }.getType(); - //private static final ObjectPool readerPool = JsonReader.createPool(Integer.getInteger("convert.json.pool.size", Integer.getInteger("convert.pool.size", 16))); - //private static final ObjectPool writerPool = JsonWriter.createPool(Integer.getInteger("convert.json.pool.size", Integer.getInteger("convert.pool.size", 16))); - // - private static final ThreadLocal writerPool = ThreadLocal.withInitial(JsonWriter::new); + private final ThreadLocal charsWriterPool = ThreadLocal.withInitial(JsonCharsWriter::new); + + private final ThreadLocal bytesWriterPool = ThreadLocal.withInitial(JsonBytesWriter::new); + + private final Consumer offerBytesConsumer = w -> offerJsonBytesWriter(w); private final boolean tiny; + private Encodeable lastConvertEncodeable; + + private Decodeable lastConvertDecodeable; + protected JsonConvert(JsonFactory factory, boolean tiny) { super(factory); this.tiny = tiny; @@ -66,45 +71,42 @@ public class JsonConvert extends TextConvert { }; } - //------------------------------ reader ----------------------------------------------------------- -// public JsonReader pollJsonReader(final ByteBuffer... buffers) { -// return new JsonByteBufferReader((ConvertMask) null, buffers); -// } -// -// public JsonReader pollJsonReader(final InputStream in) { -// return new JsonStreamReader(in); -// } -// -// public JsonReader pollJsonReader() { -// return readerPool.get(); -// } -// -// public void offerJsonReader(final JsonReader in) { -// if (in != null) readerPool.accept(in); -// } //------------------------------ writer ----------------------------------------------------------- - private JsonByteBufferWriter pollJsonWriter(final Supplier supplier) { - return configWrite(new JsonByteBufferWriter(tiny, supplier)); + private JsonCharsWriter pollJsonCharsWriter() { + JsonCharsWriter writer = charsWriterPool.get(); + if (writer == null) { + writer = new JsonCharsWriter(); + } else { + charsWriterPool.set(null); + } + return configWrite((JsonCharsWriter) writer.tiny(tiny)); } - private JsonWriter pollJsonWriter(final OutputStream out) { - return configWrite(new JsonStreamWriter(tiny, out)); + private JsonBytesWriter pollJsonBytesWriter() { + JsonBytesWriter writer = bytesWriterPool.get(); + if (writer == null) { + writer = new JsonBytesWriter(); + } else { + bytesWriterPool.set(null); + } + return configWrite((JsonBytesWriter) writer.tiny(tiny)); } -// -// public JsonWriter pollJsonWriter(final Charset charset, final OutputStream out) { -// return configWrite(new JsonStreamWriter(tiny, charset, out)); -// } -// - private JsonWriter pollJsonWriter() { - return configWrite(writerPool.get().tiny(tiny)); + private void offerJsonCharsWriter(final JsonCharsWriter writer) { + if (writer != null) { + writer.recycle(); + charsWriterPool.set(writer); + } } -// -// public void offerJsonWriter(final JsonWriter writer) { -// if (writer != null) writerPool.accept(writer); -// } + + private void offerJsonBytesWriter(final JsonBytesWriter writer) { + if (writer != null) { + writer.recycle(); + bytesWriterPool.set(writer); + } + } + //------------------------------ convertFrom ----------------------------------------------------------- - @Override public T convertFrom(final Type type, final byte[] bytes) { if (bytes == null) return null; @@ -129,34 +131,56 @@ public class JsonConvert extends TextConvert { public T convertFrom(final Type type, final char[] text, final int offset, final int length) { if (text == null || type == null) return null; - //final JsonReader in = readerPool.get(); - //in.setText(text, offset, length); - T rs = (T) factory.loadDecoder(type).convertFrom(new JsonReader(text, offset, length)); - //readerPool.accept(in); + Decodeable decoder = this.lastConvertDecodeable; + if (decoder == null || decoder.getType() != type) { + decoder = factory.loadDecoder(type); + this.lastConvertDecodeable = decoder; + } + T rs = (T) decoder.convertFrom(new JsonReader(text, offset, length)); return rs; } public T convertFrom(final Type type, final InputStream in) { if (type == null || in == null) return null; - return (T) factory.loadDecoder(type).convertFrom(new JsonStreamReader(in)); + Decodeable decoder = this.lastConvertDecodeable; + if (decoder == null || decoder.getType() != type) { + decoder = factory.loadDecoder(type); + this.lastConvertDecodeable = decoder; + } + return (T) decoder.convertFrom(new JsonStreamReader(in)); } @Override public T convertFrom(final Type type, final ByteBuffer... buffers) { if (type == null || buffers == null || buffers.length == 0) return null; - return (T) factory.loadDecoder(type).convertFrom(new JsonByteBufferReader((ConvertMask) null, buffers)); + Decodeable decoder = this.lastConvertDecodeable; + if (decoder == null || decoder.getType() != type) { + decoder = factory.loadDecoder(type); + this.lastConvertDecodeable = decoder; + } + return (T) decoder.convertFrom(new JsonByteBufferReader((ConvertMask) null, buffers)); } @Override public T convertFrom(final Type type, final ConvertMask mask, final ByteBuffer... buffers) { if (type == null || buffers == null || buffers.length == 0) return null; - return (T) factory.loadDecoder(type).convertFrom(new JsonByteBufferReader(mask, buffers)); + Decodeable decoder = this.lastConvertDecodeable; + if (decoder == null || decoder.getType() != type) { + decoder = factory.loadDecoder(type); + this.lastConvertDecodeable = decoder; + } + return (T) decoder.convertFrom(new JsonByteBufferReader(mask, buffers)); } public T convertFrom(final Type type, final JsonReader reader) { if (type == null) return null; + Decodeable decoder = this.lastConvertDecodeable; + if (decoder == null || decoder.getType() != type) { + decoder = factory.loadDecoder(type); + this.lastConvertDecodeable = decoder; + } @SuppressWarnings("unchecked") - T rs = (T) factory.loadDecoder(type).convertFrom(reader); + T rs = (T) decoder.convertFrom(reader); return rs; } @@ -217,73 +241,91 @@ public class JsonConvert extends TextConvert { public String convertTo(final Type type, final Object value) { if (type == null) return null; if (value == null) return "null"; - JsonWriter writer = pollJsonWriter(); - if (writer == null) { - writer = new JsonWriter(); - } else { - writerPool.set(null); + JsonCharsWriter writer = pollJsonCharsWriter(); + Encodeable encoder = this.lastConvertEncodeable; + if (encoder == null || encoder.getType() != type) { + encoder = factory.loadEncoder(type); + this.lastConvertEncodeable = encoder; } - writer.specify(type); - factory.loadEncoder(type).convertTo(writer, value); + if (encoder.specifyable()) writer.specify(type); + encoder.convertTo(writer, value); + String result = writer.toString(); - //writerPool.accept(writer); - if (writerPool.get() == null) { - writer.recycle(); - writerPool.set(writer); - } + offerJsonCharsWriter(writer); return result; } @Override public byte[] convertToBytes(final Object value) { if (value == null) return null; - String result = convertTo(value.getClass(), value); - return result == null ? null : result.getBytes(StandardCharsets.UTF_8); + return convertToBytes(value.getClass(), value); } @Override public byte[] convertToBytes(final Type type, final Object value) { if (type == null) return null; if (value == null) return null; - JsonWriter writer = pollJsonWriter(); - if (writer == null) { - writer = new JsonWriter(); - } else { - writerPool.set(null); + JsonBytesWriter writer = pollJsonBytesWriter(); + Encodeable encoder = this.lastConvertEncodeable; + if (encoder == null || encoder.getType() != type) { + encoder = factory.loadEncoder(type); + this.lastConvertEncodeable = encoder; } - writer.specify(type); - factory.loadEncoder(type).convertTo(writer, value); - String result = writer.toString(); - //writerPool.accept(writer); - if (writerPool.get() == null) { - writer.recycle(); - writerPool.set(writer); - } - return result == null ? null : result.getBytes(StandardCharsets.UTF_8); + if (encoder.specifyable()) writer.specify(type); + encoder.convertTo(writer, value); + + byte[] result = writer.toBytes(); + offerJsonBytesWriter(writer); + return result; } @Override - public String convertMapTo(final Object... values) { - if (values == null) return "null"; - JsonWriter writer = pollJsonWriter(); - if (writer == null) { - writer = new JsonWriter(); + public void convertToBytes(final Object value, final ConvertBytesHandler handler) { + convertToBytes(value == null ? null : value.getClass(), value, handler); + } + + @Override + public void convertToBytes(final Type type, final Object value, final ConvertBytesHandler handler) { + JsonBytesWriter writer = pollJsonBytesWriter(); + if (type == null) { + writer.writeNull(); } else { - writerPool.set(null); + Encodeable encoder = this.lastConvertEncodeable; + if (encoder == null || encoder.getType() != type) { + encoder = factory.loadEncoder(type); + this.lastConvertEncodeable = encoder; + } + if (encoder.specifyable()) writer.specify(type); + encoder.convertTo(writer, value); } - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(writer, values); - String result = writer.toString(); - //writerPool.accept(writer); - if (writerPool.get() == null) { - writer.recycle(); - writerPool.set(writer); + writer.completed(handler, offerBytesConsumer); + } + + @Override + public void convertToBytes(final ByteArray array, final Object value) { + convertToBytes(array, value == null ? null : value.getClass(), value); + } + + @Override + public void convertToBytes(final ByteArray array, final Type type, final Object value) { + JsonBytesWriter writer = configWrite(new JsonBytesWriter(tiny, array)); + if (type == null) { + writer.writeNull(); + } else { + Encodeable encoder = this.lastConvertEncodeable; + if (encoder == null || encoder.getType() != type) { + encoder = factory.loadEncoder(type); + this.lastConvertEncodeable = encoder; + } + if (encoder.specifyable()) writer.specify(type); + encoder.convertTo(writer, value); } - return result; + writer.directTo(array); } public void convertTo(final OutputStream out, final Object value) { if (value == null) { - pollJsonWriter(out).writeNull(); + configWrite(new JsonStreamWriter(tiny, out)).writeNull(); } else { convertTo(out, value.getClass(), value); } @@ -292,59 +334,23 @@ public class JsonConvert extends TextConvert { public void convertTo(final OutputStream out, final Type type, final Object value) { if (type == null) return; if (value == null) { - pollJsonWriter(out).writeNull(); + configWrite(new JsonStreamWriter(tiny, out)).writeNull(); } else { - JsonWriter writer = pollJsonWriter(); - if (writer == null) { - writer = new JsonWriter(); - } else { - writerPool.set(null); - } - writer.specify(type); - factory.loadEncoder(type).convertTo(writer, value); - byte[] bs = writer.toBytes(); - //writerPool.accept(writer); - if (writerPool.get() == null) { - writer.recycle(); - writerPool.set(writer); - } - try { - out.write(bs); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - public void convertMapTo(final OutputStream out, final Object... values) { - if (values == null) { - pollJsonWriter(out).writeNull(); - } else { - JsonWriter writer = pollJsonWriter(); - if (writer == null) { - writer = new JsonWriter(); - } else { - writerPool.set(null); - } - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(writer, values); - byte[] bs = writer.toBytes(); - //writerPool.accept(writer); - if (writerPool.get() == null) { - writer.recycle(); - writerPool.set(writer); - } - try { - out.write(bs); - } catch (IOException e) { - throw new RuntimeException(e); + JsonStreamWriter writer = configWrite(new JsonStreamWriter(tiny, out)); + Encodeable encoder = this.lastConvertEncodeable; + if (encoder == null || encoder.getType() != type) { + encoder = factory.loadEncoder(type); + this.lastConvertEncodeable = encoder; } + if (encoder.specifyable()) writer.specify(type); + encoder.convertTo(writer, value); } } @Override public ByteBuffer[] convertTo(final Supplier supplier, final Object value) { if (supplier == null) return null; - JsonByteBufferWriter out = pollJsonWriter(supplier); + JsonByteBufferWriter out = configWrite(new JsonByteBufferWriter(tiny, supplier)); if (value == null) { out.writeNull(); } else { @@ -356,7 +362,7 @@ public class JsonConvert extends TextConvert { @Override public ByteBuffer[] convertTo(final Supplier supplier, final Type type, final Object value) { if (supplier == null || type == null) return null; - JsonByteBufferWriter out = pollJsonWriter(supplier); + JsonByteBufferWriter out = configWrite(new JsonByteBufferWriter(tiny, supplier)); if (value == null) { out.writeNull(); } else { @@ -366,29 +372,6 @@ public class JsonConvert extends TextConvert { return out.toBuffers(); } - @Override - public byte[] convertMapToBytes(final Object... values) { - JsonWriter out = pollJsonWriter(); - if (values == null) { - out.writeNull(); - } else { - ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(out, values); - } - return out.toBytes(); - } - - @Override - public ByteBuffer[] convertMapTo(final Supplier supplier, final Object... values) { - if (supplier == null) return null; - JsonByteBufferWriter out = pollJsonWriter(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(); @@ -407,40 +390,4 @@ public class JsonConvert extends TextConvert { } } - 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); -// } -// -// public JsonWriter convertToWriter(final Type type, final Object value) { -// if (type == null) return null; -// JsonWriter writer = writerPool.get();// pollJsonWriter(); -// if (writer == null) { -// writer = new JsonWriter(); -// } else { -// writerPool.set(null); -// } -// writer.specify(type); -// factory.loadEncoder(type).convertTo(writer, value); -// return writer; -// } -// -// public JsonWriter convertMapToWriter(final Object... values) { -// JsonWriter writer = writerPool.get();// pollJsonWriter(); -// if (writer == null) { -// writer = new JsonWriter(); -// } else { -// writerPool.set(null); -// } -// ((AnyEncoder) factory.getAnyEncoder()).convertMapTo(writer, values); -// return writer; -// } } diff --git a/src/org/redkale/convert/json/JsonDynEncoder.java b/src/org/redkale/convert/json/JsonDynEncoder.java new file mode 100644 index 000000000..71d71db9f --- /dev/null +++ b/src/org/redkale/convert/json/JsonDynEncoder.java @@ -0,0 +1,559 @@ +/* + * 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.json; + +import java.lang.reflect.*; +import java.lang.reflect.Type; +import java.util.*; +import org.redkale.asm.*; +import static org.redkale.asm.ClassWriter.COMPUTE_FRAMES; +import static org.redkale.asm.Opcodes.*; +import org.redkale.convert.*; +import org.redkale.convert.ext.*; + +/** + * 简单对象的JSON序列化操作类 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + * + * @param 序列化的数据类型 + */ +@SuppressWarnings("unchecked") +public abstract class JsonDynEncoder implements Encodeable { + + protected final Class typeClass; + + protected final ObjectEncoder objectEncoder; + + protected JsonDynEncoder(final JsonFactory factory, Type type) { + this.typeClass = (Class) type; + factory.register(type, this); + this.objectEncoder = factory.createObjectEncoder(type); + } + + @Override + public boolean specifyable() { + return false; + } + + private static boolean checkMemberType(final JsonFactory factory, Type type, Class clazz) { + if (type == String.class) return true; + if (clazz.isPrimitive()) return true; + if (clazz.isEnum()) return true; + if (type == boolean[].class) return true; + if (type == byte[].class) return true; + if (type == short[].class) return true; + if (type == char[].class) return true; + if (type == int[].class) return true; + if (type == float[].class) return true; + if (type == long[].class) return true; + if (type == double[].class) return true; + if (type == Boolean[].class) return true; + if (type == Byte[].class) return true; + if (type == Short[].class) return true; + if (type == Character[].class) return true; + if (type == Integer[].class) return true; + if (type == Float[].class) return true; + if (type == Long[].class) return true; + if (type == Double[].class) return true; + if (type == String[].class) return true; + if (Collection.class.isAssignableFrom(clazz) && type instanceof ParameterizedType) { + Type[] ts = ((ParameterizedType) type).getActualTypeArguments(); + if (ts.length == 1) { + Type t = ts[0]; + if (t == Boolean.class || t == Byte.class || t == Short.class || t == Character.class + || t == Integer.class || t == Float.class || t == Long.class || t == Double.class + || t == String.class || ((t instanceof Class) && ((Class) t).isEnum())) return true; + if (factory.loadEncoder(t) instanceof JsonDynEncoder) return true; + } + } + if (factory.loadEncoder(type) instanceof JsonDynEncoder) return true; + return false; + } + + //字段全部是primitive或String类型,且没有泛型的类才能动态生成JsonDynEncoder, 不支持的返回null + public static JsonDynEncoder createDyncEncoder(final JsonFactory factory, final Type type) { + if (!(type instanceof Class)) return null; + //发现有自定义的基础数据类型Encoder就不动态生成JsonDynEncoder了 + if (factory.loadEncoder(boolean.class) != BoolSimpledCoder.instance) return null; + if (factory.loadEncoder(byte.class) != ByteSimpledCoder.instance) return null; + if (factory.loadEncoder(short.class) != ShortSimpledCoder.instance) return null; + if (factory.loadEncoder(char.class) != CharSimpledCoder.instance) return null; + if (factory.loadEncoder(int.class) != IntSimpledCoder.instance) return null; + if (factory.loadEncoder(float.class) != FloatSimpledCoder.instance) return null; + if (factory.loadEncoder(long.class) != LongSimpledCoder.instance) return null; + if (factory.loadEncoder(double.class) != DoubleSimpledCoder.instance) return null; + if (factory.loadEncoder(String.class) != StringSimpledCoder.instance) return null; + //array + if (factory.loadEncoder(boolean[].class) != BoolArraySimpledCoder.instance) return null; + if (factory.loadEncoder(byte[].class) != ByteArraySimpledCoder.instance) return null; + if (factory.loadEncoder(short[].class) != ShortArraySimpledCoder.instance) return null; + if (factory.loadEncoder(char[].class) != CharArraySimpledCoder.instance) return null; + if (factory.loadEncoder(int[].class) != IntArraySimpledCoder.instance) return null; + if (factory.loadEncoder(float[].class) != FloatArraySimpledCoder.instance) return null; + if (factory.loadEncoder(long[].class) != LongArraySimpledCoder.instance) return null; + if (factory.loadEncoder(double[].class) != DoubleArraySimpledCoder.instance) return null; + if (factory.loadEncoder(String[].class) != StringArraySimpledCoder.instance) return null; + + final Class clazz = (Class) type; + List members = null; + Set names = new HashSet<>(); + try { + ConvertColumnEntry ref; + for (final Field field : clazz.getFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + if (factory.isConvertDisabled(field)) continue; + ref = factory.findRef(clazz, field); + if (ref != null && ref.ignore()) continue; + if (!(checkMemberType(factory, field.getGenericType(), field.getType()))) return null; + String name = convertFieldName(factory, clazz, field); + if (names.contains(name)) continue; + names.add(name); + if (members == null) members = new ArrayList<>(); + members.add(field); + } + for (final Method method : clazz.getMethods()) { + if (Modifier.isStatic(method.getModifiers())) continue; + if (Modifier.isAbstract(method.getModifiers())) continue; + if (method.isSynthetic()) continue; + if (method.getName().length() < 3) continue; + if (method.getName().equals("getClass")) continue; + if (!method.getName().startsWith("is") && !method.getName().startsWith("get")) continue; + if (factory.isConvertDisabled(method)) continue; + if (method.getParameterTypes().length != 0) continue; + if (method.getReturnType() == void.class) continue; + ref = factory.findRef(clazz, method); + if (ref != null && ref.ignore()) continue; + if (!(checkMemberType(factory, method.getGenericReturnType(), method.getReturnType()))) return null; + String name = convertFieldName(factory, clazz, method); + if (names.contains(name)) continue; + names.add(name); + if (members == null) members = new ArrayList<>(); + members.add(method); + } + if (members == null) return null; + Collections.sort(members, (o1, o2) -> { + ConvertColumnEntry ref1 = factory.findRef(clazz, o1); + ConvertColumnEntry ref2 = factory.findRef(clazz, o2); + if ((ref1 != null && ref1.getIndex() > 0) || (ref2 != null && ref2.getIndex() > 0)) { + int idx1 = ref1 == null ? Integer.MAX_VALUE / 2 : ref1.getIndex(); + int idx2 = ref2 == null ? Integer.MAX_VALUE / 2 : ref2.getIndex(); + if (idx1 != idx2) return idx1 - idx2; + } + String n1 = ref1 == null || ref1.name().isEmpty() ? readGetSetFieldName(o1) : ref1.name(); + String n2 = ref2 == null || ref2.name().isEmpty() ? readGetSetFieldName(o2) : ref2.name(); + return n1.compareTo(n2); + }); + return generateDyncEncoder(factory, clazz, members); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + protected static String convertFieldName(final JsonFactory factory, Class clazz, AccessibleObject element) { + ConvertColumnEntry ref = factory.findRef(clazz, element); + String name = ref == null || ref.name().isEmpty() ? readGetSetFieldName(element) : ref.name(); + return name; + } + + protected static ConvertSmallString readConvertSmallString(AccessibleObject element) { + if (element instanceof Field) return ((Field) element).getAnnotation(ConvertSmallString.class); + Method method = (Method) element; + ConvertSmallString small = method.getAnnotation(ConvertSmallString.class); + if (small == null) { + try { + Field f = method.getDeclaringClass().getDeclaredField(readGetSetFieldName(method)); + if (f != null) small = f.getAnnotation(ConvertSmallString.class); + } catch (Exception e) { + } + } + return small; + } + + protected static Class readGetSetFieldType(AccessibleObject element) { + if (element instanceof Field) return ((Field) element).getType(); + return element == null ? null : ((Method) element).getReturnType(); + } + + protected static String readGetSetFieldName(AccessibleObject element) { + if (element instanceof Field) return ((Field) element).getName(); + Method method = (Method) element; + if (method == null) return null; + String fname = method.getName(); + if (!fname.startsWith("is") && !fname.startsWith("get") && !fname.startsWith("set")) return fname; + fname = fname.substring(fname.startsWith("is") ? 2 : 3); + if (fname.length() > 1 && !(fname.charAt(1) >= 'A' && fname.charAt(1) <= 'Z')) { + fname = Character.toLowerCase(fname.charAt(0)) + fname.substring(1); + } else if (fname.length() == 1) { + fname = "" + Character.toLowerCase(fname.charAt(0)); + } + return fname; + } + + protected static JsonDynEncoder generateDyncEncoder(final JsonFactory factory, final Class clazz, final List members) { + final String supDynName = JsonDynEncoder.class.getName().replace('.', '/'); + final String valtypeName = clazz.getName().replace('.', '/'); + final String writerName = JsonWriter.class.getName().replace('.', '/'); + final String encodeableName = Encodeable.class.getName().replace('.', '/'); + final String objEncoderName = ObjectEncoder.class.getName().replace('.', '/'); + final String typeDesc = org.redkale.asm.Type.getDescriptor(Type.class); + final String jsonfactoryDesc = org.redkale.asm.Type.getDescriptor(JsonFactory.class); + final String jsonwriterDesc = org.redkale.asm.Type.getDescriptor(JsonWriter.class); + final String writerDesc = org.redkale.asm.Type.getDescriptor(Writer.class); + final String encodeableDesc = org.redkale.asm.Type.getDescriptor(Encodeable.class); + final String objEncoderDesc = org.redkale.asm.Type.getDescriptor(ObjectEncoder.class); + final String valtypeDesc = org.redkale.asm.Type.getDescriptor(clazz); + + String newDynName = supDynName + "_Dyn" + clazz.getSimpleName(); + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (String.class.getClassLoader() != clazz.getClassLoader()) { + loader = clazz.getClassLoader(); + newDynName = valtypeName + "_" + JsonDynEncoder.class.getSimpleName(); + } + try { + return (JsonDynEncoder) loader.loadClass(newDynName.replace('/', '.')).getDeclaredConstructor().newInstance(); + } catch (Throwable ex) { + } + // ------------------------------------------------------------------------------ + ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, "L" + supDynName + "<" + valtypeDesc + ">;", supDynName, null); + Map mixedNames = null; + for (AccessibleObject element : members) { + ConvertColumnEntry ref1 = factory.findRef(clazz, element); + final String fieldname = ref1 == null || ref1.name().isEmpty() ? readGetSetFieldName(element) : ref1.name(); + fv = cw.visitField(ACC_PROTECTED + ACC_FINAL, fieldname + "FieldBytes", "[B", null, null); + fv.visitEnd(); + fv = cw.visitField(ACC_PROTECTED + ACC_FINAL, fieldname + "CommaFieldBytes", "[B", null, null); + fv.visitEnd(); + final Class fieldtype = readGetSetFieldType(element); + if (fieldtype != String.class && !fieldtype.isPrimitive()) { + if (mixedNames == null) mixedNames = new HashMap<>(); + mixedNames.put(fieldname, element); + fv = cw.visitField(ACC_PROTECTED, fieldname + "Encoder", encodeableDesc, null, null); + fv.visitEnd(); + } + } + + { // 构造函数 + mv = (cw.visitMethod(ACC_PUBLIC, "", "(" + jsonfactoryDesc + typeDesc + ")V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKESPECIAL, supDynName, "", "(" + jsonfactoryDesc + typeDesc + ")V", false); + + for (AccessibleObject element : members) { + ConvertColumnEntry ref1 = factory.findRef(clazz, element); + final String fieldname = ref1 == null || ref1.name().isEmpty() ? readGetSetFieldName(element) : ref1.name(); + //xxxFieldBytes + mv.visitVarInsn(ALOAD, 0); + mv.visitLdcInsn("\"" + fieldname + "\":"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "getBytes", "()[B", false); + mv.visitFieldInsn(PUTFIELD, newDynName, fieldname + "FieldBytes", "[B"); + //xxxCommaFieldBytes + mv.visitVarInsn(ALOAD, 0); + mv.visitLdcInsn(",\"" + fieldname + "\":"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "getBytes", "()[B", false); + mv.visitFieldInsn(PUTFIELD, newDynName, fieldname + "CommaFieldBytes", "[B"); + } + mv.visitInsn(RETURN); + mv.visitMaxs(1 + members.size(), 1 + members.size()); + mv.visitEnd(); + } + + { + mv = (cw.visitMethod(ACC_PUBLIC, "convertTo", "(" + jsonwriterDesc + valtypeDesc + ")V", null, null)); + //mv.setDebug(true); + { //if (value == null) { out.writeObjectNull(null); return; } + mv.visitVarInsn(ALOAD, 2); + Label valif = new Label(); + mv.visitJumpInsn(IFNONNULL, valif); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeObjectNull", "(Ljava/lang/Class;)V", false); + mv.visitInsn(RETURN); + mv.visitLabel(valif); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } + { //if (!out.isExtFuncEmpty()) { objectEncoder.convertTo(out, value); return; } + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "isExtFuncEmpty", "()Z", false); + Label extif = new Label(); + mv.visitJumpInsn(IFNE, extif); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "objectEncoder", objEncoderDesc); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, objEncoderName, "convertTo", "(" + org.redkale.asm.Type.getDescriptor(Writer.class) + "Ljava/lang/Object;)V", false); + mv.visitInsn(RETURN); + mv.visitLabel(extif); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } + { //out.writeTo('{'); + mv.visitVarInsn(ALOAD, 1); + mv.visitIntInsn(BIPUSH, '{'); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeTo", "(B)V", false); + } + + int maxLocals = 4; + int elementIndex = -1; + final Class firstType = readGetSetFieldType(members.get(0)); + final boolean mustHadComma = firstType.isPrimitive() && (firstType != boolean.class || !factory.tiny()); //byte/short/char/int/float/long/double + if (!mustHadComma) { //boolean comma = false; + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ISTORE, 3); + } + for (AccessibleObject element : members) { + elementIndex++; + ConvertColumnEntry ref1 = factory.findRef(clazz, element); + final String fieldname = ref1 == null || ref1.name().isEmpty() ? readGetSetFieldName(element) : ref1.name(); + final Class fieldtype = readGetSetFieldType(element); + int storeid = ASTORE; + int loadid = ALOAD; + { //String message = value.getMessage(); + mv.visitVarInsn(ALOAD, 2); //加载 value + if (element instanceof Field) { + mv.visitFieldInsn(GETFIELD, valtypeName, ((Field) element).getName(), org.redkale.asm.Type.getDescriptor(fieldtype)); + } else { + mv.visitMethodInsn(INVOKEVIRTUAL, valtypeName, ((Method) element).getName(), "()" + org.redkale.asm.Type.getDescriptor(fieldtype), false); + } + if (fieldtype == boolean.class) { + storeid = ISTORE; + loadid = ILOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == byte.class) { + storeid = ISTORE; + loadid = ILOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == short.class) { + storeid = ISTORE; + loadid = ILOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == char.class) { + storeid = ISTORE; + loadid = ILOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == int.class) { + storeid = ISTORE; + loadid = ILOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == float.class) { + storeid = FSTORE; + loadid = FLOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == long.class) { + storeid = LSTORE; + loadid = LLOAD; + mv.visitVarInsn(storeid, maxLocals); + } else if (fieldtype == double.class) { + storeid = DSTORE; + loadid = DLOAD; + mv.visitVarInsn(storeid, maxLocals); + } else { + //storeid = ASTORE; + //loadid = ALOAD; + mv.visitVarInsn(storeid, maxLocals); + } + } + Label msgnotemptyif = null; + if (!fieldtype.isPrimitive()) { //if (message != null) { start + mv.visitVarInsn(loadid, maxLocals); + msgnotemptyif = new Label(); + mv.visitJumpInsn(IFNULL, msgnotemptyif); + if (factory.tiny() && fieldtype == String.class) { + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "isEmpty", "()Z", false); + mv.visitJumpInsn(IFNE, msgnotemptyif); + } + } else if (fieldtype == boolean.class && factory.tiny()) { + mv.visitVarInsn(loadid, maxLocals); + msgnotemptyif = new Label(); + mv.visitJumpInsn(IFEQ, msgnotemptyif); + } + if (mustHadComma) { //第一个字段必然会写入 + if (elementIndex == 0) { //第一个 + //out.writeTo(messageFieldBytes); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, fieldname + "FieldBytes", "[B"); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeTo", "([B)V", false); + } else { + //out.writeTo(messageCommaFieldBytes); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, fieldname + "CommaFieldBytes", "[B"); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeTo", "([B)V", false); + } + } else { //if(comma) {} else {} 代码块 + //if (comma) { start + mv.visitVarInsn(ILOAD, 3); + Label commaif = new Label(); + mv.visitJumpInsn(IFEQ, commaif); + + //out.writeTo(messageCommaFieldBytes); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, fieldname + "CommaFieldBytes", "[B"); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeTo", "([B)V", false); + + Label commaelse = new Label(); + mv.visitJumpInsn(GOTO, commaelse); + mv.visitLabel(commaif); + if (fieldtype == boolean.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); + } else if (fieldtype == byte.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); + } else if (fieldtype == short.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); + } else if (fieldtype == char.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); + } else if (fieldtype == int.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); + } else if (fieldtype == float.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.FLOAT}, 0, null); + } else if (fieldtype == long.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.LONG}, 0, null); + } else if (fieldtype == double.class) { + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.DOUBLE}, 0, null); + } else { + mv.visitFrame(Opcodes.F_APPEND, 2, new Object[]{Opcodes.INTEGER, "java/lang/String"}, 0, null); // } else { comma + } + //out.writeTo(messageFieldBytes); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, fieldname + "FieldBytes", "[B"); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeTo", "([B)V", false); + //comma = true; + mv.visitInsn(ICONST_1); + mv.visitVarInsn(ISTORE, 3); + mv.visitLabel(commaelse); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); //if (comma) } end + } + //out.writeString(message); + if (fieldtype == boolean.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeBoolean", "(Z)V", false); + } else if (fieldtype == byte.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeByte", "(B)V", false); + } else if (fieldtype == short.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeShort", "(S)V", false); + } else if (fieldtype == char.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeChar", "(C)V", false); + } else if (fieldtype == int.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeInt", "(I)V", false); + } else if (fieldtype == float.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeFloat", "(F)V", false); + } else if (fieldtype == long.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeLong", "(J)V", false); + } else if (fieldtype == double.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeDouble", "(D)V", false); + } else if (fieldtype == String.class) { + if (readConvertSmallString(element) == null) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeString", "(Ljava/lang/String;)V", false); + } else { + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ICONST_1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeLatin1To", "(ZLjava/lang/String;)V", false); + } + } else { //int[],Boolean[],String[] + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, fieldname + "Encoder", encodeableDesc); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(loadid, maxLocals); + mv.visitMethodInsn(INVOKEINTERFACE, encodeableName, "convertTo", "(" + writerDesc + "Ljava/lang/Object;)V", true); + } + if (!fieldtype.isPrimitive()) { //if (message != null) } end + mv.visitLabel(msgnotemptyif); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } else if (fieldtype == boolean.class && factory.tiny()) { + mv.visitLabel(msgnotemptyif); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } + if (fieldtype == long.class || fieldtype == double.class) { + maxLocals += 2; + } else { + maxLocals++; + } + } + { //out.writeTo('}'); + mv.visitVarInsn(ALOAD, 1); + mv.visitIntInsn(BIPUSH, '}'); + mv.visitMethodInsn(INVOKEVIRTUAL, writerName, "writeTo", "(B)V", false); + } + mv.visitInsn(RETURN); + mv.visitMaxs(maxLocals, maxLocals); + mv.visitEnd(); + } + { + mv = (cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "convertTo", "(" + jsonwriterDesc + "Ljava/lang/Object;)V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, valtypeName); + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "convertTo", "(" + jsonwriterDesc + valtypeDesc + ")V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + cw.visitEnd(); + // ------------------------------------------------------------------------------ + byte[] bytes = cw.toByteArray(); + Class creatorClazz = new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + JsonDynEncoder resultEncoder = (JsonDynEncoder) creatorClazz.getDeclaredConstructor(JsonFactory.class, Type.class).newInstance(factory, clazz); + if (mixedNames != null) { + for (Map.Entry en : mixedNames.entrySet()) { + Field f = creatorClazz.getDeclaredField(en.getKey() + "Encoder"); + f.setAccessible(true); + f.set(resultEncoder, factory.loadEncoder(en.getValue() instanceof Field ? ((Field) en.getValue()).getGenericType() : ((Method) en.getValue()).getGenericReturnType())); + } + } + return resultEncoder; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public abstract void convertTo(JsonWriter out, T value); + + @Override + public Type getType() { + return typeClass; + } +} diff --git a/src/org/redkale/convert/json/JsonFactory.java b/src/org/redkale/convert/json/JsonFactory.java index b8f67832e..9ebecb630 100644 --- a/src/org/redkale/convert/json/JsonFactory.java +++ b/src/org/redkale/convert/json/JsonFactory.java @@ -6,6 +6,7 @@ package org.redkale.convert.json; import java.io.Serializable; +import java.lang.reflect.*; import java.math.BigInteger; import java.net.*; import org.redkale.convert.*; @@ -26,7 +27,6 @@ public final class JsonFactory extends ConvertFactory { private static final JsonFactory instance = new JsonFactory(null, getSystemPropertyBoolean("convert.json.tiny", "convert.tiny", true)); static { - instance.register(Serializable.class, instance.loadEncoder(Object.class)); instance.register(AnyValue.class, instance.loadDecoder(AnyValue.DefaultAnyValue.class)); @@ -63,6 +63,20 @@ public final class JsonFactory extends ConvertFactory { return new JsonFactory(null, getSystemPropertyBoolean("convert.json.tiny", "convert.tiny", true)); } + @Override + protected Encodeable createDyncEncoder(Type type) { + return JsonDynEncoder.createDyncEncoder(this, type); + } + + @Override + protected ObjectEncoder createObjectEncoder(Type type) { + return super.createObjectEncoder(type); + } + + protected boolean tiny() { + return this.tiny; + } + @Override public final JsonConvert getConvert() { if (convert == null) convert = new JsonConvert(this, tiny); diff --git a/src/org/redkale/convert/json/JsonReader.java b/src/org/redkale/convert/json/JsonReader.java index e34beaf5e..17c4683b7 100644 --- a/src/org/redkale/convert/json/JsonReader.java +++ b/src/org/redkale/convert/json/JsonReader.java @@ -28,7 +28,6 @@ public class JsonReader extends Reader { // public static ObjectPool createPool(int max) { // return new ObjectPool<>(max, (Object... params) -> new JsonReader(), null, JsonReader::recycle); // } - public JsonReader() { } @@ -395,13 +394,21 @@ public class JsonReader extends Reader { if (firstchar < '0' || firstchar > '9') throw new ConvertException("illegal escape(" + firstchar + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); value = firstchar - '0'; } + boolean dot = false; for (;;) { if (currpos == eof) break; char ch = text0[++currpos]; int val = digits[ch]; if (quote && val == -3) continue; if (val <= -3) break; - if (val == -1) throw new ConvertException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + if (dot) continue; + if (val == -1) { + if (ch == '.') { + dot = true; + continue; + } + throw new ConvertException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + } if (val != -2) value = value * 10 + val; } this.position = currpos - 1; @@ -446,13 +453,21 @@ public class JsonReader extends Reader { if (firstchar < '0' || firstchar > '9') throw new ConvertException("illegal escape(" + firstchar + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); value = firstchar - '0'; } + boolean dot = false; for (;;) { if (currpos == eof) break; char ch = text0[++currpos]; int val = digits[ch]; if (quote && val == -3) continue; if (val <= -3) break; - if (val == -1) throw new ConvertException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + if (dot) continue; + if (val == -1) { + if (ch == '.') { + dot = true; + continue; + } + throw new ConvertException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + } if (val != -2) value = value * 10 + val; } this.position = currpos - 1; diff --git a/src/org/redkale/convert/json/JsonStreamWriter.java b/src/org/redkale/convert/json/JsonStreamWriter.java index 563a37ed2..ad989ebc5 100644 --- a/src/org/redkale/convert/json/JsonStreamWriter.java +++ b/src/org/redkale/convert/json/JsonStreamWriter.java @@ -14,7 +14,7 @@ import org.redkale.util.*; /** * * 详情见: https://redkale.org - * + * * @author zhangjx */ class JsonStreamWriter extends JsonByteBufferWriter { @@ -64,6 +64,13 @@ class JsonStreamWriter extends JsonByteBufferWriter { } else if (c < 0x800) { out.write((byte) (0xc0 | (c >> 6))); out.write((byte) (0x80 | (c & 0x3f))); + } else if (Character.isSurrogate(c)) { //连取两个 + int uc = Character.toCodePoint(c, chs[i + 1]); + out.write((byte) (0xf0 | ((uc >> 18)))); + out.write((byte) (0x80 | ((uc >> 12) & 0x3f))); + out.write((byte) (0x80 | ((uc >> 6) & 0x3f))); + out.write((byte) (0x80 | (uc & 0x3f))); + i++; } else { out.write((byte) (0xe0 | ((c >> 12)))); out.write((byte) (0x80 | ((c >> 6) & 0x3f))); diff --git a/src/org/redkale/convert/json/JsonWriter.java b/src/org/redkale/convert/json/JsonWriter.java index 5850caf37..86bd8313b 100644 --- a/src/org/redkale/convert/json/JsonWriter.java +++ b/src/org/redkale/convert/json/JsonWriter.java @@ -6,7 +6,6 @@ package org.redkale.convert.json; import java.lang.reflect.Type; -import java.nio.ByteBuffer; import org.redkale.convert.*; import org.redkale.util.*; @@ -18,32 +17,12 @@ import org.redkale.util.*; * * @author zhangjx */ -public class JsonWriter extends Writer { +public abstract class JsonWriter extends Writer { - private static final char[] CHARS_TUREVALUE = "true".toCharArray(); - - private static final char[] CHARS_FALSEVALUE = "false".toCharArray(); - - private static final int defaultSize = Integer.getInteger("convert.json.writer.buffer.defsize", Integer.getInteger("convert.writer.buffer.defsize", 1024)); - - private int count; - - private char[] content; + protected static final int defaultSize = Integer.getInteger("convert.json.writer.buffer.defsize", Integer.getInteger("convert.writer.buffer.defsize", 1024)); protected boolean tiny; -// public static ObjectPool createPool(int max) { -// return new ObjectPool<>(max, (Object... params) -> new JsonWriter(), null, JsonWriter::recycle); -// } - - public JsonWriter() { - this(defaultSize); - } - - public JsonWriter(int size) { - this.content = new char[size > 128 ? size : 128]; - } - @Override public boolean tiny() { return tiny; @@ -54,34 +33,18 @@ public class JsonWriter extends Writer { return this; } - //----------------------------------------------------------------------- - //----------------------------------------------------------------------- - /** - * 返回指定至少指定长度的缓冲区 - * - * @param len - * - * @return - */ - private char[] expand(int len) { - int newcount = count + len; - if (newcount <= content.length) return content; - char[] newdata = new char[Math.max(content.length * 3 / 2, newcount)]; - System.arraycopy(content, 0, newdata, 0, count); - this.content = newdata; - return newdata; + public boolean isExtFuncEmpty() { + return this.objExtFunc == null && this.objFieldFunc == null; } - public void writeTo(final char ch) { //只能是 0 - 127 的字符 - expand(1); - content[count++] = ch; - } + //----------------------------------------------------------------------- + public abstract void writeTo(final char ch); //只能是 0 - 127 的字符 - public void writeTo(final char[] chs, final int start, final int len) { //只能是 0 - 127 的字符 - expand(len); - System.arraycopy(chs, start, content, count, len); - count += len; - } + public abstract void writeTo(final char[] chs, final int start, final int len); //只能是 0 - 127 的字符 + + public abstract void writeTo(final byte ch); //只能是 0 - 127 的字符 + + public abstract void writeTo(final byte[] chs, final int start, final int len); //只能是 0 - 127 的字符 /** * 注意: 该String值不能为null且不会进行转义, 只用于不含需要转义字符的字符串,例如enum、double、BigInteger转换的String @@ -89,81 +52,29 @@ public class JsonWriter extends Writer { * @param quote 是否加双引号 * @param value 非null且不含需要转义的字符的String值 */ - public void writeLatin1To(final boolean quote, final String value) { - int len = value.length(); - expand(len + (quote ? 2 : 0)); - if (quote) content[count++] = '"'; - value.getChars(0, len, content, count); - count += len; - if (quote) content[count++] = '"'; - } + public abstract void writeLatin1To(final boolean quote, final String value); @Override - protected boolean recycle() { - super.recycle(); - this.count = 0; - this.specify = null; - if (this.content != null && this.content.length > defaultSize) { - this.content = new char[defaultSize]; - } - return true; - } - - public ByteBuffer[] toBuffers() { - return new ByteBuffer[]{ByteBuffer.wrap(Utility.encodeUTF8(content, 0, count))}; - } - - public byte[] toBytes() { - return Utility.encodeUTF8(content, 0, count); - } - - public int count() { - return this.count; - } + public abstract void writeBoolean(boolean value); @Override - public void writeString(String value) { - if (value == null) { - writeNull(); - return; - } - expand(value.length() * 2 + 2); - content[count++] = '"'; - for (char ch : Utility.charArray(value)) { - switch (ch) { - case '\n': - content[count++] = '\\'; - content[count++] = 'n'; - break; - case '\r': - content[count++] = '\\'; - content[count++] = 'r'; - break; - case '\t': - content[count++] = '\\'; - content[count++] = 't'; - break; - case '\\': - content[count++] = '\\'; - content[count++] = ch; - break; - case '"': - content[count++] = '\\'; - content[count++] = ch; - break; - default: - content[count++] = ch; - break; - } - } - content[count++] = '"'; - } + public abstract void writeInt(int value); @Override - public final void writeFieldName(String fieldName, Type fieldType, int fieldPos) { + public abstract void writeLong(long value); + + @Override + public abstract void writeString(String value); + + @Override //只容许JsonBytesWriter重写此方法 + public void writeFieldName(EnMember member, String fieldName, Type fieldType, int fieldPos) { if (this.comma) writeTo(','); - writeLatin1To(true, fieldName); - writeTo(':'); + if (member != null) { + writeTo(member.getJsonFieldNameChars()); + } else { + writeLatin1To(true, fieldName); + writeTo(':'); + } } @Override @@ -171,26 +82,20 @@ public class JsonWriter extends Writer { writeLatin1To(true, value); } - @Override - public String toString() { - return new String(content, 0, count); - } - //---------------------------------------------------------------------------------------------- public final void writeTo(final char... chs) { //只能是 0 - 127 的字符 writeTo(chs, 0, chs.length); } - @Override - public final void writeBoolean(boolean value) { - writeTo(value ? CHARS_TUREVALUE : CHARS_FALSEVALUE); - } - @Override public final void writeByte(byte value) { writeInt(value); } + public final void writeTo(final byte[] chs) { //只能是 0 - 127 的字符 + writeTo(chs, 0, chs.length); + } + @Override public final void writeByteArray(byte[] values) { if (values == null) { @@ -217,101 +122,6 @@ public class JsonWriter extends Writer { writeInt(value); } - @Override - public void writeInt(int value) { - final char sign = value >= 0 ? 0 : '-'; - if (value < 0) value = -value; - int size; - for (int i = 0;; i++) { - if (value <= sizeTable[i]) { - size = i + 1; - break; - } - } - if (sign != 0) size++; //负数 - expand(size); - - int q, r; - int charPos = count + size; - - // Generate two digits per iteration - while (value >= 65536) { - q = value / 100; - // really: r = i - (q * 100); - r = value - ((q << 6) + (q << 5) + (q << 2)); - value = q; - content[--charPos] = DigitOnes[r]; - content[--charPos] = DigitTens[r]; - } - - // Fall thru to fast mode for smaller numbers - // assert(i <= 65536, i); - for (;;) { - q = (value * 52429) >>> (16 + 3); - r = value - ((q << 3) + (q << 1)); // r = i-(q*10) ... - content[--charPos] = digits[r]; - value = q; - if (value == 0) break; - } - if (sign != 0) content[--charPos] = sign; - count += size; - } - - @Override - public void writeLong(long value) { - final char sign = value >= 0 ? 0 : '-'; - if (value < 0) value = -value; - int size = 19; - long p = 10; - for (int i = 1; i < 19; i++) { - if (value < p) { - size = i; - break; - } - p = 10 * p; - } - if (sign != 0) size++; //负数 - expand(size); - - long q; - int r; - int charPos = count + size; - - // Get 2 digits/iteration using longs until quotient fits into an int - while (value > Integer.MAX_VALUE) { - q = value / 100; - // really: r = i - (q * 100); - r = (int) (value - ((q << 6) + (q << 5) + (q << 2))); - value = q; - content[--charPos] = DigitOnes[r]; - content[--charPos] = DigitTens[r]; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int) value; - while (i2 >= 65536) { - q2 = i2 / 100; - // really: r = i2 - (q * 100); - r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); - i2 = q2; - content[--charPos] = DigitOnes[r]; - content[--charPos] = DigitTens[r]; - } - - // Fall thru to fast mode for smaller numbers - // assert(i2 <= 65536, i2); - for (;;) { - q2 = (i2 * 52429) >>> (16 + 3); - r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... - content[--charPos] = digits[r]; - i2 = q2; - if (i2 == 0) break; - } - if (sign != 0) content[--charPos] = sign; - count += size; - } - @Override public final void writeFloat(float value) { writeLatin1To(false, String.valueOf(value)); diff --git a/src/org/redkale/mq/HttpMessageClusterClient.java b/src/org/redkale/mq/HttpMessageClusterClient.java index 01f01628c..c4f1a3032 100644 --- a/src/org/redkale/mq/HttpMessageClusterClient.java +++ b/src/org/redkale/mq/HttpMessageClusterClient.java @@ -7,13 +7,14 @@ package org.redkale.mq; import java.net.*; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.*; import java.util.logging.Level; +import javax.annotation.Resource; import org.redkale.cluster.ClusterAgent; import org.redkale.net.http.*; +import org.redkale.util.Utility; /** * 没有配置MQ的情况下依赖ClusterAgent实现的默认HttpMessageClient实例 @@ -28,19 +29,21 @@ import org.redkale.net.http.*; public class HttpMessageClusterClient extends HttpMessageClient { //jdk.internal.net.http.common.Utils.DISALLOWED_HEADERS_SET - private static final Set DISALLOWED_HEADERS_SET = Set.of("connection", "content-length", + private static final Set DISALLOWED_HEADERS_SET = Utility.ofSet("connection", "content-length", "date", "expect", "from", "host", "origin", "referer", "upgrade", "via", "warning"); protected ClusterAgent clusterAgent; - protected java.net.http.HttpClient httpClient; + @Resource(name = "cluster.httpClient") + protected HttpClient httpClient; + //protected java.net.http.HttpClient httpClient; public HttpMessageClusterClient(ClusterAgent clusterAgent) { super(null); Objects.requireNonNull(clusterAgent); this.clusterAgent = clusterAgent; - this.httpClient = java.net.http.HttpClient.newHttpClient(); + //this.httpClient = java.net.http.HttpClient.newHttpClient(); } @Override @@ -67,16 +70,17 @@ public class HttpMessageClusterClient extends HttpMessageClient { String resname = headers == null ? "" : headers.getOrDefault(Rest.REST_HEADER_RESOURCE_NAME, ""); return clusterAgent.queryMqtpAddress("mqtp", module, resname).thenCompose(addrmap -> { if (addrmap == null || addrmap.isEmpty()) return new HttpResult().status(404).toAnyFuture(); - java.net.http.HttpRequest.Builder builder = java.net.http.HttpRequest.newBuilder().timeout(Duration.ofMillis(30000)); - if (req.isRpc()) builder.header(Rest.REST_HEADER_RPC_NAME, "true"); - if (req.isFrombody()) builder.header(Rest.REST_HEADER_PARAM_FROM_BODY, "true"); - if (req.getReqConvertType() != null) builder.header(Rest.REST_HEADER_REQ_CONVERT_TYPE, req.getReqConvertType().toString()); - if (req.getRespConvertType() != null) builder.header(Rest.REST_HEADER_RESP_CONVERT_TYPE, req.getRespConvertType().toString()); - if (userid != 0) builder.header(Rest.REST_HEADER_CURRUSERID_NAME, "" + userid); + final Map clientHeaders = new LinkedHashMap<>(); + byte[] clientBody = null; + if (req.isRpc()) clientHeaders.put(Rest.REST_HEADER_RPC_NAME, "true"); + if (req.isFrombody()) clientHeaders.put(Rest.REST_HEADER_PARAM_FROM_BODY, "true"); + if (req.getReqConvertType() != null) clientHeaders.put(Rest.REST_HEADER_REQ_CONVERT_TYPE, req.getReqConvertType().toString()); + if (req.getRespConvertType() != null) clientHeaders.put(Rest.REST_HEADER_RESP_CONVERT_TYPE, req.getRespConvertType().toString()); + if (userid != 0) clientHeaders.put(Rest.REST_HEADER_CURRUSERID_NAME, "" + userid); if (headers != null) headers.forEach((n, v) -> { - if (!DISALLOWED_HEADERS_SET.contains(n.toLowerCase())) builder.header(n, v); + if (!DISALLOWED_HEADERS_SET.contains(n.toLowerCase())) clientHeaders.put(n, v); }); - builder.header("Content-Type", "x-www-form-urlencoded"); + clientHeaders.put("Content-Type", "x-www-form-urlencoded"); if (req.getBody() != null && req.getBody().length > 0) { String paramstr = req.getParametersToString(); if (paramstr != null) { @@ -86,10 +90,10 @@ public class HttpMessageClusterClient extends HttpMessageClient { req.setRequestURI(req.getRequestURI() + "?" + paramstr); } } - builder.POST(java.net.http.HttpRequest.BodyPublishers.ofByteArray(req.getBody())); + clientBody = req.getBody(); } else { String paramstr = req.getParametersToString(); - if (paramstr != null) builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(paramstr)); + if (paramstr != null) clientBody = paramstr.getBytes(StandardCharsets.UTF_8); } List futures = new ArrayList<>(); for (Map.Entry> en : addrmap.entrySet()) { @@ -99,7 +103,7 @@ public class HttpMessageClusterClient extends HttpMessageClient { String suburi = req.getRequestURI(); suburi = suburi.substring(1); //跳过 / suburi = "/" + realmodule + suburi.substring(suburi.indexOf('/')); - futures.add(forEachCollectionFuture(finest, userid, req, (req.getPath() != null && !req.getPath().isEmpty() ? req.getPath() : "") + suburi, builder, addrs.iterator())); + futures.add(forEachCollectionFuture(finest, userid, req, (req.getPath() != null && !req.getPath().isEmpty() ? req.getPath() : "") + suburi, clientHeaders, clientBody, addrs.iterator())); } if (futures.isEmpty()) return CompletableFuture.completedFuture(null); return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).thenApply(v -> null); @@ -115,16 +119,17 @@ public class HttpMessageClusterClient extends HttpMessageClient { String resname = headers == null ? "" : headers.getOrDefault(Rest.REST_HEADER_RESOURCE_NAME, ""); return clusterAgent.queryHttpAddress("http", module, resname).thenCompose(addrs -> { if (addrs == null || addrs.isEmpty()) return new HttpResult().status(404).toAnyFuture(); - java.net.http.HttpRequest.Builder builder = java.net.http.HttpRequest.newBuilder().timeout(Duration.ofMillis(30000)); - if (req.isRpc()) builder.header(Rest.REST_HEADER_RPC_NAME, "true"); - if (req.isFrombody()) builder.header(Rest.REST_HEADER_PARAM_FROM_BODY, "true"); - if (req.getReqConvertType() != null) builder.header(Rest.REST_HEADER_REQ_CONVERT_TYPE, req.getReqConvertType().toString()); - if (req.getRespConvertType() != null) builder.header(Rest.REST_HEADER_RESP_CONVERT_TYPE, req.getRespConvertType().toString()); - if (userid != 0) builder.header(Rest.REST_HEADER_CURRUSERID_NAME, "" + userid); + final Map clientHeaders = new LinkedHashMap<>(); + byte[] clientBody = null; + if (req.isRpc()) clientHeaders.put(Rest.REST_HEADER_RPC_NAME, "true"); + if (req.isFrombody()) clientHeaders.put(Rest.REST_HEADER_PARAM_FROM_BODY, "true"); + if (req.getReqConvertType() != null) clientHeaders.put(Rest.REST_HEADER_REQ_CONVERT_TYPE, req.getReqConvertType().toString()); + if (req.getRespConvertType() != null) clientHeaders.put(Rest.REST_HEADER_RESP_CONVERT_TYPE, req.getRespConvertType().toString()); + if (userid != 0) clientHeaders.put(Rest.REST_HEADER_CURRUSERID_NAME, "" + userid); if (headers != null) headers.forEach((n, v) -> { - if (!DISALLOWED_HEADERS_SET.contains(n.toLowerCase())) builder.header(n, v); + if (!DISALLOWED_HEADERS_SET.contains(n.toLowerCase())) clientHeaders.put(n, v); }); - builder.header("Content-Type", "x-www-form-urlencoded"); + clientHeaders.put("Content-Type", "x-www-form-urlencoded"); if (req.getBody() != null && req.getBody().length > 0) { String paramstr = req.getParametersToString(); if (paramstr != null) { @@ -134,46 +139,138 @@ public class HttpMessageClusterClient extends HttpMessageClient { req.setRequestURI(req.getRequestURI() + "?" + paramstr); } } - builder.POST(java.net.http.HttpRequest.BodyPublishers.ofByteArray(req.getBody())); + clientBody = req.getBody(); } else { String paramstr = req.getParametersToString(); - if (paramstr != null) builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(paramstr)); + if (paramstr != null) clientBody = paramstr.getBytes(StandardCharsets.UTF_8); } - return forEachCollectionFuture(finest, userid, req, (req.getPath() != null && !req.getPath().isEmpty() ? req.getPath() : "") + req.getRequestURI(), builder, addrs.iterator()); + return forEachCollectionFuture(finest, userid, req, (req.getPath() != null && !req.getPath().isEmpty() ? req.getPath() : "") + req.getRequestURI(), clientHeaders, clientBody, addrs.iterator()); }); } - private CompletableFuture> forEachCollectionFuture(boolean finest, int userid, HttpSimpleRequest req, String requesturi, java.net.http.HttpRequest.Builder builder, Iterator it) { + private CompletableFuture> forEachCollectionFuture(boolean finest, int userid, HttpSimpleRequest req, String requesturi, final Map clientHeaders, byte[] clientBody, Iterator it) { if (!it.hasNext()) return CompletableFuture.completedFuture(null); InetSocketAddress addr = it.next(); String url = "http://" + addr.getHostString() + ":" + addr.getPort() + requesturi; - return httpClient.sendAsync(builder.copy().uri(URI.create(url)).build(), java.net.http.HttpResponse.BodyHandlers.ofByteArray()).thenCompose(resp -> { - if (resp.statusCode() != 200) return forEachCollectionFuture(finest, userid, req, requesturi, builder, it); - HttpResult rs = new HttpResult(); - java.net.http.HttpHeaders hs = resp.headers(); - if (hs != null) { - Map> hm = hs.map(); - if (hm != null) { - for (Map.Entry> en : hm.entrySet()) { - if ("date".equals(en.getKey()) || "content-type".equals(en.getKey()) - || "server".equals(en.getKey()) || "connection".equals(en.getKey())) continue; - List val = en.getValue(); - if (val != null && val.size() == 1) { - rs.header(en.getKey(), val.get(0)); - } - } - } - } - rs.setResult(resp.body()); - if (finest) { - StringBuilder sb = new StringBuilder(); - Map params = req.getParams(); - if (params != null && !params.isEmpty()) { - params.forEach((n, v) -> sb.append('&').append(n).append('=').append(v)); - } - logger.log(Level.FINEST, url + "?userid=" + userid + sb + ", result = " + new String(resp.body(), StandardCharsets.UTF_8)); - } - return CompletableFuture.completedFuture(rs); - }); + return httpClient.postAsync(url, clientHeaders, clientBody); } + +// private CompletableFuture> mqtpAsync(int userid, HttpSimpleRequest req) { +// final boolean finest = logger.isLoggable(Level.FINEST); +// String module = req.getRequestURI(); +// module = module.substring(1); //去掉/ +// module = module.substring(0, module.indexOf('/')); +// Map headers = req.getHeaders(); +// String resname = headers == null ? "" : headers.getOrDefault(Rest.REST_HEADER_RESOURCE_NAME, ""); +// return clusterAgent.queryMqtpAddress("mqtp", module, resname).thenCompose(addrmap -> { +// if (addrmap == null || addrmap.isEmpty()) return new HttpResult().status(404).toAnyFuture(); +// java.net.http.HttpRequest.Builder builder = java.net.http.HttpRequest.newBuilder().timeout(Duration.ofMillis(30000)); +// if (req.isRpc()) builder.header(Rest.REST_HEADER_RPC_NAME, "true"); +// if (req.isFrombody()) builder.header(Rest.REST_HEADER_PARAM_FROM_BODY, "true"); +// if (req.getReqConvertType() != null) builder.header(Rest.REST_HEADER_REQ_CONVERT_TYPE, req.getReqConvertType().toString()); +// if (req.getRespConvertType() != null) builder.header(Rest.REST_HEADER_RESP_CONVERT_TYPE, req.getRespConvertType().toString()); +// if (userid != 0) builder.header(Rest.REST_HEADER_CURRUSERID_NAME, "" + userid); +// if (headers != null) headers.forEach((n, v) -> { +// if (!DISALLOWED_HEADERS_SET.contains(n.toLowerCase())) builder.header(n, v); +// }); +// builder.header("Content-Type", "x-www-form-urlencoded"); +// if (req.getBody() != null && req.getBody().length > 0) { +// String paramstr = req.getParametersToString(); +// if (paramstr != null) { +// if (req.getRequestURI().indexOf('?') > 0) { +// req.setRequestURI(req.getRequestURI() + "&" + paramstr); +// } else { +// req.setRequestURI(req.getRequestURI() + "?" + paramstr); +// } +// } +// builder.POST(java.net.http.HttpRequest.BodyPublishers.ofByteArray(req.getBody())); +// } else { +// String paramstr = req.getParametersToString(); +// if (paramstr != null) builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(paramstr)); +// } +// List futures = new ArrayList<>(); +// for (Map.Entry> en : addrmap.entrySet()) { +// String realmodule = en.getKey(); +// Collection addrs = en.getValue(); +// if (addrs == null || addrs.isEmpty()) continue; +// String suburi = req.getRequestURI(); +// suburi = suburi.substring(1); //跳过 / +// suburi = "/" + realmodule + suburi.substring(suburi.indexOf('/')); +// futures.add(forEachCollectionFuture(finest, userid, req, (req.getPath() != null && !req.getPath().isEmpty() ? req.getPath() : "") + suburi, builder, addrs.iterator())); +// } +// if (futures.isEmpty()) return CompletableFuture.completedFuture(null); +// return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).thenApply(v -> null); +// }); +// } +// +// private CompletableFuture> httpAsync(int userid, HttpSimpleRequest req) { +// final boolean finest = logger.isLoggable(Level.FINEST); +// String module = req.getRequestURI(); +// module = module.substring(1); //去掉/ +// module = module.substring(0, module.indexOf('/')); +// Map headers = req.getHeaders(); +// String resname = headers == null ? "" : headers.getOrDefault(Rest.REST_HEADER_RESOURCE_NAME, ""); +// return clusterAgent.queryHttpAddress("http", module, resname).thenCompose(addrs -> { +// if (addrs == null || addrs.isEmpty()) return new HttpResult().status(404).toAnyFuture(); +// java.net.http.HttpRequest.Builder builder = java.net.http.HttpRequest.newBuilder().timeout(Duration.ofMillis(30000)); +// if (req.isRpc()) builder.header(Rest.REST_HEADER_RPC_NAME, "true"); +// if (req.isFrombody()) builder.header(Rest.REST_HEADER_PARAM_FROM_BODY, "true"); +// if (req.getReqConvertType() != null) builder.header(Rest.REST_HEADER_REQ_CONVERT_TYPE, req.getReqConvertType().toString()); +// if (req.getRespConvertType() != null) builder.header(Rest.REST_HEADER_RESP_CONVERT_TYPE, req.getRespConvertType().toString()); +// if (userid != 0) builder.header(Rest.REST_HEADER_CURRUSERID_NAME, "" + userid); +// if (headers != null) headers.forEach((n, v) -> { +// if (!DISALLOWED_HEADERS_SET.contains(n.toLowerCase())) builder.header(n, v); +// }); +// builder.header("Content-Type", "x-www-form-urlencoded"); +// if (req.getBody() != null && req.getBody().length > 0) { +// String paramstr = req.getParametersToString(); +// if (paramstr != null) { +// if (req.getRequestURI().indexOf('?') > 0) { +// req.setRequestURI(req.getRequestURI() + "&" + paramstr); +// } else { +// req.setRequestURI(req.getRequestURI() + "?" + paramstr); +// } +// } +// builder.POST(java.net.http.HttpRequest.BodyPublishers.ofByteArray(req.getBody())); +// } else { +// String paramstr = req.getParametersToString(); +// if (paramstr != null) builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(paramstr)); +// } +// return forEachCollectionFuture(finest, userid, req, (req.getPath() != null && !req.getPath().isEmpty() ? req.getPath() : "") + req.getRequestURI(), builder, addrs.iterator()); +// }); +// } +// +// private CompletableFuture> forEachCollectionFuture(boolean finest, int userid, HttpSimpleRequest req, String requesturi, java.net.http.HttpRequest.Builder builder, Iterator it) { +// if (!it.hasNext()) return CompletableFuture.completedFuture(null); +// InetSocketAddress addr = it.next(); +// String url = "http://" + addr.getHostString() + ":" + addr.getPort() + requesturi; +// return httpClient.sendAsync(builder.copy().uri(URI.create(url)).build(), java.net.http.HttpResponse.BodyHandlers.ofByteArray()).thenCompose(resp -> { +// if (resp.statusCode() != 200) return forEachCollectionFuture(finest, userid, req, requesturi, builder, it); +// HttpResult rs = new HttpResult(); +// java.net.http.HttpHeaders hs = resp.headers(); +// if (hs != null) { +// Map> hm = hs.map(); +// if (hm != null) { +// for (Map.Entry> en : hm.entrySet()) { +// if ("date".equals(en.getKey()) || "content-type".equals(en.getKey()) +// || "server".equals(en.getKey()) || "connection".equals(en.getKey())) continue; +// List val = en.getValue(); +// if (val != null && val.size() == 1) { +// rs.header(en.getKey(), val.get(0)); +// } +// } +// } +// } +// rs.setResult(resp.body()); +// if (finest) { +// StringBuilder sb = new StringBuilder(); +// Map params = req.getParams(); +// if (params != null && !params.isEmpty()) { +// params.forEach((n, v) -> sb.append('&').append(n).append('=').append(v)); +// } +// logger.log(Level.FINEST, url + "?userid=" + userid + sb + ", result = " + new String(resp.body(), StandardCharsets.UTF_8)); +// } +// return CompletableFuture.completedFuture(rs); +// }); +// } } diff --git a/src/org/redkale/mq/HttpMessageProcessor.java b/src/org/redkale/mq/HttpMessageProcessor.java index 4b634d86f..6165c702d 100644 --- a/src/org/redkale/mq/HttpMessageProcessor.java +++ b/src/org/redkale/mq/HttpMessageProcessor.java @@ -6,13 +6,15 @@ package org.redkale.mq; import java.util.concurrent.*; +import java.util.function.*; import java.util.logging.*; import org.redkale.boot.NodeHttpServer; import org.redkale.net.http.*; import org.redkale.service.Service; -import org.redkale.util.ThreadHashExecutor; +import org.redkale.util.ObjectPool; /** + * 一个Service对应一个MessageProcessor * *

* 详情见: https://redkale.org @@ -31,14 +33,12 @@ public class HttpMessageProcessor implements MessageProcessor { protected final Logger logger; - protected MessageClient messageClient; + protected HttpMessageClient messageClient; - protected final MessageProducers producer; + protected final MessageProducers producers; protected final NodeHttpServer server; - protected final ThreadHashExecutor workExecutor; - protected final Service service; protected final HttpServlet servlet; @@ -49,6 +49,12 @@ public class HttpMessageProcessor implements MessageProcessor { protected final String multimodule; // 前后有/, 例如: /userstat/ + protected ThreadLocal> respPoolThreadLocal; + + protected final Supplier respSupplier; + + protected final Consumer respConsumer; + protected CountDownLatch cdl; protected long starttime; @@ -57,13 +63,13 @@ public class HttpMessageProcessor implements MessageProcessor { if (cdl != null) cdl.countDown(); }; - public HttpMessageProcessor(Logger logger, ThreadHashExecutor workExecutor, MessageClient messageClient, MessageProducers producer, NodeHttpServer server, Service service, HttpServlet servlet) { + public HttpMessageProcessor(Logger logger, HttpMessageClient messageClient, MessageProducers producers, NodeHttpServer server, Service service, HttpServlet servlet) { this.logger = logger; this.finest = logger.isLoggable(Level.FINEST); this.finer = logger.isLoggable(Level.FINER); this.fine = logger.isLoggable(Level.FINE); this.messageClient = messageClient; - this.producer = producer; + this.producers = producers; this.server = server; this.service = service; this.servlet = servlet; @@ -71,22 +77,21 @@ public class HttpMessageProcessor implements MessageProcessor { this.multiconsumer = mmc != null; this.restmodule = "/" + Rest.getRestModule(service) + "/"; this.multimodule = mmc != null ? ("/" + mmc.module() + "/") : null; - this.workExecutor = workExecutor; + this.respSupplier = () -> respPoolThreadLocal.get().get(); + this.respConsumer = resp -> respPoolThreadLocal.get().accept(resp); + this.respPoolThreadLocal = ThreadLocal.withInitial(() -> ObjectPool.createUnsafePool(Runtime.getRuntime().availableProcessors(), + ps -> new HttpMessageResponse(server.getHttpServer().getContext(), messageClient, respSupplier, respConsumer), HttpMessageResponse::prepare, HttpMessageResponse::recycle)); } @Override public void begin(final int size, long starttime) { this.starttime = starttime; - if (this.workExecutor != null) this.cdl = new CountDownLatch(size); + this.cdl = new CountDownLatch(size); } @Override public void process(final MessageRecord message, final Runnable callback) { - if (this.workExecutor == null) { - execute(message, innerCallback); - } else { - this.workExecutor.execute(message.hash(), () -> execute(message, innerCallback)); - } + execute(message, innerCallback); } private void execute(final MessageRecord message, final Runnable callback) { @@ -96,13 +101,13 @@ public class HttpMessageProcessor implements MessageProcessor { long cha = now - message.createtime; long e = now - starttime; if (multiconsumer) message.setResptopic(null); //不容许有响应 - HttpContext context = server.getHttpServer().getContext(); - request = new HttpMessageRequest(context, message); - if (multiconsumer) { - request.setRequestURI(request.getRequestURI().replaceFirst(this.multimodule, this.restmodule)); - } - HttpMessageResponse response = new HttpMessageResponse(context, request, callback, null, null, messageClient, producer.getProducer(message)); - servlet.execute(request, response); + + HttpMessageResponse response = respSupplier.get(); + request = response.request(); + response.prepare(message, callback, producers.getProducer(message)); + if (multiconsumer) request.setRequestURI(request.getRequestURI().replaceFirst(this.multimodule, this.restmodule)); + + server.getHttpServer().getContext().execute(servlet, request, response); long o = System.currentTimeMillis() - now; if ((cha > 1000 || e > 100 || o > 1000) && fine) { logger.log(Level.FINE, "HttpMessageProcessor.process (mqs.delays = " + cha + " ms, mqs.blocks = " + e + " ms, mqs.executes = " + o + " ms) message: " + message); @@ -114,7 +119,7 @@ public class HttpMessageProcessor implements MessageProcessor { } catch (Throwable ex) { if (message.getResptopic() != null && !message.getResptopic().isEmpty()) { HttpMessageResponse.finishHttpResult(finest, request == null ? null : request.getRespConvert(), - message, callback, messageClient, producer.getProducer(message), message.getResptopic(), new HttpResult().status(500)); + message, callback, messageClient, producers.getProducer(message), message.getResptopic(), new HttpResult().status(500)); } logger.log(Level.SEVERE, HttpMessageProcessor.class.getSimpleName() + " process error, message=" + message, ex instanceof CompletionException ? ((CompletionException) ex).getCause() : ex); } @@ -133,7 +138,7 @@ public class HttpMessageProcessor implements MessageProcessor { } public MessageProducers getProducer() { - return producer; + return producers; } public NodeHttpServer getServer() { diff --git a/src/org/redkale/mq/HttpMessageRequest.java b/src/org/redkale/mq/HttpMessageRequest.java index dbdcb0966..6f7d0fcab 100644 --- a/src/org/redkale/mq/HttpMessageRequest.java +++ b/src/org/redkale/mq/HttpMessageRequest.java @@ -21,10 +21,17 @@ public class HttpMessageRequest extends HttpRequest { protected MessageRecord message; - public HttpMessageRequest(HttpContext context, MessageRecord message) { - super(context, message.decodeContent(HttpSimpleRequestCoder.getInstance())); + public HttpMessageRequest(HttpContext context) { + super(context, (HttpSimpleRequest) null); + } + + protected HttpMessageRequest prepare(MessageRecord message) { + super.initSimpleRequest(message.decodeContent(HttpSimpleRequestCoder.getInstance())); this.message = message; + this.hashid = this.message.hash(); this.currentUserid = message.getUserid(); + this.createtime = System.currentTimeMillis(); + return this; } public void setRequestURI(String uri) { @@ -35,4 +42,16 @@ public class HttpMessageRequest extends HttpRequest { public Convert getRespConvert() { return this.respConvert; } + + @Override + protected void prepare() { + super.prepare(); + this.keepAlive = false; + } + + @Override + protected void recycle() { + super.recycle(); + this.message = null; + } } diff --git a/src/org/redkale/mq/HttpMessageResponse.java b/src/org/redkale/mq/HttpMessageResponse.java index 7566aa090..b9b41cbb7 100644 --- a/src/org/redkale/mq/HttpMessageResponse.java +++ b/src/org/redkale/mq/HttpMessageResponse.java @@ -7,13 +7,14 @@ package org.redkale.mq; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.function.*; import java.util.logging.Level; import org.redkale.convert.*; -import org.redkale.net.Response; import org.redkale.net.http.*; import org.redkale.service.RetResult; -import org.redkale.util.ObjectPool; import static org.redkale.mq.MessageRecord.CTYPE_HTTP_RESULT; +import org.redkale.net.Response; /** * @@ -27,7 +28,7 @@ import static org.redkale.mq.MessageRecord.CTYPE_HTTP_RESULT; */ public class HttpMessageResponse extends HttpResponse { - protected MessageClient messageClient; + protected final HttpMessageClient messageClient; protected MessageRecord message; @@ -37,22 +38,33 @@ public class HttpMessageResponse extends HttpResponse { protected Runnable callback; - public HttpMessageResponse(HttpContext context, HttpMessageRequest request, Runnable callback, - ObjectPool responsePool, HttpResponseConfig config, MessageClient messageClient, MessageProducer producer) { - super(context, request, responsePool, config); - this.message = request.message; - this.callback = callback; + public HttpMessageResponse(HttpContext context, HttpMessageClient messageClient, final Supplier respSupplier, final Consumer respConsumer) { + super(context, new HttpMessageRequest(context), null); + this.responseSupplier = (Supplier) respSupplier; + this.responseConsumer = (Consumer) respConsumer; this.messageClient = messageClient; + } + +// public HttpMessageResponse(HttpContext context, HttpMessageRequest request, Runnable callback, +// HttpResponseConfig config, HttpMessageClient messageClient, MessageProducer producer) { +// super(context, request, config); +// this.message = request.message; +// this.callback = callback; +// this.messageClient = messageClient; +// this.producer = producer; +// this.finest = producer.logger.isLoggable(Level.FINEST); +// } + + public void prepare(MessageRecord message, Runnable callback, MessageProducer producer) { + ((HttpMessageRequest)request).prepare(message); + this.message = message; + this.callback = callback; this.producer = producer; this.finest = producer.logger.isLoggable(Level.FINEST); } - public HttpMessageResponse(HttpContext context, MessageRecord message, Runnable callback, HttpResponseConfig config, MessageClient messageClient, MessageProducer producer) { - super(context, new HttpMessageRequest(context, message), null, config); - this.message = message; - this.callback = callback; - this.messageClient = messageClient; - this.producer = producer; + public HttpMessageRequest request() { + return (HttpMessageRequest) request; } public void finishHttpResult(HttpResult result) { @@ -77,6 +89,25 @@ public class HttpMessageResponse extends HttpResponse { producer.apply(messageClient.createMessageRecord(msg.getSeqid(), CTYPE_HTTP_RESULT, resptopic, null, content)); } + @Override + protected void prepare() { + super.prepare(); + } + + @Override + protected boolean recycle() { + Supplier respSupplier = this.responseSupplier; + Consumer respConsumer = this.responseConsumer; + boolean rs = super.recycle(); + this.responseSupplier = respSupplier; + this.responseConsumer = respConsumer; + this.message = null; + this.producer = null; + this.callback = null; + this.finest = false; + return rs; + } + @Override public void finishJson(org.redkale.service.RetResult ret) { if (message.isEmptyResptopic()) { @@ -103,7 +134,7 @@ public class HttpMessageResponse extends HttpResponse { @Override public void finish(int status, String msg) { if (status > 400) { - producer.logger.log(Level.INFO, "HttpMessageResponse.finish status: " + status + ", message: " + this.message); + producer.logger.log(Level.WARNING, "HttpMessageResponse.finish status: " + status + ", message: " + this.message); } else if (finest) { producer.logger.log(Level.FINEST, "HttpMessageResponse.finish status: " + status); } @@ -125,21 +156,26 @@ public class HttpMessageResponse extends HttpResponse { } @Override - public void finish(final byte[] bs) { + public void finish(boolean kill, final byte[] bs, int offset, int length) { if (message.isEmptyResptopic()) { if (callback != null) callback.run(); return; } - finishHttpResult(new HttpResult(bs)); + if (offset == 0 && bs.length == length) { + finishHttpResult(new HttpResult(bs)); + } else { + finishHttpResult(new HttpResult(Arrays.copyOfRange(bs, offset, offset + length))); + } } @Override - public void finish(final String contentType, final byte[] bs) { + public void finish(boolean kill, final String contentType, final byte[] bs, int offset, int length) { if (message.isEmptyResptopic()) { if (callback != null) callback.run(); return; } - finishHttpResult(new HttpResult(bs).contentType(contentType)); + byte[] rs = (offset == 0 && bs.length == length) ? bs : Arrays.copyOfRange(bs, offset, offset + length); + finishHttpResult(new HttpResult(rs).contentType(contentType)); } @Override diff --git a/src/org/redkale/mq/HttpSimpleRequestCoder.java b/src/org/redkale/mq/HttpSimpleRequestCoder.java index f86d5a56d..75bb1461a 100644 --- a/src/org/redkale/mq/HttpSimpleRequestCoder.java +++ b/src/org/redkale/mq/HttpSimpleRequestCoder.java @@ -40,6 +40,7 @@ public class HttpSimpleRequestCoder implements MessageCoder { byte[] body = MessageCoder.getBytes(data.getBody()); int count = 1 //rpc + 1 //frombody + + 4 //hashid + 4 //reqConvertType + 4 //respConvertType + 4 + requestURI.length + 2 + path.length + 2 + remoteAddr.length + 2 + sessionid.length @@ -48,6 +49,7 @@ public class HttpSimpleRequestCoder implements MessageCoder { ByteBuffer buffer = ByteBuffer.wrap(bs); buffer.put((byte) (data.isRpc() ? 'T' : 'F')); buffer.put((byte) (data.isFrombody() ? 'T' : 'F')); + buffer.putInt(data.getHashid()); buffer.putInt(data.getReqConvertType() == null ? 0 : data.getReqConvertType().getValue()); buffer.putInt(data.getRespConvertType() == null ? 0 : data.getRespConvertType().getValue()); buffer.putInt(requestURI.length); @@ -75,6 +77,7 @@ public class HttpSimpleRequestCoder implements MessageCoder { HttpSimpleRequest req = new HttpSimpleRequest(); req.setRpc(buffer.get() == 'T'); req.setFrombody(buffer.get() == 'T'); + req.setHashid(buffer.getInt()); int reqformat = buffer.getInt(); int respformat = buffer.getInt(); if (reqformat != 0) req.setReqConvertType(ConvertType.find(reqformat)); diff --git a/src/org/redkale/mq/MessageAgent.java b/src/org/redkale/mq/MessageAgent.java index 6b5a24304..08cb25cc7 100644 --- a/src/org/redkale/mq/MessageAgent.java +++ b/src/org/redkale/mq/MessageAgent.java @@ -16,7 +16,7 @@ import static org.redkale.boot.Application.RESNAME_APP_NODEID; import org.redkale.net.Servlet; import org.redkale.net.http.*; import org.redkale.net.sncp.*; -import org.redkale.service.Service; +import org.redkale.service.*; import org.redkale.util.*; /** @@ -56,8 +56,6 @@ public abstract class MessageAgent { protected ScheduledThreadPoolExecutor timeoutExecutor; - protected ThreadHashExecutor workExecutor; - protected int producerCount = 1; //本地Service消息接收处理器, key:consumer @@ -68,23 +66,16 @@ public abstract class MessageAgent { this.httpMessageClient = new HttpMessageClient(this); this.sncpMessageClient = new SncpMessageClient(this); this.producerCount = config.getIntValue("producers", Runtime.getRuntime().availableProcessors()); - if ("hash".equalsIgnoreCase(config.getValue("pool", "hash"))) { - this.workExecutor = new ThreadHashExecutor(Math.max(4, config.getIntValue("threads", Runtime.getRuntime().availableProcessors()))); - } // application (it doesn't execute completion handlers). this.timeoutExecutor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1, (Runnable r) -> { Thread t = new Thread(r); - t.setName("MessageAgent-Timeout-Thread"); + t.setName("Redkale-MessageAgent-Timeout-Thread"); t.setDaemon(true); return t; }); this.timeoutExecutor.setRemoveOnCancelPolicy(true); } - public boolean isHashPool() { - return this.workExecutor != null; - } - public CompletableFuture> start() { final LinkedHashMap map = new LinkedHashMap<>(); final List futures = new ArrayList<>(); @@ -108,7 +99,6 @@ public abstract class MessageAgent { public void destroy(AnyValue config) { this.httpMessageClient.close().join(); this.sncpMessageClient.close().join(); - this.workExecutor.shutdown(); if (this.timeoutExecutor != null) this.timeoutExecutor.shutdown(); if (this.sncpProducer != null) this.sncpProducer.shutdown().join(); if (this.httpProducer != null) this.httpProducer.shutdown().join(); @@ -126,12 +116,12 @@ public abstract class MessageAgent { protected List getAllMessageProducer() { List producers = new ArrayList<>(); - if (this.httpProducer != null) producers.addAll(List.of(this.httpProducer.producers)); - if (this.sncpProducer != null) producers.addAll(List.of(this.sncpProducer.producers)); + if (this.httpProducer != null) producers.addAll(Utility.ofList(this.httpProducer.producers)); + if (this.sncpProducer != null) producers.addAll(Utility.ofList(this.sncpProducer.producers)); MessageProducers one = this.httpMessageClient == null ? null : this.httpMessageClient.getProducer(); - if (one != null) producers.addAll(List.of(one.producers)); + if (one != null) producers.addAll(Utility.ofList(one.producers)); one = this.sncpMessageClient == null ? null : this.sncpMessageClient.getProducer(); - if (one != null) producers.addAll(List.of(one.producers)); + if (one != null) producers.addAll(Utility.ofList(one.producers)); return producers; } @@ -224,18 +214,22 @@ public abstract class MessageAgent { public abstract MessageConsumer createConsumer(String[] topics, String group, MessageProcessor processor); public final synchronized void putService(NodeHttpServer ns, Service service, HttpServlet servlet) { + AutoLoad al = service.getClass().getAnnotation(AutoLoad.class); + if (al != null && !al.value() && service.getClass().getAnnotation(Local.class) != null) return; String[] topics = generateHttpReqTopics(service); String consumerid = generateHttpConsumerid(topics, service); if (messageNodes.containsKey(consumerid)) throw new RuntimeException("consumerid(" + consumerid + ") is repeat"); - HttpMessageProcessor processor = new HttpMessageProcessor(this.logger, this.workExecutor, httpMessageClient, getHttpProducer(), ns, service, servlet); + HttpMessageProcessor processor = new HttpMessageProcessor(this.logger, httpMessageClient, getHttpProducer(), ns, service, servlet); this.messageNodes.put(consumerid, new MessageConsumerNode(ns, service, servlet, processor, createConsumer(topics, consumerid, processor))); } public final synchronized void putService(NodeSncpServer ns, Service service, SncpServlet servlet) { + AutoLoad al = service.getClass().getAnnotation(AutoLoad.class); + if (al != null && !al.value() && service.getClass().getAnnotation(Local.class) != null) return; String topic = generateSncpReqTopic(service); String consumerid = generateSncpConsumerid(topic, service); if (messageNodes.containsKey(consumerid)) throw new RuntimeException("consumerid(" + consumerid + ") is repeat"); - SncpMessageProcessor processor = new SncpMessageProcessor(this.logger, this.workExecutor, sncpMessageClient, getSncpProducer(), ns, service, servlet); + SncpMessageProcessor processor = new SncpMessageProcessor(this.logger, sncpMessageClient, getSncpProducer(), ns, service, servlet); this.messageNodes.put(consumerid, new MessageConsumerNode(ns, service, servlet, processor, createConsumer(new String[]{topic}, consumerid, processor))); } diff --git a/src/org/redkale/mq/MessageClient.java b/src/org/redkale/mq/MessageClient.java index cac01151a..3f0d1bfd4 100644 --- a/src/org/redkale/mq/MessageClient.java +++ b/src/org/redkale/mq/MessageClient.java @@ -79,11 +79,11 @@ public abstract class MessageClient { node.future.complete(msg); long cha = now - msg.createtime; if (cha > 1000 && fine) { - messageAgent.logger.log(Level.FINE, clazzName + ".MessageRespFutureNode.process (mqs.delays = " + cha + "ms, mqs.counters = " + ncer + ") mqresp.msg: " + msg); + messageAgent.logger.log(Level.FINE, clazzName + ".MessageRespFutureNode.process (mqs.delays = " + cha + "ms, mqs.counters = " + ncer + ") mqresp.msg: " + formatRespMessage(msg)); } else if (cha > 50 && finer) { - messageAgent.logger.log(Level.FINER, clazzName + ".MessageRespFutureNode.process (mq.delays = " + cha + "ms, mq.counters = " + ncer + ") mqresp.msg: " + msg); + messageAgent.logger.log(Level.FINER, clazzName + ".MessageRespFutureNode.process (mq.delays = " + cha + "ms, mq.counters = " + ncer + ") mqresp.msg: " + formatRespMessage(msg)); } else if (finest) { - messageAgent.logger.log(Level.FINEST, clazzName + ".MessageRespFutureNode.process (mq.delay = " + cha + "ms, mq.counter = " + ncer + ") mqresp.msg: " + msg); + messageAgent.logger.log(Level.FINEST, clazzName + ".MessageRespFutureNode.process (mq.delay = " + cha + "ms, mq.counter = " + ncer + ") mqresp.msg: " + formatRespMessage(msg)); } }; MessageConsumer one = messageAgent.createConsumer(new String[]{respTopic}, respConsumerid, processor); @@ -114,6 +114,10 @@ public abstract class MessageClient { } } + protected MessageRecord formatRespMessage(MessageRecord message) { + return message; + } + protected abstract MessageProducers getProducer(); public MessageRecord createMessageRecord(String resptopic, String content) { diff --git a/src/org/redkale/mq/MessageProcessor.java b/src/org/redkale/mq/MessageProcessor.java index 6751e38d4..288c0fb06 100644 --- a/src/org/redkale/mq/MessageProcessor.java +++ b/src/org/redkale/mq/MessageProcessor.java @@ -6,6 +6,7 @@ package org.redkale.mq; /** + * 一个Service对应一个MessageProcessor * *

* 详情见: https://redkale.org diff --git a/src/org/redkale/mq/MessageRecord.java b/src/org/redkale/mq/MessageRecord.java index f234321fe..4a12402db 100644 --- a/src/org/redkale/mq/MessageRecord.java +++ b/src/org/redkale/mq/MessageRecord.java @@ -8,7 +8,10 @@ package org.redkale.mq; import java.io.Serializable; import java.nio.charset.StandardCharsets; import org.redkale.convert.*; +import org.redkale.convert.bson.BsonConvert; import org.redkale.convert.json.JsonConvert; +import org.redkale.net.http.HttpSimpleRequest; +import org.redkale.net.sncp.SncpRequest; import org.redkale.util.Comment; /** @@ -33,6 +36,8 @@ public class MessageRecord implements Serializable { protected static final byte CTYPE_HTTP_RESULT = 3; + protected static final byte CTYPE_BSON_RESULT = 4; + @ConvertColumn(index = 1) @Comment("消息序列号") protected long seqid; @@ -274,8 +279,17 @@ public class MessageRecord implements Serializable { if (this.topic != null) sb.append(",\"topic\":\"").append(this.topic).append("\""); if (this.resptopic != null) sb.append(",\"resptopic\":\"").append(this.resptopic).append("\""); if (this.content != null) { - if (this.ctype == CTYPE_HTTP_REQUEST) { - sb.append(",\"content\":").append(HttpSimpleRequestCoder.getInstance().decode(this.content)); + if (this.ctype == CTYPE_BSON_RESULT && this.content.length > SncpRequest.HEADER_SIZE) { + int offset = SncpRequest.HEADER_SIZE + 1; //循环占位符 + Object rs = BsonConvert.root().convertFrom(Object.class, this.content, offset, this.content.length - offset); + sb.append(",\"content\":").append(rs); + } else if (this.ctype == CTYPE_HTTP_REQUEST) { + HttpSimpleRequest req = HttpSimpleRequestCoder.getInstance().decode(this.content); + if (req != null) { + if (req.getCurrentUserid() == 0) req.setCurrentUserid(this.userid); + if (req.getHashid() == 0) req.setHashid(this.hash()); + } + sb.append(",\"content\":").append(req); } else if (this.ctype == CTYPE_HTTP_RESULT) { sb.append(",\"content\":").append(HttpResultCoder.getInstance().decode(this.content)); } else if (localattach != null) { diff --git a/src/org/redkale/mq/SncpMessageClient.java b/src/org/redkale/mq/SncpMessageClient.java index 43de5e0e5..471961769 100644 --- a/src/org/redkale/mq/SncpMessageClient.java +++ b/src/org/redkale/mq/SncpMessageClient.java @@ -53,4 +53,9 @@ public class SncpMessageClient extends MessageClient { return sendMessage(message, true, counter); } + @Override + protected MessageRecord formatRespMessage(MessageRecord message) { + if (message != null) message.ctype = MessageRecord.CTYPE_BSON_RESULT; + return message; + } } diff --git a/src/org/redkale/mq/SncpMessageProcessor.java b/src/org/redkale/mq/SncpMessageProcessor.java index f6569b3e4..8174f0544 100644 --- a/src/org/redkale/mq/SncpMessageProcessor.java +++ b/src/org/redkale/mq/SncpMessageProcessor.java @@ -10,9 +10,9 @@ import java.util.logging.*; import org.redkale.boot.NodeSncpServer; import org.redkale.net.sncp.*; import org.redkale.service.Service; -import org.redkale.util.ThreadHashExecutor; /** + * 一个Service对应一个MessageProcessor * *

* 详情见: https://redkale.org @@ -37,8 +37,6 @@ public class SncpMessageProcessor implements MessageProcessor { protected final NodeSncpServer server; - protected final ThreadHashExecutor workExecutor; - protected final Service service; protected final SncpServlet servlet; @@ -51,7 +49,7 @@ public class SncpMessageProcessor implements MessageProcessor { if (cdl != null) cdl.countDown(); }; - public SncpMessageProcessor(Logger logger, ThreadHashExecutor workExecutor, MessageClient messageClient, MessageProducers producer, NodeSncpServer server, Service service, SncpServlet servlet) { + public SncpMessageProcessor(Logger logger, SncpMessageClient messageClient, MessageProducers producer, NodeSncpServer server, Service service, SncpServlet servlet) { this.logger = logger; this.finest = logger.isLoggable(Level.FINEST); this.finer = logger.isLoggable(Level.FINER); @@ -61,22 +59,17 @@ public class SncpMessageProcessor implements MessageProcessor { this.server = server; this.service = service; this.servlet = servlet; - this.workExecutor = workExecutor; } @Override public void begin(final int size, long starttime) { this.starttime = starttime; - if (this.workExecutor != null) this.cdl = new CountDownLatch(size); + this.cdl = new CountDownLatch(size); } @Override public void process(final MessageRecord message, final Runnable callback) { - if (this.workExecutor == null) { - execute(message, innerCallback); - } else { - this.workExecutor.execute(message.hash(), () -> execute(message, innerCallback)); - } + execute(message, innerCallback); } private void execute(final MessageRecord message, final Runnable callback) { @@ -87,15 +80,16 @@ public class SncpMessageProcessor implements MessageProcessor { long e = now - starttime; SncpContext context = server.getSncpServer().getContext(); SncpMessageRequest request = new SncpMessageRequest(context, message); - response = new SncpMessageResponse(context, request, callback, null, messageClient, producer.getProducer(message)); - servlet.execute(request, response); + response = new SncpMessageResponse(context, request, callback, messageClient, producer.getProducer(message)); + + context.execute(servlet, request, response); long o = System.currentTimeMillis() - now; if ((cha > 1000 || e > 100 || o > 1000) && fine) { - logger.log(Level.FINE, "SncpMessageProcessor.process (mqs.delays = " + cha + " ms, mqs.blocks = " + e + " ms, mqs.executes = " + o + " ms, works=" + (workExecutor != null ? workExecutor.size() : 0) + ") message: " + message); + logger.log(Level.FINE, "SncpMessageProcessor.process (mqs.delays = " + cha + " ms, mqs.blocks = " + e + " ms, mqs.executes = " + o + " ms) message: " + message); } else if ((cha > 50 || e > 10 || o > 50) && finer) { - logger.log(Level.FINER, "SncpMessageProcessor.process (mq.delays = " + cha + " ms, mq.blocks = " + e + " ms, mq.executes = " + o + " ms, works=" + (workExecutor != null ? workExecutor.size() : 0) + ") message: " + message); + logger.log(Level.FINER, "SncpMessageProcessor.process (mq.delays = " + cha + " ms, mq.blocks = " + e + " ms, mq.executes = " + o + " ms) message: " + message); } else if (finest) { - logger.log(Level.FINEST, "SncpMessageProcessor.process (mq.delay = " + cha + " ms, mq.block = " + e + " ms, mq.execute = " + o + " ms, works=" + (workExecutor != null ? workExecutor.size() : 0) + ") message: " + message); + logger.log(Level.FINEST, "SncpMessageProcessor.process (mq.delay = " + cha + " ms, mq.block = " + e + " ms, mq.execute = " + o + " ms) message: " + message); } } catch (Throwable ex) { if (response != null) response.finish(SncpResponse.RETCODE_ILLSERVICEID, null); diff --git a/src/org/redkale/mq/SncpMessageRequest.java b/src/org/redkale/mq/SncpMessageRequest.java index b7f02b8f8..75b6639a6 100644 --- a/src/org/redkale/mq/SncpMessageRequest.java +++ b/src/org/redkale/mq/SncpMessageRequest.java @@ -28,7 +28,9 @@ public class SncpMessageRequest extends SncpRequest { public SncpMessageRequest(SncpContext context, MessageRecord message) { super(context); this.message = message; - readHeader(ByteBuffer.wrap(message.getContent())); + this.hashid = this.message.hash(); + this.createtime = System.currentTimeMillis(); + readHeader(ByteBuffer.wrap(message.getContent()), null); } @Override //被SncpAsyncHandler.sncp_setParams调用 diff --git a/src/org/redkale/mq/SncpMessageResponse.java b/src/org/redkale/mq/SncpMessageResponse.java index 626d11969..78d935754 100644 --- a/src/org/redkale/mq/SncpMessageResponse.java +++ b/src/org/redkale/mq/SncpMessageResponse.java @@ -5,12 +5,10 @@ */ package org.redkale.mq; -import java.nio.ByteBuffer; import org.redkale.convert.bson.BsonWriter; -import org.redkale.net.Response; import org.redkale.net.sncp.*; import static org.redkale.net.sncp.SncpRequest.HEADER_SIZE; -import org.redkale.util.ObjectPool; +import org.redkale.util.ByteArray; /** * @@ -31,16 +29,16 @@ public class SncpMessageResponse extends SncpResponse { protected Runnable callback; - public SncpMessageResponse(SncpContext context, SncpMessageRequest request, Runnable callback, ObjectPool responsePool, MessageClient messageClient, MessageProducer producer) { - super(context, request, responsePool); + public SncpMessageResponse(SncpContext context, SncpMessageRequest request, Runnable callback, MessageClient messageClient, MessageProducer producer) { + super(context, request); this.message = request.message; this.callback = callback; this.messageClient = messageClient; this.producer = producer; } - public SncpMessageResponse(SncpContext context, MessageRecord message, Runnable callback, ObjectPool responsePool, MessageClient messageClient, MessageProducer producer) { - super(context, new SncpMessageRequest(context, message), responsePool); + public SncpMessageResponse(SncpContext context, MessageRecord message, Runnable callback, MessageClient messageClient, MessageProducer producer) { + super(context, new SncpMessageRequest(context, message)); this.message = message; this.callback = callback; this.messageClient = messageClient; @@ -51,14 +49,14 @@ public class SncpMessageResponse extends SncpResponse { public void finish(final int retcode, final BsonWriter out) { if (callback != null) callback.run(); if (out == null) { - final byte[] result = new byte[SncpRequest.HEADER_SIZE]; - fillHeader(ByteBuffer.wrap(result), 0, retcode); + final ByteArray result = new ByteArray(SncpRequest.HEADER_SIZE); + fillHeader(result, 0, retcode); producer.apply(messageClient.createMessageRecord(message.getSeqid(), message.getResptopic(), null, (byte[]) null)); return; } final int respBodyLength = out.count(); //body总长度 - final byte[] result = out.toArray(); - fillHeader(ByteBuffer.wrap(result), respBodyLength - HEADER_SIZE, retcode); - producer.apply(messageClient.createMessageRecord(message.getSeqid(), message.getResptopic(), null, result)); + final ByteArray result = out.toByteArray(); + fillHeader(result, respBodyLength - HEADER_SIZE, retcode); + producer.apply(messageClient.createMessageRecord(message.getSeqid(), message.getResptopic(), null, result.getBytes())); } } diff --git a/src/org/redkale/net/AioTcpAsyncConnection.java b/src/org/redkale/net/AsyncAioTcpConnection.java similarity index 92% rename from src/org/redkale/net/AioTcpAsyncConnection.java rename to src/org/redkale/net/AsyncAioTcpConnection.java index 12e73eab8..d6d3f27f2 100644 --- a/src/org/redkale/net/AioTcpAsyncConnection.java +++ b/src/org/redkale/net/AsyncAioTcpConnection.java @@ -22,7 +22,8 @@ import javax.net.ssl.SSLContext; * * @author zhangjx */ -class AioTcpAsyncConnection extends AsyncConnection { +@Deprecated //@since 2.3.0 +class AsyncAioTcpConnection extends AsyncConnection { //private final Semaphore semaphore = new Semaphore(1); private int readTimeoutSeconds; @@ -35,11 +36,11 @@ class AioTcpAsyncConnection extends AsyncConnection { private BlockingQueue writeQueue; - public AioTcpAsyncConnection(Supplier bufferSupplier, Consumer bufferConsumer, + public AsyncAioTcpConnection(int bufferCapacity, Supplier bufferSupplier, Consumer bufferConsumer, final AsynchronousSocketChannel ch, final SSLContext sslContext, final SocketAddress addr0, final int readTimeoutSeconds, final int writeTimeoutSeconds, final AtomicLong livingCounter, final AtomicLong closedCounter) { - super(bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); + super(false, bufferCapacity, bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); this.channel = ch; this.readTimeoutSeconds = readTimeoutSeconds; this.writeTimeoutSeconds = writeTimeoutSeconds; @@ -135,17 +136,6 @@ class AioTcpAsyncConnection extends AsyncConnection { } private void write(boolean acquire, ByteBuffer src, A attachment, CompletionHandler handler) { -// if (acquire && !semaphore.tryAcquire()) { -// if (this.writeQueue == null) { -// synchronized (semaphore) { -// if (this.writeQueue == null) { -// this.writeQueue = new LinkedBlockingDeque<>(); -// } -// } -// } -// this.writeQueue.add(new WriteEntry(src, attachment, handler)); -// return; -// } WriteOneCompletionHandler newHandler = new WriteOneCompletionHandler(src, handler); if (!channel.isOpen()) { newHandler.failed(new ClosedChannelException(), attachment); @@ -282,6 +272,8 @@ class AioTcpAsyncConnection extends AsyncConnection { @Override public final void close() throws IOException { super.close(); + channel.shutdownInput(); + channel.shutdownOutput(); channel.close(); BlockingQueue queue = this.writeQueue; if (queue == null) return; @@ -306,6 +298,11 @@ class AioTcpAsyncConnection extends AsyncConnection { return true; } + @Override + protected void continueRead() { + throw new UnsupportedOperationException("Not supported yet."); + } + private class WriteMoreCompletionHandler implements CompletionHandler { private final CompletionHandler writeHandler; diff --git a/src/org/redkale/net/AioTcpProtocolServer.java b/src/org/redkale/net/AsyncAioTcpProtocolServer.java similarity index 73% rename from src/org/redkale/net/AioTcpProtocolServer.java rename to src/org/redkale/net/AsyncAioTcpProtocolServer.java index ab0701b6d..897f37ca3 100644 --- a/src/org/redkale/net/AioTcpProtocolServer.java +++ b/src/org/redkale/net/AsyncAioTcpProtocolServer.java @@ -10,8 +10,9 @@ import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.*; import java.util.logging.Level; +import org.redkale.boot.Application; import org.redkale.util.*; /** @@ -22,20 +23,36 @@ import org.redkale.util.*; * * @author zhangjx */ -public class AioTcpProtocolServer extends ProtocolServer { +@Deprecated //@since 2.3.0 +class AsyncAioTcpProtocolServer extends ProtocolServer { + + //创建数 + private final AtomicLong createCounter = new AtomicLong(); + + //关闭数 + private final AtomicLong closedCounter = new AtomicLong(); + + //在线数 + private final AtomicLong livingCounter = new AtomicLong(); private AsynchronousChannelGroup group; private AsynchronousServerSocketChannel serverChannel; - public AioTcpProtocolServer(Context context) { + public AsyncAioTcpProtocolServer(Context context) { super(context); } @Override public void open(AnyValue config) throws IOException { //group = AsynchronousChannelGroup.withThreadPool(context.executor); - group = AsynchronousChannelGroup.withFixedThreadPool(context.executor.getCorePoolSize(), context.executor.getThreadFactory()); + final AtomicInteger workcounter = new AtomicInteger(); + group = AsynchronousChannelGroup.withFixedThreadPool(Runtime.getRuntime().availableProcessors(), (Runnable r) -> { + int c = workcounter.incrementAndGet(); + String threadname = "Redkale-AioThread-" + (c > 9 ? c : ("0" + c)); + Thread t = new WorkThread(threadname, application.getWorkExecutor(), r); + return t; + }); this.serverChannel = AsynchronousServerSocketChannel.open(group); final Set> options = this.serverChannel.supportedOptions(); @@ -72,7 +89,7 @@ public class AioTcpProtocolServer extends ProtocolServer { } @Override - public void accept(Server server) throws IOException { + public void accept(Application application, Server server) throws IOException { AtomicLong createBufferCounter = new AtomicLong(); AtomicLong cycleBufferCounter = new AtomicLong(); ObjectPool bufferPool = server.createBufferPool(createBufferCounter, cycleBufferCounter, server.bufferPoolSize); @@ -101,10 +118,9 @@ public class AioTcpProtocolServer extends ProtocolServer { channel.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024); channel.setOption(StandardSocketOptions.SO_SNDBUF, 16 * 1024); - AsyncConnection conn = new AioTcpAsyncConnection(bufferPool, bufferPool, channel, + AsyncConnection conn = new AsyncAioTcpConnection(server.bufferCapacity, bufferPool, bufferPool, channel, context.getSSLContext(), null, context.readTimeoutSeconds, context.writeTimeoutSeconds, livingCounter, closedCounter); - //context.runAsync(new PrepareRunner(context, responsePool, conn, null, null)); - new PrepareRunner(context, responsePool, conn, null, null).run(); + new ProtocolCodec(context, responsePool, responsePool, conn).run(null); } catch (Throwable e) { context.logger.log(Level.INFO, channel + " accept error", e); } @@ -118,6 +134,21 @@ public class AioTcpProtocolServer extends ProtocolServer { }); } + @Override + public long getCreateConnectionCount() { + return this.createCounter.get(); + } + + @Override + public long getClosedConnectionCount() { + return this.closedCounter.get(); + } + + @Override + public long getLivingConnectionCount() { + return this.livingCounter.get(); + } + @Override public void close() throws IOException { this.serverChannel.close(); diff --git a/src/org/redkale/net/AsyncConnection.java b/src/org/redkale/net/AsyncConnection.java index 094f0451b..85e52597e 100644 --- a/src/org/redkale/net/AsyncConnection.java +++ b/src/org/redkale/net/AsyncConnection.java @@ -10,7 +10,6 @@ import java.net.*; import java.nio.*; import java.nio.channels.*; import java.util.*; -import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.function.*; import javax.net.ssl.SSLContext; @@ -35,10 +34,18 @@ public abstract class AsyncConnection implements AutoCloseable { protected volatile long writetime; + protected final boolean client; + + protected final int bufferCapacity; + private final Supplier bufferSupplier; private final Consumer bufferConsumer; + private ByteBufferWriter pipelineWriter; + + private PipelineDataNode pipelineDataNode; + private ByteBuffer readBuffer; //在线数 @@ -52,15 +59,20 @@ public abstract class AsyncConnection implements AutoCloseable { //关联的事件数, 小于1表示没有事件 private final AtomicInteger eventing = new AtomicInteger(); - protected AsyncConnection(ObjectPool bufferPool, SSLContext sslContext, + //用于服务端的Socket, 等同于一直存在的readCompletionHandler + ProtocolCodec protocolCodec; + + protected AsyncConnection(boolean client, final int bufferCapacity, ObjectPool bufferPool, SSLContext sslContext, final AtomicLong livingCounter, final AtomicLong closedCounter) { - this(bufferPool, bufferPool, sslContext, livingCounter, closedCounter); + this(client, bufferCapacity, bufferPool, bufferPool, sslContext, livingCounter, closedCounter); } - protected AsyncConnection(Supplier bufferSupplier, Consumer bufferConsumer, + protected AsyncConnection(boolean client, final int bufferCapacity, Supplier bufferSupplier, Consumer bufferConsumer, SSLContext sslContext, final AtomicLong livingCounter, final AtomicLong closedCounter) { Objects.requireNonNull(bufferSupplier); Objects.requireNonNull(bufferConsumer); + this.client = client; + this.bufferCapacity = bufferCapacity; this.bufferSupplier = bufferSupplier; this.bufferConsumer = bufferConsumer; this.sslContext = sslContext; @@ -92,6 +104,8 @@ public abstract class AsyncConnection implements AutoCloseable { return eventing.decrementAndGet(); } + protected abstract void continueRead(); + public abstract boolean isOpen(); public abstract boolean isTCP(); @@ -118,16 +132,77 @@ public abstract class AsyncConnection implements AutoCloseable { public abstract ReadableByteChannel readableByteChannel(); - public abstract void read(CompletionHandler handler); - public abstract WritableByteChannel writableByteChannel(); + public abstract void read(CompletionHandler handler); + + //src会写完才会回调 public abstract void write(ByteBuffer src, A attachment, CompletionHandler handler); + //srcs会写完才会回调 public final void write(ByteBuffer[] srcs, A attachment, CompletionHandler handler) { write(srcs, 0, srcs.length, attachment, handler); } + public final void write(byte[] bytes, CompletionHandler handler) { + write(bytes, 0, bytes.length, null, 0, 0, handler); + } + + public final void write(ByteTuple array, CompletionHandler handler) { + write(array.content(), array.offset(), array.length(), null, 0, 0, handler); + } + + public final void write(byte[] bytes, int offset, int length, CompletionHandler handler) { + write(bytes, offset, length, null, 0, 0, handler); + } + + public final void write(ByteTuple header, ByteTuple body, CompletionHandler handler) { + write(header.content(), header.offset(), header.length(), body == null ? null : body.content(), body == null ? 0 : body.offset(), body == null ? 0 : body.length(), handler); + } + + public void write(byte[] headerContent, int headerOffset, int headerLength, byte[] bodyContent, int bodyOffset, int bodyLength, CompletionHandler handler) { + final ByteBuffer buffer = pollWriteBuffer(); + if (buffer.remaining() >= headerLength + bodyLength) { + buffer.put(headerContent, headerOffset, headerLength); + if (bodyLength > 0) buffer.put(bodyContent, bodyOffset, bodyLength); + buffer.flip(); + CompletionHandler newhandler = new CompletionHandler() { + @Override + public void completed(Integer result, Void attachment) { + offerBuffer(buffer); + handler.completed(result, attachment); + } + + @Override + public void failed(Throwable exc, Void attachment) { + offerBuffer(buffer); + handler.failed(exc, attachment); + } + }; + write(buffer, null, newhandler); + } else { + ByteBufferWriter writer = ByteBufferWriter.create(bufferSupplier, buffer); + writer.put(headerContent, headerOffset, headerLength); + if (bodyLength > 0) writer.put(bodyContent, bodyOffset, bodyLength); + final ByteBuffer[] buffers = writer.toBuffers(); + CompletionHandler newhandler = new CompletionHandler() { + @Override + public void completed(Integer result, Void attachment) { + offerBuffer(buffers); + handler.completed(result, attachment); + } + + @Override + public void failed(Throwable exc, Void attachment) { + offerBuffer(buffers); + handler.failed(exc, attachment); + } + }; + write(buffers, null, newhandler); + } + } + + //srcs会写完才会回调 public abstract void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler); public void setReadBuffer(ByteBuffer buffer) { @@ -135,11 +210,139 @@ public abstract class AsyncConnection implements AutoCloseable { this.readBuffer = buffer; } - public static class Message { + public boolean hasPipelineData() { + ByteBufferWriter writer = this.pipelineWriter; + return writer != null && writer.position() > 0; + } - public String value; + public final void flushPipelineData(CompletionHandler handler) { + flushPipelineData(null, handler); + } - public Message() { + public void flushPipelineData(A attachment, CompletionHandler handler) { + ByteBufferWriter writer = this.pipelineWriter; + this.pipelineWriter = null; + if (writer == null) { + handler.completed(0, attachment); + } else { + ByteBuffer[] srcs = writer.toBuffers(); + CompletionHandler newhandler = new CompletionHandler() { + @Override + public void completed(Integer result, A attachment) { + offerBuffer(srcs); + handler.completed(result, attachment); + } + + @Override + public void failed(Throwable exc, A attachment) { + offerBuffer(srcs); + handler.failed(exc, attachment); + } + }; + if (srcs.length == 1) { + write(srcs[0], attachment, newhandler); + } else { + write(srcs, attachment, newhandler); + } + } + } + + //返回 是否over + public final boolean writePipelineData(int pipelineIndex, int pipelineCount, ByteTuple array) { + return writePipelineData(pipelineIndex, pipelineCount, array.content(), array.offset(), array.length()); + } + + //返回 是否over + public boolean writePipelineData(int pipelineIndex, int pipelineCount, byte[] bs, int offset, int length) { + synchronized (this) { + ByteBufferWriter writer = this.pipelineWriter; + if (writer == null) { + writer = ByteBufferWriter.create(getBufferSupplier()); + this.pipelineWriter = writer; + } + if (this.pipelineDataNode == null && pipelineIndex == writer.getWriteBytesCounter() + 1) { + writer.put(bs, offset, length); + return (pipelineIndex == pipelineCount); + } else { + PipelineDataNode dataNode = this.pipelineDataNode; + if (dataNode == null) { + dataNode = new PipelineDataNode(); + this.pipelineDataNode = dataNode; + } + if (pipelineIndex == pipelineCount) { //此时pipelineCount为最大值 + dataNode.pipelineCount = pipelineCount; + } + dataNode.put(pipelineIndex, bs, offset, length); + if (writer.getWriteBytesCounter() + dataNode.itemsize == dataNode.pipelineCount) { + for (PipelineDataItem item : dataNode.arrayItems()) { + writer.put(item.data); + } + this.pipelineDataNode = null; + return true; + } + return false; + } + } + } + + private static class PipelineDataNode { + + public int pipelineCount; + + public int itemsize; + + private PipelineDataItem head; + + private PipelineDataItem tail; + + public PipelineDataItem[] arrayItems() { + PipelineDataItem[] items = new PipelineDataItem[itemsize]; + PipelineDataItem item = head; + int i = 0; + while (item != null) { + items[i] = item; + item = item.next; + items[i].next = null; + i++; + } + Arrays.sort(items); + return items; + } + + public void put(int pipelineIndex, byte[] bs, int offset, int length) { + if (tail == null) { + head = new PipelineDataItem(pipelineIndex, bs, offset, length); + tail = head; + } else { + PipelineDataItem item = new PipelineDataItem(pipelineIndex, bs, offset, length); + tail.next = item; + tail = item; + } + itemsize++; + } + } + + private static class PipelineDataItem implements Comparable { + + final byte[] data; + + final int index; + + public PipelineDataItem next; + + public PipelineDataItem(int index, byte[] bs, int offset, int length) { + this.index = index; + this.data = Arrays.copyOfRange(bs, offset, offset + length); + } + + @Override + public int compareTo(PipelineDataItem o) { + return this.index - o.index; + } + + @Override + public String toString() { + return "{\"index\":" + index + "}"; } } @@ -199,11 +402,7 @@ public abstract class AsyncConnection implements AutoCloseable { } if (this.readBuffer != null) { Consumer consumer = this.bufferConsumer; -// Thread thread = Thread.currentThread(); -// if (thread instanceof IOThread) { -// consumer = ((IOThread) thread).getBufferPool(); -// } - consumer.accept(this.readBuffer); + if (consumer != null) consumer.accept(this.readBuffer); } if (attributes == null) return; try { @@ -245,155 +444,4 @@ public abstract class AsyncConnection implements AutoCloseable { if (this.attributes != null) this.attributes.clear(); } - //------------------------------------------------------------------------------------------------------------------------------ - /** - * 创建TCP协议客户端连接 - * - * @param bufferPool ByteBuffer对象池 - * @param address 连接点子 - * @param group 连接AsynchronousChannelGroup - * @param readTimeoutSeconds 读取超时秒数 - * @param writeTimeoutSeconds 写入超时秒数 - * - * @return 连接CompletableFuture - */ - public static CompletableFuture createTCP(final ObjectPool bufferPool, final AsynchronousChannelGroup group, - final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds) { - return createTCP(bufferPool, group, null, address, readTimeoutSeconds, writeTimeoutSeconds); - } - - /** - * 创建TCP协议客户端连接 - * - * @param bufferPool ByteBuffer对象池 - * @param address 连接点子 - * @param sslContext SSLContext - * @param group 连接AsynchronousChannelGroup - * @param readTimeoutSeconds 读取超时秒数 - * @param writeTimeoutSeconds 写入超时秒数 - * - * @return 连接CompletableFuture - */ - public static CompletableFuture createTCP(final ObjectPool bufferPool, final AsynchronousChannelGroup group, final SSLContext sslContext, - final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds) { - return createTCP(bufferPool, bufferPool, group, sslContext, address, readTimeoutSeconds, writeTimeoutSeconds); - } - - /** - * 创建TCP协议客户端连接 - * - * @param bufferSupplier ByteBuffer生产器 - * @param bufferConsumer ByteBuffer回收器 - * @param address 连接点子 - * @param sslContext SSLContext - * @param group 连接AsynchronousChannelGroup - * @param readTimeoutSeconds 读取超时秒数 - * @param writeTimeoutSeconds 写入超时秒数 - * - * @return 连接CompletableFuture - */ - public static CompletableFuture createTCP(final Supplier bufferSupplier, Consumer bufferConsumer, final AsynchronousChannelGroup group, final SSLContext sslContext, - final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds) { - final CompletableFuture future = new CompletableFuture<>(); - try { - final AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(group); - try { - channel.setOption(StandardSocketOptions.TCP_NODELAY, true); - channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); - channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); - } catch (IOException e) { - } - channel.connect(address, null, new CompletionHandler() { - @Override - public void completed(Void result, Void attachment) { - future.complete(new AioTcpAsyncConnection(bufferSupplier, bufferConsumer, channel, sslContext, address, readTimeoutSeconds, writeTimeoutSeconds, null, null)); - } - - @Override - public void failed(Throwable exc, Void attachment) { - future.completeExceptionally(exc); - } - }); - } catch (IOException e) { - future.completeExceptionally(e); - } - return future; - } - -// public static AsyncConnection create(final Socket socket) { -// return create(socket, null, 0, 0); -// } -// public static AsyncConnection create(final Socket socket, final SocketAddress addr0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { -// return new TcpBioAsyncConnection(socket, addr0, readTimeoutSecond0, writeTimeoutSecond0, null, null); -// } -// -// public static AsyncConnection create(final Socket socket, final SocketAddress addr0, final int readTimeoutSecond0, -// final int writeTimeoutSecond0, final AtomicLong livingCounter, final AtomicLong closedCounter) { -// return new TcpBioAsyncConnection(socket, addr0, readTimeoutSecond0, writeTimeoutSecond0, livingCounter, closedCounter); -// } -// -// public static AsyncConnection create(final SocketChannel ch, SocketAddress addr, final Selector selector, -// final int readTimeoutSeconds0, final int writeTimeoutSeconds0) { -// return new TcpNioAsyncConnection(ch, addr, selector, readTimeoutSeconds0, writeTimeoutSeconds0, null, null); -// } -// -// public static AsyncConnection create(final SocketChannel ch, final SocketAddress addr0, final Selector selector, final Context context) { -// return new TcpNioAsyncConnection(ch, addr0, selector, context.readTimeoutSeconds, context.writeTimeoutSeconds, null, null); -// } -// -// public static AsyncConnection create(final SocketChannel ch, SocketAddress addr, final Selector selector, -// final int readTimeoutSeconds0, final int writeTimeoutSeconds0, -// final AtomicLong livingCounter, final AtomicLong closedCounter) { -// return new TcpNioAsyncConnection(ch, addr, selector, readTimeoutSeconds0, writeTimeoutSeconds0, livingCounter, closedCounter); -// } - public static AsyncConnection create(final ObjectPool bufferPool, final DatagramChannel ch, - SocketAddress addr, final boolean client0, - final int readTimeoutSeconds0, final int writeTimeoutSeconds0) { - return new BioUdpAsyncConnection(bufferPool, bufferPool, ch, null, addr, client0, readTimeoutSeconds0, writeTimeoutSeconds0, null, null); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final DatagramChannel ch, - SocketAddress addr, final boolean client0, - final int readTimeoutSeconds0, final int writeTimeoutSeconds0, - final AtomicLong livingCounter, final AtomicLong closedCounter) { - return new BioUdpAsyncConnection(bufferPool, bufferPool, ch, null, addr, client0, readTimeoutSeconds0, writeTimeoutSeconds0, livingCounter, closedCounter); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final DatagramChannel ch, SSLContext sslContext, - SocketAddress addr, final boolean client0, - final int readTimeoutSeconds0, final int writeTimeoutSeconds0) { - return new BioUdpAsyncConnection(bufferPool, bufferPool, ch, sslContext, addr, client0, readTimeoutSeconds0, writeTimeoutSeconds0, null, null); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final DatagramChannel ch, SSLContext sslContext, - SocketAddress addr, final boolean client0, - final int readTimeoutSeconds0, final int writeTimeoutSeconds0, - final AtomicLong livingCounter, final AtomicLong closedCounter) { - return new BioUdpAsyncConnection(bufferPool, bufferPool, ch, sslContext, addr, client0, readTimeoutSeconds0, writeTimeoutSeconds0, livingCounter, closedCounter); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final AsynchronousSocketChannel ch) { - return create(bufferPool, ch, null, 0, 0); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final AsynchronousSocketChannel ch, - final SocketAddress addr0, final int readTimeoutSeconds, final int writeTimeoutSeconds) { - return new AioTcpAsyncConnection(bufferPool, bufferPool, ch, null, addr0, readTimeoutSeconds, writeTimeoutSeconds, null, null); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final AsynchronousSocketChannel ch, SSLContext sslContext, - final SocketAddress addr0, final int readTimeoutSeconds, final int writeTimeoutSeconds) { - return new AioTcpAsyncConnection(bufferPool, bufferPool, ch, sslContext, addr0, readTimeoutSeconds, writeTimeoutSeconds, null, null); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final AsynchronousSocketChannel ch, - final SocketAddress addr0, final int readTimeoutSeconds, final int writeTimeoutSeconds, final AtomicLong livingCounter, final AtomicLong closedCounter) { - return new AioTcpAsyncConnection(bufferPool, bufferPool, ch, null, addr0, readTimeoutSeconds, writeTimeoutSeconds, livingCounter, closedCounter); - } - - public static AsyncConnection create(final ObjectPool bufferPool, final AsynchronousSocketChannel ch, SSLContext sslContext, - final SocketAddress addr0, final int readTimeoutSeconds, final int writeTimeoutSeconds, final AtomicLong livingCounter, final AtomicLong closedCounter) { - return new AioTcpAsyncConnection(bufferPool, bufferPool, ch, sslContext, addr0, readTimeoutSeconds, writeTimeoutSeconds, livingCounter, closedCounter); - } - } diff --git a/src/org/redkale/net/AsyncGroup.java b/src/org/redkale/net/AsyncGroup.java new file mode 100644 index 000000000..cd7e16522 --- /dev/null +++ b/src/org/redkale/net/AsyncGroup.java @@ -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.net; + +import java.net.SocketAddress; +import java.util.concurrent.*; + +/** + * Client模式的AsyncConnection连接构造器 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.3.0 + */ +public abstract class AsyncGroup { + + public CompletableFuture createTCP(final SocketAddress address) { + return createTCP(address, 0, 0); + } + + public abstract CompletableFuture createTCP(final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds); + + public CompletableFuture createUDP(final SocketAddress address) { + return createUDP(address, 0, 0); + } + + public abstract CompletableFuture createUDP(final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds); + + public CompletableFuture create(final boolean tcp, final SocketAddress address) { + return tcp ? createTCP(address) : createUDP(address); + } + + public CompletableFuture create(final boolean tcp, final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds) { + return tcp ? createTCP(address, readTimeoutSeconds, writeTimeoutSeconds) : createUDP(address, readTimeoutSeconds, writeTimeoutSeconds); + } + + public abstract ScheduledFuture scheduleTimeout(Runnable callable, long delay, TimeUnit unit); +} diff --git a/src/org/redkale/net/AsyncIOGroup.java b/src/org/redkale/net/AsyncIOGroup.java new file mode 100644 index 000000000..27eb8b1c4 --- /dev/null +++ b/src/org/redkale/net/AsyncIOGroup.java @@ -0,0 +1,217 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net; + +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import org.redkale.util.*; + +/** + * 协议处理的IO线程组 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.1.0 + */ +@ResourceType(AsyncGroup.class) +public class AsyncIOGroup extends AsyncGroup { + + private boolean started; + + private boolean closed; + + AsyncIOThread[] ioThreads; + + private AsyncIOThread connectThread; + + final int bufferCapacity; + + private final AtomicInteger readIndex = new AtomicInteger(); + + //创建数 + final AtomicLong connCreateCounter = new AtomicLong(); + + //关闭数 + final AtomicLong connLivingCounter = new AtomicLong(); + + //在线数 + final AtomicLong connClosedCounter = new AtomicLong(); + + private ScheduledThreadPoolExecutor timeoutExecutor; + + public AsyncIOGroup(final int bufferCapacity, final int bufferPoolSize) { + this(null, Runtime.getRuntime().availableProcessors(), bufferCapacity, bufferPoolSize); + } + + public AsyncIOGroup(final ExecutorService workExecutor, + final int iothreads, final int bufferCapacity, final int bufferPoolSize) { + this(null, workExecutor, iothreads, bufferCapacity, ObjectPool.createSafePool(null, null, bufferPoolSize, + (Object... params) -> ByteBuffer.allocateDirect(bufferCapacity), null, (e) -> { + if (e == null || e.isReadOnly() || e.capacity() != bufferCapacity) return false; + e.clear(); + return true; + })); + } + + public AsyncIOGroup(String threadPrefixName0, ExecutorService workExecutor, + int iothreads, final int bufferCapacity, ObjectPool safeBufferPool) { + this.bufferCapacity = bufferCapacity; + final String threadPrefixName = threadPrefixName0 == null ? "Redkale-Client-IOThread" : threadPrefixName0; + this.ioThreads = new AsyncIOThread[Math.max(iothreads, 1)]; + try { + for (int i = 0; i < this.ioThreads.length; i++) { + ObjectPool unsafeBufferPool = ObjectPool.createUnsafePool(safeBufferPool, safeBufferPool.getCreatCounter(), + safeBufferPool.getCycleCounter(), 512, safeBufferPool.getCreator(), safeBufferPool.getPrepare(), safeBufferPool.getRecycler()); + String name = threadPrefixName + "-" + (i >= 9 ? (i + 1) : ("0" + (i + 1))); + + this.ioThreads[i] = new AsyncIOThread(true, name, workExecutor, Selector.open(), unsafeBufferPool, safeBufferPool); + } + { + ObjectPool unsafeBufferPool = ObjectPool.createUnsafePool(safeBufferPool, safeBufferPool.getCreatCounter(), + safeBufferPool.getCycleCounter(), 512, safeBufferPool.getCreator(), safeBufferPool.getPrepare(), safeBufferPool.getRecycler()); + String name = threadPrefixName.replace("ServletThread", "ConnectThread").replace("Redkale-Client-IOThread", "Redkale-Client-ConnectThread"); + this.connectThread = new AsyncIOThread(false, name, workExecutor, Selector.open(), unsafeBufferPool, safeBufferPool); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + this.timeoutExecutor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1, (Runnable r) -> { + Thread t = new Thread(r); + t.setName(threadPrefixName + "-Timeout"); + t.setDaemon(true); + return t; + }); + } + + public int size() { + return this.ioThreads.length; + } + + public void start() { + if (started) return; + if (closed) throw new RuntimeException("group is closed"); + for (AsyncIOThread thread : ioThreads) { + thread.start(); + } + connectThread.start(); + started = true; + } + + public void close() { + if (closed) return; + for (AsyncIOThread thread : ioThreads) { + thread.close(); + } + connectThread.close(); + this.timeoutExecutor.shutdownNow(); + closed = true; + } + + public AtomicLong getCreateConnectionCount() { + return connCreateCounter; + } + + public AtomicLong getClosedConnectionCount() { + return connLivingCounter; + } + + public AtomicLong getLivingConnectionCount() { + return connClosedCounter; + } + + public AsyncIOThread nextIOThread() { + return ioThreads[Math.abs(readIndex.getAndIncrement()) % ioThreads.length]; + } + + public AsyncIOThread connectThread() { + return connectThread; + } + + @Override + public ScheduledFuture scheduleTimeout(Runnable callable, long delay, TimeUnit unit) { + return timeoutExecutor.schedule(callable, delay, unit); + } + + public void interestOpsOr(AsyncIOThread thread, SelectionKey key, int opt) { + if (key == null) return; + if (key.selector() != thread.selector) throw new RuntimeException("NioThread.selector not the same to SelectionKey.selector"); + if ((key.interestOps() & opt) != 0) return; + key.interestOps(key.interestOps() | opt); + if (thread.inCurrThread()) return; + //非IO线程中 + key.selector().wakeup(); + } + + @Override + public CompletableFuture createTCP(final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds) { + SocketChannel channel; + try { + channel = SocketChannel.open(); + channel.configureBlocking(false); + channel.setOption(StandardSocketOptions.TCP_NODELAY, true); + channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); + channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); + } catch (IOException e) { + CompletableFuture future = new CompletableFuture(); + future.completeExceptionally(e); + return future; + } + AsyncIOThread readThread = nextIOThread(); + final AsyncNioTcpConnection conn = new AsyncNioTcpConnection(true, this, readThread, connectThread, channel, null, address, connLivingCounter, connClosedCounter); + final CompletableFuture future = new CompletableFuture<>(); + conn.connect(address, null, new CompletionHandler() { + @Override + public void completed(Void result, Void attachment) { + conn.setReadTimeoutSeconds(readTimeoutSeconds); + conn.setWriteTimeoutSeconds(writeTimeoutSeconds); + connCreateCounter.incrementAndGet(); + connLivingCounter.incrementAndGet(); + future.complete(conn); + } + + @Override + public void failed(Throwable exc, Void attachment) { + future.completeExceptionally(exc); + } + }); + return Utility.orTimeout(future, 30, TimeUnit.SECONDS); + } + + @Override + public CompletableFuture createUDP(final SocketAddress address, final int readTimeoutSeconds, final int writeTimeoutSeconds) { + DatagramChannel channel; + try { + channel = DatagramChannel.open(); + } catch (IOException e) { + CompletableFuture future = new CompletableFuture(); + future.completeExceptionally(e); + return future; + } + AsyncIOThread readThread = nextIOThread(); + AsyncNioUdpConnection conn = new AsyncNioUdpConnection(true, this, readThread, connectThread, channel, null, address, connLivingCounter, connClosedCounter); + CompletableFuture future = new CompletableFuture(); + conn.connect(address, null, new CompletionHandler() { + @Override + public void completed(Void result, Void attachment) { + future.complete(conn); + } + + @Override + public void failed(Throwable exc, Void attachment) { + future.completeExceptionally(exc); + } + }); + return future; + } + +} diff --git a/src/org/redkale/net/AsyncIOThread.java b/src/org/redkale/net/AsyncIOThread.java new file mode 100644 index 000000000..a7b57ede7 --- /dev/null +++ b/src/org/redkale/net/AsyncIOThread.java @@ -0,0 +1,133 @@ +/* + * 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.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.*; +import java.util.logging.*; +import org.redkale.util.*; + +/** + * 协议处理的IO线程类 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.1.0 + */ +public class AsyncIOThread extends WorkThread { + + protected static final Logger logger = Logger.getLogger(AsyncIOThread.class.getSimpleName()); + + final Selector selector; + + final AtomicInteger connCounter = new AtomicInteger(); + + private final Supplier bufferSupplier; + + private final Consumer bufferConsumer; + + private final ConcurrentLinkedQueue> registers = new ConcurrentLinkedQueue<>(); + + private boolean closed; + + int invoker = 0; + + public AsyncIOThread(final boolean readable, String name, ExecutorService workExecutor, Selector selector, + ObjectPool unsafeBufferPool, ObjectPool safeBufferPool) { + super(name, workExecutor, null); + this.selector = selector; + this.setDaemon(true); + this.bufferSupplier = () -> (inCurrThread() ? unsafeBufferPool : safeBufferPool).get(); + this.bufferConsumer = (v) -> (inCurrThread() ? unsafeBufferPool : safeBufferPool).accept(v); + } + + public void register(Consumer consumer) { + registers.offer(consumer); + selector.wakeup(); + } + + public Supplier getBufferSupplier() { + return bufferSupplier; + } + + public Consumer getBufferConsumer() { + return bufferConsumer; + } + + public int currConnections() { + return connCounter.get(); + } + + @Override + public void run() { + this.localThread = Thread.currentThread(); + while (!this.closed) { + try { + Consumer register; + while ((register = registers.poll()) != null) { + register.accept(selector); + } + int count = selector.select(); + if (count == 0) continue; + Set keys = selector.selectedKeys(); + Iterator it = keys.iterator(); + while (it.hasNext()) { + SelectionKey key = it.next(); + it.remove(); + invoker = 0; + if (!key.isValid()) continue; + AsyncNioConnection conn = (AsyncNioConnection) key.attachment(); + if (conn.client) { + if (key.isConnectable()) { + key.interestOps(key.interestOps() & ~SelectionKey.OP_CONNECT); + conn.doConnect(); + } else if (key.isReadable()) { + conn.currReadInvoker = 0; + key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); + conn.doRead(true); + } else if (key.isWritable()) { + conn.currWriteInvoker = 0; + key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); + conn.doWrite(true); + } + } else { + if (key.isReadable()) { + conn.currReadInvoker = 0; + //key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); + conn.doRead(true); + } else if (conn.writeCompletionHandler != null && key.isWritable()) { + conn.currWriteInvoker = 0; + key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); + conn.doWrite(true); + } else if (key.isConnectable()) { + key.interestOps(key.interestOps() & ~SelectionKey.OP_CONNECT); + conn.doConnect(); + } + } + } + } catch (Exception ex) { + if (!this.closed) logger.log(Level.FINE, getName() + " selector run failed", ex); + } + } + } + + public void close() { + this.closed = true; + this.interrupt(); + try { + this.selector.close(); + } catch (Exception e) { + logger.log(Level.FINE, getName() + " selector close failed", e); + } + } +} diff --git a/src/org/redkale/net/NioCompletionHandler.java b/src/org/redkale/net/AsyncNioCompletionHandler.java similarity index 62% rename from src/org/redkale/net/NioCompletionHandler.java rename to src/org/redkale/net/AsyncNioCompletionHandler.java index 23f59f7a6..89f33ee51 100644 --- a/src/org/redkale/net/NioCompletionHandler.java +++ b/src/org/redkale/net/AsyncNioCompletionHandler.java @@ -5,6 +5,7 @@ */ package org.redkale.net; +import java.nio.ByteBuffer; import java.nio.channels.CompletionHandler; import java.util.concurrent.*; @@ -17,7 +18,7 @@ import java.util.concurrent.*; * * @since 2.1.0 */ -public class NioCompletionHandler implements CompletionHandler, Runnable { +class AsyncNioCompletionHandler implements CompletionHandler, Runnable { private final CompletionHandler handler; @@ -25,11 +26,20 @@ public class NioCompletionHandler implements CompletionHandler, R public ScheduledFuture timeoutFuture; - public NioCompletionHandler(CompletionHandler handler, A attachment) { + private ByteBuffer[] buffers; + + private AsyncNioConnection conn; + + public AsyncNioCompletionHandler(CompletionHandler handler, A attachment) { this.handler = handler; this.attachment = attachment; } + public void setConnBuffers(AsyncNioConnection conn, ByteBuffer... buffs) { + this.conn = conn; + this.buffers = buffs; + } + public void setAttachment(A attachment) { this.attachment = attachment; } @@ -41,6 +51,9 @@ public class NioCompletionHandler implements CompletionHandler, R this.timeoutFuture = null; future.cancel(true); } + if (conn != null && buffers != null) { + conn.offerBuffer(buffers); + } handler.completed(result, attachment); } @@ -51,11 +64,17 @@ public class NioCompletionHandler implements CompletionHandler, R this.timeoutFuture = null; future.cancel(true); } + if (conn != null && buffers != null) { + conn.offerBuffer(buffers); + } handler.failed(exc, attachment); } @Override public void run() { + if (conn != null && buffers != null) { + conn.offerBuffer(buffers); + } handler.failed(new TimeoutException(), attachment); } diff --git a/src/org/redkale/net/AsyncNioConnection.java b/src/org/redkale/net/AsyncNioConnection.java new file mode 100644 index 000000000..2d9c24f60 --- /dev/null +++ b/src/org/redkale/net/AsyncNioConnection.java @@ -0,0 +1,450 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.*; +import javax.net.ssl.SSLContext; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.3.0 + */ +abstract class AsyncNioConnection extends AsyncConnection { + + protected static final int MAX_INVOKER_ONSTACK = Integer.getInteger("net.invoker.max.onstack", 16); + + final AsyncIOThread ioThread; + + final AsyncIOThread connectThread; + + final AsyncIOGroup ioGroup; + + protected SocketAddress remoteAddress; + + //-------------------------------- 连操作 -------------------------------------- + protected Object connectAttachment; + + protected CompletionHandler connectCompletionHandler; + + protected boolean connectPending; + + protected SelectionKey connectKey; + + //-------------------------------- 读操作 -------------------------------------- + protected int readTimeoutSeconds; + + int currReadInvoker; + + protected ByteBuffer readByteBuffer; + + protected CompletionHandler readCompletionHandler; + + protected boolean readPending; + + protected SelectionKey readKey; + + //-------------------------------- 写操作 -------------------------------------- + protected int writeTimeoutSeconds; + + int currWriteInvoker; + + protected byte[] writeByteTuple1Array; + + protected int writeByteTuple1Offset; + + protected int writeByteTuple1Length; + + protected byte[] writeByteTuple2Array; + + protected int writeByteTuple2Offset; + + protected int writeByteTuple2Length; + + //写操作, 二选一,要么writeByteBuffer有值,要么writeByteBuffers、writeOffset、writeLength有值 + protected ByteBuffer writeByteBuffer; + + protected ByteBuffer[] writeByteBuffers; + + protected int writeOffset; + + protected int writeLength; + + protected Object writeAttachment; + + protected CompletionHandler writeCompletionHandler; + + protected boolean writePending; + + protected SelectionKey writeKey; + + public AsyncNioConnection(boolean client, AsyncIOGroup ioGroup, AsyncIOThread ioThread, AsyncIOThread connectThread, + final int bufferCapacity, ObjectPool bufferPool, SSLContext sslContext, AtomicLong livingCounter, AtomicLong closedCounter) { + super(client, bufferCapacity, bufferPool, sslContext, livingCounter, closedCounter); + this.ioGroup = ioGroup; + this.ioThread = ioThread; + this.connectThread = connectThread; + } + + public AsyncNioConnection(boolean client, AsyncIOGroup ioGroup, AsyncIOThread ioThread, AsyncIOThread connectThread, + final int bufferCapacity, Supplier bufferSupplier, Consumer bufferConsumer, SSLContext sslContext, AtomicLong livingCounter, AtomicLong closedCounter) { + super(client, bufferCapacity, bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); + this.ioGroup = ioGroup; + this.ioThread = ioThread; + this.connectThread = connectThread; + } + + @Override + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public void setReadTimeoutSeconds(int readTimeoutSeconds) { + this.readTimeoutSeconds = readTimeoutSeconds; + } + + @Override + public void setWriteTimeoutSeconds(int writeTimeoutSeconds) { + this.writeTimeoutSeconds = writeTimeoutSeconds; + } + + @Override + public int getReadTimeoutSeconds() { + return this.readTimeoutSeconds; + } + + @Override + public int getWriteTimeoutSeconds() { + return this.writeTimeoutSeconds; + } + + @Override + protected void continueRead() { + if (readKey == null) { + ioThread.register(selector -> { + try { + readKey = implRegister(selector, SelectionKey.OP_READ); + readKey.attach(this); + } catch (ClosedChannelException e) { + handleRead(0, e); + } + }); + } else { + ioGroup.interestOpsOr(ioThread, readKey, SelectionKey.OP_READ); + } + } + + @Override + public void read(CompletionHandler handler) { + Objects.requireNonNull(handler); + if (!this.isConnected()) { + handler.failed(new NotYetConnectedException(), null); + return; + } + if (this.readPending) { + handler.failed(new ReadPendingException(), null); + return; + } + this.readPending = true; + if (this.readTimeoutSeconds > 0) { + AsyncNioCompletionHandler newhandler = new AsyncNioCompletionHandler(handler, this.readByteBuffer); + this.readCompletionHandler = newhandler; + newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.readTimeoutSeconds, TimeUnit.SECONDS); + } else { + this.readCompletionHandler = handler; + } + doRead(currReadInvoker < MAX_INVOKER_ONSTACK && this.ioThread.inCurrThread()); + } + + @Override + public void write(byte[] headerContent, int headerOffset, int headerLength, byte[] bodyContent, int bodyOffset, int bodyLength, CompletionHandler handler) { + Objects.requireNonNull(headerContent); + Objects.requireNonNull(handler); + if (!this.isConnected()) { + handler.failed(new NotYetConnectedException(), null); + return; + } + if (this.writePending) { + handler.failed(new WritePendingException(), null); + return; + } + this.writePending = true; + this.writeByteTuple1Array = headerContent; + this.writeByteTuple1Offset = headerOffset; + this.writeByteTuple1Length = headerLength; + this.writeByteTuple2Array = bodyContent; + this.writeByteTuple2Offset = bodyOffset; + this.writeByteTuple2Length = bodyLength; + this.writeAttachment = null; + if (this.writeTimeoutSeconds > 0) { + AsyncNioCompletionHandler newhandler = new AsyncNioCompletionHandler(handler, null); + this.writeCompletionHandler = newhandler; + newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.writeTimeoutSeconds, TimeUnit.SECONDS); + } else { + this.writeCompletionHandler = new AsyncNioCompletionHandler(handler, null); + } + doWrite(true || !client || currWriteInvoker < MAX_INVOKER_ONSTACK); // this.ioThread.inCurrThread() // !client && ioThread.workExecutor == null + } + + @Override + public void write(ByteBuffer src, A attachment, CompletionHandler handler) { + Objects.requireNonNull(src); + Objects.requireNonNull(handler); + if (!this.isConnected()) { + handler.failed(new NotYetConnectedException(), attachment); + return; + } + if (this.writePending) { + handler.failed(new WritePendingException(), attachment); + return; + } + this.writePending = true; + this.writeByteBuffer = src; + this.writeAttachment = attachment; + if (this.writeTimeoutSeconds > 0) { + AsyncNioCompletionHandler newhandler = new AsyncNioCompletionHandler(handler, attachment); + this.writeCompletionHandler = newhandler; + newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.writeTimeoutSeconds, TimeUnit.SECONDS); + } else { + this.writeCompletionHandler = (CompletionHandler) handler; + } + doWrite(true || !client || currWriteInvoker < MAX_INVOKER_ONSTACK); // this.ioThread.inCurrThread() // !client && ioThread.workExecutor == null + } + + @Override + public void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler) { + Objects.requireNonNull(srcs); + Objects.requireNonNull(handler); + if (!this.isConnected()) { + handler.failed(new NotYetConnectedException(), attachment); + return; + } + if (this.writePending) { + handler.failed(new WritePendingException(), attachment); + return; + } + this.writePending = true; + this.writeByteBuffers = srcs; + this.writeOffset = offset; + this.writeLength = length; + this.writeAttachment = attachment; + if (this.writeTimeoutSeconds > 0) { + AsyncNioCompletionHandler newhandler = new AsyncNioCompletionHandler(handler, attachment); + this.writeCompletionHandler = newhandler; + newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.writeTimeoutSeconds, TimeUnit.SECONDS); + } else { + this.writeCompletionHandler = (CompletionHandler) handler; + } + doWrite(true || !client || currWriteInvoker < MAX_INVOKER_ONSTACK); // this.ioThread.inCurrThread() // !client && ioThread.workExecutor == null + } + + public void doRead(boolean direct) { + try { + this.readtime = System.currentTimeMillis(); + int readCount = 0; + if (direct) { + currReadInvoker++; + if (this.readByteBuffer == null) { + this.readByteBuffer = pollReadBuffer(); + if (this.readTimeoutSeconds > 0 && this.readCompletionHandler != null) { + ((AsyncNioCompletionHandler) this.readCompletionHandler).setAttachment(this.readByteBuffer); + } + } + readCount = implRead(readByteBuffer); + } + + if (readCount != 0) { + handleRead(readCount, null); + } else if (readKey == null) { + ioThread.register(selector -> { + try { + readKey = implRegister(selector, SelectionKey.OP_READ); + readKey.attach(this); + } catch (ClosedChannelException e) { + handleRead(0, e); + } + }); + } else { + if (client || !direct) ioGroup.interestOpsOr(ioThread, readKey, SelectionKey.OP_READ); + } + } catch (Exception e) { + handleRead(0, e); + } + } + + public void doWrite(boolean direct) { + try { + this.writetime = System.currentTimeMillis(); + final boolean invokeDirect = direct; + int totalCount = 0; + boolean hasRemain = true; + if (invokeDirect) currWriteInvoker++; + while (invokeDirect && hasRemain) { + if (writeByteTuple1Array != null) { + final ByteBuffer buffer = pollWriteBuffer(); + if (buffer.remaining() >= writeByteTuple1Length + writeByteTuple2Length) { + buffer.put(writeByteTuple1Array, writeByteTuple1Offset, writeByteTuple1Length); + if (writeByteTuple2Length > 0) buffer.put(writeByteTuple2Array, writeByteTuple2Offset, writeByteTuple2Length); + buffer.flip(); + writeByteBuffer = buffer; + writeByteTuple1Array = null; + writeByteTuple1Offset = 0; + writeByteTuple1Length = 0; + writeByteTuple2Array = null; + writeByteTuple2Offset = 0; + writeByteTuple2Length = 0; + } else { + ByteBufferWriter writer = ByteBufferWriter.create(getBufferSupplier(), buffer); + writer.put(writeByteTuple1Array, writeByteTuple1Offset, writeByteTuple1Length); + if (writeByteTuple2Length > 0) writer.put(writeByteTuple2Array, writeByteTuple2Offset, writeByteTuple2Length); + final ByteBuffer[] buffers = writer.toBuffers(); + writeByteBuffers = buffers; + writeOffset = 0; + writeLength = buffers.length; + writeByteTuple1Array = null; + writeByteTuple1Offset = 0; + writeByteTuple1Length = 0; + writeByteTuple2Array = null; + writeByteTuple2Offset = 0; + writeByteTuple2Length = 0; + } + if (writeByteBuffer == null) { + ((AsyncNioCompletionHandler) writeCompletionHandler).setConnBuffers(this, writeByteBuffers); + } else { + ((AsyncNioCompletionHandler) writeCompletionHandler).setConnBuffers(this, writeByteBuffer); + } + } + int writeCount; + if (writeByteBuffer != null) { + writeCount = implWrite(writeByteBuffer); + hasRemain = writeByteBuffer.hasRemaining(); + } else { + writeCount = implWrite(writeByteBuffers, writeOffset, writeLength); + boolean remain = false; + for (int i = writeByteBuffers.length - 1; i >= writeOffset; i--) { + if (writeByteBuffers[i].hasRemaining()) { + remain = true; + break; + } + } + hasRemain = remain; + } + if (writeCount <= 0) { + if (totalCount == 0) totalCount = writeCount; + break; + } else { + totalCount += writeCount; + } + if (!hasRemain) break; + } + + if (totalCount != 0 || !hasRemain) { + handleWrite(totalCount, null); + } else if (writeKey == null) { + ioThread.register(selector -> { + try { + writeKey = implRegister(selector, SelectionKey.OP_WRITE); + writeKey.attach(this); + } catch (ClosedChannelException e) { + handleWrite(0, e); + } + }); + } else { + ioGroup.interestOpsOr(ioThread, writeKey, SelectionKey.OP_WRITE); + } + } catch (IOException e) { + handleWrite(0, e); + } + } + + protected void handleConnect(Throwable t) { + if (connectKey != null) { + connectKey.cancel(); + connectKey = null; + } + CompletionHandler handler = this.connectCompletionHandler; + Object attach = this.connectAttachment; + + this.connectCompletionHandler = null; + this.connectAttachment = null; + this.connectPending = false;//必须放最后 + + if (handler != null) { + if (t == null) { + handler.completed(null, attach); + } else { + handler.failed(t, attach); + } + } + } + + protected void handleRead(final int totalCount, Throwable t) { + CompletionHandler handler = this.readCompletionHandler; + ByteBuffer attach = this.readByteBuffer; + //清空读参数 + this.readCompletionHandler = null; + this.readByteBuffer = null; + this.readPending = false; //必须放最后 + + if (handler == null) { + if (t == null) { + protocolCodec.completed(totalCount, attach); + } else { + protocolCodec.failed(t, attach); + } + } else { + if (t == null) { + handler.completed(totalCount, attach); + } else { + handler.failed(t, attach); + } + } + } + + protected void handleWrite(final int totalCount, Throwable t) { + CompletionHandler handler = this.writeCompletionHandler; + Object attach = this.writeAttachment; + //清空写参数 + this.writeCompletionHandler = null; + this.writeAttachment = null; + this.writeByteBuffer = null; + this.writeByteBuffers = null; + this.writeOffset = 0; + this.writeLength = 0; + this.writePending = false; //必须放最后 + + if (t == null) { + handler.completed(totalCount, attach); + } else { + handler.failed(t, attach); + } + } + + protected abstract SelectionKey implRegister(Selector sel, int ops) throws ClosedChannelException; + + protected abstract int implRead(ByteBuffer dst) throws IOException; + + protected abstract int implWrite(ByteBuffer src) throws IOException; + + protected abstract int implWrite(ByteBuffer[] srcs, int offset, int length) throws IOException; + + public abstract boolean isConnected(); + + public abstract void doConnect(); +} diff --git a/src/org/redkale/net/AsyncNioTcpConnection.java b/src/org/redkale/net/AsyncNioTcpConnection.java new file mode 100644 index 000000000..4bec83c1d --- /dev/null +++ b/src/org/redkale/net/AsyncNioTcpConnection.java @@ -0,0 +1,201 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.*; +import javax.net.ssl.SSLContext; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * + * @since 2.1.0 + */ +class AsyncNioTcpConnection extends AsyncNioConnection { + + private final SocketChannel channel; + + public AsyncNioTcpConnection(boolean client, AsyncIOGroup ioGroup, AsyncIOThread ioThread, AsyncIOThread connectThread, + SocketChannel ch, SSLContext sslContext, final SocketAddress addr0, AtomicLong livingCounter, AtomicLong closedCounter) { + super(client, ioGroup, ioThread, connectThread, ioGroup.bufferCapacity, ioThread.getBufferSupplier(), ioThread.getBufferConsumer(), sslContext, livingCounter, closedCounter); + this.channel = ch; + SocketAddress addr = addr0; + if (addr == null) { + try { + addr = ch.getRemoteAddress(); + } catch (Exception e) { + //do nothing + } + } + this.remoteAddress = addr; + ioThread.connCounter.incrementAndGet(); + } + + public AsyncNioTcpConnection(boolean client, AsyncIOGroup ioGroup, AsyncIOThread ioThread, AsyncIOThread connectThread, Supplier bufferSupplier, Consumer bufferConsumer, + SocketChannel ch, SSLContext sslContext, final SocketAddress addr0, AtomicLong livingCounter, AtomicLong closedCounter) { + super(client, ioGroup, ioThread, connectThread, ioGroup.bufferCapacity, bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); + this.channel = ch; + SocketAddress addr = addr0; + if (addr == null) { + try { + addr = ch.getRemoteAddress(); + } catch (Exception e) { + //do nothing + } + } + this.remoteAddress = addr; + } + + @Override + public boolean isOpen() { + return this.channel.isOpen(); + } + + @Override + public boolean isTCP() { + return true; + } + + @Override + public boolean shutdownInput() { + try { + this.channel.shutdownInput(); + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public boolean shutdownOutput() { + try { + this.channel.shutdownOutput(); + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public boolean setOption(SocketOption name, T value) { + try { + this.channel.setOption(name, value); + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public Set> supportedOptions() { + return this.channel.supportedOptions(); + } + + @Override + public SocketAddress getLocalAddress() { + try { + return channel.getLocalAddress(); + } catch (IOException e) { + return null; + } + } + + @Override + public ReadableByteChannel readableByteChannel() { + return this.channel; + } + + @Override + public WritableByteChannel writableByteChannel() { + return this.channel; + } + + @Override + public boolean isConnected() { + return this.channel.isConnected(); + } + + @Override + protected SelectionKey implRegister(Selector sel, int ops) throws ClosedChannelException { + return this.channel.register(sel, ops); + } + + @Override + protected int implRead(ByteBuffer dst) throws IOException { + return this.channel.read(dst); + } + + @Override + protected int implWrite(ByteBuffer src) throws IOException { + return this.channel.write(src); + } + + @Override + protected int implWrite(ByteBuffer[] srcs, int offset, int length) throws IOException { + return (int) this.channel.write(srcs, offset, length); + } + + public void connect(SocketAddress remote, A attachment, CompletionHandler handler) { + if (channel.isConnected()) { + throw new AlreadyConnectedException(); + } + if (connectPending) { + throw new ConnectionPendingException(); + } + connectPending = true; + this.connectAttachment = attachment; + this.connectCompletionHandler = (CompletionHandler) handler; + this.remoteAddress = remote; + doConnect(); + } + + @Override + public void doConnect() { + try { + boolean connected = channel.isConnectionPending(); + if (connected || channel.connect(remoteAddress)) { + connected = channel.finishConnect(); + } + if (connected) { + handleConnect(null); + } else if (connectKey == null) { + connectThread.register(selector -> { + try { + connectKey = channel.register(selector, SelectionKey.OP_CONNECT); + connectKey.attach(this); + } catch (ClosedChannelException e) { + handleConnect(e); + } + }); + } else { + handleConnect(new IOException()); + } + } catch (IOException e) { + handleConnect(e); + } + } + + @Override + public final void close() throws IOException { + super.close(); + ioThread.connCounter.decrementAndGet(); + channel.shutdownInput(); + channel.shutdownOutput(); + channel.close(); + if (this.connectKey != null) this.connectKey.cancel(); + if (this.readKey != null) this.readKey.cancel(); + if (this.writeKey != null) this.writeKey.cancel(); + } +} diff --git a/src/org/redkale/net/NioTcpProtocolServer.java b/src/org/redkale/net/AsyncNioTcpProtocolServer.java similarity index 58% rename from src/org/redkale/net/NioTcpProtocolServer.java rename to src/org/redkale/net/AsyncNioTcpProtocolServer.java index 7b4eb07df..7610d892b 100644 --- a/src/org/redkale/net/NioTcpProtocolServer.java +++ b/src/org/redkale/net/AsyncNioTcpProtocolServer.java @@ -11,6 +11,8 @@ import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.*; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.*; +import org.redkale.boot.Application; import org.redkale.util.*; /** @@ -22,23 +24,23 @@ import org.redkale.util.*; * * @since 2.1.0 */ -public class NioTcpProtocolServer extends ProtocolServer { - - private ObjectPool bufferPool; - - private ObjectPool responsePool; +class AsyncNioTcpProtocolServer extends ProtocolServer { private ServerSocketChannel serverChannel; private Selector selector; - private NioThreadGroup ioGroup; + private AsyncIOGroup ioGroup; private Thread acceptThread; private boolean closed; - public NioTcpProtocolServer(Context context) { + private Supplier responseSupplier; + + private Consumer responseConsumer; + + public AsyncNioTcpProtocolServer(Context context) { super(context); } @@ -81,20 +83,38 @@ public class NioTcpProtocolServer extends ProtocolServer { } @Override - public void accept(Server server) throws IOException { + public void accept(Application application, Server server) throws IOException { this.serverChannel.register(this.selector, SelectionKey.OP_ACCEPT); AtomicLong createBufferCounter = new AtomicLong(); AtomicLong cycleBufferCounter = new AtomicLong(); - this.bufferPool = server.createBufferPool(createBufferCounter, cycleBufferCounter, server.bufferPoolSize); + + ObjectPool bufferPool = server.createBufferPool(createBufferCounter, cycleBufferCounter, server.bufferPoolSize); AtomicLong createResponseCounter = new AtomicLong(); AtomicLong cycleResponseCounter = new AtomicLong(); - - this.responsePool = server.createResponsePool(createResponseCounter, cycleResponseCounter, server.responsePoolSize); - this.ioGroup = new NioThreadGroup(server.name, null, Runtime.getRuntime().availableProcessors(), bufferPool, responsePool); + ObjectPool safeResponsePool = server.createResponsePool(createResponseCounter, cycleResponseCounter, server.responsePoolSize); + final int threads = Runtime.getRuntime().availableProcessors(); + ThreadLocal> localResponsePool = ThreadLocal.withInitial(() -> { + if (!(Thread.currentThread() instanceof WorkThread)) return null; + return ObjectPool.createUnsafePool(safeResponsePool, safeResponsePool.getCreatCounter(), + safeResponsePool.getCycleCounter(), 16, safeResponsePool.getCreator(), safeResponsePool.getPrepare(), safeResponsePool.getRecycler()); + }); + this.responseSupplier = () -> { + ObjectPool pool = localResponsePool.get(); + return pool == null ? safeResponsePool.get() : pool.get(); + }; + this.responseConsumer = (v) -> { + ObjectPool pool = localResponsePool.get(); + (pool == null ? safeResponsePool : pool).accept(v); + }; + final String threadPrefixName = server.name == null || server.name.isEmpty() ? "Redkale-IOServletThread" : ("Redkale-" + server.name.replace("Server-", "") + "-IOServletThread"); + this.ioGroup = new AsyncIOGroup(threadPrefixName, null, threads, server.bufferCapacity, bufferPool); this.ioGroup.start(); - this.acceptThread = new Thread() { + { + setName(threadPrefixName.replace("ServletThread", "AcceptThread")); + } + @Override public void run() { while (!closed) { @@ -125,9 +145,13 @@ public class NioTcpProtocolServer extends ProtocolServer { channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); channel.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024); channel.setOption(StandardSocketOptions.SO_SNDBUF, 16 * 1024); - NioThread ioThread = ioGroup.nextThread(); - AsyncConnection conn = new NioTcpAsyncConnection(ioGroup, ioThread, channel, context.getSSLContext(), null, livingCounter, closedCounter); - new NioTcpPrepareRunner(context, responsePool, conn, null, null).run(); + AsyncIOThread readThread = ioGroup.nextIOThread(); + ioGroup.connCreateCounter.incrementAndGet(); + ioGroup.connLivingCounter.incrementAndGet(); + AsyncNioTcpConnection conn = new AsyncNioTcpConnection(false, ioGroup, readThread, ioGroup.connectThread(), channel, context.getSSLContext(), null, ioGroup.connLivingCounter, ioGroup.connClosedCounter); + ProtocolCodec codec = new ProtocolCodec(context, responseSupplier, responseConsumer, conn); + conn.protocolCodec = codec; + codec.run(null); } @Override @@ -140,4 +164,18 @@ public class NioTcpProtocolServer extends ProtocolServer { this.selector.close(); } + @Override + public long getCreateConnectionCount() { + return ioGroup.connCreateCounter.get(); + } + + @Override + public long getClosedConnectionCount() { + return ioGroup.connClosedCounter.get(); + } + + @Override + public long getLivingConnectionCount() { + return ioGroup.connLivingCounter.get(); + } } diff --git a/src/org/redkale/net/AsyncNioUdpConnection.java b/src/org/redkale/net/AsyncNioUdpConnection.java new file mode 100644 index 000000000..e490687ce --- /dev/null +++ b/src/org/redkale/net/AsyncNioUdpConnection.java @@ -0,0 +1,171 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net; + +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.*; +import javax.net.ssl.SSLContext; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + */ +class AsyncNioUdpConnection extends AsyncNioConnection { + + private final DatagramChannel channel; + + public AsyncNioUdpConnection(boolean client, AsyncIOGroup ioGroup, AsyncIOThread ioThread, AsyncIOThread connectThread, DatagramChannel ch, + SSLContext sslContext, final SocketAddress addr0, AtomicLong livingCounter, AtomicLong closedCounter) { + super(client, ioGroup, ioThread, connectThread, ioGroup.bufferCapacity, ioThread.getBufferSupplier(), ioThread.getBufferConsumer(), sslContext, livingCounter, closedCounter); + this.channel = ch; + SocketAddress addr = addr0; + if (addr == null) { + try { + addr = ch.getRemoteAddress(); + } catch (Exception e) { + //do nothing + } + } + this.remoteAddress = addr; + } + + public AsyncNioUdpConnection(boolean client, AsyncIOGroup ioGroup, AsyncIOThread ioThread, AsyncIOThread connectThread, + Supplier bufferSupplier, Consumer bufferConsumer, + DatagramChannel ch, SSLContext sslContext, final SocketAddress addr0, + AtomicLong livingCounter, AtomicLong closedCounter) { + super(client, ioGroup, ioThread, connectThread, ioGroup.bufferCapacity, bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); + this.channel = ch; + SocketAddress addr = addr0; + if (addr == null) { + try { + addr = ch.getRemoteAddress(); + } catch (Exception e) { + //do nothing + } + } + this.remoteAddress = addr; + } + + @Override + public boolean isOpen() { + return this.channel.isOpen(); + } + + @Override + public boolean isTCP() { + return false; + } + + @Override + public boolean shutdownInput() { + return true; + } + + @Override + public boolean shutdownOutput() { + return true; + } + + @Override + public boolean setOption(SocketOption name, T value) { + try { + this.channel.setOption(name, value); + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public Set> supportedOptions() { + return this.channel.supportedOptions(); + } + + @Override + public SocketAddress getLocalAddress() { + try { + return channel.getLocalAddress(); + } catch (IOException e) { + return null; + } + } + + @Override + public ReadableByteChannel readableByteChannel() { + return this.channel; + } + + @Override + public WritableByteChannel writableByteChannel() { + return this.channel; + } + + @Override + public boolean isConnected() { + if (!client) return true; + return this.channel.isConnected(); + } + + @Override + protected SelectionKey implRegister(Selector sel, int ops) throws ClosedChannelException { + return this.channel.register(sel, ops); + } + + @Override + protected int implRead(ByteBuffer dst) throws IOException { + return this.channel.read(dst); + } + + @Override + protected int implWrite(ByteBuffer src) throws IOException { + return this.channel.send(src, remoteAddress); + } + + @Override + protected int implWrite(ByteBuffer[] srcs, int offset, int length) throws IOException { + int end = offset + length; + for (int i = offset; i < end; i++) { + ByteBuffer buf = srcs[i]; + if (buf.hasRemaining()) return this.channel.send(buf, remoteAddress); + } + return 0; + } + + public void connect(SocketAddress remote, A attachment, CompletionHandler handler) { + this.connectAttachment = attachment; + this.connectCompletionHandler = (CompletionHandler) handler; + this.remoteAddress = remote; + doConnect(); + } + + @Override + public void doConnect() { + try { + channel.connect(remoteAddress); + handleConnect(null); + } catch (IOException e) { + handleConnect(e); + } + } + + @Override + public final void close() throws IOException { + super.close(); + if (client) channel.close(); //不能关闭channel + if (this.connectKey != null) this.connectKey.cancel(); + if (this.readKey != null) this.readKey.cancel(); + if (this.writeKey != null) this.writeKey.cancel(); + } + +} diff --git a/src/org/redkale/net/AsyncNioUdpProtocolServer.java b/src/org/redkale/net/AsyncNioUdpProtocolServer.java new file mode 100644 index 000000000..e1b0722e7 --- /dev/null +++ b/src/org/redkale/net/AsyncNioUdpProtocolServer.java @@ -0,0 +1,169 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net; + +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.*; +import org.redkale.boot.Application; +import org.redkale.util.*; + +/** + * 协议底层Server + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + */ +class AsyncNioUdpProtocolServer extends ProtocolServer { + + private DatagramChannel serverChannel; + + private Selector selector; + + private AsyncIOGroup ioGroup; + + private Thread acceptThread; + + private boolean closed; + + private Supplier responseSupplier; + + private Consumer responseConsumer; + + public AsyncNioUdpProtocolServer(Context context) { + super(context); + } + + @Override + public void open(AnyValue config) throws IOException { + this.serverChannel = DatagramChannel.open(); + this.serverChannel.configureBlocking(false); + this.selector = Selector.open(); + final Set> options = this.serverChannel.supportedOptions(); + if (options.contains(StandardSocketOptions.TCP_NODELAY)) { + this.serverChannel.setOption(StandardSocketOptions.TCP_NODELAY, true); + } + if (options.contains(StandardSocketOptions.SO_KEEPALIVE)) { + this.serverChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); + } + if (options.contains(StandardSocketOptions.SO_REUSEADDR)) { + this.serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); + } + if (options.contains(StandardSocketOptions.SO_RCVBUF)) { + this.serverChannel.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024); + } + if (options.contains(StandardSocketOptions.SO_SNDBUF)) { + this.serverChannel.setOption(StandardSocketOptions.SO_SNDBUF, 16 * 1024); + } + } + + @Override + public void bind(SocketAddress local, int backlog) throws IOException { + this.serverChannel.bind(local); + } + + @Override + public void setOption(SocketOption name, T value) throws IOException { + this.serverChannel.setOption(name, value); + } + + @Override + public Set> supportedOptions() { + return this.serverChannel.supportedOptions(); + } + + @Override + public void accept(Application application, Server server) throws IOException { + AtomicLong createBufferCounter = new AtomicLong(); + AtomicLong cycleBufferCounter = new AtomicLong(); + + ObjectPool safeBufferPool = server.createBufferPool(createBufferCounter, cycleBufferCounter, server.bufferPoolSize); + AtomicLong createResponseCounter = new AtomicLong(); + AtomicLong cycleResponseCounter = new AtomicLong(); + ObjectPool safeResponsePool = server.createResponsePool(createResponseCounter, cycleResponseCounter, server.responsePoolSize); + final int threads = Runtime.getRuntime().availableProcessors(); + ThreadLocal> localResponsePool = ThreadLocal.withInitial(() -> { + if (!(Thread.currentThread() instanceof WorkThread)) return null; + return ObjectPool.createUnsafePool(safeResponsePool, safeResponsePool.getCreatCounter(), + safeResponsePool.getCycleCounter(), 16, safeResponsePool.getCreator(), safeResponsePool.getPrepare(), safeResponsePool.getRecycler()); + }); + this.responseSupplier = () -> { + ObjectPool pool = localResponsePool.get(); + return pool == null ? safeResponsePool.get() : pool.get(); + }; + this.responseConsumer = (v) -> { + ObjectPool pool = localResponsePool.get(); + (pool == null ? safeResponsePool : pool).accept(v); + }; + final String threadPrefixName = server.name == null || server.name.isEmpty() ? "Redkale-IOServletThread" : ("Redkale-" + server.name.replace("Server-", "") + "-IOServletThread"); + this.ioGroup = new AsyncIOGroup(threadPrefixName, null, threads, server.bufferCapacity, safeBufferPool); + this.ioGroup.start(); + this.serverChannel.register(this.selector, SelectionKey.OP_READ); + + this.acceptThread = new Thread() { + ObjectPool unsafeBufferPool = ObjectPool.createUnsafePool(safeBufferPool, safeBufferPool.getCreatCounter(), + safeBufferPool.getCycleCounter(), 512, safeBufferPool.getCreator(), safeBufferPool.getPrepare(), safeBufferPool.getRecycler()); + + { + setName(threadPrefixName.replace("ServletThread", "AcceptThread")); + } + + @Override + public void run() { + while (!closed) { + final ByteBuffer buffer = unsafeBufferPool.get(); + try { + SocketAddress address = serverChannel.receive(buffer); + buffer.flip(); + accept(address, buffer); + } catch (Throwable t) { + unsafeBufferPool.accept(buffer); + } + } + } + }; + this.acceptThread.start(); + } + + private void accept(SocketAddress address, ByteBuffer buffer) throws IOException { + AsyncIOThread readThread = ioGroup.nextIOThread(); + ioGroup.connCreateCounter.incrementAndGet(); + ioGroup.connLivingCounter.incrementAndGet(); + AsyncNioUdpConnection conn = new AsyncNioUdpConnection(false, ioGroup, readThread, ioGroup.connectThread(), this.serverChannel, context.getSSLContext(), address, ioGroup.connLivingCounter, ioGroup.connClosedCounter); + ProtocolCodec codec = new ProtocolCodec(context, responseSupplier, responseConsumer, conn); + conn.protocolCodec = codec; + codec.run(buffer); + } + + @Override + public void close() throws IOException { + if (this.closed) return; + this.closed = true; + this.ioGroup.close(); + this.serverChannel.close(); + } + + @Override + public long getCreateConnectionCount() { + return -1; + } + + @Override + public long getClosedConnectionCount() { + return -1; + } + + @Override + public long getLivingConnectionCount() { + return -1; + } +} diff --git a/src/org/redkale/net/BioUdpAsyncConnection.java b/src/org/redkale/net/BioUdpAsyncConnection.java deleted file mode 100644 index a19eba5ee..000000000 --- a/src/org/redkale/net/BioUdpAsyncConnection.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.io.*; -import java.net.*; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.*; -import javax.net.ssl.SSLContext; - -/** - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - */ -class BioUdpAsyncConnection extends AsyncConnection { - - private int readTimeoutSeconds; - - private int writeTimeoutSeconds; - - private final DatagramChannel channel; - - private final SocketAddress remoteAddress; - - private final boolean client; - - public BioUdpAsyncConnection(Supplier bufferSupplier, Consumer bufferConsumer, - final DatagramChannel ch, final SSLContext sslContext, SocketAddress addr0, final boolean client0, - final int readTimeoutSeconds0, final int writeTimeoutSeconds0, - final AtomicLong livingCounter, final AtomicLong closedCounter) { - super(bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); - this.channel = ch; - this.client = client0; - this.readTimeoutSeconds = readTimeoutSeconds0; - this.writeTimeoutSeconds = writeTimeoutSeconds0; - SocketAddress addr = addr0; - if (addr == null) { - try { - addr = ch.getRemoteAddress(); - } catch (Exception e) { - //do nothing - } - } - this.remoteAddress = addr; - } - - @Override - public void setReadTimeoutSeconds(int readTimeoutSeconds) { - this.readTimeoutSeconds = readTimeoutSeconds; - } - - @Override - public void setWriteTimeoutSeconds(int writeTimeoutSeconds) { - this.writeTimeoutSeconds = writeTimeoutSeconds; - } - - @Override - public int getReadTimeoutSeconds() { - return this.readTimeoutSeconds; - } - - @Override - public int getWriteTimeoutSeconds() { - return this.writeTimeoutSeconds; - } - - @Override - public final SocketAddress getRemoteAddress() { - return remoteAddress; - } - - @Override - public SocketAddress getLocalAddress() { - try { - return channel.getLocalAddress(); - } catch (IOException e) { - return null; - } - } - - @Override - public boolean shutdownInput() { - return false; - } - - @Override - public boolean shutdownOutput() { - return false; - } - - @Override - public boolean setOption(SocketOption name, T value) { - try { - this.channel.setOption(name, value); - return true; - } catch (IOException e) { - return false; - } - } - - @Override - public Set> supportedOptions() { - return this.channel.supportedOptions(); - } - - @Override - public void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler) { - try { - int rs = 0; - for (int i = offset; i < offset + length; i++) { - rs += channel.send(srcs[i], remoteAddress); - if (i != offset) Thread.sleep(10); - } - this.writetime = System.currentTimeMillis(); - if (handler != null) handler.completed(rs, attachment); - } catch (Exception e) { - if (handler != null) handler.failed(e, attachment); - } - } - - @Override - public void read(CompletionHandler handler) { - ByteBuffer dst = pollReadBuffer(); - try { - int rs = channel.read(dst); - this.readtime = System.currentTimeMillis(); - if (handler != null) handler.completed(rs, dst); - } catch (IOException e) { - if (handler != null) handler.failed(e, dst); - } - } - - @Override - public final ReadableByteChannel readableByteChannel() { - return this.channel; - } - - @Override - public final WritableByteChannel writableByteChannel() { - return this.channel; - } - - @Override - public void write(ByteBuffer src, A attachment, CompletionHandler handler) { - try { - int rs = channel.send(src, remoteAddress); - this.writetime = System.currentTimeMillis(); - if (handler != null) handler.completed(rs, attachment); - } catch (IOException e) { - if (handler != null) handler.failed(e, attachment); - } - } - - @Override - public final void close() throws IOException { - super.close(); - if (client) channel.close(); - } - - @Override - public final boolean isOpen() { - return channel.isOpen(); - } - - @Override - public final boolean isTCP() { - return false; - } -} diff --git a/src/org/redkale/net/BioUdpProtocolServer.java b/src/org/redkale/net/BioUdpProtocolServer.java deleted file mode 100644 index 713284f43..000000000 --- a/src/org/redkale/net/BioUdpProtocolServer.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.io.IOException; -import java.net.*; -import java.nio.ByteBuffer; -import java.nio.channels.DatagramChannel; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicLong; -import org.redkale.util.*; - -/** - * 协议底层Server - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - */ -public class BioUdpProtocolServer extends ProtocolServer { - - private boolean running; - - private DatagramChannel serverChannel; - - public BioUdpProtocolServer(Context context) { - super(context); - } - - @Override - public void open(AnyValue config) throws IOException { - DatagramChannel ch = DatagramChannel.open(); - ch.configureBlocking(true); - this.serverChannel = ch; - final Set> options = this.serverChannel.supportedOptions(); - if (options.contains(StandardSocketOptions.TCP_NODELAY)) { - this.serverChannel.setOption(StandardSocketOptions.TCP_NODELAY, true); - } - if (options.contains(StandardSocketOptions.SO_KEEPALIVE)) { - this.serverChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); - } - if (options.contains(StandardSocketOptions.SO_REUSEADDR)) { - this.serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); - } - if (options.contains(StandardSocketOptions.SO_RCVBUF)) { - this.serverChannel.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024); - } - if (options.contains(StandardSocketOptions.SO_SNDBUF)) { - this.serverChannel.setOption(StandardSocketOptions.SO_SNDBUF, 16 * 1024); - } - } - - @Override - public void bind(SocketAddress local, int backlog) throws IOException { - this.serverChannel.bind(local); - } - - @Override - public void setOption(SocketOption name, T value) throws IOException { - this.serverChannel.setOption(name, value); - } - - @Override - public Set> supportedOptions() { - return this.serverChannel.supportedOptions(); - } - - @Override - public void accept(Server server) throws IOException { - AtomicLong createBufferCounter = new AtomicLong(); - AtomicLong cycleBufferCounter = new AtomicLong(); - ObjectPool bufferPool = server.createBufferPool(createBufferCounter, cycleBufferCounter, server.bufferPoolSize); - AtomicLong createResponseCounter = new AtomicLong(); - AtomicLong cycleResponseCounter = new AtomicLong(); - ObjectPool responsePool = server.createResponsePool(createResponseCounter, cycleResponseCounter, server.responsePoolSize); - final DatagramChannel serchannel = this.serverChannel; - final int readTimeoutSeconds = this.context.readTimeoutSeconds; - final int writeTimeoutSeconds = this.context.writeTimeoutSeconds; - final CountDownLatch cdl = new CountDownLatch(1); - this.running = true; - new Thread() { - @Override - public void run() { - cdl.countDown(); - while (running) { - final ByteBuffer buffer = bufferPool.get(); - try { - SocketAddress address = serchannel.receive(buffer); - buffer.flip(); - AsyncConnection conn = new BioUdpAsyncConnection(bufferPool, bufferPool, serchannel, - context.getSSLContext(), address, false, readTimeoutSeconds, writeTimeoutSeconds, null, null); - context.runAsync(new PrepareRunner(context, responsePool, conn, buffer, null)); - } catch (Exception e) { - bufferPool.accept(buffer); - } - } - } - }.start(); - try { - cdl.await(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Override - public void close() throws IOException { - this.running = false; - this.serverChannel.close(); - } - - @Override - public long getCreateCount() { - return -1; - } - - @Override - public long getClosedCount() { - return -1; - } - - @Override - public long getLivingCount() { - return -1; - } -} diff --git a/src/org/redkale/net/Context.java b/src/org/redkale/net/Context.java index 486d07748..c23a78345 100644 --- a/src/org/redkale/net/Context.java +++ b/src/org/redkale/net/Context.java @@ -28,7 +28,9 @@ public class Context { protected final long serverStartTime; //Server的线程池 - protected final ThreadPoolExecutor executor; + protected final ExecutorService workExecutor; + + protected final ThreadHashExecutor workHashExecutor; //SSL protected final SSLContext sslContext; @@ -73,17 +75,17 @@ public class Context { protected Charset charset; public Context(ContextConfig config) { - this(config.serverStartTime, config.logger, config.executor, config.sslContext, + this(config.serverStartTime, config.logger, config.workExecutor, config.sslContext, config.bufferCapacity, config.maxconns, config.maxbody, config.charset, config.address, config.resourceFactory, config.prepare, config.aliveTimeoutSeconds, config.readTimeoutSeconds, config.writeTimeoutSeconds); } - public Context(long serverStartTime, Logger logger, ThreadPoolExecutor executor, SSLContext sslContext, + public Context(long serverStartTime, Logger logger, ExecutorService workExecutor, SSLContext sslContext, int bufferCapacity, final int maxconns, final int maxbody, Charset charset, InetSocketAddress address, ResourceFactory resourceFactory, PrepareServlet prepare, int aliveTimeoutSeconds, int readTimeoutSeconds, int writeTimeoutSeconds) { this.serverStartTime = serverStartTime; this.logger = logger; - this.executor = executor; + this.workExecutor = workExecutor; this.sslContext = sslContext; this.bufferCapacity = bufferCapacity; this.maxconns = maxconns; @@ -97,6 +99,57 @@ public class Context { this.writeTimeoutSeconds = writeTimeoutSeconds; this.jsonFactory = JsonFactory.root(); this.bsonFactory = BsonFactory.root(); + if (workExecutor instanceof ThreadHashExecutor) { + this.workHashExecutor = (ThreadHashExecutor) workExecutor; + } else { + this.workHashExecutor = null; + } + } + + protected void executePrepareServlet(Request request, Response response) { + if (workHashExecutor != null) { + workHashExecutor.execute(request.getHashid(), () -> prepare.prepare(request, response)); + } else if (workExecutor != null) { + workExecutor.execute(() -> prepare.prepare(request, response)); + } else { + prepare.prepare(request, response); + } + } + + public void execute(Servlet servlet, Request request, Response response) { + if (workHashExecutor != null) { + workHashExecutor.execute(request.getHashid(), () -> { + try { + long cha = System.currentTimeMillis() - request.getCreatetime(); + servlet.execute(request, response); + if (cha > 1000 && response.context.logger.isLoggable(Level.WARNING)) { + response.context.logger.log(Level.WARNING, "hash execute servlet delays=" + cha + "ms, request=" + request); + } else if (cha > 100 && response.context.logger.isLoggable(Level.FINE)) { + response.context.logger.log(Level.FINE, "hash execute servlet delay=" + cha + "ms, request=" + request); + } + } catch (Throwable t) { + response.context.logger.log(Level.WARNING, "execute servlet abort, force to close channel ", t); + response.finish(true); + } + }); + } else if (workExecutor != null) { + workExecutor.execute(() -> { + try { + servlet.execute(request, response); + } catch (Throwable t) { + response.context.logger.log(Level.WARNING, "execute servlet abort, force to close channel ", t); + response.finish(true); + } + }); + } else { + try { + servlet.execute(request, response); + } catch (Throwable t) { + response.context.logger.log(Level.WARNING, "execute servlet abort, force to close channel ", t); + response.finish(true); + } + } + } public ResourceFactory getResourceFactory() { @@ -127,22 +180,6 @@ public class Context { return charset; } - public Future submitAsync(Runnable r) { - return executor.submit(r); - } - - public void runAsync(Runnable r) { - executor.execute(r); - } - - public int getCorePoolSize() { - return executor.getCorePoolSize(); - } - - public ThreadFactory getThreadFactory() { - return executor.getThreadFactory(); - } - public int getBufferCapacity() { return bufferCapacity; } @@ -177,7 +214,7 @@ public class Context { public long serverStartTime; //Server的线程池 - public ThreadPoolExecutor executor; + public ExecutorService workExecutor; //SSL public SSLContext sslContext; diff --git a/src/org/redkale/net/NioTcpAsyncConnection.java b/src/org/redkale/net/NioTcpAsyncConnection.java deleted file mode 100644 index 8030b18f6..000000000 --- a/src/org/redkale/net/NioTcpAsyncConnection.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.io.IOException; -import java.net.*; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.*; -import javax.net.ssl.SSLContext; - -/** - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - * - * @since 2.1.0 - */ -public class NioTcpAsyncConnection extends AsyncConnection { - - private int readTimeoutSeconds; - - private int writeTimeoutSeconds; - - private SocketAddress remoteAddress; - - final SocketChannel channel; - - final NioThread ioThread; - - final NioThreadGroup ioGroup; - - //连 - private Object connectAttachment; - - private CompletionHandler connectCompletionHandler; - - private boolean connectPending; - - private SelectionKey connectKey; - - //读操作 - private ByteBuffer readByteBuffer; - - private CompletionHandler readCompletionHandler; - - private boolean readPending; - - private SelectionKey readKey; - - //写操作, 二选一,要么writeByteBuffer有值,要么writeByteBuffers、writeOffset、writeLength有值 - private ByteBuffer writeByteBuffer; - - private ByteBuffer[] writeByteBuffers; - - private int writeOffset; - - private int writeLength; - - private Object writeAttachment; - - private CompletionHandler writeCompletionHandler; - - private boolean writePending; - - private SelectionKey writeKey; - - public NioTcpAsyncConnection(NioThreadGroup ioGroup, NioThread ioThread, SocketChannel ch, - SSLContext sslContext, final SocketAddress addr0, AtomicLong livingCounter, AtomicLong closedCounter) { - super(ioThread.getBufferSupplier(), ioThread.getBufferConsumer(), sslContext, livingCounter, closedCounter); - this.ioGroup = ioGroup; - this.ioThread = ioThread; - this.channel = ch; - SocketAddress addr = addr0; - if (addr == null) { - try { - addr = ch.getRemoteAddress(); - } catch (Exception e) { - //do nothing - } - } - this.remoteAddress = addr; - } - - public NioTcpAsyncConnection(NioThreadGroup ioGroup, NioThread ioThread, - Supplier bufferSupplier, Consumer bufferConsumer, - SocketChannel ch, SSLContext sslContext, final SocketAddress addr0, - AtomicLong livingCounter, AtomicLong closedCounter) { - super(bufferSupplier, bufferConsumer, sslContext, livingCounter, closedCounter); - this.ioGroup = ioGroup; - this.ioThread = ioThread; - this.channel = ch; - SocketAddress addr = addr0; - if (addr == null) { - try { - addr = ch.getRemoteAddress(); - } catch (Exception e) { - //do nothing - } - } - this.remoteAddress = addr; - } - - @Override - public boolean isOpen() { - return this.channel.isOpen(); - } - - @Override - public boolean isTCP() { - return true; - } - - @Override - public boolean shutdownInput() { - try { - this.channel.shutdownInput(); - return true; - } catch (IOException e) { - return false; - } - } - - @Override - public boolean shutdownOutput() { - try { - this.channel.shutdownOutput(); - return true; - } catch (IOException e) { - return false; - } - } - - @Override - public boolean setOption(SocketOption name, T value) { - try { - this.channel.setOption(name, value); - return true; - } catch (IOException e) { - return false; - } - } - - @Override - public Set> supportedOptions() { - return this.channel.supportedOptions(); - } - - @Override - public SocketAddress getRemoteAddress() { - return remoteAddress; - } - - @Override - public SocketAddress getLocalAddress() { - try { - return channel.getLocalAddress(); - } catch (IOException e) { - return null; - } - } - - @Override - public void setReadTimeoutSeconds(int readTimeoutSeconds) { - this.readTimeoutSeconds = readTimeoutSeconds; - } - - @Override - public void setWriteTimeoutSeconds(int writeTimeoutSeconds) { - this.writeTimeoutSeconds = writeTimeoutSeconds; - } - - @Override - public int getReadTimeoutSeconds() { - return this.readTimeoutSeconds; - } - - @Override - public int getWriteTimeoutSeconds() { - return this.writeTimeoutSeconds; - } - - @Override - public ReadableByteChannel readableByteChannel() { - return this.channel; - } - - public void connect(SocketAddress remote, A attachment, CompletionHandler handler) { - if (channel.isConnected()) { - throw new AlreadyConnectedException(); - } - if (connectPending) { - throw new ConnectionPendingException(); - } - connectPending = true; - this.connectAttachment = attachment; - this.connectCompletionHandler = (CompletionHandler) handler; - this.remoteAddress = remote; - doConnect(); - } - - @Override - public void read(CompletionHandler handler) { - Objects.requireNonNull(handler); - if (!this.channel.isConnected()) { - handler.failed(new NotYetConnectedException(), pollReadBuffer()); - return; - } - if (this.readPending) { - handler.failed(new ReadPendingException(), pollReadBuffer()); - return; - } - this.readPending = true; - if (this.readTimeoutSeconds > 0) { - NioCompletionHandler newhandler = new NioCompletionHandler(handler, this.readByteBuffer); - this.readCompletionHandler = newhandler; - newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.readTimeoutSeconds, TimeUnit.SECONDS); - } else { - this.readCompletionHandler = handler; - } - doRead(); - } - - @Override - public WritableByteChannel writableByteChannel() { - return this.channel; - } - - @Override - public void write(ByteBuffer src, A attachment, CompletionHandler handler) { - Objects.requireNonNull(src); - Objects.requireNonNull(handler); - if (!this.channel.isConnected()) { - handler.failed(new NotYetConnectedException(), attachment); - return; - } - if (this.writePending) { - handler.failed(new WritePendingException(), attachment); - return; - } - this.writePending = true; - this.writeByteBuffer = src; - this.writeAttachment = attachment; - if (this.writeTimeoutSeconds > 0) { - NioCompletionHandler newhandler = new NioCompletionHandler(handler, attachment); - this.writeCompletionHandler = newhandler; - newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.writeTimeoutSeconds, TimeUnit.SECONDS); - } else { - this.writeCompletionHandler = (CompletionHandler) handler; - } - doWrite(); - } - - @Override - public void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler) { - Objects.requireNonNull(srcs); - Objects.requireNonNull(handler); - if (!this.channel.isConnected()) { - handler.failed(new NotYetConnectedException(), attachment); - return; - } - if (this.writePending) { - handler.failed(new WritePendingException(), attachment); - return; - } - this.writePending = true; - this.writeByteBuffers = srcs; - this.writeOffset = offset; - this.writeLength = length; - this.writeAttachment = attachment; - if (this.writeTimeoutSeconds > 0) { - NioCompletionHandler newhandler = new NioCompletionHandler(handler, attachment); - this.writeCompletionHandler = newhandler; - newhandler.timeoutFuture = ioGroup.scheduleTimeout(newhandler, this.writeTimeoutSeconds, TimeUnit.SECONDS); - } else { - this.writeCompletionHandler = (CompletionHandler) handler; - } - doWrite(); - } - - public void doConnect() { - try { - boolean connected = channel.isConnectionPending(); - if (connected || channel.connect(remoteAddress)) { - connected = channel.finishConnect(); - } - if (connected) { - handleConnect(null); - } else if (connectKey == null) { - ioThread.register(selector -> { - try { - connectKey = channel.register(selector, SelectionKey.OP_CONNECT); - connectKey.attach(this); - } catch (ClosedChannelException e) { - handleConnect(e); - } - }); - } else { - handleConnect(new IOException()); - } - } catch (IOException e) { - handleConnect(e); - } - } - - private void handleConnect(Throwable t) { - CompletionHandler handler = this.connectCompletionHandler; - Object attach = this.connectAttachment; - clearConnect(); - if (handler != null) { - if (t == null) { - handler.completed(null, attach); - } else { - handler.failed(t, attach); - } - } - } - - private void clearConnect() { - this.connectCompletionHandler = null; - this.connectAttachment = null; - this.connectPending = false;//必须放最后 - } - - public void doRead() { - try { - final boolean invokeDirect = this.ioThread.inCurrThread(); - int totalCount = 0; - boolean hasRemain = true; - if (invokeDirect && this.readByteBuffer == null) { - this.readByteBuffer = pollReadBuffer(); - if (this.readTimeoutSeconds > 0) { - ((NioCompletionHandler) this.readCompletionHandler).setAttachment(this.readByteBuffer); - } - } - while (invokeDirect && hasRemain) { - int readCount = this.channel.read(readByteBuffer); - hasRemain = readByteBuffer.hasRemaining(); - if (readCount <= 0) { - if (totalCount == 0) totalCount = readCount; - break; - } - totalCount += readCount; - } - if (totalCount != 0 || !hasRemain) { - if (!readPending && readKey != null) readKey.interestOps(readKey.interestOps() & ~SelectionKey.OP_READ); - handleRead(totalCount, null); - } else if (readKey == null) { - ioThread.register(selector -> { - try { - readKey = channel.register(selector, SelectionKey.OP_READ); - readKey.attach(this); - } catch (ClosedChannelException e) { - handleRead(0, e); - } - }); - } else { - ioGroup.interestOpsOr(ioThread, readKey, SelectionKey.OP_READ); - } - } catch (Exception e) { - handleRead(0, e); - } - } - - private void handleRead(final int totalCount, Throwable t) { - CompletionHandler handler = this.readCompletionHandler; - ByteBuffer attach = this.readByteBuffer; - clearRead(); - if (handler != null) { - if (t == null) { - handler.completed(totalCount, attach); - } else { - handler.failed(t, attach); - } - } - } - - private void clearRead() { - this.readCompletionHandler = null; - this.readByteBuffer = null; - this.readPending = false; //必须放最后 - } - - public void doWrite() { - try { - final boolean invokeDirect = this.ioThread.inCurrThread(); - int totalCount = 0; - boolean hasRemain = true; - while (invokeDirect && hasRemain) { - int writeCount; - if (writeByteBuffer != null) { - writeCount = channel.write(writeByteBuffer); - hasRemain = writeByteBuffer.hasRemaining(); - } else { - writeCount = (int) channel.write(writeByteBuffers, writeOffset, writeLength); - boolean remain = false; - for (int i = writeByteBuffers.length - 1; i >= writeOffset; i--) { - if (writeByteBuffers[i].hasRemaining()) { - remain = true; - break; - } - } - hasRemain = remain; - } - if (writeCount <= 0) { - if (totalCount == 0) totalCount = writeCount; - break; - } - totalCount += writeCount; - } - - if (totalCount > 0 || !hasRemain) { - if (writeKey != null) writeKey.interestOps(writeKey.interestOps() & ~SelectionKey.OP_WRITE); - CompletionHandler handler = this.writeCompletionHandler; - Object attach = this.writeAttachment; - clearWrite(); - if (handler != null) { - handler.completed(totalCount, attach); - } - } else if (writeKey == null) { - ioThread.register(selector -> { - try { - writeKey = channel.register(selector, SelectionKey.OP_WRITE); - writeKey.attach(this); - } catch (ClosedChannelException e) { - CompletionHandler handler = this.writeCompletionHandler; - Object attach = this.writeAttachment; - clearWrite(); - if (handler != null) { - handler.failed(e, attach); - } - } - }); - } else { - ioGroup.interestOpsOr(ioThread, writeKey, SelectionKey.OP_WRITE); - } - } catch (IOException e) { - CompletionHandler handler = this.writeCompletionHandler; - Object attach = this.writeAttachment; - clearWrite(); - if (handler != null) { - handler.failed(e, attach); - } - } - } - - private void clearWrite() { - this.writeCompletionHandler = null; - this.writeAttachment = null; - this.writeByteBuffer = null; - this.writeByteBuffers = null; - this.writeOffset = 0; - this.writeLength = 0; - this.writePending = false; //必须放最后 - } - - @Override - public final void close() throws IOException { - super.close(); - if (this.connectKey != null) this.connectKey.cancel(); - if (this.readKey != null) this.readKey.cancel(); - if (this.writeKey != null) this.writeKey.cancel(); - channel.close(); - } -} diff --git a/src/org/redkale/net/NioTcpPrepareRunner.java b/src/org/redkale/net/NioTcpPrepareRunner.java deleted file mode 100644 index ba98e4766..000000000 --- a/src/org/redkale/net/NioTcpPrepareRunner.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.CompletionHandler; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import org.redkale.util.ObjectPool; - -/** - * - * @author zhangjx - */ -public class NioTcpPrepareRunner implements Runnable { - - private final AsyncConnection channel; - - private final Context context; - - private final ObjectPool responsePool; - - private ByteBuffer data; - - private Response response; - - public NioTcpPrepareRunner(Context context, ObjectPool responsePool, AsyncConnection channel, ByteBuffer data, Response response) { - this.context = context; - this.responsePool = responsePool; - this.channel = channel; - this.data = data; - this.response = response; - } - - @Override - public void run() { - try { - channel.read(new CompletionHandler() { - @Override - public void completed(Integer count, ByteBuffer buffer) { - if (response == null) response = ((NioThread) Thread.currentThread()).getResponseSupplier().get(); - if (count < 1) { - buffer.clear(); - channel.setReadBuffer(buffer); - channel.dispose();// response.init(channel); 在调用之前异常 - response.removeChannel(); - response.finish(true); - return; - } -// { //测试 -// buffer.flip(); -// byte[] bs = new byte[buffer.remaining()]; -// buffer.get(bs); -// System.println(new String(bs)); -// } - buffer.flip(); - try { - response.init(channel); - codec(buffer, response); - } catch (Throwable t) { //此处不可 context.offerBuffer(buffer); 以免prepare.prepare内部异常导致重复 offerBuffer - context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); - response.finish(true); - } - } - - @Override - public void failed(Throwable exc, ByteBuffer buffer) { - buffer.clear(); - channel.setReadBuffer(buffer); - channel.dispose();// response.init(channel); 在调用之前异常 - response.removeChannel(); - response.finish(true); - if (exc != null && context.logger.isLoggable(Level.FINEST)) { - context.logger.log(Level.FINEST, "Servlet Handler read channel erroneous, force to close channel ", exc); - } - } - }); - } catch (Exception te) { - channel.dispose();// response.init(channel); 在调用之前异常 - response.removeChannel(); - response.finish(true); - if (context.logger.isLoggable(Level.FINEST)) { - context.logger.log(Level.FINEST, "Servlet read channel erroneous, force to close channel ", te); - } - } - } - - protected void codec(final ByteBuffer buffer, final Response response) throws IOException { - final Request request = response.request; - final PrepareServlet preparer = context.prepare; - preparer.executeCounter.incrementAndGet(); - final int rs = request.readHeader(buffer); - if (rs < 0) { //表示数据格式不正确 - channel.offerBuffer(buffer); - if (rs != Integer.MIN_VALUE) preparer.illRequestCounter.incrementAndGet(); - response.finish(true); - } else if (rs == 0) { - if (buffer.hasRemaining()) { - request.setMoredata(buffer); - } else { - buffer.clear(); - channel.setReadBuffer(buffer); - } - preparer.prepare(request, response); - } else { - buffer.clear(); - channel.setReadBuffer(buffer); - final AtomicInteger ai = new AtomicInteger(rs); - channel.read(new CompletionHandler() { - - @Override - public void completed(Integer result, ByteBuffer attachment) { - attachment.flip(); - ai.addAndGet(-request.readBody(attachment)); - if (ai.get() > 0) { - attachment.clear(); - channel.setReadBuffer(attachment); - channel.read(this); - } else { - if (attachment.hasRemaining()) { - request.setMoredata(attachment); - } else { - attachment.clear(); - channel.setReadBuffer(attachment); - } - try { - preparer.prepare(request, response); - } catch (Throwable t) { //此处不可 context.offerBuffer(buffer); 以免preparer.prepare内部异常导致重复 offerBuffer - context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); - response.finish(true); - } - } - } - - @Override - public void failed(Throwable exc, ByteBuffer attachment) { - preparer.illRequestCounter.incrementAndGet(); - attachment.clear(); - channel.setReadBuffer(attachment); - response.finish(true); - if (exc != null) request.context.logger.log(Level.FINER, "Servlet read channel erroneous, force to close channel ", exc); - } - }); - } - } - - protected void initResponse(Response response, AsyncConnection channel) { - response.init(channel); - } - - protected Response pollResponse() { - return responsePool.get(); - } - - protected Request pollRequest(Response response) { - return response.request; - } - - protected AsyncConnection removeChannel(Response response) { - return response.removeChannel(); - } - -} diff --git a/src/org/redkale/net/NioThread.java b/src/org/redkale/net/NioThread.java deleted file mode 100644 index dcf95343c..000000000 --- a/src/org/redkale/net/NioThread.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.*; -import java.util.concurrent.*; -import java.util.function.*; -import org.redkale.util.*; - -/** - * 协议处理的IO线程类 - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - * - * @since 2.1.0 - */ -public class NioThread extends WorkThread { - - final Selector selector; - - private final Supplier bufferSupplier; - - private final Consumer bufferConsumer; - - private final Supplier responseSupplier; - - private final Consumer responseConsumer; - - private final ConcurrentLinkedQueue> registers = new ConcurrentLinkedQueue<>(); - - private boolean closed; - - public NioThread(String name, ExecutorService workExecutor, Selector selector, - ObjectPool unsafeBufferPool, ObjectPool safeBufferPool, - ObjectPool unsafeResponsePool, ObjectPool safeResponsePool) { - super(name, workExecutor, null); - this.selector = selector; - this.setDaemon(true); - this.bufferSupplier = () -> inCurrThread() ? unsafeBufferPool.get() : safeBufferPool.get(); - this.bufferConsumer = (v) -> { - if (inCurrThread()) { - unsafeBufferPool.accept(v); - } else { - safeBufferPool.accept(v); - } - }; - this.responseSupplier = () -> inCurrThread() ? unsafeResponsePool.get() : safeResponsePool.get(); - this.responseConsumer = (v) -> { - if (inCurrThread()) { - unsafeResponsePool.accept(v); - } else { - safeResponsePool.accept(v); - } - }; - } - - public void register(Consumer consumer) { - registers.offer(consumer); - selector.wakeup(); - } - - public Supplier getBufferSupplier() { - return bufferSupplier; - } - - public Consumer getBufferConsumer() { - return bufferConsumer; - } - - public Supplier getResponseSupplier() { - return responseSupplier; - } - - public Consumer getResponseConsumer() { - return responseConsumer; - } - - @Override - public void run() { - this.localThread = Thread.currentThread(); - while (!this.closed) { - try { - Consumer register; - while ((register = registers.poll()) != null) { - register.accept(selector); - } - int count = selector.select(); - if (count == 0) continue; - Set keys = selector.selectedKeys(); - Iterator it = keys.iterator(); - while (it.hasNext()) { - SelectionKey key = it.next(); - it.remove(); - if (!key.isValid()) continue; - NioTcpAsyncConnection conn = (NioTcpAsyncConnection) key.attachment(); - if (key.isWritable()) { - //key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); - conn.doWrite(); - } else if (key.isReadable()) { - conn.doRead(); - } else if (key.isConnectable()) { - conn.doConnect(); - } - } - } catch (Exception ex) { - ex.printStackTrace(); - } - } - } - - public void close() { - this.closed = true; - this.interrupt(); - } -} diff --git a/src/org/redkale/net/NioThreadGroup.java b/src/org/redkale/net/NioThreadGroup.java deleted file mode 100644 index cfda032ea..000000000 --- a/src/org/redkale/net/NioThreadGroup.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; -import org.redkale.net.Response; -import org.redkale.util.ObjectPool; - -/** - * 协议处理的IO线程组 - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - * - * @since 2.1.0 - */ -public class NioThreadGroup { - - private NioThread[] threads; - - private final AtomicInteger index = new AtomicInteger(); - - private ScheduledThreadPoolExecutor timeoutExecutor; - - public NioThreadGroup(final String serverName, ExecutorService workExecutor, int iothreads, - ObjectPool safeBufferPool, ObjectPool safeResponsePool) throws IOException { - this.threads = new NioThread[Math.max(iothreads, 1)]; - for (int i = 0; i < this.threads.length; i++) { - ObjectPool unsafeBufferPool = ObjectPool.createUnsafePool(safeBufferPool.getCreatCounter(), - safeBufferPool.getCycleCounter(), 8, - safeBufferPool.getCreator(), safeBufferPool.getPrepare(), safeBufferPool.getRecycler()); - - ObjectPool unsafeResponsePool = ObjectPool.createUnsafePool(safeResponsePool.getCreatCounter(), - safeResponsePool.getCycleCounter(), 8, - safeResponsePool.getCreator(), safeResponsePool.getPrepare(), safeResponsePool.getRecycler()); - String name = "Redkale-" + serverName + "-ServletThread" + "-" + (i >= 9 ? (i + 1) : ("0" + (i + 1))); - this.threads[i] = new NioThread(name, workExecutor, Selector.open(), unsafeBufferPool, safeBufferPool, unsafeResponsePool, safeResponsePool); - } - this.timeoutExecutor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1, (Runnable r) -> { - Thread t = new Thread(r); - t.setName("Redkale-" + serverName + "-IOTimeoutThread"); - t.setDaemon(true); - return t; - }); - } - - public void start() { - for (NioThread thread : threads) { - thread.start(); - } - } - - public void close() { - for (NioThread thread : threads) { - thread.close(); - } - this.timeoutExecutor.shutdownNow(); - } - - public NioThread nextThread() { - return threads[Math.abs(index.getAndIncrement()) % threads.length]; - } - - public ScheduledFuture scheduleTimeout(Runnable callable, long delay, TimeUnit unit) { - return timeoutExecutor.schedule(callable, delay, unit); - } - - public void interestOpsOr(NioThread thread, SelectionKey key, int opt) { - if (key == null) return; - if (key.selector() != thread.selector) throw new RuntimeException("NioThread.selector not the same to SelectionKey.selector"); - if ((key.interestOps() & opt) != 0) return; - key.interestOps(key.interestOps() | opt); - if (thread.inCurrThread()) return; - //非IO线程中 - key.selector().wakeup(); - } - -} diff --git a/src/org/redkale/net/PrepareRunner.java b/src/org/redkale/net/PrepareRunner.java deleted file mode 100644 index 0a51ed35b..000000000 --- a/src/org/redkale/net/PrepareRunner.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net; - -import java.io.IOException; -import java.nio.*; -import java.nio.channels.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.*; -import org.redkale.util.*; - -/** - * 根Servlet的处理逻辑类 - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - */ -@SuppressWarnings("unchecked") -public class PrepareRunner implements Runnable { - - private final AsyncConnection channel; - - private final Context context; - - private final ObjectPool responsePool; - - private ByteBuffer data; - - private Response response; - - public PrepareRunner(Context context, ObjectPool responsePool, AsyncConnection channel, ByteBuffer data, Response response) { - this.context = context; - this.responsePool = responsePool; - this.channel = channel; - this.data = data; - this.response = response; - } - - @Override - public void run() { - if (data != null) { //BIO模式的UDP连接创建AsyncConnection时已经获取到ByteBuffer数据了 - if (response == null) response = responsePool.get(); - try { - response.init(channel); - codec(data, response); - } catch (Throwable t) { - context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); - response.finish(true); - } - return; - } - if (response == null) response = responsePool.get(); - try { - channel.read(new CompletionHandler() { - @Override - public void completed(Integer count, ByteBuffer buffer) { - if (count < 1) { - buffer.clear(); - channel.setReadBuffer(buffer); - channel.dispose();// response.init(channel); 在调用之前异常 - response.removeChannel(); - response.finish(true); - return; - } -// { //测试 -// buffer.flip(); -// byte[] bs = new byte[buffer.remaining()]; -// buffer.get(bs); -// System.println(new String(bs)); -// } - buffer.flip(); - try { - response.init(channel); - codec(buffer, response); - } catch (Throwable t) { //此处不可 context.offerBuffer(buffer); 以免prepare.prepare内部异常导致重复 offerBuffer - context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); - response.finish(true); - } - } - - @Override - public void failed(Throwable exc, ByteBuffer buffer) { - buffer.clear(); - channel.setReadBuffer(buffer); - channel.dispose();// response.init(channel); 在调用之前异常 - response.removeChannel(); - response.finish(true); - if (exc != null && context.logger.isLoggable(Level.FINEST)) { - context.logger.log(Level.FINEST, "Servlet Handler read channel erroneous, force to close channel ", exc); - } - } - }); - } catch (Exception te) { - channel.dispose();// response.init(channel); 在调用之前异常 - response.removeChannel(); - response.finish(true); - if (context.logger.isLoggable(Level.FINEST)) { - context.logger.log(Level.FINEST, "Servlet read channel erroneous, force to close channel ", te); - } - } - } - - protected void codec(final ByteBuffer buffer, final Response response) throws IOException { - final Request request = response.request; - final PrepareServlet preparer = context.prepare; - preparer.executeCounter.incrementAndGet(); - final int rs = request.readHeader(buffer); - if (rs < 0) { //表示数据格式不正确 - channel.offerBuffer(buffer); - if (rs != Integer.MIN_VALUE) preparer.illRequestCounter.incrementAndGet(); - response.finish(true); - } else if (rs == 0) { - if (buffer.hasRemaining()) { - request.setMoredata(buffer); - } else { - buffer.clear(); - channel.setReadBuffer(buffer); - } - preparer.prepare(request, response); - } else { - buffer.clear(); - channel.setReadBuffer(buffer); - final AtomicInteger ai = new AtomicInteger(rs); - channel.read(new CompletionHandler() { - - @Override - public void completed(Integer result, ByteBuffer attachment) { - attachment.flip(); - ai.addAndGet(-request.readBody(attachment)); - if (ai.get() > 0) { - attachment.clear(); - channel.setReadBuffer(attachment); - channel.read(this); - } else { - if (attachment.hasRemaining()) { - request.setMoredata(attachment); - } else { - attachment.clear(); - channel.setReadBuffer(attachment); - } - try { - preparer.prepare(request, response); - } catch (Throwable t) { //此处不可 context.offerBuffer(buffer); 以免preparer.prepare内部异常导致重复 offerBuffer - context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); - response.finish(true); - } - } - } - - @Override - public void failed(Throwable exc, ByteBuffer attachment) { - preparer.illRequestCounter.incrementAndGet(); - attachment.clear(); - channel.setReadBuffer(attachment); - response.finish(true); - if (exc != null) request.context.logger.log(Level.FINER, "Servlet read channel erroneous, force to close channel ", exc); - } - }); - } - } - - protected void initResponse(Response response, AsyncConnection channel) { - response.init(channel); - } - - protected Response pollResponse() { - return responsePool.get(); - } - - protected Request pollRequest(Response response) { - return response.request; - } - - protected AsyncConnection removeChannel(Response response) { - return response.removeChannel(); - } - -} diff --git a/src/org/redkale/net/PrepareServlet.java b/src/org/redkale/net/PrepareServlet.java index 0bd8ebb38..d7c3ebe57 100644 --- a/src/org/redkale/net/PrepareServlet.java +++ b/src/org/redkale/net/PrepareServlet.java @@ -9,6 +9,7 @@ import java.io.*; import java.util.*; import java.util.concurrent.atomic.*; import java.util.function.Predicate; +import java.util.logging.Level; import org.redkale.util.*; /** @@ -207,11 +208,16 @@ public abstract class PrepareServlet { + + private final AsyncConnection channel; + + private final Context context; + + private Supplier responseSupplier; + + private Consumer responseConsumer; + + private Response resp; + + public ProtocolCodec(Context context, Supplier responseSupplier, + Consumer responseConsumer, AsyncConnection channel) { + this.context = context; + this.channel = channel; + this.responseSupplier = responseSupplier; + this.responseConsumer = responseConsumer; + } + + public ProtocolCodec response(Response resp) { + this.resp = resp; + return this; + } + + private Response createResponse() { + Response response = resp; + if (response == null) { + response = responseSupplier.get(); + } else { + response.prepare(); + } + response.responseSupplier = responseSupplier; + response.responseConsumer = responseConsumer; + return response; + } + + @Override + public void completed(Integer count, ByteBuffer buffer) { + if (count < 1) { + channel.offerBuffer(buffer); + channel.dispose(); // response.init(channel); 在调用之前异常 + return; + } +// { //测试 +// buffer.flip(); +// byte[] bs = new byte[buffer.remaining()]; +// buffer.get(bs); +// System.println(new String(bs)); +// } + buffer.flip(); + final Response response = createResponse(); + try { + decode(buffer, response, 0, null); + } catch (Throwable t) { //此处不可 context.offerBuffer(buffer); 以免prepare.prepare内部异常导致重复 offerBuffer + context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); + response.finish(true); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer buffer) { + channel.offerBuffer(buffer); + channel.dispose();// response.init(channel); 在调用之前异常 + if (exc != null && context.logger.isLoggable(Level.FINEST)) { + context.logger.log(Level.FINEST, "Servlet Handler read channel erroneous, force to close channel ", exc); + } + } + + public void run(final ByteBuffer data) { + if (data != null) { //pipeline模式或UDP连接创建AsyncConnection时已经获取到ByteBuffer数据了 + final Response response = createResponse(); + try { + decode(data, response, 0, null); + } catch (Throwable t) { + context.logger.log(Level.WARNING, "prepare servlet abort, force to close channel ", t); + response.finish(true); + } + return; + } + try { + channel.read(this); + } catch (Exception te) { + channel.dispose();// response.init(channel); 在调用之前异常 + if (context.logger.isLoggable(Level.FINEST)) { + context.logger.log(Level.FINEST, "Servlet read channel erroneous, force to close channel ", te); + } + } + } + + protected void decode(final ByteBuffer buffer, final Response response, final int pipelineIndex, final Request lastreq) { + response.init(channel); + final Request request = response.request; + final int rs = request.readHeader(buffer, lastreq); + if (rs < 0) { //表示数据格式不正确 + final PrepareServlet preparer = context.prepare; + preparer.executeCounter.incrementAndGet(); + channel.offerBuffer(buffer); + if (rs != Integer.MIN_VALUE) preparer.illRequestCounter.incrementAndGet(); + response.finish(true); + if (context.logger.isLoggable(Level.FINEST)) { + context.logger.log(Level.FINEST, "request.readHeader erroneous (" + rs + "), force to close channel "); + } + } else if (rs == 0) { + final PrepareServlet preparer = context.prepare; + preparer.executeCounter.incrementAndGet(); + int pindex = pipelineIndex; + boolean pipeline = false; + Request hreq = lastreq; + if (buffer.hasRemaining()) { + pipeline = true; + if (pindex == 0) pindex++; + request.pipeline(pindex, pindex + 1); + if (hreq == null) hreq = request.copyHeader(); + } else { + request.pipeline(pindex, pindex); + channel.setReadBuffer((ByteBuffer) buffer.clear()); + } + context.executePrepareServlet(request, response); + if (pipeline) { + final Response pipelineResponse = createResponse(); + try { + decode(buffer, pipelineResponse, pindex + 1, hreq); + } catch (Throwable t) { //此处不可 offerBuffer(buffer); 以免prepare.prepare内部异常导致重复 offerBuffer + context.logger.log(Level.WARNING, "prepare pipeline servlet abort, force to close channel ", t); + pipelineResponse.finish(true); + } + } + } else { + channel.setReadBuffer(buffer); + channel.read(new CompletionHandler() { + + @Override + public void completed(Integer count, ByteBuffer attachment) { + if (count < 1) { + channel.offerBuffer(attachment); + channel.dispose(); + return; + } + attachment.flip(); + decode(attachment, response, pipelineIndex, lastreq); + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + context.prepare.illRequestCounter.incrementAndGet(); + channel.offerBuffer(attachment); + response.finish(true); + if (exc != null) request.context.logger.log(Level.FINER, "Servlet read channel erroneous, force to close channel ", exc); + } + }); + } + } + +} diff --git a/src/org/redkale/net/ProtocolServer.java b/src/org/redkale/net/ProtocolServer.java index 031fff77e..be04e263a 100644 --- a/src/org/redkale/net/ProtocolServer.java +++ b/src/org/redkale/net/ProtocolServer.java @@ -8,7 +8,8 @@ package org.redkale.net; import java.io.IOException; import java.net.*; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Resource; +import org.redkale.boot.Application; import org.redkale.util.AnyValue; /** @@ -21,20 +22,14 @@ import org.redkale.util.AnyValue; */ public abstract class ProtocolServer { - //创建数 - protected final AtomicLong createCounter = new AtomicLong(); - - //关闭数 - protected final AtomicLong closedCounter = new AtomicLong(); - - //在线数 - protected final AtomicLong livingCounter = new AtomicLong(); - protected final Context context; //最大连接数,小于1表示无限制 protected int maxconns; + @Resource + protected Application application; + public abstract void open(AnyValue config) throws IOException; public abstract void bind(SocketAddress local, int backlog) throws IOException; @@ -43,7 +38,7 @@ public abstract class ProtocolServer { public abstract void setOption(SocketOption name, T value) throws IOException; - public abstract void accept(Server server) throws IOException; + public abstract void accept(Application application, Server server) throws IOException; public abstract void close() throws IOException; @@ -52,45 +47,28 @@ public abstract class ProtocolServer { this.maxconns = context.getMaxconns(); } - public long getCreateCount() { - return createCounter.longValue(); - } - - public long getClosedCount() { - return closedCounter.longValue(); - } - - public long getLivingCount() { - return livingCounter.longValue(); - } - //--------------------------------------------------------------------- public static ProtocolServer create(String protocol, Context context, ClassLoader classLoader, String netimpl) { if (netimpl != null) netimpl = netimpl.trim(); if ("TCP".equalsIgnoreCase(protocol)) { - if (netimpl == null || netimpl.isEmpty()) { - return new AioTcpProtocolServer(context); - } else if ("aio".equalsIgnoreCase(netimpl)) { - return new AioTcpProtocolServer(context); - } else if ("nio".equalsIgnoreCase(netimpl)) { - return new NioTcpProtocolServer(context); - } + return new AsyncNioTcpProtocolServer(context); } else if ("UDP".equalsIgnoreCase(protocol)) { - if (netimpl == null || netimpl.isEmpty()) { - return null;// return new UdpBioProtocolServer(context); - } else if ("bio".equalsIgnoreCase(netimpl)) { - return null;// return new UdpBioProtocolServer(context); - } + return new AsyncNioUdpProtocolServer(context); } else if (netimpl == null || netimpl.isEmpty()) { - throw new RuntimeException("ProtocolServer not support protocol " + protocol); + throw new RuntimeException(ProtocolServer.class.getSimpleName() + " not support protocol " + protocol); } try { if (classLoader == null) classLoader = Thread.currentThread().getContextClassLoader(); Class clazz = classLoader.loadClass(netimpl); return (ProtocolServer) clazz.getDeclaredConstructor(Context.class).newInstance(context); } catch (Exception e) { - throw new RuntimeException("ProtocolServer(netimple=" + netimpl + ") newinstance error", e); + throw new RuntimeException(ProtocolServer.class.getSimpleName() + "(netimple=" + netimpl + ") newinstance error", e); } } + public abstract long getCreateConnectionCount(); + + public abstract long getClosedConnectionCount(); + + public abstract long getLivingConnectionCount(); } diff --git a/src/org/redkale/net/Request.java b/src/org/redkale/net/Request.java index 271f2ce6d..550225b5b 100644 --- a/src/org/redkale/net/Request.java +++ b/src/org/redkale/net/Request.java @@ -31,9 +31,13 @@ public abstract class Request { protected boolean keepAlive; - protected boolean more; //pipeline模式 + protected int pipelineIndex; - protected ByteBuffer moredata; //pipeline模式 + protected int pipelineCount; + + protected boolean pipelineOver; + + protected int hashid; protected AsyncConnection channel; @@ -51,41 +55,35 @@ public abstract class Request { this.jsonConvert = context.getJsonConvert(); } - protected void setMoredata(ByteBuffer buffer) { - this.moredata = buffer; + protected Request copyHeader() { + return null; } - protected ByteBuffer removeMoredata() { - ByteBuffer rs = this.moredata; - this.moredata = null; - return rs; + protected Request pipeline(int pipelineIndex, int pipelineCount) { + this.pipelineIndex = pipelineIndex; + this.pipelineCount = pipelineCount; + return this; } /** * 返回值:Integer.MIN_VALUE: 帧数据; -1:数据不合法; 0:解析完毕; >0: 需再读取的字节数。 * * @param buffer ByteBuffer对象 + * @param last 同一Channel的上一个Request * * @return 缺少的字节数 */ - protected abstract int readHeader(ByteBuffer buffer); - - /** - * 读取buffer,并返回读取的有效数据长度 - * - * @param buffer ByteBuffer对象 - * - * @return 有效数据长度 - */ - protected abstract int readBody(ByteBuffer buffer); + protected abstract int readHeader(ByteBuffer buffer, Request last); protected abstract void prepare(); protected void recycle() { + hashid = 0; createtime = 0; + pipelineIndex = 0; + pipelineCount = 0; + pipelineOver = false; keepAlive = false; - more = false; - moredata = null; attributes.clear(); channel = null; // close it by response } @@ -136,4 +134,13 @@ public abstract class Request { return createtime; } + public int getHashid() { + return hashid; + } + + public Request hashid(int hashid) { + this.hashid = hashid; + return this; + } + } diff --git a/src/org/redkale/net/Response.java b/src/org/redkale/net/Response.java index 4b79923b6..520f7abbf 100644 --- a/src/org/redkale/net/Response.java +++ b/src/org/redkale/net/Response.java @@ -10,7 +10,7 @@ import java.nio.ByteBuffer; import java.nio.channels.CompletionHandler; import java.util.function.*; import java.util.logging.Level; -import org.redkale.util.ObjectPool; +import org.redkale.util.ByteTuple; /** * 协议响应对象 @@ -25,11 +25,11 @@ import org.redkale.util.ObjectPool; @SuppressWarnings("unchecked") public abstract class Response> { - protected static final boolean respConvertByBuffer = Boolean.getBoolean("resp.convert.bytebuffer"); - protected final C context; - protected final ObjectPool responsePool; //虚拟构建的Response可能不存在responsePool + protected Supplier responseSupplier; //虚拟构建的Response可能不存在responseSupplier + + protected Consumer responseConsumer; //虚拟构建的Response可能不存在responseConsumer protected final R request; @@ -45,20 +45,26 @@ public abstract class Response> { protected Servlet> servlet; - private final CompletionHandler finishHandler = new CompletionHandler() { + private final CompletionHandler finishBytesHandler = new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + finish(); + } + + @Override + public void failed(Throwable exc, Void attachment) { + finish(true); + } + + }; + + private final CompletionHandler finishBufferHandler = new CompletionHandler() { @Override public void completed(Integer result, ByteBuffer attachment) { - if (attachment.hasRemaining()) { - channel.write(attachment, attachment, this); - } else { - channel.offerBuffer(attachment); - ByteBuffer data = request.removeMoredata(); - final boolean more = data != null && request.keepAlive; - request.more = more; - finish(); - if (more) new PrepareRunner(context, responsePool, request.channel, null, Response.this).run(); - } + channel.offerBuffer(attachment); + finish(); } @Override @@ -69,51 +75,33 @@ public abstract class Response> { }; - private final CompletionHandler finishHandler2 = new CompletionHandler() { + private final CompletionHandler finishBuffersHandler = new CompletionHandler() { @Override public void completed(final Integer result, final ByteBuffer[] attachments) { - int index = -1; - for (int i = 0; i < attachments.length; i++) { - if (attachments[i].hasRemaining()) { - index = i; - break; - } - } - if (index >= 0) { - channel.write(attachments, index, attachments.length - index, attachments, this); - } else { + if (attachments != null) { for (ByteBuffer attachment : attachments) { channel.offerBuffer(attachment); } - ByteBuffer data = request.removeMoredata(); - final boolean more = data != null && request.keepAlive; - request.more = more; - finish(); - if (more) new PrepareRunner(context, responsePool, request.channel, null, Response.this).run(); } + finish(); } @Override public void failed(Throwable exc, final ByteBuffer[] attachments) { - for (ByteBuffer attachment : attachments) { - channel.offerBuffer(attachment); + if (attachments != null) { + for (ByteBuffer attachment : attachments) { + channel.offerBuffer(attachment); + } } finish(true); } }; - protected Response(C context, final R request, ObjectPool responsePool) { + protected Response(C context, final R request) { this.context = context; this.request = request; - this.responsePool = responsePool; - } - - protected void offerBuffer(ByteBuffer... buffers) { - for (ByteBuffer buffer : buffers) { - channel.offerBuffer(buffer); - } } protected AsyncConnection removeChannel() { @@ -125,6 +113,7 @@ public abstract class Response> { protected void prepare() { inited = true; + request.prepare(); } protected boolean recycle() { @@ -132,11 +121,14 @@ public abstract class Response> { this.output = null; this.filter = null; this.servlet = null; + boolean notpipeline = request.pipelineIndex == 0 || request.pipelineOver; request.recycle(); if (channel != null) { - channel.dispose(); + if (notpipeline) channel.dispose(); channel = null; } + this.responseSupplier = null; + this.responseConsumer = null; this.inited = false; return true; } @@ -207,73 +199,127 @@ public abstract class Response> { } this.recycleListener = null; } - if (request.more) removeChannel(); - if (request.keepAlive && !request.more && channel != null) { - if (channel.isOpen()) { - AsyncConnection conn = removeChannel(); - this.recycle(); - this.prepare(); - new PrepareRunner(context, this.responsePool, conn, null, this).run(); + if (request.keepAlive && (request.pipelineIndex == 0 || request.pipelineOver)) { + AsyncConnection conn = removeChannel(); + if (conn != null && conn.protocolCodec != null) { + this.responseConsumer.accept(this); + conn.read(conn.protocolCodec); } else { - channel.dispose(); + Supplier poolSupplier = this.responseSupplier; + Consumer poolConsumer = this.responseConsumer; + this.recycle(); + new ProtocolCodec(context, poolSupplier, poolConsumer, conn).response(this).run(null); } } else { - this.responsePool.accept(this); + this.responseConsumer.accept(this); } } - public void finish(final byte[] bs) { + public final void finish(final byte[] bs) { + finish(false, bs, 0, bs.length); + } + + public final void finish(final byte[] bs, int offset, int length) { + finish(false, bs, offset, length); + } + + public final void finish(final ByteTuple array) { + finish(false, array.content(), array.offset(), array.length()); + } + + public final void finish(boolean kill, final byte[] bs) { + finish(kill, bs, 0, bs.length); + } + + public final void finish(boolean kill, final ByteTuple array) { + finish(kill, array.content(), array.offset(), array.length()); + } + + public void finish(boolean kill, final byte[] bs, int offset, int length) { if (!this.inited) return; //避免重复关闭 - if (this.context.bufferCapacity == bs.length) { - ByteBuffer buffer = channel.getBufferSupplier().get(); - buffer.put(bs); - buffer.flip(); - this.finish(buffer); + if (kill) refuseAlive(); + if (this.channel.hasPipelineData()) { + this.channel.flushPipelineData(null, new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + channel.write(bs, offset, length, finishBytesHandler); + } + + @Override + public void failed(Throwable exc, Void attachment) { + finishBytesHandler.failed(exc, attachment); + } + }); } else { - this.finish(ByteBuffer.wrap(bs)); + this.channel.write(bs, offset, length, finishBytesHandler); } } - public void finish(ByteBuffer buffer) { - if (!this.inited) return; //避免重复关闭 - final AsyncConnection conn = this.channel; -// ByteBuffer data = this.request.removeMoredata(); -// final boolean more = data != null && this.request.keepAlive; -// this.request.more = more; - conn.write(buffer, buffer, finishHandler); -// if (more) new PrepareRunner(this.context, this.responsePool, conn, data, null).run(); + protected final void finish(ByteBuffer buffer) { + finish(false, buffer); } - public void finish(boolean kill, ByteBuffer buffer) { + protected final void finish(ByteBuffer... buffers) { + finish(false, buffers); + } + + protected void finish(boolean kill, ByteBuffer buffer) { if (!this.inited) return; //避免重复关闭 if (kill) refuseAlive(); - final AsyncConnection conn = this.channel; -// ByteBuffer data = this.request.removeMoredata(); -// final boolean more = data != null && this.request.keepAlive; -// this.request.more = more; - conn.write(buffer, buffer, finishHandler); -// if (more) new PrepareRunner(this.context, this.responsePool, conn, data, null).run(); + if (this.channel.hasPipelineData()) { + this.channel.flushPipelineData(null, new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + channel.write(buffer, buffer, finishBufferHandler); + } + + @Override + public void failed(Throwable exc, Void attachment) { + finishBufferHandler.failed(exc, buffer); + } + }); + } else { + this.channel.write(buffer, buffer, finishBufferHandler); + } } - public void finish(ByteBuffer... buffers) { - if (!this.inited) return; //避免重复关闭 - final AsyncConnection conn = this.channel; -// ByteBuffer data = this.request.removeMoredata(); -// final boolean more = data != null && this.request.keepAlive; -// this.request.more = more; - conn.write(buffers, buffers, finishHandler2); -// if (more) new PrepareRunner(this.context, this.responsePool, conn, data, null).run(); - } - - public void finish(boolean kill, ByteBuffer... buffers) { + protected void finish(boolean kill, ByteBuffer... buffers) { if (!this.inited) return; //避免重复关闭 if (kill) refuseAlive(); - final AsyncConnection conn = this.channel; - ByteBuffer data = this.request.removeMoredata(); - final boolean more = data != null && this.request.keepAlive; - this.request.more = more; - conn.write(buffers, buffers, finishHandler2); - if (more) new PrepareRunner(this.context, this.responsePool, conn, data, null).run(); + if (this.channel.hasPipelineData()) { + this.channel.flushPipelineData(null, new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + channel.write(buffers, buffers, finishBuffersHandler); + } + + @Override + public void failed(Throwable exc, Void attachment) { + finishBuffersHandler.failed(exc, buffers); + } + }); + } else { + this.channel.write(buffers, buffers, finishBuffersHandler); + } + } + + protected void send(final ByteTuple array, final CompletionHandler handler) { + this.channel.write(array, new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + if (handler != null) handler.completed(result, attachment); + } + + @Override + public void failed(Throwable exc, Void attachment) { + if (handler != null) handler.failed(exc, attachment); + } + + }); } protected void send(final ByteBuffer buffer, final A attachment, final CompletionHandler handler) { @@ -281,12 +327,8 @@ public abstract class Response> { @Override public void completed(Integer result, A attachment) { - if (buffer.hasRemaining()) { - channel.write(buffer, attachment, this); - } else { - channel.offerBuffer(buffer); - if (handler != null) handler.completed(result, attachment); - } + channel.offerBuffer(buffer); + if (handler != null) handler.completed(result, attachment); } @Override @@ -303,21 +345,8 @@ public abstract class Response> { @Override public void completed(Integer result, A attachment) { - int index = -1; - for (int i = 0; i < buffers.length; i++) { - if (buffers[i].hasRemaining()) { - index = i; - break; - } - channel.offerBuffer(buffers[i]); - } - if (index == 0) { - channel.write(buffers, attachment, this); - } else if (index > 0) { - ByteBuffer[] newattachs = new ByteBuffer[buffers.length - index]; - System.arraycopy(buffers, index, newattachs, 0, newattachs.length); - channel.write(newattachs, attachment, this); - } else if (handler != null) handler.completed(result, attachment); + channel.offerBuffer(buffers); + if (handler != null) handler.completed(result, attachment); } @Override diff --git a/src/org/redkale/net/Server.java b/src/org/redkale/net/Server.java index dd9bbb40c..a859d354d 100644 --- a/src/org/redkale/net/Server.java +++ b/src/org/redkale/net/Server.java @@ -9,12 +9,11 @@ import java.io.*; import java.net.*; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.text.*; import java.util.*; -import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.logging.*; import javax.net.ssl.SSLContext; +import org.redkale.boot.Application; import org.redkale.util.*; /** @@ -33,7 +32,8 @@ public abstract class Server servlet) { + protected Server(Application application, long serverStartTime, String netprotocol, ResourceFactory resourceFactory, PrepareServlet servlet) { this.serverStartTime = serverStartTime; - this.protocol = protocol; + this.netprotocol = netprotocol; this.resourceFactory = resourceFactory; this.prepare = servlet; } @@ -127,12 +121,11 @@ public abstract class Server { - String threadname = "Redkale-" + n + "-WorkThread-" + f.format(counter.incrementAndGet()); - Thread t = new WorkThread(threadname, workExecutor, r); - return t; - }); } protected static int parseLenth(String value, int defValue) { @@ -187,10 +172,6 @@ public abstract class Server createBufferPool(AtomicLong createCounter, AtomicLong cycleCounter, int bufferPoolSize); @@ -370,12 +343,12 @@ public abstract class Server 10) sf = "00"; - if (this.threads > 100) sf = "000"; - if (this.threads > 1000) sf = "0000"; - return new DecimalFormat(sf); + return serverChannel == null ? -1 : serverChannel.getLivingConnectionCount(); } public static URL[] loadLib(final RedkaleClassLoader classLoader, final Logger logger, final String lib) throws Exception { diff --git a/src/org/redkale/net/Transport.java b/src/org/redkale/net/Transport.java index 4825274dd..d97b75fc4 100644 --- a/src/org/redkale/net/Transport.java +++ b/src/org/redkale/net/Transport.java @@ -30,7 +30,7 @@ import org.redkale.util.*; */ public final class Transport { - public static final String DEFAULT_PROTOCOL = "TCP"; + public static final String DEFAULT_NETPROTOCOL = "TCP"; protected final AtomicInteger seq = new AtomicInteger(-1); @@ -40,17 +40,16 @@ public final class Transport { protected final boolean tcp; - protected final String protocol; + protected final String netprotocol; - protected final AsynchronousChannelGroup group; + //传输端的AsyncGroup + protected final AsyncGroup asyncGroup; protected final InetSocketAddress clientAddress; //不可能为null protected TransportNode[] transportNodes = new TransportNode[0]; - protected final ObjectPool bufferPool; - protected final SSLContext sslContext; //负载均衡策略 @@ -59,23 +58,20 @@ public final class Transport { //连接上限, 为null表示无限制 protected Semaphore semaphore; - protected Transport(String name, TransportFactory factory, final ObjectPool transportBufferPool, - final AsynchronousChannelGroup transportChannelGroup, final SSLContext sslContext, final InetSocketAddress clientAddress, + protected Transport(String name, TransportFactory factory, final AsyncGroup asyncGroup, final SSLContext sslContext, final InetSocketAddress clientAddress, final Collection addresses, final TransportStrategy strategy) { - this(name, DEFAULT_PROTOCOL, factory, transportBufferPool, transportChannelGroup, sslContext, clientAddress, addresses, strategy); + this(name, DEFAULT_NETPROTOCOL, factory, asyncGroup, sslContext, clientAddress, addresses, strategy); } - protected Transport(String name, String protocol, final TransportFactory factory, final ObjectPool transportBufferPool, - final AsynchronousChannelGroup transportChannelGroup, final SSLContext sslContext, final InetSocketAddress clientAddress, + protected Transport(String name, String netprotocol, final TransportFactory factory, final AsyncGroup asyncGroup, final SSLContext sslContext, final InetSocketAddress clientAddress, final Collection addresses, final TransportStrategy strategy) { this.name = name; - this.protocol = protocol; + this.netprotocol = netprotocol; this.factory = factory; factory.transportReferences.add(new WeakReference<>(this)); - this.tcp = "TCP".equalsIgnoreCase(protocol); - this.group = transportChannelGroup; + this.tcp = "TCP".equalsIgnoreCase(netprotocol); + this.asyncGroup = asyncGroup; this.sslContext = sslContext; - this.bufferPool = transportBufferPool; this.clientAddress = clientAddress; this.strategy = strategy; updateRemoteAddresses(addresses); @@ -190,44 +186,19 @@ public final class Transport { @Override public String toString() { - return Transport.class.getSimpleName() + "{name = " + name + ", protocol = " + protocol + ", clientAddress = " + clientAddress + ", remoteNodes = " + Arrays.toString(transportNodes) + "}"; + return Transport.class.getSimpleName() + "{name = " + name + ", protocol = " + netprotocol + ", clientAddress = " + clientAddress + ", remoteNodes = " + Arrays.toString(transportNodes) + "}"; } - public ByteBuffer pollBuffer() { - return bufferPool.get(); - } - - public Supplier getBufferSupplier() { - return bufferPool; - } - - public void offerBuffer(ByteBuffer buffer) { - bufferPool.accept(buffer); - } - - public void offerBuffer(ByteBuffer... buffers) { - for (ByteBuffer buffer : buffers) offerBuffer(buffer); - } - - public AsynchronousChannelGroup getTransportChannelGroup() { - return group; - } - - public String getProtocol() { - return protocol; + public String getNetprotocol() { + return netprotocol; } public boolean isTCP() { return tcp; } - protected CompletableFuture pollAsync(TransportNode node, SocketAddress addr, Supplier> func, final int count) { - if (count >= 5) { - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new RuntimeException("create AsyncConnection error")); - return future; - } - final BlockingQueue queue = node.conns; + protected CompletableFuture pollAsync(TransportNode node, SocketAddress addr, Supplier> func) { + final BlockingQueue queue = node.connQueue; if (!queue.isEmpty()) { AsyncConnection conn; while ((conn = queue.poll()) != null) { @@ -239,24 +210,16 @@ public final class Transport { } } if (semaphore != null && !semaphore.tryAcquire()) { - return CompletableFuture.supplyAsync(() -> { - try { - return queue.poll(1, TimeUnit.SECONDS); - } catch (Exception t) { - return null; - } - }, factory.executor).thenCompose((conn2) -> { - if (conn2 != null && conn2.isOpen()) { - return CompletableFuture.completedFuture(conn2); - } - return pollAsync(node, addr, func, count + 1); - }); + final CompletableFuture future = Utility.orTimeout(new CompletableFuture<>(), 10, TimeUnit.SECONDS); + future.whenComplete((r, t) -> node.pollQueue.remove(future)); + if (node.pollQueue.offer(future)) return future; + future.completeExceptionally(new IOException("create transport connection error")); + return future; } return func.get().thenApply(conn -> { if (conn != null && semaphore != null) conn.beforeCloseListener((c) -> semaphore.release()); return conn; }); - } public CompletableFuture pollConnection(SocketAddress addr0) { @@ -269,17 +232,12 @@ public final class Transport { try { if (!tcp) { // UDP SocketAddress udpaddr = rand ? nodes[0].address : addr; - DatagramChannel channel = DatagramChannel.open(); - channel.configureBlocking(true); - channel.connect(udpaddr); - return CompletableFuture.completedFuture(AsyncConnection.create(bufferPool, channel, sslContext, udpaddr, true, factory.readTimeoutSeconds, factory.writeTimeoutSeconds)); + return asyncGroup.createUDP(udpaddr, factory.readTimeoutSeconds, factory.writeTimeoutSeconds); } if (!rand) { //指定地址 TransportNode node = findTransportNode(addr); - if (node == null) { - return AsyncConnection.createTCP(bufferPool, group, sslContext, addr, factory.readTimeoutSeconds, factory.writeTimeoutSeconds); - } - return pollAsync(node, addr, () -> AsyncConnection.createTCP(bufferPool, group, sslContext, addr, factory.readTimeoutSeconds, factory.writeTimeoutSeconds), 1); + if (node == null) return asyncGroup.createTCP(addr, factory.readTimeoutSeconds, factory.writeTimeoutSeconds); + return pollAsync(node, addr, () -> asyncGroup.createTCP(addr, factory.readTimeoutSeconds, factory.writeTimeoutSeconds)); } //---------------------随机取地址------------------------ @@ -292,7 +250,7 @@ public final class Transport { final long now = System.currentTimeMillis(); if (enablecount > 0) { //存在可用的地址 final TransportNode one = newnodes[Math.abs(seq.incrementAndGet()) % enablecount]; - final BlockingQueue queue = one.conns; + final BlockingQueue queue = one.connQueue; if (!queue.isEmpty()) { AsyncConnection conn; while ((conn = queue.poll()) != null) { @@ -304,52 +262,11 @@ public final class Transport { } } return pollAsync(one, one.getAddress(), () -> { - CompletableFuture future = new CompletableFuture(); - AsynchronousSocketChannel channel0 = null; - try { - channel0 = AsynchronousSocketChannel.open(group); - channel0.setOption(StandardSocketOptions.TCP_NODELAY, true); - channel0.setOption(StandardSocketOptions.SO_KEEPALIVE, true); - channel0.setOption(StandardSocketOptions.SO_REUSEADDR, true); - } catch (Exception ex) { - ex.printStackTrace(); - } - final AsynchronousSocketChannel channel = channel0; - channel.connect(one.address, one, new CompletionHandler() { - @Override - public void completed(Void result, TransportNode attachment) { - attachment.disabletime = 0; - AsyncConnection asyncConn = AsyncConnection.create(bufferPool, channel, attachment.address, factory.readTimeoutSeconds, factory.writeTimeoutSeconds); - if (future.isDone()) { - if (!attachment.conns.offer(asyncConn)) asyncConn.dispose(); - } else { - future.complete(asyncConn); - } - } - - @Override - public void failed(Throwable exc, TransportNode attachment) { - attachment.disabletime = now; - try { - channel.close(); - } catch (Exception e) { - } - try { - pollConnection0(nodes, one, now).whenComplete((r, t) -> { - if (t != null) { - future.completeExceptionally(t); - } else { - future.complete(r); - } - }); - - } catch (Exception e) { - future.completeExceptionally(e); - } - } - }); - return future; - }, 1); + return asyncGroup.createTCP(one.address, factory.readTimeoutSeconds, factory.writeTimeoutSeconds) + .whenComplete((c, t) -> { + one.disabletime = t == null ? 0 : System.currentTimeMillis(); + }); + }); } return pollConnection0(nodes, null, now); } catch (Exception ex) { @@ -359,43 +276,15 @@ public final class Transport { private CompletableFuture pollConnection0(TransportNode[] nodes, TransportNode exclude, long now) throws IOException { //从可用/不可用的地址列表中创建连接 - AtomicInteger count = new AtomicInteger(nodes.length); CompletableFuture future = new CompletableFuture(); for (final TransportNode node : nodes) { if (node == exclude) continue; if (future.isDone()) return future; - final AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(group); - channel.setOption(StandardSocketOptions.TCP_NODELAY, true); - channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); - channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); - channel.connect(node.address, node, new CompletionHandler() { - @Override - public void completed(Void result, TransportNode attachment) { - try { - attachment.disabletime = 0; - AsyncConnection asyncConn = AsyncConnection.create(bufferPool, channel, attachment.address, factory.readTimeoutSeconds, factory.writeTimeoutSeconds); - if (future.isDone()) { - if (!attachment.conns.offer(asyncConn)) asyncConn.dispose(); - } else { - future.complete(asyncConn); - } - } catch (Exception e) { - failed(e, attachment); - } - } - - @Override - public void failed(Throwable exc, TransportNode attachment) { - attachment.disabletime = now; - if (count.decrementAndGet() < 1) { - future.completeExceptionally(exc); - } - try { - channel.close(); - } catch (Exception e) { - } - } - }); + asyncGroup.createTCP(node.address, factory.readTimeoutSeconds, factory.writeTimeoutSeconds) + .whenComplete((c, t) -> { + if (c != null && !future.complete(c)) node.connQueue.offer(c); + node.disabletime = t == null ? 0 : System.currentTimeMillis(); + }); } return future; } @@ -405,7 +294,7 @@ public final class Transport { if (!forceClose && conn.isTCP()) { if (conn.isOpen()) { TransportNode node = findTransportNode(conn.getRemoteAddress()); - if (node == null || !node.conns.offer(conn)) conn.dispose(); + if (node == null || !node.connQueue.offer(conn)) conn.dispose(); } else { conn.dispose(); } @@ -459,25 +348,29 @@ public final class Transport { protected volatile long disabletime; //不可用时的时间, 为0表示可用 - protected final BlockingQueue conns; + protected final BlockingQueue connQueue; + + protected final ArrayBlockingQueue> pollQueue; protected final ConcurrentHashMap attributes = new ConcurrentHashMap<>(); public TransportNode(int poolmaxconns, InetSocketAddress address) { this.address = address; this.disabletime = 0; - this.conns = new ArrayBlockingQueue<>(poolmaxconns); + this.connQueue = new ArrayBlockingQueue<>(poolmaxconns); + this.pollQueue = new ArrayBlockingQueue(this.connQueue.remainingCapacity() * 100); } @ConstructorParameters({"poolmaxconns", "address", "disabletime"}) public TransportNode(int poolmaxconns, InetSocketAddress address, long disabletime) { this.address = address; this.disabletime = disabletime; - this.conns = new LinkedBlockingQueue<>(poolmaxconns); + this.connQueue = new LinkedBlockingQueue<>(poolmaxconns); + this.pollQueue = new ArrayBlockingQueue(this.connQueue.remainingCapacity() * 100); } public int getPoolmaxconns() { - return this.conns.remainingCapacity() + this.conns.size(); + return this.connQueue.remainingCapacity() + this.connQueue.size(); } public T setAttribute(String name, T value) { @@ -518,13 +411,13 @@ public final class Transport { } @ConvertDisabled - public BlockingQueue getConns() { - return conns; + public BlockingQueue getConnQueue() { + return connQueue; } public void dispose() { AsyncConnection conn; - while ((conn = conns.poll()) != null) { + while ((conn = connQueue.poll()) != null) { conn.dispose(); } } diff --git a/src/org/redkale/net/TransportFactory.java b/src/org/redkale/net/TransportFactory.java index b8073801e..523a2907d 100644 --- a/src/org/redkale/net/TransportFactory.java +++ b/src/org/redkale/net/TransportFactory.java @@ -5,15 +5,12 @@ */ package org.redkale.net; -import java.io.IOException; import java.lang.ref.WeakReference; import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.*; -import java.util.function.Supplier; import java.util.logging.*; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; @@ -45,14 +42,8 @@ public class TransportFactory { protected static final Logger logger = Logger.getLogger(TransportFactory.class.getSimpleName()); - //传输端的线程池 - protected final ExecutorService executor; - - //传输端的ByteBuffer对象池 - protected final ObjectPool bufferPool; - - //传输端的ChannelGroup - protected final AsynchronousChannelGroup channelGroup; + //传输端的AsyncGroup + protected final AsyncGroup asyncGroup; //每个地址对应的Group名 protected final Map groupAddrs = new HashMap<>(); @@ -90,23 +81,25 @@ public class TransportFactory { //pong的数据长度, 小于0表示不进行判断 protected int pongLength; + //是否TCP + protected String netprotocol = "TCP"; + //负载均衡策略 protected final TransportStrategy strategy; - protected TransportFactory(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, - SSLContext sslContext, int readTimeoutSeconds, int writeTimeoutSeconds, final TransportStrategy strategy) { - this.executor = executor; - this.bufferPool = bufferPool; - this.channelGroup = channelGroup; + protected TransportFactory(AsyncGroup asyncGroup, SSLContext sslContext, String netprotocol, + int readTimeoutSeconds, int writeTimeoutSeconds, final TransportStrategy strategy) { + this.asyncGroup = asyncGroup; this.sslContext = sslContext; + this.netprotocol = netprotocol; this.readTimeoutSeconds = readTimeoutSeconds; this.writeTimeoutSeconds = writeTimeoutSeconds; this.strategy = strategy; } - protected TransportFactory(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, - SSLContext sslContext, int readTimeoutSeconds, int writeTimeoutSeconds) { - this(executor, bufferPool, channelGroup, sslContext, readTimeoutSeconds, writeTimeoutSeconds, null); + protected TransportFactory(AsyncGroup asyncGroup, SSLContext sslContext, String netprotocol, + int readTimeoutSeconds, int writeTimeoutSeconds) { + this(asyncGroup, sslContext, netprotocol, readTimeoutSeconds, writeTimeoutSeconds, null); } public void init(AnyValue conf, ByteBuffer pingBuffer, int pongLength) { @@ -143,71 +136,28 @@ public class TransportFactory { } } - public static TransportFactory create(int threads) { - return create(threads, threads * 2, 8 * 1024, DEFAULT_READTIMEOUTSECONDS, DEFAULT_WRITETIMEOUTSECONDS); + public static TransportFactory create(AsyncGroup asyncGroup, int readTimeoutSeconds, int writeTimeoutSeconds) { + return new TransportFactory(asyncGroup, null, "TCP", readTimeoutSeconds, writeTimeoutSeconds, null); } - public static TransportFactory create(int threads, int bufferPoolSize, int bufferCapacity) { - return create(threads, bufferPoolSize, bufferCapacity, DEFAULT_READTIMEOUTSECONDS, DEFAULT_WRITETIMEOUTSECONDS); + public static TransportFactory create(AsyncGroup asyncGroup, SSLContext sslContext, int readTimeoutSeconds, int writeTimeoutSeconds, final TransportStrategy strategy) { + return new TransportFactory(asyncGroup, sslContext, "TCP", readTimeoutSeconds, writeTimeoutSeconds, strategy); } - public static TransportFactory create(int threads, int bufferPoolSize, int bufferCapacity, int readTimeoutSeconds, int writeTimeoutSeconds) { - final ObjectPool transportPool = ObjectPool.createSafePool(new AtomicLong(), new AtomicLong(), bufferPoolSize, - (Object... params) -> ByteBuffer.allocateDirect(bufferCapacity), null, (e) -> { - if (e == null || e.isReadOnly() || e.capacity() != bufferCapacity) return false; - e.clear(); - return true; - }); - final AtomicInteger counter = new AtomicInteger(); - ExecutorService transportExec = Executors.newFixedThreadPool(threads, (Runnable r) -> { - Thread t = new Thread(r); - t.setDaemon(true); - t.setName("Redkale-Transport-Thread-" + counter.incrementAndGet()); - return t; - }); - AsynchronousChannelGroup transportGroup = null; - try { - transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1); - } catch (IOException e) { - throw new RuntimeException(e); - } - return create(transportExec, transportPool, transportGroup, readTimeoutSeconds, writeTimeoutSeconds); + public static TransportFactory create(AsyncGroup asyncGroup, String netprotocol, int readTimeoutSeconds, int writeTimeoutSeconds) { + return new TransportFactory(asyncGroup, null, netprotocol, readTimeoutSeconds, writeTimeoutSeconds, null); } - public static TransportFactory create(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup) { - return new TransportFactory(executor, bufferPool, channelGroup, null, DEFAULT_READTIMEOUTSECONDS, DEFAULT_WRITETIMEOUTSECONDS, null); - } - - public static TransportFactory create(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, - int readTimeoutSeconds, int writeTimeoutSeconds) { - return new TransportFactory(executor, bufferPool, channelGroup, null, readTimeoutSeconds, writeTimeoutSeconds, null); - } - - public static TransportFactory create(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, - int readTimeoutSeconds, int writeTimeoutSeconds, final TransportStrategy strategy) { - return new TransportFactory(executor, bufferPool, channelGroup, null, readTimeoutSeconds, writeTimeoutSeconds, strategy); - } - - public static TransportFactory create(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, SSLContext sslContext) { - return new TransportFactory(executor, bufferPool, channelGroup, sslContext, DEFAULT_READTIMEOUTSECONDS, DEFAULT_WRITETIMEOUTSECONDS, null); - } - - public static TransportFactory create(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, - SSLContext sslContext, int readTimeoutSeconds, int writeTimeoutSeconds) { - return new TransportFactory(executor, bufferPool, channelGroup, sslContext, readTimeoutSeconds, writeTimeoutSeconds, null); - } - - public static TransportFactory create(ExecutorService executor, ObjectPool bufferPool, AsynchronousChannelGroup channelGroup, - SSLContext sslContext, int readTimeoutSeconds, int writeTimeoutSeconds, final TransportStrategy strategy) { - return new TransportFactory(executor, bufferPool, channelGroup, sslContext, readTimeoutSeconds, writeTimeoutSeconds, strategy); + public static TransportFactory create(AsyncGroup asyncGroup, SSLContext sslContext, String netprotocol, int readTimeoutSeconds, int writeTimeoutSeconds, final TransportStrategy strategy) { + return new TransportFactory(asyncGroup, sslContext, netprotocol, readTimeoutSeconds, writeTimeoutSeconds, strategy); } public Transport createTransportTCP(String name, final InetSocketAddress clientAddress, final Collection addresses) { - return new Transport(name, "TCP", this, this.bufferPool, this.channelGroup, this.sslContext, clientAddress, addresses, strategy); + return new Transport(name, "TCP", this, this.asyncGroup, this.sslContext, clientAddress, addresses, strategy); } - public Transport createTransport(String name, String protocol, final InetSocketAddress clientAddress, final Collection addresses) { - return new Transport(name, protocol, this, this.bufferPool, this.channelGroup, this.sslContext, clientAddress, addresses, strategy); + public Transport createTransport(String name, String netprotocol, final InetSocketAddress clientAddress, final Collection addresses) { + return new Transport(name, netprotocol, this, this.asyncGroup, this.sslContext, clientAddress, addresses, strategy); } public String findGroupName(InetSocketAddress addr) { @@ -269,17 +219,13 @@ public class TransportFactory { if (info == null) continue; addresses.addAll(info.addresses); } - if (info == null) info = new TransportGroupInfo("TCP"); + if (info == null) { + info = new TransportGroupInfo(netprotocol); + } else { + info.protocol = netprotocol; + } if (sncpAddress != null) addresses.remove(sncpAddress); - return new Transport(groups.stream().sorted().collect(Collectors.joining(";")), info.protocol, this, this.bufferPool, this.channelGroup, this.sslContext, sncpAddress, addresses, this.strategy); - } - - public ExecutorService getExecutor() { - return executor; - } - - public Supplier getBufferSupplier() { - return bufferPool; + return new Transport(groups.stream().sorted().collect(Collectors.joining(";")), info.protocol, this, this.asyncGroup, this.sslContext, sncpAddress, addresses, this.strategy); } public List getGroupInfos() { @@ -306,11 +252,6 @@ public class TransportFactory { public void shutdownNow() { if (this.scheduler != null) this.scheduler.shutdownNow(); - try { - this.channelGroup.shutdownNow(); - } catch (Exception e) { - logger.log(Level.FINER, "close transportChannelGroup erroneous", e); - } } private void checks() { @@ -324,21 +265,11 @@ public class TransportFactory { Transport.TransportNode[] nodes = transport.getTransportNodes(); for (final Transport.TransportNode node : nodes) { if (node.disabletime < 1) continue; //可用 - try { - final AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(transport.group); - channel.connect(node.address, node, new CompletionHandler() { - @Override - public void completed(Void result, Transport.TransportNode attachment) { - attachment.disabletime = 0; - } - - @Override - public void failed(Throwable exc, Transport.TransportNode attachment) { - attachment.disabletime = System.currentTimeMillis(); - } - }); - } catch (Exception e) { - } + CompletableFuture future = Utility.orTimeout(asyncGroup.createTCP(node.address), 2, TimeUnit.SECONDS); + future.whenComplete((r, t) -> { + node.disabletime = t == null ? 0 : System.currentTimeMillis(); + if (r != null) r.dispose(); + }); } } for (WeakReference ref : nulllist) { @@ -353,7 +284,7 @@ public class TransportFactory { if (transport == null) continue; Transport.TransportNode[] nodes = transport.getTransportNodes(); for (final Transport.TransportNode node : nodes) { - final BlockingQueue queue = node.conns; + final BlockingQueue queue = node.connQueue; AsyncConnection conn; while ((conn = queue.poll()) != null) { if (conn.getLastWriteTime() > timex && false) { //最近几秒内已经进行过IO操作 @@ -365,10 +296,6 @@ public class TransportFactory { localconn.write(sendBuffer, sendBuffer, new CompletionHandler() { @Override public void completed(Integer result, ByteBuffer wbuffer) { - if (wbuffer.hasRemaining()) { - localconn.write(wbuffer, wbuffer, this); - return; - } localconn.read(new CompletionHandler() { int counter = 0; diff --git a/src/org/redkale/net/WorkThread.java b/src/org/redkale/net/WorkThread.java index f3309e3c4..a2b919a8a 100644 --- a/src/org/redkale/net/WorkThread.java +++ b/src/org/redkale/net/WorkThread.java @@ -6,6 +6,7 @@ package org.redkale.net; import java.util.concurrent.*; +import org.redkale.util.ThreadHashExecutor; /** * 协议处理的自定义线程类 @@ -21,15 +22,42 @@ public class WorkThread extends Thread { protected final ExecutorService workExecutor; + protected final ThreadHashExecutor hashExecutor; + public WorkThread(String name, ExecutorService workExecutor, Runnable target) { super(target); if (name != null) setName(name); this.workExecutor = workExecutor; + this.hashExecutor = workExecutor instanceof ThreadHashExecutor ? (ThreadHashExecutor) workExecutor : null; this.setDaemon(true); } - public void runAsync(Runnable runner) { - workExecutor.execute(runner); + public void runWork(Runnable command) { + if (workExecutor == null) { + command.run(); + } else { + workExecutor.execute(command); + } + } + + public void runAsync(Runnable command) { + if (workExecutor == null) { + ForkJoinPool.commonPool().execute(command); + } else { + workExecutor.execute(command); + } + } + + public void runAsync(int hash, Runnable command) { + if (hashExecutor == null) { + if (workExecutor == null) { + ForkJoinPool.commonPool().execute(command); + } else { + workExecutor.execute(command); + } + } else { + hashExecutor.execute(hash, command); + } } public ExecutorService getWorkExecutor() { diff --git a/src/org/redkale/net/client/Client.java b/src/org/redkale/net/client/Client.java new file mode 100644 index 000000000..2489c94f5 --- /dev/null +++ b/src/org/redkale/net/client/Client.java @@ -0,0 +1,219 @@ +/* + * 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.client; + +import java.net.SocketAddress; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + * + * @param 请求对象 + * @param

响应对象 + */ +public abstract class Client { + + protected final AsyncGroup group; //连接构造器 + + protected final boolean tcp; //是否TCP协议 + + protected final SocketAddress address; //连接的地址 + + protected final ConcurrentLinkedDeque> connQueue = new ConcurrentLinkedDeque(); + + protected final Creator> codecCreator; + + protected final ScheduledThreadPoolExecutor timeoutScheduler; + + protected final AtomicLong writeReqCounter = new AtomicLong(); + + protected final AtomicLong pollRespCounter = new AtomicLong(); + + protected ScheduledFuture timeoutFuture; + + protected ClientConnection[] connArray; //连接池 + + protected AtomicInteger[] connResps; //连接当前处理数 + + protected AtomicBoolean[] connFlags; //conns的标记组,当conn不存在或closed状态,标记为false + + protected int connLimit = Runtime.getRuntime().availableProcessors(); //最大连接数 + + protected int maxPipelines = 16; //单个连接最大并行处理数 + + protected int readTimeoutSeconds; + + protected int writeTimeoutSeconds; + + //------------------ 可选项 ------------------ + //PING心跳的请求数据,为null且pingInterval<1表示不需要定时ping + protected R pingRequest; + + //关闭请求的数据, 为null表示直接关闭 + protected R closeRequest; + + //创建连接后进行的登录鉴权操作 + protected Function, CompletableFuture> authenticate; + + protected Client(AsyncGroup group, SocketAddress address, Creator> responseCreator) { + this(group, true, address, Runtime.getRuntime().availableProcessors(), 16, responseCreator, null, null, null); + } + + protected Client(AsyncGroup group, boolean tcp, SocketAddress address, Creator> codecCreator) { + this(group, tcp, address, Runtime.getRuntime().availableProcessors(), 16, codecCreator, null, null, null); + } + + protected Client(AsyncGroup group, boolean tcp, SocketAddress address, int maxconns, Creator> codecCreator) { + this(group, tcp, address, maxconns, 16, codecCreator, null, null, null); + } + + protected Client(AsyncGroup group, boolean tcp, SocketAddress address, int maxconns, int maxPipelines, Creator> codecCreator) { + this(group, tcp, address, maxconns, maxPipelines, codecCreator, null, null, null); + } + + protected Client(AsyncGroup group, boolean tcp, SocketAddress address, int maxconns, Creator> codecCreator, + Function, CompletableFuture> authenticate) { + this(group, tcp, address, maxconns, 16, codecCreator, null, null, authenticate); + } + + protected Client(AsyncGroup group, boolean tcp, SocketAddress address, int maxconns, Creator> codecCreator, + R closeRequest, Function, CompletableFuture> authenticate) { + this(group, tcp, address, maxconns, 16, codecCreator, null, closeRequest, authenticate); + } + + protected Client(AsyncGroup group, boolean tcp, SocketAddress address, int maxconns, + int maxPipelines, Creator> codecCreator, R pingRequest, R closeRequest, + Function, CompletableFuture> authenticate) { + if (maxPipelines < 1) throw new IllegalArgumentException("maxPipelines must bigger 0"); + this.group = group; + this.tcp = tcp; + this.address = address; + this.connLimit = maxconns; + this.maxPipelines = maxPipelines; + this.pingRequest = pingRequest; + this.closeRequest = closeRequest; + this.codecCreator = codecCreator; + this.authenticate = authenticate; + this.connArray = new ClientConnection[connLimit]; + this.connFlags = new AtomicBoolean[connLimit]; + this.connResps = new AtomicInteger[connLimit]; + for (int i = 0; i < connFlags.length; i++) { + this.connFlags[i] = new AtomicBoolean(); + this.connResps[i] = new AtomicInteger(); + } + //timeoutScheduler 不仅仅给超时用, 还给write用 + this.timeoutScheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { + final Thread t = new Thread(r, "Redkale-" + Client.this.getClass().getSimpleName() + "-Interval-Thread"); + t.setDaemon(true); + return t; + }); + if (pingRequest != null) { + this.timeoutFuture = this.timeoutScheduler.scheduleAtFixedRate(() -> { + try { + ClientRequest req = pingRequest; + if (req == null) { //可能运行中进行重新赋值 + timeoutFuture.cancel(true); + timeoutFuture = null; + return; + } + long now = System.currentTimeMillis(); + for (ClientConnection conn : this.connArray) { + if (conn == null) continue; + if (now - conn.getLastWriteTime() > 10_000) conn.writeChannel(req); + } + } catch (Throwable t) { + } + }, 10, 10, TimeUnit.SECONDS); + } + } + + public void close() { + this.timeoutScheduler.shutdownNow(); + final R closereq = closeRequest; + for (ClientConnection conn : this.connArray) { + if (conn == null) continue; + if (closereq == null) { + conn.dispose(null); + } else { + try { + conn.writeChannel(closereq).get(100, TimeUnit.MILLISECONDS); + } catch (Exception e) { + } + conn.dispose(null); + } + } + } + + public CompletableFuture

sendAsync(R request) { + return connect().thenCompose(conn -> conn.writeChannel(request)); + } + + protected CompletableFuture

writeChannel(ClientConnection conn, R request) { + return conn.writeChannel(request); + } + + protected CompletableFuture connect() { + ClientConnection minRunningConn = null; + for (int i = 0; i < this.connArray.length; i++) { + final int index = i; + final ClientConnection conn = this.connArray[index]; + if (conn == null || !conn.isOpen()) { + if (this.connFlags[index].compareAndSet(false, true)) { + CompletableFuture future = group.create(tcp, address, readTimeoutSeconds, writeTimeoutSeconds).thenApply(c -> createClientConnection(index, c)); + return (authenticate == null ? future : authenticate.apply(future)).thenApply(c -> { + c.authenticated = true; + this.connArray[index] = c; + return c; + }).whenComplete((r, t) -> { + if (t != null) this.connFlags[index].set(false); + }); + } + } else if (conn.runningCount() < 1) { + return CompletableFuture.completedFuture(conn); + } else if (minRunningConn == null || minRunningConn.runningCount() > conn.runningCount()) { + minRunningConn = conn; + } + } + if (minRunningConn != null && minRunningConn.runningCount() < maxPipelines) return CompletableFuture.completedFuture(minRunningConn); + return waitClientConnection(); + } + + protected CompletableFuture waitClientConnection() { + CompletableFuture rs = Utility.orTimeout(new CompletableFuture(), 6, TimeUnit.SECONDS); + connQueue.offer(rs); + return rs; + } + + protected ClientConnection createClientConnection(final int index, AsyncConnection channel) { + return new ClientConnection(this, index, channel); + } + + public int getReadTimeoutSeconds() { + return readTimeoutSeconds; + } + + public void setReadTimeoutSeconds(int readTimeoutSeconds) { + this.readTimeoutSeconds = readTimeoutSeconds; + } + + public int getWriteTimeoutSeconds() { + return writeTimeoutSeconds; + } + + public void setWriteTimeoutSeconds(int writeTimeoutSeconds) { + this.writeTimeoutSeconds = writeTimeoutSeconds; + } + +} diff --git a/src/org/redkale/net/client/ClientCodec.java b/src/org/redkale/net/client/ClientCodec.java new file mode 100644 index 000000000..1488a368a --- /dev/null +++ b/src/org/redkale/net/client/ClientCodec.java @@ -0,0 +1,57 @@ +/* + * 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.client; + +import java.nio.ByteBuffer; +import java.util.*; +import org.redkale.convert.json.JsonConvert; +import org.redkale.util.ByteArray; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + * @param ClientRequest + * @param

响应对象 + */ +public abstract class ClientCodec { + + private final List> results = new ArrayList<>(); + + public ClientCodec() { + } + + //返回true后array会clear + public abstract boolean codecResult(ClientConnection conn, List requests, ByteBuffer buffer, ByteArray array); + + public List> removeResults() { + if (results.isEmpty()) return null; + List> rs = new ArrayList<>(results); + this.results.clear(); + return rs; + } + + public void addResult(P result) { + this.results.add(new ClientResult<>(result)); + } + + public void addResult(Throwable exc) { + this.results.add(new ClientResult<>(exc)); + } + + public void reset() { + this.results.clear(); + } + + @Override + public String toString() { + return JsonConvert.root().convertTo(this); + } + +} diff --git a/src/org/redkale/net/client/ClientConnection.java b/src/org/redkale/net/client/ClientConnection.java new file mode 100644 index 000000000..1358fcc0c --- /dev/null +++ b/src/org/redkale/net/client/ClientConnection.java @@ -0,0 +1,273 @@ +/* + * 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.client; + +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import java.util.stream.*; +import org.redkale.net.AsyncConnection; +import org.redkale.util.ByteArray; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + * + * @param 请求对象 + * @param

响应对象 + */ +public class ClientConnection implements Consumer { + + protected final int index; + + protected final Client client; + + protected final AtomicInteger respCounter; + + protected final AsyncConnection channel; + + protected final ByteArray writeArray = new ByteArray(); + + protected final ByteArray readArray = new ByteArray(); + + protected final AtomicBoolean readPending = new AtomicBoolean(); + + protected final AtomicBoolean writePending = new AtomicBoolean(); + + protected final ConcurrentLinkedDeque requestQueue = new ConcurrentLinkedDeque(); + + protected final ConcurrentLinkedDeque responseQueue = new ConcurrentLinkedDeque(); + + protected final CompletionHandler writeHandler = new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + if (writeLastRequest != null && writeLastRequest == client.closeRequest) { + if (closeFuture != null) closeFuture.complete(null); + closeFuture = null; + return; + } + + if (continueWrite()) return; + if (!writePending.compareAndSet(true, false)) { + if (continueWrite()) return; + } + + readChannel(); + } + + @Override + public void failed(Throwable exc, Void attachment) { + dispose(exc); + } + }; + + private boolean continueWrite() { + writeArray.clear(); + int pipelines = client.maxPipelines > 1 ? (client.maxPipelines - responseQueue.size()) : 1; + currPipelineIndex = 0; + for (int i = 0; i < pipelines; i++) { + R r = requestQueue.poll(); + if (r == null) break; + writeLastRequest = r; + r.accept(ClientConnection.this, writeArray); + currPipelineIndex++; + } + if (writeArray.length() > 0) { + channel.write(writeArray, writeHandler); + return true; + } + return false; + } + + protected final CompletionHandler readHandler = new CompletionHandler() { + + ClientCodec codec; + + @Override + public void completed(Integer count, ByteBuffer attachment) { + if (count < 1) { + channel.setReadBuffer(attachment); + dispose(new NonReadableChannelException()); + return; + } + try { + if (codec == null) codec = client.codecCreator.create(); + attachment.flip(); + codecResponse(attachment); + } catch (Exception e) { + channel.setReadBuffer(attachment); + dispose(e); + } + } + + public void codecResponse(ByteBuffer buffer) { + Stream reqstream = responseQueue.stream().map(r -> (R) r.request); + List requests = reqstream.collect(Collectors.toList()); + if (codec.codecResult(ClientConnection.this, requests, buffer, readArray)) { //成功了 + readArray.clear(); + List> results = codec.removeResults(); + if (results != null) { + for (ClientResult

rs : results) { + ClientFuture respFuture = responseQueue.poll(); + if (respFuture != null) { + respCounter.decrementAndGet(); + if (isAuthenticated()) client.pollRespCounter.incrementAndGet(); + try { + if (respFuture.timeout != null) respFuture.timeout.cancel(true); + if (rs.exc != null) { + respFuture.completeExceptionally(rs.exc); + } else { + respFuture.complete(rs.result); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + } + { + CompletableFuture connFuture = client.connQueue.poll(); + if (connFuture != null) connFuture.complete(ClientConnection.this); + } + if (buffer.hasRemaining()) { + codecResponse(buffer); + } else if (responseQueue.isEmpty()) { //队列都已处理完了 + buffer.clear(); + channel.setReadBuffer(buffer); + if (readPending.compareAndSet(true, false)) { + CompletableFuture connFuture = client.connQueue.poll(); + if (connFuture != null) connFuture.complete(ClientConnection.this); + } else { + channel.read(this); + } + } else { + buffer.clear(); + channel.setReadBuffer(buffer); + channel.read(this); + } + } else { //数据不全, 继续读 + buffer.clear(); + channel.setReadBuffer(buffer); + channel.read(this); + } + } + + @Override + public void failed(Throwable t, ByteBuffer attachment) { + dispose(t); + } + }; + + protected boolean authenticated; + + protected int currPipelineIndex; + + protected ClientFuture closeFuture; + + private R writeLastRequest; + + public ClientConnection(Client client, int index, AsyncConnection channel) { + this.client = client; + this.index = index; + this.respCounter = client.connResps[index]; + this.channel = channel.beforeCloseListener(this); + } + + protected CompletableFuture

writeChannel(R request) { + ClientFuture respFuture = createClientFuture(request); + if (request == client.closeRequest) { + respFuture.request = null; + closeFuture = respFuture; + } else { + int rts = this.channel.getReadTimeoutSeconds(); + if (rts > 0 && respFuture.request != null) { + respFuture.responseQueue = responseQueue; + respFuture.timeout = client.timeoutScheduler.schedule(respFuture, rts, TimeUnit.SECONDS); + } + } + synchronized (requestQueue) { //保证顺序一致 + responseQueue.offer(respFuture.request == null ? ClientFuture.EMPTY : respFuture); + requestQueue.offer(request); + respCounter.incrementAndGet(); + if (isAuthenticated()) client.writeReqCounter.incrementAndGet(); + } + if (writePending.compareAndSet(false, true)) { + writeArray.clear(); + int pipelines = client.maxPipelines > 1 ? client.maxPipelines : 1; //pipelines必须大于0 + currPipelineIndex = 0; + for (int i = 0; i < pipelines; i++) { + R r = requestQueue.poll(); + if (r == null) break; + r.accept(ClientConnection.this, writeArray); + currPipelineIndex++; + } + channel.write(writeArray, writeHandler); + } + return respFuture; + } + + protected ClientFuture createClientFuture(R request) { + return new ClientFuture(request); + } + + protected void readChannel() { + if (readPending.compareAndSet(false, true)) { + readArray.clear(); + channel.read(readHandler); + } + } + + public boolean isAuthenticated() { + return authenticated; + } + + public AsyncConnection getChannel() { + return channel; + } + + public int currPipelineIndex() { + return currPipelineIndex; + } + + @Override //AsyncConnection.beforeCloseListener + public void accept(AsyncConnection t) { + respCounter.set(0); + client.connFlags[index].set(false); + client.connArray[index] = null; //必须connflags之后 + } + + public void dispose(Throwable exc) { + channel.dispose(); + Throwable e = exc; + CompletableFuture f; + respCounter.set(0); + while ((f = responseQueue.poll()) != null) { + if (e == null) e = new ClosedChannelException(); + f.completeExceptionally(e); + } + } + + public int runningCount() { + return respCounter.get(); + } + + public long getLastWriteTime() { + return channel.getLastWriteTime(); + } + + public boolean isOpen() { + return channel.isOpen(); + } + +} diff --git a/src/org/redkale/net/client/ClientFuture.java b/src/org/redkale/net/client/ClientFuture.java new file mode 100644 index 000000000..3bb9992d6 --- /dev/null +++ b/src/org/redkale/net/client/ClientFuture.java @@ -0,0 +1,58 @@ +/* + * 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.client; + +import java.util.concurrent.*; + +/** + * + * @author zhangjx + * @param 泛型 + */ +public class ClientFuture extends CompletableFuture implements Runnable { + + public static final ClientFuture EMPTY = new ClientFuture() { + @Override + public boolean complete(Object value) { + return true; + } + + @Override + public boolean completeExceptionally(Throwable ex) { + return true; + } + }; + + protected ClientRequest request; + + ScheduledFuture timeout; + + ConcurrentLinkedDeque responseQueue; + + public ClientFuture() { + super(); + } + + public ClientFuture(ClientRequest request) { + super(); + this.request = request; + } + + //@Override //JDK9+ + public ClientFuture newIncompleteFuture() { + return new ClientFuture<>(); + } + + public R getRequest() { + return (R) request; + } + + @Override + public void run() { + if (responseQueue != null) responseQueue.remove(this); + this.completeExceptionally(new TimeoutException()); + } +} diff --git a/src/org/redkale/net/client/ClientRequest.java b/src/org/redkale/net/client/ClientRequest.java new file mode 100644 index 000000000..6c47cf25e --- /dev/null +++ b/src/org/redkale/net/client/ClientRequest.java @@ -0,0 +1,35 @@ +/* + * 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.client; + +import java.util.function.*; +import org.redkale.util.ByteArray; + +/** + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + */ +public interface ClientRequest extends BiConsumer { + + public static class ClientBytesRequest implements ClientRequest { + + protected byte[] bytes; + + public ClientBytesRequest(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public void accept(ClientConnection conn, ByteArray array) { + array.put(bytes); + } + + } +} diff --git a/src/org/redkale/net/client/ClientResult.java b/src/org/redkale/net/client/ClientResult.java new file mode 100644 index 000000000..b62a61db6 --- /dev/null +++ b/src/org/redkale/net/client/ClientResult.java @@ -0,0 +1,47 @@ +/* + * 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.client; + +/** + * + * @author zhangjx + */ +public class ClientResult

{ + + protected P result; + + protected Throwable exc; + + public ClientResult(P result) { + this.result = result; + } + + public ClientResult(Throwable exc) { + this.exc = exc; + } + + public P getResult() { + return result; + } + + public void setResult(P result) { + this.result = result; + } + + public Throwable getExc() { + return exc; + } + + public void setExc(Throwable exc) { + this.exc = exc; + } + + @Override + public String toString() { + if (exc != null) return "{\"exc\":" + exc + "}"; + return "{\"result\":" + result + "}"; + } +} diff --git a/src/org/redkale/net/http/HttpClient.java b/src/org/redkale/net/http/HttpClient.java new file mode 100644 index 000000000..1c633f917 --- /dev/null +++ b/src/org/redkale/net/http/HttpClient.java @@ -0,0 +1,347 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net.http; + +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.CompletionHandler; +import java.nio.charset.*; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.redkale.net.*; +import static org.redkale.net.http.HttpRequest.parseHeaderName; +import org.redkale.util.*; + +/** + * 简单的HttpClient实现, 存在以下情况不能使用此类:
+ * 1、使用HTTPS;
+ * 2、上传下载文件;
+ * 3、返回超大响应包;
+ * 类似JDK11的 java.net.http.HttpClient
+ * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + * + */ +public class HttpClient { + + protected final AsyncGroup asyncGroup; + + protected int readTimeoutSeconds = 6; + + protected int writeTimeoutSeconds = 6; + + protected HttpClient(AsyncGroup asyncGroup) { + this.asyncGroup = asyncGroup; + } + + public static HttpClient create(AsyncGroup asyncGroup) { + return new HttpClient(asyncGroup); + } + + public CompletableFuture> getAsync(String url) { + return sendAsync("GET", url, null, (byte[]) null); + } + + public CompletableFuture> postAsync(String url) { + return sendAsync("POST", url, null, (byte[]) null); + } + + public CompletableFuture> getAsync(String url, String body) { + return sendAsync("GET", url, null, body == null ? null : body.getBytes(StandardCharsets.UTF_8)); + } + + public CompletableFuture> postAsync(String url, String body) { + return sendAsync("POST", url, null, body == null ? null : body.getBytes(StandardCharsets.UTF_8)); + } + + public CompletableFuture> getAsync(String url, byte[] body) { + return sendAsync("GET", url, null, body); + } + + public CompletableFuture> postAsync(String url, byte[] body) { + return sendAsync("POST", url, null, body); + } + + public CompletableFuture> getAsync(String url, Map headers) { + return sendAsync("GET", url, headers, (byte[]) null); + } + + public CompletableFuture> postAsync(String url, Map headers) { + return sendAsync("POST", url, headers, (byte[]) null); + } + + public CompletableFuture> getAsync(String url, Map headers, String body) { + return sendAsync("GET", url, headers, body == null ? null : body.getBytes(StandardCharsets.UTF_8)); + } + + public CompletableFuture> postAsync(String url, Map headers, String body) { + return sendAsync("POST", url, headers, body == null ? null : body.getBytes(StandardCharsets.UTF_8)); + } + + public CompletableFuture> getAsync(String url, Map headers, byte[] body) { + return sendAsync("GET", url, headers, body); + } + + public CompletableFuture> postAsync(String url, Map headers, byte[] body) { + return sendAsync("POST", url, headers, body); + } + + public CompletableFuture> sendAsync(String method, String url, Map headers, byte[] body) { + final URI uri = URI.create(url); + final SocketAddress address = new InetSocketAddress(uri.getHost(), uri.getPort() > 0 ? uri.getPort() : (url.startsWith("https:") ? 443 : 80)); + return asyncGroup.createTCP(address, readTimeoutSeconds, writeTimeoutSeconds).thenCompose(conn -> { + final ByteArray array = new ByteArray(); + int urlpos = url.indexOf("/", url.indexOf("//") + 3); + array.put((method + " " + (urlpos > 0 ? url.substring(urlpos) : "/") + " HTTP/1.1\r\n" + + "Host: " + uri.getHost() + "\r\n" + + "Content-Length: " + (body == null ? 0 : body.length) + "\r\n").getBytes(StandardCharsets.UTF_8)); + if (headers == null || !headers.containsKey("User-Agent")) { + array.put(("User-Agent: redkale-httpclient/" + Redkale.getDotedVersion() + "\r\n").getBytes(StandardCharsets.UTF_8)); + } + if (headers == null || !headers.containsKey("Connection")) { + array.put(("Connection: close\r\n").getBytes(StandardCharsets.UTF_8)); + } + if (headers != null) { + headers.forEach((k, v) -> { + array.put((k + ": " + v + "\r\n").getBytes(StandardCharsets.UTF_8)); + }); + } + array.put((byte) '\r', (byte) '\n'); + if (body != null) array.put(body); + System.out.println(array); + final CompletableFuture> future = new CompletableFuture(); + conn.write(array, new CompletionHandler() { + @Override + public void completed(Integer result, Void attachment) { + conn.read(new ClientReadCompletionHandler(conn, array.clear(), future)); + } + + @Override + public void failed(Throwable exc, Void attachment) { + conn.dispose(); + future.completeExceptionally(exc); + } + }); + return future; + }); + } + + public static void main(String[] args) throws Throwable { + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); + String url = "http://redkale.org"; + HttpClient client = HttpClient.create(asyncGroup); + System.out.println(client.getAsync(url).join()); + } + + protected static class ClientReadCompletionHandler implements CompletionHandler { + + protected static final int READ_STATE_ROUTE = 1; + + protected static final int READ_STATE_HEADER = 2; + + protected static final int READ_STATE_BODY = 3; + + protected static final int READ_STATE_END = 4; + + protected final AsyncConnection conn; + + protected final ByteArray array; + + protected final CompletableFuture> future; + + protected HttpResult responseResult; + + protected int readState = READ_STATE_ROUTE; + + protected int contentLength = -1; + + public ClientReadCompletionHandler(AsyncConnection conn, ByteArray array, CompletableFuture> future) { + this.conn = conn; + this.array = array; + this.future = future; + } + + @Override + public void completed(Integer count, ByteBuffer buffer) { + buffer.flip(); + if (this.readState == READ_STATE_ROUTE) { + if (this.responseResult == null) { + this.responseResult = new HttpResult<>(); + } + int rs = readStatusLine(buffer); + if (rs != 0) { + buffer.clear(); + conn.setReadBuffer(buffer); + conn.read(this); + return; + } + this.readState = READ_STATE_HEADER; + } + if (this.readState == READ_STATE_HEADER) { + int rs = readHeaderLines(buffer); + if (rs != 0) { + buffer.clear(); + conn.setReadBuffer(buffer); + conn.read(this); + return; + } + this.readState = READ_STATE_BODY; + } + if (this.readState == READ_STATE_BODY) { + if (this.contentLength > 0) { + array.put(buffer, Math.min((int) this.contentLength, buffer.remaining())); + int lr = (int) this.contentLength - array.length(); + if (lr == 0) { + this.readState = READ_STATE_END; + } else { + buffer.clear(); + conn.setReadBuffer(buffer); + conn.read(this); + return; + } + } + if (buffer.hasRemaining()) array.put(buffer, buffer.remaining()); + this.readState = READ_STATE_END; + } + this.responseResult.setResult(array.getBytes()); + this.future.complete(this.responseResult); + conn.offerBuffer(buffer); + conn.dispose(); + } + + //解析 HTTP/1.1 200 OK + private int readStatusLine(final ByteBuffer buffer) { + int remain = buffer.remaining(); + ByteArray bytes = array; + for (;;) { + if (remain-- < 1) { + buffer.clear(); + return 1; + } + byte b = buffer.get(); + if (b == '\r') { + if (remain-- < 1) { + buffer.clear(); + buffer.put((byte) '\r'); + return 1; + } + if (buffer.get() != '\n') return -1; + break; + } + bytes.put(b); + } + String value = bytes.toString(null); + int pos = value.indexOf(' '); + this.responseResult.setStatus(Integer.decode(value.substring(pos + 1, value.indexOf(" ", pos + 2)))); + bytes.clear(); + return 0; + } + + //解析Header Connection: keep-alive + private int readHeaderLines(final ByteBuffer buffer) { + int remain = buffer.remaining(); + ByteArray bytes = array; + HttpResult result = responseResult; + for (;;) { + bytes.clear(); + if (remain-- < 2) { + if (remain == 1) { + byte one = buffer.get(); + buffer.clear(); + buffer.put(one); + return 1; + } + buffer.clear(); + return 1; + } + remain--; + byte b1 = buffer.get(); + byte b2 = buffer.get(); + if (b1 == '\r' && b2 == '\n') return 0; + bytes.put(b1, b2); + for (;;) { // name + if (remain-- < 1) { + buffer.clear(); + buffer.put(bytes.content(), 0, bytes.length()); + return 1; + } + byte b = buffer.get(); + if (b == ':') break; + bytes.put(b); + } + String name = parseHeaderName(bytes, null); + bytes.clear(); + boolean first = true; + int space = 0; + for (;;) { // value + if (remain-- < 1) { + buffer.clear(); + buffer.put(name.getBytes()); + buffer.put((byte) ':'); + if (space == 1) { + buffer.put((byte) ' '); + } else if (space > 0) { + for (int i = 0; i < space; i++) buffer.put((byte) ' '); + } + buffer.put(bytes.content(), 0, bytes.length()); + return 1; + } + byte b = buffer.get(); + if (b == '\r') { + if (remain-- < 1) { + buffer.clear(); + buffer.put(name.getBytes()); + buffer.put((byte) ':'); + if (space == 1) { + buffer.put((byte) ' '); + } else if (space > 0) { + for (int i = 0; i < space; i++) buffer.put((byte) ' '); + } + buffer.put(bytes.content(), 0, bytes.length()); + buffer.put((byte) '\r'); + return 1; + } + if (buffer.get() != '\n') return -1; + break; + } + if (first) { + if (b <= ' ') { + space++; + continue; + } + first = false; + } + bytes.put(b); + } + String value; + switch (name) { + case "Content-Length": + case "content-length": + value = bytes.toString(null); + this.contentLength = Integer.decode(value); + result.header(name, value); + break; + default: + value = bytes.toString(null); + result.header(name, value); + } + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + conn.offerBuffer(attachment); + conn.dispose(); + future.completeExceptionally(exc); + } + + } +} diff --git a/src/org/redkale/net/http/HttpContext.java b/src/org/redkale/net/http/HttpContext.java index aba71922f..80d1a5e40 100644 --- a/src/org/redkale/net/http/HttpContext.java +++ b/src/org/redkale/net/http/HttpContext.java @@ -28,11 +28,14 @@ public class HttpContext extends Context { protected final ConcurrentHashMap asyncHandlerCreators = new ConcurrentHashMap<>(); - protected String remoteAddrHeader; + protected final String remoteAddrHeader; + + protected final boolean lazyHeaders; public HttpContext(HttpContextConfig config) { super(config); this.remoteAddrHeader = config.remoteAddrHeader; + this.lazyHeaders = config.lazyHeaders; random.setSeed(Math.abs(System.nanoTime())); } @@ -42,10 +45,6 @@ public class HttpContext extends Context { return new String(Utility.binToHex(bytes)); } - protected ExecutorService getExecutor() { - return executor; - } - @SuppressWarnings("unchecked") protected Creator loadAsyncHandlerCreator(Class handlerClass) { Creator creator = asyncHandlerCreators.get(handlerClass); @@ -162,5 +161,7 @@ public class HttpContext extends Context { public static class HttpContextConfig extends ContextConfig { public String remoteAddrHeader; + + public boolean lazyHeaders; } } diff --git a/src/org/redkale/net/http/HttpMapping.java b/src/org/redkale/net/http/HttpMapping.java index e12590df3..67852793b 100644 --- a/src/org/redkale/net/http/HttpMapping.java +++ b/src/org/redkale/net/http/HttpMapping.java @@ -46,6 +46,10 @@ public @interface HttpMapping { */ int cacheseconds() default 0; + //json结果的长度, 临时功能, 仅供Rest的HttpResponse.finishJson(final int length, final Object obj) 方法使用 + @Deprecated + int length() default 0; + /** * 是否只接受RPC请求, 默认为false * diff --git a/src/org/redkale/net/http/HttpPrepareServlet.java b/src/org/redkale/net/http/HttpPrepareServlet.java index fc58bb6f9..543cd4b77 100644 --- a/src/org/redkale/net/http/HttpPrepareServlet.java +++ b/src/org/redkale/net/http/HttpPrepareServlet.java @@ -319,62 +319,66 @@ public class HttpPrepareServlet extends PrepareServlet newmappings = new HashMap<>(wsmappings); - newmappings.put(mapping, (WebSocketServlet) servlet); + newmappings.put(mappingpath, (WebSocketServlet) servlet); this.wsmappings = newmappings; } } - if (this.allMapStrings.containsKey(mapping)) { - Class old = this.allMapStrings.get(mapping); - throw new RuntimeException("mapping [" + mapping + "] repeat on " + old.getName() + " and " + servlet.getClass().getName()); + if (this.allMapStrings.containsKey(mappingpath)) { + Class old = this.allMapStrings.get(mappingpath); + throw new RuntimeException("mapping [" + mappingpath + "] repeat on " + old.getName() + " and " + servlet.getClass().getName()); } - this.allMapStrings.put(mapping, servlet.getClass()); + this.allMapStrings.put(mappingpath, servlet.getClass()); } setServletConf(servlet, conf); servlet._prefix = prefix.toString(); diff --git a/src/org/redkale/net/http/HttpRequest.java b/src/org/redkale/net/http/HttpRequest.java index 6a42a4731..f132c4fc7 100644 --- a/src/org/redkale/net/http/HttpRequest.java +++ b/src/org/redkale/net/http/HttpRequest.java @@ -37,10 +37,61 @@ public class HttpRequest extends Request { protected static final Serializable CURRUSERID_NIL = new Serializable() { }; + protected static final int READ_STATE_ROUTE = 1; + + protected static final int READ_STATE_HEADER = 2; + + protected static final int READ_STATE_BODY = 3; + + protected static final int READ_STATE_END = 4; + + protected static final byte[] EMPTY_BYTES = new byte[0]; + + protected static final String KEY_GET = "GET"; + + protected static final String KEY_POST = "POST"; + + protected static final String KEY_HTTP_1_1 = "HTTP/1.1"; + + protected static final String KEY_COOKIE = "Cookie"; + + protected static final String KEY_CONNECTION = "Connection"; + + protected static final String KEY_CONTENT_TYPE = "Content-Type"; + + protected static final String KEY_ACCEPT = "Accept"; + + protected static final String KEY_HOST = "Host"; + public static final String SESSIONID_NAME = "JSESSIONID"; + //---------- header 相关参数 开始 ---------- + protected int headerLength; + + protected int headerHalfLen; + + protected String contentType; + + protected long contentLength = -1; + + protected String host; + + @Comment("原始的cookie字符串,解析后值赋给HttpCookie[] cookies") + protected String cookie; + + protected HttpCookie[] cookies; + + private boolean maybews = false; //是否可能是WebSocket + protected boolean rpc; + protected int readState = READ_STATE_ROUTE; + + // @since 2.1.0 + protected Serializable currentUserid = CURRUSERID_NIL; + + protected Object currentUser; + protected boolean frombody; protected ConvertType reqConvertType; @@ -51,33 +102,22 @@ public class HttpRequest extends Request { protected Convert respConvert; + protected final Map headers = new HashMap<>(); + //---------- header 相关参数 结束 ---------- + @Comment("Method GET/POST/...") protected String method; + protected boolean getmethod; + protected String protocol; protected String requestURI; protected byte[] queryBytes; - protected long contentLength = -1; - - protected String contentType; - - protected String host; - - protected String connection; - - @Comment("原始的cookie字符串,解析后值赋给HttpCookie[] cookies") - protected String cookie; - - protected HttpCookie[] cookies; - protected String newsessionid; - //protected final DefaultAnyValue headers = new DefaultAnyValue(); - protected final Map headers = new HashMap<>(); - protected final Map params = new HashMap<>(); protected boolean boundary = false; @@ -88,32 +128,42 @@ public class HttpRequest extends Request { protected Annotation[] annotations; - // @since 2.1.0 - protected Serializable currentUserid = CURRUSERID_NIL; - - protected Object currentUser; - protected String remoteAddr; - private final ByteArray array = new ByteArray(); + private final ByteArray array; - private boolean bodyparsed = false; + private byte[] headerBytes; + + private boolean headerParsed = false; + + private boolean bodyParsed = false; private final String remoteAddrHeader; - Object attachment; //仅供HttpServlet传递Entry使用 + HttpServlet.ActionEntry actionEntry; //仅供HttpServlet传递Entry使用 public HttpRequest(HttpContext context) { + this(context, new ByteArray()); + } + + protected HttpRequest(HttpContext context, ByteArray array) { super(context); + this.array = array; this.remoteAddrHeader = context.remoteAddrHeader; } - public HttpRequest(HttpContext context, HttpSimpleRequest req) { + @SuppressWarnings("OverridableMethodCallInConstructor") + protected HttpRequest(HttpContext context, HttpSimpleRequest req) { super(context); + this.array = new ByteArray(); this.remoteAddrHeader = null; + if (req != null) initSimpleRequest(req); + } + + protected HttpRequest initSimpleRequest(HttpSimpleRequest req) { if (req != null) { this.rpc = req.rpc; - if (req.getBody() != null) this.array.write(req.getBody()); + if (req.getBody() != null) this.array.put(req.getBody()); if (req.getHeaders() != null) this.headers.putAll(req.getHeaders()); this.frombody = req.isFrombody(); this.reqConvertType = req.getReqConvertType(); @@ -121,6 +171,7 @@ public class HttpRequest extends Request { this.respConvertType = req.getRespConvertType(); this.respConvert = req.getRespConvertType() == null ? null : ConvertFactory.findConvert(req.getRespConvertType()); if (req.getParams() != null) this.params.putAll(req.getParams()); + this.hashid = req.getHashid(); if (req.getCurrentUserid() != 0) this.currentUserid = req.getCurrentUserid(); this.contentType = req.getContentType(); this.remoteAddr = req.getRemoteAddr(); @@ -130,12 +181,13 @@ public class HttpRequest extends Request { this.cookies = new HttpCookie[]{new HttpCookie(SESSIONID_NAME, req.getSessionid())}; } } + return this; } public HttpSimpleRequest createSimpleRequest(String prefix) { HttpSimpleRequest req = new HttpSimpleRequest(); - req.setBody(array.size() == 0 ? null : array.getBytes()); - if (!headers.isEmpty()) { + req.setBody(array.length() == 0 ? null : array.getBytes()); + if (!getHeaders().isEmpty()) { if (headers.containsKey(Rest.REST_HEADER_RPC_NAME) || headers.containsKey(Rest.REST_HEADER_CURRUSERID_NAME)) { //外部request不能包含RPC的header信息 req.setHeaders(new HashMap<>(headers)); @@ -154,6 +206,7 @@ public class HttpRequest extends Request { if (prefix != null && !prefix.isEmpty() && uri.startsWith(prefix)) { uri = uri.substring(prefix.length()); } + req.setHashid(this.hashid); req.setRequestURI(uri); req.setSessionid(getSessionid(false)); req.setRpc(this.rpc); @@ -161,7 +214,11 @@ public class HttpRequest extends Request { } protected boolean isWebSocket() { - return "websocket".equalsIgnoreCase(getHeader("Upgrade")) && "Upgrade".equalsIgnoreCase(connection) && "GET".equalsIgnoreCase(method); + return maybews && "Upgrade".equalsIgnoreCase(getHeader("Connection")) && "GET".equalsIgnoreCase(method); + } + + protected void setPipelineOver(boolean pipelineOver) { + this.pipelineOver = pipelineOver; } protected void setKeepAlive(boolean keepAlive) { @@ -176,6 +233,14 @@ public class HttpRequest extends Request { return this.channel; } + protected int getPipelineIndex() { + return this.pipelineIndex; + } + + protected int getPipelineCount() { + return this.pipelineCount; + } + protected ConvertType getRespConvertType() { return this.respConvertType; } @@ -185,60 +250,337 @@ public class HttpRequest extends Request { } @Override - protected int readHeader(final ByteBuffer buffer) { + protected int readHeader(final ByteBuffer buffer, final Request last) { ByteArray bytes = array; - if (!readLine(buffer, bytes)) return -1; - Charset charset = this.context.getCharset(); - int index = 0; - int offset = bytes.find(index, ' '); - if (offset <= 0) return -1; - this.method = bytes.toString(index, offset, charset); - index = ++offset; - offset = bytes.find(index, ' '); - if (offset <= 0) return -1; - int off = bytes.find(index, '#'); - if (off > 0) offset = off; - int qst = bytes.find(index, offset, (byte) '?'); - if (qst > 0) { - this.requestURI = bytes.toDecodeString(index, qst - index, charset); - this.queryBytes = bytes.getBytes(qst + 1, offset - qst - 1); - try { - addParameter(bytes, qst + 1, offset - qst - 1); - } catch (Exception e) { - this.context.getLogger().log(Level.WARNING, "HttpRequest.addParameter error: " + bytes.toString(), e); - } - } else { - this.requestURI = bytes.toDecodeString(index, offset - index, charset); - this.queryBytes = new byte[0]; + if (this.readState == READ_STATE_ROUTE) { + int rs = readMethodLine(buffer); + if (rs != 0) return rs; + this.readState = READ_STATE_HEADER; } - index = ++offset; - this.protocol = bytes.toString(index, bytes.size() - index, charset); + if (this.readState == READ_STATE_HEADER) { + if (last != null && ((HttpRequest) last).headerLength > 0) { + final HttpRequest httplast = (HttpRequest) last; + int bufremain = buffer.remaining(); + int remainhalf = httplast.headerLength - this.headerHalfLen; + if (remainhalf > bufremain) { + bytes.put(buffer); + this.headerHalfLen += bufremain; + buffer.clear(); + return 1; + } + buffer.position(buffer.position() + remainhalf); + this.contentType = httplast.contentType; + this.contentLength = httplast.contentLength; + this.host = httplast.host; + this.cookie = httplast.cookie; + this.cookies = httplast.cookies; + this.keepAlive = httplast.keepAlive; + this.maybews = httplast.maybews; + this.rpc = httplast.rpc; + this.hashid = httplast.hashid; + this.currentUserid = httplast.currentUserid; + this.frombody = httplast.frombody; + this.reqConvertType = httplast.reqConvertType; + this.reqConvert = httplast.reqConvert; + this.respConvertType = httplast.respConvertType; + this.respConvert = httplast.respConvert; + this.headerLength = httplast.headerLength; + this.headerHalfLen = httplast.headerLength; + this.headerBytes = httplast.headerBytes; + this.headerParsed = httplast.headerParsed; + this.headers.putAll(httplast.headers); + } else if (context.lazyHeaders && getmethod) { + int rs = loadHeaderBytes(buffer); + if (rs != 0) return rs; + this.headerParsed = false; + } else { + int startpos = buffer.position(); + int rs = readHeaderLines(buffer, bytes); + if (rs != 0) { + this.headerHalfLen = bytes.length(); + return rs; + } + this.headerParsed = true; + this.headerLength = buffer.position() - startpos + this.headerHalfLen; + this.headerHalfLen = this.headerLength; + } + bytes.clear(); + this.readState = READ_STATE_BODY; + } + if (this.contentType != null && this.contentType.contains("boundary=")) this.boundary = true; + if (this.boundary) this.keepAlive = false; //文件上传必须设置keepAlive为false,因为文件过大时用户不一定会skip掉多余的数据 + if (this.readState == READ_STATE_BODY) { + if (this.contentLength > 0 && (this.contentType == null || !this.boundary)) { + if (this.contentLength > context.getMaxbody()) return -1; + bytes.put(buffer, Math.min((int) this.contentLength, buffer.remaining())); + int lr = (int) this.contentLength - bytes.length(); + if (lr == 0) this.readState = READ_STATE_END; + return lr > 0 ? lr : 0; + } + if (buffer.hasRemaining() && (this.boundary || !this.keepAlive)) bytes.put(buffer, buffer.remaining()); //文件上传、HTTP1.0或Connection:close + this.readState = READ_STATE_END; + } + //暂不考虑是keep-alive且存在body却没有指定Content-Length的情况 + return 0; + } - //header - while (readLine(buffer, bytes)) { - if (bytes.size() < 2) break; - index = 0; - offset = bytes.find(index, ':'); - if (offset <= 0) return -1; - String name = bytes.toString(index, offset, charset); - index = offset + 1; - //Upgrade: websocket 前面有空格,所以需要trim() - String value = bytes.toString(index, bytes.size() - index, charset).trim(); + private int loadHeaderBytes(final ByteBuffer buffer) { + ByteArray bytes = array; + int remain = buffer.remaining(); + byte b1, b2, b3, b4; + for (;;) { + if (remain-- < 4) { //bytes不存放\r\n\r\n这4个字节 + bytes.put(buffer); + buffer.clear(); + if (bytes.length() > 0) { + byte rn1 = 0, rn2 = 0, rn3 = 0; + byte b = bytes.getLastByte(); + if (b == '\r' || b == '\n') { + rn3 = b; + bytes.backCount(); + if (bytes.length() > 0) { + b = bytes.getLastByte(); + if (b == '\r' || b == '\n') { + rn2 = b; + bytes.backCount(); + if (bytes.length() > 0) { + b = bytes.getLastByte(); + if (b == '\r' || b == '\n') { + rn1 = b; + bytes.backCount(); + } + } + } + } + } + if (rn1 != 0) buffer.put(rn1); + if (rn2 != 0) buffer.put(rn2); + if (rn3 != 0) buffer.put(rn3); + } + return 1; + } + b1 = buffer.get(); + bytes.put(b1); + if (b1 == '\r') { + remain--; + b2 = buffer.get(); + bytes.put(b2); + if (b2 == '\n') { + remain--; + b3 = buffer.get(); + bytes.put(b3); + if (b3 == '\r') { + remain--; + b4 = buffer.get(); + bytes.put(b4); + if (b4 == '\n') { + this.headerBytes = Utility.append(this.headerBytes, bytes.content(), 0, bytes.length()); + this.headerLength = this.headerBytes.length; + this.headerHalfLen = this.headerLength; + bytes.clear(); + return 0; + } + } + } + } + } + } + +// @Override +// protected int readBody(ByteBuffer buffer, int length) { +// int len = buffer.remaining(); +// array.put(buffer, len); +// return len; +// } + //解析 GET /xxx HTTP/1.1 + private int readMethodLine(final ByteBuffer buffer) { + Charset charset = this.context.getCharset(); + int remain = buffer.remaining(); + int size; + ByteArray bytes = array; + //读method + if (this.method == null) { + for (;;) { + if (remain-- < 1) { + buffer.clear(); + return 1; + } + byte b = buffer.get(); + if (b == ' ') break; + bytes.put(b); + } + size = bytes.length(); + if (size == 3 && bytes.get(0) == 'G' && bytes.get(1) == 'E' && bytes.get(2) == 'T') { + this.method = KEY_GET; + this.getmethod = true; + } else if (size == 4 && bytes.get(0) == 'P' && bytes.get(1) == 'O' && bytes.get(2) == 'S' && bytes.get(3) == 'T') { + this.method = KEY_POST; + this.getmethod = false; + } else { + this.method = bytes.toString(charset); + this.getmethod = false; + } + bytes.clear(); + } + //读uri + if (this.requestURI == null) { + int qst = -1;//?的位置 + boolean decodeable = false; + for (;;) { + if (remain-- < 1) { + buffer.clear(); + return 1; + } + byte b = buffer.get(); + if (b == ' ') break; + if (b == '?' && qst < 0) { + qst = bytes.length(); + } else if (!decodeable && (b == '+' || b == '%')) { + decodeable = true; + } + bytes.put(b); + } + size = bytes.length(); + if (qst > 0) { + this.requestURI = decodeable ? bytes.toDecodeString(0, qst, charset) : bytes.toString(0, qst, charset); + this.queryBytes = bytes.getBytes(qst + 1, size - qst - 1); + try { + addParameter(bytes, qst + 1, size - qst - 1); + } catch (Exception e) { + this.context.getLogger().log(Level.WARNING, "HttpRequest.addParameter error: " + bytes.toString(), e); + } + } else { + this.requestURI = decodeable ? bytes.toDecodeString(charset) : bytes.toString(charset); + this.queryBytes = EMPTY_BYTES; + } + bytes.clear(); + } + //读protocol + for (;;) { + if (remain-- < 1) { + this.params.clear(); + buffer.clear(); + return 1; + } + byte b = buffer.get(); + if (b == '\r') { + if (remain-- < 1) { + this.params.clear(); + buffer.clear(); + buffer.put((byte) '\r'); + return 1; + } + if (buffer.get() != '\n') return -1; + break; + } + bytes.put(b); + } + size = bytes.length(); + if (size == 8 && bytes.get(0) == 'H' && bytes.get(5) == '1' && bytes.get(7) == '1') { + this.protocol = KEY_HTTP_1_1; + } else { + this.protocol = bytes.toString(charset); + } + bytes.clear(); + return 0; + } + + //解析Header Connection: keep-alive + private int readHeaderLines(final ByteBuffer buffer, ByteArray bytes) { + Charset charset = this.context.getCharset(); + int remain = buffer.remaining(); + for (;;) { + bytes.clear(); + if (remain-- < 2) { + if (remain == 1) { + byte one = buffer.get(); + buffer.clear(); + buffer.put(one); + return 1; + } + buffer.clear(); + return 1; + } + remain--; + byte b1 = buffer.get(); + byte b2 = buffer.get(); + if (b1 == '\r' && b2 == '\n') return 0; + bytes.put(b1, b2); + for (;;) { // name + if (remain-- < 1) { + buffer.clear(); + buffer.put(bytes.content(), 0, bytes.length()); + return 1; + } + byte b = buffer.get(); + if (b == ':') break; + bytes.put(b); + } + String name = parseHeaderName(bytes, charset); + bytes.clear(); + boolean first = true; + int space = 0; + for (;;) { // value + if (remain-- < 1) { + buffer.clear(); + buffer.put(name.getBytes()); + buffer.put((byte) ':'); + if (space == 1) { + buffer.put((byte) ' '); + } else if (space > 0) { + for (int i = 0; i < space; i++) buffer.put((byte) ' '); + } + buffer.put(bytes.content(), 0, bytes.length()); + return 1; + } + byte b = buffer.get(); + if (b == '\r') { + if (remain-- < 1) { + buffer.clear(); + buffer.put(name.getBytes()); + buffer.put((byte) ':'); + if (space == 1) { + buffer.put((byte) ' '); + } else if (space > 0) { + for (int i = 0; i < space; i++) buffer.put((byte) ' '); + } + buffer.put(bytes.content(), 0, bytes.length()); + buffer.put((byte) '\r'); + return 1; + } + if (buffer.get() != '\n') return -1; + break; + } + if (first) { + if (b <= ' ') { + space++; + continue; + } + first = false; + } + bytes.put(b); + } + String value; + int vallen = bytes.length(); switch (name) { case "Content-Type": case "content-type": + value = bytes.toString(charset); this.contentType = value; break; case "Content-Length": case "content-length": + value = bytes.toString(charset); this.contentLength = Long.decode(value); break; case "Host": case "host": + value = bytes.toString(charset); this.host = value; break; case "Cookie": case "cookie": + value = bytes.toString(charset); if (this.cookie == null || this.cookie.isEmpty()) { this.cookie = value; } else { @@ -247,75 +589,199 @@ public class HttpRequest extends Request { break; case "Connection": case "connection": - this.connection = value; - if (context.getAliveTimeoutSeconds() >= 0) { - this.setKeepAlive(!"close".equalsIgnoreCase(value)); + if (vallen > 0) { + if (bytes.get(0) == 'c' && vallen == 5 + && bytes.get(1) == 'l' && bytes.get(2) == 'o' + && bytes.get(3) == 's' && bytes.get(4) == 'e') { + value = "close"; + this.setKeepAlive(false); + } else if (bytes.get(0) == 'k' && vallen == 10 + && bytes.get(1) == 'e' && bytes.get(2) == 'e' + && bytes.get(3) == 'p' && bytes.get(4) == '-' + && bytes.get(5) == 'a' && bytes.get(6) == 'l' + && bytes.get(7) == 'i' && bytes.get(8) == 'v' + && bytes.get(9) == 'e') { + value = "keep-alive"; + //if (context.getAliveTimeoutSeconds() >= 0) { + this.setKeepAlive(true); + //} + } else { + value = bytes.toString(charset); + this.setKeepAlive(true); + } + } else { + value = ""; } + headers.put("Connection", value); + break; + case "Upgrade": + case "upgrade": + value = bytes.toString(charset); + this.maybews = "websocket".equalsIgnoreCase(value); + headers.put("Upgrade", value); break; case "user-agent": + value = bytes.toString(charset); headers.put("User-Agent", value); break; case Rest.REST_HEADER_RPC_NAME: + value = bytes.toString(charset); this.rpc = "true".equalsIgnoreCase(value); headers.put(name, value); break; case Rest.REST_HEADER_CURRUSERID_NAME: + value = bytes.toString(charset); + this.hashid = value.hashCode(); this.currentUserid = value; headers.put(name, value); break; case Rest.REST_HEADER_PARAM_FROM_BODY: + value = bytes.toString(charset); this.frombody = "true".equalsIgnoreCase(value); headers.put(name, value); break; case Rest.REST_HEADER_REQ_CONVERT_TYPE: + value = bytes.toString(charset); reqConvertType = ConvertType.valueOf(value); reqConvert = ConvertFactory.findConvert(reqConvertType); headers.put(name, value); break; case Rest.REST_HEADER_RESP_CONVERT_TYPE: + value = bytes.toString(charset); respConvertType = ConvertType.valueOf(value); respConvert = ConvertFactory.findConvert(respConvertType); headers.put(name, value); break; default: + value = bytes.toString(charset); headers.put(name, value); } } - if (this.contentType != null && this.contentType.contains("boundary=")) this.boundary = true; - if (this.boundary) this.keepAlive = false; //文件上传必须设置keepAlive为false,因为文件过大时用户不一定会skip掉多余的数据 + } - bytes.clear(); - if (this.contentLength > 0 && (this.contentType == null || !this.boundary)) { - if (this.contentLength > context.getMaxbody()) return -1; - bytes.write(buffer, Math.min((int) this.contentLength, buffer.remaining())); - int lr = (int) this.contentLength - bytes.size(); - return lr > 0 ? lr : 0; + private void parseHeader() { + if (headerParsed) return; + headerParsed = true; + if (headerBytes == null) return; + if (array.isEmpty()) { + readHeaderLines(ByteBuffer.wrap(headerBytes), array); + array.clear(); + } else { //array存有body数据 + readHeaderLines(ByteBuffer.wrap(headerBytes), new ByteArray()); } - if (buffer.hasRemaining() && (this.boundary || !this.keepAlive)) bytes.write(buffer, buffer.remaining()); //文件上传、HTTP1.0或Connection:close - //暂不考虑是keep-alive且存在body却没有指定Content-Length的情况 - return 0; + } + + static String parseHeaderName(ByteArray bytes, Charset charset) { + final int size = bytes.length(); + final byte[] bs = bytes.content(); + final byte first = bs[0]; + if (first == 'H' && size == 4) { //Host + if (bs[1] == 'o' && bs[2] == 's' && bs[3] == 't') return KEY_HOST; + } else if (first == 'A' && size == 6) { //Accept + if (bs[1] == 'c' && bs[2] == 'c' && bs[3] == 'e' + && bs[4] == 'p' && bs[5] == 't') return KEY_ACCEPT; + } else if (first == 'C') { + if (size == 10) { //Connection + if (bs[1] == 'o' && bs[2] == 'n' && bs[3] == 'n' + && bs[4] == 'e' && bs[5] == 'c' && bs[6] == 't' + && bs[7] == 'i' && bs[8] == 'o' && bs[9] == 'n') return KEY_CONNECTION; + } else if (size == 12) { //Content-Type + if (bs[1] == 'o' && bs[2] == 'n' && bs[3] == 't' + && bs[4] == 'e' && bs[5] == 'n' && bs[6] == 't' + && bs[7] == '-' && bs[8] == 'T' && bs[9] == 'y' + && bs[10] == 'p' && bs[11] == 'e') return KEY_CONTENT_TYPE; + } else if (size == 6) { //Cookie + if (bs[1] == 'o' && bs[2] == 'o' && bs[3] == 'k' + && bs[4] == 'i' && bs[5] == 'e') return KEY_COOKIE; + } + } + return bytes.toString(charset); } @Override - protected int readBody(ByteBuffer buffer) { - int len = buffer.remaining(); - array.write(buffer, len); - return len; + protected HttpRequest copyHeader() { + if (!context.lazyHeaders) return null; + HttpRequest req = new HttpRequest(context, this.array); + req.headerLength = this.headerLength; + req.headerBytes = this.headerBytes; + req.headerParsed = this.headerParsed; + req.contentType = this.contentType; + req.contentLength = this.contentLength; + req.host = this.host; + req.cookie = this.cookie; + req.cookies = this.cookies; + req.keepAlive = this.keepAlive; + req.maybews = this.maybews; + req.rpc = this.rpc; + req.hashid = this.hashid; + req.currentUserid = this.currentUserid; + req.currentUser = this.currentUser; + req.frombody = this.frombody; + req.reqConvertType = this.reqConvertType; + req.reqConvert = this.reqConvert; + req.respConvert = this.respConvert; + req.respConvertType = this.respConvertType; + req.headers.putAll(this.headers); + return req; } @Override protected void prepare() { + this.keepAlive = true; //默认HTTP/1.1 + } + + @Override + protected void recycle() { + //header + this.headerLength = 0; + this.headerHalfLen = 0; + this.headerBytes = null; + this.headerParsed = false; + this.contentType = null; + this.contentLength = -1; + this.host = null; + this.cookie = null; + this.cookies = null; + this.maybews = false; + this.rpc = false; + this.readState = READ_STATE_ROUTE; + this.currentUserid = CURRUSERID_NIL; + this.currentUser = null; + this.frombody = false; + this.reqConvertType = null; + this.reqConvert = null; + this.respConvert = jsonConvert; + this.respConvertType = null; + this.headers.clear(); + //其他 + this.newsessionid = null; + this.method = null; + this.getmethod = false; + this.protocol = null; + this.requestURI = null; + this.queryBytes = null; + this.boundary = false; + this.bodyParsed = false; + this.moduleid = 0; + this.actionid = 0; + this.annotations = null; + this.remoteAddr = null; + this.params.clear(); + this.array.clear(); + //内部 + this.actionEntry = null; + super.recycle(); } protected void skipBodyParse() { - this.bodyparsed = true; + this.bodyParsed = true; } private void parseBody() { - if (this.boundary || bodyparsed) return; - bodyparsed = true; + if (this.boundary || bodyParsed) return; + bodyParsed = true; if (this.contentType != null && this.contentType.toLowerCase().contains("x-www-form-urlencoded")) { - addParameter(array, 0, array.size()); + addParameter(array, 0, array.length()); } } @@ -339,22 +805,6 @@ public class HttpRequest extends Request { } } - private boolean readLine(ByteBuffer buffer, ByteArray bytes) { - byte lasted = '\r'; - bytes.clear(); - for (;;) { - if (!buffer.hasRemaining()) { - if (lasted != '\r') bytes.write(lasted); - return false; - } - byte b = buffer.get(); - if (b == -1 || (lasted == '\r' && b == '\n')) break; - if (lasted != '\r') bytes.write(lasted); - lasted = b; - } - return true; - } - @Override protected T setProperty(String name, T value) { return super.setProperty(name, value); @@ -536,6 +986,7 @@ public class HttpRequest extends Request { */ public String getRemoteAddr() { if (this.remoteAddr != null) return this.remoteAddr; + parseHeader(); if (remoteAddrHeader != null) { String val = getHeader(remoteAddrHeader); if (val != null) { @@ -587,7 +1038,7 @@ public class HttpRequest extends Request { Convert convert = this.reqConvert; if (convert == null) convert = context.getJsonConvert(); if (type == byte[].class) return (T) array.getBytes(); - return (T) convert.convertFrom(type, array.directBytes()); + return (T) convert.convertFrom(type, array.content()); } /** @@ -602,7 +1053,7 @@ public class HttpRequest extends Request { public T getBodyJson(Convert convert, java.lang.reflect.Type type) { if (array.isEmpty()) return null; if (type == byte[].class) return (T) array.getBytes(); - return (T) convert.convertFrom(type, array.directBytes()); + return (T) convert.convertFrom(type, array.content()); } /** @@ -611,7 +1062,7 @@ public class HttpRequest extends Request { * @return 内容 */ public byte[] getBody() { - return array.size() == 0 ? null : array.getBytes(); + return array.length() == 0 ? null : array.getBytes(); } /** @@ -631,9 +1082,10 @@ public class HttpRequest extends Request { + (this.frombody ? (", \r\n frombody: " + this.frombody) : "") + (this.reqConvertType != null ? (", \r\n reqConvertType: " + this.reqConvertType) : "") + (this.respConvertType != null ? (", \r\n respConvertType: " + this.respConvertType) : "") - + ", \r\n remoteAddr: " + this.getRemoteAddr() + ", \r\n cookies: " + this.cookie + ", \r\n contentType: " + this.contentType - + ", \r\n connection: " + this.connection + ", \r\n protocol: " + this.protocol + ", \r\n host: " + this.host - + ", \r\n contentLength: " + this.contentLength + ", \r\n bodyLength: " + this.array.size() + + ", \r\n currentUserid: " + (this.currentUserid == CURRUSERID_NIL ? null : this.currentUserid) + ", \r\n remoteAddr: " + this.getRemoteAddr() + + ", \r\n cookies: " + this.cookie + ", \r\n contentType: " + this.contentType + + ", \r\n protocol: " + this.protocol + ", \r\n host: " + this.host + + ", \r\n contentLength: " + this.contentLength + ", \r\n bodyLength: " + this.array.length() + (this.boundary || this.array.isEmpty() ? "" : (", \r\n bodyContent: " + (this.respConvertType == null || this.respConvertType == ConvertType.JSON ? this.getBodyUTF8() : Arrays.toString(getBody())))) + ", \r\n params: " + toMapString(this.params, 4) + ", \r\n header: " + toMapString(this.headers, 4) + "\r\n}"; //this.headers.toString(4) } @@ -659,10 +1111,10 @@ public class HttpRequest extends Request { @ConvertDisabled public final MultiContext getMultiContext() { return new MultiContext(context.getCharset(), this.getContentType(), this.params, - new BufferedInputStream(Channels.newInputStream(this.channel.readableByteChannel()), Math.max(array.size(), 8192)) { + new BufferedInputStream(Channels.newInputStream(this.channel.readableByteChannel()), Math.max(array.length(), 8192)) { { array.copyTo(this.buf); - this.count = array.size(); + this.count = array.length(); } }, null); } @@ -679,39 +1131,6 @@ public class HttpRequest extends Request { return getMultiContext().parts(); } - @Override - protected void recycle() { - this.cookie = null; - this.cookies = null; - this.newsessionid = null; - this.method = null; - this.protocol = null; - this.requestURI = null; - this.queryBytes = null; - this.contentType = null; - this.host = null; - this.connection = null; - this.contentLength = -1; - this.boundary = false; - this.bodyparsed = false; - this.frombody = false; - this.moduleid = 0; - this.actionid = 0; - this.annotations = null; - this.currentUserid = CURRUSERID_NIL; - this.currentUser = null; - this.remoteAddr = null; - - this.attachment = null; - this.reqConvert = null; - this.respConvert = jsonConvert; - - this.headers.clear(); - this.params.clear(); - this.array.clear(); - super.recycle(); - } - /** * 获取sessionid * @@ -764,6 +1183,7 @@ public class HttpRequest extends Request { * @return cookie对象数组 */ public HttpCookie[] getCookies() { + parseHeader(); if (this.cookies == null) this.cookies = parseCookies(this.cookie); return this.cookies.length == 0 ? null : this.cookies; } @@ -848,15 +1268,6 @@ public class HttpRequest extends Request { return contentLength; } - /** - * 获取Connection的Header值 - * - * @return Connection - */ - public String getConnection() { - return connection; - } - /** * 获取Host的Header值 * @@ -1240,6 +1651,7 @@ public class HttpRequest extends Request { * @return AnyValue */ public Map getHeaders() { + parseHeader(); return headers; } @@ -1252,6 +1664,7 @@ public class HttpRequest extends Request { */ @ConvertDisabled public Map getHeadersToMap(Map map) { + parseHeader(); if (map == null) map = new LinkedHashMap<>(); final Map map0 = map; headers.forEach((k, v) -> map0.put(k, v)); @@ -1265,6 +1678,7 @@ public class HttpRequest extends Request { */ @ConvertDisabled public String[] getHeaderNames() { + parseHeader(); Set names = headers.keySet(); return names.toArray(new String[names.size()]); } @@ -1277,6 +1691,7 @@ public class HttpRequest extends Request { * @return header值 */ public String getHeader(String name) { + parseHeader(); return headers.get(name); } @@ -1289,6 +1704,7 @@ public class HttpRequest extends Request { * @return header值 */ public String getHeader(String name, String defaultValue) { + parseHeader(); return headers.getOrDefault(name, defaultValue); } @@ -1331,6 +1747,7 @@ public class HttpRequest extends Request { */ public boolean getBooleanHeader(String name, boolean defaultValue) { //return headers.getBoolValue(name, defaultValue); + parseHeader(); String value = headers.get(name); return value == null || value.length() == 0 ? defaultValue : Boolean.parseBoolean(value); } @@ -1344,7 +1761,8 @@ public class HttpRequest extends Request { * @return header值 */ public short getShortHeader(String name, short defaultValue) { - //return headers.getShortValue(name, defaultValue); + //return headers.getShortValue(name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1365,6 +1783,7 @@ public class HttpRequest extends Request { */ public short getShortHeader(int radix, String name, short defaultValue) { //return headers.getShortValue(name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1384,6 +1803,7 @@ public class HttpRequest extends Request { */ public short getShortHeader(String name, int defaultValue) { //return headers.getShortValue(name, (short) defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return (short) defaultValue; try { @@ -1404,6 +1824,7 @@ public class HttpRequest extends Request { */ public short getShortHeader(int radix, String name, int defaultValue) { //return headers.getShortValue(radix, name, (short) defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return (short) defaultValue; try { @@ -1423,6 +1844,7 @@ public class HttpRequest extends Request { */ public int getIntHeader(String name, int defaultValue) { //return headers.getIntValue(name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1443,6 +1865,7 @@ public class HttpRequest extends Request { */ public int getIntHeader(int radix, String name, int defaultValue) { //return headers.getIntValue(radix, name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1462,6 +1885,7 @@ public class HttpRequest extends Request { */ public long getLongHeader(String name, long defaultValue) { //return headers.getLongValue(name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1482,6 +1906,7 @@ public class HttpRequest extends Request { */ public long getLongHeader(int radix, String name, long defaultValue) { //return headers.getLongValue(radix, name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1501,6 +1926,7 @@ public class HttpRequest extends Request { */ public float getFloatHeader(String name, float defaultValue) { //return headers.getFloatValue(name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1520,6 +1946,7 @@ public class HttpRequest extends Request { */ public double getDoubleHeader(String name, double defaultValue) { //return headers.getDoubleValue(name, defaultValue); + parseHeader(); String value = headers.get(name); if (value == null || value.length() == 0) return defaultValue; try { @@ -1607,7 +2034,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return null; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (String) convert.convertFrom(String.class, array.directBytes()); + return (String) convert.convertFrom(String.class, array.content()); } parseBody(); return params.get(name); @@ -1626,7 +2053,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (String) convert.convertFrom(String.class, array.directBytes()); + return (String) convert.convertFrom(String.class, array.content()); } parseBody(); return params.getOrDefault(name, defaultValue); @@ -1647,7 +2074,7 @@ public class HttpRequest extends Request { Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; if (type == byte[].class) return (T) array.getBytes(); - return (T) convert.convertFrom(type, array.directBytes()); + return (T) convert.convertFrom(type, array.content()); } String v = getParameter(name); return v == null || v.isEmpty() ? null : jsonConvert.convertFrom(type, v); @@ -1681,7 +2108,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (boolean) convert.convertFrom(boolean.class, array.directBytes()); + return (boolean) convert.convertFrom(boolean.class, array.content()); } parseBody(); String value = params.get(name); @@ -1701,7 +2128,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (short) convert.convertFrom(short.class, array.directBytes()); + return (short) convert.convertFrom(short.class, array.content()); } parseBody(); String value = params.get(name); @@ -1727,7 +2154,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return (short) defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (short) convert.convertFrom(short.class, array.directBytes()); + return (short) convert.convertFrom(short.class, array.content()); } parseBody(); String value = params.get(name); @@ -1752,7 +2179,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return (short) defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (short) convert.convertFrom(short.class, array.directBytes()); + return (short) convert.convertFrom(short.class, array.content()); } parseBody(); String value = params.get(name); @@ -1777,7 +2204,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (int) convert.convertFrom(int.class, array.directBytes()); + return (int) convert.convertFrom(int.class, array.content()); } parseBody(); String value = params.get(name); @@ -1803,7 +2230,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (int) convert.convertFrom(int.class, array.directBytes()); + return (int) convert.convertFrom(int.class, array.content()); } parseBody(); String value = params.get(name); @@ -1828,7 +2255,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (long) convert.convertFrom(long.class, array.directBytes()); + return (long) convert.convertFrom(long.class, array.content()); } parseBody(); String value = params.get(name); @@ -1854,7 +2281,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (long) convert.convertFrom(long.class, array.directBytes()); + return (long) convert.convertFrom(long.class, array.content()); } parseBody(); String value = params.get(name); @@ -1879,7 +2306,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (float) convert.convertFrom(float.class, array.directBytes()); + return (float) convert.convertFrom(float.class, array.content()); } parseBody(); String value = params.get(name); @@ -1904,7 +2331,7 @@ public class HttpRequest extends Request { if (array.isEmpty()) return defaultValue; Convert convert = this.reqConvert; if (convert == null) convert = jsonConvert; - return (double) convert.convertFrom(double.class, array.directBytes()); + return (double) convert.convertFrom(double.class, array.content()); } parseBody(); String value = params.get(name); diff --git a/src/org/redkale/net/http/HttpResponse.java b/src/org/redkale/net/http/HttpResponse.java index 8a5323aa8..a2e86291a 100644 --- a/src/org/redkale/net/http/HttpResponse.java +++ b/src/org/redkale/net/http/HttpResponse.java @@ -23,6 +23,7 @@ import org.redkale.net.*; import org.redkale.util.AnyValue.DefaultAnyValue; import org.redkale.util.AnyValue.Entry; import org.redkale.util.*; +import static org.redkale.util.Utility.append; /** * Http响应包 与javax.servlet.http.HttpServletResponse 基本类似。
@@ -35,9 +36,9 @@ import org.redkale.util.*; */ public class HttpResponse extends Response { - private static final ByteBuffer buffer304 = ByteBuffer.wrap("HTTP/1.1 304 Not Modified\r\nContent-Length:0\r\n\r\n".getBytes()).asReadOnlyBuffer(); + protected static final byte[] bytes304 = "HTTP/1.1 304 Not Modified\r\nContent-Length:0\r\n\r\n".getBytes(); - private static final ByteBuffer buffer404 = ByteBuffer.wrap("HTTP/1.1 404 Not Found\r\nContent-Length:0\r\n\r\n".getBytes()).asReadOnlyBuffer(); + protected static final byte[] bytes404 = "HTTP/1.1 404 Not Found\r\nContent-Length:0\r\n\r\n".getBytes(); protected static final byte[] status200Bytes = "HTTP/1.1 200 OK\r\n".getBytes(); @@ -45,20 +46,25 @@ public class HttpResponse extends Response { protected static final byte[] serverNameBytes = ("Server: " + System.getProperty("http.response.header.server", "redkale" + "/" + Redkale.getDotedVersion()) + "\r\n").getBytes(); - protected static final byte[] connectCloseBytes = "Connection: close\r\n".getBytes(); + protected static final byte[] connectCloseBytes = "none".equalsIgnoreCase(System.getProperty("http.response.header.connection")) ? new byte[0] : "Connection: close\r\n".getBytes(); - protected static final byte[] connectAliveBytes = "Connection: keep-alive\r\n".getBytes(); + protected static final byte[] connectAliveBytes = "none".equalsIgnoreCase(System.getProperty("http.response.header.connection")) ? new byte[0] : "Connection: keep-alive\r\n".getBytes(); - private static final byte[] fillContentLengthBytes = ("Content-Length: \r\n").getBytes(); + private static final int cacheMaxContentLength = 999; + + private static final byte[] status200_server_live_Bytes = append(append(status200Bytes, serverNameBytes), connectAliveBytes); + + private static final byte[] status200_server_close_Bytes = append(append(status200Bytes, serverNameBytes), connectCloseBytes); private static final ZoneId ZONE_GMT = ZoneId.of("GMT"); - private static final Set options = new HashSet<>(); + private static final OpenOption[] options = new OpenOption[]{StandardOpenOption.READ}; private static final Map httpCodes = new HashMap<>(); + private static final Map contentLengthMap = new HashMap<>(); + static { - options.add(StandardOpenOption.READ); httpCodes.put(100, "Continue"); httpCodes.put(101, "Switching Protocols"); @@ -104,6 +110,10 @@ public class HttpResponse extends Response { httpCodes.put(503, "Service Unavailable"); httpCodes.put(504, "Gateway Timeout"); httpCodes.put(505, "HTTP Version Not Supported"); + + for (int i = 0; i <= cacheMaxContentLength; i++) { + contentLengthMap.put(i, ("Content-Length: " + i + "\r\n").getBytes()); + } } private int status = 200; @@ -114,19 +124,22 @@ public class HttpResponse extends Response { private HttpCookie[] cookies; + private boolean respHeadContainsConnection; + private int headWritedSize = -1; //0表示跳过header,正数表示header的字节长度。 - private ByteBuffer headBuffer; - - private int headLenPos = -1; - - private BiFunction bufferHandler; + private BiConsumer cacheHandler; private BiFunction retResultHandler; - private Supplier bodyBufferSupplier; - //------------------------------------------------ + private Map lastContentLengthMap;//lazyHeaders=true下缓存, recycle不会清空 + private int lastContentLength; //lazyHeaders=true下缓存, recycle不会清空 + + private byte[] lastContentLengthBytes; //lazyHeaders=true下缓存, recycle不会清空 + + //private Supplier bodyBufferSupplier; + //------------------------------------------------ private final String plainContentType; private final byte[] plainContentTypeBytes; @@ -153,12 +166,39 @@ public class HttpResponse extends Response { private final HttpRender onlyoneHttpRender; - public HttpResponse(HttpContext context, HttpRequest request, ObjectPool responsePool, HttpResponseConfig config) { - super(context, request, responsePool); - this.plainContentType = config == null || config.plainContentType == null || config.plainContentType.isEmpty() ? "text/plain; charset=utf-8" : config.plainContentType; - this.jsonContentType = config == null || config.jsonContentType == null || config.jsonContentType.isEmpty() ? "application/json; charset=utf-8" : config.jsonContentType; - this.plainContentTypeBytes = ("Content-Type: " + this.plainContentType + "\r\n").getBytes(); - this.jsonContentTypeBytes = ("Content-Type: " + this.jsonContentType + "\r\n").getBytes(); + private final ByteArray headerArray = new ByteArray(); + + private final Map plainLiveContentLengthMap; + + private final Map jsonLiveContentLengthMap; + + private final Map plainCloseContentLengthMap; + + private final Map jsonCloseContentLengthMap; + + protected final CompletionHandler pipelineWriteHandler = new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + finish(); + } + + @Override + public void failed(Throwable exc, Void attachment) { + finish(true); + } + }; + + @SuppressWarnings("Convert2Lambda") + protected final ConvertBytesHandler convertHandler = new ConvertBytesHandler() { + @Override + public
void completed(byte[] bs, int offset, int length, Consumer callback, A attachment) { + finish(bs, offset, length, callback, attachment); + } + }; + + public HttpResponse(HttpContext context, HttpRequest request, HttpResponseConfig config) { + super(context, request); this.defaultAddHeaders = config == null ? null : config.defaultAddHeaders; this.defaultSetHeaders = config == null ? null : config.defaultSetHeaders; this.defaultCookie = config == null ? null : config.defaultCookie; @@ -167,12 +207,16 @@ public class HttpResponse extends Response { this.renders = config == null ? null : config.renders; this.hasRender = renders != null && !renders.isEmpty(); this.onlyoneHttpRender = renders != null && renders.size() == 1 ? renders.get(0) : null; + + this.plainContentType = config == null ? "text/plain; charset=utf-8" : config.plainContentType; + this.jsonContentType = config == null ? "application/json; charset=utf-8" : config.jsonContentType; + this.plainContentTypeBytes = config == null ? ("Content-Type: " + this.plainContentType + "\r\n").getBytes() : config.plainContentTypeBytes; + this.jsonContentTypeBytes = config == null ? ("Content-Type: " + this.jsonContentType + "\r\n").getBytes() : config.jsonContentTypeBytes; + this.plainLiveContentLengthMap = config == null ? new HashMap<>() : config.plainLiveContentLengthMap; + this.plainCloseContentLengthMap = config == null ? new HashMap<>() : config.plainCloseContentLengthMap; + this.jsonLiveContentLengthMap = config == null ? new HashMap<>() : config.jsonLiveContentLengthMap; + this.jsonCloseContentLengthMap = config == null ? new HashMap<>() : config.jsonCloseContentLengthMap; this.contentType = this.plainContentType; - this.bodyBufferSupplier = () -> { - if (headWritedSize >= 0 || bufferHandler != null) return channel.pollWriteBuffer(); //bufferHandler 需要cached的请求不能带上header - if (contentLength < 0) contentLength = -2; - return createHeader(); - }; } @Override @@ -196,18 +240,18 @@ public class HttpResponse extends Response { this.contentType = null; this.cookies = null; this.headWritedSize = -1; - this.headBuffer = null; - this.headLenPos = -1; + //this.headBuffer = null; this.header.clear(); - this.bufferHandler = null; + this.headerArray.clear(); + this.cacheHandler = null; this.retResultHandler = null; + this.respHeadContainsConnection = false; return super.recycle(); } - protected Supplier getBodyBufferSupplier() { - return bodyBufferSupplier; - } - +// protected Supplier getBodyBufferSupplier() { +// return bodyBufferSupplier; +// } @Override protected void init(AsyncConnection channel) { super.init(channel); @@ -243,11 +287,6 @@ public class HttpResponse extends Response { return this.autoOptions; } - @Override - protected void offerBuffer(ByteBuffer... buffers) { - super.offerBuffer(buffers); - } - /** * 增加Cookie值 * @@ -310,22 +349,42 @@ public class HttpResponse extends Response { public void finishJson(final Object obj) { this.contentType = this.jsonContentType; if (this.recycleListener != null) this.output = obj; - finish(request.getRespConvert().convertTo(getBodyBufferSupplier(), obj)); + request.getRespConvert().convertToBytes(obj, convertHandler); } /** - * 将对象数组用Map的形式以JSON格式输出
- * 例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3} + * 指定json内容长度将对象以JSON格式输出, 临时功能 * - * @param objs 输出对象 + * @param length json内容长度 + * @param obj 输出对象 */ - public void finishMapJson(final Object... objs) { + @Deprecated + public void finishJson(final int length, final Object obj) { this.contentType = this.jsonContentType; - if (this.recycleListener != null) this.output = objs; - if (respConvertByBuffer) { - finish(request.getRespConvert().convertMapTo(getBodyBufferSupplier(), objs)); + this.contentLength = length; + if (this.recycleListener != null) this.output = obj; + createHeader(); + ByteArray data = headerArray; + request.getRespConvert().convertToBytes(data, obj); + + int pipelineIndex = request.getPipelineIndex(); + if (pipelineIndex > 0) { + boolean over = this.channel.writePipelineData(pipelineIndex, request.getPipelineCount(), data); + if (over) { + request.setPipelineOver(true); + this.channel.flushPipelineData(this.pipelineWriteHandler); + } else { + removeChannel(); + this.responseConsumer.accept(this); + } } else { - finish(request.getRespConvert().convertMapToBytes(objs)); + if (this.channel.hasPipelineData()) { + this.channel.writePipelineData(pipelineIndex, request.getPipelineCount(), data); + this.channel.flushPipelineData(this.pipelineWriteHandler); + } else { + //不能用finish(boolean kill, final ByteTuple array) 否则会调this.finish + super.finish(false, data.content(), 0, data.length()); + } } } @@ -338,28 +397,7 @@ public class HttpResponse extends Response { public void finishJson(final JsonConvert convert, final Object obj) { this.contentType = this.jsonContentType; if (this.recycleListener != null) this.output = obj; - if (respConvertByBuffer) { - finish(convert.convertTo(getBodyBufferSupplier(), obj)); - } else { - finish(convert.convertToBytes(obj)); - } - } - - /** - * 将对象数组用Map的形式以JSON格式输出
- * 例如: 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 = this.jsonContentType; - if (this.recycleListener != null) this.output = objs; - if (respConvertByBuffer) { - finish(convert.convertMapTo(getBodyBufferSupplier(), objs)); - } else { - finish(convert.convertMapToBytes(objs)); - } + convert.convertToBytes(obj, convertHandler); } /** @@ -370,12 +408,8 @@ public class HttpResponse extends Response { */ public void finishJson(final Type type, final Object obj) { this.contentType = this.jsonContentType; - this.output = obj; - if (respConvertByBuffer) { - finish(request.getRespConvert().convertTo(getBodyBufferSupplier(), type, obj)); - } else { - finish(request.getRespConvert().convertToBytes(type, obj)); - } + if (this.recycleListener != null) this.output = obj; + request.getRespConvert().convertToBytes(type, obj, convertHandler); } /** @@ -388,11 +422,7 @@ public class HttpResponse extends Response { public void finishJson(final JsonConvert convert, final Type type, final Object obj) { this.contentType = this.jsonContentType; if (this.recycleListener != null) this.output = obj; - if (respConvertByBuffer) { - finish(convert.convertTo(getBodyBufferSupplier(), type, obj)); - } else { - finish(convert.convertToBytes(type, obj)); - } + convert.convertToBytes(type, obj, convertHandler); } /** @@ -400,14 +430,11 @@ public class HttpResponse extends Response { * * @param objs 输出对象 */ - public void finishJson(final Object... objs) { + @Deprecated //@since 2.3.0 + void finishJson(final Object... objs) { this.contentType = this.jsonContentType; if (this.recycleListener != null) this.output = objs; - if (respConvertByBuffer) { - finish(request.getRespConvert().convertTo(getBodyBufferSupplier(), objs)); - } else { - finish(request.getRespConvert().convertToBytes(objs)); - } + request.getRespConvert().convertToBytes(objs, convertHandler); } /** @@ -427,11 +454,7 @@ public class HttpResponse extends Response { } Convert convert = ret == null ? null : ret.convert(); if (convert == null) convert = request.getRespConvert(); - if (respConvertByBuffer) { - finish(convert.convertTo(getBodyBufferSupplier(), ret)); - } else { - finish(convert.convertToBytes(ret)); - } + convert.convertToBytes(ret, convertHandler); } /** @@ -450,11 +473,7 @@ public class HttpResponse extends Response { this.header.addValue("retcode", String.valueOf(ret.getRetcode())); this.header.addValue("retinfo", ret.getRetinfo()); } - if (respConvertByBuffer) { - finish(convert.convertTo(getBodyBufferSupplier(), ret)); - } else { - finish(convert.convertToBytes(ret)); - } + convert.convertToBytes(ret, convertHandler); } /** @@ -539,6 +558,7 @@ public class HttpResponse extends Response { */ @SuppressWarnings("unchecked") public void finish(final Convert convert, final Type type, Object obj) { + //以下if条件会被Rest类第1900行左右的地方用到 if (obj == null) { finish("null"); } else if (obj instanceof CompletableFuture) { @@ -554,10 +574,6 @@ public class HttpResponse extends Response { finish((String) obj.toString()); } else if (obj instanceof byte[]) { finish((byte[]) obj); - } else if (obj instanceof ByteBuffer) { - finish((ByteBuffer) obj); - } else if (obj instanceof ByteBuffer[]) { - finish((ByteBuffer[]) obj); } else if (obj instanceof File) { try { finish((File) obj); @@ -602,18 +618,11 @@ public class HttpResponse extends Response { this.header.addValue("retcode", String.valueOf(ret.getRetcode())).addValue("retinfo", ret.getRetinfo()); } } - - if (this.channel == null) { //虚拟的HttpResponse - finish(type == null ? convert.convertToBytes(obj) : convert.convertToBytes(type, obj)); - return; - } - if (respConvertByBuffer) { - ByteBuffer[] buffers = type == null ? convert.convertTo(getBodyBufferSupplier(), obj) - : convert.convertTo(getBodyBufferSupplier(), type, obj); - finish(buffers); + //this.channel == null为虚拟的HttpResponse + if (type == null) { + convert.convertToBytes(obj, convertHandler); } else { - byte[] bs = type == null ? convert.convertToBytes(obj) : convert.convertToBytes(type, obj); - finish(bs); + convert.convertToBytes(type, obj, convertHandler); } } } @@ -624,40 +633,7 @@ public class HttpResponse extends Response { * @param obj 输出内容 */ public void finish(String obj) { - 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); - return; - } - if (context.getCharset() == null) { - if (bufferHandler != null) { - bufferHandler.apply(this, new ByteBuffer[]{ByteBuffer.wrap(Utility.encodeUTF8(obj))}); - } - final char[] chars = Utility.charArray(obj); - this.contentLength = Utility.encodeUTF8Length(chars); - final ByteBuffer headbuf = createHeader(); - ByteBuffer buf2 = Utility.encodeUTF8(headbuf, (int) this.contentLength, chars); - headbuf.flip(); - if (buf2 == null) { - super.finish(headbuf); - } else { - super.finish(headbuf, buf2); - } - } else { - ByteBuffer buffer = context.getCharset().encode(obj); - if (bufferHandler != null) { - ByteBuffer[] bufs = bufferHandler.apply(this, new ByteBuffer[]{buffer}); - if (bufs != null) buffer = bufs[0]; - } - this.contentLength = buffer.remaining(); - final ByteBuffer headbuf = createHeader(); - headbuf.flip(); - super.finish(headbuf, buffer); - } + finish(200, obj); } /** @@ -670,31 +646,17 @@ public class HttpResponse extends Response { if (isClosed()) return; this.status = status; if (status != 200) super.refuseAlive(); - finish(message); + final byte[] val = message == null ? new byte[0] : (context.getCharset() == null ? Utility.encodeUTF8(message) : message.getBytes(context.getCharset())); + finish(false, null, val, 0, val.length, null, null); } - /** - * 以304状态码输出 - */ - public void finish304() { - super.finish(buffer304.duplicate()); - } - - /** - * 以404状态码输出 - */ - public void finish404() { - super.finish(buffer404.duplicate()); - } - - /** - * 将指定byte[]按响应结果输出 - * - * @param bs 输出内容 - */ @Override - public void finish(final byte[] bs) { - this.finish(this.contentType, bs); + public void finish(boolean kill, final byte[] bs, int offset, int length) { + finish(false, null, bs, offset, length, null, null); + } + + public
void finish(final byte[] bs, int offset, int length, Consumer callback, A attachment) { + finish(false, null, bs, offset, length, callback, attachment); } /** @@ -704,173 +666,244 @@ public class HttpResponse extends Response { * @param bs 输出内容 */ public void finish(final String contentType, final byte[] bs) { + finish(false, contentType, bs, 0, bs == null ? 0 : bs.length, null, null); + } + + /** + * 将指定byte[]按响应结果输出 + * + * @param kill kill + * @param contentType ContentType + * @param bs 输出内容 + * @param offset 偏移量 + * @param length 长度 + */ + protected void finish(boolean kill, final String contentType, final byte[] bs, int offset, int length) { + finish(kill, contentType, bs, offset, length, null, null); + } + + /** + * 将指定byte[]按响应结果输出 + * + * @param kill kill + * @param contentType ContentType + * @param bs 输出内容 + * @param offset 偏移量 + * @param length 长度 + * @param callback Consumer + * @param attachment ConvertWriter + * @param A + */ + protected void finish(boolean kill, final String contentType, final byte[] bs, int offset, int length, Consumer callback, A attachment) { if (isClosed()) return; //避免重复关闭 - final byte[] content = bs == null ? new byte[0] : bs; + ByteArray data = headerArray; if (this.headWritedSize < 0) { - this.contentType = contentType; - this.contentLength = content.length; - ByteBuffer headbuf = createHeader(); - if (headbuf.remaining() >= content.length) { - headbuf.put(content); - headbuf.flip(); - super.finish(false, headbuf); - } else { - headbuf.flip(); - super.finish(false, new ByteBuffer[]{headbuf, ByteBuffer.wrap(content)}); - } - } else { - if (this.context.getBufferCapacity() >= content.length) { - ByteBuffer buffer = getBodyBufferSupplier().get(); - buffer.put(content); - buffer.flip(); - this.finish(false, buffer); - } else { - this.finish(false, ByteBuffer.wrap(content)); - } + if (contentType != null) this.contentType = contentType; + this.contentLength = length; + createHeader(); } + data.put(bs, offset, length); + if (callback != null) callback.accept(attachment); + if (cacheHandler != null) cacheHandler.accept(this, data.getBytes()); - } - - /** - * 将指定ByteBuffer按响应结果输出 - * - * @param buffer 输出内容 - */ - @Override - public void finish(ByteBuffer buffer) { - finish(false, buffer); - } - - /** - * 将指定ByteBuffer按响应结果输出 - * - * @param kill 输出后是否强制关闭连接 - * @param buffer 输出内容 - */ - @Override - public void finish(boolean kill, ByteBuffer buffer) { - if (isClosed()) return; //避免重复关闭 - if (this.headWritedSize < 0) { - this.contentLength = buffer == null ? 0 : buffer.remaining(); - ByteBuffer headbuf = createHeader(); - headbuf.flip(); - if (buffer == null) { - super.finish(kill, headbuf); + int pipelineIndex = request.getPipelineIndex(); + if (pipelineIndex > 0) { + boolean over = this.channel.writePipelineData(pipelineIndex, request.getPipelineCount(), data); + if (over) { + request.setPipelineOver(true); + this.channel.flushPipelineData(this.pipelineWriteHandler); } else { - super.finish(kill, new ByteBuffer[]{headbuf, buffer}); + removeChannel(); + this.responseConsumer.accept(this); } } else { - super.finish(kill, buffer); + if (this.channel.hasPipelineData()) { + this.channel.writePipelineData(pipelineIndex, request.getPipelineCount(), data); + this.channel.flushPipelineData(this.pipelineWriteHandler); + } else { + //不能用finish(boolean kill, final ByteTuple array) 否则会调this.finish + super.finish(false, data.content(), 0, data.length()); + } } } /** - * 将指定ByteBuffer数组按响应结果输出 - * - * @param buffers 输出内容 + * 以304状态码输出 */ - @Override - public void finish(ByteBuffer... buffers) { - finish(false, buffers); + public void finish304() { + skipHeader(); + super.finish(false, bytes304); } /** - * 将指定ByteBuffer数组按响应结果输出 - * - * @param kill 输出后是否强制关闭连接 - * @param buffers 输出内容 + * 以404状态码输出 */ - @Override - public void finish(boolean kill, ByteBuffer... buffers) { - if (isClosed()) return; //避免重复关闭 - if (bufferHandler != null) { - ByteBuffer[] bufs = bufferHandler.apply(this, buffers); - if (bufs != null) buffers = bufs; - } - if (kill) refuseAlive(); - if (this.headWritedSize < 0) { - long len = 0; - for (ByteBuffer buf : buffers) { - len += buf.remaining(); - } - this.contentLength = len; - ByteBuffer headbuf = createHeader(); - headbuf.flip(); - if (buffers == null) { - super.finish(kill, headbuf); - } else { - ByteBuffer[] newbuffers = new ByteBuffer[buffers.length + 1]; - newbuffers[0] = headbuf; - System.arraycopy(buffers, 0, newbuffers, 1, buffers.length); - super.finish(kill, newbuffers); - } - } else { - if (this.headLenPos > 0 && buffers[0] == headBuffer) { - long contentlen = -this.headWritedSize; - for (ByteBuffer buf : buffers) { - contentlen += buf.remaining(); + public void finish404() { + skipHeader(); + super.finish(false, bytes404); + } + + //Header大小 + protected void createHeader() { + if (this.status == 200 && !this.respHeadContainsConnection && !this.request.isWebSocket() + && (this.contentType == null || this.contentType == this.jsonContentType || this.contentType == this.plainContentType) + && (this.contentLength >= 0 && this.contentLength < jsonLiveContentLengthMap.size())) { + Map lengthMap = this.plainLiveContentLengthMap; + if (this.request.isKeepAlive()) { + if (this.contentType == this.jsonContentType) { + lengthMap = this.jsonLiveContentLengthMap; } - byte[] lenBytes = String.valueOf(contentlen).getBytes(); - int start = this.headLenPos - lenBytes.length; - for (int i = 0; i < lenBytes.length; i++) { - headBuffer.put(start + i, lenBytes[i]); + } else { + if (this.contentType == this.jsonContentType) { + lengthMap = this.jsonCloseContentLengthMap; + } else { + lengthMap = this.plainCloseContentLengthMap; + } + } + if (context.lazyHeaders) { + if (this.lastContentLength == this.contentLength && this.lastContentLengthMap == lengthMap) { + headerArray.put(this.lastContentLengthBytes); + } else { + byte[] lenbs = lengthMap.get((int) this.contentLength); + this.lastContentLength = (int) this.contentLength; + this.lastContentLengthMap = lengthMap; + this.lastContentLengthBytes = lenbs; + headerArray.put(lenbs); + } + } else { + headerArray.put(lengthMap.get((int) this.contentLength)); + } + } else { + if (this.status == 200 && !this.respHeadContainsConnection && !this.request.isWebSocket()) { + if (this.request.isKeepAlive()) { + headerArray.put(status200_server_live_Bytes); + } else { + headerArray.put(status200_server_close_Bytes); + } + } else { + if (this.status == 200) { + headerArray.put(status200Bytes); + } else { + headerArray.put(("HTTP/1.1 " + this.status + " " + httpCodes.get(this.status) + "\r\n").getBytes()); + } + headerArray.put(serverNameBytes); + if (!this.respHeadContainsConnection) { + headerArray.put(this.request.isKeepAlive() ? connectAliveBytes : connectCloseBytes); + } + } + if (!this.request.isWebSocket()) { + if (this.contentType == this.jsonContentType) { + headerArray.put(this.jsonContentTypeBytes); + } else if (this.contentType == null || this.contentType == this.plainContentType) { + headerArray.put(this.plainContentTypeBytes); + } else { + headerArray.put(("Content-Type: " + this.contentType + "\r\n").getBytes()); + } + } + if (this.contentLength >= 0) { + if (this.contentLength < contentLengthMap.size()) { + headerArray.put(contentLengthMap.get((int) this.contentLength)); + } else { + headerArray.put(("Content-Length: " + this.contentLength + "\r\n").getBytes()); } } - super.finish(kill, buffers); } + if (dateSupplier != null) headerArray.put(dateSupplier.get()); + + if (this.defaultAddHeaders != null) { + for (String[] headers : this.defaultAddHeaders) { + if (headers.length > 3) { + String v = request.getParameter(headers[2]); + if (v != null) this.header.addValue(headers[0], v); + } else if (headers.length > 2) { + String v = request.getHeader(headers[2]); + if (v != null) this.header.addValue(headers[0], v); + } else { + this.header.addValue(headers[0], headers[1]); + } + } + } + if (this.defaultSetHeaders != null) { + for (String[] headers : this.defaultSetHeaders) { + if (headers.length > 3) { + String v = request.getParameter(headers[2]); + if (v != null) this.header.setValue(headers[0], v); + } else if (headers.length > 2) { + String v = request.getHeader(headers[2]); + if (v != null) this.header.setValue(headers[0], v); + } else { + this.header.setValue(headers[0], headers[1]); + } + } + } + for (Entry en : this.header.getStringEntrys()) { + headerArray.put((en.name + ": " + en.getValue() + "\r\n").getBytes()); + } + if (request.newsessionid != null) { + String domain = defaultCookie == null ? null : defaultCookie.getDomain(); + if (domain == null || domain.isEmpty()) { + domain = ""; + } else { + domain = "Domain=" + domain + "; "; + } + String path = defaultCookie == null ? null : defaultCookie.getPath(); + if (path == null || path.isEmpty()) path = "/"; + if (request.newsessionid.isEmpty()) { + headerArray.put(("Set-Cookie: " + HttpRequest.SESSIONID_NAME + "=; " + domain + "Path=/; Max-Age=0; HttpOnly\r\n").getBytes()); + } else { + headerArray.put(("Set-Cookie: " + HttpRequest.SESSIONID_NAME + "=" + request.newsessionid + "; " + domain + "Path=/; HttpOnly\r\n").getBytes()); + } + } + if (this.cookies != null) { + for (HttpCookie cookie : this.cookies) { + if (cookie == null) continue; + if (defaultCookie != null) { + if (defaultCookie.getDomain() != null && cookie.getDomain() == null) cookie.setDomain(defaultCookie.getDomain()); + if (defaultCookie.getPath() != null && cookie.getPath() == null) cookie.setPath(defaultCookie.getPath()); + } + headerArray.put(("Set-Cookie: " + cookieString(cookie) + "\r\n").getBytes()); + } + } + headerArray.put(LINE); + this.headWritedSize = headerArray.length(); + } + + private CharSequence cookieString(HttpCookie cookie) { + StringBuilder sb = new StringBuilder(); + sb.append(cookie.getName()).append("=").append(cookie.getValue()).append("; Version=1"); + if (cookie.getDomain() != null) sb.append("; Domain=").append(cookie.getDomain()); + if (cookie.getPath() != null) sb.append("; Path=").append(cookie.getPath()); + if (cookie.getPortlist() != null) sb.append("; Port=").append(cookie.getPortlist()); + if (cookie.getMaxAge() > 0) { + sb.append("; Max-Age=").append(cookie.getMaxAge()); + sb.append("; Expires=").append(RFC_1123_DATE_TIME.format(java.time.ZonedDateTime.now(ZONE_GMT).plusSeconds(cookie.getMaxAge()))); + } + if (cookie.getSecure()) sb.append("; Secure"); + if (cookie.isHttpOnly()) sb.append("; HttpOnly"); + return sb; } /** * 异步输出指定内容 * - * @param 泛型 - * @param buffer 输出内容 - * @param attachment 异步回调参数 - * @param handler 异步回调函数 + * @param 泛型 + * @param buffer 输出内容 + * @param handler 异步回调函数 */ - public void sendBody(ByteBuffer buffer, A attachment, CompletionHandler handler) { + protected void sendBody(ByteBuffer buffer, CompletionHandler handler) { if (this.headWritedSize < 0) { if (this.contentLength < 0) this.contentLength = buffer == null ? 0 : buffer.remaining(); - ByteBuffer headbuf = createHeader(); - headbuf.flip(); + createHeader(); if (buffer == null) { - super.send(headbuf, attachment, handler); + super.send(headerArray, handler); } else { - super.send(new ByteBuffer[]{headbuf, buffer}, attachment, handler); + ByteBuffer headbuf = channel.pollWriteBuffer(); + headbuf.put(headerArray.content(), 0, headerArray.length()); + headbuf.flip(); + super.send(new ByteBuffer[]{headbuf, buffer}, null, handler); } } else { - super.send(buffer, attachment, handler); - } - } - - /** - * 异步输出指定内容 - * - * @param 泛型 - * @param buffers 输出内容 - * @param attachment 异步回调参数 - * @param handler 异步回调函数 - */ - public void sendBody(ByteBuffer[] buffers, A attachment, CompletionHandler handler) { - if (this.headWritedSize < 0) { - if (this.contentLength < 0) { - int len = 0; - if (buffers != null && buffers.length > 0) { - for (ByteBuffer b : buffers) { - len += b.remaining(); - } - } - this.contentLength = len; - } - ByteBuffer headbuf = createHeader(); - headbuf.flip(); - if (buffers == null || buffers.length == 0) { - super.send(headbuf, attachment, handler); - } else { - super.send(Utility.unshift(buffers, headbuf), attachment, handler); - } - } else { - super.send(buffers, attachment, handler); + super.send(buffer, null, handler); } } @@ -958,7 +991,9 @@ public class HttpResponse extends Response { len = end > 0 ? clen : end; } this.addHeader("ETag", etag); - ByteBuffer hbuffer = createHeader(); + createHeader(); + ByteBuffer hbuffer = channel.pollWriteBuffer(); + hbuffer.put(headerArray.content(), 0, headerArray.length()); hbuffer.flip(); if (fileBody == null) { if (this.recycleListener != null) this.output = file; @@ -977,112 +1012,6 @@ public class HttpResponse extends Response { this.channel.write(hbuffer, hbuffer, new TransferFileHandler(file, offset, length)); } - //Header大小不能超过一个ByteBuffer的容量 - protected ByteBuffer createHeader() { - ByteBuffer buffer = this.channel.pollWriteBuffer(); - int oldpos = buffer.position(); - if (this.status == 200) { - buffer.put(status200Bytes); - } else { - buffer.put(("HTTP/1.1 " + this.status + " " + httpCodes.get(this.status) + "\r\n").getBytes()); - } - if (this.contentLength >= 0) { - buffer.put(("Content-Length: " + this.contentLength + "\r\n").getBytes()); - } else if (this.contentLength == -2) { - buffer.put(fillContentLengthBytes); - this.headLenPos = buffer.position() - 2; //去掉\r\n - } - if (!this.request.isWebSocket()) { - if (this.contentType == this.jsonContentType) { - buffer.put(this.jsonContentTypeBytes); - } else if (this.contentType == null || this.contentType == this.plainContentType) { - buffer.put(this.plainContentTypeBytes); - } else { - buffer.put(("Content-Type: " + (this.contentType == null ? this.plainContentType : this.contentType) + "\r\n").getBytes()); - } - } - buffer.put(serverNameBytes); - if (dateSupplier != null) buffer.put(dateSupplier.get()); - - if (this.header.getValue("Connection") == null) { - buffer.put(this.request.isKeepAlive() ? connectAliveBytes : connectCloseBytes); - } - - if (this.defaultAddHeaders != null) { - for (String[] headers : this.defaultAddHeaders) { - if (headers.length > 3) { - String v = request.getParameter(headers[2]); - if (v != null) this.header.addValue(headers[0], v); - } else if (headers.length > 2) { - String v = request.getHeader(headers[2]); - if (v != null) this.header.addValue(headers[0], v); - } else { - this.header.addValue(headers[0], headers[1]); - } - } - } - if (this.defaultSetHeaders != null) { - for (String[] headers : this.defaultSetHeaders) { - if (headers.length > 3) { - String v = request.getParameter(headers[2]); - if (v != null) this.header.setValue(headers[0], v); - } else if (headers.length > 2) { - String v = request.getHeader(headers[2]); - if (v != null) this.header.setValue(headers[0], v); - } else { - this.header.setValue(headers[0], headers[1]); - } - } - } - for (Entry en : this.header.getStringEntrys()) { - buffer.put((en.name + ": " + en.getValue() + "\r\n").getBytes()); - } - if (request.newsessionid != null) { - String domain = defaultCookie == null ? null : defaultCookie.getDomain(); - if (domain == null || domain.isEmpty()) { - domain = ""; - } else { - domain = "Domain=" + domain + "; "; - } - String path = defaultCookie == null ? null : defaultCookie.getPath(); - if (path == null || path.isEmpty()) path = "/"; - if (request.newsessionid.isEmpty()) { - buffer.put(("Set-Cookie: " + HttpRequest.SESSIONID_NAME + "=; " + domain + "Path=/; Max-Age=0; HttpOnly\r\n").getBytes()); - } else { - buffer.put(("Set-Cookie: " + HttpRequest.SESSIONID_NAME + "=" + request.newsessionid + "; " + domain + "Path=/; HttpOnly\r\n").getBytes()); - } - } - if (this.cookies != null) { - for (HttpCookie cookie : this.cookies) { - if (cookie == null) continue; - if (defaultCookie != null) { - if (defaultCookie.getDomain() != null && cookie.getDomain() == null) cookie.setDomain(defaultCookie.getDomain()); - if (defaultCookie.getPath() != null && cookie.getPath() == null) cookie.setPath(defaultCookie.getPath()); - } - buffer.put(("Set-Cookie: " + cookieString(cookie) + "\r\n").getBytes()); - } - } - buffer.put(LINE); - this.headWritedSize = buffer.position() - oldpos; - this.headBuffer = buffer; - return buffer; - } - - private CharSequence cookieString(HttpCookie cookie) { - StringBuilder sb = new StringBuilder(); - sb.append(cookie.getName()).append("=").append(cookie.getValue()).append("; Version=1"); - if (cookie.getDomain() != null) sb.append("; Domain=").append(cookie.getDomain()); - if (cookie.getPath() != null) sb.append("; Path=").append(cookie.getPath()); - if (cookie.getPortlist() != null) sb.append("; Port=").append(cookie.getPortlist()); - if (cookie.getMaxAge() > 0) { - sb.append("; Max-Age=").append(cookie.getMaxAge()); - sb.append("; Expires=").append(RFC_1123_DATE_TIME.format(java.time.ZonedDateTime.now(ZONE_GMT).plusSeconds(cookie.getMaxAge()))); - } - if (cookie.getSecure()) sb.append("; Secure"); - if (cookie.isHttpOnly()) sb.append("; HttpOnly"); - return sb; - } - /** * 跳过header的输出 * 通常应用场景是,调用者的输出内容里已经包含了HTTP的响应头信息,因此需要调用此方法避免重复输出HTTP响应头信息。 @@ -1108,6 +1037,7 @@ public class HttpResponse extends Response { */ public HttpResponse setHeader(String name, Object value) { this.header.setValue(name, String.valueOf(value)); + if ("Connection".equalsIgnoreCase(name)) this.respHeadContainsConnection = true; return this; } @@ -1121,6 +1051,7 @@ public class HttpResponse extends Response { */ public HttpResponse addHeader(String name, Object value) { this.header.addValue(name, String.valueOf(value)); + if ("Connection".equalsIgnoreCase(name)) this.respHeadContainsConnection = true; return this; } @@ -1135,6 +1066,9 @@ public class HttpResponse extends Response { if (map == null || map.isEmpty()) return this; for (Map.Entry en : map.entrySet()) { this.header.addValue(en.getKey(), String.valueOf(en.getValue())); + if (!respHeadContainsConnection && "Connection".equalsIgnoreCase(en.getKey())) { + this.respHeadContainsConnection = true; + } } return this; } @@ -1207,17 +1141,17 @@ public class HttpResponse extends Response { * * @return 拦截器 */ - protected BiFunction getBufferHandler() { - return bufferHandler; + protected BiConsumer getCacheHandler() { + return cacheHandler; } /** * 设置输出时的拦截器 * - * @param bufferHandler 拦截器 + * @param cacheHandler 拦截器 */ - protected void setBufferHandler(BiFunction bufferHandler) { - this.bufferHandler = bufferHandler; + protected void setCacheHandler(BiConsumer cacheHandler) { + this.cacheHandler = cacheHandler; } /** @@ -1256,14 +1190,14 @@ public class HttpResponse extends Response { public TransferFileHandler(File file) throws IOException { this.file = file; - this.filechannel = AsynchronousFileChannel.open(file.toPath(), options, ((HttpContext) context).getExecutor()); + this.filechannel = AsynchronousFileChannel.open(file.toPath(), options); this.readpos = 0; this.max = file.length(); } public TransferFileHandler(File file, long offset, long len) throws IOException { this.file = file; - this.filechannel = AsynchronousFileChannel.open(file.toPath(), options, ((HttpContext) context).getExecutor()); + this.filechannel = AsynchronousFileChannel.open(file.toPath(), options); this.readpos = offset <= 0 ? 0 : offset; this.max = len <= 0 ? file.length() : len; } @@ -1331,6 +1265,10 @@ public class HttpResponse extends Response { public String jsonContentType; + public byte[] plainContentTypeBytes; + + public byte[] jsonContentTypeBytes; + public String[][] defaultAddHeaders; public String[][] defaultSetHeaders; @@ -1343,6 +1281,33 @@ public class HttpResponse extends Response { public List< HttpRender> renders; + public final Map plainLiveContentLengthMap = new HashMap<>(); + + public final Map jsonLiveContentLengthMap = new HashMap<>(); + + public final Map plainCloseContentLengthMap = new HashMap<>(); + + public final Map jsonCloseContentLengthMap = new HashMap<>(); + + public HttpResponseConfig init(AnyValue config) { + if (this.plainContentTypeBytes == null) { + String plainct = plainContentType == null || plainContentType.isEmpty() ? "text/plain; charset=utf-8" : plainContentType; + String jsonct = jsonContentType == null || jsonContentType.isEmpty() ? "application/json; charset=utf-8" : jsonContentType; + this.plainContentType = plainct; + this.jsonContentType = jsonct; + this.plainContentTypeBytes = ("Content-Type: " + plainct + "\r\n").getBytes(); + this.jsonContentTypeBytes = ("Content-Type: " + jsonct + "\r\n").getBytes(); + for (int i = 0; i <= cacheMaxContentLength; i++) { + byte[] lenbytes = ("Content-Length: " + i + "\r\n").getBytes(); + plainLiveContentLengthMap.put(i, append(append(status200_server_live_Bytes, plainContentTypeBytes), lenbytes)); + plainCloseContentLengthMap.put(i, append(append(status200_server_close_Bytes, plainContentTypeBytes), lenbytes)); + jsonLiveContentLengthMap.put(i, append(append(status200_server_live_Bytes, jsonContentTypeBytes), lenbytes)); + jsonCloseContentLengthMap.put(i, append(append(status200_server_close_Bytes, jsonContentTypeBytes), lenbytes)); + } + } + return this; + } + @Override public String toString() { return JsonConvert.root().convertTo(this); diff --git a/src/org/redkale/net/http/HttpResult.java b/src/org/redkale/net/http/HttpResult.java index 916b6a0af..a0c048c2c 100644 --- a/src/org/redkale/net/http/HttpResult.java +++ b/src/org/redkale/net/http/HttpResult.java @@ -69,6 +69,12 @@ public class HttpResult { return cookie(new HttpCookie(name, String.valueOf(value))); } + public HttpResult cookie(String name, Serializable value, boolean httpOnly) { + HttpCookie c = new HttpCookie(name, String.valueOf(value)); + c.setHttpOnly(httpOnly); + return cookie(c); + } + public HttpResult cookie(HttpCookie cookie) { if (this.cookies == null) this.cookies = new ArrayList<>(); this.cookies.add(cookie); diff --git a/src/org/redkale/net/http/HttpServer.java b/src/org/redkale/net/http/HttpServer.java index df16f3042..f33890519 100644 --- a/src/org/redkale/net/http/HttpServer.java +++ b/src/org/redkale/net/http/HttpServer.java @@ -16,6 +16,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; import java.util.logging.Level; +import org.redkale.boot.Application; import org.redkale.mq.MessageAgent; import org.redkale.net.*; import org.redkale.net.http.HttpContext.HttpContextConfig; @@ -41,15 +42,15 @@ public class HttpServer extends Server defaultAddHeaders = new ArrayList<>(); final List defaultSetHeaders = new ArrayList<>(); boolean autoOptions = false; @@ -322,8 +324,10 @@ public class HttpServer extends Server 0) { if (this.dateScheduler == null) { this.dateScheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { - final Thread t = new Thread(r, "HTTP:" + port + "-DateSchedule-Thread"); + final Thread t = new Thread(r, "Redkale-HTTP:" + port + "-DateSchedule-Thread"); t.setDaemon(true); return t; }); @@ -432,11 +436,12 @@ public class HttpServer extends Server createResponsePool(AtomicLong createCounter, AtomicLong cycleCounter, int responsePoolSize) { - ObjectPool pool = ObjectPool.createSafePool(createCounter, cycleCounter, responsePoolSize,(Creator)null, (x) ->((HttpResponse)x).prepare(),(x) -> ((HttpResponse)x).recycle()); - pool.setCreator((Object... params) -> new HttpResponse(this.context, new HttpRequest(this.context), pool, this.respConfig)); + Creator creator = (Object... params) -> new HttpResponse(this.context, new HttpRequest(this.context), this.respConfig); + ObjectPool pool = ObjectPool.createSafePool(createCounter, cycleCounter, responsePoolSize, creator, (x) -> ((HttpResponse) x).prepare(), (x) -> ((HttpResponse) x).recycle()); return pool; } } diff --git a/src/org/redkale/net/http/HttpServlet.java b/src/org/redkale/net/http/HttpServlet.java index 0a8e912d8..ec04f0fad 100644 --- a/src/org/redkale/net/http/HttpServlet.java +++ b/src/org/redkale/net/http/HttpServlet.java @@ -8,10 +8,9 @@ package org.redkale.net.http; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; +import java.util.function.*; import org.redkale.asm.*; import static org.redkale.asm.ClassWriter.COMPUTE_FRAMES; import static org.redkale.asm.Opcodes.*; @@ -35,28 +34,29 @@ public class HttpServlet extends Servlet String _prefix = ""; //当前HttpServlet的path前缀 - HashMap _tmpentrys; //Rest生成时赋值, 字段名Rest有用到 + HashMap _actionmap; //Rest生成时赋值, 字段名Rest有用到 - private Map.Entry[] mappings; //字段名Rest有用到 + private Map.Entry[] mappings; //字段名Rest有用到 //这里不能直接使用HttpServlet,会造成死循环初始化HttpServlet private final Servlet authSuccessServlet = new Servlet() { @Override public void execute(HttpRequest request, HttpResponse response) throws IOException { - InnerActionEntry entry = (InnerActionEntry) request.attachment; + ActionEntry entry = request.actionEntry; if (entry.rpconly && !request.rpc) { response.finish(503, null); return; } if (entry.cacheseconds > 0) {//有缓存设置 - CacheEntry ce = entry.cache.get(request.getRequestURI()); + CacheEntry ce = entry.modeOneCache ? entry.oneCache : entry.cache.get(request.getRequestURI()); if (ce != null && ce.time + entry.cacheseconds * 1000 > System.currentTimeMillis()) { //缓存有效 response.setStatus(ce.status); response.setContentType(ce.contentType); - response.finish(ce.getBuffers()); + response.skipHeader(); + response.finish(ce.getBytes()); return; } - response.setBufferHandler(entry.cacheHandler); + response.setCacheHandler(entry.cacheHandler); } entry.servlet.execute(request, response); } @@ -66,14 +66,31 @@ public class HttpServlet extends Servlet private final Servlet preSuccessServlet = new Servlet() { @Override public void execute(HttpRequest request, HttpResponse response) throws IOException { - for (Map.Entry en : mappings) { + if (request.actionEntry != null) { + ActionEntry entry = request.actionEntry; + if (!entry.checkMethod(request.getMethod())) { + response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error")); + return; + } + request.moduleid = entry.moduleid; + request.actionid = entry.actionid; + request.annotations = entry.annotations; + if (entry.auth) { + response.thenEvent(authSuccessServlet); + authenticate(request, response); + } else { + authSuccessServlet.execute(request, response); + } + return; + } + for (Map.Entry en : mappings) { if (request.getRequestURI().startsWith(en.getKey())) { - InnerActionEntry entry = en.getValue(); + ActionEntry entry = en.getValue(); if (!entry.checkMethod(request.getMethod())) { response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error")); return; } - request.attachment = entry; + request.actionEntry = entry; request.moduleid = entry.moduleid; request.actionid = entry.actionid; request.annotations = entry.annotations; @@ -97,10 +114,10 @@ public class HttpServlet extends Servlet String path = _prefix == null ? "" : _prefix; WebServlet ws = this.getClass().getAnnotation(WebServlet.class); if (ws != null && !ws.repair()) path = ""; - HashMap map = this._tmpentrys != null ? this._tmpentrys : loadActionEntry(); + HashMap map = this._actionmap != null ? this._actionmap : loadActionEntry(); this.mappings = new Map.Entry[map.size()]; int i = -1; - for (Map.Entry en : map.entrySet()) { + for (Map.Entry en : map.entrySet()) { mappings[++i] = new AbstractMap.SimpleEntry<>(path + en.getKey(), en.getValue()); } //必须要倒排序, /query /query1 /query12 确保含子集的优先匹配 /query12 /query1 /query @@ -122,7 +139,7 @@ public class HttpServlet extends Servlet * public void preExecute(final HttpRequest request, final HttpResponse response) throws IOException { * //设置当前用户信息 * final String sessionid = request.getSessionid(false); - * if (sessionid != null) request.setCurrentUser(userService.current(sessionid)); + * if (sessionid != null) request.setCurrentUserid(userService.currentUserid(sessionid)); * * if (finer) response.recycleListener((req, resp) -> { //记录处理时间比较长的请求 * long e = System.currentTimeMillis() - ((HttpRequest) req).getCreatetime(); @@ -148,13 +165,10 @@ public class HttpServlet extends Servlet *

      *      @Override
      *      public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
-     *          UserInfo info = request.currentUser();
-     *          if (info == null) {
+     *          Serializable userid = request.currentUserid();
+     *          if (userid == null) {
      *              response.finishJson(RET_UNLOGIN);
      *              return;
-     *          } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
-     *              response.finishJson(RET_AUTHILLEGAL);
-     *              return;
      *          }
      *          response.nextEvent();
      *      }
@@ -177,10 +191,10 @@ public class HttpServlet extends Servlet
         preExecute(request, response);
     }
 
-    private HashMap loadActionEntry() {
+    private HashMap loadActionEntry() {
         WebServlet module = this.getClass().getAnnotation(WebServlet.class);
         final int serviceid = module == null ? 0 : module.moduleid();
-        final HashMap map = new HashMap<>();
+        final HashMap map = new HashMap<>();
         HashMap nameset = new HashMap<>();
         final Class selfClz = this.getClass();
         Class clz = this.getClass();
@@ -192,8 +206,7 @@ public class HttpServlet extends Servlet
                 if ("service".equals(methodname) || "preExecute".equals(methodname) || "execute".equals(methodname) || "authenticate".equals(methodname)) continue;
                 //-----------------------------------------------
                 Class[] paramTypes = method.getParameterTypes();
-                if (paramTypes.length != 2 || paramTypes[0] != HttpRequest.class
-                    || paramTypes[1] != HttpResponse.class) continue;
+                if (paramTypes.length != 2 || paramTypes[0] != HttpRequest.class || paramTypes[1] != HttpResponse.class) continue;
                 //-----------------------------------------------
                 Class[] exps = method.getExceptionTypes();
                 if (exps.length > 0 && (exps.length != 1 || exps[0] != IOException.class)) continue;
@@ -211,21 +224,21 @@ public class HttpServlet extends Servlet
                     throw new RuntimeException(this.getClass().getSimpleName() + " have two same " + HttpMapping.class.getSimpleName() + "(" + name + ")");
                 }
                 nameset.put(name, clz);
-                map.put(name, new InnerActionEntry(serviceid, actionid, name, methods, method, createActionServlet(method)));
+                map.put(name, new ActionEntry(serviceid, actionid, name, methods, method, createActionServlet(method)));
             }
         } while ((clz = clz.getSuperclass()) != HttpServlet.class);
         return map;
     }
 
-    protected static final class InnerActionEntry {
+    protected static final class ActionEntry {
 
-        InnerActionEntry(int moduleid, int actionid, String name, String[] methods, Method method, HttpServlet servlet) {
+        ActionEntry(int moduleid, int actionid, String name, String[] methods, Method method, HttpServlet servlet) {
             this(moduleid, actionid, name, methods, method, rpconly(method), auth(method), cacheseconds(method), servlet);
             this.annotations = annotations(method);
         }
 
         //供Rest类使用,参数不能随便更改
-        public InnerActionEntry(int moduleid, int actionid, String name, String[] methods, Method method, boolean rpconly, boolean auth, int cacheseconds, HttpServlet servlet) {
+        public ActionEntry(int moduleid, int actionid, String name, String[] methods, Method method, boolean rpconly, boolean auth, int cacheseconds, HttpServlet servlet) {
             this.moduleid = moduleid;
             this.actionid = actionid;
             this.name = name;
@@ -235,14 +248,24 @@ public class HttpServlet extends Servlet
             this.rpconly = rpconly;
             this.auth = auth;
             this.cacheseconds = cacheseconds;
-            this.cache = cacheseconds > 0 ? new ConcurrentHashMap<>() : null;
-            this.cacheHandler = cacheseconds > 0 ? (HttpResponse response, ByteBuffer[] buffers) -> {
-                int status = response.getStatus();
-                if (status != 200) return null;
-                CacheEntry ce = new CacheEntry(response.getStatus(), response.getContentType(), buffers);
-                cache.put(response.getRequest().getRequestURI(), ce);
-                return ce.getBuffers();
-            } : null;
+            if (Utility.contains(name, '.', '*', '{', '[', '(', '|', '^', '$', '+', '?', '\\') || name.endsWith("/")) { //是否是正则表达式
+                this.modeOneCache = false;
+                this.cache = cacheseconds > 0 ? new ConcurrentHashMap<>() : null;
+                this.cacheHandler = cacheseconds > 0 ? (HttpResponse response, byte[] content) -> {
+                    int status = response.getStatus();
+                    if (status != 200) return;
+                    CacheEntry ce = new CacheEntry(response.getStatus(), response.getContentType(), content);
+                    cache.put(response.getRequest().getRequestURI(), ce);
+                } : null;
+            } else { //单一url
+                this.modeOneCache = true;
+                this.cache = null;
+                this.cacheHandler = cacheseconds > 0 ? (HttpResponse response, byte[] content) -> {
+                    int status = response.getStatus();
+                    if (status != 200) return;
+                    oneCache = new CacheEntry(response.getStatus(), response.getContentType(), content);
+                } : null;
+            }
         }
 
         protected static boolean auth(Method method) {
@@ -277,10 +300,12 @@ public class HttpServlet extends Servlet
             return false;
         }
 
-        final BiFunction cacheHandler;
+        final BiConsumer cacheHandler;
 
         final ConcurrentHashMap cache;
 
+        final boolean modeOneCache;
+
         final int cacheseconds;
 
         final boolean rpconly;
@@ -299,6 +324,8 @@ public class HttpServlet extends Servlet
 
         Method method;
 
+        CacheEntry oneCache;
+
         Annotation[] annotations;
     }
 
@@ -385,28 +412,38 @@ public class HttpServlet extends Servlet
 
         public final long time = System.currentTimeMillis();
 
-        private final ByteBuffer[] buffers;
+        private final byte[] cacheBytes;
 
         private final int status;
 
         private final String contentType;
 
-        public CacheEntry(int status, String contentType, ByteBuffer[] bufs) {
+        public CacheEntry(int status, String contentType, byte[] cacheBytes) {
             this.status = status;
             this.contentType = contentType;
-            final ByteBuffer[] newBuffers = new ByteBuffer[bufs.length];
-            for (int i = 0; i < newBuffers.length; i++) {
-                newBuffers[i] = bufs[i].duplicate().asReadOnlyBuffer();
-            }
-            this.buffers = newBuffers;
+            this.cacheBytes = cacheBytes;
         }
 
-        public ByteBuffer[] getBuffers() {
-            final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length];
-            for (int i = 0; i < newBuffers.length; i++) {
-                newBuffers[i] = buffers[i].duplicate();
-            }
-            return newBuffers;
+        public byte[] getBytes() {
+            return cacheBytes;
+        }
+    }
+
+    static class HttpActionServlet extends HttpServlet {
+
+        final ActionEntry action;
+
+        final HttpServlet servlet;
+
+        public HttpActionServlet(ActionEntry actionEntry, HttpServlet servlet) {
+            this.action = actionEntry;
+            this.servlet = servlet;
+        }
+
+        @Override
+        public void execute(HttpRequest request, HttpResponse response) throws IOException {
+            request.actionEntry = action;
+            servlet.execute(request, response);
         }
     }
 }
diff --git a/src/org/redkale/net/http/HttpSimpleRequest.java b/src/org/redkale/net/http/HttpSimpleRequest.java
index d7e2b4752..d4ab4dbbf 100644
--- a/src/org/redkale/net/http/HttpSimpleRequest.java
+++ b/src/org/redkale/net/http/HttpSimpleRequest.java
@@ -5,8 +5,8 @@
  */
 package org.redkale.net.http;
 
+import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.redkale.convert.*;
@@ -62,17 +62,20 @@ public class HttpSimpleRequest implements java.io.Serializable {
     protected String contentType;
 
     @ConvertColumn(index = 10)
-    protected int currentUserid;
+    protected int hashid;
 
     @ConvertColumn(index = 11)
+    protected int currentUserid;
+
+    @ConvertColumn(index = 12)
     @Comment("http header信息")
     protected Map headers;
 
-    @ConvertColumn(index = 12)
+    @ConvertColumn(index = 13)
     @Comment("参数信息")
     protected Map params;
 
-    @ConvertColumn(index = 13)
+    @ConvertColumn(index = 14)
     @Comment("http body信息")
     protected byte[] body; //对应HttpRequest.array
 
@@ -95,8 +98,11 @@ public class HttpSimpleRequest implements java.io.Serializable {
         final StringBuilder sb = new StringBuilder();
         AtomicBoolean no2 = new AtomicBoolean(false);
         this.params.forEach((n, v) -> {
-            if (no2.get()) sb.append('&');
-            sb.append(n).append('=').append(URLEncoder.encode(v, StandardCharsets.UTF_8));
+            if (no2.get()) sb.append('&'); //JDK9+ 可直接用 URLEncoder.encode(v, StandardCharsets.UTF_8)
+            try {
+                sb.append(n).append('=').append(URLEncoder.encode(v, "UTF-8"));
+            } catch (UnsupportedEncodingException e) {
+            }
             no2.set(true);
         });
         return sb.toString();
@@ -158,6 +164,11 @@ public class HttpSimpleRequest implements java.io.Serializable {
         return this;
     }
 
+    public HttpSimpleRequest hashid(int hashid) {
+        this.hashid = hashid;
+        return this;
+    }
+
     public HttpSimpleRequest currentUserid(int userid) {
         this.currentUserid = userid;
         return this;
@@ -307,6 +318,14 @@ public class HttpSimpleRequest implements java.io.Serializable {
         this.remoteAddr = remoteAddr;
     }
 
+    public int getHashid() {
+        return hashid;
+    }
+
+    public void setHashid(int hashid) {
+        this.hashid = hashid;
+    }
+
     public int getCurrentUserid() {
         return currentUserid;
     }
diff --git a/src/org/redkale/net/http/MultiContext.java b/src/org/redkale/net/http/MultiContext.java
index b92d30b1b..a796541da 100644
--- a/src/org/redkale/net/http/MultiContext.java
+++ b/src/org/redkale/net/http/MultiContext.java
@@ -340,15 +340,15 @@ public final class MultiContext {
             int b = in.read();
             c++;
             if (b == -1 || (lasted == '\r' && b == '\n')) break;
-            if (lasted != '\r') buf.write(lasted);
+            if (lasted != '\r') buf.put(lasted);
             lasted = (byte) b;
             if (bd && bc == c) {
-                buf.write(lasted);
+                buf.put(lasted);
                 if (buf.equal(this.endboundarray)) break;
                 buf.removeLastByte();
             }
         }
-        if (buf.size() == 0) return "";
+        if (buf.length() == 0) return "";
         return buf.toString(this.charset).trim();
     }
 
diff --git a/src/org/redkale/net/http/Rest.java b/src/org/redkale/net/http/Rest.java
index 9f12763ee..ff9c1b09e 100644
--- a/src/org/redkale/net/http/Rest.java
+++ b/src/org/redkale/net/http/Rest.java
@@ -782,7 +782,7 @@ public final class Rest {
         final String futureDesc = Type.getDescriptor(CompletableFuture.class);
         final String flipperDesc = Type.getDescriptor(Flipper.class);
         final String httpServletName = HttpServlet.class.getName().replace('.', '/');
-        final String innerEntryName = HttpServlet.InnerActionEntry.class.getName().replace('.', '/');
+        final String actionEntryName = HttpServlet.ActionEntry.class.getName().replace('.', '/');
         final String attrDesc = Type.getDescriptor(org.redkale.util.Attribute.class);
         final String multiContextDesc = Type.getDescriptor(MultiContext.class);
         final String multiContextName = MultiContext.class.getName().replace('.', '/');
@@ -898,6 +898,7 @@ public final class Rest {
         }
         if (entrys.isEmpty()) return null; //没有可HttpMapping的方法
 
+        Collections.sort(entrys);
         RestClassLoader newLoader = new RestClassLoader(loader);
         final int moduleid = controller == null ? 0 : controller.moduleid();
         { //注入 @WebServlet 注解
@@ -938,7 +939,7 @@ public final class Rest {
             //classMap.put("comment", comment); //不显示太多信息
         }
         { //内部类
-            cw.visitInnerClass(innerEntryName, httpServletName, HttpServlet.InnerActionEntry.class.getSimpleName(), ACC_PROTECTED + ACC_FINAL + ACC_STATIC);
+            cw.visitInnerClass(actionEntryName, httpServletName, HttpServlet.ActionEntry.class.getSimpleName(), ACC_PROTECTED + ACC_FINAL + ACC_STATIC);
 
             for (final MappingEntry entry : entrys) {
                 cw.visitInnerClass(newDynName + "$" + entry.newActionClassName, newDynName, entry.newActionClassName, ACC_PRIVATE + ACC_STATIC);
@@ -1886,6 +1887,13 @@ public final class Rest {
                 mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finish", "(Ljava/lang/String;)V", false);
                 mv.visitInsn(RETURN);
                 maxLocals += 2;
+            } else if (returnType == byte[].class) {
+                mv.visitVarInsn(ASTORE, maxLocals);
+                mv.visitVarInsn(ALOAD, 2); //response
+                mv.visitVarInsn(ALOAD, maxLocals);
+                mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finish", "([B)V", false);
+                mv.visitInsn(RETURN);
+                maxLocals++;
             } else if (returnType == String.class) {
                 mv.visitVarInsn(ASTORE, maxLocals);
                 mv.visitVarInsn(ALOAD, 2); //response
@@ -1909,19 +1917,36 @@ public final class Rest {
                 mv.visitInsn(RETURN);
                 maxLocals++;
             } else {
-                mv.visitVarInsn(ASTORE, maxLocals);
-                mv.visitVarInsn(ALOAD, 2); //response
-                if (rcs != null && rcs.length > 0) {
-                    mv.visitVarInsn(ALOAD, 0);
-                    mv.visitFieldInsn(GETFIELD, newDynName, REST_JSONCONVERT_FIELD_PREFIX + restConverts.size(), jsonConvertDesc);
-                    mv.visitVarInsn(ALOAD, maxLocals);
-                    mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finish", "(" + convertDesc + "Ljava/lang/Object;)V", false);
+                if (!CompletableFuture.class.isAssignableFrom(returnType) && !org.redkale.service.RetResult.class.isAssignableFrom(returnType)
+                    && !HttpResult.class.isAssignableFrom(returnType) && !HttpScope.class.isAssignableFrom(returnType)) {
+                    mv.visitVarInsn(ASTORE, maxLocals);
+                    if (entry.contentLength > 0) {
+                        mv.visitVarInsn(ALOAD, 2); //response
+                        pushInt(mv, entry.contentLength);
+                        mv.visitVarInsn(ALOAD, maxLocals);
+                        mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(ILjava/lang/Object;)V", false);
+                    } else {
+                        mv.visitVarInsn(ALOAD, 2); //response
+                        mv.visitVarInsn(ALOAD, maxLocals);
+                        mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finishJson", "(Ljava/lang/Object;)V", false);
+                    }
+                    mv.visitInsn(RETURN);
+                    maxLocals++;
                 } else {
-                    mv.visitVarInsn(ALOAD, maxLocals);
-                    mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finish", "(Ljava/lang/Object;)V", false);
+                    mv.visitVarInsn(ASTORE, maxLocals);
+                    mv.visitVarInsn(ALOAD, 2); //response
+                    if (rcs != null && rcs.length > 0) {
+                        mv.visitVarInsn(ALOAD, 0);
+                        mv.visitFieldInsn(GETFIELD, newDynName, REST_JSONCONVERT_FIELD_PREFIX + restConverts.size(), jsonConvertDesc);
+                        mv.visitVarInsn(ALOAD, maxLocals);
+                        mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finish", "(" + convertDesc + "Ljava/lang/Object;)V", false);
+                    } else {
+                        mv.visitVarInsn(ALOAD, maxLocals);
+                        mv.visitMethodInsn(INVOKEVIRTUAL, respInternalName, "finish", "(Ljava/lang/Object;)V", false);
+                    }
+                    mv.visitInsn(RETURN);
+                    maxLocals++;
                 }
-                mv.visitInsn(RETURN);
-                maxLocals++;
             }
             mv.visitMaxs(maxStack, maxLocals);
             mappingMap.put("params", paramMaps);
@@ -1972,15 +1997,15 @@ public final class Rest {
             }
         } // end  for each 
 
-//        HashMap _createRestInnerActionEntry() {
-//              HashMap map = new HashMap<>();
-//              map.put("asyncfind3", new InnerActionEntry(100000,200000,"asyncfind3", new String[]{},null,false,false,0, new _Dync_asyncfind3_HttpServlet()));
-//              map.put("asyncfind2", new InnerActionEntry(1,2,"asyncfind2", new String[]{"GET", "POST"},null,false,true,0, new _Dync_asyncfind2_HttpServlet()));
+//        HashMap _createRestActionEntry() {
+//              HashMap map = new HashMap<>();
+//              map.put("asyncfind3", new ActionEntry(100000,200000,"asyncfind3", new String[]{},null,false,false,0, new _Dync_asyncfind3_HttpServlet()));
+//              map.put("asyncfind2", new ActionEntry(1,2,"asyncfind2", new String[]{"GET", "POST"},null,false,true,0, new _Dync_asyncfind2_HttpServlet()));
 //              return map;
 //          }
         Map mappingurlToMethod = new HashMap<>();
-        { //_createRestInnerActionEntry 方法
-            mv = new MethodDebugVisitor(cw.visitMethod(0, "_createRestInnerActionEntry", "()Ljava/util/HashMap;", "()Ljava/util/HashMap;", null));
+        { //_createRestActionEntry 方法
+            mv = new MethodDebugVisitor(cw.visitMethod(0, "_createRestActionEntry", "()Ljava/util/HashMap;", "()Ljava/util/HashMap;", null));
             //mv.setDebug(true);
             mv.visitTypeInsn(NEW, "java/util/HashMap");
             mv.visitInsn(DUP);
@@ -1991,7 +2016,7 @@ public final class Rest {
                 mappingurlToMethod.put(entry.mappingurl, entry.mappingMethod);
                 mv.visitVarInsn(ALOAD, 1);
                 mv.visitLdcInsn(entry.mappingurl);  //name
-                mv.visitTypeInsn(NEW, innerEntryName); //new InnerActionEntry
+                mv.visitTypeInsn(NEW, actionEntryName); //new ActionEntry
                 mv.visitInsn(DUP);
                 pushInt(mv, moduleid); //moduleid
                 pushInt(mv, entry.actionid); //actionid
@@ -2012,7 +2037,7 @@ public final class Rest {
                 mv.visitInsn(DUP);
                 mv.visitVarInsn(ALOAD, 0);
                 mv.visitMethodInsn(INVOKESPECIAL, newDynName + "$" + entry.newActionClassName, "", "(L" + newDynName + ";)V", false);
-                mv.visitMethodInsn(INVOKESPECIAL, innerEntryName, "", "(IILjava/lang/String;[Ljava/lang/String;Ljava/lang/reflect/Method;ZZILorg/redkale/net/http/HttpServlet;)V", false);
+                mv.visitMethodInsn(INVOKESPECIAL, actionEntryName, "", "(IILjava/lang/String;[Ljava/lang/String;Ljava/lang/reflect/Method;ZZILorg/redkale/net/http/HttpServlet;)V", false);
                 mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false);
                 mv.visitInsn(POP);
             }
@@ -2084,14 +2109,14 @@ public final class Rest {
             java.util.function.Supplier sSupplier = () -> JsonConvert.root().convertTo(classMap);
             tostringfield.set(obj, sSupplier);
 
-            Method restactMethod = newClazz.getDeclaredMethod("_createRestInnerActionEntry");
+            Method restactMethod = newClazz.getDeclaredMethod("_createRestActionEntry");
             restactMethod.setAccessible(true);
-            Field tmpentrysfield = HttpServlet.class.getDeclaredField("_tmpentrys");
+            Field tmpentrysfield = HttpServlet.class.getDeclaredField("_actionmap");
             tmpentrysfield.setAccessible(true);
-            HashMap innerEntryMap = (HashMap) restactMethod.invoke(obj);
-            for (Map.Entry en : innerEntryMap.entrySet()) {
+            HashMap innerEntryMap = (HashMap) restactMethod.invoke(obj);
+            for (Map.Entry en : innerEntryMap.entrySet()) {
                 Method m = mappingurlToMethod.get(en.getKey());
-                if (m != null) en.getValue().annotations = HttpServlet.InnerActionEntry.annotations(m);
+                if (m != null) en.getValue().annotations = HttpServlet.ActionEntry.annotations(m);
             }
             tmpentrysfield.set(obj, innerEntryMap);
             return obj;
@@ -2159,7 +2184,7 @@ public final class Rest {
         }
     }
 
-    private static class MappingEntry {
+    private static class MappingEntry implements Comparable {
 
         private static final RestMapping DEFAULT__MAPPING;
 
@@ -2188,6 +2213,7 @@ public final class Rest {
             this.rpconly = serrpconly || mapping.rpconly();
             this.actionid = mapping.actionid();
             this.cacheseconds = mapping.cacheseconds();
+            this.contentLength = mapping.length();
             this.comment = mapping.comment();
             boolean pound = false;
             Parameter[] params = method.getParameters();
@@ -2203,6 +2229,8 @@ public final class Rest {
             this.newActionClassName = "_Dyn_" + this.newMethodName + "_ActionHttpServlet";
         }
 
+        public final int contentLength;
+
         public final int methodidx; // _paramtypes 的下标,从0开始
 
         public final Method mappingMethod;
@@ -2229,7 +2257,7 @@ public final class Rest {
 
         public final boolean existsPound;  //是否包含#的参数
 
-        String mappingurl; //在生成方法时赋值, 供_createRestInnerActionEntry使用
+        String mappingurl; //在生成方法时赋值, 供 _createRestActionEntry 使用
 
         @RestMapping()
         void mapping() { //用于获取Mapping 默认值
@@ -2247,5 +2275,10 @@ public final class Rest {
             return this.name.equals(((MappingEntry) obj).name);
         }
 
+        @Override
+        public int compareTo(MappingEntry o) {
+            return this.name.compareTo(o.name);
+        }
+
     }
 }
diff --git a/src/org/redkale/net/http/RestMapping.java b/src/org/redkale/net/http/RestMapping.java
index 4fc48e744..e927c69e6 100644
--- a/src/org/redkale/net/http/RestMapping.java
+++ b/src/org/redkale/net/http/RestMapping.java
@@ -74,6 +74,10 @@ public @interface RestMapping {
      */
     int cacheseconds() default 0;
 
+    //json结果的长度, 临时功能, 对应@HttpMapping.length
+    @Deprecated
+    int length() default 0;
+
     /**
      * 允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法, 对应@HttpMapping.methods
      *
diff --git a/src/org/redkale/net/http/WebSocket.java b/src/org/redkale/net/http/WebSocket.java
index 458b84aba..f459aacbc 100644
--- a/src/org/redkale/net/http/WebSocket.java
+++ b/src/org/redkale/net/http/WebSocket.java
@@ -9,6 +9,7 @@ import org.redkale.net.http.WebSocketPacket.FrameType;
 import java.io.*;
 import java.net.*;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.function.*;
@@ -80,10 +81,13 @@ public abstract class WebSocket {
     @Comment("WebSocket将延迟发送")
     public static final int RETCODE_DEAYSEND = 1 << 9; //512
 
-    WebSocketRunner _runner; //不可能为空 
-
     WebSocketEngine _engine; //不可能为空 
 
+    //WebSocketRunner _runner; //不可能为空 
+    WebSocketReadHandler _readHandler;
+
+    WebSocketWriteHandler _writeHandler;
+
     InetSocketAddress _sncpAddress; //分布式下不可为空
 
     AsyncConnection _channel;//不可能为空 
@@ -110,24 +114,33 @@ public abstract class WebSocket {
 
     long createtime = System.currentTimeMillis();
 
-    private long pingtime;
+    List delayPackets;
 
     private Map attributes = new HashMap<>(); //非线程安全
 
-    List delayPackets;
+    private long lastPingTime;
+
+    long lastReadTime;
+
+    long lastSendTime;
+
+    boolean initiateClosed; //收到客户端发送的CLOSE消息
+
+    private boolean closed = false;
 
     protected WebSocket() {
     }
 
     //----------------------------------------------------------------
     public final CompletableFuture sendPing() {
-        this.pingtime = System.currentTimeMillis();
+        this.lastPingTime = System.currentTimeMillis();
         //if (_engine.finest) _engine.logger.finest(this + " on "+_engine.getEngineid()+" ping...");
         return sendPacket(WebSocketPacket.DEFAULT_PING_PACKET);
     }
 
     public final CompletableFuture sendPing(byte[] data) {
-        this.pingtime = System.currentTimeMillis();
+        if (data == null) return sendPing();
+        this.lastPingTime = System.currentTimeMillis();
         return sendPacket(new WebSocketPacket(FrameType.PING, data));
     }
 
@@ -147,18 +160,7 @@ public abstract class WebSocket {
      * @return 0表示成功, 非0表示错误码
      */
     public final CompletableFuture send(Object message) {
-        return send(false, message, true);
-    }
-
-    /**
-     * 给自身发送消息, 消息类型是key-value键值对
-     *
-     * @param messages key-value键值对
-     *
-     * @return 0表示成功, 非0表示错误码
-     */
-    public final CompletableFuture sendMap(Object... messages) {
-        return send(true, messages, true);
+        return send(message, true);
     }
 
     /**
@@ -170,48 +172,30 @@ public abstract class WebSocket {
      * @return 0表示成功, 非0表示错误码
      */
     public final CompletableFuture send(Object message, boolean last) {
-        return send(false, message, last);
-    }
-
-    /**
-     * 给自身发送消息, 消息类型是key-value键值对
-     *
-     * @param last     是否最后一条
-     * @param messages key-value键值对
-     *
-     * @return 0表示成功, 非0表示错误码
-     */
-    public final CompletableFuture 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 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[]) {
-                    return sendPacket(new WebSocketPacket((Serializable) json, last));
+                if (json instanceof CharSequence) {
+                    return sendPacket(new WebSocketPacket(json.toString(), last));
+                } else if (json == null || json instanceof byte[]) {
+                    return sendPacket(new WebSocketPacket((byte[]) json, last));
                 } else if (message instanceof WebSocketPacket) {
                     return sendPacket((WebSocketPacket) message);
                 } else {
-                    return sendPacket(new WebSocketPacket(getSendConvert(), mapconvable, json, last));
+                    Convert convert = getSendConvert();
+                    return sendPacket(new WebSocketPacket(convert.isBinary() ? FrameType.BINARY : FrameType.TEXT, convert.convertToBytes(json), last));
                 }
             });
         }
-        if (message == null || message instanceof CharSequence || message instanceof byte[]) {
-            return sendPacket(new WebSocketPacket((Serializable) message, last));
+        Object json = message;
+        if (json instanceof CharSequence) {
+            return sendPacket(new WebSocketPacket(FrameType.TEXT, json.toString().getBytes(StandardCharsets.UTF_8), last));
+        } else if (json == null || json instanceof byte[]) {
+            return sendPacket(new WebSocketPacket(FrameType.BINARY, (byte[]) json, last));
         } else if (message instanceof WebSocketPacket) {
             return sendPacket((WebSocketPacket) message);
         } else {
-            return sendPacket(new WebSocketPacket(getSendConvert(), mapconvable, message, last));
+            Convert convert = getSendConvert();
+            return sendPacket(new WebSocketPacket(convert.isBinary() ? FrameType.BINARY : FrameType.TEXT, convert.convertToBytes(json), last));
         }
     }
 
@@ -237,10 +221,11 @@ public abstract class WebSocket {
      * @return 0表示成功, 非0表示错误码
      */
     public final CompletableFuture send(Convert convert, Object message, boolean last) {
+        final Convert c = convert == null ? getSendConvert() : convert;
         if (message instanceof CompletableFuture) {
-            return ((CompletableFuture) message).thenCompose((json) -> sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, false, json, last)));
+            return ((CompletableFuture) message).thenCompose((json) -> sendPacket(new WebSocketPacket(c.isBinary() ? FrameType.BINARY : FrameType.TEXT, c.convertToBytes(json), last)));
         }
-        return sendPacket(new WebSocketPacket(convert == null ? getSendConvert() : convert, false, message, last));
+        return sendPacket(new WebSocketPacket(c.isBinary() ? FrameType.BINARY : FrameType.TEXT, c.convertToBytes(message), last));
     }
 
     /**
@@ -251,12 +236,12 @@ public abstract class WebSocket {
      * @return 0表示成功, 非0表示错误码
      */
     CompletableFuture sendPacket(WebSocketPacket packet) {
-        if (this._runner == null) {
+        if (this._writeHandler == null) {
             if (delayPackets == null) delayPackets = new ArrayList<>();
             delayPackets.add(packet);
             return CompletableFuture.completedFuture(RETCODE_DEAYSEND);
         }
-        CompletableFuture rs = this._runner.sendMessage(packet);
+        CompletableFuture rs = this._writeHandler.send(packet);
         if (_engine.logger.isLoggable(Level.FINER) && packet != WebSocketPacket.DEFAULT_PING_PACKET) {
             _engine.logger.finer("userid:" + getUserid() + " send websocket message(" + packet + ")" + " on " + this);
         }
@@ -874,7 +859,7 @@ public abstract class WebSocket {
      * @return long
      */
     public long getLastSendTime() {
-        return this._runner == null ? 0 : this._runner.lastSendTime;
+        return this.lastSendTime;
     }
 
     /**
@@ -883,7 +868,7 @@ public abstract class WebSocket {
      * @return long
      */
     public long getLastReadTime() {
-        return this._runner == null ? 0 : this._runner.lastReadTime;
+        return this.lastReadTime;
     }
 
     /**
@@ -892,7 +877,7 @@ public abstract class WebSocket {
      * @return long
      */
     public long getLastPingTime() {
-        return this.pingtime;
+        return this.lastPingTime;
     }
 
     /**
@@ -901,9 +886,22 @@ public abstract class WebSocket {
     public final void close() {
         if (this.deflater != null) this.deflater.end();
         if (this.inflater != null) this.inflater.end();
-        if (this._runner != null) {
-            CompletableFuture future = this._runner.closeRunner(CLOSECODE_SERVERCLOSE, "user close");
-            if (future != null) future.join();
+        CompletableFuture future = kill(CLOSECODE_SERVERCLOSE, "user close");
+        if (future != null) future.join();
+    }
+
+    //closeRunner
+    CompletableFuture kill(int code, String reason) {
+        if (closed) return null;
+        synchronized (this) {
+            if (closed) return null;
+            closed = true;
+            if (_channel == null) return null;
+            CompletableFuture future = _engine.removeLocalThenDisconnect(this);
+            _channel.dispose();
+            CompletableFuture closeFuture = onClose(code, reason);
+            if (closeFuture == null) return future;
+            return CompletableFuture.allOf(future, closeFuture);
         }
     }
 
@@ -913,11 +911,11 @@ public abstract class WebSocket {
      * @return boolean
      */
     public final boolean isClosed() {
-        return this._runner != null ? this._runner.closed : true;
+        return this.closed;
     }
 
     @Override
     public String toString() {
-        return this.getUserid() + "@" + _remoteAddr;
+        return this.getUserid() + "@" + _remoteAddr + "@" + Objects.hashCode(this);
     }
 }
diff --git a/src/org/redkale/net/http/WebSocketEngine.java b/src/org/redkale/net/http/WebSocketEngine.java
index 2dacc1653..4f6f45580 100644
--- a/src/org/redkale/net/http/WebSocketEngine.java
+++ b/src/org/redkale/net/http/WebSocketEngine.java
@@ -7,7 +7,6 @@ package org.redkale.net.http;
 
 import static org.redkale.net.http.WebSocketServlet.DEFAILT_LIVEINTERVAL;
 import java.io.*;
-import java.nio.ByteBuffer;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
@@ -110,7 +109,7 @@ public class WebSocketEngine {
         if (props != null) this.wsmaxbody = props.getIntValue(WEBPARAM__WSMAXBODY, this.wsmaxbody);
         if (scheduler != null) return;
         this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> {
-            final Thread t = new Thread(r, engineid + "-WebSocket-LiveInterval-Thread");
+            final Thread t = new Thread(r, "Redkale-" + engineid + "-WebSocket-LiveInterval-Thread");
             t.setDaemon(true);
             return t;
         });
@@ -119,7 +118,7 @@ public class WebSocketEngine {
         scheduler.scheduleWithFixedDelay(() -> {
             try {
                 long now = System.currentTimeMillis();
-                getLocalWebSockets().stream().filter(x -> (now - x.getLastReadTime()) > intervalms).forEach(x -> x.sendPing());
+                getLocalWebSockets().stream().filter(x -> ((now - x.getLastReadTime()) > intervalms && (now - x.getLastSendTime()) > intervalms)).forEach(x -> x.sendPing());
             } catch (Throwable t) {
                 logger.log(Level.SEVERE, "WebSocketEngine schedule(interval=" + liveinterval + "s) ping error", t);
             }
@@ -150,7 +149,7 @@ public class WebSocketEngine {
     }
 
     @Comment("从WebSocketEngine删除指定WebSocket")
-    CompletableFuture removeLocalThenClose(WebSocket socket) {
+    CompletableFuture removeLocalThenDisconnect(WebSocket socket) {
         Serializable userid = socket._userid;
         if (userid == null) return null; //尚未登录成功
         if (single) {
@@ -164,7 +163,7 @@ public class WebSocketEngine {
                 list.remove(socket);
                 if (list.isEmpty()) {
                     websockets2.remove(userid);
-                    if (node != null) return node.disconnect(userid);
+                    return node.disconnect(userid);
                 }
             }
         }
@@ -229,65 +228,65 @@ public class WebSocketEngine {
         if (message instanceof CompletableFuture) {
             return ((CompletableFuture) message).thenCompose((json) -> WebSocketEngine.this.broadcastLocalMessage(predicate, json, last));
         }
-        final boolean more = (!(message instanceof WebSocketPacket) || ((WebSocketPacket) message).sendBuffers == null);
-        if (more) {
-            Supplier bufferSupplier = null;
-            Consumer bufferConsumer = null;
-            //此处的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, false, message, last));
-            //packet.setSendBuffers(packet.encode(context.getBufferSupplier(), context.getBufferConsumer(), cryptor));
-            CompletableFuture future = null;
-            if (single) {
-                for (WebSocket websocket : websockets.values()) {
-                    if (predicate != null && !predicate.test(websocket)) continue;
-                    if (bufferSupplier == null) {
-                        bufferSupplier = websocket.getBufferSupplier();
-                        bufferConsumer = websocket.getBufferConsumer();
-                        packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
-                    }
-                    future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
-                }
-            } else {
-                for (List list : websockets2.values()) {
-                    for (WebSocket websocket : list) {
-                        if (predicate != null && !predicate.test(websocket)) continue;
-                        if (bufferSupplier == null) {
-                            bufferSupplier = websocket.getBufferSupplier();
-                            bufferConsumer = websocket.getBufferConsumer();
-                            packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
-                        }
-                        future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
-                    }
-                }
+//        final boolean more = (!(message instanceof WebSocketPacket) || ((WebSocketPacket) message).sendBuffers == null);
+//        if (more) {
+//            Supplier bufferSupplier = null;
+//            Consumer bufferConsumer = null;
+//            //此处的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));
+//            //packet.setSendBuffers(packet.encode(context.getBufferSupplier(), context.getBufferConsumer(), cryptor));
+//            CompletableFuture future = null;
+//            if (single) {
+//                for (WebSocket websocket : websockets.values()) {
+//                    if (predicate != null && !predicate.test(websocket)) continue;
+//                    if (bufferSupplier == null) {
+//                        bufferSupplier = websocket.getBufferSupplier();
+//                        bufferConsumer = websocket.getBufferConsumer();
+//                        packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
+//                    }
+//                    future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
+//                }
+//            } else {
+//                for (List list : websockets2.values()) {
+//                    for (WebSocket websocket : list) {
+//                        if (predicate != null && !predicate.test(websocket)) continue;
+//                        if (bufferSupplier == null) {
+//                            bufferSupplier = websocket.getBufferSupplier();
+//                            bufferConsumer = websocket.getBufferConsumer();
+//                            packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
+//                        }
+//                        future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
+//                    }
+//                }
+//            }
+//            final Consumer bufferConsumer0 = bufferConsumer;
+//            if (future != null) future.whenComplete((rs, ex) -> {
+//                    if (packet.sendBuffers != null && bufferConsumer0 != null) {
+//                        for (ByteBuffer buffer : packet.sendBuffers) {
+//                            bufferConsumer0.accept(buffer);
+//                        }
+//                    }
+//                });
+//            return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
+//        } else {
+        CompletableFuture 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);
             }
-            final Consumer bufferConsumer0 = bufferConsumer;
-            if (future != null) future.whenComplete((rs, ex) -> {
-                    if (packet.sendBuffers != null && bufferConsumer0 != null) {
-                        for (ByteBuffer buffer : packet.sendBuffers) {
-                            bufferConsumer0.accept(buffer);
-                        }
-                    }
-                });
-            return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
         } else {
-            CompletableFuture future = null;
-            if (single) {
-                for (WebSocket websocket : websockets.values()) {
+            for (List 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);
                 }
-            } else {
-                for (List 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);
-                    }
-                }
             }
-            return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
         }
+        return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
+        //}
     }
 
     @Comment("给指定用户组发送消息")
@@ -305,69 +304,69 @@ public class WebSocketEngine {
         if (message instanceof CompletableFuture) {
             return ((CompletableFuture) message).thenCompose((json) -> WebSocketEngine.this.sendLocalMessage(json, last, userids));
         }
-        final boolean more = (!(message instanceof WebSocketPacket) || ((WebSocketPacket) message).sendBuffers == null) && userids.length > 1;
-        if (more) {
-            Supplier bufferSupplier = null;
-            Consumer bufferConsumer = null;
-            //此处的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, false, message, last));
-            //packet.encode(context.getBufferSupplier(), context.getBufferConsumer(), cryptor);
-            CompletableFuture future = null;
-            if (single) {
-                for (Serializable userid : userids) {
-                    WebSocket websocket = websockets.get(userid);
-                    if (websocket == null) continue;
-                    if (bufferSupplier == null) {
-                        bufferSupplier = websocket.getBufferSupplier();
-                        bufferConsumer = websocket.getBufferConsumer();
-                        packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
-                    }
-                    future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
-                }
-            } else {
-                for (Serializable userid : userids) {
-                    List list = websockets2.get(userid);
-                    if (list == null) continue;
-                    for (WebSocket websocket : list) {
-                        if (bufferSupplier == null) {
-                            bufferSupplier = websocket.getBufferSupplier();
-                            bufferConsumer = websocket.getBufferConsumer();
-                            packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
-                        }
-                        future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
-                    }
-                }
+//        final boolean more = userids.length > 1;
+//        if (more) {
+//            Supplier bufferSupplier = null;
+//            Consumer bufferConsumer = null;
+//            //此处的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));
+//            //packet.encode(context.getBufferSupplier(), context.getBufferConsumer(), cryptor);
+//            CompletableFuture future = null;
+//            if (single) {
+//                for (Serializable userid : userids) {
+//                    WebSocket websocket = websockets.get(userid);
+//                    if (websocket == null) continue;
+//                    if (bufferSupplier == null) {
+//                        bufferSupplier = websocket.getBufferSupplier();
+//                        bufferConsumer = websocket.getBufferConsumer();
+//                        packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
+//                    }
+//                    future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
+//                }
+//            } else {
+//                for (Serializable userid : userids) {
+//                    List list = websockets2.get(userid);
+//                    if (list == null) continue;
+//                    for (WebSocket websocket : list) {
+//                        if (bufferSupplier == null) {
+//                            bufferSupplier = websocket.getBufferSupplier();
+//                            bufferConsumer = websocket.getBufferConsumer();
+//                            packet.encodePacket(bufferSupplier, bufferConsumer, cryptor);
+//                        }
+//                        future = future == null ? websocket.sendPacket(packet) : future.thenCombine(websocket.sendPacket(packet), (a, b) -> a | (Integer) b);
+//                    }
+//                }
+//            }
+//            final Consumer bufferConsumer0 = bufferConsumer;
+//            if (future != null) future.whenComplete((rs, ex) -> {
+//                    if (packet.sendBuffers != null && bufferConsumer0 != null) {
+//                        for (ByteBuffer buffer : packet.sendBuffers) {
+//                            bufferConsumer0.accept(buffer);
+//                        }
+//                    }
+//                });
+//            return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
+//        } else {
+        CompletableFuture future = null;
+        if (single) {
+            for (Serializable userid : userids) {
+                WebSocket websocket = websockets.get(userid);
+                if (websocket == null) continue;
+                future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
             }
-            final Consumer bufferConsumer0 = bufferConsumer;
-            if (future != null) future.whenComplete((rs, ex) -> {
-                    if (packet.sendBuffers != null && bufferConsumer0 != null) {
-                        for (ByteBuffer buffer : packet.sendBuffers) {
-                            bufferConsumer0.accept(buffer);
-                        }
-                    }
-                });
-            return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
         } else {
-            CompletableFuture future = null;
-            if (single) {
-                for (Serializable userid : userids) {
-                    WebSocket websocket = websockets.get(userid);
-                    if (websocket == null) continue;
+            for (Serializable userid : userids) {
+                List list = websockets2.get(userid);
+                if (list == null) continue;
+                for (WebSocket websocket : list) {
                     future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
                 }
-            } else {
-                for (Serializable userid : userids) {
-                    List list = websockets2.get(userid);
-                    if (list == null) continue;
-                    for (WebSocket websocket : list) {
-                        future = future == null ? websocket.send(message, last) : future.thenCombine(websocket.send(message, last), (a, b) -> a | (Integer) b);
-                    }
-                }
             }
-            return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
         }
+        return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
+        //}
     }
 
     @Comment("给指定WebSocket连接用户发起操作指令")
diff --git a/src/org/redkale/net/http/WebSocketNode.java b/src/org/redkale/net/http/WebSocketNode.java
index 1fc2f7122..b6b0a0bff 100644
--- a/src/org/redkale/net/http/WebSocketNode.java
+++ b/src/org/redkale/net/http/WebSocketNode.java
@@ -31,10 +31,10 @@ import org.redkale.util.*;
 public abstract class WebSocketNode {
 
     @Comment("存储用户ID的key前缀")
-    public static final String SOURCE_SNCP_USERID_PREFIX = "sncpws_uid:";
+    public static final String WS_SOURCE_KEY_USERID_PREFIX = "sncpws_uid:";
 
     @Comment("存储当前SNCP节点列表的key")
-    public static final String SOURCE_SNCP_NODES_KEY = "sncpws_nodes";
+    public static final String WS_SOURCE_KEY_NODES = "sncpws_nodes";
 
     protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
 
@@ -42,7 +42,7 @@ public abstract class WebSocketNode {
     @Resource(name = Application.RESNAME_SNCP_ADDR)
     protected InetSocketAddress localSncpAddress;  //为SncpServer的服务address
 
-    protected WebSocketAddress wsaddress;
+    protected WebSocketAddress wsNodeAddress;
 
     protected String name;
 
@@ -71,17 +71,17 @@ public abstract class WebSocketNode {
     public void init(AnyValue conf) {
         this.tryAcquireSeconds = Integer.getInteger("WebSocketNode.tryAcquireSeconds", 12);
 
-        if (source != null && "memory".equals(source.getType())) {
-            source.initValueType(WebSocketAddress.class);
-        }
         if (localEngine != null) {
             int wsthreads = localEngine.wsthreads;
             if (wsthreads == 0) wsthreads = Runtime.getRuntime().availableProcessors() * 8;
             if (wsthreads > 0) this.semaphore = new Semaphore(wsthreads);
         }
-        String mqtopic = this.messageAgent == null ? null : this.messageAgent.generateSncpReqTopic((Service)this);
+        String mqtopic = this.messageAgent == null ? null : this.messageAgent.generateSncpReqTopic((Service) this);
         if (mqtopic != null || this.localSncpAddress != null) {
-            this.wsaddress = new WebSocketAddress(mqtopic, localSncpAddress);
+            this.wsNodeAddress = new WebSocketAddress(mqtopic, localSncpAddress);
+        }
+        if (source != null && wsNodeAddress != null) {
+            source.appendSetItem(WS_SOURCE_KEY_NODES, WebSocketAddress.class, this.wsNodeAddress);
         }
     }
 
@@ -103,8 +103,8 @@ public abstract class WebSocketNode {
         if (this.localEngine == null) return;
         //关掉所有本地本地WebSocket
         this.localEngine.getLocalWebSockets().forEach(g -> g.close());
-        if (source != null && wsaddress != null) {
-            source.removeSetItem(SOURCE_SNCP_NODES_KEY, WebSocketAddress.class, this.wsaddress);
+        if (source != null && wsNodeAddress != null) {
+            source.removeSetItem(WS_SOURCE_KEY_NODES, WebSocketAddress.class, this.wsNodeAddress);
         }
     }
 
@@ -118,6 +118,8 @@ public abstract class WebSocketNode {
 
     protected abstract CompletableFuture broadcastAction(@RpcTargetTopic String topic, @RpcTargetAddress InetSocketAddress targetAddress, WebSocketAction action);
 
+    protected abstract CompletableFuture getUserSize(@RpcTargetTopic String topic, @RpcTargetAddress InetSocketAddress targetAddress);
+
     protected abstract CompletableFuture connect(Serializable userid, WebSocketAddress wsaddr);
 
     protected abstract CompletableFuture disconnect(Serializable userid, WebSocketAddress wsaddr);
@@ -130,18 +132,18 @@ public abstract class WebSocketNode {
 
     //--------------------------------------------------------------------------------
     final CompletableFuture connect(final Serializable userid) {
-        if (logger.isLoggable(Level.FINEST)) logger.finest(this.wsaddress + " receive websocket connect event (" + userid + " on " + (this.localEngine == null ? null : this.localEngine.getEngineid()) + ").");
-        return connect(userid, this.wsaddress);
+        if (logger.isLoggable(Level.FINEST)) logger.finest(wsNodeAddress + " receive websocket connect event (" + userid + " on " + (this.localEngine == null ? null : this.localEngine.getEngineid()) + ").");
+        return connect(userid, wsNodeAddress);
     }
 
     final CompletableFuture disconnect(final Serializable userid) {
-        if (logger.isLoggable(Level.FINEST)) logger.finest(this.wsaddress + " receive websocket disconnect event (" + userid + " on " + (this.localEngine == null ? null : this.localEngine.getEngineid()) + ").");
-        return disconnect(userid, this.wsaddress);
+        if (logger.isLoggable(Level.FINEST)) logger.finest(wsNodeAddress + " receive websocket disconnect event (" + userid + " on " + (this.localEngine == null ? null : this.localEngine.getEngineid()) + ").");
+        return disconnect(userid, wsNodeAddress);
     }
 
     final CompletableFuture changeUserid(Serializable olduserid, final Serializable newuserid) {
-        if (logger.isLoggable(Level.FINEST)) logger.finest(this.wsaddress + " receive websocket changeUserid event (from " + olduserid + " to " + newuserid + " on " + (this.localEngine == null ? null : this.localEngine.getEngineid()) + ").");
-        return changeUserid(olduserid, newuserid, this.wsaddress);
+        if (logger.isLoggable(Level.FINEST)) logger.finest(wsNodeAddress + " receive websocket changeUserid event (from " + olduserid + " to " + newuserid + " on " + (this.localEngine == null ? null : this.localEngine.getEngineid()) + ").");
+        return changeUserid(olduserid, newuserid, wsNodeAddress);
     }
 
     public final String getName() {
@@ -180,12 +182,12 @@ public abstract class WebSocketNode {
     public CompletableFuture> getRpcNodeAddresses(final Serializable userid) {
         if (this.source != null) {
             tryAcquireSemaphore();
-            CompletableFuture> result = this.source.getCollectionAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class);
+            CompletableFuture> result = this.source.getCollectionAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class);
             if (semaphore != null) result.whenComplete((r, e) -> releaseSemaphore());
             return result;
         }
         List rs = new ArrayList<>();
-        rs.add(this.wsaddress);
+        rs.add(this.wsNodeAddress);
         return CompletableFuture.completedFuture(rs);
     }
 
@@ -213,21 +215,43 @@ public abstract class WebSocketNode {
         });
     }
 
+//    public CompletableFuture getUserSize() {
+//        if (this.localEngine != null && this.source == null) {
+//            return CompletableFuture.completedFuture(this.localEngine.getLocalUserSize());
+//        }
+//        tryAcquireSemaphore();
+//        CompletableFuture> listFuture = this.source.queryKeysStartsWithAsync(WS_SOURCE_KEY_USERID_PREFIX);
+//        CompletableFuture rs = listFuture.thenApply(v -> v.size());
+//        if (semaphore != null) rs.whenComplete((r, e) -> releaseSemaphore());
+//        return rs;
+//    }
     /**
      * 获取在线用户总数
      *
      *
      * @return boolean
      */
+    @Local
     public CompletableFuture getUserSize() {
-        if (this.localEngine != null && this.source == null) {
+        if (this.localEngine != null && this.source == null) { //本地模式且没有分布式
             return CompletableFuture.completedFuture(this.localEngine.getLocalUserSize());
         }
+        CompletableFuture localFuture = this.localEngine == null ? null : CompletableFuture.completedFuture(this.localEngine.getLocalUserSize());
         tryAcquireSemaphore();
-        CompletableFuture> listFuture = this.source.queryKeysStartsWithAsync(SOURCE_SNCP_USERID_PREFIX);
-        CompletableFuture rs = listFuture.thenApply(v -> v.size());
-        if (semaphore != null) rs.whenComplete((r, e) -> releaseSemaphore());
-        return rs;
+        CompletableFuture> addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_NODES, WebSocketAddress.class);
+        if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
+        CompletableFuture remoteFuture = addrsFuture.thenCompose((Collection addrs) -> {
+            if (logger.isLoggable(Level.FINEST)) logger.finest("websocket getUserSize on " + addrs);
+            if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
+            CompletableFuture future = null;
+            for (WebSocketAddress addr : addrs) {
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
+                future = future == null ? remoteNode.getUserSize(addr.getTopic(), addr.getAddr())
+                    : future.thenCombine(remoteNode.getUserSize(addr.getTopic(), addr.getAddr()), (a, b) -> a + b);
+            }
+            return future == null ? CompletableFuture.completedFuture(0) : future;
+        });
+        return localFuture == null ? remoteFuture : localFuture.thenCombine(remoteFuture, (a, b) -> a + b);
     }
 
     /**
@@ -241,8 +265,8 @@ public abstract class WebSocketNode {
             return CompletableFuture.completedFuture(new LinkedHashSet<>(this.localEngine.getLocalUserSet().stream().map(x -> String.valueOf(x)).collect(Collectors.toList())));
         }
         tryAcquireSemaphore();
-        CompletableFuture> listFuture = this.source.queryKeysStartsWithAsync(SOURCE_SNCP_USERID_PREFIX);
-        CompletableFuture> rs = listFuture.thenApply(v -> new LinkedHashSet<>(v.stream().map(x -> x.substring(SOURCE_SNCP_USERID_PREFIX.length())).collect(Collectors.toList())));
+        CompletableFuture> listFuture = this.source.queryKeysStartsWithAsync(WS_SOURCE_KEY_USERID_PREFIX);
+        CompletableFuture> rs = listFuture.thenApply(v -> new LinkedHashSet<>(v.stream().map(x -> x.substring(WS_SOURCE_KEY_USERID_PREFIX.length())).collect(Collectors.toList())));
         if (semaphore != null) rs.whenComplete((r, e) -> releaseSemaphore());
         return rs;
     }
@@ -266,14 +290,14 @@ public abstract class WebSocketNode {
         }
         //远程节点关闭
         tryAcquireSemaphore();
-        CompletableFuture> addrsFuture = source.getCollectionAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class);
+        CompletableFuture> addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class);
         if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
         CompletableFuture remoteFuture = addrsFuture.thenCompose((Collection addrs) -> {
             //if (logger.isLoggable(Level.FINEST)) logger.finest("websocket found userid:" + userid + " on " + addrs);
             if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(false);
             CompletableFuture future = null;
             for (WebSocketAddress addr : addrs) {
-                if (addr == null || addr.equals(wsaddress)) continue;
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
                 future = future == null ? remoteNode.existsWebSocket(userid, addr.getTopic(), addr.getAddr())
                     : future.thenCombine(remoteNode.existsWebSocket(userid, addr.getTopic(), addr.getAddr()), (a, b) -> a | b);
             }
@@ -306,7 +330,7 @@ public abstract class WebSocketNode {
         if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(false);
         CompletableFuture future = null;
         for (WebSocketAddress addr : addrs) {
-            if (addr == null || addr.equals(wsaddress)) continue;
+            if (addr == null || addr.equals(wsNodeAddress)) continue;
             future = future == null ? remoteNode.existsWebSocket(userAddress.userid(), addr.getTopic(), addr.getAddr())
                 : future.thenCombine(remoteNode.existsWebSocket(userAddress.userid(), addr.getTopic(), addr.getAddr()), (a, b) -> a | b);
         }
@@ -349,7 +373,7 @@ public abstract class WebSocketNode {
         CompletableFuture> addrsFuture;
         if (userAddress == null) {
             tryAcquireSemaphore();
-            addrsFuture = source.getCollectionAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class);
+            addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class);
             if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
         } else {
             Collection addrs = userAddress.addresses();
@@ -366,7 +390,7 @@ public abstract class WebSocketNode {
             if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
             CompletableFuture future = null;
             for (WebSocketAddress addr : addrs) {
-                if (addr == null || addr.equals(wsaddress)) continue;
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
                 future = future == null ? remoteNode.forceCloseWebSocket(userid, addr.getTopic(), addr.getAddr())
                     : future.thenCombine(remoteNode.forceCloseWebSocket(userid, addr.getTopic(), addr.getAddr()), (a, b) -> a + b);
             }
@@ -518,7 +542,7 @@ public abstract class WebSocketNode {
             return sendMessage(convert, message0, last, useraddrs);
         }
         if (message0 instanceof CompletableFuture) return ((CompletableFuture) message0).thenApply(msg -> sendMessage(convert, msg, last, userids));
-        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));
+        final Object message = (convert == null || message0 instanceof WebSocketPacket) ? message0 : ((convert instanceof TextConvert) ? new WebSocketPacket(WebSocketPacket.FrameType.TEXT, ((TextConvert) convert).convertToBytes(message0), last) : new WebSocketPacket(WebSocketPacket.FrameType.BINARY, ((BinaryConvert) convert).convertToBytes(message0), last));
         if (this.localEngine != null && this.source == null) { //本地模式且没有分布式
             return this.localEngine.sendLocalMessage(message, last, userids);
         }
@@ -530,7 +554,7 @@ public abstract class WebSocketNode {
             String[] keys = new String[userids.length];
             final Map keyuser = new HashMap<>();
             for (int i = 0; i < userids.length; i++) {
-                keys[i] = SOURCE_SNCP_USERID_PREFIX + userids[i];
+                keys[i] = WS_SOURCE_KEY_USERID_PREFIX + userids[i];
                 keyuser.put(keys[i], userids[i]);
             }
             tryAcquireSemaphore();
@@ -612,17 +636,17 @@ public abstract class WebSocketNode {
         //远程节点发送消息
         final Object remoteMessage = formatRemoteMessage(message);
         tryAcquireSemaphore();
-        CompletableFuture> addrsFuture = source.getCollectionAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class);
+        CompletableFuture> addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class);
         if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
         CompletableFuture remoteFuture = addrsFuture.thenCompose((Collection addrs) -> {
             if (addrs == null || addrs.isEmpty()) {
                 if (logger.isLoggable(Level.FINER)) logger.finer("websocket not found userid:" + userid + " on any node ");
                 return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
             }
-            if (logger.isLoggable(Level.FINEST)) logger.finest("websocket(localaddr=" + wsaddress + ") found userid:" + userid + " on " + addrs);
+            if (logger.isLoggable(Level.FINEST)) logger.finest("websocket(localaddr=" + wsNodeAddress + ") found userid:" + userid + " on " + addrs);
             CompletableFuture future = null;
             for (WebSocketAddress addr : addrs) {
-                if (addr == null || addr.equals(wsaddress)) continue;
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
                 future = future == null ? remoteNode.sendMessage(addr.getTopic(), addr.getAddr(), remoteMessage, last, userid)
                     : future.thenCombine(remoteNode.sendMessage(addr.getTopic(), addr.getAddr(), remoteMessage, last, userid), (a, b) -> a | b);
             }
@@ -636,7 +660,7 @@ public abstract class WebSocketNode {
         if (logger.isLoggable(Level.FINEST) && this.localEngine == null) { //只打印远程模式的
             logger.finest("websocket want send message {userids:" + JsonConvert.root().convertTo(userids) + ", sncpaddr:" + addr + ", content:" + (message instanceof WebSocketPacket ? ((WebSocketPacket) message).toSimpleString() : (message instanceof CharSequence ? message : JsonConvert.root().convertTo(message))) + "} from locale node to " + ((this.localEngine != null) ? "locale" : "remote") + " engine");
         }
-        if (Objects.equals(addr, this.wsaddress)) {
+        if (Objects.equals(addr, this.wsNodeAddress)) {
             return this.localEngine == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : localEngine.sendLocalMessage(message, last, userids);
         }
         if (this.source == null || this.remoteNode == null) {
@@ -787,14 +811,14 @@ public abstract class WebSocketNode {
         final Object remoteMessage = formatRemoteMessage(message);
         CompletableFuture localFuture = this.localEngine == null ? null : this.localEngine.broadcastLocalMessage(wsrange, message, last);
         tryAcquireSemaphore();
-        CompletableFuture> addrsFuture = source.getCollectionAsync(SOURCE_SNCP_NODES_KEY, WebSocketAddress.class);
+        CompletableFuture> addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_NODES, WebSocketAddress.class);
         if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
         CompletableFuture remoteFuture = addrsFuture.thenCompose((Collection addrs) -> {
             if (logger.isLoggable(Level.FINEST)) logger.finest("websocket broadcast message (" + remoteMessage + ") on " + addrs);
             if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
             CompletableFuture future = null;
             for (WebSocketAddress addr : addrs) {
-                if (addr == null || addr.equals(wsaddress)) continue;
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
                 future = future == null ? remoteNode.broadcastMessage(addr.getTopic(), addr.getAddr(), wsrange, remoteMessage, last)
                     : future.thenCombine(remoteNode.broadcastMessage(addr.getTopic(), addr.getAddr(), wsrange, remoteMessage, last), (a, b) -> a | b);
             }
@@ -817,14 +841,14 @@ public abstract class WebSocketNode {
         }
         CompletableFuture localFuture = this.localEngine == null ? null : this.localEngine.broadcastLocalAction(action);
         tryAcquireSemaphore();
-        CompletableFuture> addrsFuture = source.getCollectionAsync(SOURCE_SNCP_NODES_KEY, WebSocketAddress.class);
+        CompletableFuture> addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_NODES, WebSocketAddress.class);
         if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
         CompletableFuture remoteFuture = addrsFuture.thenCompose((Collection addrs) -> {
             if (logger.isLoggable(Level.FINEST)) logger.finest("websocket broadcast action (" + action + ") on " + addrs);
             if (addrs == null || addrs.isEmpty()) return CompletableFuture.completedFuture(0);
             CompletableFuture future = null;
             for (WebSocketAddress addr : addrs) {
-                if (addr == null || addr.equals(wsaddress)) continue;
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
                 future = future == null ? remoteNode.broadcastAction(addr.getTopic(), addr.getAddr(), action)
                     : future.thenCombine(remoteNode.broadcastAction(addr.getTopic(), addr.getAddr(), action), (a, b) -> a | b);
             }
@@ -862,7 +886,7 @@ public abstract class WebSocketNode {
             String[] keys = new String[userids.length];
             final Map keyuser = new HashMap<>();
             for (int i = 0; i < userids.length; i++) {
-                keys[i] = SOURCE_SNCP_USERID_PREFIX + userids[i];
+                keys[i] = WS_SOURCE_KEY_USERID_PREFIX + userids[i];
                 keyuser.put(keys[i], userids[i]);
             }
             tryAcquireSemaphore();
@@ -936,7 +960,7 @@ public abstract class WebSocketNode {
         }
         //远程节点发送操作
         tryAcquireSemaphore();
-        CompletableFuture> addrsFuture = source.getCollectionAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class);
+        CompletableFuture> addrsFuture = source.getCollectionAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class);
         if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
         CompletableFuture remoteFuture = addrsFuture.thenCompose((Collection addrs) -> {
             if (addrs == null || addrs.isEmpty()) {
@@ -946,7 +970,7 @@ public abstract class WebSocketNode {
             if (logger.isLoggable(Level.FINEST)) logger.finest("websocket(localaddr=" + localSncpAddress + ") found userid:" + userid + " on " + addrs);
             CompletableFuture future = null;
             for (WebSocketAddress addr : addrs) {
-                if (addr == null || addr.equals(wsaddress)) continue;
+                if (addr == null || addr.equals(wsNodeAddress)) continue;
                 future = future == null ? remoteNode.sendAction(addr.getTopic(), addr.getAddr(), action, userid)
                     : future.thenCombine(remoteNode.sendAction(addr.getTopic(), addr.getAddr(), action, userid), (a, b) -> a | b);
             }
@@ -959,7 +983,7 @@ public abstract class WebSocketNode {
         if (logger.isLoggable(Level.FINEST) && this.localEngine == null) { //只打印远程模式的
             logger.finest("websocket want send action {userids:" + JsonConvert.root().convertTo(userids) + ", sncpaddr:" + addr + ", action:" + action + " from locale node to " + ((this.localEngine != null) ? "locale" : "remote") + " engine");
         }
-        if (Objects.equals(addr, this.wsaddress)) {
+        if (Objects.equals(addr, this.wsNodeAddress)) {
             return this.localEngine == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : localEngine.sendLocalAction(action, userids);
         }
         if (this.source == null || this.remoteNode == null) {
diff --git a/src/org/redkale/net/http/WebSocketPacket.java b/src/org/redkale/net/http/WebSocketPacket.java
index 5a677e9ca..4cc22bc0e 100644
--- a/src/org/redkale/net/http/WebSocketPacket.java
+++ b/src/org/redkale/net/http/WebSocketPacket.java
@@ -5,17 +5,9 @@
  */
 package org.redkale.net.http;
 
-import org.redkale.util.Utility;
 import java.io.*;
-import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.function.*;
-import java.util.logging.*;
-import java.util.zip.*;
-import org.redkale.convert.*;
-import org.redkale.net.Cryptor;
-import org.redkale.util.*;
+import org.redkale.net.http.WebSocketPacket.FrameType;
 
 /**
  *
@@ -30,8 +22,6 @@ public final class WebSocketPacket {
 
     static final WebSocketPacket NONE = new WebSocketPacket();
 
-    private static final byte[] EOM_BYTES = new byte[]{0, 0, -1, -1};
-
     public static final WebSocketPacket DEFAULT_PING_PACKET = new WebSocketPacket(FrameType.PING, new byte[0]);
 
     public static enum MessageType {
@@ -67,114 +57,39 @@ public final class WebSocketPacket {
 
     protected FrameType type;
 
-    protected String payload;
-
-    protected byte[] bytes;
+    protected byte[] payload;
 
     protected boolean last = true;
 
-    //---------------发送------------------------
-    Object sendJson;
-
-    Convert sendConvert;
-
-    boolean sendMapconvable;
-
-    ByteBuffer[] sendBuffers;
-
-    //---------------接收------------------------
-    MessageType receiveType;
-
-    boolean receiveCompress;
-
-    int receiveCount;
-
-    int receiveLength;
-
-    Object receiveMessage;
-
-    ConvertMask receiveMasker;
-
-    ByteBuffer[] receiveBuffers;
-
     public WebSocketPacket() {
     }
 
-    public WebSocketPacket(String payload) {
-        this(payload, true);
-    }
-
-    public WebSocketPacket(String payload, boolean fin) {
-        this.type = FrameType.TEXT;
-        this.payload = payload;
-        this.last = fin;
-    }
-
-    public WebSocketPacket(byte[] data) {
-        this(FrameType.BINARY, data, true);
-    }
-
-    public WebSocketPacket(byte[] data, boolean fin) {
-        this(FrameType.BINARY, data, fin);
-    }
-
     public WebSocketPacket(FrameType type, byte[] data) {
         this(type, data, true);
     }
 
-    public WebSocketPacket(FrameType type, byte[] data, boolean fin) {
+    public WebSocketPacket(FrameType type, byte[] data, boolean last) {
         this.type = type;
-        if (type == FrameType.TEXT) {
-            this.payload = new String(Utility.decodeUTF8(data));
-        } else {
-            this.bytes = data;
-        }
-        this.last = fin;
+        this.payload = data;
+        this.last = last;
     }
 
-    public WebSocketPacket(Serializable message, boolean fin) {
+    public WebSocketPacket(Serializable message, boolean last) {
         boolean bin = message != null && message.getClass() == byte[].class;
         if (bin) {
             this.type = FrameType.BINARY;
-            this.bytes = (byte[]) message;
+            this.payload = (byte[]) message;
         } else {
             this.type = FrameType.TEXT;
-            this.payload = String.valueOf(message);
+            this.payload = String.valueOf(message).getBytes(StandardCharsets.UTF_8);
         }
-        this.last = fin;
+        this.last = last;
     }
 
-    WebSocketPacket(Convert convert, boolean mapconvable, Object json, boolean fin) {
-        this.type = (convert == null || !convert.isBinary()) ? FrameType.TEXT : FrameType.BINARY;
-        this.sendConvert = convert;
-        this.sendMapconvable = mapconvable;
-        this.sendJson = json;
-        this.last = fin;
-        if (mapconvable && !(json instanceof Object[])) throw new IllegalArgumentException();
-    }
-
-    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;
-    }
-
-    public String getPayload() {
+    public byte[] getPayload() {
         return payload;
     }
 
-    public byte[] getBytes() {
-        return bytes;
-    }
-
     public boolean isLast() {
         return last;
     }
@@ -187,435 +102,22 @@ public final class WebSocketPacket {
         this.type = type;
     }
 
-    public void setPayload(String payload) {
+    public void setPayload(byte[] payload) {
         this.payload = payload;
     }
 
-    public void setBytes(byte[] bytes) {
-        this.bytes = bytes;
-    }
-
     public void setLast(boolean last) {
         this.last = last;
     }
 
     public String toSimpleString() {
-        if (payload != null) return payload;
-        if (bytes != null) return "byte[" + bytes.length + "]";
-        return this.toString();
+        if (payload == null) return null;
+        return type == FrameType.TEXT ? new String(payload, StandardCharsets.UTF_8) : ("bytes(" + payload.length + ")");
     }
 
     @Override
     public String toString() {
-        return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + (payload != null ? (", payload=" + payload) : "") + (bytes != null ? (", bytes=[" + bytes.length + ']') : (receiveLength > 0 ? (", receivemsg=" + (receiveMessage instanceof byte[] ? ("byte[" + ((byte[]) receiveMessage).length + "]") : receiveMessage)) : "")) + (sendJson != null ? (", json=" + (sendMapconvable ? Utility.ofMap((Object[]) sendJson) : sendJson)) : "") + "]";
-    }
-
-    /**
-     * 消息编码
-     *
-     * @param supplier Supplier
-     * @param cryptor  Cryptor
-     *
-     * @return ByteBuffer[]
-     */
-    void encodePacket(final Supplier supplier, final Consumer consumer, final Cryptor cryptor) {
-        final byte opcode = (byte) (this.type.getValue() | 0x80);
-        if (this.sendConvert != null) {
-            Supplier newsupplier = new Supplier() {
-
-                private ByteBuffer buf = supplier.get();
-
-                @Override
-                public ByteBuffer get() {
-                    if (buf != null) {
-                        ByteBuffer rs = buf;
-                        rs.position(6);
-                        this.buf = null;
-                        return rs;
-                    }
-                    return supplier.get();
-                }
-            };
-            ByteBuffer[] buffers = this.sendMapconvable ? this.sendConvert.convertMapTo(newsupplier, (Object[]) sendJson) : this.sendConvert.convertTo(newsupplier, sendJson);
-            if (cryptor != null) buffers = cryptor.encrypt(buffers, supplier, consumer);
-            int len = 0;
-            for (ByteBuffer buf : buffers) {
-                len += buf.remaining();
-            }
-            int contentLength = len - 6;
-            ByteBuffer firstbuf = buffers[0];
-            if (contentLength <= 0x7D) { //125
-                firstbuf.put(4, opcode);
-                firstbuf.put(5, (byte) contentLength);
-                firstbuf.position(4);
-            } else if (contentLength <= 0xFFFF) {
-                firstbuf.put(2, opcode);
-                firstbuf.put(3, (byte) 0x7E); //126
-                firstbuf.putChar(4, (char) contentLength);
-                firstbuf.position(2);
-            } else {
-                firstbuf.put(0, opcode);
-                firstbuf.put(1, (byte) 0x7F); //127
-                firstbuf.putInt(2, contentLength);
-            }
-            this.sendBuffers = buffers;
-            return;
-        }
-
-        ByteBuffer buffer = supplier.get();  //确保ByteBuffer的capacity不能小于128
-        byte[] content = content();
-        if (cryptor != null) {
-            ByteBuffer[] ss = new ByteBuffer[]{ByteBuffer.wrap(content)};
-            ByteBuffer[] bs = cryptor.encrypt(ss, supplier, consumer);
-            if (bs != ss) {
-                int r = 0;
-                for (ByteBuffer bb : bs) {
-                    r += bb.remaining();
-                }
-                content = new byte[r];
-                int index = 0;
-                for (ByteBuffer bb : bs) {
-                    int re = bb.remaining();
-                    bb.get(content, index, re);
-                    index += re;
-                }
-                for (ByteBuffer bb : bs) {
-                    consumer.accept(bb);
-                }
-            }
-        }
-        final int len = content.length;
-        if (len <= 0x7D) { //125
-            buffer.put(opcode);
-            buffer.put((byte) len);
-            buffer.put(content);
-            buffer.flip();
-            this.sendBuffers = new ByteBuffer[]{buffer};
-            return;
-        }
-        if (len <= 0xFFFF) { // 65535
-            buffer.put(opcode);
-            buffer.put((byte) 0x7E); //126
-            buffer.putChar((char) len);
-        } else {
-            buffer.put(opcode);
-            buffer.put((byte) 0x7F); //127
-            buffer.putInt(len);
-        }
-        int start = buffer.remaining();
-        int pend = len - buffer.remaining();
-        if (pend <= 0) {
-            buffer.put(content);
-            buffer.flip();
-            this.sendBuffers = new ByteBuffer[]{buffer};
-            return;
-        }
-        buffer.put(content, 0, buffer.remaining());
-        buffer.flip();
-        final int capacity = buffer.capacity();
-        final ByteBuffer[] buffers = new ByteBuffer[(pend / capacity) + 1 + ((pend % capacity) > 0 ? 1 : 0)];
-        buffers[0] = buffer;
-        for (int i = 1; i < buffers.length; i++) {
-            ByteBuffer buf = supplier.get();
-            buf.put(content, start, Math.min(pend, capacity));
-            buf.flip();
-            buffers[i] = buf;
-            start += capacity;
-            pend -= capacity;
-        }
-        this.sendBuffers = buffers;
-    }
-
-//    public static void main(String[] args) throws Throwable {
-//        byte[] mask = new byte[]{(byte) 0x8f, (byte) 0xf8, (byte) 0x6d, (byte) 0x94};
-//        ByteBuffer buffer = ByteBuffer.wrap(new byte[]{(byte) 0x67, (byte) 0x47, (byte) 0xf4, (byte) 0x70, (byte) 0x37, (byte) 0x52, (byte) 0x8b, (byte) 0x0c, (byte) 0x20, (byte) 0x1e, (byte) 0xdb, (byte) 0x1c, (byte) 0x69, (byte) 0x79, (byte) 0xc2});
-//        ConvertMask masker = new ConvertMask() {
-//            private int index = 0;
-//
-//            public byte unmask(byte value) {
-//                return (byte) (value ^ mask[index++ % 4]);
-//            }
-//        };
-//        String rs = JsonConvert.root().convertFrom(String.class, masker, buffer);
-//        System.out.println(rs);
-//    }
-    /**
-     *
-     * @param webSocket  WebSocket
-     * @param readBuffer ByteBuffer
-     *
-     * @return boolean 已接收完返回true, 需要继续接收body返回false;
-     */
-    boolean receiveBody(final Logger logger, WebSocketRunner runner, WebSocket webSocket, ByteBuffer readBuffer) {
-        final boolean debug = false; //调试开关
-        int need = receiveLength - receiveCount;
-        final int remain = readBuffer.remaining();
-        boolean over = remain >= need;
-        this.receiveBuffers = Utility.append(this.receiveBuffers, readBuffer);
-        if (debug) logger.finest("receiveBody: receiveLength=" + receiveLength + ", this.receiveCount=" + this.receiveCount + ", readBuffer=" + remain);
-        if (over) {
-            this.receiveCount = this.receiveLength;
-            parseReceiveMessage(logger, runner, webSocket, this.receiveBuffers);
-        } else {
-            this.receiveCount += remain;
-        }
-        return over;
-    }
-
-    /**
-     * 消息解码  
- * - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-------+-+-------------+-------------------------------+ - * |F|R|R|R| opcode|M| Payload len | Extended payload length | - * |I|S|S|S| (4) |A| (7) | (16/64) | - * |N|V|V|V| |S| | (if payload len==126/127) | - * | |1|2|3| |K| | | - * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - * | Extended payload length continued, if payload len == 127 | - * + - - - - - - - - - - - - - - - +-------------------------------+ - * | |Masking-key, if MASK set to 1 | - * +-------------------------------+-------------------------------+ - * | Masking-key (continued) | Payload Data | - * +-------------------------------- - - - - - - - - - - - - - - - + - * : Payload Data continued : - * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - * | Payload Data continued | - * +-----------------------------------------------------------------------+ - * - * @param buffer - * @param exbuffers - * - * @return 返回NONE表示Buffer内容不够; 返回this表示解析完成或部分解析完成;返回null表示解析异常; - */ - WebSocketPacket decodePacket(final Logger logger, final WebSocketRunner runner, final WebSocket webSocket, final int wsmaxbody, - final AbstractMap.SimpleEntry halfBytes, final ByteBuffer buffer) { - //开始 - final boolean debug = true; //调试开关 - if (debug) logger.log(Level.FINEST, "read websocket message's length = " + buffer.remaining()); - if (!buffer.hasRemaining()) return NONE; - if (buffer.remaining() < 2) { - byte[] bs = new byte[buffer.remaining()]; - buffer.get(bs); - halfBytes.setValue(bs); - return NONE; - } - final byte opcode = buffer.get(); //第一个字节 - this.last = (opcode & 0B1000_0000) != 0; - this.type = FrameType.valueOf(opcode & 0B0000_1111); - - //0x00 表示一个后续帧 - //0x01 表示一个文本帧 - //0x02 表示一个二进制帧 - //0x03-07 为以后的非控制帧保留 - //0x8 表示一个连接关闭 - //0x9 表示一个ping - //0xA 表示一个pong - //0x0B-0F 为以后的控制帧保留 - final boolean control = (opcode & 0B0000_1000) != 0; //是否控制帧 - this.receiveCompress = !control && webSocket.inflater != null && (opcode & 0B0100_0000) != 0; //rsv1 为 1 - - if (type == FrameType.CLOSE) { - if (debug) logger.log(Level.FINEST, " receive close command from websocket client"); - } - if (type == null) { - logger.log(Level.SEVERE, " receive unknown frametype(opcode=" + (opcode & 0B0000_1111) + ") from websocket client"); - } - final boolean checkrsv = false;//暂时不校验 - if (checkrsv && (opcode & 0B0111_0000) != 0) { - if (debug) logger.log(Level.FINE, "rsv1 rsv2 rsv3 must be 0, but not (" + opcode + ")"); - return null; //rsv1 rsv2 rsv3 must be 0 - } - final byte crcode = buffer.get(); //第二个字节 - - byte lengthCode = crcode; - final boolean masked = (lengthCode & 0x80) == 0x80; - if (masked) lengthCode ^= 0x80; //mask - if (debug) logger.log(Level.FINEST, " receive type=" + type + ", control=" + control + ", opcode=" + opcode + ", masked=" + masked + ", remaining=" + buffer.remaining()); - - //判断Buffer剩余内容够不够基本信息的创建 - int minBufferLength = ((lengthCode <= 0x7D) ? 0 : (lengthCode == 0x7E ? 2 : 4)) + (masked ? 4 : 0); - if (buffer.remaining() < minBufferLength) { - byte[] bs = new byte[2 + buffer.remaining()]; - bs[0] = opcode; - bs[1] = crcode; - buffer.get(bs, 2, buffer.remaining()); - halfBytes.setValue(bs); - if (debug) logger.log(Level.FINEST, " receive not enough buffer,length =" + bs.length); - return NONE; - } - - int length; - if (lengthCode <= 0x7D) { //125 - length = lengthCode; - } else { - if (control) { - if (debug) logger.log(Level.FINE, " receive control command from websocket client"); - return null; - } - if (lengthCode == 0x7E) {//0x7E=126 - length = (int) buffer.getChar(); - } else if (lengthCode == 0x7F) {//0x7E=127 - length = (int) buffer.getLong(); - } else { - length = buffer.getInt(); - } - } - if (length > wsmaxbody && wsmaxbody > 0) { - logger.log(Level.WARNING, "message length (" + length + ") too big, must less " + wsmaxbody + ""); - return null; - } - this.receiveLength = length; - if (debug) logger.finest("this.receiveLength: " + length + ", code=" + lengthCode + ", last=" + last); - if (masked) { - final byte[] masks = new byte[4]; - buffer.get(masks); - this.receiveMasker = new ConvertMask() { - - private int index = 0; - - @Override - public byte unmask(byte value) { - return (byte) (value ^ masks[index++ % 4]); - } - }; - } - if (buffer.remaining() >= this.receiveLength) { //内容足够, 可以解析 - this.parseReceiveMessage(logger, runner, webSocket, buffer); - this.receiveCount = this.receiveLength; - } else { - this.receiveCount = buffer.remaining(); - this.receiveBuffers = buffer.hasRemaining() ? new ByteBuffer[]{buffer} : null; - } - return this; - } - - void parseReceiveMessage(final Logger logger, WebSocketRunner runner, WebSocket webSocket, ByteBuffer... buffers) { - if (webSocket._engine.cryptor != null) { - HttpContext context = webSocket._engine.context; - buffers = webSocket._engine.cryptor.decrypt(buffers, webSocket._channel.getBufferSupplier(), webSocket._channel.getBufferConsumer()); - } - FrameType selfType = this.type; - final boolean series = selfType == FrameType.SERIES; - if (series) { - selfType = runner.currSeriesMergeFrameType; - this.type = selfType; - } else if (!this.last && (selfType == FrameType.TEXT || selfType == FrameType.BINARY)) { - runner.currSeriesMergeFrameType = selfType; - } - - if (selfType == FrameType.TEXT) { - Convert textConvert = webSocket.getTextConvert(); - if (textConvert == null || (!runner.mergemsg && (series || !this.last))) { - this.receiveMessage = new String(this.getReceiveBytes(webSocket, selfType, buffers), StandardCharsets.UTF_8); - this.receiveType = MessageType.STRING; - } else { - if (this.last || !runner.mergemsg) { - if (runner.currSeriesMergeMessage == null) { - if (this.receiveCompress) { - this.receiveMessage = textConvert.convertFrom(webSocket._messageTextType, this.getReceiveBytes(webSocket, selfType, buffers)); - } else { - this.receiveMessage = textConvert.convertFrom(webSocket._messageTextType, this.receiveMasker, buffers); - } - } else { - runner.currSeriesMergeMessage.write(this.getReceiveBytes(webSocket, selfType, buffers)); - try { - this.receiveMessage = textConvert.convertFrom(webSocket._messageTextType, runner.currSeriesMergeMessage.getBytes()); - } finally { - runner.currSeriesMergeMessage = null; - } - } - } else { - if (runner.currSeriesMergeMessage == null) runner.currSeriesMergeMessage = new ByteArray(); - runner.currSeriesMergeMessage.write(this.getReceiveBytes(webSocket, selfType, buffers)); - this.receiveMessage = MESSAGE_NIL; - } - this.receiveCount = this.receiveLength; - this.receiveType = MessageType.OBJECT; - } - } else if (selfType == FrameType.BINARY) { - Convert binaryConvert = webSocket.getBinaryConvert(); - if (binaryConvert == null || (!runner.mergemsg && (series || !this.last))) { - this.receiveMessage = this.getReceiveBytes(webSocket, selfType, buffers); - this.receiveType = MessageType.BYTES; - } else { - if (this.last || !runner.mergemsg) { - if (runner.currSeriesMergeMessage == null) { - if (this.receiveCompress) { - this.receiveMessage = binaryConvert.convertFrom(webSocket._messageTextType, this.getReceiveBytes(webSocket, selfType, buffers)); - } else { - this.receiveMessage = binaryConvert.convertFrom(webSocket._messageTextType, this.receiveMasker, buffers); - } - } else { - runner.currSeriesMergeMessage.write(this.getReceiveBytes(webSocket, selfType, buffers)); - try { - this.receiveMessage = binaryConvert.convertFrom(webSocket._messageTextType, runner.currSeriesMergeMessage.getBytes()); - } finally { - runner.currSeriesMergeMessage = null; - } - } - } else { - if (runner.currSeriesMergeMessage == null) runner.currSeriesMergeMessage = new ByteArray(); - runner.currSeriesMergeMessage.write(this.getReceiveBytes(webSocket, selfType, buffers)); - this.receiveMessage = MESSAGE_NIL; - } - this.receiveCount = this.receiveLength; - this.receiveType = MessageType.OBJECT; - } - } else if (selfType == FrameType.PING) { - this.receiveMessage = this.getReceiveBytes(webSocket, selfType, buffers); - this.receiveType = MessageType.BYTES; - } else if (selfType == FrameType.PONG) { - this.receiveMessage = this.getReceiveBytes(webSocket, selfType, buffers); - this.receiveType = MessageType.BYTES; - } else if (selfType == FrameType.CLOSE) { - this.receiveMessage = this.getReceiveBytes(webSocket, selfType, buffers); - this.receiveType = MessageType.BYTES; - } - } - - boolean isReceiveFinished() { - return this.receiveLength <= this.receiveCount; - } - - byte[] getReceiveBytes(WebSocket webSocket, FrameType frameType, ByteBuffer... buffers) { - final int length = this.receiveLength; - if (length == 0) return new byte[0]; - byte[] bs = new byte[length]; - int index = 0; - for (ByteBuffer buf : buffers) { - int r = Math.min(buf.remaining(), length - index); - buf.get(bs, index, r); - index += r; - if (index >= length) break; - } - this.receiveCount = index; - ConvertMask mask = this.receiveMasker; - if (mask != null) { - for (int i = 0; i < bs.length; i++) { - bs[i] = mask.unmask(bs[i]); - } - } - if (bs.length > 6 && this.receiveCompress && (frameType == FrameType.BINARY || frameType == FrameType.TEXT || frameType == FrameType.SERIES)) { - Inflater inflater = webSocket.inflater; - inflater.reset(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(bs.length); - inflater.setInput(Utility.append(bs, EOM_BYTES)); - byte[] buff = new byte[1024]; - try { - while (!inflater.finished()) { - int count = inflater.inflate(buff); - if (count == 0) break; - baos.write(buff, 0, count); - } - return baos.toByteArray(); - } catch (Exception ex) { - ex.printStackTrace(); - return bs; - } - } - return bs; + return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + ", payload=" + toSimpleString() + "]"; } } diff --git a/src/org/redkale/net/http/WebSocketReadHandler.java b/src/org/redkale/net/http/WebSocketReadHandler.java new file mode 100644 index 000000000..c54d7a53e --- /dev/null +++ b/src/org/redkale/net/http/WebSocketReadHandler.java @@ -0,0 +1,363 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net.http; + +import java.util.logging.*; +import java.nio.ByteBuffer; +import java.nio.channels.CompletionHandler; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import org.redkale.convert.Convert; +import static org.redkale.net.http.WebSocket.*; +import org.redkale.net.http.WebSocketPacket.FrameType; +import org.redkale.util.ByteArray; + +/** + * + * @author zhangjx + */ +public class WebSocketReadHandler implements CompletionHandler { + + protected final HttpContext context; + + protected final WebSocket webSocket; + + protected final BiConsumer restMessageConsumer; //主要供RestWebSocket使用 + + protected final Logger logger; + + protected final boolean debug; + + protected final List currPackets = new ArrayList<>(); + + protected ByteArray currSeriesMergeMessageBytes; + + protected FrameType currSeriesMergeMessageType; + + protected final ByteArray halfFrameBytes = new ByteArray(); + + protected byte halfFrameOpcode; + + protected byte halfFrameCrcode; + + protected byte[] halfFrameMasks; + + protected int halfFrameStart; + + protected int halfFrameLength = -1; + + public WebSocketReadHandler(HttpContext context, WebSocket webSocket, BiConsumer messageConsumer) { + this.context = context; + this.restMessageConsumer = messageConsumer; + this.webSocket = webSocket; + this.logger = context.getLogger(); + this.debug = context.getLogger().isLoggable(Level.FINEST); + } + + public void startRead() { + CompletableFuture connectFuture = webSocket.onConnected(); + if (connectFuture == null) { + webSocket._channel.read(this); + } else { + connectFuture.whenComplete((r, t) -> { + webSocket._channel.read(this); + }); + } + } + + /** + * 消息解码
+ * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|M| Payload len | Extended payload length | + * |I|S|S|S| (4) |A| (7) | (16/64) | + * |N|V|V|V| |S| | (if payload len==126/127) | + * | |1|2|3| |K| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | |Masking-key, if MASK set to 1 | + * +-------------------------------+-------------------------------+ + * | Masking-key (continued) | Payload Data | + * +-------------------------------- - - - - - - - - - - - - - - - + + * : Payload Data continued : + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * | Payload Data continued | + * +-----------------------------------------------------------------------+ + * + * @param realbuf + * + */ + protected void readDecode(final ByteBuffer realbuf) { + if (debug) logger.log(Level.FINEST, "read websocket message's length = " + realbuf.remaining()); + if (!realbuf.hasRemaining()) return; + ByteBuffer buffer = realbuf; + byte frameOpcode; + byte frameCrcode; + byte[] frameMasks; + int frameLength; + //System.out.println("realbuf读到的长度: " + realbuf.remaining() + ", halfFrameBytes=" + (halfFrameBytes == null ? -1 : halfFrameBytes.length())); + if (halfFrameBytes.length() > 0) { //存在半包 + int remain = realbuf.remaining(); + if (halfFrameLength == -1) { + int cha = 2 - halfFrameBytes.length(); + if (remain < cha) { //还是不够2字节 + halfFrameBytes.put(realbuf); + return; + } + final byte opcode0 = halfFrameBytes.get(0); //第一个字节 + final byte crcode0 = halfFrameBytes.get(1); //第二个字节 + byte lengthCode = crcode0; + final boolean masked = (lengthCode & 0x80) == 0x80; + if (masked) lengthCode ^= 0x80; //mask + int minLength = ((lengthCode <= 0x7D) ? 0 : (lengthCode == 0x7E ? 2 : 8)) + (masked ? 4 : 0); + cha = minLength + 2 - halfFrameBytes.length(); + if (remain < cha) { //还不够读取长度值和mask + halfFrameBytes.put(realbuf); + return; + } + int length; + if (lengthCode <= 0x7D) { //125 长度<=125 + length = lengthCode; + } else if (lengthCode == 0x7E) {//0x7E=126 长度:126~65535 + length = (int) realbuf.getChar(); + } else if (lengthCode == 0x7F) {//0x7E=127 长度>65535 + length = (int) realbuf.getLong(); + } else { + throw new RuntimeException("read webSocket packet lengthCode (" + (int) lengthCode + ") error"); + } + byte[] masks0 = null; + if (masked) { + masks0 = new byte[4]; + realbuf.get(masks0); + } + this.halfFrameOpcode = opcode0; + this.halfFrameCrcode = crcode0; + this.halfFrameMasks = masks0; + this.halfFrameStart = 2 + minLength; + this.halfFrameLength = length; + } + //此时必然有halfFrameLength + int bulen = halfFrameLength + halfFrameStart - halfFrameBytes.length(); //还差多少字节 + if (bulen > remain) { //不够,继续读取 + halfFrameBytes.put(realbuf); + return; + } + halfFrameBytes.put(realbuf, bulen); + //此时halfFrameBytes是完整的frame数据 + buffer = ByteBuffer.wrap(halfFrameBytes.content(), halfFrameStart, halfFrameBytes.length()); + + frameOpcode = this.halfFrameOpcode; + frameCrcode = this.halfFrameCrcode; + frameMasks = this.halfFrameMasks; + frameLength = this.halfFrameLength; + + this.halfFrameBytes.clear(); + this.halfFrameLength = -1; + } else { //第一次就只有几个字节buffer + int remain = realbuf.remaining(); + if (remain < 2) { + this.halfFrameBytes.put(realbuf); + return; + } + final byte opcode0 = realbuf.get(); //第一个字节 + final byte crcode0 = realbuf.get(); //第二个字节 + byte lengthCode = crcode0; + final boolean masked = (lengthCode & 0x80) == 0x80; + if (masked) lengthCode ^= 0x80; //mask + int minLength = ((lengthCode <= 0x7D) ? 0 : (lengthCode == 0x7E ? 2 : 8)) + (masked ? 4 : 0); + if (remain < minLength + 2) { //还不够读取长度值 + this.halfFrameBytes.put(opcode0, crcode0); + this.halfFrameBytes.put(realbuf); + return; + } + int length; + if (lengthCode <= 0x7D) { //125 长度<=125 + length = lengthCode; + } else if (lengthCode == 0x7E) {//0x7E=126 长度:126~65535 + length = (int) realbuf.getChar(); + } else if (lengthCode == 0x7F) {//0x7E=127 长度>65535 + length = (int) realbuf.getLong(); + } else { + throw new RuntimeException("read webSocket packet lengthCode (" + (int) lengthCode + ") error"); + } + byte[] masks0 = null; + if (masked) { + masks0 = new byte[4]; + realbuf.get(masks0); + } + int bulen = length + minLength + 2; //还差多少字节 + if (bulen > remain) { //不够,继续读取 + this.halfFrameBytes.put(opcode0, crcode0); + if (lengthCode <= 0x7D) { //125 长度<=125 + this.halfFrameBytes.put((byte) length); + } else if (lengthCode == 0x7E) {//0x7E=126 长度:126~65535 + this.halfFrameBytes.putChar((char) length); + } else if (lengthCode == 0x7F) {//0x7E=127 长度>65535 + this.halfFrameBytes.putLong((long) length); + } + if (masks0 != null) this.halfFrameBytes.put(masks0); + this.halfFrameBytes.put(realbuf); + this.halfFrameOpcode = opcode0; + this.halfFrameCrcode = crcode0; + this.halfFrameMasks = masks0; + this.halfFrameStart = 2 + minLength; + this.halfFrameLength = length; + return; + } + frameOpcode = opcode0; + frameCrcode = crcode0; + frameMasks = masks0; + frameLength = length; + } + + final boolean last = (frameOpcode & 0B1000_0000) != 0; + final FrameType type = FrameType.valueOf(frameOpcode & 0B0000_1111); + //0x00 表示一个后续帧 + //0x01 表示一个文本帧 + //0x02 表示一个二进制帧 + //0x03-07 为以后的非控制帧保留 + //0x8 表示一个连接关闭 + //0x9 表示一个ping + //0xA 表示一个pong + //0x0B-0F 为以后的控制帧保留 + final boolean control = (frameOpcode & 0B0000_1000) != 0; //是否控制帧 + // this.receiveCompress = !control && webSocket.inflater != null && (opcode & 0B0100_0000) != 0; //rsv1 为 1 + + if (type == FrameType.CLOSE) { + if (debug) logger.log(Level.FINEST, " receive close command from websocket client"); + } + if (type == null) { + logger.log(Level.SEVERE, " receive unknown frametype(opcode=" + (frameOpcode & 0B0000_1111) + ") from websocket client"); + } + final boolean checkrsv = false;//暂时不校验 + if (checkrsv && (frameOpcode & 0B0111_0000) != 0) { + if (debug) logger.log(Level.FINE, "rsv1 rsv2 rsv3 must be 0, but not (" + frameOpcode + ")"); + return; //rsv1 rsv2 rsv3 must be 0 + } + byte[] content = new byte[frameLength]; + if (frameLength > 0) { + buffer.get(content); + int mi = 0; + if (frameMasks != null) { + for (int i = 0; i < content.length; i++) { + content[i] = (byte) (content[i] ^ frameMasks[mi++ % 4]); + } + } + } + if (!last && (type == FrameType.TEXT || type == FrameType.BINARY)) { + this.currSeriesMergeMessageBytes = new ByteArray(); + this.currSeriesMergeMessageBytes.put(content); + this.currSeriesMergeMessageType = type; + } else if (type == FrameType.SERIES) { + this.currSeriesMergeMessageBytes.put(content); + } else if (last && this.currSeriesMergeMessageBytes != null) { + this.currSeriesMergeMessageBytes.put(content); + byte[] bs = this.currSeriesMergeMessageBytes.getBytes(); + FrameType t = this.currSeriesMergeMessageType; + this.currSeriesMergeMessageBytes = null; + this.currSeriesMergeMessageType = null; + currPackets.add(new WebSocketPacket(t, bs, last)); + } else { + currPackets.add(new WebSocketPacket(type, content, last)); + } + buffer = realbuf; + while (buffer.hasRemaining()) { + readDecode(realbuf); + } + } + + @Override + public void completed(Integer count, ByteBuffer readBuffer) { + if (count < 1) { + if (debug) logger.log(Level.FINEST, "WebSocket(" + webSocket + ") abort on read buffer count, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds"); + webSocket.kill(CLOSECODE_ILLPACKET, "read buffer count is " + count); + return; + } + try { + webSocket.lastReadTime = System.currentTimeMillis(); + currPackets.clear(); + + readBuffer.flip(); + readDecode(readBuffer); + readBuffer.clear(); + webSocket._channel.setReadBuffer(readBuffer); + try { + //消息处理 + for (final WebSocketPacket packet : currPackets) { + if (packet.type == FrameType.TEXT) { + try { + Convert convert = webSocket.getTextConvert(); + if (restMessageConsumer != null) { //主要供RestWebSocket使用 + restMessageConsumer.accept(webSocket, convert.convertFrom(webSocket._messageTextType, packet.getPayload())); + } else { + webSocket.onMessage(packet.getPayload() == null ? null : new String(packet.getPayload(), StandardCharsets.UTF_8), packet.last); + } + } catch (Throwable e) { + logger.log(Level.SEVERE, "WebSocket onTextMessage error (" + packet + ")", e); + } + } else if (packet.type == FrameType.BINARY) { + try { + Convert convert = webSocket.getBinaryConvert(); + if (restMessageConsumer != null) { //主要供RestWebSocket使用 + restMessageConsumer.accept(webSocket, convert.convertFrom(webSocket._messageTextType, packet.getPayload())); + } else { + webSocket.onMessage(packet.getPayload(), packet.last); + } + } catch (Throwable e) { + logger.log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e); + } + } else if (packet.type == FrameType.PING) { + try { + webSocket.onPing(packet.getPayload()); + } catch (Exception e) { + logger.log(Level.SEVERE, "WebSocket onPing error (" + packet + ")", e); + } + } else if (packet.type == FrameType.PONG) { + try { + //if (debug) logger.log(Level.FINEST, "WebSocket onMessage by PONG FrameType : " + packet); + webSocket.onPong(packet.getPayload()); + } catch (Exception e) { + logger.log(Level.SEVERE, "WebSocket(" + webSocket + ") onPong error (" + packet + ")", e); + } + } else if (packet.type == FrameType.CLOSE) { + webSocket.initiateClosed = true; + if (debug) logger.log(Level.FINEST, "WebSocket(" + webSocket + ") onMessage by CLOSE FrameType : " + packet); + webSocket.kill(CLOSECODE_CLIENTCLOSE, "received CLOSE frame-type message"); + return; + } else { + logger.log(Level.WARNING, "WebSocket(" + webSocket + ") onMessage by unknown FrameType : " + packet); + webSocket.kill(CLOSECODE_ILLPACKET, "received unknown frame-type message"); + return; + } + } + } catch (Throwable t) { + logger.log(Level.WARNING, "WebSocket(" + webSocket + ") onMessage error", t); + } + webSocket._channel.read(this); + } catch (Exception e) { + logger.log(Level.WARNING, "WebSocket(" + webSocket + ") onMessage by received error", e); + webSocket.kill(CLOSECODE_WSEXCEPTION, "websocket-received error"); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment2) { + if (webSocket.initiateClosed) return; + if (exc != null) { + if (debug) context.getLogger().log(Level.FINEST, "WebSocket(" + webSocket + ") read WebSocketPacket failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc); + webSocket.kill(CLOSECODE_WSEXCEPTION, "read websocket-packet failed"); + } else { + webSocket.kill(CLOSECODE_WSEXCEPTION, "decode websocket-packet error"); + } + } + +} diff --git a/src/org/redkale/net/http/WebSocketRunner.java b/src/org/redkale/net/http/WebSocketRunner.java deleted file mode 100644 index 6284dbaef..000000000 --- a/src/org/redkale/net/http/WebSocketRunner.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.redkale.net.http; - -import static org.redkale.net.http.WebSocket.*; -import org.redkale.net.http.WebSocketPacket.FrameType; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.*; -import java.util.AbstractMap.SimpleEntry; -import java.util.concurrent.*; -import java.util.function.BiConsumer; -import java.util.logging.*; -import org.redkale.util.ByteArray; - -/** - * WebSocket的消息接收发送器, 一个WebSocket对应一个WebSocketRunner - * - *

- * 详情见: https://redkale.org - * - * @author zhangjx - */ -class WebSocketRunner implements Runnable { - - private final WebSocketEngine engine; - - private final WebSocket webSocket; - - protected final HttpContext context; - - protected final boolean mergemsg; - - protected final Semaphore writeSemaphore = new Semaphore(1); - - protected final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); - - volatile boolean closed = false; - - FrameType currSeriesMergeFrameType; - - ByteArray currSeriesMergeMessage; - - private final BiConsumer restMessageConsumer; //主要供RestWebSocket使用 - - protected long lastSendTime; - - protected long lastReadTime; - - WebSocketRunner(HttpContext context, WebSocket webSocket, BiConsumer messageConsumer) { - this.context = context; - this.engine = webSocket._engine; - this.webSocket = webSocket; - this.mergemsg = webSocket._engine.mergemsg; - this.restMessageConsumer = messageConsumer; - } - - @Override - public void run() { - final boolean debug = context.getLogger().isLoggable(Level.FINEST); - final WebSocketRunner self = this; - try { - CompletableFuture connectfFuture = webSocket.onConnected(); - if (connectfFuture != null) connectfFuture.join(); - webSocket._channel.setReadTimeoutSeconds(300); //读取超时5分钟 - if (webSocket._channel.isOpen()) { - final int wsmaxbody = webSocket._engine.wsmaxbody; - webSocket._channel.read(new CompletionHandler() { - - //尚未解析完的数据包 - private WebSocketPacket unfinishPacket; - - //当接收的数据流长度大于ByteBuffer长度时, 则需要额外的ByteBuffer 辅助; - private final List exBuffers = new ArrayList<>(); - - private final SimpleEntry halfBytes = new SimpleEntry("", null); - - @Override - public void completed(Integer count, ByteBuffer readBuffer) { - if (count < 1) { - if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner(userid=" + webSocket.getUserid() + ") abort on read buffer count, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds"); - closeRunner(CLOSECODE_ILLPACKET, "read buffer count is " + count); - return; - } - try { - lastReadTime = System.currentTimeMillis(); - readBuffer.flip(); - - WebSocketPacket onePacket = null; - if (unfinishPacket != null) { - if (unfinishPacket.receiveBody(context.getLogger(), self, webSocket, readBuffer)) { //已经接收完毕 - onePacket = unfinishPacket; - unfinishPacket = null; - for (ByteBuffer b : exBuffers) { - webSocket._channel.offerBuffer(b); - } - exBuffers.clear(); - } else { //需要继续接收, 此处不能回收readBuffer - webSocket._channel.read(this); - return; - } - } - - final List packets = new ArrayList<>(); - if (onePacket != null) packets.add(onePacket); - try { - while (true) { - WebSocketPacket packet = new WebSocketPacket().decodePacket(context.getLogger(), self, webSocket, wsmaxbody, halfBytes, readBuffer); - if (packet == WebSocketPacket.NONE) break; //解析完毕但是buffer有多余字节 - if (packet != null && !packet.isReceiveFinished()) { - unfinishPacket = packet; - if (readBuffer.hasRemaining()) { - exBuffers.add(readBuffer); - } - break; - } - packets.add(packet); - if (packet == null || !readBuffer.hasRemaining()) break; - } - } catch (Exception e) { - context.getLogger().log(Level.SEVERE, "WebSocket parse message error", e); - webSocket.onOccurException(e, null); - } - //继续监听消息 - if (readBuffer.hasRemaining()) { //exBuffers缓存了 - readBuffer = webSocket._channel.pollReadBuffer(); - } else { - readBuffer.clear(); - } - if (halfBytes.getValue() != null) { - readBuffer.put(halfBytes.getValue()); - halfBytes.setValue(null); - } - webSocket._channel.setReadBuffer(readBuffer); - webSocket._channel.read(this); - - //消息处理 - for (final WebSocketPacket packet : packets) { - if (packet == null) { - if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on decode WebSocketPacket, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds"); - failed(null, readBuffer); - return; - } - if (packet.receiveMessage == WebSocketPacket.MESSAGE_NIL) continue; //last=false && mergemsg=true 的粘包 - - if (packet.type == FrameType.TEXT) { - try { - if (packet.receiveType == WebSocketPacket.MessageType.STRING) { - webSocket.onMessage((String) packet.receiveMessage, packet.last); - } else { - if (restMessageConsumer != null) { //主要供RestWebSocket使用 - restMessageConsumer.accept(webSocket, packet.receiveMessage); - } else { - webSocket.onMessage(packet.receiveMessage, packet.last); - } - } - } catch (Throwable e) { - context.getLogger().log(Level.SEVERE, "WebSocket onTextMessage error (" + packet + ")", e); - } - } else if (packet.type == FrameType.BINARY) { - try { - if (packet.receiveType == WebSocketPacket.MessageType.BYTES) { - webSocket.onMessage((byte[]) packet.receiveMessage, packet.last); - } else { - if (restMessageConsumer != null) { //主要供RestWebSocket使用 - restMessageConsumer.accept(webSocket, packet.receiveMessage); - } else { - webSocket.onMessage(packet.receiveMessage, packet.last); - } - } - } catch (Throwable e) { - context.getLogger().log(Level.SEVERE, "WebSocket onBinaryMessage error (" + packet + ")", e); - } - } else if (packet.type == FrameType.PING) { - try { - webSocket.onPing((byte[]) packet.receiveMessage); - } catch (Exception e) { - context.getLogger().log(Level.SEVERE, "WebSocket onPing error (" + packet + ")", e); - } - } else if (packet.type == FrameType.PONG) { - try { - //if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner onMessage by PONG FrameType : " + packet); - webSocket.onPong((byte[]) packet.receiveMessage); - } catch (Exception e) { - context.getLogger().log(Level.SEVERE, "WebSocket onPong error (" + packet + ")", e); - } - } else if (packet.type == FrameType.CLOSE) { - if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner onMessage by CLOSE FrameType : " + packet); - closeRunner(CLOSECODE_CLIENTCLOSE, "received CLOSE frame-type message"); - return; - } else { - context.getLogger().log(Level.WARNING, "WebSocketRunner onMessage by unknown FrameType : " + packet); - closeRunner(CLOSECODE_ILLPACKET, "received unknown frame-type message"); - return; - } - } - } catch (Exception e) { - context.getLogger().log(Level.WARNING, "WebSocketRunner(userid=" + webSocket.getUserid() + ") onMessage by received error", e); - closeRunner(CLOSECODE_WSEXCEPTION, "websocket-received error"); - } - } - - @Override - public void failed(Throwable exc, ByteBuffer attachment2) { - if (exc != null) { - if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner read WebSocketPacket failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc); - closeRunner(CLOSECODE_WSEXCEPTION, "read websocket-packet failed"); - } else { - closeRunner(CLOSECODE_WSEXCEPTION, "decode websocket-packet error"); - } - } - }); - } else { - if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort by AsyncConnection closed"); - closeRunner(RETCODE_WSOCKET_CLOSED, "webSocket channel is not opened"); - } - } catch (Throwable e) { - if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read bytes from channel, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e); - closeRunner(CLOSECODE_WSEXCEPTION, "read bytes from channel error"); - } - } - - public CompletableFuture sendMessage(WebSocketPacket packet) { - if (packet == null) return CompletableFuture.completedFuture(RETCODE_SEND_ILLPACKET); - if (closed) return CompletableFuture.completedFuture(RETCODE_WSOCKET_CLOSED); - boolean debug = context.getLogger().isLoggable(Level.FINEST); - //System.out.println("推送消息"); - final CompletableFuture futureResult = new CompletableFuture<>(); - try { - if (packet.sendBuffers == null) packet.encodePacket(webSocket._channel.getBufferSupplier(), webSocket._channel.getBufferConsumer(), webSocket._engine.cryptor); - ByteBuffer[] buffers = packet.duplicateSendBuffers(); - //if (debug) context.getLogger().log(Level.FINEST, "wsrunner.sending websocket message: " + packet); - CompletionHandler handler = new CompletionHandler() { - - private CompletableFuture future = futureResult; - - @Override - public void completed(Integer result, ByteBuffer[] attachments) { - if (attachments == null || closed) { - if (future != null) { - future.complete(RETCODE_WSOCKET_CLOSED); - future = null; - if (attachments != null) { - for (ByteBuffer buf : attachments) { - webSocket._channel.offerBuffer(buf); - } - } - } - return; - } - try { - int index = -1; - for (int i = 0; i < attachments.length; i++) { - if (attachments[i].hasRemaining()) { - index = i; - break; - } - } - if (index >= 0) { //ByteBuffer[]统一回收的可以采用此写法 - webSocket._channel.write(attachments, index, attachments.length - index, attachments, this); - return; - } - if (future != null) { - future.complete(0); - future = null; - if (attachments != null) { - for (ByteBuffer buf : attachments) { - webSocket._channel.offerBuffer(buf); - } - } - } - } catch (Exception e) { - future.complete(RETCODE_SENDEXCEPTION); - closeRunner(RETCODE_SENDEXCEPTION, "websocket send message failed on rewrite"); - context.getLogger().log(Level.WARNING, "WebSocket sendMessage abort on rewrite, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e); - } - } - - @Override - public void failed(Throwable exc, ByteBuffer[] attachments) { - future.complete(RETCODE_SENDEXCEPTION); - closeRunner(RETCODE_SENDEXCEPTION, "websocket send message failed on CompletionHandler"); - if (exc != null && context.getLogger().isLoggable(Level.FINER)) { - context.getLogger().log(Level.FINER, "WebSocket sendMessage on CompletionHandler failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc); - } - - } - }; - this.lastSendTime = System.currentTimeMillis(); - synchronized (this) { - if (writeSemaphore.tryAcquire()) { - webSocket._channel.write(buffers, buffers, handler); - } else { - writeQueue.add(new WriteEntry(buffers, handler)); - } - } - } catch (Exception t) { - futureResult.complete(RETCODE_SENDEXCEPTION); - closeRunner(RETCODE_SENDEXCEPTION, "websocket send message failed on channel.write"); - if (t != null && context.getLogger().isLoggable(Level.FINER)) { - context.getLogger().log(Level.FINER, "WebSocket sendMessage abort, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t); - } - } - int ts = webSocket._channel.getWriteTimeoutSeconds(); - return futureResult.orTimeout(ts > 0 ? ts : 6, TimeUnit.SECONDS).whenComplete((r, t) -> { - nextWrite(); - if (t != null && context.getLogger().isLoggable(Level.FINER)) { - context.getLogger().log(Level.FINER, "WebSocket sendMessage " + packet + " timeout ", t); - } - }); - } - - private void nextWrite() { - synchronized (this) { - WriteEntry entry = writeQueue.poll(); - if (entry != null) { - webSocket._channel.write(entry.writeBuffers, entry.writeBuffers, entry.writeHandler); - } else { - writeSemaphore.release(); - } - } - } - - public boolean isClosed() { - return closed; - } - - public CompletableFuture closeRunner(int code, String reason) { - if (closed) return null; - synchronized (this) { - if (closed) return null; - closed = true; - CompletableFuture future = engine.removeLocalThenClose(webSocket); - webSocket._channel.dispose(); - CompletableFuture closeFuture = webSocket.onClose(code, reason); - if (closeFuture == null) return future; - return CompletableFuture.allOf(future, closeFuture); - } - } - - private static class WriteEntry { - - ByteBuffer[] writeBuffers; - - CompletionHandler writeHandler; - - public WriteEntry(ByteBuffer[] writeBuffers, CompletionHandler writeHandler) { - this.writeBuffers = writeBuffers; - this.writeHandler = writeHandler; - } - - } -} diff --git a/src/org/redkale/net/http/WebSocketServlet.java b/src/org/redkale/net/http/WebSocketServlet.java index f23e55d10..9a8f9eea3 100644 --- a/src/org/redkale/net/http/WebSocketServlet.java +++ b/src/org/redkale/net/http/WebSocketServlet.java @@ -233,6 +233,7 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl response.finish(true); return; } + //onOpen成功或者存在delayPackets webSocket._sessionid = sessionid; request.setKeepAlive(true); byte[] bytes = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes(); @@ -245,14 +246,12 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl response.addHeader("Sec-WebSocket-Accept", Base64.getEncoder().encodeToString(bytes)); if (webSocket.deflater != null) response.addHeader("Sec-WebSocket-Extensions", "permessage-deflate"); - response.sendBody((ByteBuffer) null, null, new CompletionHandler() { - - WebSocketRunner temprunner = null; + response.sendBody((ByteBuffer) null, new CompletionHandler() { @Override public void completed(Integer result, Void attachment) { - HttpContext context = response.getContext(); - + webSocket._readHandler = new WebSocketReadHandler(response.getContext(), webSocket, restMessageConsumer); + webSocket._writeHandler = new WebSocketWriteHandler(response.getContext(), webSocket); Runnable createUseridHandler = () -> { CompletableFuture userFuture = webSocket.createUserid(); if (userFuture == null) { @@ -267,7 +266,6 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl return; } Runnable runHandler = () -> { - temprunner = null; webSocket._userid = userid; if (single && !anyuser) { WebSocketServlet.this.node.existsWebSocket(userid).whenComplete((rs, nex) -> { @@ -277,9 +275,10 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl if (oldkilled) { WebSocketServlet.this.node.localEngine.addLocal(webSocket); response.removeChannel(); - WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer); - webSocket._runner = runner; - context.runAsync(runner); + webSocket._readHandler.startRead(); +// WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer); +// webSocket._runner = runner; +// runner.run(); //context.runAsync(runner); response.finish(true); } else { //关闭新连接 response.finish(true); @@ -299,35 +298,29 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl } else { WebSocketServlet.this.node.localEngine.addLocal(webSocket); response.removeChannel(); - WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer); - webSocket._runner = runner; - context.runAsync(runner); + webSocket._readHandler.startRead(); +// WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer); +// webSocket._runner = runner; +// runner.run(); //context.runAsync(runner); response.finish(true); } }); } else { WebSocketServlet.this.node.localEngine.addLocal(webSocket); response.removeChannel(); - WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer); - webSocket._runner = runner; - context.runAsync(runner); + webSocket._readHandler.startRead(); +// WebSocketRunner runner = new WebSocketRunner(context, webSocket, restMessageConsumer); +// webSocket._runner = runner; +// runner.run(); //context.runAsync(runner); response.finish(true); } }; if (webSocket.delayPackets != null) { //存在待发送的消息 - if (temprunner == null) temprunner = new WebSocketRunner(context, webSocket, restMessageConsumer); List delayPackets = webSocket.delayPackets; webSocket.delayPackets = null; - CompletableFuture cf = null; - for (WebSocketPacket packet : delayPackets) { - if (cf == null) { - cf = temprunner.sendMessage(packet); - } else { - cf = cf.thenCombine(temprunner.sendMessage(packet), (a, b) -> a | b); - } - } + CompletableFuture cf = webSocket._writeHandler.send(delayPackets.toArray(new WebSocketPacket[delayPackets.size()])); cf.whenComplete((Integer v, Throwable t) -> { - if (userid == null || t != null || (temprunner != null && temprunner.isClosed())) { + if (userid == null || t != null) { if (t != null) logger.log(Level.FINEST, "WebSocket connect abort, Response send delayPackets abort. request = " + request, t); response.finish(true); } else { @@ -340,19 +333,11 @@ public abstract class WebSocketServlet extends HttpServlet implements Resourcabl }); }; if (webSocket.delayPackets != null) { //存在待发送的消息 - if (temprunner == null) temprunner = new WebSocketRunner(context, webSocket, restMessageConsumer); List delayPackets = webSocket.delayPackets; webSocket.delayPackets = null; - CompletableFuture cf = null; - for (WebSocketPacket packet : delayPackets) { - if (cf == null) { - cf = temprunner.sendMessage(packet); - } else { - cf = cf.thenCombine(temprunner.sendMessage(packet), (a, b) -> a | b); - } - } + CompletableFuture cf = webSocket._writeHandler.send(delayPackets.toArray(new WebSocketPacket[delayPackets.size()])); cf.whenComplete((Integer v, Throwable t) -> { - if (sessionid == null || t != null || (temprunner != null && temprunner.isClosed())) { + if (sessionid == null || t != null) { if (t != null) logger.log(Level.FINEST, "WebSocket connect abort, Response send delayPackets abort. request = " + request, t); response.finish(true); } else { diff --git a/src/org/redkale/net/http/WebSocketWriteHandler.java b/src/org/redkale/net/http/WebSocketWriteHandler.java new file mode 100644 index 000000000..198f14e45 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketWriteHandler.java @@ -0,0 +1,132 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net.http; + +import java.util.*; +import java.util.logging.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.nio.channels.CompletionHandler; +import org.redkale.util.ByteArray; +import static org.redkale.net.http.WebSocket.*; + +/** + * + * @author zhangjx + */ +public class WebSocketWriteHandler implements CompletionHandler { + + protected final HttpContext context; + + protected final WebSocket webSocket; + + protected final AtomicBoolean writePending = new AtomicBoolean(); + + protected final ByteArray writeArray = new ByteArray(); + + protected final List> respList = new ArrayList(); + + protected final ConcurrentLinkedDeque> requestQueue = new ConcurrentLinkedDeque(); + + public WebSocketWriteHandler(HttpContext context, WebSocket webSocket) { + this.context = context; + this.webSocket = webSocket; + } + + public CompletableFuture send(WebSocketPacket... packets) { + WebSocketFuture future = new WebSocketFuture<>(packets); + if (writePending.compareAndSet(false, true)) { + respList.clear(); + respList.add(future); + writeArray.clear(); + for (WebSocketPacket p : packets) { + writeEncode(p); + } + webSocket._channel.write(writeArray, this); + } else { + requestQueue.offer(future); + } + return future; + } + + @Override + public void completed(Integer result, Void attachment) { + webSocket.lastSendTime = System.currentTimeMillis(); + for (WebSocketFuture future : respList) { + future.complete(0); + } + respList.clear(); + writeArray.clear(); + WebSocketFuture req; + while ((req = requestQueue.poll()) != null) { + respList.add(req); + for (WebSocketPacket p : req.packets) { + writeEncode(p); + } + } + if (writeArray.isEmpty()) { + if (!writePending.compareAndSet(true, false)) { + completed(0, attachment); + } + } else { + webSocket._channel.write(writeArray, this); + } + } + + @Override + public void failed(Throwable exc, Void attachment) { + WebSocketFuture req; + try { + while ((req = requestQueue.poll()) != null) { + req.completeExceptionally(exc); + } + for (WebSocketFuture future : respList) { + future.completeExceptionally(exc); + } + respList.clear(); + } catch (Exception e) { + } + webSocket.kill(RETCODE_SENDEXCEPTION, "websocket send message failed on CompletionHandler"); + if (exc != null && context.getLogger().isLoggable(Level.FINER)) { + context.getLogger().log(Level.FINER, "WebSocket sendMessage on CompletionHandler failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc); + } + } + + //消息编码 + protected void writeEncode(final WebSocketPacket packet) { + final ByteArray array = writeArray; + final byte opcode = (byte) (packet.type.getValue() | 0x80); + final byte[] content = packet.getPayload(); + final int len = content.length; + if (len <= 0x7D) { //125 + array.put(opcode); + array.put((byte) len); + } else if (len <= 0xFFFF) { // 65535 + array.put(opcode); + array.put((byte) 0x7E); //126 + array.putChar((char) len); + } else { + array.put(opcode); + array.put((byte) 0x7F); //127 + array.putLong(len); + } + array.put(content); + } + + protected static class WebSocketFuture extends CompletableFuture { + + protected WebSocketPacket[] packets; + + public WebSocketFuture() { + super(); + } + + public WebSocketFuture(WebSocketPacket... packets) { + super(); + this.packets = packets; + } + } +} diff --git a/src/org/redkale/net/sncp/SncpClient.java b/src/org/redkale/net/sncp/SncpClient.java index 860314420..a54ac71ab 100644 --- a/src/org/redkale/net/sncp/SncpClient.java +++ b/src/org/redkale/net/sncp/SncpClient.java @@ -12,7 +12,6 @@ import java.nio.*; import java.nio.channels.*; import java.util.*; import java.util.concurrent.*; -import java.util.function.Supplier; import java.util.logging.*; import javax.annotation.Resource; import org.redkale.convert.bson.*; @@ -55,16 +54,12 @@ public final class SncpClient { protected final SncpAction[] actions; - protected final ExecutorService executor; - protected final MessageAgent messageAgent; protected final SncpMessageClient messageClient; protected final String topic; - protected final Supplier bufferSupplier; - @Resource protected JsonConvert jsonConvert; @@ -80,8 +75,6 @@ public final class SncpClient { public SncpClient(final String serviceName, final Class serviceTypeOrImplClass, final T service, MessageAgent messageAgent, final TransportFactory factory, final boolean remote, final Class serviceClass, final InetSocketAddress clientSncpAddress) { this.remote = remote; - this.executor = factory.getExecutor(); - this.bufferSupplier = factory.getBufferSupplier(); this.messageAgent = messageAgent; this.messageClient = messageAgent == null ? null : messageAgent.getSncpMessageClient(); this.topic = messageAgent == null ? null : messageAgent.generateSncpReqTopic(service); @@ -269,7 +262,7 @@ public final class SncpClient { final Class[] myparamclass = action.paramClass; if (action.addressSourceParamIndex >= 0) params[action.addressSourceParamIndex] = this.clientSncpAddress; if (bsonConvert == null) bsonConvert = BsonConvert.root(); - final BsonWriter writer = messageAgent == null ? bsonConvert.pollBsonWriter() : bsonConvert.pollBsonWriter(transport.getBufferSupplier()); // 将head写入 + final BsonWriter writer = bsonConvert.pollBsonWriter(); // 将head写入 writer.writeTo(DEFAULT_HEADER); for (int i = 0; i < params.length; i++) { //params 可能包含: 3 个 boolean BsonConvert bcc = bsonConvert; @@ -283,11 +276,11 @@ public final class SncpClient { final long seqid = System.nanoTime(); final DLong actionid = action.actionid; if (messageAgent != null) { //MQ模式 - final byte[] reqbytes = writer.toArray(); - fillHeader(ByteBuffer.wrap(reqbytes), seqid, actionid, reqBodyLength); + final ByteArray reqbytes = writer.toByteArray(); + fillHeader(reqbytes, seqid, actionid, reqBodyLength); String targetTopic = action.topicTargetParamIndex >= 0 ? (String) params[action.topicTargetParamIndex] : this.topic; if (targetTopic == null) targetTopic = this.topic; - MessageRecord message = messageClient.createMessageRecord(targetTopic, null, reqbytes); + MessageRecord message = messageClient.createMessageRecord(targetTopic, null, reqbytes.getBytes()); final String tt = targetTopic; if (logger.isLoggable(Level.FINER)) { message.attach(Utility.append(new Object[]{action.actionName()}, params)); @@ -327,31 +320,13 @@ public final class SncpClient { return future; } final AsyncConnection conn = conn0; - final ByteBuffer[] sendBuffers = writer.toBuffers(); - fillHeader(sendBuffers[0], seqid, actionid, reqBodyLength); + final ByteArray array = writer.toByteArray(); + fillHeader(array, seqid, actionid, reqBodyLength); - conn.write(sendBuffers, sendBuffers, new CompletionHandler() { + conn.write(array, new CompletionHandler() { @Override - public void completed(Integer result, ByteBuffer[] attachments) { - int index = -1; - for (int i = 0; i < attachments.length; i++) { - if (attachments[i].hasRemaining()) { - index = i; - break; - } else { - transport.offerBuffer(attachments[i]); - } - } - if (index == 0) { //ByteBuffer[]不统一回收的必须采用此写法分开 - conn.write(attachments, attachments, this); - return; - } else if (index > 0) { - ByteBuffer[] newattachs = new ByteBuffer[attachments.length - index]; - System.arraycopy(attachments, index, newattachs, 0, newattachs.length); - conn.write(newattachs, newattachs, this); - return; - } + public void completed(Integer result, Void attachments) { //----------------------- 读取返回结果 ------------------------------------- conn.read(new CompletionHandler() { @@ -460,7 +435,7 @@ public final class SncpClient { } @Override - public void failed(Throwable exc, ByteBuffer[] attachment) { + public void failed(Throwable exc, Void attachment) { future.completeExceptionally(new RpcRemoteException(action.method + " sncp remote exec failed, params=" + JsonConvert.root().convertTo(params))); transport.offerConnection(true, conn); if (handler != null) { @@ -489,22 +464,44 @@ public final class SncpClient { buffer.getChar(); //端口 } - private void fillHeader(ByteBuffer buffer, long seqid, DLong actionid, int bodyLength) { + private void fillHeader(ByteArray buffer, long seqid, DLong actionid, int bodyLength) { //---------------------head---------------------------------- - final int currentpos = buffer.position(); - buffer.position(0); - buffer.putLong(seqid); //序列号 - buffer.putChar((char) HEADER_SIZE); //header长度 - DLong.write(buffer, this.serviceid); - buffer.putInt(this.serviceversion); - DLong.write(buffer, actionid); - buffer.put(addrBytes); - buffer.putChar((char) this.addrPort); - buffer.putInt(bodyLength); //body长度 - buffer.putInt(0); //结果码, 请求方固定传0 - buffer.position(currentpos); + int offset = 0; + buffer.putLong(offset, seqid); //序列号 + offset += 8; + buffer.putChar(offset, (char) HEADER_SIZE); //header长度 + offset += 2; + DLong.write(buffer, offset, this.serviceid); + offset += 16; + buffer.putInt(offset, this.serviceversion); + offset += 4; + DLong.write(buffer, offset, actionid); + offset += 16; + buffer.put(offset, addrBytes); + offset += addrBytes.length; //4 + buffer.putChar(offset, (char) this.addrPort); + offset += 2; + buffer.putInt(offset, bodyLength); //body长度 + offset += 4; + buffer.putInt(offset, 0); //结果码, 请求方固定传0 + //offset += 4; } +// private void fillHeader(ByteBuffer buffer, long seqid, DLong actionid, int bodyLength) { +// //---------------------head---------------------------------- +// final int currentpos = buffer.position(); +// buffer.position(0); +// buffer.putLong(seqid); //序列号 +// buffer.putChar((char) HEADER_SIZE); //header长度 +// DLong.write(buffer, this.serviceid); +// buffer.putInt(this.serviceversion); +// DLong.write(buffer, actionid); +// buffer.put(addrBytes); +// buffer.putChar((char) this.addrPort); +// buffer.putInt(bodyLength); //body长度 +// buffer.putInt(0); //结果码, 请求方固定传0 +// buffer.position(currentpos); +// } protected static final class SncpAction { protected final DLong actionid; diff --git a/src/org/redkale/net/sncp/SncpPrepareServlet.java b/src/org/redkale/net/sncp/SncpPrepareServlet.java index 5e991f2b4..58500a1db 100644 --- a/src/org/redkale/net/sncp/SncpPrepareServlet.java +++ b/src/org/redkale/net/sncp/SncpPrepareServlet.java @@ -22,7 +22,6 @@ public class SncpPrepareServlet extends PrepareServlet { public static final byte[] DEFAULT_HEADER = new byte[HEADER_SIZE]; + protected static final int READ_STATE_ROUTE = 1; + + protected static final int READ_STATE_HEADER = 2; + + protected static final int READ_STATE_BODY = 3; + + protected static final int READ_STATE_END = 4; + protected final BsonConvert convert; private long seqid; + protected int readState = READ_STATE_ROUTE; + private int serviceversion; private DLong serviceid; @@ -52,44 +62,54 @@ public class SncpRequest extends Request { } @Override - protected int readHeader(ByteBuffer buffer) { - if (buffer.remaining() < HEADER_SIZE) { + protected int readHeader(ByteBuffer buffer, Request last) { + if (buffer.remaining() == Sncp.PING_BUFFER.remaining()) { if (buffer.hasRemaining()) buffer.get(new byte[buffer.remaining()]); - this.ping = true; + this.ping = true; //Sncp.PING_BUFFER + this.readState = READ_STATE_END; return 0; } //---------------------head---------------------------------- - this.seqid = buffer.getLong(); - if (buffer.getChar() != HEADER_SIZE) { - if (context.getLogger().isLoggable(Level.FINEST)) context.getLogger().finest("sncp buffer header.length not " + HEADER_SIZE); - return -1; - } - this.serviceid = DLong.read(buffer); - this.serviceversion = buffer.getInt(); - this.actionid = DLong.read(buffer); - buffer.get(addrbytes); //ipaddr - this.bodylength = buffer.getInt(); + if (this.readState == READ_STATE_ROUTE) { + if (buffer.remaining() < HEADER_SIZE) return 1; //小于60 + this.seqid = buffer.getLong(); //8 + if (buffer.getChar() != HEADER_SIZE) { //2 + if (context.getLogger().isLoggable(Level.FINEST)) context.getLogger().finest("sncp buffer header.length not " + HEADER_SIZE); + return -1; + } + this.serviceid = DLong.read(buffer); //16 + this.serviceversion = buffer.getInt(); //4 + this.actionid = DLong.read(buffer); //16 + buffer.get(addrbytes); //ipaddr //6 + this.bodylength = buffer.getInt(); //4 - if (buffer.getInt() != 0) { - if (context.getLogger().isLoggable(Level.FINEST)) context.getLogger().finest("sncp buffer header.retcode not 0"); - return -1; + if (buffer.getInt() != 0) { //4 + if (context.getLogger().isLoggable(Level.FINEST)) context.getLogger().finest("sncp buffer header.retcode not 0"); + return -1; + } + this.body = new byte[this.bodylength]; + this.readState = READ_STATE_BODY; } //---------------------body---------------------------------- - this.body = new byte[this.bodylength]; - int len = Math.min(this.bodylength, buffer.remaining()); - buffer.get(body, 0, len); - this.bodyoffset = len; - return bodylength - len; - } - - @Override - protected int readBody(ByteBuffer buffer) { - final int framelen = buffer.remaining(); - buffer.get(this.body, this.bodyoffset, framelen); - this.bodyoffset += framelen; - return framelen; + if (this.readState == READ_STATE_BODY) { + int len = Math.min(this.bodylength, buffer.remaining()); + buffer.get(body, 0, len); + this.bodyoffset = len; + int rs = bodylength - len; + if (rs == 0) this.readState = READ_STATE_END; + return rs; + } + return 0; } +// @Override +// protected int readBody(ByteBuffer buffer, int length) { +// final int framelen = buffer.remaining(); +// int len = Math.min(framelen, length); +// buffer.get(this.body, this.bodyoffset, len); +// this.bodyoffset += len; +// return len; +// } @Override protected void prepare() { this.keepAlive = true; @@ -110,6 +130,7 @@ public class SncpRequest extends Request { @Override protected void recycle() { this.seqid = 0; + this.readState = READ_STATE_ROUTE; this.serviceid = null; this.serviceversion = 0; this.actionid = null; diff --git a/src/org/redkale/net/sncp/SncpResponse.java b/src/org/redkale/net/sncp/SncpResponse.java index 678661a7a..ea3959b37 100644 --- a/src/org/redkale/net/sncp/SncpResponse.java +++ b/src/org/redkale/net/sncp/SncpResponse.java @@ -33,15 +33,15 @@ public class SncpResponse extends Response { private final int addrPort; public static String getRetCodeInfo(int retcode) { - if (retcode == RETCODE_ILLSERVICEID) return "serviceid is invalid"; - if (retcode == RETCODE_ILLSERVICEVER) return "serviceversion is invalid"; - if (retcode == RETCODE_ILLACTIONID) return "actionid is invalid"; + if (retcode == RETCODE_ILLSERVICEID) return "The serviceid is invalid"; + if (retcode == RETCODE_ILLSERVICEVER) return "The serviceversion is invalid"; + if (retcode == RETCODE_ILLACTIONID) return "The actionid is invalid"; if (retcode == RETCODE_THROWEXCEPTION) return "Inner exception"; return null; } - protected SncpResponse(SncpContext context, SncpRequest request, ObjectPool responsePool) { - super(context, request, responsePool); + protected SncpResponse(SncpContext context, SncpRequest request) { + super(context, request); this.addrBytes = context.getServerAddress().getAddress().getAddress(); this.addrPort = context.getServerAddress().getPort(); if (this.addrBytes.length != 4) throw new RuntimeException("SNCP serverAddress only support IPv4"); @@ -58,36 +58,59 @@ public class SncpResponse extends Response { } @Override - protected void offerBuffer(ByteBuffer... buffers) { - super.offerBuffer(buffers); + protected void finish(boolean kill, ByteBuffer buffer) { + super.finish(kill, buffer); } public void finish(final int retcode, final BsonWriter out) { if (out == null) { - final ByteBuffer buffer = channel.pollWriteBuffer(); + final ByteArray buffer = new ByteArray(SncpRequest.HEADER_SIZE); fillHeader(buffer, 0, retcode); finish(buffer); return; } final int respBodyLength = out.count(); //body总长度 - final ByteBuffer[] buffers = out.toBuffers(); - fillHeader(buffers[0], respBodyLength - HEADER_SIZE, retcode); - finish(buffers); + final ByteArray array = out.toByteArray(); + fillHeader(array, respBodyLength - HEADER_SIZE, retcode); + finish(array); } - protected void fillHeader(ByteBuffer buffer, int bodyLength, int retcode) { + protected void fillHeader(ByteArray buffer, int bodyLength, int retcode) { //---------------------head---------------------------------- - final int currentpos = buffer.position(); - buffer.position(0); - buffer.putLong(request.getSeqid()); - buffer.putChar((char) SncpRequest.HEADER_SIZE); - DLong.write(buffer, request.getServiceid()); - buffer.putInt(request.getServiceversion()); - DLong.write(buffer, request.getActionid()); - buffer.put(addrBytes); - buffer.putChar((char) this.addrPort); - buffer.putInt(bodyLength); - buffer.putInt(retcode); - buffer.position(currentpos); + int offset = 0; + buffer.putLong(offset, request.getSeqid()); + offset += 8; + buffer.putChar(offset, (char) SncpRequest.HEADER_SIZE); + offset += 2; + DLong.write(buffer, offset, request.getServiceid()); + offset += 16; + buffer.putInt(offset, request.getServiceversion()); + offset += 4; + DLong.write(buffer, offset, request.getActionid()); + offset += 16; + buffer.put(offset, addrBytes); + offset += addrBytes.length; //4 + buffer.putChar(offset, (char) this.addrPort); + offset += 2; + buffer.putInt(offset, bodyLength); + offset += 4; + buffer.putInt(offset, retcode); + //offset += 4; } + +// protected void fillHeader(ByteBuffer buffer, int bodyLength, int retcode) { +// //---------------------head---------------------------------- +// final int currentpos = buffer.position(); +// buffer.position(0); +// buffer.putLong(request.getSeqid()); +// buffer.putChar((char) SncpRequest.HEADER_SIZE); +// DLong.write(buffer, request.getServiceid()); +// buffer.putInt(request.getServiceversion()); +// DLong.write(buffer, request.getActionid()); +// buffer.put(addrBytes); +// buffer.putChar((char) this.addrPort); +// buffer.putInt(bodyLength); +// buffer.putInt(retcode); +// buffer.position(currentpos); +// } } diff --git a/src/org/redkale/net/sncp/SncpServer.java b/src/org/redkale/net/sncp/SncpServer.java index d39760718..49b6d369d 100644 --- a/src/org/redkale/net/sncp/SncpServer.java +++ b/src/org/redkale/net/sncp/SncpServer.java @@ -8,6 +8,7 @@ package org.redkale.net.sncp; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.*; +import org.redkale.boot.Application; import org.redkale.convert.bson.BsonFactory; import org.redkale.net.*; import org.redkale.net.sncp.SncpContext.SncpContextConfig; @@ -29,19 +30,23 @@ public class SncpServer extends Server getSncpServlets() { return this.prepare.getServlets(); } @@ -107,13 +108,13 @@ public class SncpServer extends Server createResponsePool(AtomicLong createCounter, AtomicLong cycleCounter, int responsePoolSize) { - ObjectPool pool = ObjectPool.createSafePool(createCounter, cycleCounter, responsePoolSize, (Creator) null, (x) -> ((SncpResponse) x).prepare(), (x) -> ((SncpResponse) x).recycle()); - pool.setCreator((Object... params) -> new SncpResponse(this.context, new SncpRequest(this.context), pool)); + Creator creator = (Object... params) -> new SncpResponse(this.context, new SncpRequest(this.context)); + ObjectPool pool = ObjectPool.createSafePool(createCounter, cycleCounter, responsePoolSize, creator, (x) -> ((SncpResponse) x).prepare(), (x) -> ((SncpResponse) x).recycle()); return pool; } diff --git a/src/org/redkale/service/AbstractService.java b/src/org/redkale/service/AbstractService.java index 64a9dfb41..dec289ed9 100644 --- a/src/org/redkale/service/AbstractService.java +++ b/src/org/redkale/service/AbstractService.java @@ -7,7 +7,9 @@ package org.redkale.service; import java.util.concurrent.*; import javax.annotation.Resource; +import org.redkale.boot.Application; import org.redkale.net.*; +import org.redkale.util.ThreadHashExecutor; /** * @@ -15,25 +17,46 @@ import org.redkale.net.*; */ public abstract class AbstractService implements Service { - //如果开启了SNCP,此处线程池为SncpServer的线程池 - @Resource(name = Server.RESNAME_SERVER_EXECUTOR) - private ExecutorService serverWorkExecutor; + @Resource(name = Application.RESNAME_APP_EXECUTOR) + private ExecutorService workExecutor; - protected void runAsync(Runnable runner) { - if (serverWorkExecutor != null) { - serverWorkExecutor.execute(runner); + protected void runAsync(Runnable command) { + if (workExecutor != null) { + workExecutor.execute(command); } else { Thread thread = Thread.currentThread(); if (thread instanceof WorkThread) { - ((WorkThread) thread).runAsync(runner); + ((WorkThread) thread).runAsync(command); } else { - ForkJoinPool.commonPool().execute(runner); + ForkJoinPool.commonPool().execute(command); + } + } + } + + protected void runAsync(int hash, Runnable command) { + if (workExecutor != null) { + if (workExecutor instanceof ThreadHashExecutor) { + ((ThreadHashExecutor) workExecutor).execute(hash, command); + } else { + Thread thread = Thread.currentThread(); + if (thread instanceof WorkThread) { + ((WorkThread) thread).runAsync(hash, command); + } else { + workExecutor.execute(command); + } + } + } else { + Thread thread = Thread.currentThread(); + if (thread instanceof WorkThread) { + ((WorkThread) thread).runAsync(hash, command); + } else { + ForkJoinPool.commonPool().execute(command); } } } protected ExecutorService getExecutor() { - if (serverWorkExecutor != null) return serverWorkExecutor; + if (workExecutor != null) return workExecutor; Thread thread = Thread.currentThread(); if (thread instanceof WorkThread) { return ((WorkThread) thread).getWorkExecutor(); diff --git a/src/org/redkale/service/RetLabel.java b/src/org/redkale/service/RetLabel.java index 0de46725f..c12884acf 100644 --- a/src/org/redkale/service/RetLabel.java +++ b/src/org/redkale/service/RetLabel.java @@ -52,7 +52,8 @@ public @interface RetLabel { public static Map> loadMap(Class clazz) { final Map> rets = new HashMap<>(); ServiceLoader loader = ServiceLoader.load(RetInfoTransfer.class); - RetInfoTransfer func = loader.findFirst().orElse(null); + Iterator it = loader.iterator(); + RetInfoTransfer func = it.hasNext() ? it.next() : null; for (Field field : clazz.getFields()) { if (!Modifier.isStatic(field.getModifiers())) continue; if (field.getType() != int.class) continue; diff --git a/src/org/redkale/service/WebSocketNodeService.java b/src/org/redkale/service/WebSocketNodeService.java index b47b87a63..21bbad7ae 100644 --- a/src/org/redkale/service/WebSocketNodeService.java +++ b/src/org/redkale/service/WebSocketNodeService.java @@ -41,7 +41,7 @@ public class WebSocketNodeService extends WebSocketNode implements Service { @Override public CompletableFuture> getWebSocketAddresses(@RpcTargetTopic String topic, final @RpcTargetAddress InetSocketAddress targetAddress, final Serializable groupid) { - if ((topic == null || !topic.equals(wsaddress.getTopic())) && (localSncpAddress == null || !localSncpAddress.equals(targetAddress))) return remoteWebSocketAddresses(topic, targetAddress, groupid); + if ((topic == null || !topic.equals(this.wsNodeAddress.getTopic())) && (localSncpAddress == null || !localSncpAddress.equals(targetAddress))) return remoteWebSocketAddresses(topic, targetAddress, groupid); if (this.localEngine == null) return CompletableFuture.completedFuture(new ArrayList<>()); final List rs = new ArrayList<>(); this.localEngine.getLocalWebSockets(groupid).forEach(x -> rs.add(x.getRemoteAddr())); @@ -72,6 +72,12 @@ public class WebSocketNodeService extends WebSocketNode implements Service { return this.localEngine.broadcastLocalAction(action); } + @Override + public CompletableFuture getUserSize(@RpcTargetTopic String topic, @RpcTargetAddress InetSocketAddress targetAddress) { + if (this.localEngine == null) return CompletableFuture.completedFuture(0); + return CompletableFuture.completedFuture(this.localEngine.getLocalUserSize()); + } + /** * 当用户连接到节点,需要更新到CacheSource * @@ -83,8 +89,7 @@ public class WebSocketNodeService extends WebSocketNode implements Service { @Override public CompletableFuture connect(Serializable userid, WebSocketAddress wsaddr) { tryAcquireSemaphore(); - CompletableFuture future = source.appendSetItemAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class, wsaddr); - future = future.thenAccept((a) -> source.appendSetItemAsync(SOURCE_SNCP_NODES_KEY, WebSocketAddress.class, wsaddr)); + CompletableFuture future = source.appendSetItemAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class, wsaddr); if (semaphore != null) future.whenComplete((r, e) -> releaseSemaphore()); if (logger.isLoggable(Level.FINEST)) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " connect from " + wsaddr); return future; @@ -101,7 +106,7 @@ public class WebSocketNodeService extends WebSocketNode implements Service { @Override public CompletableFuture disconnect(Serializable userid, WebSocketAddress wsaddr) { tryAcquireSemaphore(); - CompletableFuture future = source.removeSetItemAsync(SOURCE_SNCP_USERID_PREFIX + userid, WebSocketAddress.class, wsaddr); + CompletableFuture future = source.removeSetItemAsync(WS_SOURCE_KEY_USERID_PREFIX + userid, WebSocketAddress.class, wsaddr); if (semaphore != null) future.whenComplete((r, e) -> releaseSemaphore()); if (logger.isLoggable(Level.FINEST)) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + userid + " disconnect from " + wsaddr); return future.thenApply(v -> null); @@ -119,8 +124,8 @@ public class WebSocketNodeService extends WebSocketNode implements Service { @Override public CompletableFuture changeUserid(Serializable olduserid, Serializable newuserid, WebSocketAddress wsaddr) { tryAcquireSemaphore(); - CompletableFuture future = source.appendSetItemAsync(SOURCE_SNCP_USERID_PREFIX + newuserid, WebSocketAddress.class, wsaddr); - future = future.thenAccept((a) -> source.removeSetItemAsync(SOURCE_SNCP_USERID_PREFIX + olduserid, WebSocketAddress.class, wsaddr)); + CompletableFuture future = source.appendSetItemAsync(WS_SOURCE_KEY_USERID_PREFIX + newuserid, WebSocketAddress.class, wsaddr); + future = future.thenAccept((a) -> source.removeSetItemAsync(WS_SOURCE_KEY_USERID_PREFIX + olduserid, WebSocketAddress.class, wsaddr)); if (semaphore != null) future.whenComplete((r, e) -> releaseSemaphore()); if (logger.isLoggable(Level.FINEST)) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + olduserid + " changeUserid to " + newuserid + " from " + wsaddr); return future; diff --git a/src/org/redkale/source/CacheMemorySource.java b/src/org/redkale/source/CacheMemorySource.java index 5cf92345e..af6dc7af8 100644 --- a/src/org/redkale/source/CacheMemorySource.java +++ b/src/org/redkale/source/CacheMemorySource.java @@ -93,6 +93,11 @@ public final class CacheMemorySource extends AbstractService i return "memory"; } + @Override + public String toString() { + return "CacheMemorySource(type=memory)"; + } + @Override //ServiceLoader时判断配置是否符合当前实现类 public boolean match(AnyValue config) { return false; @@ -126,7 +131,7 @@ public final class CacheMemorySource extends AbstractService i } if (scheduler == null) { this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { - final Thread t = new Thread(r, self.getClass().getSimpleName() + "-Expirer-Thread"); + final Thread t = new Thread(r, "Redkale-" + self.getClass().getSimpleName() + "-Expirer-Thread"); t.setDaemon(true); return t; }); diff --git a/src/org/redkale/source/DataJdbcSource.java b/src/org/redkale/source/DataJdbcSource.java index 8fe01a332..b35cf299e 100644 --- a/src/org/redkale/source/DataJdbcSource.java +++ b/src/org/redkale/source/DataJdbcSource.java @@ -13,6 +13,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.function.*; import java.util.logging.Level; +import org.redkale.net.AsyncGroup; import org.redkale.service.Local; import org.redkale.util.*; @@ -45,7 +46,7 @@ public class DataJdbcSource extends DataSqlSource { } @Override - protected PoolSource createPoolSource(DataSource source, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop) { + protected PoolSource createPoolSource(DataSource source, AsyncGroup asyncGroup, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop) { return new PoolJdbcSource(this.name, this.persistxml, rwtype, queue, semaphore, prop, this.logger); } diff --git a/src/org/redkale/source/DataMemorySource.java b/src/org/redkale/source/DataMemorySource.java index 2eba78f06..6f49f9c18 100644 --- a/src/org/redkale/source/DataMemorySource.java +++ b/src/org/redkale/source/DataMemorySource.java @@ -11,6 +11,7 @@ import java.sql.ResultSet; import java.util.*; import java.util.concurrent.*; import java.util.function.*; +import org.redkale.net.AsyncGroup; import org.redkale.service.Local; import org.redkale.util.*; @@ -79,7 +80,7 @@ public class DataMemorySource extends DataSqlSource { } @Override - protected PoolSource createPoolSource(DataSource source, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop) { + protected PoolSource createPoolSource(DataSource source, AsyncGroup asyncGroup, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop) { return null; } diff --git a/src/org/redkale/source/DataSource.java b/src/org/redkale/source/DataSource.java index 28212565e..4cd48ea86 100644 --- a/src/org/redkale/source/DataSource.java +++ b/src/org/redkale/source/DataSource.java @@ -1320,6 +1320,56 @@ public interface DataSource { */ public CompletableFuture findAsync(final Class clazz, final SelectColumn selects, final Serializable pk); + /** + * 获取指定主键值的多个记录, 返回数组,数组长度与pks一样
+ * 等价SQL: SELECT * FROM {table} WHERE {primary} = {id}
+ * + * @param Entity泛型 + * @param clazz Entity类 + * @param pks 主键值集合 + * + * @return Entity对象 + */ + //public T[] finds(final Class clazz, final Serializable... pks); + + /** + * 获取指定主键值的多个记录, 返回数组,数组长度与pks一样
+ * 等价SQL: SELECT * FROM {table} WHERE {primary} = {id}
+ * + * @param Entity泛型 + * @param clazz Entity类 + * @param pks 主键值集合 + * + * @return Entity对象 CompletableFuture + */ + //public CompletableFuture findsAsync(final Class clazz, final Serializable... pks); + + /** + * 获取指定主键值的多个记录, 返回数组,数组长度与pks一样
+ * 等价SQL: SELECT {column1},{column2}, ··· FROM {table} WHERE {primary} = {id}
+ * + * @param Entity泛型 + * @param clazz Entity类 + * @param selects 指定字段 + * @param pks 主键值集合 + * + * @return Entity对象 + */ + //public T[] finds(final Class clazz, final SelectColumn selects, final Serializable... pks); + + /** + * 获取指定主键值的多个记录, 返回数组,数组长度与pks一样
+ * 等价SQL: SELECT {column1},{column2}, ··· FROM {table} WHERE {primary} = {id}
+ * + * @param Entity泛型 + * @param clazz Entity类 + * @param selects 指定字段 + * @param pks 主键值集合 + * + * @return Entity对象CompletableFuture + */ + //public CompletableFuture findsAsync(final Class clazz, final SelectColumn selects, final Serializable... pks); + /** * 获取符合过滤条件单个记录, 返回null表示不存在值
* 等价SQL: SELECT * FROM {table} WHERE {column} = {key}
diff --git a/src/org/redkale/source/DataSources.java b/src/org/redkale/source/DataSources.java index 1bb7fbc39..f34d948d1 100644 --- a/src/org/redkale/source/DataSources.java +++ b/src/org/redkale/source/DataSources.java @@ -81,38 +81,38 @@ public final class DataSources { public static DataSource createDataSource(final String unitName, URL persistxml, Properties readprop, Properties writeprop) throws IOException { String impl = readprop.getProperty(JDBC_DATASOURCE_CLASS, DataJdbcSource.class.getName()); if (DataJdbcSource.class.getName().equals(impl)) { - try { + if (PoolJdbcSource.checkSource(readprop)) { return new DataJdbcSource(unitName, persistxml, readprop, writeprop); - } catch (RuntimeException re) { - if (!(re.getCause() instanceof ClassNotFoundException)) throw re; - String dbtype = null; - { - /* jdbc:mysql:// jdbc:microsoft:sqlserver:// 取://之前的到最后一个:之间的字符串 */ - String url = readprop.getProperty(JDBC_URL); - int pos = url.indexOf("://"); - if (pos > 0) { - String url0 = url.substring(0, pos); - pos = url0.lastIndexOf(':'); - if (pos > 0) dbtype = url0.substring(pos + 1); - } else { //jdbc:oracle:thin:@localhost:1521 - String url0 = url.substring(url.indexOf(":") + 1); - pos = url0.indexOf(':'); - if (pos > 0) dbtype = url0.substring(0, pos); - } - } - if (dbtype == null) throw re; - Iterator it = ServiceLoader.load(SourceLoader.class).iterator(); - Class dsClass = null; - while (it.hasNext()) { - SourceLoader loader = it.next(); - if (dbtype.equalsIgnoreCase(loader.dbtype())) { - dsClass = loader.dataSourceClass(); - if (dsClass != null) break; - } - } - if (dsClass == null) throw re; - impl = dsClass.getName(); } + + final String url = readprop.getProperty(JDBC_URL); + String dbtype = null; + { + /* jdbc:mysql:// jdbc:microsoft:sqlserver:// 取://之前的到最后一个:之间的字符串 */ + int pos = url.indexOf("://"); + if (pos > 0) { + String url0 = url.substring(0, pos); + pos = url0.lastIndexOf(':'); + if (pos > 0) dbtype = url0.substring(pos + 1); + } else { //jdbc:oracle:thin:@localhost:1521 + String url0 = url.substring(url.indexOf(":") + 1); + pos = url0.indexOf(':'); + if (pos > 0) dbtype = url0.substring(0, pos); + } + } + if (dbtype == null) throw new RuntimeException("not found datasource implements class, url=" + url); + + Iterator it = ServiceLoader.load(SourceLoader.class).iterator(); + Class dsClass = null; + while (it.hasNext()) { + SourceLoader loader = it.next(); + if (dbtype.equalsIgnoreCase(loader.dbtype())) { + dsClass = loader.dataSourceClass(); + if (dsClass != null) break; + } + } + if (dsClass == null) throw new RuntimeException("not found datasource implements ServiceLoader, url=" + url); + impl = dsClass.getName(); } try { Class ds = Thread.currentThread().getContextClassLoader().loadClass(impl); diff --git a/src/org/redkale/source/DataSqlSource.java b/src/org/redkale/source/DataSqlSource.java index 786644b5c..431fbfa52 100644 --- a/src/org/redkale/source/DataSqlSource.java +++ b/src/org/redkale/source/DataSqlSource.java @@ -7,17 +7,18 @@ package org.redkale.source; import java.io.Serializable; import java.net.URL; -import java.nio.ByteBuffer; import java.sql.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.*; import java.util.function.*; import java.util.logging.*; import java.util.stream.Stream; +import javax.annotation.Resource; +import org.redkale.net.AsyncGroup; import org.redkale.service.*; import static org.redkale.source.DataSources.*; import org.redkale.util.*; +import static org.redkale.boot.Application.RESNAME_APP_GROUP; /** * DataSource的SQL抽象实现类
@@ -43,11 +44,9 @@ public abstract class DataSqlSource extends AbstractService implement protected URL persistxml; - protected int threads; + protected Properties readprop; - protected ObjectPool bufferPool; - - protected ThreadPoolExecutor executor; + protected Properties writeprop; protected boolean cacheForbidden; @@ -55,57 +54,65 @@ public abstract class DataSqlSource extends AbstractService implement protected PoolSource writePool; + @Resource(name = RESNAME_APP_GROUP) + protected AsyncGroup asyncGroup; + protected final BiFunction sqlFormatter; protected final BiConsumer futureCompleteConsumer = (r, t) -> { - if (t != null) logger.log(Level.SEVERE, "CompletableFuture complete error", (Throwable) t); + if (t != null) logger.log(Level.INFO, "CompletableFuture complete error", (Throwable) t); }; - protected final BiFunction fullloader = (s, t) -> ((Sheet) querySheetCompose(false, false, false, t, null, null, (FilterNode) null).join()).list(true); + protected final BiFunction> fullloader = (s, i) + -> ((CompletableFuture) querySheetDB(i, false, false, false, null, null, (FilterNode) null)).thenApply(e -> e == null ? new ArrayList() : e.list(true)); @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"}) public DataSqlSource(String unitName, URL persistxml, Properties readprop, Properties writeprop) { if (readprop == null) readprop = new Properties(); if (writeprop == null) writeprop = readprop; - final AtomicInteger counter = new AtomicInteger(); - this.threads = Integer.decode(readprop.getProperty(JDBC_CONNECTIONS_LIMIT, "" + Runtime.getRuntime().availableProcessors() * 16)); - int maxconns = Math.max(8, Integer.decode(readprop.getProperty(JDBC_CONNECTIONS_LIMIT, "" + Math.min(1000, Runtime.getRuntime().availableProcessors() * 200)))); - if (readprop != writeprop) { - this.threads += Integer.decode(writeprop.getProperty(JDBC_CONNECTIONS_LIMIT, "" + Runtime.getRuntime().availableProcessors() * 16)); - maxconns = 0; - } - final String cname = this.getClass().getSimpleName(); - final Thread.UncaughtExceptionHandler ueh = (t, e) -> { - logger.log(Level.SEVERE, cname + " error", e); - }; - this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threads, (Runnable r) -> { - Thread t = new Thread(r); - t.setDaemon(true); - String s = "" + counter.incrementAndGet(); - if (s.length() == 1) { - s = "00" + s; - } else if (s.length() == 2) { - s = "0" + s; - } - t.setName("Redkale-" + cname + "-Thread-" + s); - t.setUncaughtExceptionHandler(ueh); - return t; - }); - final int bufferCapacity = Math.max(8 * 1024, Integer.decode(readprop.getProperty(JDBC_CONNECTIONSCAPACITY, "" + 8 * 1024))); - this.bufferPool = ObjectPool.createSafePool(new AtomicLong(), new AtomicLong(), Math.max(maxconns, this.threads * 2), - (Object... params) -> ByteBuffer.allocateDirect(bufferCapacity), null, (e) -> { - if (e == null || e.isReadOnly() || e.capacity() != bufferCapacity) return false; - e.clear(); - return true; - }); this.name = unitName; this.persistxml = persistxml; + this.readprop = readprop; + this.writeprop = writeprop; + this.sqlFormatter = (info, val) -> formatValueToString(info, val); + } + + @Override + public void init(AnyValue conf) { + int maxconns = Math.max(8, Integer.decode(readprop.getProperty(JDBC_CONNECTIONS_LIMIT, "" + Runtime.getRuntime().availableProcessors() * 32))); + if (readprop != writeprop) maxconns = 0; this.cacheForbidden = "NONE".equalsIgnoreCase(readprop.getProperty(JDBC_CACHE_MODE)); ArrayBlockingQueue queue = maxconns > 0 ? new ArrayBlockingQueue(maxconns) : null; Semaphore semaphore = maxconns > 0 ? new Semaphore(maxconns) : null; - this.readPool = createPoolSource(this, "read", queue, semaphore, readprop); - this.writePool = createPoolSource(this, "write", queue, semaphore, writeprop); - this.sqlFormatter = (info, val) -> formatValueToString(info, val); + this.readPool = createPoolSource(this, this.asyncGroup, "read", queue, semaphore, readprop); + this.writePool = createPoolSource(this, this.asyncGroup, "write", queue, semaphore, writeprop); + } + + public void updateMaxconns(int maxconns) { + if (readprop == writeprop) { + Semaphore semaphore = new Semaphore(maxconns); + this.readPool.setSemaphore(semaphore); + this.writePool.setSemaphore(semaphore); + if (readPool instanceof PoolTcpSource) { + ArrayBlockingQueue connQueue = new ArrayBlockingQueue(maxconns); + ((PoolTcpSource) readPool).updateConnQueue(connQueue); + ((PoolTcpSource) writePool).updateConnQueue(connQueue); + } + } else { + int onemax = maxconns / 2; + this.readPool.setSemaphore(new Semaphore(onemax)); + this.writePool.setSemaphore(new Semaphore(onemax)); + if (readPool instanceof PoolTcpSource) { + ((PoolTcpSource) readPool).updateConnQueue(new ArrayBlockingQueue(onemax)); + ((PoolTcpSource) writePool).updateConnQueue(new ArrayBlockingQueue(onemax)); + } + } + } + + @Override + public void destroy(AnyValue config) { + if (readPool != null) readPool.close(); + if (writePool != null) writePool.close(); } @Local @@ -124,7 +131,7 @@ public abstract class DataSqlSource extends AbstractService implement protected abstract String prepareParamSign(int index); //创建连接池 - protected abstract PoolSource createPoolSource(DataSource source, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop); + protected abstract PoolSource createPoolSource(DataSource source, AsyncGroup asyncGroup, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop); //插入纪录 protected abstract CompletableFuture insertDB(final EntityInfo info, T... entitys); @@ -196,22 +203,6 @@ public abstract class DataSqlSource extends AbstractService implement return node == null ? null : node.createSQLExpress(info, joinTabalis); } - @Override - protected ExecutorService getExecutor() { - return executor; - } - - @Override - public void init(AnyValue config) { - } - - @Override - public void destroy(AnyValue config) { - if (this.executor != null) this.executor.shutdownNow(); - if (readPool != null) readPool.close(); - if (writePool != null) writePool.close(); - } - @Local @Override public String getType() { @@ -254,6 +245,11 @@ public abstract class DataSqlSource extends AbstractService implement return info.isVirtualEntity(); } + public EntityCache loadCache(Class clazz) { + EntityInfo info = loadEntityInfo(clazz); + return info.getCache(); + } + /** * 将entity的对象全部加载到Cache中去,如果clazz没有被@javax.persistence.Cacheable注解则不做任何事 * @@ -264,7 +260,7 @@ public abstract class DataSqlSource extends AbstractService implement EntityInfo info = loadEntityInfo(clazz); EntityCache cache = info.getCache(); if (cache == null) return; - cache.fullLoad(); + cache.fullLoadAsync(); } ////检查对象是否都是同一个Entity类 @@ -346,7 +342,7 @@ public abstract class DataSqlSource extends AbstractService implement if (future != null) return future; final EntityInfo info = loadEntityInfo((Class) entitys[0].getClass()); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> insertCache(info, entitys), getExecutor()); + return CompletableFuture.completedFuture(insertCache(info, entitys)); } if (isAsync()) return insertDB(info, entitys).whenComplete((rs, t) -> { if (t != null) { @@ -445,7 +441,7 @@ public abstract class DataSqlSource extends AbstractService implement if (pks.length == 0) return CompletableFuture.completedFuture(-1); final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> deleteCache(info, -1, pks), getExecutor()); + return CompletableFuture.completedFuture(deleteCache(info, -1, pks)); } if (isAsync()) return deleteCompose(info, pks).whenComplete((rs, t) -> { if (t != null) { @@ -490,7 +486,7 @@ public abstract class DataSqlSource extends AbstractService implement public CompletableFuture deleteAsync(final Class clazz, final Flipper flipper, FilterNode node) { final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> deleteCache(info, -1, flipper, node), getExecutor()); + return CompletableFuture.completedFuture(deleteCache(info, -1, flipper, node)); } if (isAsync()) return this.deleteCompose(info, flipper, node).whenComplete((rs, t) -> { if (t != null) { @@ -571,7 +567,7 @@ public abstract class DataSqlSource extends AbstractService implement public CompletableFuture clearTableAsync(final Class clazz, FilterNode node) { final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> clearTableCache(info, node), getExecutor()); + return CompletableFuture.completedFuture(clearTableCache(info, node)); } if (isAsync()) return this.clearTableCompose(info, node).whenComplete((rs, t) -> { if (t != null) { @@ -624,7 +620,7 @@ public abstract class DataSqlSource extends AbstractService implement public CompletableFuture dropTableAsync(final Class clazz, FilterNode node) { final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> dropTableCache(info, node), getExecutor()); + return CompletableFuture.completedFuture(dropTableCache(info, node)); } if (isAsync()) return this.dropTableCompose(info, node).whenComplete((rs, t) -> { if (t != null) { @@ -721,7 +717,9 @@ public abstract class DataSqlSource extends AbstractService implement if (future != null) return future; final Class clazz = (Class) entitys[0].getClass(); final EntityInfo info = loadEntityInfo(clazz); - if (isOnlyCache(info)) return CompletableFuture.supplyAsync(() -> updateCache(info, -1, entitys), getExecutor()); + if (isOnlyCache(info)) { + return CompletableFuture.completedFuture(updateCache(info, -1, entitys)); + } if (isAsync()) return updateDB(info, entitys).whenComplete((rs, t) -> { if (t != null) { futureCompleteConsumer.accept(rs, t); @@ -766,7 +764,7 @@ public abstract class DataSqlSource extends AbstractService implement public CompletableFuture updateColumnAsync(final Class clazz, final Serializable pk, final String column, final Serializable colval) { final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> updateCache(info, -1, pk, column, colval), getExecutor()); + return CompletableFuture.completedFuture(updateCache(info, -1, pk, column, colval)); } if (isAsync()) return updateColumnCompose(info, pk, column, colval).whenComplete((rs, t) -> { if (t != null) { @@ -823,7 +821,7 @@ public abstract class DataSqlSource extends AbstractService implement public CompletableFuture updateColumnAsync(final Class clazz, final String column, final Serializable colval, final FilterNode node) { final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> updateCache(info, -1, column, colval, node), getExecutor()); + return CompletableFuture.completedFuture(updateCache(info, -1, column, colval, node)); } if (isAsync()) return this.updateColumnCompose(info, column, colval, node).whenComplete((rs, t) -> { if (t != null) { @@ -898,7 +896,7 @@ public abstract class DataSqlSource extends AbstractService implement if (values == null || values.length < 1) return CompletableFuture.completedFuture(-1); final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> updateCache(info, -1, pk, values), getExecutor()); + return CompletableFuture.completedFuture(updateCache(info, -1, pk, values)); } if (isAsync()) return this.updateColumnCompose(info, pk, values).whenComplete((rs, t) -> { if (t != null) { @@ -979,7 +977,7 @@ public abstract class DataSqlSource extends AbstractService implement if (values == null || values.length < 1) return CompletableFuture.completedFuture(-1); final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> updateCache(info, -1, node, flipper, values), getExecutor()); + return CompletableFuture.completedFuture(updateCache(info, -1, node, flipper, values)); } if (isAsync()) return this.updateColumnCompose(info, node, flipper, values).whenComplete((rs, t) -> { if (t != null) { @@ -1076,7 +1074,7 @@ public abstract class DataSqlSource extends AbstractService implement Class clazz = (Class) entity.getClass(); final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> updateCache(info, -1, false, entity, null, selects), getExecutor()); + return CompletableFuture.completedFuture(updateCache(info, -1, false, entity, null, selects)); } if (isAsync()) return this.updateColumnCompose(info, false, entity, null, selects).whenComplete((rs, t) -> { if (t != null) { @@ -1115,7 +1113,7 @@ public abstract class DataSqlSource extends AbstractService implement Class clazz = (Class) entity.getClass(); final EntityInfo info = loadEntityInfo(clazz); if (isOnlyCache(info)) { - return CompletableFuture.supplyAsync(() -> updateCache(info, -1, true, entity, node, selects), getExecutor()); + return CompletableFuture.completedFuture(updateCache(info, -1, true, entity, node, selects)); } if (isAsync()) return this.updateColumnCompose(info, true, entity, node, selects).whenComplete((rs, t) -> { if (t != null) { @@ -1623,7 +1621,7 @@ public abstract class DataSqlSource extends AbstractService implement final EntityInfo info = loadEntityInfo(clazz); final EntityCache cache = info.getCache(); if (cache != null) { - T rs = cache.find(selects, pk); + T rs = selects == null ? cache.find(pk) : cache.find(selects, pk); if (cache.isFullLoaded() || rs != null) return rs; } return findCompose(info, selects, pk).join(); @@ -1634,7 +1632,7 @@ public abstract class DataSqlSource extends AbstractService implement final EntityInfo info = loadEntityInfo(clazz); final EntityCache cache = info.getCache(); if (cache != null) { - T rs = cache.find(selects, pk); + T rs = selects == null ? cache.find(pk) : cache.find(selects, pk); if (cache.isFullLoaded() || rs != null) return CompletableFuture.completedFuture(rs); } if (isAsync()) return findCompose(info, selects, pk); diff --git a/src/org/redkale/source/EntityCache.java b/src/org/redkale/source/EntityCache.java index 88a4e8f65..a0efa376c 100644 --- a/src/org/redkale/source/EntityCache.java +++ b/src/org/redkale/source/EntityCache.java @@ -32,6 +32,8 @@ public final class EntityCache { //日志 private static final Logger logger = Logger.getLogger(EntityCache.class.getName()); + private Object[] array; + //主键与对象的键值对 private ConcurrentHashMap map = new ConcurrentHashMap(); @@ -62,6 +64,8 @@ public final class EntityCache { //是否已经全量加载过 private volatile boolean fullloaded; + private final AtomicBoolean loading = new AtomicBoolean(); + //Entity信息 final EntityInfo info; @@ -71,6 +75,8 @@ public final class EntityCache { //@Cacheable的定时器 private ScheduledThreadPoolExecutor scheduler; + private CompletableFuture loadFuture; + public EntityCache(final EntityInfo info, final Cacheable c) { this.info = info; this.interval = c == null ? 0 : c.interval(); @@ -78,7 +84,9 @@ public final class EntityCache { this.creator = info.getCreator(); this.primary = info.primary; VirtualEntity ve = info.getType().getAnnotation(VirtualEntity.class); - this.needcopy = ve == null || !ve.direct(); + boolean direct = c != null && c.direct(); + if (!direct) direct = ve != null && ve.direct(); + this.needcopy = !direct; this.newReproduce = Reproduce.create(type, type, (m) -> { try { return type.getDeclaredField(m).getAnnotation(Transient.class) == null; @@ -98,34 +106,35 @@ public final class EntityCache { }); } - public void fullLoad() { + public void fullLoadAsync() { + if (loading.getAndSet(true)) return; if (info.fullloader == null) { this.list = new ConcurrentLinkedQueue(); this.map = new ConcurrentHashMap(); this.fullloaded = true; + loading.set(false); return; } this.fullloaded = false; - ConcurrentHashMap newmap = new ConcurrentHashMap(); - List all = info.fullloader.apply(info.source, type); - if (all != null) { - all.stream().filter(x -> x != null).forEach(x -> { - newmap.put(this.primary.get(x), x); - }); + CompletableFuture allFuture = info.fullloader.apply(info.source, info); + this.loadFuture = allFuture; + if (allFuture == null) { + this.list = new ConcurrentLinkedQueue(); + this.map = new ConcurrentHashMap(); + this.fullloaded = true; + loading.set(false); + return; } - this.list = all == null ? new ConcurrentLinkedQueue() : new ConcurrentLinkedQueue(all); - this.map = newmap; - this.fullloaded = true; - if (this.interval > 0) { + if (this.interval > 0 && this.scheduler == null && info.fullloader != null) { this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { - final Thread t = new Thread(r, "EntityCache-" + type + "-Thread"); + final Thread t = new Thread(r, "Redkale-EntityCache-" + type + "-Thread"); t.setDaemon(true); return t; }); this.scheduler.scheduleAtFixedRate(() -> { try { ConcurrentHashMap newmap2 = new ConcurrentHashMap(); - List all2 = info.fullloader.apply(info.source, type); + List all2 = info.fullloader.apply(info.source, info).join(); if (all2 != null) { all2.stream().filter(x -> x != null).forEach(x -> { newmap2.put(this.primary.get(x), x); @@ -138,6 +147,23 @@ public final class EntityCache { } }, interval - System.currentTimeMillis() / 1000 % interval, interval, TimeUnit.SECONDS); } + allFuture.whenComplete((l, t) -> { + if (t != null) { + loading.set(false); + return; + } + List all = l; + ConcurrentHashMap newmap = new ConcurrentHashMap(); + if (all != null) { + all.stream().filter(x -> x != null).forEach(x -> { + newmap.put(this.primary.get(x), x); + }); + } + this.list = new ConcurrentLinkedQueue(all); + this.map = newmap; + this.fullloaded = true; + loading.set(false); + }); } public Class getType() { @@ -159,6 +185,20 @@ public final class EntityCache { return fullloaded; } + //临时功能 + @Deprecated + public EntityCache array() { + if (!isFullLoaded()) this.loadFuture.join(); + this.array = this.list.toArray(); + return this; + } + + //临时功能 + @Deprecated + public T findAt(int pk) { + return (T) this.array[pk - 1]; + } + public T find(Serializable pk) { if (pk == null) return null; T rs = map.get(pk); diff --git a/src/org/redkale/source/EntityInfo.java b/src/org/redkale/source/EntityInfo.java index e1bf4e97f..f5a9f5ad8 100644 --- a/src/org/redkale/source/EntityInfo.java +++ b/src/org/redkale/source/EntityInfo.java @@ -64,6 +64,9 @@ public final class EntityInfo { //Entity缓存对象 private final EntityCache cache; + //用于存储绑定在EntityInfo上的对象 + private final ConcurrentHashMap subobjectMap = new ConcurrentHashMap<>(); + //key是field的name, 不是sql字段。 //存放所有与数据库对应的字段, 包括主键 private final HashMap> attributeMap = new HashMap<>(); @@ -103,14 +106,17 @@ public final class EntityInfo { //分表 策略 private final DistributeTableStrategy tableStrategy; + //根据主键查找所有对象的SQL + private final String allQueryPrepareSQL; + //根据主键查找单个对象的SQL, 含 ? - private final String queryPrepareSQL; + private final String findPrepareSQL; //根据主键查找单个对象的SQL, 含 $ - private final String queryDollarPrepareSQL; + private final String findDollarPrepareSQL; //根据主键查找单个对象的SQL, 含 :name - private final String queryNamesPrepareSQL; + private final String findNamesPrepareSQL; //数据库中所有字段 private final Attribute[] queryAttributes; @@ -161,7 +167,7 @@ public final class EntityInfo { final DataSource source; //全量数据的加载器 - final BiFunction fullloader; + final BiFunction> fullloader; //------------------------------------------------------------ /** @@ -174,7 +180,7 @@ public final class EntityInfo { * @param fullloader 全量加载器,可为null */ static EntityInfo load(Class clazz, final boolean cacheForbidden, final Properties conf, - DataSource source, BiFunction fullloader) { + DataSource source, BiFunction> fullloader) { EntityInfo rs = entityInfos.get(clazz); if (rs != null && (rs.cache == null || rs.cache.isFullLoaded())) return rs; synchronized (entityInfos) { @@ -182,10 +188,10 @@ public final class EntityInfo { if (rs == null) { rs = new EntityInfo(clazz, cacheForbidden, conf, source, fullloader); entityInfos.put(clazz, rs); - if (rs.cache != null) { - if (fullloader == null) throw new IllegalArgumentException(clazz.getName() + " auto loader is illegal"); - rs.cache.fullLoad(); - } + } + if (rs.cache != null && !rs.isCacheFullLoaded()) { + if (fullloader == null) throw new IllegalArgumentException(clazz.getName() + " auto loader is illegal"); + rs.cache.fullLoadAsync(); } return rs; } @@ -213,7 +219,7 @@ public final class EntityInfo { * @param fullloader 全量加载器,可为null */ private EntityInfo(Class type, final boolean cacheForbidden, - Properties conf, DataSource source, BiFunction fullloader) { + Properties conf, DataSource source, BiFunction> fullloader) { this.type = type; this.source = source; //--------------------------------------------- @@ -244,7 +250,7 @@ public final class EntityInfo { Table t = type.getAnnotation(Table.class); if (type.getAnnotation(VirtualEntity.class) != null || (source == null || "memory".equalsIgnoreCase(source.getType()))) { this.table = null; - BiFunction loader = null; + BiFunction> loader = null; try { VirtualEntity ve = type.getAnnotation(VirtualEntity.class); if (ve != null) loader = ve.loader().getDeclaredConstructor().newInstance(); @@ -424,24 +430,26 @@ public final class EntityInfo { this.deletePrepareSQL = "DELETE FROM " + (this.tableStrategy == null ? table : "${newtable}") + " WHERE " + getPrimarySQLColumn(null) + " = ?"; this.deleteDollarPrepareSQL = "DELETE FROM " + (this.tableStrategy == null ? table : "${newtable}") + " WHERE " + getPrimarySQLColumn(null) + " = $1"; this.deleteNamesPrepareSQL = "DELETE FROM " + (this.tableStrategy == null ? table : "${newtable}") + " WHERE " + getPrimarySQLColumn(null) + " = :" + getPrimarySQLColumn(null); - this.queryPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = ?"; - this.queryDollarPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = $1"; - this.queryNamesPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = :" + getPrimarySQLColumn(null); + this.allQueryPrepareSQL = "SELECT * FROM " + table; + this.findPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = ?"; + this.findDollarPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = $1"; + this.findNamesPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = :" + getPrimarySQLColumn(null); } else { this.insertPrepareSQL = null; this.updatePrepareSQL = null; this.deletePrepareSQL = null; - this.queryPrepareSQL = null; + this.findPrepareSQL = null; + this.allQueryPrepareSQL = null; this.insertDollarPrepareSQL = null; this.updateDollarPrepareSQL = null; this.deleteDollarPrepareSQL = null; - this.queryDollarPrepareSQL = null; + this.findDollarPrepareSQL = null; this.insertNamesPrepareSQL = null; this.updateNamesPrepareSQL = null; this.deleteNamesPrepareSQL = null; - this.queryNamesPrepareSQL = null; + this.findNamesPrepareSQL = null; } //----------------cache-------------- Cacheable c = type.getAnnotation(Cacheable.class); @@ -458,6 +466,23 @@ public final class EntityInfo { this.tablecopySQL = conf.getProperty(DataSources.JDBC_TABLECOPY_SQLTEMPLATE, "CREATE TABLE ${newtable} LIKE ${oldtable}"); } + @SuppressWarnings("unchecked") + public V getSubobject(String name) { + return (V) this.subobjectMap.get(name); + } + + public void setSubobject(String name, Object value) { + this.subobjectMap.put(name, value); + } + + public void removeSubobject(String name) { + this.subobjectMap.remove(name); + } + + public void clearSubobjects() { + this.subobjectMap.clear(); + } + /** * 获取JsonConvert * @@ -564,9 +589,19 @@ public final class EntityInfo { * * @return String */ - public String getQueryPrepareSQL(T bean) { - if (this.tableStrategy == null) return queryPrepareSQL; - return queryPrepareSQL.replace("${newtable}", getTable(bean)); + public String getFindPrepareSQL(T bean) { + if (this.tableStrategy == null) return findPrepareSQL; + return findPrepareSQL.replace("${newtable}", getTable(bean)); + } + + /** + * 获取Entity的QUERY SQL + * + * + * @return String + */ + public String getAllQueryPrepareSQL() { + return this.allQueryPrepareSQL; } /** @@ -576,9 +611,9 @@ public final class EntityInfo { * * @return String */ - public String getQueryDollarPrepareSQL(T bean) { - if (this.tableStrategy == null) return queryDollarPrepareSQL; - return queryDollarPrepareSQL.replace("${newtable}", getTable(bean)); + public String getFindDollarPrepareSQL(T bean) { + if (this.tableStrategy == null) return findDollarPrepareSQL; + return findDollarPrepareSQL.replace("${newtable}", getTable(bean)); } /** @@ -588,9 +623,9 @@ public final class EntityInfo { * * @return String */ - public String getQueryNamesPrepareSQL(T bean) { - if (this.tableStrategy == null) return queryNamesPrepareSQL; - return queryNamesPrepareSQL.replace("${newtable}", getTable(bean)); + public String getFindNamesPrepareSQL(T bean) { + if (this.tableStrategy == null) return findNamesPrepareSQL; + return findNamesPrepareSQL.replace("${newtable}", getTable(bean)); } /** diff --git a/src/org/redkale/source/PoolJdbcSource.java b/src/org/redkale/source/PoolJdbcSource.java index d4eaa4c80..fc1a83fc4 100644 --- a/src/org/redkale/source/PoolJdbcSource.java +++ b/src/org/redkale/source/PoolJdbcSource.java @@ -64,6 +64,16 @@ public class PoolJdbcSource extends PoolSource { }; } + public static boolean checkSource(Properties property) { + final String source = sourceImpl(property.getProperty(JDBC_SOURCE, property.getProperty(JDBC_DRIVER)), property.getProperty(JDBC_URL)); + try { + Thread.currentThread().getContextClassLoader().loadClass(source); + } catch (Exception e) { + return false; + } + return true; + } + private static ConnectionPoolDataSource createDataSource(Properties property) { try { return createDataSource(property.getProperty(JDBC_SOURCE, property.getProperty(JDBC_DRIVER)), @@ -73,7 +83,7 @@ public class PoolJdbcSource extends PoolSource { } } - private static ConnectionPoolDataSource createDataSource(String source0, String url, String user, String password) throws Exception { + private static String sourceImpl(String source0, String url) { String source = source0; if (source0 == null || source0.isEmpty()) { if (url.startsWith("jdbc:mysql:")) { @@ -98,12 +108,12 @@ public class PoolJdbcSource extends PoolSource { case "com.mysql.cj.jdbc.Driver": case "com.mysql.jdbc.Driver": try { - Thread.currentThread().getContextClassLoader().loadClass("com.mysql.cj.jdbc.MysqlConnectionPoolDataSource"); - source = "com.mysql.cj.jdbc.MysqlConnectionPoolDataSource"; - } catch (Throwable e) { - source = "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource"; - } - break; + Thread.currentThread().getContextClassLoader().loadClass("com.mysql.cj.jdbc.MysqlConnectionPoolDataSource"); + source = "com.mysql.cj.jdbc.MysqlConnectionPoolDataSource"; + } catch (Throwable e) { + source = "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource"; + } + break; case "oracle.jdbc.driver.OracleDriver": source = "oracle.jdbc.pool.OracleConnectionPoolDataSource"; break; @@ -118,6 +128,11 @@ public class PoolJdbcSource extends PoolSource { break; } } + return source; + } + + private static ConnectionPoolDataSource createDataSource(String source0, String url, String user, String password) throws Exception { + final String source = sourceImpl(source0, url); final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(source); Object pdsource = clazz.getDeclaredConstructor().newInstance(); if (source.contains(".postgresql.")) { diff --git a/src/org/redkale/source/PoolSource.java b/src/org/redkale/source/PoolSource.java index 5f3b27453..f7c649771 100644 --- a/src/org/redkale/source/PoolSource.java +++ b/src/org/redkale/source/PoolSource.java @@ -33,16 +33,16 @@ public abstract class PoolSource { protected final AtomicLong saveCounter = new AtomicLong(); - protected final Semaphore semaphore; - protected final Logger logger; protected final String rwtype; // "" 或 "read" 或 "write" - protected final int maxconns; - protected final String dbtype; + protected int maxconns; + + protected Semaphore semaphore; + protected int connectTimeoutSeconds; protected int readTimeoutSeconds; @@ -77,7 +77,7 @@ public abstract class PoolSource { this.connectTimeoutSeconds = Integer.decode(prop.getProperty(JDBC_CONNECTTIMEOUT_SECONDS, "6")); this.readTimeoutSeconds = Integer.decode(prop.getProperty(JDBC_READTIMEOUT_SECONDS, "6")); this.writeTimeoutSeconds = Integer.decode(prop.getProperty(JDBC_WRITETIMEOUT_SECONDS, "6")); - this.maxconns = Math.max(8, Integer.decode(prop.getProperty(JDBC_CONNECTIONS_LIMIT, "" + Runtime.getRuntime().availableProcessors() * 100))); + this.maxconns = Math.max(8, Integer.decode(prop.getProperty(JDBC_CONNECTIONS_LIMIT, "" + Runtime.getRuntime().availableProcessors()))); this.semaphore = semaphore == null ? new Semaphore(this.maxconns) : semaphore; String dbtype0 = ""; { //jdbc:mysql:// jdbc:microsoft:sqlserver:// 取://之前的到最后一个:之间的字符串 @@ -160,6 +160,22 @@ public abstract class PoolSource { public abstract void close(); + public Semaphore getSemaphore() { + return semaphore; + } + + public void setSemaphore(Semaphore semaphore) { + this.semaphore = semaphore; + } + + public int getMaxconns() { + return maxconns; + } + + public void setMaxconns(int maxconns) { + this.maxconns = maxconns; + } + public final String getDbtype() { return dbtype; } @@ -184,10 +200,6 @@ public abstract class PoolSource { return saveCounter.longValue(); } - public final int getMaxconns() { - return maxconns; - } - public final int getConnectTimeoutSeconds() { return connectTimeoutSeconds; } diff --git a/src/org/redkale/source/PoolTcpSource.java b/src/org/redkale/source/PoolTcpSource.java index 828c05904..f195b49bd 100644 --- a/src/org/redkale/source/PoolTcpSource.java +++ b/src/org/redkale/source/PoolTcpSource.java @@ -5,16 +5,15 @@ */ package org.redkale.source; -import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.*; +import java.nio.channels.CompletionHandler; import java.sql.*; import java.util.*; import java.util.concurrent.*; import java.util.logging.*; -import org.redkale.net.AsyncConnection; +import org.redkale.net.*; import static org.redkale.source.DataSources.*; -import org.redkale.util.ObjectPool; +import org.redkale.util.*; /** * @@ -22,38 +21,21 @@ import org.redkale.util.ObjectPool; */ public abstract class PoolTcpSource extends PoolSource { - //ByteBuffer池 - protected ObjectPool bufferPool; - - //线程池 - protected ThreadPoolExecutor executor; - - //供supplyAsync->poll使用的线程池 - protected ExecutorService pollExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4, (r) -> { - Thread t = new Thread(r); - t.setDaemon(true); - return t; - }); - - //TCP Channel组 - protected AsynchronousChannelGroup group; + protected AsyncGroup asyncGroup; protected ScheduledThreadPoolExecutor scheduler; - protected final ArrayBlockingQueue connQueue; + protected ArrayBlockingQueue> pollQueue; - public PoolTcpSource(String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop, Logger logger, ObjectPool bufferPool, ThreadPoolExecutor executor) { + protected ArrayBlockingQueue connQueue; + + public PoolTcpSource(AsyncGroup asyncGroup, String rwtype, ArrayBlockingQueue queue, Semaphore semaphore, Properties prop, Logger logger) { super(rwtype, semaphore, prop, logger); - this.bufferPool = bufferPool; - this.executor = executor; - try { - this.group = AsynchronousChannelGroup.withThreadPool(executor); - } catch (IOException e) { - throw new RuntimeException(e); - } + this.asyncGroup = asyncGroup; this.connQueue = queue == null ? new ArrayBlockingQueue<>(this.maxconns) : queue; + this.pollQueue = new ArrayBlockingQueue(this.connQueue.remainingCapacity() * 1000); this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { - final Thread t = new Thread(r, "PoolSource-Scheduled-Thread"); + final Thread t = new Thread(r, "Redkale-PoolSource-Scheduled-Thread"); t.setDaemon(true); return t; }); @@ -62,6 +44,17 @@ public abstract class PoolTcpSource extends PoolSource { }, 60, 30, TimeUnit.SECONDS); } + public void updateConnQueue(ArrayBlockingQueue queue) { + ArrayBlockingQueue old = this.connQueue; + this.connQueue = queue; + AsyncConnection conn; + final Semaphore localSemaphore = this.semaphore; + while ((conn = old.poll()) != null) { + queue.offer(conn); + if (localSemaphore != null) localSemaphore.tryAcquire(); + } + } + private void runPingTask() { try { if (connQueue.isEmpty()) return; @@ -93,23 +86,28 @@ public abstract class PoolTcpSource extends PoolSource { @Override public void offerConnection(final AsyncConnection conn) { if (conn == null) return; - if (conn.isOpen() && connQueue.offer(conn)) { - saveCounter.incrementAndGet(); - usingCounter.decrementAndGet(); + if (conn.isOpen()) { + CompletableFuture future = pollQueue.poll(); + if (future != null && future.complete(conn)) return; + + if (connQueue.offer(conn)) { + saveCounter.incrementAndGet(); + usingCounter.decrementAndGet(); + return; + } + } + //usingCounter 会在close方法中执行 + CompletableFuture closefuture = null; + try { + closefuture = sendCloseCommand(conn); + } catch (Exception e) { + } + if (closefuture == null) { + conn.dispose(); } else { - //usingCounter 会在close方法中执行 - CompletableFuture future = null; - try { - future = sendCloseCommand(conn); - } catch (Exception e) { - } - if (future == null) { - conn.dispose(); - } else { - future.whenComplete((c, t) -> { - if (c != null) c.dispose(); - }); - } + closefuture.whenComplete((c, t) -> { + if (c != null) c.dispose(); + }); } } @@ -130,22 +128,12 @@ public abstract class PoolTcpSource extends PoolSource { return pollAsync().join(); } - protected abstract ByteBuffer reqConnectBuffer(AsyncConnection conn); + protected abstract ByteArray reqConnectBuffer(AsyncConnection conn); protected abstract void respConnectBuffer(final ByteBuffer buffer, CompletableFuture future, AsyncConnection conn); @Override public CompletableFuture pollAsync() { - return pollAsync(0); - } - - protected CompletableFuture pollAsync(final int count) { - if (count >= 5) { - logger.log(Level.WARNING, "create datasource connection error"); - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new SQLException("create datasource connection error")); - return future; - } AsyncConnection conn0 = connQueue.poll(); if (conn0 != null && conn0.isOpen()) { @@ -153,34 +141,24 @@ public abstract class PoolTcpSource extends PoolSource { usingCounter.incrementAndGet(); return CompletableFuture.completedFuture(conn0); } - - if (!semaphore.tryAcquire()) { - return CompletableFuture.supplyAsync(() -> { - try { - return connQueue.poll(1, TimeUnit.SECONDS); - } catch (Exception t) { - return null; - } - }, pollExecutor).thenCompose((conn2) -> { - if (conn2 != null && conn2.isOpen()) { - cycleCounter.incrementAndGet(); - usingCounter.incrementAndGet(); - return CompletableFuture.completedFuture(conn2); - } - return pollAsync(count + 1); - }); + final Semaphore localSemaphore = this.semaphore; + if (!localSemaphore.tryAcquire()) { + final CompletableFuture future = Utility.orTimeout(new CompletableFuture<>(), 10, TimeUnit.SECONDS); + future.whenComplete((r, t) -> pollQueue.remove(future)); + if (pollQueue.offer(future)) return future; + future.completeExceptionally(new SQLException("create datasource connection error")); + return future; } - - return AsyncConnection.createTCP(bufferPool, group, this.servaddr, this.readTimeoutSeconds, this.writeTimeoutSeconds).thenCompose(conn -> { + return asyncGroup.createTCP(this.servaddr, this.readTimeoutSeconds, this.writeTimeoutSeconds).thenCompose(conn -> { conn.beforeCloseListener((c) -> { - semaphore.release(); + localSemaphore.release(); closeCounter.incrementAndGet(); usingCounter.decrementAndGet(); }); CompletableFuture future = new CompletableFuture(); - final ByteBuffer buffer = reqConnectBuffer(conn); - - if (buffer == null) { + if (conn.getSubobject() == null) conn.setSubobject(new ByteArray()); + final ByteArray array = reqConnectBuffer(conn); + if (array == null) { conn.read(new CompletionHandler() { @Override public void completed(Integer result, ByteBuffer rbuffer) { @@ -200,19 +178,13 @@ public abstract class PoolTcpSource extends PoolSource { } }); } else { - conn.write(buffer, null, new CompletionHandler() { + conn.write(array, new CompletionHandler() { @Override - public void completed(Integer result, Void attachment1) { - if (result < 0) { - failed(new SQLException("Write Buffer Error"), attachment1); + public void completed(Integer result0, Void attachment0) { + if (result0 < 0) { + failed(new SQLException("Write Buffer Error"), attachment0); return; } - if (buffer.hasRemaining()) { - conn.write(buffer, attachment1, this); - return; - } - buffer.clear(); - conn.setReadBuffer(buffer); conn.read(new CompletionHandler() { @Override public void completed(Integer result, ByteBuffer rbuffer) { @@ -220,8 +192,8 @@ public abstract class PoolTcpSource extends PoolSource { failed(new SQLException("Read Buffer Error"), rbuffer); return; } - buffer.flip(); - respConnectBuffer(buffer, future, conn); + rbuffer.flip(); + respConnectBuffer(rbuffer, future, conn); } @Override @@ -234,8 +206,7 @@ public abstract class PoolTcpSource extends PoolSource { } @Override - public void failed(Throwable exc, Void attachment1) { - bufferPool.accept(buffer); + public void failed(Throwable exc, Void attachment0) { future.completeExceptionally(exc); conn.dispose(); } @@ -247,14 +218,31 @@ public abstract class PoolTcpSource extends PoolSource { creatCounter.incrementAndGet(); usingCounter.incrementAndGet(); } else { - semaphore.release(); + localSemaphore.release(); } }); } + public ArrayBlockingQueue> getPollQueue() { + return pollQueue; + } + + public void setPollQueue(ArrayBlockingQueue> pollQueue) { + this.pollQueue = pollQueue; + } + + public ArrayBlockingQueue getConnQueue() { + return connQueue; + } + + public void setConnQueue(ArrayBlockingQueue connQueue) { + this.connQueue = connQueue; + } + @Override public void close() { this.scheduler.shutdownNow(); + final List futures = new ArrayList<>(); connQueue.stream().forEach(x -> { CompletableFuture future = null; try { @@ -264,11 +252,14 @@ public abstract class PoolTcpSource extends PoolSource { if (future == null) { x.dispose(); } else { - future.whenComplete((c, t) -> { + futures.add(future.whenComplete((c, t) -> { if (c != null) c.dispose(); - }); + })); } }); + if (!futures.isEmpty()) { + CompletableFuture.allOf(new CompletableFuture[futures.size()]).join(); + } } protected abstract CompletableFuture sendPingCommand(final AsyncConnection conn); diff --git a/src/org/redkale/source/VirtualEntity.java b/src/org/redkale/source/VirtualEntity.java index f293bf54b..344b0748c 100644 --- a/src/org/redkale/source/VirtualEntity.java +++ b/src/org/redkale/source/VirtualEntity.java @@ -9,6 +9,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.*; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.*; /** @@ -36,16 +37,16 @@ public @interface VirtualEntity { * * @return Class */ - Class> loader() default DefaultFunctionLoader.class; + Class>> loader() default DefaultFunctionLoader.class; /** * 默认全量加载器 * */ - public static class DefaultFunctionLoader implements BiFunction { + public static class DefaultFunctionLoader implements BiFunction> { @Override - public List apply(DataSource source, Class type) { + public CompletableFuture apply(DataSource source, EntityInfo info) { return null; } } diff --git a/src/org/redkale/util/ByteArray.java b/src/org/redkale/util/ByteArray.java index 8ab31b503..089060d3d 100644 --- a/src/org/redkale/util/ByteArray.java +++ b/src/org/redkale/util/ByteArray.java @@ -17,7 +17,7 @@ import java.util.Arrays; * * @author zhangjx */ -public final class ByteArray { +public final class ByteArray implements ByteTuple { private byte[] content; @@ -31,11 +31,19 @@ public final class ByteArray { content = new byte[Math.max(128, size)]; } + public ByteArray(ByteTuple tuple) { + content = tuple.content(); + count = tuple.length(); + } + /** * 清空数据,将count置为0,并不清掉byte[]的内容 + * + * @return ByteArray 是否相同 */ - public void clear() { + public ByteArray clear() { this.count = 0; + return this; } /** @@ -67,12 +75,18 @@ public final class ByteArray { * * @return 长度 */ - public int size() { + @Override + public int length() { return count; } + @Override + public int offset() { + return 0; + } + /** - * 获取指定位置的byte值,须确保0 <= index < size + * 获取指定位置的byte值,须确保0 <= index < length * * @param index 位置 * @@ -82,6 +96,51 @@ public final class ByteArray { return content[index]; } + /** + * 获取指定位置的char值,须确保0 <= offset+2 < length + * + * @param offset 位置 + * + * @return short值 + */ + public char getChar(int offset) { + return (char) ((0xff00 & (content[offset] << 8)) | (0xff & content[offset + 1])); + } + + /** + * 获取指定位置的short值,须确保0 <= offset+2 < length + * + * @param offset 位置 + * + * @return short值 + */ + public int getShort(int offset) { + return (short) ((0xff00 & (content[offset] << 8)) | (0xff & content[offset + 1])); + } + + /** + * 获取指定位置的int值,须确保0 <= offset+4 < length + * + * @param offset 位置 + * + * @return int值 + */ + public int getInt(int offset) { + return ((content[offset] & 0xff) << 24) | ((content[offset + 1] & 0xff) << 16) | ((content[offset + 2] & 0xff) << 8) | (content[offset + 3] & 0xff); + } + + /** + * 获取指定位置的long值,须确保0 <= offset+8 < length + * + * @param offset 位置 + * + * @return long值 + */ + public long getLong(int offset) { + return (((long) content[offset] & 0xff) << 56) | (((long) content[offset + 1] & 0xff) << 48) | (((long) content[offset + 2] & 0xff) << 40) | (((long) content[offset + 3] & 0xff) << 32) + | (((long) content[offset + 4] & 0xff) << 24) | (((long) content[offset + 5] & 0xff) << 16) | (((long) content[offset + 6] & 0xff) << 8) | ((long) content[offset + 7] & 0xff); + } + /** * 获取最后一个字节值,调用前须保证count大于0 * @@ -91,6 +150,14 @@ public final class ByteArray { return content[count - 1]; } + /** + * count减一,调用前须保证count大于0 + * + */ + public void backCount() { + count--; + } + /** * 将buf内容覆盖到本对象内容中 * @@ -101,7 +168,26 @@ public final class ByteArray { } /** - * 将array的内容引用复制给本对象 + * 将ByteBuffer的内容读取到本对象中 + * + * @param buffer ByteBuffer + */ + public void put(ByteBuffer buffer) { + if (buffer == null) return; + int remain = buffer.remaining(); + if (remain == 0) return; + int l = this.content.length - count; + if (remain > l) { + byte[] ns = new byte[this.content.length + remain]; + if (count > 0) System.arraycopy(content, 0, ns, 0, count); + this.content = ns; + } + buffer.get(content, count, remain); + count += remain; + } + + /** + * 将array的内容引用给本对象 * * @param array ByteArray */ @@ -112,6 +198,17 @@ public final class ByteArray { } } + /** + * 将content的内容直接给本对象 + * + * @param content 内容 + * @param count 长度 + */ + public void directFrom(byte[] content, int count) { + this.content = content; + this.count = count; + } + /** * 将本对象的内容引用复制给array * @@ -125,11 +222,12 @@ public final class ByteArray { } /** - * 直接获取全部数据, 实际数据需要根据size长度来截取 + * 直接获取全部数据, 实际数据需要根据length长度来截取 * * @return byte[] */ - public byte[] directBytes() { + @Override + public byte[] content() { return content; } @@ -241,24 +339,162 @@ public final class ByteArray { if (count > 0) count--; } + /** + * 写入一个char值 + * + * @param value int值 + */ + public void putChar(char value) { + this.put((byte) (value >> 8 & 0xFF), + (byte) (value & 0xFF)); + } + + /** + * 写入一个char值, content.length 必须不能小于offset+2 + * + * @param offset 偏移量 + * @param value char值 + */ + public void putChar(int offset, char value) { + this.put(offset, (byte) (value >> 8 & 0xFF), + (byte) (value & 0xFF)); + } + + /** + * 写入一个short值 + * + * @param value short值 + */ + public void putShort(short value) { + this.put((byte) (value >> 8 & 0xFF), + (byte) (value & 0xFF)); + } + + /** + * 写入一个short值, content.length 必须不能小于offset+2 + * + * @param offset 偏移量 + * @param value short值 + */ + public void putShort(int offset, short value) { + this.put(offset, (byte) (value >> 8 & 0xFF), + (byte) (value & 0xFF)); + } + /** * 写入一个int值 * * @param value int值 */ - public void writeInt(int value) { - write((byte) (value >> 24 & 0xFF), + public void putInt(int value) { + this.put((byte) (value >> 24 & 0xFF), (byte) (value >> 16 & 0xFF), (byte) (value >> 8 & 0xFF), (byte) (value & 0xFF)); } + /** + * 指定位置写入一个int值, content.length 必须不能小于offset+4 + * + * @param offset 偏移量 + * @param value int值 + */ + public void putInt(int offset, int value) { + content[offset] = (byte) (value >> 24 & 0xFF); + content[offset + 1] = (byte) (value >> 16 & 0xFF); + content[offset + 2] = (byte) (value >> 8 & 0xFF); + content[offset + 3] = (byte) (value & 0xFF); + } + + /** + * 写入一个float值 + * + * @param value float值 + */ + public void putFloat(float value) { + this.putInt(Float.floatToIntBits(value)); + } + + /** + * 指定位置写入一个float值, content.length 必须不能小于offset+4 + * + * @param offset 偏移量 + * @param value float值 + */ + public void putFloat(int offset, float value) { + this.putInt(offset, Float.floatToIntBits(value)); + } + + /** + * 写入一个long值 + * + * @param value long值 + */ + public void putLong(long value) { + this.put((byte) (value >> 56 & 0xFF), + (byte) (value >> 48 & 0xFF), + (byte) (value >> 40 & 0xFF), + (byte) (value >> 32 & 0xFF), + (byte) (value >> 24 & 0xFF), + (byte) (value >> 16 & 0xFF), + (byte) (value >> 8 & 0xFF), + (byte) (value & 0xFF)); + } + + /** + * 指定位置写入一个long值, content.length 必须不能小于offset+8 + * + * @param offset 偏移量 + * @param value long值 + */ + public void putLong(int offset, long value) { + this.put(offset, (byte) (value >> 56 & 0xFF), + (byte) (value >> 48 & 0xFF), + (byte) (value >> 40 & 0xFF), + (byte) (value >> 32 & 0xFF), + (byte) (value >> 24 & 0xFF), + (byte) (value >> 16 & 0xFF), + (byte) (value >> 8 & 0xFF), + (byte) (value & 0xFF)); + } + + /** + * 写入一个double值 + * + * @param value double值 + */ + public void putDouble(double value) { + this.putLong(Double.doubleToLongBits(value)); + } + + /** + * 指定位置写入一个double值, content.length 必须不能小于offset+8 + * + * @param offset 偏移量 + * @param value double值 + */ + public void putDouble(int offset, double value) { + this.putLong(offset, Double.doubleToLongBits(value)); + } + + public void putByte(short value) { + put((byte) value); + } + + public void putByte(char value) { + put((byte) value); + } + + public void putByte(int value) { + put((byte) value); + } + /** * 写入一个byte值 * * @param value byte值 */ - public void write(byte value) { + public void put(byte value) { if (count >= content.length - 1) { byte[] ns = new byte[content.length + 8]; System.arraycopy(content, 0, ns, 0, count); @@ -267,12 +503,32 @@ public final class ByteArray { content[count++] = value; } + /** + * 写入一个byte值, content.length 必须不能小于offset+1 + * + * @param offset 偏移量 + * @param value byte值 + */ + public void putByte(int offset, byte value) { + content[offset] = value; + } + + /** + * 写入一个byte值, content.length 必须不能小于offset+1 + * + * @param offset 偏移量 + * @param value byte值 + */ + public void putByte(int offset, int value) { + content[offset] = (byte) value; + } + /** * 写入一组byte值 * * @param values 一组byte值 */ - public void write(byte... values) { + public void put(byte... values) { if (count >= content.length - values.length) { byte[] ns = new byte[content.length + values.length]; System.arraycopy(content, 0, ns, 0, count); @@ -282,13 +538,52 @@ public final class ByteArray { count += values.length; } + /** + * 指定位置写入一组byte值, content.length 必须不能小于offset+values.length + * + * @param offset 偏移量 + * @param values 一组byte值 + */ + public void put(int offset, byte... values) { + System.arraycopy(values, 0, content, offset, values.length); + } + + /** + * 写入一组byte值 + * + * @param values 一组byte值 + * @param offset 偏移量 + * @param length 长度 + */ + public void put(byte[] values, int offset, int length) { + if (count >= content.length - length) { + byte[] ns = new byte[content.length + Math.max(16, length)]; + System.arraycopy(content, 0, ns, 0, count); + this.content = ns; + } + System.arraycopy(values, offset, content, count, length); + count += length; + } + + /** + * 写入一组byte值, content.length 必须不能小于poffset+length + * + * @param poffset 偏移量 + * @param values 一组byte值 + * @param offset 偏移量 + * @param length 长度 + */ + public void put(int poffset, byte[] values, int offset, int length) { + System.arraycopy(values, offset, content, poffset, length); + } + /** * 写入ByteBuffer指定长度的数据 * * @param buffer 数据 * @param len 指定长度 */ - public void write(ByteBuffer buffer, int len) { + public void put(ByteBuffer buffer, int len) { if (len < 1) return; if (count >= content.length - len) { byte[] ns = new byte[content.length + len]; @@ -342,6 +637,28 @@ public final class ByteArray { return new String(content, offset, len, charset); } + /** + * 将指定的起始位置和长度按指定字符集转成字符串,并trim + * + * @param offset 起始位置 + * @param len 长度 + * @param charset 字符集 + * + * @return 字符串 + */ + public String toTrimString(int offset, int len, final Charset charset) { + if (len == 0) return ""; + int st = 0; + while (st < len && (content[offset] & 0xff) <= ' ') { + offset++; + st++; + } + while (len > 0 && (content[len - 1] & 0xff) <= ' ') len--; + if (len == 0) return ""; + if (charset == null) return new String(content, offset, len - st, StandardCharsets.UTF_8); + return new String(content, offset, len - st, charset); + } + /** * 将指定的起始位置和长度按指定字符集并转义后转成字符串 * diff --git a/src/org/redkale/util/ByteBufferWriter.java b/src/org/redkale/util/ByteBufferWriter.java index ca90cf9ed..5938a05e7 100644 --- a/src/org/redkale/util/ByteBufferWriter.java +++ b/src/org/redkale/util/ByteBufferWriter.java @@ -24,6 +24,8 @@ public class ByteBufferWriter { private int position; + private int writeBytesCounter = 0; //put(byte[] src, int offset, int length) 调用的次数 + private boolean bigEndian = true; protected ByteBufferWriter(Supplier supplier) { @@ -34,12 +36,23 @@ public class ByteBufferWriter { return new ByteBufferWriter(supplier); } + public static ByteBufferWriter create(Supplier supplier, ByteBuffer one) { + ByteBufferWriter writer = new ByteBufferWriter(supplier); + writer.bigEndian = one.order() == ByteOrder.BIG_ENDIAN; + writer.buffers = new ByteBuffer[]{one}; + return writer; + } + public static ByteBuffer[] toBuffers(Supplier supplier, byte[] content) { - return new ByteBufferWriter(supplier).put(content, 0, content.length).toBuffers(); + ByteBufferWriter writer = new ByteBufferWriter(supplier); + writer.put(false, content, 0, content.length); + return writer.toBuffers(); } public static ByteBuffer[] toBuffers(Supplier supplier, byte[] content, int offset, int length) { - return new ByteBufferWriter(supplier).put(content, offset, length).toBuffers(); + ByteBufferWriter writer = new ByteBufferWriter(supplier); + writer.put(false, content, offset, length); + return writer.toBuffers(); } private ByteBuffer getLastBuffer(int size) { @@ -163,11 +176,30 @@ public class ByteBufferWriter { return this; } - public ByteBufferWriter put(byte[] src) { - return put(src, 0, src.length); + public int put(byte[] src) { + return put(true, src, 0, src.length); } - public ByteBufferWriter put(byte[] src, int offset, int length) { + public int put(byte[] src, int offset, int length) { + return put(true, src, offset, length); + } + + public int put(byte[] src, int offset, int length, byte[] src2, int offset2, int length2) { + ByteBuffer buf = getLastBuffer(1); + int remain = buf.remaining(); + if (remain >= length + length2) { + buf.put(src, offset, length); + if (src2 != null) buf.put(src2, offset2, length2); + position += length + length2; + this.writeBytesCounter++; + } else { + put(true, src, offset, length); + if (src2 != null) put(false, src2, offset2, length2); + } + return writeBytesCounter; + } + + private int put(boolean outside, byte[] src, int offset, int length) { ByteBuffer buf = getLastBuffer(1); int remain = buf.remaining(); if (remain >= length) { @@ -176,9 +208,13 @@ public class ByteBufferWriter { } else { buf.put(src, offset, remain); position += remain; - put(src, offset + remain, length - remain); + put(false, src, offset + remain, length - remain); } - return this; + if (outside) this.writeBytesCounter++; + return writeBytesCounter; } + public int getWriteBytesCounter() { + return this.writeBytesCounter; + } } diff --git a/src/org/redkale/util/ByteTuple.java b/src/org/redkale/util/ByteTuple.java new file mode 100644 index 000000000..70d43d966 --- /dev/null +++ b/src/org/redkale/util/ByteTuple.java @@ -0,0 +1,24 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +/** + * 简单的byte[]数据接口。 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.3.0 + */ +public interface ByteTuple { + + public byte[] content(); + + public int offset(); + + public int length(); +} diff --git a/src/org/redkale/util/DLong.java b/src/org/redkale/util/DLong.java index 70e615f68..7481f6895 100644 --- a/src/org/redkale/util/DLong.java +++ b/src/org/redkale/util/DLong.java @@ -57,6 +57,16 @@ public final class DLong extends Number implements Comparable { return buffer; } + public static ByteArray write(ByteArray array, DLong dlong) { + array.put(dlong.value); + return array; + } + + public static ByteArray write(ByteArray array, int offset, DLong dlong) { + array.put(offset, dlong.value); + return array; + } + public boolean equals(byte[] bytes) { return Arrays.equals(this.value, bytes); } @@ -88,13 +98,13 @@ public final class DLong extends Number implements Comparable { @Override public long longValue() { return ((((long) value[8] & 0xff) << 56) - | (((long) value[9] & 0xff) << 48) - | (((long) value[10] & 0xff) << 40) - | (((long) value[11] & 0xff) << 32) - | (((long) value[12] & 0xff) << 24) - | (((long) value[13] & 0xff) << 16) - | (((long) value[14] & 0xff) << 8) - | (((long) value[15] & 0xff))); + | (((long) value[9] & 0xff) << 48) + | (((long) value[10] & 0xff) << 40) + | (((long) value[11] & 0xff) << 32) + | (((long) value[12] & 0xff) << 24) + | (((long) value[13] & 0xff) << 16) + | (((long) value[14] & 0xff) << 8) + | (((long) value[15] & 0xff))); } @Override diff --git a/src/org/redkale/util/ObjectPool.java b/src/org/redkale/util/ObjectPool.java index 8530dcc84..e1a6abdfe 100644 --- a/src/org/redkale/util/ObjectPool.java +++ b/src/org/redkale/util/ObjectPool.java @@ -39,6 +39,8 @@ public class ObjectPool implements Supplier, Consumer { protected final Queue queue; + protected final boolean unsafeDequeable; + protected final ObjectPool parent; protected ObjectPool(ObjectPool parent, AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator creator, Consumer prepare, Predicate recycler, Queue queue) { @@ -51,6 +53,7 @@ public class ObjectPool implements Supplier, Consumer { this.max = max; this.debug = logger.isLoggable(Level.FINEST); this.queue = queue; + this.unsafeDequeable = queue instanceof ArrayDeque; } //非线程安全版 @@ -192,9 +195,11 @@ public class ObjectPool implements Supplier, Consumer { public T get() { T result = queue.poll(); if (result == null) { - if (creatCounter != null) creatCounter.incrementAndGet(); if (parent != null) result = parent.queue.poll(); - if (result == null) result = this.creator.create(); + if (result == null) { + if (creatCounter != null) creatCounter.incrementAndGet(); + result = this.creator.create(); + } } if (prepare != null) prepare.accept(result); return result; @@ -213,7 +218,8 @@ public class ObjectPool implements Supplier, Consumer { // } // } // } - if (!queue.offer(e) && parent != null) parent.accept(e); + boolean rs = unsafeDequeable ? queue.size() < max && queue.offer(e) : queue.offer(e); + if (!rs && parent != null) parent.accept(e); } } diff --git a/src/org/redkale/util/ThreadHashExecutor.java b/src/org/redkale/util/ThreadHashExecutor.java index ea0fd6806..84c08ec66 100644 --- a/src/org/redkale/util/ThreadHashExecutor.java +++ b/src/org/redkale/util/ThreadHashExecutor.java @@ -19,35 +19,42 @@ import java.util.concurrent.atomic.AtomicInteger; * * @since 2.1.0 */ -public class ThreadHashExecutor { - - private final AtomicInteger index = new AtomicInteger(); +public class ThreadHashExecutor extends AbstractExecutorService { private final LinkedBlockingQueue[] queues; - private final ExecutorService[] executors; + private final ThreadPoolExecutor[] executors; + + public ThreadHashExecutor() { + this(Runtime.getRuntime().availableProcessors(), null); + } public ThreadHashExecutor(int size) { - ExecutorService[] array = new ExecutorService[size]; + this(size, null); + } + + public ThreadHashExecutor(int size, ThreadFactory factory) { + ThreadPoolExecutor[] array = new ThreadPoolExecutor[size]; LinkedBlockingQueue[] ques = new LinkedBlockingQueue[size]; final AtomicInteger counter = new AtomicInteger(); for (int i = 0; i < array.length; i++) { LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); ques[i] = queue; array[i] = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, queue, - (Runnable r) -> { - Thread t = new Thread(r); - t.setDaemon(true); - t.setName("Redkale-HashThread-" + counter.incrementAndGet()); - return t; - }); + factory == null ? (Runnable r) -> { + Thread t = new Thread(r); + t.setDaemon(true); + int c = counter.incrementAndGet(); + t.setName("Redkale-HashThread-" + (c > 9 ? c : ("0" + c))); + return t; + } : factory); } this.queues = ques; this.executors = array; } - public void execute(int hash, Runnable command) { - if (hash < 1) { + private ExecutorService hashExecutor(int hash) { + if (hash == 0) { int k = 0; int minsize = queues[0].size(); for (int i = 1; i < queues.length; i++) { @@ -57,12 +64,58 @@ public class ThreadHashExecutor { k = i; } } - this.executors[k].execute(command); + return this.executors[k]; } else { - this.executors[hash % this.executors.length].execute(command); + return this.executors[(hash < 0 ? -hash : hash) % this.executors.length]; } } + public void setThreadFactory(ThreadFactory factory) { + for (ThreadPoolExecutor executor : this.executors) { + executor.setThreadFactory(factory); + } + } + + public int size() { + return executors.length; + } + + @Override + public void execute(Runnable command) { + hashExecutor(0).execute(command); + } + + public void execute(int hash, Runnable command) { + hashExecutor(hash).execute(command); + } + + @Override + public Future submit(Runnable task) { + return hashExecutor(0).submit(task); + } + + public Future submit(int hash, Runnable task) { + return hashExecutor(hash).submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return hashExecutor(0).submit(task, result); + } + + public Future submit(int hash, Runnable task, T result) { + return hashExecutor(hash).submit(task, result); + } + + @Override + public Future submit(Callable task) { + return hashExecutor(0).submit(task); + } + + public Future submit(int hash, Callable task) { + return hashExecutor(hash).submit(task); + } + public int waitingSize() { int wsize = queues[0].size(); for (int i = 1; i < queues.length; i++) { @@ -71,12 +124,14 @@ public class ThreadHashExecutor { return wsize; } + @Override public void shutdown() { for (ExecutorService executor : this.executors) { executor.shutdown(); } } + @Override public List shutdownNow() { List list = new ArrayList<>(); for (ExecutorService executor : this.executors) { @@ -85,11 +140,38 @@ public class ThreadHashExecutor { return list; } + @Override public boolean isShutdown() { return this.executors[0].isShutdown(); } - public int size() { - return executors.length; + @Override + public boolean isTerminated() { + return this.executors[0].isTerminated(); + } + + @Override + public boolean awaitTermination(long l, TimeUnit tu) throws InterruptedException { + return this.executors[0].awaitTermination(l, tu); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return hashExecutor(0).invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return hashExecutor(0).invokeAny(tasks, timeout, unit); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return hashExecutor(0).invokeAll(tasks); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { + return hashExecutor(0).invokeAll(tasks, timeout, unit); } } diff --git a/src/org/redkale/util/Utility.java b/src/org/redkale/util/Utility.java index 984bcbe27..b03c1316c 100644 --- a/src/org/redkale/util/Utility.java +++ b/src/org/redkale/util/Utility.java @@ -7,13 +7,14 @@ package org.redkale.util; import java.io.*; import java.lang.reflect.*; import java.net.*; -import java.nio.ByteBuffer; +import java.nio.*; import java.nio.channels.CompletionHandler; import java.nio.charset.*; import static java.nio.charset.StandardCharsets.UTF_8; import java.security.*; import java.time.*; import java.util.*; +import java.util.concurrent.*; import java.util.function.*; import java.util.zip.GZIPInputStream; import javax.net.ssl.*; @@ -37,145 +38,252 @@ public final class Utility { private static final char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static final boolean greatejdk8; + + private static final ScheduledThreadPoolExecutor futureDelayer; + + //复制JDK9+ java.util.concurrent.CompletableFuture.Canceller + private static final class FutureCanceller implements BiConsumer { + + final Future f; + + FutureCanceller(Future f) { + this.f = f; + } + + public void accept(Object ignore, Throwable ex) { + if (ex == null && f != null && !f.isDone()) + f.cancel(false); + } + } + //复制JDK9+ java.util.concurrent.CompletableFuture.Timeout + + private static final class FutureTimeout implements Runnable { + + final CompletableFuture f; + + FutureTimeout(CompletableFuture f) { + this.f = f; + } + + @Override + public void run() { + if (f != null && !f.isDone()) + f.completeExceptionally(new TimeoutException()); + } + } + /** *

-     * public final class AnonymousCharArrayFunction implements java.util.function.Function<Object, char[]> {
+     * public final class AnonymousUnsafeObjectFunction implements java.util.function.Function<Object, Object> {
      *
-     *      final sun.misc.Unsafe unsafe;
+     *      private final sun.misc.Unsafe unsafe;
      *
-     *      final long fd;
+     *      private final long fd;
      *
-     *      public AnonymousCharArrayFunction(Object obj, long fd) {
+     *      public AnonymousUnsafeObjectFunction(Object obj, long fd) {
      *          this.unsafe = (sun.misc.Unsafe) obj;
      *          this.fd = fd;
      *      }
      *
      *      @Override
-     *      public char[] apply(Object t) {
-     *          return (char[]) unsafe.getObject(t, fd);
+     *      public Object apply(Object t) {
+     *          return unsafe.getObject(t, fd);
      *      }
      *
      * }
      * 
*/ - private static final String functionCharClassBinary = "cafebabe00000034002d0a00090020070021090008002209000800230a00020024070025" - + "0a00080026070027070028070029010006756e736166650100114c73756e2f6d6973632f556e736166653b01000266640100014a0100063c696e69743e0" - + "10016284c6a6176612f6c616e672f4f626a6563743b4a2956010004436f646501000f4c696e654e756d6265725461626c650100124c6f63616c56617269" - + "61626c655461626c650100047468697301002d4c6f72672f7265646b616c652f7574696c2f416e6f6e796d6f757343686172417272617946756e6374696" - + "f6e3b0100036f626a0100124c6a6176612f6c616e672f4f626a6563743b0100056170706c79010016284c6a6176612f6c616e672f4f626a6563743b295b" - + "4301000174010026284c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b0100095369676e61747572650100454" - + "c6a6176612f6c616e672f4f626a6563743b4c6a6176612f7574696c2f66756e6374696f6e2f46756e6374696f6e3c4c6a6176612f6c616e672f4f626a65" - + "63743b5b433e3b01000a536f7572636546696c6501001f416e6f6e796d6f757343686172417272617946756e6374696f6e2e6a6176610c000f002a01000" - + "f73756e2f6d6973632f556e736166650c000b000c0c000d000e0c002b002c0100025b430c0018001901002b6f72672f7265646b616c652f7574696c2f41" - + "6e6f6e796d6f757343686172417272617946756e6374696f6e0100106a6176612f6c616e672f4f626a65637401001b6a6176612f7574696c2f66756e637" - + "4696f6e2f46756e6374696f6e0100032829560100096765744f626a656374010027284c6a6176612f6c616e672f4f626a6563743b4a294c6a6176612f6c" - + "616e672f4f626a6563743b0021000800090001000a00020010000b000c00000010000d000e000000030001000f0010000100110000005c0003000400000" - + "0122ab700012a2bc00002b500032a20b50004b10000000200120000001200040000001200040013000c0014001100150013000000200003000000120014" - + "001500000000001200160017000100000012000d000e0002000100180019000100110000004700040002000000132ab400032b2ab40004b60005c00006c" - + "00006b00000000200120000000600010000001900130000001600020000001300140015000000000013001a0017000110410018001b0001001100000030" - + "00020002000000062a2bb60007b00000000200120000000600010000000c00130000000c0001000000060014001500000002001c00000002001d001e000" - + "00002001f"; - - private static final Function strCharFunction; - - private static final Function sbCharFunction; + private static final String functionUnsafeObjectBinary = "cafebabe0000003400280a0007001d07001e090006001f09000600" + + "200a00020021070022070023070024010006756e736166650100114c73756e2f6d6973632f556e736166653b01000266640" + + "100014a0100063c696e69743e010016284c6a6176612f6c616e672f4f626a6563743b4a2956010004436f646501000f4c69" + + "6e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c65010004746869730100304c6f72672f7" + + "265646b616c652f7574696c2f416e6f6e796d6f7573556e736166654f626a65637446756e6374696f6e3b0100036f626a01" + + "00124c6a6176612f6c616e672f4f626a6563743b0100056170706c79010026284c6a6176612f6c616e672f4f626a6563743" + + "b294c6a6176612f6c616e672f4f626a6563743b010001740100095369676e61747572650100554c6a6176612f6c616e672f" + + "4f626a6563743b4c6a6176612f7574696c2f66756e6374696f6e2f46756e6374696f6e3c4c6a6176612f6c616e672f4f626" + + "a6563743b4c6a6176612f6c616e672f4f626a6563743b3e3b01000a536f7572636546696c65010022416e6f6e796d6f7573" + + "556e736166654f626a65637446756e6374696f6e2e6a6176610c000d002501000f73756e2f6d6973632f556e736166650c0" + + "009000a0c000b000c0c0026002701002e6f72672f7265646b616c652f7574696c2f416e6f6e796d6f7573556e736166654f" + + "626a65637446756e6374696f6e0100106a6176612f6c616e672f4f626a65637401001b6a6176612f7574696c2f66756e637" + + "4696f6e2f46756e6374696f6e0100032829560100096765744f626a656374010027284c6a6176612f6c616e672f4f626a65" + + "63743b4a294c6a6176612f6c616e672f4f626a6563743b00210006000700010008000200120009000a00000012000b000c0" + + "00000020001000d000e0001000f0000005c00030004000000122ab700012a2bc00002b500032a20b50004b1000000020010" + + "0000001200040000001200040013000c0014001100150011000000200003000000120012001300000000001200140015000" + + "100000012000b000c00020001001600170001000f00000041000400020000000d2ab400032b2ab40004b60005b000000002" + + "00100000000600010000001900110000001600020000000d0012001300000000000d0018001500010002001900000002001" + + "a001b00000002001c"; /** *
-     * public final class AnonymousByteArrayFunction implements java.util.function.Function<Object, byte[]> {
+     * public final class AnonymousUnsafeLongFunction implements java.util.function.ToLongFunction<Object> {
      *
-     *      final sun.misc.Unsafe unsafe;
+     *      private final sun.misc.Unsafe unsafe;
      *
-     *      final long fd;
+     *      private final long fd;
      *
-     *      public AnonymousByteArrayFunction(Object obj, long fd) {
+     *      public AnonymousUnsafeLongFunction(Object obj, long fd) {
      *          this.unsafe = (sun.misc.Unsafe) obj;
      *          this.fd = fd;
      *      }
      *
      *      @Override
-     *      public byte[] apply(Object t) {
-     *          return (byte[]) unsafe.getObject(t, fd);
+     *      public long applyAsLong(Object t) {
+     *          return unsafe.getLong(t, fd);
      *      }
      *
      * }
      * 
*/ - private static final String functionByteClassBinary = "cafebabe00000034002d0a00090020070021090008002209000800230a00020024070025" - + "0a00080026070027070028070029010006756e736166650100114c73756e2f6d6973632f556e736166653b01000266640100014a0100063c696e69743e0" - + "10016284c6a6176612f6c616e672f4f626a6563743b4a2956010004436f646501000f4c696e654e756d6265725461626c650100124c6f63616c56617269" - + "61626c655461626c650100047468697301002d4c6f72672f7265646b616c652f7574696c2f416e6f6e796d6f757342797465417272617946756e6374696" - + "f6e3b0100036f626a0100124c6a6176612f6c616e672f4f626a6563743b0100056170706c79010016284c6a6176612f6c616e672f4f626a6563743b295b" - + "4201000174010026284c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b0100095369676e61747572650100454" - + "c6a6176612f6c616e672f4f626a6563743b4c6a6176612f7574696c2f66756e6374696f6e2f46756e6374696f6e3c4c6a6176612f6c616e672f4f626a65" - + "63743b5b423e3b01000a536f7572636546696c6501001f416e6f6e796d6f757342797465417272617946756e6374696f6e2e6a6176610c000f002a01000" - + "f73756e2f6d6973632f556e736166650c000b000c0c000d000e0c002b002c0100025b420c0018001901002b6f72672f7265646b616c652f7574696c2f41" - + "6e6f6e796d6f757342797465417272617946756e6374696f6e0100106a6176612f6c616e672f4f626a65637401001b6a6176612f7574696c2f66756e637" - + "4696f6e2f46756e6374696f6e0100032829560100096765744f626a656374010027284c6a6176612f6c616e672f4f626a6563743b4a294c6a6176612f6c" - + "616e672f4f626a6563743b0021000800090001000a00020010000b000c00000010000d000e000000030001000f0010000100110000005c0003000400000" - + "0122ab700012a2bc00002b500032a20b50004b10000000200120000001200040000001200040013000c0014001100150013000000200003000000120014" - + "001500000000001200160017000100000012000d000e0002000100180019000100110000004700040002000000132ab400032b2ab40004b60005c00006c" - + "00006b00000000200120000000600010000001900130000001600020000001300140015000000000013001a0017000110410018001b0001001100000030" - + "00020002000000062a2bb60007b00000000200120000000600010000000c00130000000c0001000000060014001500000002001c00000002001d001e000" - + "00002001f"; + private static final String functionUnsafeLongBinary = "cafebabe0000003400280a0007001d07001e090006001f0900" + + "0600200a00020021070022070023070024010006756e736166650100114c73756e2f6d6973632f556e736166653b0100026" + + "6640100014a0100063c696e69743e010016284c6a6176612f6c616e672f4f626a6563743b4a2956010004436f646501000f" + + "4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c650100047468697301002e4c6f726" + + "72f7265646b616c652f7574696c2f416e6f6e796d6f7573556e736166654c6f6e6746756e6374696f6e3b0100036f626a01" + + "00124c6a6176612f6c616e672f4f626a6563743b01000b6170706c7941734c6f6e67010015284c6a6176612f6c616e672f4" + + "f626a6563743b294a010001740100095369676e61747572650100494c6a6176612f6c616e672f4f626a6563743b4c6a6176" + + "612f7574696c2f66756e6374696f6e2f546f4c6f6e6746756e6374696f6e3c4c6a6176612f6c616e672f4f626a6563743b3" + + "e3b01000a536f7572636546696c65010020416e6f6e796d6f7573556e736166654c6f6e6746756e6374696f6e2e6a617661" + + "0c000d002501000f73756e2f6d6973632f556e736166650c0009000a0c000b000c0c0026002701002c6f72672f7265646b6" + + "16c652f7574696c2f416e6f6e796d6f7573556e736166654c6f6e6746756e6374696f6e0100106a6176612f6c616e672f4f" + + "626a6563740100216a6176612f7574696c2f66756e6374696f6e2f546f4c6f6e6746756e6374696f6e01000328295601000" + + "76765744c6f6e67010016284c6a6176612f6c616e672f4f626a6563743b4a294a0021000600070001000800020012000900" + + "0a00000012000b000c000000020001000d000e0001000f0000005c00030004000000122ab700012a2bc00002b500032a20b" + + "50004b10000000200100000001200040000001200040013000c001400110015001100000020000300000012001200130000" + + "0000001200140015000100000012000b000c00020001001600170001000f00000041000400020000000d2ab400032b2ab40" + + "004b60005ad0000000200100000000600010000001900110000001600020000000d0012001300000000000d001800150001" + + "0002001900000002001a001b00000002001c"; - private static final Function strByteFunction; + /** + *
+     * public final class AnonymousUnsafeByteBooleanFunction implements java.util.function.Predicate<Object> {
+     *
+     *      private final sun.misc.Unsafe unsafe;
+     *
+     *      private final long fd;
+     *
+     *      public AnonymousUnsafeByteBooleanFunction(Object obj, long fd) {
+     *          this.unsafe = (sun.misc.Unsafe) obj;
+     *          this.fd = fd;
+     *      }
+     *
+     *      @Override
+     *      public boolean test(Object t) {
+     *          return unsafe.getByte(t, fd) == 0; //LATIN1:0  UTF16:1
+     *      }
+     *
+     * }
+     * 
+ */ + private static final String functionUnsafeByteBooleanBinary = "cafebabe0000003400290a0007001e07001f09000600" + + "2009000600210a00020022070023070024070025010006756e736166650100114c73756e2f6d6973632f556e736166653b01" + + "000266640100014a0100063c696e69743e010016284c6a6176612f6c616e672f4f626a6563743b4a2956010004436f646501" + + "000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c65010004746869730100354c6f" + + "72672f7265646b616c652f7574696c2f416e6f6e796d6f7573556e7361666542797465426f6f6c65616e46756e6374696f6e" + + "3b0100036f626a0100124c6a6176612f6c616e672f4f626a6563743b01000474657374010015284c6a6176612f6c616e672f" + + "4f626a6563743b295a0100017401000d537461636b4d61705461626c650100095369676e61747572650100444c6a6176612f" + + "6c616e672f4f626a6563743b4c6a6176612f7574696c2f66756e6374696f6e2f5072656469636174653c4c6a6176612f6c61" + + "6e672f4f626a6563743b3e3b01000a536f7572636546696c65010027416e6f6e796d6f7573556e7361666542797465426f6f" + + "6c65616e46756e6374696f6e2e6a6176610c000d002601000f73756e2f6d6973632f556e736166650c0009000a0c000b000c" + + "0c002700280100336f72672f7265646b616c652f7574696c2f416e6f6e796d6f7573556e7361666542797465426f6f6c6561" + + "6e46756e6374696f6e0100106a6176612f6c616e672f4f626a65637401001c6a6176612f7574696c2f66756e6374696f6e2f" + + "50726564696361746501000328295601000767657442797465010016284c6a6176612f6c616e672f4f626a6563743b4a2942" + + "00210006000700010008000200120009000a00000012000b000c000000020001000d000e0001000f0000005c000300040000" + + "00122ab700012a2bc00002b500032a20b50004b10000000200100000001200040000001200040013000c0014001100150011" + + "000000200003000000120012001300000000001200140015000100000012000b000c00020001001600170001000f00000054" + + "00040002000000152ab400032b2ab40004b600059a000704a7000403ac000000030010000000060001000000190011000000" + + "160002000000150012001300000000001500180015000100190000000500021340010002001a00000002001b001c00000002" + + "001d"; - private static final Function sbByteFunction; + private static final Function strByteFunction; + + private static final Function sbByteFunction; + + private static final Function strCharFunction; + + private static final Function sbCharFunction; + + private static final Predicate strLatin1Function; + + private static final ToLongFunction bufferAddrFunction; private static final javax.net.ssl.SSLContext DEFAULTSSL_CONTEXT; private static final javax.net.ssl.HostnameVerifier defaultVerifier = (s, ss) -> true; static { - Function strCharFunction0 = null; - Function sbCharFunction0 = null; - Function strByteFunction0 = null; - Function sbByteFunction0 = null; + + (futureDelayer = new ScheduledThreadPoolExecutor(1, r -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("Redkale-CompletableFutureDelayScheduler"); + return t; + })).setRemoveOnCancelPolicy(true); + + Function strCharFunction0 = null; + Function sbCharFunction0 = null; + Function strByteFunction0 = null; + Function sbByteFunction0 = null; + Predicate strLatin1Function0 = null; + ToLongFunction bufferAddrFunction0 = null; + boolean big8 = true; try { Field f = String.class.getDeclaredField("value"); - if (f.getType() == char[].class) { //JDK9及以上不再是char[] - Class unsafeClass = Class.forName("sun.misc.Unsafe"); - Field safeField = unsafeClass.getDeclaredField("theUnsafe"); - safeField.setAccessible(true); - final Object usafe = safeField.get(null); - final Method fm = usafe.getClass().getMethod("objectFieldOffset", Field.class); + final Class unsafeClass = Class.forName("sun.misc.Unsafe"); + final Field safeField = unsafeClass.getDeclaredField("theUnsafe"); + safeField.setAccessible(true); + final Object usafe = safeField.get(null); + final Method fm = usafe.getClass().getMethod("objectFieldOffset", Field.class); + if (f.getType() == char[].class) { //JDK8及以下还是char[] + big8 = false; final long fd1 = (Long) fm.invoke(usafe, f); final long fd2 = (Long) fm.invoke(usafe, StringBuilder.class.getSuperclass().getDeclaredField("value")); - byte[] bytes = hexToBin(functionCharClassBinary); Class creatorClazz = (Class) new ClassLoader() { public final Class loadClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } - }.loadClass("org.re" + "dkale.util.AnonymousCharArrayFunction", bytes); - - strCharFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd1); - sbCharFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd2); + }.loadClass("org.re" + "dkale.util.AnonymousUnsafeObjectFunction", hexToBin(functionUnsafeObjectBinary)); + strCharFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd1); + sbCharFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd2); } else { - Class unsafeClass = Class.forName("sun.misc.Unsafe"); - Field safeField = unsafeClass.getDeclaredField("theUnsafe"); - safeField.setAccessible(true); - final Object usafe = safeField.get(null); - final Method fm = usafe.getClass().getMethod("objectFieldOffset", Field.class); final long fd1 = (Long) fm.invoke(usafe, f); final long fd2 = (Long) fm.invoke(usafe, StringBuilder.class.getSuperclass().getDeclaredField("value")); - byte[] bytes = hexToBin(functionByteClassBinary); + final long fd3 = (Long) fm.invoke(usafe, String.class.getDeclaredField("coder")); + final long fd4 = (Long) fm.invoke(usafe, Buffer.class.getDeclaredField("address")); + Class creatorClazz = (Class) new ClassLoader() { public final Class loadClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } - }.loadClass("org.re" + "dkale.util.AnonymousByteArrayFunction", bytes); + }.loadClass("org.re" + "dkale.util.AnonymousUnsafeObjectFunction", hexToBin(functionUnsafeObjectBinary)); - strByteFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd1); - sbByteFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd2); + Class creatorClazz2 = (Class) new ClassLoader() { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass("org.re" + "dkale.util.AnonymousUnsafeByteBooleanFunction", hexToBin(functionUnsafeByteBooleanBinary)); + + Class creatorClazz3 = (Class) new ClassLoader() { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass("org.re" + "dkale.util.AnonymousUnsafeLongFunction", hexToBin(functionUnsafeLongBinary)); + + strByteFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd1); + sbByteFunction0 = (Function) creatorClazz.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd2); + strLatin1Function0 = (Predicate) creatorClazz2.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd3); + bufferAddrFunction0 = (ToLongFunction) creatorClazz3.getDeclaredConstructor(Object.class, long.class).newInstance(usafe, fd4); } } catch (Throwable e) { //不会发生 - //e.printStackTrace(); + e.printStackTrace(); } + greatejdk8 = big8; strCharFunction = strCharFunction0; sbCharFunction = sbCharFunction0; strByteFunction = strByteFunction0; sbByteFunction = sbByteFunction0; + strLatin1Function = strLatin1Function0; + bufferAddrFunction = bufferAddrFunction0; try { DEFAULTSSL_CONTEXT = javax.net.ssl.SSLContext.getInstance("SSL"); @@ -201,6 +309,21 @@ public final class Utility { private Utility() { } + //是否JDK9+ + public static boolean greaterJDK8() { + return greatejdk8; + } + + public static CompletableFuture orTimeout(CompletableFuture future, long timeout, TimeUnit unit) { + //if (greatejdk8) return future.orTimeout(timeout, unit); + return future.whenComplete(new FutureCanceller(futureDelayer.schedule(new FutureTimeout(future), timeout, unit))); + } + + public static void main(String[] args) throws Throwable { + byte[] bs = readBytes(new FileInputStream("D:\\Java-Projects\\JavaApplication20\\dist\\AnonymousUnsafeByteBooleanFunction.class")); + System.out.println(binToHex(bs)); + } + /** * 将多个key:value的字符串键值对组合成一个Map,items长度必须是偶数, 参数个数若是奇数的话,最后一个会被忽略 * 类似 JDK9中的 Map.of 方法 @@ -273,7 +396,8 @@ public final class Utility { } /** - * 将多个元素组合成一个List + * 将多个元素组合成一个List
+ * 类似 JDK9中的 List.of 方法 * * @param 泛型 * @param items 元素 @@ -570,6 +694,30 @@ public final class Utility { return news; } + /** + * 将一个或多个byte新元素添加到byte数组结尾 + * + * @param array 原数组 + * @param objs 待追加数据 + * @param offset 待追加数据偏移量 + * @param length 待追加数据的长度 + * + * @return 新数组 + */ + public static byte[] append(final byte[] array, final byte[] objs, int offset, int length) { + if (array == null || array.length == 0) { + if (objs != null && offset == 0 && objs.length == length) return objs; + final byte[] news = new byte[length]; + System.arraycopy(objs, 0, news, 0, length); + return news; + } + if (objs == null || length == 0) return array; + final byte[] news = new byte[array.length + length]; + System.arraycopy(array, 0, news, 0, array.length); + System.arraycopy(objs, offset, news, array.length, length); + return news; + } + /** * 将一个或多个short新元素添加到short数组结尾 * @@ -2008,30 +2156,46 @@ public final class Utility { return bytes; } + public static long getAddress(ByteBuffer buffer) { + return bufferAddrFunction.applyAsLong(buffer); + } + + public static boolean isLatin1(String value) { + if (value == null) return true; + if (strLatin1Function != null) { + return strLatin1Function.test(value); //LATIN1:0 UTF16:1 + } + char[] chs = charArray(value); + for (char ch : chs) { + if (ch >= 0x80) return false; + } + return true; + } + public static char[] charArray(String value) { if (value == null) return null; if (strCharFunction == null) return value.toCharArray(); - return strCharFunction.apply(value); + return (char[]) strCharFunction.apply(value); } public static char[] charArray(StringBuilder value) { if (value == null) return null; if (sbCharFunction == null) return value.toString().toCharArray(); - return sbCharFunction.apply(value); + return (char[]) sbCharFunction.apply(value); } //只能是单字节字符串 public static byte[] byteArray(String latin1Value) { if (latin1Value == null) return null; if (strByteFunction == null) return latin1Value.getBytes(); - return strByteFunction.apply(latin1Value); + return (byte[]) strByteFunction.apply(latin1Value); } //只能是单字节字符串 public static byte[] byteArray(StringBuilder latin1Value) { if (latin1Value == null) return null; if (sbByteFunction == null) return latin1Value.toString().getBytes(); - return sbByteFunction.apply(latin1Value); + return (byte[]) sbByteFunction.apply(latin1Value); } public static ByteBuffer encodeUTF8(final ByteBuffer buffer, final char[] array) { @@ -2045,7 +2209,7 @@ public final class Utility { public static int encodeUTF8Length(String value) { if (value == null) return -1; if (strCharFunction == null) return encodeUTF8Length(value.toCharArray()); - return encodeUTF8Length(strCharFunction.apply(value)); + return encodeUTF8Length((char[]) strCharFunction.apply(value)); } public static int encodeUTF8Length(final char[] text) { diff --git a/test/org/redkale/source/FilterNodeTest.java b/test/org/redkale/source/FilterNodeTest.java index bb98fb5e9..6a47f1b73 100644 --- a/test/org/redkale/source/FilterNodeTest.java +++ b/test/org/redkale/source/FilterNodeTest.java @@ -8,6 +8,7 @@ package org.redkale.source; import org.redkale.util.AutoLoad; import static org.redkale.source.FilterExpress.*; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.*; import javax.persistence.*; import org.redkale.convert.json.*; @@ -20,11 +21,11 @@ public class FilterNodeTest { public static void main(String[] args) throws Exception { final Properties props = new Properties(); - final BiFunction fullloader = (s, t) -> new ArrayList(); + final BiFunction> fullloader = (s, t) -> CompletableFuture.completedFuture(new ArrayList()); final Function func = (Class t) -> EntityInfo.load(t, false, props, null, fullloader); - final EntityInfo carEntity = EntityInfo.load(CarTestTable.class, false, props, null, (s, t) -> CarTestTable.createList()); - final EntityInfo userEntity = EntityInfo.load(UserTestTable.class, false, props, null, (s, t) -> UserTestTable.createList()); - final EntityInfo typeEntity = EntityInfo.load(CarTypeTestTable.class, false, props, null, (s, t) -> CarTypeTestTable.createList()); + final EntityInfo carEntity = EntityInfo.load(CarTestTable.class, false, props, null, (s, t) -> CompletableFuture.completedFuture(CarTestTable.createList())); + final EntityInfo userEntity = EntityInfo.load(UserTestTable.class, false, props, null, (s, t) -> CompletableFuture.completedFuture(UserTestTable.createList())); + final EntityInfo typeEntity = EntityInfo.load(CarTypeTestTable.class, false, props, null, (s, t) -> CompletableFuture.completedFuture(CarTypeTestTable.createList())); final CarTestBean bean = new CarTestBean(); bean.carid = 70002; @@ -47,10 +48,10 @@ public class FilterNodeTest { System.out.println("node.sql = SELECT a.* FROM " + CarTestTable.class.getSimpleName().toLowerCase() + " a" + (nodeJoinsql == null ? "" : nodeJoinsql) + " WHERE " + nodeWhere); System.out.println("bean.sql = SELECT a.* FROM " + CarTestTable.class.getSimpleName().toLowerCase() + " a" + (beanJoinsql == null ? "" : beanJoinsql) + " WHERE " + beanWhere); boolean r1 = node.isCacheUseable(func); - if(!r1) System.err.println("node.isCacheUseable 应该是true"); + if (!r1) System.err.println("node.isCacheUseable 应该是true"); boolean r2 = beanNode.isCacheUseable(func); - if(!r2) System.err.println("beanNode.isCacheUseable 应该是true"); - + if (!r2) System.err.println("beanNode.isCacheUseable 应该是true"); + System.out.println("node.Predicate = " + node.createPredicate(carEntity.getCache())); System.out.println("bean.Predicate = " + beanNode.createPredicate(carEntity.getCache())); System.out.println("node.sheet = " + carEntity.getCache().querySheet(null, new Flipper(), node)); diff --git a/test/org/redkale/test/asm/AsmCreator.java b/test/org/redkale/test/asm/AsmCreator.java index c7bcad7b9..8de08f93e 100644 --- a/test/org/redkale/test/asm/AsmCreator.java +++ b/test/org/redkale/test/asm/AsmCreator.java @@ -16,7 +16,7 @@ public class AsmCreator { public static void main(String[] args) throws Throwable { boolean realasm = false; //从http://forge.ow2.org/projects/asm/ 下载最新asm的src放在 srcasmroot 目录下 - File srcasmroot = new File("D:/JAVA/JDK源码/jdk/internal/org/objectweb/asm"); + File srcasmroot = new File("D:/JAVA/JDK源码/JDK11源码/java.base/jdk/internal/org/objectweb/asm"); if (realasm) srcasmroot = new File("D:/JAVA/JDK源码/org/objectweb/asm"); File destasmroot = new File("D:/Java-Projects/RedkaleProject/src/org/redkale/asm"); String line = null; @@ -29,7 +29,9 @@ public class AsmCreator { File destfile = new File(destasmroot, line); String content = Utility.readThenClose(new FileInputStream(srcfile)); FileOutputStream out = new FileOutputStream(destfile); - out.write(content.replace("jdk.internal.org.objectweb", "org.redkale").replace("org.objectweb", "org.redkale").getBytes()); + out.write(content.replace("jdk.internal.org.objectweb", "org.redkale").replace("org.objectweb", "org.redkale") + .replace("", "<tt>").replace("", "</tt>") + .replace("{@link org.redkale.asm.tree.MethodNode#getLabelNode} method.", "").getBytes()); out.close(); } //需要屏蔽ClassReader中判断checks the class version的部分 diff --git a/test/org/redkale/test/convert/Fortune.java b/test/org/redkale/test/convert/Fortune.java new file mode 100644 index 000000000..ff66c3ed4 --- /dev/null +++ b/test/org/redkale/test/convert/Fortune.java @@ -0,0 +1,56 @@ +/* + * 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.test.convert; + +import javax.persistence.Id; +import org.redkale.convert.json.JsonConvert; + +/** + * + * @author zhangjx + */ +public class Fortune implements Comparable { + + @Id + private int id; + + private String message = ""; + + public Fortune() { + } + + public Fortune(int id, String message) { + this.id = id; + this.message = message; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public int compareTo(Fortune o) { + return message.compareTo(o.message); + } + + @Override + public String toString() { + return JsonConvert.root().convertTo(this); + } + +} \ No newline at end of file diff --git a/test/org/redkale/test/convert/Message.java b/test/org/redkale/test/convert/Message.java new file mode 100644 index 000000000..7729783e1 --- /dev/null +++ b/test/org/redkale/test/convert/Message.java @@ -0,0 +1,91 @@ +/* + * 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.test.convert; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import org.redkale.convert.*; +import org.redkale.convert.json.*; +import org.redkale.util.ByteArray; + +/** + * + * @author zhangjx + */ +public final class Message { + + protected boolean flag; + + private int[] ints; + + private List longs; + + @ConvertSmallString + private String message; + + public Message() { + } + + public List getLongs() { + return longs; + } + + public void setLongs(List longs) { + this.longs = longs; + } + + public int[] getInts() { + return ints; + } + + public void setInts(int[] ints) { + this.ints = ints; + } + + public Message(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isFlag() { + return flag; + } + + public void setFlag(boolean flag) { + this.flag = flag; + } + + @Override + public String toString() { + return JsonConvert.root().convertTo(this); + } + + public static void main(String[] args) throws Throwable { + Message msg = new Message(); + msg.message = "dddd"; + List longs = new ArrayList<>(); + longs.add(2222L); + longs.add(3333L); + msg.longs = longs; + msg.ints = new int[]{2, 3, 4}; + JsonConvert convert = JsonFactory.root().getConvert(); + Encodeable encoder = JsonFactory.root().loadEncoder(Message.class); + System.out.println(encoder); + ByteArray array = new ByteArray(); + array.put("数据: ".getBytes(StandardCharsets.UTF_8)); + JsonConvert.root().convertToBytes(array, msg); + System.out.println(array); + Message[] mss = new Message[]{msg}; + System.out.println(JsonConvert.root().convertTo(mss)); + } +} diff --git a/test/org/redkale/test/convert/One.java b/test/org/redkale/test/convert/One.java index 8ef3b1de1..0fbf8d415 100644 --- a/test/org/redkale/test/convert/One.java +++ b/test/org/redkale/test/convert/One.java @@ -5,7 +5,9 @@ */ package org.redkale.test.convert; +import java.util.Arrays; import org.redkale.convert.json.JsonConvert; +import org.redkale.util.Utility; /** * @@ -60,4 +62,28 @@ public class One { public String toString() { return JsonConvert.root().convertTo(this); } + + public static void main(String[] args) throws Throwable { + int count = 100_0000; + One one = new One(234); + one.bytes = new byte[]{1, 2, 3}; + one.key = "哈哈"; + + System.out.println(Arrays.toString(Utility.encodeUTF8(JsonConvert.root().convertTo(one)))); + System.out.println(Arrays.toString(JsonConvert.root().convertToBytes(one))); + long s = System.currentTimeMillis(); + for (int i = 0; i < count; i++) { + JsonConvert.root().convertTo(one).getBytes(); + } + long e = System.currentTimeMillis() - s; + + long s2 = System.currentTimeMillis(); + for (int i = 0; i < count; i++) { + JsonConvert.root().convertToBytes(one); + } + long e2 = System.currentTimeMillis() - s2; + System.out.println(e); + System.out.println(e2); + + } } diff --git a/test/org/redkale/test/convert/World.java b/test/org/redkale/test/convert/World.java new file mode 100644 index 000000000..ca3272a79 --- /dev/null +++ b/test/org/redkale/test/convert/World.java @@ -0,0 +1,57 @@ +/* + * 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.test.convert; + +import javax.persistence.Id; +import org.redkale.convert.json.JsonConvert; + +/** + * + * @author zhangjx + */ +public class World implements Comparable { + + @Id + private int id; + + private int randomNumber; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getRandomNumber() { + return randomNumber; + } + + public void setRandomNumber(int randomNumber) { + this.randomNumber = randomNumber; + } + + @Override + public int compareTo(World o) { + return Integer.compare(id, o.id); + } + + @Override + public String toString() { + return JsonConvert.root().convertTo(this); + } + public static void main(String[] args) throws Throwable { + World[] worlds = new World[20]; + int index = 8866; + for(int i =0;i { + + protected final byte[] idFieldBytes = "\"id\":".getBytes(); + + protected final byte[] messageCommaFieldBytes = ",\"message\":".getBytes(); + + public _DyncFortuneJsonEncoder(JsonFactory factory, Type type) { + super(factory, type); + } + + @Override + public void convertTo(JsonWriter out, Fortune value) { + if (value == null) { + out.writeObjectNull(null); + return; + } + if (!out.isExtFuncEmpty()) { + objectEncoder.convertTo(out, value); + return; + } + + out.writeTo('{'); + + out.writeTo(idFieldBytes); + out.writeInt(value.getId()); + + String message = value.getMessage(); + if (message != null) { + out.writeTo(messageCommaFieldBytes); + out.writeString(message); + } + + out.writeTo('}'); + } +} diff --git a/test/org/redkale/test/convert/_DyncMessageJsonEncoder.java b/test/org/redkale/test/convert/_DyncMessageJsonEncoder.java new file mode 100644 index 000000000..c9a54ed14 --- /dev/null +++ b/test/org/redkale/test/convert/_DyncMessageJsonEncoder.java @@ -0,0 +1,51 @@ +/* + * 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.test.convert; + +import java.lang.reflect.Type; +import org.redkale.convert.json.*; + +/** + * + * @author zhangjx + */ +public class _DyncMessageJsonEncoder extends JsonDynEncoder { + + protected final byte[] messageFieldBytes = "\"message\":".getBytes(); + + protected final byte[] messageCommaFieldBytes = ",\"message\":".getBytes(); + + public _DyncMessageJsonEncoder(JsonFactory factory, Type type) { + super(factory, type); + } + + @Override + public void convertTo(JsonWriter out, Message value) { + if (value == null) { + out.writeObjectNull(null); + return; + } + if (!out.isExtFuncEmpty()) { + objectEncoder.convertTo(out, value); + return; + } + + out.writeTo('{'); + boolean comma = false; + String message = value.getMessage(); + if (message != null) { + if (comma) { + out.writeTo(messageCommaFieldBytes); + } else { + out.writeTo(messageFieldBytes); + comma = true; + } + out.writeLatin1To(true, message); //out.writeString(message); + } + out.writeTo('}'); + } + +} diff --git a/test/org/redkale/test/convert/_DyncWorldJsonEncoder.java b/test/org/redkale/test/convert/_DyncWorldJsonEncoder.java new file mode 100644 index 000000000..3b41a0a9b --- /dev/null +++ b/test/org/redkale/test/convert/_DyncWorldJsonEncoder.java @@ -0,0 +1,47 @@ +/* + * 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.test.convert; + +import java.lang.reflect.Type; +import org.redkale.convert.json.*; + +/** + * + * @author zhangjx + */ +public class _DyncWorldJsonEncoder extends JsonDynEncoder { + + protected final byte[] idFieldBytes = "\"id\":".getBytes(); + + protected final byte[] randomNumberFieldBytes = ",\"randomNumber\":".getBytes(); + + public _DyncWorldJsonEncoder(JsonFactory factory, Type type) { + super(factory, type); + } + + @Override + public void convertTo(JsonWriter out, World value) { + if (value == null) { + out.writeObjectNull(null); + return; + } + if (!out.isExtFuncEmpty()) { + objectEncoder.convertTo(out, value); + return; + } + + out.writeTo('{'); + boolean comma = false; + + out.writeTo(idFieldBytes); + out.writeInt(value.getId()); + + out.writeTo(randomNumberFieldBytes); + out.writeInt(value.getRandomNumber()); + + out.writeTo('}'); + } +} diff --git a/test/org/redkale/test/http/SocketMain.java b/test/org/redkale/test/http/SocketMain.java new file mode 100644 index 000000000..0ac3aaf83 --- /dev/null +++ b/test/org/redkale/test/http/SocketMain.java @@ -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.test.http; + +import java.net.Socket; + +/** + * + * @author zhangjx + */ +public class SocketMain { + + public static void main(String[] args) throws Throwable { + Socket socket = new Socket("127.0.0.1", 6060); + socket.getOutputStream().write("GET /json1 HTTP/1.1\r\nAccpet: aaa\r\nConnection: keep-alive\r\n\r\nGET /json2 HTTP/1.1\r\nAccpet: a".getBytes()); + socket.getOutputStream().flush(); + Thread.sleep(1000); + socket.getOutputStream().write("aa\r\nConnection: keep-alive\r\n\r".getBytes()); + socket.getOutputStream().flush(); + Thread.sleep(1000); + socket.getOutputStream().write("\nGET /json3 HTTP/1.1\r\nAccpet: aaa\r\nConnection: keep-alive\r\n\r".getBytes()); + socket.getOutputStream().flush(); + Thread.sleep(1000); + socket.getOutputStream().write("\n".getBytes()); + socket.getOutputStream().flush(); + byte[] bs = new byte[10240]; + int rs = socket.getInputStream().read(bs); + System.out.println(new String(bs, 0, rs)); + rs = socket.getInputStream().read(bs); + System.out.println(new String(bs, 0, rs)); + } + +} diff --git a/test/org/redkale/test/net/TransportTest.java b/test/org/redkale/test/net/TransportTest.java index 8f5c49350..b4903db02 100644 --- a/test/org/redkale/test/net/TransportTest.java +++ b/test/org/redkale/test/net/TransportTest.java @@ -33,11 +33,13 @@ public class TransportTest { HttpServer server = new HttpServer(); DefaultAnyValue servconf = DefaultAnyValue.create("port", servaddr.getPort()); server.init(servconf); - server.start(); + server.start(null); } addrs.add(new InetSocketAddress("127.0.0.1", 22005)); + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); Thread.sleep(1000); - TransportFactory factory = TransportFactory.create(10); + TransportFactory factory = TransportFactory.create(asyncGroup, 0, 0); DefaultAnyValue conf = DefaultAnyValue.create(TransportFactory.NAME_PINGINTERVAL, 5); factory.init(conf, Sncp.PING_BUFFER, Sncp.PONG_BUFFER.remaining()); Transport transport = factory.createTransportTCP("", null, addrs); @@ -54,7 +56,7 @@ public class TransportTest { HttpServer server = new HttpServer(); DefaultAnyValue servconf = DefaultAnyValue.create("port", 22005); server.init(servconf); - server.start(); + server.start(null); Thread.sleep(4000); CountDownLatch cdl2 = new CountDownLatch(20); for (int i = 0; i < 20; i++) { diff --git a/test/org/redkale/test/rest/_DynHelloRestServlet1.java b/test/org/redkale/test/rest/_DynHelloRestServlet1.java index 575978888..b48ac9bb0 100644 --- a/test/org/redkale/test/rest/_DynHelloRestServlet1.java +++ b/test/org/redkale/test/rest/_DynHelloRestServlet1.java @@ -28,7 +28,7 @@ public class _DynHelloRestServlet1 extends SimpleRestServlet { DefaultAnyValue conf = DefaultAnyValue.create("port", "" + port); server.init(conf); - server.start(); + server.start(null); Thread.sleep(100); HelloEntity entity = new HelloEntity(); diff --git a/test/org/redkale/test/service/ABMainService.java b/test/org/redkale/test/service/ABMainService.java index 17fe028cd..e9231e130 100644 --- a/test/org/redkale/test/service/ABMainService.java +++ b/test/org/redkale/test/service/ABMainService.java @@ -15,7 +15,7 @@ import java.util.logging.*; import javax.annotation.Resource; import org.redkale.convert.bson.BsonConvert; import org.redkale.convert.json.JsonConvert; -import org.redkale.net.TransportFactory; +import org.redkale.net.*; import org.redkale.net.http.*; import org.redkale.net.sncp.*; import org.redkale.service.Service; @@ -35,9 +35,11 @@ public class ABMainService implements Service { public static void remotemain(String[] args) throws Throwable { System.out.println("------------------- 远程模式调用 -----------------------------------"); final int abport = 8888; + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); ResourceFactory resFactory = ResourceFactory.root(); ExecutorService executor = Executors.newSingleThreadExecutor(); - final TransportFactory transFactory = TransportFactory.create(executor, newBufferPool(), newChannelGroup()); + final TransportFactory transFactory = TransportFactory.create(asyncGroup, 0, 0); transFactory.addGroupInfo("g77", new InetSocketAddress("127.0.0.1", 5577)); transFactory.addGroupInfo("g88", new InetSocketAddress("127.0.0.1", 5588)); transFactory.addGroupInfo("g99", new InetSocketAddress("127.0.0.1", 5599)); @@ -51,7 +53,7 @@ public class ABMainService implements Service { cserver.getLogger().setLevel(Level.WARNING); cserver.addSncpServlet(cservice); cserver.init(DefaultAnyValue.create("port", 5577)); - cserver.start(); + cserver.start(null); //------------------------ 初始化 BCService ------------------------------------ BCService bcservice = Sncp.createSimpleLocalService(BCService.class, null, transFactory, new InetSocketAddress("127.0.0.1", 5588), "g88"); @@ -62,7 +64,7 @@ public class ABMainService implements Service { bcserver.getLogger().setLevel(Level.WARNING); bcserver.addSncpServlet(bcservice); bcserver.init(DefaultAnyValue.create("port", 5588)); - bcserver.start(); + bcserver.start(null); //------------------------ 初始化 ABMainService ------------------------------------ ABMainService service = Sncp.createSimpleLocalService(ABMainService.class, null, transFactory, new InetSocketAddress("127.0.0.1", 5599), "g99"); @@ -80,7 +82,7 @@ public class ABMainService implements Service { resFactory.inject(service); server.init(DefaultAnyValue.create("port", abport)); - server.start(); + server.start(null); Thread.sleep(100); //同步方法 @@ -117,7 +119,7 @@ public class ABMainService implements Service { server.addRestServlet(null, service, null, HttpServlet.class, "/pipes"); server.init(DefaultAnyValue.create("port", "" + abport)); - server.start(); + server.start(null); Thread.sleep(100); //同步方法 diff --git a/test/org/redkale/test/sncp/SncpTest.java b/test/org/redkale/test/sncp/SncpTest.java index 09109e2c6..a2a49285e 100644 --- a/test/org/redkale/test/sncp/SncpTest.java +++ b/test/org/redkale/test/sncp/SncpTest.java @@ -14,7 +14,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.logging.LogManager; import org.redkale.convert.bson.*; -import org.redkale.net.TransportFactory; +import org.redkale.net.*; import org.redkale.net.sncp.*; import org.redkale.service.Service; import org.redkale.util.*; @@ -33,6 +33,8 @@ public class SncpTest { private static final int port2 = 4240; + private static final String protocol = "SNCP.UDP"; + public static void main(String[] args) throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(out); @@ -79,7 +81,9 @@ public class SncpTest { Set set = new LinkedHashSet<>(); set.add(addr); if (port2 > 0) set.add(new InetSocketAddress(myhost, port2)); - final TransportFactory transFactory = TransportFactory.create(Executors.newSingleThreadExecutor(), newBufferPool(), newChannelGroup()); + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); + final TransportFactory transFactory = TransportFactory.create(asyncGroup, protocol.endsWith(".UDP") ? "UDP" : "TCP", 0, 0); transFactory.addGroupInfo("client", set); final SncpTestIService service = Sncp.createSimpleRemoteService(SncpTestIService.class, null, transFactory, addr, "client"); ResourceFactory.root().inject(service); @@ -146,6 +150,8 @@ public class SncpTest { private static void runServer() throws Exception { InetSocketAddress addr = new InetSocketAddress(myhost, port); final CountDownLatch cdl = new CountDownLatch(1); + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); new Thread() { { setName("Thread-Server-01"); @@ -154,20 +160,21 @@ public class SncpTest { @Override public void run() { try { - SncpServer server = new SncpServer(); + AnyValue.DefaultAnyValue conf = new AnyValue.DefaultAnyValue(); + conf.addValue("host", "0.0.0.0"); + conf.addValue("port", "" + port); + conf.addValue("protocol", protocol); + SncpServer server = new SncpServer(null, System.currentTimeMillis(), conf, ResourceFactory.root()); Set set = new LinkedHashSet<>(); if (port2 > 0) set.add(new InetSocketAddress(myhost, port2)); - final TransportFactory transFactory = TransportFactory.create(Executors.newSingleThreadExecutor(), newBufferPool(), newChannelGroup()); + final TransportFactory transFactory = TransportFactory.create(asyncGroup, protocol.endsWith(".UDP") ? "UDP" : "TCP", 0, 0); transFactory.addGroupInfo("server", set); SncpTestIService service = Sncp.createSimpleLocalService(SncpTestServiceImpl.class, null, transFactory, addr, "server"); ResourceFactory.root().inject(service); server.addSncpServlet(service); System.out.println(service); - AnyValue.DefaultAnyValue conf = new AnyValue.DefaultAnyValue(); - conf.addValue("host", "0.0.0.0"); - conf.addValue("port", "" + port); server.init(conf); - server.start(); + server.start(null); cdl.countDown(); } catch (Exception e) { e.printStackTrace(); @@ -180,6 +187,8 @@ public class SncpTest { private static void runServer2() throws Exception { InetSocketAddress addr = new InetSocketAddress(myhost, port2); final CountDownLatch cdl = new CountDownLatch(1); + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); new Thread() { { setName("Thread-Server-02"); @@ -188,19 +197,20 @@ public class SncpTest { @Override public void run() { try { - SncpServer server = new SncpServer(); - Set set = new LinkedHashSet<>(); - set.add(new InetSocketAddress(myhost, port)); - - final TransportFactory transFactory = TransportFactory.create(Executors.newSingleThreadExecutor(), newBufferPool(), newChannelGroup()); - transFactory.addGroupInfo("server", set); - Service service = Sncp.createSimpleLocalService(SncpTestServiceImpl.class, null, transFactory, addr, "server"); - server.addSncpServlet(service); AnyValue.DefaultAnyValue conf = new AnyValue.DefaultAnyValue(); conf.addValue("host", "0.0.0.0"); conf.addValue("port", "" + port2); + conf.addValue("protocol", protocol); + SncpServer server = new SncpServer(null, System.currentTimeMillis(), conf, ResourceFactory.root()); + Set set = new LinkedHashSet<>(); + set.add(new InetSocketAddress(myhost, port)); + + final TransportFactory transFactory = TransportFactory.create(asyncGroup, protocol.endsWith(".UDP") ? "UDP" : "TCP", 0, 0); + transFactory.addGroupInfo("server", set); + Service service = Sncp.createSimpleLocalService(SncpTestServiceImpl.class, null, transFactory, addr, "server"); + server.addSncpServlet(service); server.init(conf); - server.start(); + server.start(null); cdl.countDown(); } catch (Exception e) { e.printStackTrace(); diff --git a/test/org/redkale/test/sncp/SncpTestServiceImpl.java b/test/org/redkale/test/sncp/SncpTestServiceImpl.java index 3590a7fa2..f5254b374 100644 --- a/test/org/redkale/test/sncp/SncpTestServiceImpl.java +++ b/test/org/redkale/test/sncp/SncpTestServiceImpl.java @@ -9,10 +9,9 @@ import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.nio.channels.CompletionHandler; import java.util.concurrent.*; -import org.redkale.net.TransportFactory; +import org.redkale.net.*; import org.redkale.net.sncp.*; import org.redkale.service.*; -import static org.redkale.test.sncp.SncpTest.*; import org.redkale.util.*; /** @@ -109,7 +108,9 @@ public class SncpTestServiceImpl implements SncpTestIService { public static void main(String[] args) throws Exception { - final TransportFactory transFactory = TransportFactory.create(Executors.newSingleThreadExecutor(), newBufferPool(), newChannelGroup()); + final AsyncIOGroup asyncGroup = new AsyncIOGroup(8192, 16); + asyncGroup.start(); + final TransportFactory transFactory = TransportFactory.create(asyncGroup, 0, 0); transFactory.addGroupInfo("g70", new InetSocketAddress("127.0.0.1", 7070)); Service service = Sncp.createSimpleLocalService(SncpTestServiceImpl.class, null, transFactory, new InetSocketAddress("127.0.0.1", 7070), "g70"); diff --git a/test/org/redkale/test/source/CacheTestBean.java b/test/org/redkale/test/source/CacheTestBean.java index ccb30aa65..74025ea2a 100644 --- a/test/org/redkale/test/source/CacheTestBean.java +++ b/test/org/redkale/test/source/CacheTestBean.java @@ -7,6 +7,7 @@ package org.redkale.test.source; import java.lang.reflect.Method; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import javax.persistence.Id; import org.redkale.convert.json.JsonConvert; @@ -31,7 +32,7 @@ public class CacheTestBean { method.setAccessible(true); final EntityInfo info = (EntityInfo) method.invoke(null, CacheTestBean.class, true, new Properties(), null, new CacheTestBean.DefaultBeanLoader()); EntityCache cache = new EntityCache(info, null); - cache.fullLoad(); + cache.fullLoadAsync(); System.out.println(cache.queryColumnMap("pkgid", FilterFunc.COUNT, "name", null)); System.out.println(cache.queryColumnMap("pkgid", FilterFunc.DISTINCTCOUNT, "name", null)); @@ -83,16 +84,16 @@ public class CacheTestBean { return JsonConvert.root().convertTo(this); } - public static class DefaultBeanLoader implements BiFunction { + public static class DefaultBeanLoader implements BiFunction> { @Override - public List apply(DataSource t, Class u) { + public CompletableFuture apply(DataSource t, EntityInfo u) { final List list = new ArrayList<>(); list.add(new CacheTestBean(1, "a", 12)); list.add(new CacheTestBean(1, "a", 18)); list.add(new CacheTestBean(2, "b", 20)); list.add(new CacheTestBean(2, "bb", 60)); - return list; + return CompletableFuture.completedFuture(list); } } diff --git a/test/org/redkale/test/websocket/VideoWebSocketServlet.java b/test/org/redkale/test/websocket/VideoWebSocketServlet.java index 33a6037ba..7701dc86a 100644 --- a/test/org/redkale/test/websocket/VideoWebSocketServlet.java +++ b/test/org/redkale/test/websocket/VideoWebSocketServlet.java @@ -118,7 +118,7 @@ public class VideoWebSocketServlet extends WebSocketServlet { HttpServer server = new HttpServer(); server.addHttpServlet("/pipes", new VideoWebSocketServlet(), "/listen/*"); server.init(config); - server.start(); + server.start(null); cdl.await(); }