diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/PackableMethod.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/PackableMethod.java index 90c931a7c14a..2065726c2291 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/PackableMethod.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/PackableMethod.java @@ -16,6 +16,9 @@ */ package org.apache.dubbo.rpc.model; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + /** * A packable method is used to customize serialization for methods. It can provide a common wrapper * for RESP / Protobuf. @@ -38,6 +41,27 @@ default Object parseResponse(byte[] data, boolean isReturnTriException) throws E return unPack.unpack(data); } + /** + * Parse response from InputStream. + * Default implementation reads all bytes and delegates to byte[] version. + * + * @param inputStream the input stream containing the response data + * @param isReturnTriException whether the response is a Triple exception + * @return the parsed response object + * @throws Exception if parsing fails + */ + default Object parseResponse(InputStream inputStream, boolean isReturnTriException) throws Exception { + // Read all bytes from InputStream + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] tmp = new byte[4096]; + int len; + while ((len = inputStream.read(tmp)) != -1) { + buffer.write(tmp, 0, len); + } + byte[] data = buffer.toByteArray(); + return parseResponse(data, isReturnTriException); + } + default byte[] packRequest(Object request) throws Exception { return getRequestPack().pack(request); } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java index 7832d0e19f15..53ba79cf523c 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java @@ -109,9 +109,15 @@ public void close() throws IOException { } } - private void releaseHeadStream() throws IOException { - InputStream removeStream = inputStreams.remove(); - removeStream.close(); + private void releaseHeadStream() { + InputStream removeStream = inputStreams.poll(); + if (removeStream != null) { + try { + removeStream.close(); + } catch (IOException ignore) { + // ignore + } + } } private void releaseIfNecessary(InputStream inputStream) throws IOException { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultStreamingDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultStreamingDecoder.java index 9fb1d43830e2..6d341783c0a5 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultStreamingDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultStreamingDecoder.java @@ -51,7 +51,7 @@ public void close() { return; } closed = true; - listener.onFragmentMessage(accumulate); + listener.onFragmentMessage(accumulate, accumulate.available()); accumulate.close(); listener.onClose(); } catch (IOException e) { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java index d1130375d7ee..e71a05334577 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java @@ -24,6 +24,7 @@ import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.model.ApplicationModel; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -119,7 +120,9 @@ private void doClose() { } catch (IOException e) { // ignore } - listener.onClose(); + if (listener != null) { + listener.onClose(); + } } /** @@ -233,9 +236,9 @@ private void processBody() throws IOException { // Calculate total bytes read: header (offset + length field) + payload int totalBytesRead = lengthFieldOffset + lengthFieldLength + requiredLength; - byte[] rawMessage; + MessageStream messageStream; try { - rawMessage = readRawMessage(accumulate, requiredLength); + messageStream = readMessageStream(accumulate, requiredLength); } finally { // Notify listener about bytes read for flow control immediately after reading bytes // This must be in finally block to ensure flow control works even if reading fails @@ -243,17 +246,23 @@ private void processBody() throws IOException { listener.bytesRead(totalBytesRead); } - // Process the message after notifying about bytes read - InputStream inputStream = new ByteArrayInputStream(rawMessage); - invokeListener(inputStream); + invokeListener(messageStream.inputStream, messageStream.length); // Done with this frame, begin processing the next header. state = DecodeState.HEADER; requiredLength = lengthFieldOffset + lengthFieldLength; } - public void invokeListener(InputStream inputStream) { - this.listener.onFragmentMessage(inputStream); + public void invokeListener(InputStream inputStream, int messageLength) { + this.listener.onFragmentMessage(inputStream, messageLength); + } + + /** + * Read message from the input stream and return it as a MessageStream. + */ + protected MessageStream readMessageStream(InputStream inputStream, int length) throws IOException { + InputStream boundedStream = new BoundedInputStream(inputStream, length); + return new MessageStream(boundedStream, length); } protected byte[] readRawMessage(InputStream inputStream, int length) throws IOException { @@ -262,6 +271,82 @@ protected byte[] readRawMessage(InputStream inputStream, int length) throws IOEx return data; } + protected static class MessageStream { + + public final InputStream inputStream; + public final int length; + + public MessageStream(InputStream inputStream, int length) { + this.inputStream = inputStream; + this.length = length; + } + } + + /** + * A bounded InputStream that reads at most 'limit' bytes from the source stream. + * Extends BufferedInputStream to support mark/reset, which is required by + * deserializers like Hessian2. + */ + private static class BoundedInputStream extends BufferedInputStream { + + private final int limit; + private int remaining; + private int markedRemaining; + + public BoundedInputStream(InputStream source, int limit) { + super(source, limit); + this.limit = limit; + this.remaining = limit; + this.markedRemaining = limit; + } + + @Override + public synchronized int read() throws IOException { + if (remaining <= 0) { + return -1; + } + int result = super.read(); + if (result != -1) { + remaining--; + } + return result; + } + + @Override + public synchronized int read(byte[] b, int off, int len) throws IOException { + if (remaining <= 0) { + return -1; + } + int toRead = Math.min(len, remaining); + int result = super.read(b, off, toRead); + if (result > 0) { + remaining -= result; + } + return result; + } + + @Override + public synchronized int available() throws IOException { + return Math.min(super.available(), remaining); + } + + @Override + public synchronized void mark(int readlimit) { + // Force readlimit to be at least the remaining message length. + // This ensures mark is always valid within the bounded stream, + // regardless of what readlimit is passed by the deserializer (e.g., Hessian2). + super.mark(Math.max(readlimit, limit)); + markedRemaining = remaining; + } + + @Override + public synchronized void reset() throws IOException { + super.reset(); + // Restore the remaining count to the value at mark time + remaining = markedRemaining; + } + } + private boolean hasEnoughBytes() { return requiredLength - accumulate.available() <= 0; } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/StreamingDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/StreamingDecoder.java index 08de725c88ce..301312f0703e 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/StreamingDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/StreamingDecoder.java @@ -42,9 +42,12 @@ interface FragmentListener { void bytesRead(int numBytes); /** - * @param rawMessage raw message + * Called when a complete message fragment is received. + * + * @param rawMessage raw message as InputStream + * @param messageLength the length of the message payload in bytes */ - void onFragmentMessage(InputStream rawMessage); + void onFragmentMessage(InputStream rawMessage, int messageLength); default void onClose() {} } @@ -59,6 +62,6 @@ private NoopFragmentListener() {} public void bytesRead(int numBytes) {} @Override - public void onFragmentMessage(InputStream rawMessage) {} + public void onFragmentMessage(InputStream rawMessage, int messageLength) {} } } diff --git a/dubbo-remoting/dubbo-remoting-websocket/src/main/java/org/apache/dubbo/remoting/websocket/FinalFragmentStreamingDecoder.java b/dubbo-remoting/dubbo-remoting-websocket/src/main/java/org/apache/dubbo/remoting/websocket/FinalFragmentStreamingDecoder.java index d5530171186d..8f0f392eb266 100644 --- a/dubbo-remoting/dubbo-remoting-websocket/src/main/java/org/apache/dubbo/remoting/websocket/FinalFragmentStreamingDecoder.java +++ b/dubbo-remoting/dubbo-remoting-websocket/src/main/java/org/apache/dubbo/remoting/websocket/FinalFragmentStreamingDecoder.java @@ -107,12 +107,13 @@ private void deliver() { private void processBody() throws IOException { byte[] rawMessage = readRawMessage(accumulate, accumulate.available()); + int messageLength = rawMessage.length; InputStream inputStream = new ByteArrayInputStream(rawMessage); - invokeListener(inputStream); + invokeListener(inputStream, messageLength); } - protected void invokeListener(InputStream inputStream) { - this.listener.onFragmentMessage(inputStream); + protected void invokeListener(InputStream inputStream, int messageLength) { + this.listener.onFragmentMessage(inputStream, messageLength); } protected byte[] readRawMessage(InputStream inputStream, int length) throws IOException { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java index e52755092a89..dbc2ac0b6032 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java @@ -125,6 +125,7 @@ public static MethodDescriptor findTripleMethodDescriptor( ServiceDescriptor serviceDescriptor, String methodName, InputStream rawMessage) throws IOException { MethodDescriptor methodDescriptor = findReflectionMethodDescriptor(serviceDescriptor, methodName); if (methodDescriptor == null) { + rawMessage.mark(Integer.MAX_VALUE); byte[] data = StreamUtils.readBytes(rawMessage); List methodDescriptors = serviceDescriptor.getMethods(methodName); TripleRequestWrapper request = TripleRequestWrapper.parseFrom(data); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java index 128b649c6c78..d6efad068687 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java @@ -29,6 +29,7 @@ import org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils; import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; +import java.io.InputStream; import java.util.Map; import java.util.concurrent.Executor; @@ -84,7 +85,7 @@ public boolean isReady() { // stream listener start @Override - public void onMessage(byte[] message, boolean isReturnTriException) { + public void onMessage(InputStream message, int messageLength, boolean isReturnTriException) { if (done) { LOGGER.warn( PROTOCOL_STREAM_LISTENER, @@ -95,8 +96,9 @@ public void onMessage(byte[] message, boolean isReturnTriException) { return; } try { + // Use the new InputStream-based parseResponse method Object unpacked = requestMetadata.packableMethod.parseResponse(message, isReturnTriException); - listener.onMessage(unpacked, message.length); + listener.onMessage(unpacked, messageLength); } catch (Throwable t) { TriRpcStatus status = TriRpcStatus.INTERNAL .withDescription("Deserialize response failed") diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/frame/Deframer.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/frame/Deframer.java deleted file mode 100644 index 59083e9a2419..000000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/frame/Deframer.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.frame; - -import io.netty.buffer.ByteBuf; - -public interface Deframer { - - /** - * Adds the given data to this deframer and attempts delivery to the listener. - * - * @param data the raw data read from the remote endpoint. Must be non-null. - */ - void deframe(ByteBuf data); - - /** - * Requests up to the given number of messages from the call. No additional messages will be - * delivered. - * - *

If {@link #close()} has been called, this method will have no effect. - * - * @param numMessages the requested number of messages to be delivered to the listener. - */ - void request(int numMessages); - - /** - * Closes this deframer and frees any resources. After this method is called, additional calls - * will have no effect. - */ - void close(); -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/frame/TriDecoder.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/frame/TriDecoder.java deleted file mode 100644 index 026399627bd4..000000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/frame/TriDecoder.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.frame; - -import org.apache.dubbo.common.config.Configuration; -import org.apache.dubbo.common.config.ConfigurationUtils; -import org.apache.dubbo.rpc.Constants; -import org.apache.dubbo.rpc.RpcException; -import org.apache.dubbo.rpc.model.ApplicationModel; -import org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.CompositeByteBuf; -import io.netty.buffer.Unpooled; - -public class TriDecoder implements Deframer { - - private static final int HEADER_LENGTH = 5; - private static final int COMPRESSED_FLAG_MASK = 1; - private static final int RESERVED_MASK = 0xFE; - private final CompositeByteBuf accumulate = Unpooled.compositeBuffer(); - private final Listener listener; - private final DeCompressor decompressor; - private final Integer maxMessageSize; - private boolean compressedFlag; - private long pendingDeliveries; - private boolean inDelivery = false; - private boolean closing; - private boolean closed; - - private int requiredLength = HEADER_LENGTH; - - private GrpcDecodeState state = GrpcDecodeState.HEADER; - - public TriDecoder(DeCompressor decompressor, Listener listener) { - Configuration conf = ConfigurationUtils.getEnvConfiguration(ApplicationModel.defaultModel()); - maxMessageSize = conf.getInteger(Constants.H2_SETTINGS_MAX_MESSAGE_SIZE, 50 * 1024 * 1024); - this.decompressor = decompressor; - this.listener = listener; - } - - @Override - public void deframe(ByteBuf data) { - if (closing || closed) { - // ignored - return; - } - accumulate.addComponent(true, data); - deliver(); - } - - public void request(int numMessages) { - pendingDeliveries += numMessages; - deliver(); - } - - @Override - public void close() { - closing = true; - deliver(); - } - - private void deliver() { - // We can have reentrancy here when using a direct executor, triggered by calls to - // request more messages. This is safe as we simply loop until pendingDelivers = 0 - if (inDelivery) { - return; - } - inDelivery = true; - try { - // Process the uncompressed bytes. - while (pendingDeliveries > 0 && hasEnoughBytes()) { - switch (state) { - case HEADER: - processHeader(); - break; - case PAYLOAD: - // Read the body and deliver the message. - processBody(); - - // Since we've delivered a message, decrement the number of pending - // deliveries remaining. - pendingDeliveries--; - break; - default: - throw new AssertionError("Invalid state: " + state); - } - } - if (closing) { - if (!closed) { - closed = true; - accumulate.clear(); - accumulate.release(); - listener.close(); - } - } - } finally { - inDelivery = false; - } - } - - private boolean hasEnoughBytes() { - return requiredLength - accumulate.readableBytes() <= 0; - } - - /** - * Processes the GRPC compression header which is composed of the compression flag and the outer - * frame length. - */ - private void processHeader() { - int type = accumulate.readUnsignedByte(); - if ((type & RESERVED_MASK) != 0) { - throw new RpcException("gRPC frame header malformed: reserved bits not zero"); - } - compressedFlag = (type & COMPRESSED_FLAG_MASK) != 0; - - requiredLength = accumulate.readInt(); - - if (requiredLength < 0) { - throw new RpcException("Invalid message length: " + requiredLength); - } - if (requiredLength > maxMessageSize) { - throw new RpcException(String.format("Message size %d exceeds limit %d", requiredLength, maxMessageSize)); - } - - // Continue reading the frame body. - state = GrpcDecodeState.PAYLOAD; - } - - /** - * Processes the GRPC message body, which depending on frame header flags may be compressed. - */ - private void processBody() { - // Calculate total bytes read: header + payload (before decompression) - int totalBytesRead = HEADER_LENGTH + requiredLength; - - byte[] stream; - try { - // There is no reliable way to get the uncompressed size per message when it's compressed, - // because the uncompressed bytes are provided through an InputStream whose total size is - // unknown until all bytes are read, and we don't know when it happens. - stream = compressedFlag ? getCompressedBody() : getUncompressedBody(); - } finally { - // Notify listener about bytes read for flow control immediately after reading bytes - // This must be in finally block to ensure flow control works even if reading fails - // Following gRPC's pattern: bytesRead is called as soon as bytes are consumed from input - listener.bytesRead(totalBytesRead); - } - - // Process the message after notifying about bytes read - listener.onRawMessage(stream); - - // Done with this frame, begin processing the next header. - state = GrpcDecodeState.HEADER; - requiredLength = HEADER_LENGTH; - } - - private byte[] getCompressedBody() { - final byte[] compressedBody = getUncompressedBody(); - return decompressor.decompress(compressedBody); - } - - private byte[] getUncompressedBody() { - byte[] data = new byte[requiredLength]; - accumulate.readBytes(data); - accumulate.discardReadComponents(); - return data; - } - - private enum GrpcDecodeState { - HEADER, - PAYLOAD - } - - public interface Listener { - - void bytesRead(int numBytes); - - void onRawMessage(byte[] data); - - void close(); - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java index 9aa3f5254249..2b97a0c24db8 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java @@ -199,7 +199,7 @@ public void onClose() { } @Override - public void onFragmentMessage(InputStream rawMessage) { + public void onFragmentMessage(InputStream rawMessage, int messageLength) { try { RpcInvocationBuildContext context = getContext(); if (context.getMethodDescriptor() == null) { @@ -209,7 +209,7 @@ public void onFragmentMessage(InputStream rawMessage) { setHttpMessageListener(GrpcHttp2ServerTransportListener.super.buildHttpMessageListener()); } - ((GrpcStreamingDecoder) getStreamingDecoder()).invokeListener(rawMessage); + ((GrpcStreamingDecoder) getStreamingDecoder()).invokeListener(rawMessage, messageLength); } catch (IOException e) { throw new DecodeException(e); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcStreamingDecoder.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcStreamingDecoder.java index d0bc805afcb9..01d8dcd3dee6 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcStreamingDecoder.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcStreamingDecoder.java @@ -50,12 +50,13 @@ protected void processOffset(InputStream inputStream, int lengthFieldOffset) thr } @Override - protected byte[] readRawMessage(InputStream inputStream, int length) throws IOException { - byte[] rawMessage = super.readRawMessage(inputStream, length); - return compressedFlag ? deCompressedMessage(rawMessage) : rawMessage; - } - - private byte[] deCompressedMessage(byte[] rawMessage) { - return deCompressor.decompress(rawMessage); + protected MessageStream readMessageStream(InputStream inputStream, int length) throws IOException { + if (compressedFlag) { + // For compressed messages, we need to read bytes first, then decompress + byte[] rawMessage = readRawMessage(inputStream, length); + byte[] decompressed = deCompressor.decompress(rawMessage); + return new MessageStream(new java.io.ByteArrayInputStream(decompressed), decompressed.length); + } + return super.readMessageStream(inputStream, length); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java index 64ca2ab6993e..81dc76273c56 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java @@ -127,7 +127,7 @@ public void bytesRead(int numBytes) { } @Override - public void onFragmentMessage(InputStream rawMessage) { + public void onFragmentMessage(InputStream rawMessage, int messageLength) { listeningDecoder.decode(rawMessage); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java index 339517e71442..b36d761ec755 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java @@ -21,6 +21,7 @@ import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.message.StreamingDecoder; import org.apache.dubbo.rpc.TriRpcStatus; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.ClassLoadUtil; @@ -33,8 +34,7 @@ import org.apache.dubbo.rpc.protocol.tri.command.InitOnReadyQueueCommand; import org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor; import org.apache.dubbo.rpc.protocol.tri.compressor.Identity; -import org.apache.dubbo.rpc.protocol.tri.frame.Deframer; -import org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder; +import org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcStreamingDecoder; import org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcUtils; import org.apache.dubbo.rpc.protocol.tri.transport.AbstractH2TransportListener; import org.apache.dubbo.rpc.protocol.tri.transport.H2TransportListener; @@ -43,6 +43,7 @@ import javax.net.ssl.SSLSession; import java.io.IOException; +import java.io.InputStream; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.HashMap; @@ -56,6 +57,7 @@ import com.google.rpc.ErrorInfo; import com.google.rpc.Status; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.handler.codec.http2.Http2Error; @@ -79,7 +81,7 @@ public abstract class AbstractTripleClientStream extends AbstractStream implemen private final ClientStream.Listener listener; protected final TripleWriteQueue writeQueue; - private Deframer deframer; + private StreamingDecoder deframer; private final Channel parent; private TripleStreamChannelFuture streamChannelFuture; private boolean halfClosed; @@ -410,7 +412,7 @@ void onHeaderReceived(Http2Headers headers) { } } } - TriDecoder.Listener listener = new TriDecoder.Listener() { + StreamingDecoder.FragmentListener fragmentListener = new StreamingDecoder.FragmentListener() { @Override public void bytesRead(int numBytes) { @@ -418,15 +420,19 @@ public void bytesRead(int numBytes) { } @Override - public void onRawMessage(byte[] data) { - AbstractTripleClientStream.this.listener.onMessage(data, isReturnTriException); + public void onFragmentMessage(InputStream rawMessage, int messageLength) { + AbstractTripleClientStream.this.listener.onMessage(rawMessage, messageLength, isReturnTriException); } - public void close() { + @Override + public void onClose() { finishProcess(statusFromTrailers(trailers), trailers, isReturnTriException); } }; - deframer = new TriDecoder(decompressor, listener); + GrpcStreamingDecoder grpcDecoder = new GrpcStreamingDecoder(); + grpcDecoder.setDeCompressor(decompressor); + grpcDecoder.setFragmentListener(fragmentListener); + deframer = grpcDecoder; AbstractTripleClientStream.this.listener.onStart(); } @@ -580,10 +586,13 @@ private void doOnData(ByteBuf data, boolean endStream) { return; } if (!headerReceived) { + ReferenceCountUtil.release(data); handleH2TransportError(TriRpcStatus.INTERNAL.withDescription("headers not received before payload")); return; } - deframer.deframe(data); + // Use ByteBufInputStream to adapt ByteBuf to InputStream + // The second parameter 'true' means release ByteBuf after reading + deframer.decode(new ByteBufInputStream(data, true)); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/Stream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/Stream.java index d9e56346efbf..5ddbfe2952f7 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/Stream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/Stream.java @@ -20,6 +20,7 @@ import javax.net.ssl.SSLSession; +import java.io.InputStream; import java.net.SocketAddress; import io.netty.handler.codec.http2.Http2Headers; @@ -41,9 +42,11 @@ interface Listener { * Callback when receive message. Note this method may be called many times if is a * streaming . * - * @param message message received from remote peer + * @param message message received from remote peer as InputStream + * @param messageLength the length of the message in bytes + * @param isReturnTriException whether the message is a Triple exception */ - void onMessage(byte[] message, boolean isReturnTriException); + void onMessage(InputStream message, int messageLength, boolean isReturnTriException); /** * Callback when receive cancel signal. diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/frame/RecordListener.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/frame/RecordListener.java deleted file mode 100644 index ac74331562f1..000000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/frame/RecordListener.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.frame; - -public class RecordListener implements TriDecoder.Listener { - byte[] lastData; - int dataCount; - boolean close; - - @Override - public void bytesRead(int numBytes) {} - - @Override - public void onRawMessage(byte[] data) { - dataCount += 1; - lastData = data; - } - - @Override - public void close() { - close = true; - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/frame/TriDecoderTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/frame/TriDecoderTest.java deleted file mode 100644 index 40e793fcb3fb..000000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/frame/TriDecoderTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.frame; - -import org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class TriDecoderTest { - - @Test - void decode() { - final RecordListener listener = new RecordListener(); - TriDecoder decoder = new TriDecoder(DeCompressor.NONE, listener); - final ByteBuf buf = Unpooled.buffer(); - buf.writeByte(0); - buf.writeInt(1); - buf.writeByte(2); - decoder.deframe(buf); - final ByteBuf buf2 = Unpooled.buffer(); - buf2.writeByte(0); - buf2.writeInt(2); - buf2.writeByte(2); - buf2.writeByte(3); - decoder.deframe(buf2); - Assertions.assertEquals(0, listener.dataCount); - decoder.request(1); - Assertions.assertEquals(1, listener.dataCount); - Assertions.assertEquals(1, listener.lastData.length); - decoder.request(1); - Assertions.assertEquals(2, listener.dataCount); - Assertions.assertEquals(2, listener.lastData.length); - decoder.close(); - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/MockClientStreamListener.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/MockClientStreamListener.java index 35219f1f05bf..63d75dd3417d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/MockClientStreamListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/MockClientStreamListener.java @@ -18,6 +18,9 @@ import org.apache.dubbo.rpc.TriRpcStatus; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Map; public class MockClientStreamListener implements ClientStream.Listener { @@ -40,8 +43,18 @@ public void onComplete(TriRpcStatus status, Map attachments) { public void onClose() {} @Override - public void onMessage(byte[] message, boolean isNeedReturnException) { - this.message = message; + public void onMessage(InputStream message, int messageLength, boolean isNeedReturnException) { + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] tmp = new byte[4096]; + int len; + while ((len = message.read(tmp)) != -1) { + buffer.write(tmp, 0, len); + } + this.message = buffer.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override