mirror of https://github.com/grpc/grpc-java.git
core: Delete AbstractStream (replaced by AbstractStream2)
AbstractStream2 will be renamed in a later commit.
This commit is contained in:
parent
55e3b71888
commit
b661ac7d73
|
|
@ -1,336 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014, Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above
|
|
||||||
* copyright notice, this list of conditions and the following disclaimer
|
|
||||||
* in the documentation and/or other materials provided with the
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of Google Inc. nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.grpc.internal;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import io.grpc.Metadata;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The abstract base class for {@link ClientStream} implementations.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractClientStream extends AbstractStream
|
|
||||||
implements ClientStream {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(AbstractClientStream.class.getName());
|
|
||||||
|
|
||||||
private final StatsTraceContext statsTraceCtx;
|
|
||||||
private ClientStreamListener listener;
|
|
||||||
private boolean listenerClosed;
|
|
||||||
|
|
||||||
// Stored status & trailers to report when deframer completes or
|
|
||||||
// transportReportStatus is directly called.
|
|
||||||
private Status status;
|
|
||||||
private Metadata trailers;
|
|
||||||
private Runnable closeListenerTask;
|
|
||||||
private volatile boolean cancelled;
|
|
||||||
|
|
||||||
protected AbstractClientStream(WritableBufferAllocator bufferAllocator, int maxMessageSize,
|
|
||||||
StatsTraceContext statsTraceCtx) {
|
|
||||||
super(bufferAllocator, maxMessageSize, statsTraceCtx);
|
|
||||||
this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxInboundMessageSize(int maxSize) {
|
|
||||||
setMaxInboundMessageSizeProtected(maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxOutboundMessageSize(int maxSize) {
|
|
||||||
setMaxOutboundMessageSizeProtected(maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final ClientStreamListener listener() {
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the stream may proceed to do traffic. Sub classes should override and invoke
|
|
||||||
* this method after they have been created.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void start(ClientStreamListener listener) {
|
|
||||||
checkState(this.listener == null, "stream already started");
|
|
||||||
this.listener = checkNotNull(listener, "listener");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void receiveMessage(InputStream is) {
|
|
||||||
if (!listenerClosed) {
|
|
||||||
checkState(listener != null, "stream not started");
|
|
||||||
listener.messageRead(is);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The transport implementation has detected a protocol error on the stream. Transports are
|
|
||||||
* responsible for properly closing streams when protocol errors occur.
|
|
||||||
*
|
|
||||||
* @param errorStatus the error to report
|
|
||||||
* @param metadata any metadata received
|
|
||||||
*/
|
|
||||||
protected void inboundTransportError(Status errorStatus, Metadata metadata) {
|
|
||||||
Preconditions.checkNotNull(metadata, "metadata");
|
|
||||||
if (inboundPhase() == Phase.STATUS) {
|
|
||||||
log.log(Level.INFO, "Received transport error on closed stream {0} {1}",
|
|
||||||
new Object[]{id(), errorStatus});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// For transport errors we immediately report status to the application layer
|
|
||||||
// and do not wait for additional payloads.
|
|
||||||
transportReportStatus(errorStatus, false, metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by transport implementations when they receive headers.
|
|
||||||
*
|
|
||||||
* @param headers the parsed headers
|
|
||||||
*/
|
|
||||||
protected void inboundHeadersReceived(Metadata headers) {
|
|
||||||
checkState(listener != null, "stream not started");
|
|
||||||
if (inboundPhase() == Phase.STATUS) {
|
|
||||||
log.log(Level.INFO, "Received headers on closed stream {0} {1}",
|
|
||||||
new Object[]{id(), headers});
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundPhase(Phase.MESSAGE);
|
|
||||||
listener.headersRead(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the contents of a received data frame from the server.
|
|
||||||
*
|
|
||||||
* @param frame the received data frame. Its ownership is transferred to this method.
|
|
||||||
*/
|
|
||||||
protected void inboundDataReceived(ReadableBuffer frame) {
|
|
||||||
Preconditions.checkNotNull(frame, "frame");
|
|
||||||
boolean needToCloseFrame = true;
|
|
||||||
try {
|
|
||||||
if (inboundPhase() == Phase.STATUS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inboundPhase() == Phase.HEADERS) {
|
|
||||||
// Have not received headers yet so error
|
|
||||||
inboundTransportError(Status.INTERNAL
|
|
||||||
.withDescription("headers not received before payload"), new Metadata());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inboundPhase(Phase.MESSAGE);
|
|
||||||
|
|
||||||
needToCloseFrame = false;
|
|
||||||
deframe(frame, false);
|
|
||||||
} finally {
|
|
||||||
if (needToCloseFrame) {
|
|
||||||
frame.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inboundDeliveryPaused() {
|
|
||||||
runCloseListenerTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void deframeFailed(Throwable cause) {
|
|
||||||
cancel(Status.fromThrowable(cause));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the trailers and status from the server.
|
|
||||||
*
|
|
||||||
* @param trailers the received trailers
|
|
||||||
* @param status the status extracted from the trailers
|
|
||||||
*/
|
|
||||||
protected void inboundTrailersReceived(Metadata trailers, Status status) {
|
|
||||||
Preconditions.checkNotNull(trailers, "trailers");
|
|
||||||
if (inboundPhase() == Phase.STATUS) {
|
|
||||||
log.log(Level.INFO, "Received trailers on closed stream {0}\n {1}\n {2}",
|
|
||||||
new Object[]{id(), status, trailers});
|
|
||||||
}
|
|
||||||
// Stash the status & trailers so they can be delivered by the deframer calls
|
|
||||||
// remoteEndClosed
|
|
||||||
this.status = status;
|
|
||||||
this.trailers = trailers;
|
|
||||||
deframe(ReadableBuffers.empty(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void remoteEndClosed() {
|
|
||||||
transportReportStatus(status, true, trailers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void internalSendFrame(WritableBuffer frame, boolean endOfStream, boolean flush) {
|
|
||||||
Preconditions.checkArgument(frame != null || endOfStream, "null frame before EOS");
|
|
||||||
sendFrame(frame, endOfStream, flush);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an outbound frame to the remote end point.
|
|
||||||
*
|
|
||||||
* @param frame a buffer containing the chunk of data to be sent or {@code null} if the framer is
|
|
||||||
* closing and has no data to send.
|
|
||||||
* @param endOfStream if {@code true} indicates that no more data will be sent on the stream by
|
|
||||||
* this endpoint.
|
|
||||||
* @param flush {@code true} if more data may not be arriving soon
|
|
||||||
*/
|
|
||||||
protected abstract void sendFrame(WritableBuffer frame, boolean endOfStream, boolean flush);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report stream closure with status to the application layer if not already reported. This method
|
|
||||||
* must be called from the transport thread.
|
|
||||||
*
|
|
||||||
* @param newStatus the new status to set
|
|
||||||
* @param stopDelivery if {@code true}, interrupts any further delivery of inbound messages that
|
|
||||||
* may already be queued up in the deframer. If {@code false}, the listener will be
|
|
||||||
* notified immediately after all currently completed messages in the deframer have been
|
|
||||||
* delivered to the application.
|
|
||||||
* @param trailers new instance of {@code Trailers}, either empty or those returned by the server
|
|
||||||
*/
|
|
||||||
public void transportReportStatus(final Status newStatus, boolean stopDelivery,
|
|
||||||
final Metadata trailers) {
|
|
||||||
Preconditions.checkNotNull(newStatus, "newStatus");
|
|
||||||
|
|
||||||
boolean closingLater = closeListenerTask != null && !stopDelivery;
|
|
||||||
if (listenerClosed || closingLater) {
|
|
||||||
// We already closed (or are about to close) the listener.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundPhase(Phase.STATUS);
|
|
||||||
status = newStatus;
|
|
||||||
closeListenerTask = null;
|
|
||||||
|
|
||||||
// Determine if the deframer is stalled (i.e. currently has no complete messages to deliver).
|
|
||||||
boolean deliveryStalled = isDeframerStalled();
|
|
||||||
|
|
||||||
if (stopDelivery || deliveryStalled) {
|
|
||||||
// Close the listener immediately.
|
|
||||||
closeListener(newStatus, trailers);
|
|
||||||
} else {
|
|
||||||
// Delay close until inboundDeliveryStalled()
|
|
||||||
closeListenerTask = newCloseListenerTask(newStatus, trailers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@link Runnable} to close the listener with the given status/trailers.
|
|
||||||
*/
|
|
||||||
private Runnable newCloseListenerTask(final Status status, final Metadata trailers) {
|
|
||||||
return new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
closeListener(status, trailers);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the listener if not previously closed.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if the call has not yet been started.
|
|
||||||
*/
|
|
||||||
private void closeListener(Status newStatus, Metadata trailers) {
|
|
||||||
checkState(listener != null, "stream not started");
|
|
||||||
if (!listenerClosed) {
|
|
||||||
listenerClosed = true;
|
|
||||||
closeDeframer();
|
|
||||||
statsTraceCtx.streamClosed(newStatus);
|
|
||||||
listener.closed(newStatus, trailers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the pending listener close task, if one exists.
|
|
||||||
*/
|
|
||||||
private void runCloseListenerTask() {
|
|
||||||
if (closeListenerTask != null) {
|
|
||||||
closeListenerTask.run();
|
|
||||||
closeListenerTask = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void halfClose() {
|
|
||||||
if (outboundPhase(Phase.STATUS) != Phase.STATUS) {
|
|
||||||
closeFramer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel the stream. Called by the application layer, never called by the transport.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final void cancel(Status reason) {
|
|
||||||
checkArgument(!reason.isOk(), "Should not cancel with OK status");
|
|
||||||
cancelled = true;
|
|
||||||
sendCancel(reason);
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean isReady() {
|
|
||||||
return !cancelled && super.isReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel the stream and send a stream cancellation message to the remote server, if necessary.
|
|
||||||
* Can be called by either the application or transport layers. This method is safe to be called
|
|
||||||
* at any time and multiple times.
|
|
||||||
*/
|
|
||||||
protected abstract void sendCancel(Status reason);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected MoreObjects.ToStringHelper toStringHelper() {
|
|
||||||
MoreObjects.ToStringHelper toStringHelper = super.toStringHelper();
|
|
||||||
if (status != null) {
|
|
||||||
toStringHelper.add("status", status);
|
|
||||||
}
|
|
||||||
return toStringHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isClosed() {
|
|
||||||
return super.isClosed() || listenerClosed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,448 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014, Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above
|
|
||||||
* copyright notice, this list of conditions and the following disclaimer
|
|
||||||
* in the documentation and/or other materials provided with the
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of Google Inc. nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.grpc.internal;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import io.grpc.Codec;
|
|
||||||
import io.grpc.Compressor;
|
|
||||||
import io.grpc.Decompressor;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract base class for {@link Stream} implementations.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractStream implements Stream {
|
|
||||||
/**
|
|
||||||
* The default number of queued bytes for a given stream, below which
|
|
||||||
* {@link StreamListener#onReady()} will be called.
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_ONREADY_THRESHOLD = 32 * 1024;
|
|
||||||
|
|
||||||
public static final int ABSENT_ID = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates the phase of the GRPC stream in one direction.
|
|
||||||
*/
|
|
||||||
protected enum Phase {
|
|
||||||
HEADERS, MESSAGE, STATUS
|
|
||||||
}
|
|
||||||
|
|
||||||
private final MessageFramer framer;
|
|
||||||
private final MessageDeframer deframer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inbound phase is exclusively written to by the transport thread.
|
|
||||||
*/
|
|
||||||
private Phase inboundPhase = Phase.HEADERS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outbound phase is exclusively written to by the application thread.
|
|
||||||
*/
|
|
||||||
private Phase outboundPhase = Phase.HEADERS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of queued bytes for a given stream, below which {@link StreamListener#onReady()}
|
|
||||||
* will be called.
|
|
||||||
*/
|
|
||||||
private int onReadyThreshold = DEFAULT_ONREADY_THRESHOLD;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of bytes currently queued, waiting to be sent. When this falls below
|
|
||||||
* onReadyThreshold, {@link StreamListener#onReady()} will be called.
|
|
||||||
*/
|
|
||||||
private int numSentBytesQueued;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates the stream has been created on the connection. This implies that the stream is no
|
|
||||||
* longer limited by MAX_CONCURRENT_STREAMS.
|
|
||||||
*/
|
|
||||||
@GuardedBy("onReadyLock")
|
|
||||||
private boolean allocated;
|
|
||||||
|
|
||||||
private final Object onReadyLock = new Object();
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
class FramerSink implements MessageFramer.Sink {
|
|
||||||
@Override
|
|
||||||
public void deliverFrame(WritableBuffer frame, boolean endOfStream, boolean flush) {
|
|
||||||
internalSendFrame(frame, endOfStream, flush);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
class DeframerListener implements MessageDeframer.Listener {
|
|
||||||
@Override
|
|
||||||
public void bytesRead(int numBytes) {
|
|
||||||
returnProcessedBytes(numBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageRead(InputStream input) {
|
|
||||||
receiveMessage(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deliveryStalled() {
|
|
||||||
inboundDeliveryPaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endOfStream() {
|
|
||||||
remoteEndClosed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AbstractStream(WritableBufferAllocator bufferAllocator, int maxMessageSize,
|
|
||||||
StatsTraceContext statsTraceCtx) {
|
|
||||||
framer = new MessageFramer(new FramerSink(), bufferAllocator, statsTraceCtx);
|
|
||||||
deframer = new MessageDeframer(new DeframerListener(), Codec.Identity.NONE, maxMessageSize,
|
|
||||||
statsTraceCtx, getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void setMaxInboundMessageSizeProtected(int maxSize) {
|
|
||||||
deframer.setMaxInboundMessageSize(maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void setMaxOutboundMessageSizeProtected(int maxSize) {
|
|
||||||
framer.setMaxOutboundMessageSize(maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
AbstractStream(MessageFramer framer, MessageDeframer deframer) {
|
|
||||||
this.framer = framer;
|
|
||||||
this.deframer = deframer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this method to provide a stream listener.
|
|
||||||
*/
|
|
||||||
protected abstract StreamListener listener();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the internal ID for this stream. Note that ID can be {@link #ABSENT_ID} for client
|
|
||||||
* streams as the transport may defer creating the stream to the remote side until it has a
|
|
||||||
* payload or metadata to send.
|
|
||||||
*/
|
|
||||||
public abstract int id();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of queued bytes for a given stream, below which {@link StreamListener#onReady()}
|
|
||||||
* will be called. Defaults to {@link #DEFAULT_ONREADY_THRESHOLD}.
|
|
||||||
*/
|
|
||||||
public int getOnReadyThreshold() {
|
|
||||||
synchronized (onReadyLock) {
|
|
||||||
return onReadyThreshold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeMessage(InputStream message) {
|
|
||||||
checkNotNull(message, "message");
|
|
||||||
outboundPhase(Phase.MESSAGE);
|
|
||||||
if (!framer.isClosed()) {
|
|
||||||
framer.writePayload(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void setMessageCompression(boolean enable) {
|
|
||||||
framer.setMessageCompression(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void flush() {
|
|
||||||
if (!framer.isClosed()) {
|
|
||||||
framer.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
if (listener() != null && outboundPhase() != Phase.STATUS) {
|
|
||||||
synchronized (onReadyLock) {
|
|
||||||
return allocated && numSentBytesQueued < onReadyThreshold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the underlying framer.
|
|
||||||
*
|
|
||||||
* <p>No-op if the framer has already been closed.
|
|
||||||
*/
|
|
||||||
final void closeFramer() {
|
|
||||||
if (!framer.isClosed()) {
|
|
||||||
framer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees any resources associated with this stream. Subclass implementations must call this
|
|
||||||
* version.
|
|
||||||
*
|
|
||||||
* <p>NOTE: Can be called by both the transport thread and the application thread. Transport
|
|
||||||
* threads need to dispose when the remote side has terminated the stream. Application threads
|
|
||||||
* will dispose when the application decides to close the stream as part of normal processing.
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
|
||||||
framer.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an outbound frame to the remote end point.
|
|
||||||
*
|
|
||||||
* @param frame a buffer containing the chunk of data to be sent.
|
|
||||||
* @param endOfStream if {@code true} indicates that no more data will be sent on the stream by
|
|
||||||
* this endpoint.
|
|
||||||
* @param flush {@code true} if more data may not be arriving soon
|
|
||||||
*/
|
|
||||||
protected abstract void internalSendFrame(WritableBuffer frame, boolean endOfStream,
|
|
||||||
boolean flush);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a message that was just deframed.
|
|
||||||
*
|
|
||||||
* @param is the stream containing the message
|
|
||||||
*/
|
|
||||||
protected abstract void receiveMessage(InputStream is);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event that the deframer has no pending deliveries.
|
|
||||||
*/
|
|
||||||
protected abstract void inboundDeliveryPaused();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the event that the deframer has reached end of stream.
|
|
||||||
*/
|
|
||||||
protected abstract void remoteEndClosed();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the given number of processed bytes back to inbound flow control to enable receipt of
|
|
||||||
* more data.
|
|
||||||
*/
|
|
||||||
protected abstract void returnProcessedBytes(int processedBytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a {@link #deframe(ReadableBuffer, boolean)} operation failed.
|
|
||||||
*
|
|
||||||
* @param cause the actual failure
|
|
||||||
*/
|
|
||||||
protected abstract void deframeFailed(Throwable cause);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this deframer and frees any resources. After this method is called, additional calls
|
|
||||||
* will have no effect.
|
|
||||||
*/
|
|
||||||
protected final void closeDeframer() {
|
|
||||||
deframer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to parse a received frame and attempt delivery of any completed
|
|
||||||
* messages. Must be called from the transport thread.
|
|
||||||
*/
|
|
||||||
protected final void deframe(ReadableBuffer frame, boolean endOfStream) {
|
|
||||||
try {
|
|
||||||
deframer.deframe(frame, endOfStream);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
deframeFailed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether delivery is currently stalled, pending receipt of more data.
|
|
||||||
*/
|
|
||||||
protected final boolean isDeframerStalled() {
|
|
||||||
return deframer.isStalled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to request the given number of messages from the deframer. Must be called
|
|
||||||
* from the transport thread.
|
|
||||||
*/
|
|
||||||
protected final void requestMessagesFromDeframer(int numMessages) {
|
|
||||||
try {
|
|
||||||
deframer.request(numMessages);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
deframeFailed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void setCompressor(Compressor compressor) {
|
|
||||||
framer.setCompressor(checkNotNull(compressor, "compressor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void setDecompressor(Decompressor decompressor) {
|
|
||||||
deframer.setDecompressor(checkNotNull(decompressor, "decompressor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event handler to be called by the subclass when the stream's headers have passed any connection
|
|
||||||
* flow control (i.e., MAX_CONCURRENT_STREAMS). It may call the listener's {@link
|
|
||||||
* StreamListener#onReady()} handler if appropriate. This must be called from the transport
|
|
||||||
* thread, since the listener may be called back directly.
|
|
||||||
*/
|
|
||||||
protected final void onStreamAllocated() {
|
|
||||||
checkState(listener() != null);
|
|
||||||
synchronized (onReadyLock) {
|
|
||||||
checkState(!allocated, "Already allocated");
|
|
||||||
allocated = true;
|
|
||||||
}
|
|
||||||
notifyIfReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event handler to be called by the subclass when a number of bytes are being queued for sending
|
|
||||||
* to the remote endpoint.
|
|
||||||
*
|
|
||||||
* @param numBytes the number of bytes being sent.
|
|
||||||
*/
|
|
||||||
protected final void onSendingBytes(int numBytes) {
|
|
||||||
synchronized (onReadyLock) {
|
|
||||||
numSentBytesQueued += numBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event handler to be called by the subclass when a number of bytes has been sent to the remote
|
|
||||||
* endpoint. May call back the listener's {@link StreamListener#onReady()} handler if appropriate.
|
|
||||||
* This must be called from the transport thread, since the listener may be called back directly.
|
|
||||||
*
|
|
||||||
* @param numBytes the number of bytes that were sent.
|
|
||||||
*/
|
|
||||||
protected final void onSentBytes(int numBytes) {
|
|
||||||
boolean doNotify;
|
|
||||||
synchronized (onReadyLock) {
|
|
||||||
boolean belowThresholdBefore = numSentBytesQueued < onReadyThreshold;
|
|
||||||
numSentBytesQueued -= numBytes;
|
|
||||||
boolean belowThresholdAfter = numSentBytesQueued < onReadyThreshold;
|
|
||||||
doNotify = !belowThresholdBefore && belowThresholdAfter;
|
|
||||||
}
|
|
||||||
if (doNotify) {
|
|
||||||
notifyIfReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
final void notifyIfReady() {
|
|
||||||
boolean doNotify = false;
|
|
||||||
synchronized (onReadyLock) {
|
|
||||||
doNotify = isReady();
|
|
||||||
}
|
|
||||||
if (doNotify) {
|
|
||||||
listener().onReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Phase inboundPhase() {
|
|
||||||
return inboundPhase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transitions the inbound phase to the given phase and returns the previous phase.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if the transition is disallowed
|
|
||||||
*/
|
|
||||||
final Phase inboundPhase(Phase nextPhase) {
|
|
||||||
Phase tmp = inboundPhase;
|
|
||||||
inboundPhase = verifyNextPhase(inboundPhase, nextPhase);
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Phase outboundPhase() {
|
|
||||||
return outboundPhase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transitions the outbound phase to the given phase and returns the previous phase.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if the transition is disallowed
|
|
||||||
*/
|
|
||||||
final Phase outboundPhase(Phase nextPhase) {
|
|
||||||
Phase tmp = outboundPhase;
|
|
||||||
outboundPhase = verifyNextPhase(outboundPhase, nextPhase);
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Phase verifyNextPhase(Phase currentPhase, Phase nextPhase) {
|
|
||||||
if (nextPhase.ordinal() < currentPhase.ordinal()) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format("Cannot transition phase from %s to %s", currentPhase, nextPhase));
|
|
||||||
}
|
|
||||||
return nextPhase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if the stream can receive data from its remote peer.
|
|
||||||
*/
|
|
||||||
public boolean canReceive() {
|
|
||||||
return inboundPhase() != Phase.STATUS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if the stream can send data to its remote peer.
|
|
||||||
*/
|
|
||||||
public boolean canSend() {
|
|
||||||
return outboundPhase() != Phase.STATUS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the stream is fully closed. Note that this method is not thread-safe as {@code
|
|
||||||
* inboundPhase} and {@code outboundPhase} are mutated in different threads. Tests must account
|
|
||||||
* for thread coordination when calling.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
public boolean isClosed() {
|
|
||||||
return inboundPhase() == Phase.STATUS && outboundPhase() == Phase.STATUS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toStringHelper().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MoreObjects.ToStringHelper toStringHelper() {
|
|
||||||
return MoreObjects.toStringHelper(this)
|
|
||||||
.add("id", id())
|
|
||||||
.add("inboundPhase", inboundPhase().name())
|
|
||||||
.add("outboundPhase", outboundPhase().name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -34,6 +34,7 @@ package io.grpc.internal;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.grpc.Codec;
|
import io.grpc.Codec;
|
||||||
import io.grpc.Compressor;
|
import io.grpc.Compressor;
|
||||||
import io.grpc.Decompressor;
|
import io.grpc.Decompressor;
|
||||||
|
|
@ -119,7 +120,8 @@ public abstract class AbstractStream2 implements Stream {
|
||||||
* The default number of queued bytes for a given stream, below which
|
* The default number of queued bytes for a given stream, below which
|
||||||
* {@link StreamListener#onReady()} will be called.
|
* {@link StreamListener#onReady()} will be called.
|
||||||
*/
|
*/
|
||||||
private static final int DEFAULT_ONREADY_THRESHOLD = 32 * 1024;
|
@VisibleForTesting
|
||||||
|
public static final int DEFAULT_ONREADY_THRESHOLD = 32 * 1024;
|
||||||
|
|
||||||
private final MessageDeframer deframer;
|
private final MessageDeframer deframer;
|
||||||
private final Object onReadyLock = new Object();
|
private final Object onReadyLock = new Object();
|
||||||
|
|
|
||||||
|
|
@ -1,255 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014, Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above
|
|
||||||
* copyright notice, this list of conditions and the following disclaimer
|
|
||||||
* in the documentation and/or other materials provided with the
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of Google Inc. nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.grpc.internal;
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import io.grpc.InternalMetadata;
|
|
||||||
import io.grpc.Metadata;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base implementation for client streams using HTTP2 as the transport.
|
|
||||||
*/
|
|
||||||
public abstract class Http2ClientStream extends AbstractClientStream {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata marshaller for HTTP status lines.
|
|
||||||
*/
|
|
||||||
private static final InternalMetadata.TrustedAsciiMarshaller<Integer> HTTP_STATUS_MARSHALLER =
|
|
||||||
new InternalMetadata.TrustedAsciiMarshaller<Integer>() {
|
|
||||||
@Override
|
|
||||||
public byte[] toAsciiString(Integer value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 7231 says status codes are 3 digits long.
|
|
||||||
*
|
|
||||||
* @see: <a href="https://tools.ietf.org/html/rfc7231#section-6">RFC 7231</a>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Integer parseAsciiString(byte[] serialized) {
|
|
||||||
if (serialized.length >= 3) {
|
|
||||||
return (serialized[0] - '0') * 100 + (serialized[1] - '0') * 10 + (serialized[2] - '0');
|
|
||||||
}
|
|
||||||
throw new NumberFormatException(
|
|
||||||
"Malformed status code " + new String(serialized, InternalMetadata.US_ASCII));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Metadata.Key<Integer> HTTP2_STATUS = InternalMetadata.keyOf(":status",
|
|
||||||
HTTP_STATUS_MARSHALLER);
|
|
||||||
|
|
||||||
/** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */
|
|
||||||
private Status transportError;
|
|
||||||
private Metadata transportErrorMetadata;
|
|
||||||
private Charset errorCharset = Charsets.UTF_8;
|
|
||||||
private boolean headersReceived;
|
|
||||||
|
|
||||||
protected Http2ClientStream(WritableBufferAllocator bufferAllocator, int maxMessageSize,
|
|
||||||
StatsTraceContext statsTraceCtx) {
|
|
||||||
super(bufferAllocator, maxMessageSize, statsTraceCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by subclasses whenever {@code Headers} are received from the transport.
|
|
||||||
*
|
|
||||||
* @param headers the received headers
|
|
||||||
*/
|
|
||||||
protected void transportHeadersReceived(Metadata headers) {
|
|
||||||
Preconditions.checkNotNull(headers, "headers");
|
|
||||||
if (transportError != null) {
|
|
||||||
// Already received a transport error so just augment it. Something is really, really strange.
|
|
||||||
transportError = transportError.augmentDescription("headers: " + headers);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (headersReceived) {
|
|
||||||
transportError = Status.INTERNAL.withDescription("Received headers twice");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Integer httpStatus = headers.get(HTTP2_STATUS);
|
|
||||||
if (httpStatus != null && httpStatus >= 100 && httpStatus < 200) {
|
|
||||||
// Ignore the headers. See RFC 7540 §8.1
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
headersReceived = true;
|
|
||||||
|
|
||||||
transportError = validateInitialMetadata(headers);
|
|
||||||
if (transportError != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stripTransportDetails(headers);
|
|
||||||
inboundHeadersReceived(headers);
|
|
||||||
} finally {
|
|
||||||
if (transportError != null) {
|
|
||||||
// Note we don't immediately report the transport error, instead we wait for more data on
|
|
||||||
// the stream so we can accumulate more detail into the error before reporting it.
|
|
||||||
transportError = transportError.augmentDescription("headers: " + headers);
|
|
||||||
transportErrorMetadata = headers;
|
|
||||||
errorCharset = extractCharset(headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by subclasses whenever a data frame is received from the transport.
|
|
||||||
*
|
|
||||||
* @param frame the received data frame
|
|
||||||
* @param endOfStream {@code true} if there will be no more data received for this stream
|
|
||||||
*/
|
|
||||||
protected void transportDataReceived(ReadableBuffer frame, boolean endOfStream) {
|
|
||||||
if (transportError == null && inboundPhase() == Phase.HEADERS) {
|
|
||||||
// Must receive headers prior to receiving any payload as we use headers to check for
|
|
||||||
// protocol correctness.
|
|
||||||
transportError = Status.INTERNAL.withDescription("no headers received prior to data");
|
|
||||||
transportErrorMetadata = new Metadata();
|
|
||||||
}
|
|
||||||
if (transportError != null) {
|
|
||||||
// We've already detected a transport error and now we're just accumulating more detail
|
|
||||||
// for it.
|
|
||||||
transportError = transportError.augmentDescription("DATA-----------------------------\n"
|
|
||||||
+ ReadableBuffers.readAsString(frame, errorCharset));
|
|
||||||
frame.close();
|
|
||||||
if (transportError.getDescription().length() > 1000 || endOfStream) {
|
|
||||||
inboundTransportError(transportError, transportErrorMetadata);
|
|
||||||
// We have enough error detail so lets cancel.
|
|
||||||
sendCancel(Status.CANCELLED);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
inboundDataReceived(frame);
|
|
||||||
if (endOfStream) {
|
|
||||||
// This is a protocol violation as we expect to receive trailers.
|
|
||||||
transportError =
|
|
||||||
Status.INTERNAL.withDescription("Received unexpected EOS on DATA frame from server.");
|
|
||||||
transportErrorMetadata = new Metadata();
|
|
||||||
inboundTransportError(transportError, transportErrorMetadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by subclasses for the terminal trailer metadata on a stream.
|
|
||||||
*
|
|
||||||
* @param trailers the received terminal trailer metadata
|
|
||||||
*/
|
|
||||||
protected void transportTrailersReceived(Metadata trailers) {
|
|
||||||
Preconditions.checkNotNull(trailers, "trailers");
|
|
||||||
if (transportError == null && !headersReceived) {
|
|
||||||
transportError = validateInitialMetadata(trailers);
|
|
||||||
if (transportError != null) {
|
|
||||||
transportErrorMetadata = trailers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (transportError != null) {
|
|
||||||
transportError = transportError.augmentDescription("trailers: " + trailers);
|
|
||||||
inboundTransportError(transportError, transportErrorMetadata);
|
|
||||||
sendCancel(Status.CANCELLED);
|
|
||||||
} else {
|
|
||||||
Status status = statusFromTrailers(trailers);
|
|
||||||
stripTransportDetails(trailers);
|
|
||||||
inboundTrailersReceived(trailers, status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the response status from trailers.
|
|
||||||
*/
|
|
||||||
private Status statusFromTrailers(Metadata trailers) {
|
|
||||||
Status status = trailers.get(Status.CODE_KEY);
|
|
||||||
if (status != null) {
|
|
||||||
return status.withDescription(trailers.get(Status.MESSAGE_KEY));
|
|
||||||
}
|
|
||||||
// No status; something is broken. Try to provide a resonanable error.
|
|
||||||
if (headersReceived) {
|
|
||||||
return Status.UNKNOWN.withDescription("missing GRPC status in response");
|
|
||||||
}
|
|
||||||
Integer httpStatus = trailers.get(HTTP2_STATUS);
|
|
||||||
if (httpStatus != null) {
|
|
||||||
status = GrpcUtil.httpStatusToGrpcStatus(httpStatus);
|
|
||||||
} else {
|
|
||||||
status = Status.INTERNAL.withDescription("missing HTTP status code");
|
|
||||||
}
|
|
||||||
return status.augmentDescription(
|
|
||||||
"missing GRPC status, inferred error from HTTP status code");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inspect initial headers to make sure they conform to HTTP and gRPC, returning a {@code Status}
|
|
||||||
* on failure.
|
|
||||||
*
|
|
||||||
* @return status with description of failure, or {@code null} when valid
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private Status validateInitialMetadata(Metadata headers) {
|
|
||||||
Integer httpStatus = headers.get(HTTP2_STATUS);
|
|
||||||
if (httpStatus == null) {
|
|
||||||
return Status.INTERNAL.withDescription("Missing HTTP status code");
|
|
||||||
}
|
|
||||||
String contentType = headers.get(GrpcUtil.CONTENT_TYPE_KEY);
|
|
||||||
if (!GrpcUtil.isGrpcContentType(contentType)) {
|
|
||||||
return GrpcUtil.httpStatusToGrpcStatus(httpStatus)
|
|
||||||
.augmentDescription("invalid content-type: " + contentType);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inspect the raw metadata and figure out what charset is being used.
|
|
||||||
*/
|
|
||||||
private static Charset extractCharset(Metadata headers) {
|
|
||||||
String contentType = headers.get(GrpcUtil.CONTENT_TYPE_KEY);
|
|
||||||
if (contentType != null) {
|
|
||||||
String[] split = contentType.split("charset=");
|
|
||||||
try {
|
|
||||||
return Charset.forName(split[split.length - 1].trim());
|
|
||||||
} catch (Exception t) {
|
|
||||||
// Ignore and assume UTF-8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Charsets.UTF_8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strip HTTP transport implementation details so they don't leak via metadata into
|
|
||||||
* the application layer.
|
|
||||||
*/
|
|
||||||
private static void stripTransportDetails(Metadata metadata) {
|
|
||||||
metadata.discardAll(HTTP2_STATUS);
|
|
||||||
metadata.discardAll(Status.CODE_KEY);
|
|
||||||
metadata.discardAll(Status.MESSAGE_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,288 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015, Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above
|
|
||||||
* copyright notice, this list of conditions and the following disclaimer
|
|
||||||
* in the documentation and/or other materials provided with the
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of Google Inc. nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.grpc.internal;
|
|
||||||
|
|
||||||
import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
import static org.mockito.Matchers.isA;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.Codec;
|
|
||||||
import io.grpc.Metadata;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.Status.Code;
|
|
||||||
import io.grpc.internal.AbstractStream.Phase;
|
|
||||||
import io.grpc.internal.MessageFramerTest.ByteWritableBuffer;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test for {@link AbstractClientStream}. This class tries to test functionality in
|
|
||||||
* AbstractClientStream, but not in any super classes.
|
|
||||||
*/
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class AbstractClientStreamTest {
|
|
||||||
|
|
||||||
@Rule public final ExpectedException thrown = ExpectedException.none();
|
|
||||||
|
|
||||||
@Mock private ClientStreamListener mockListener;
|
|
||||||
@Captor private ArgumentCaptor<Status> statusCaptor;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final WritableBufferAllocator allocator = new WritableBufferAllocator() {
|
|
||||||
@Override
|
|
||||||
public WritableBuffer allocate(int capacityHint) {
|
|
||||||
return new ByteWritableBuffer(capacityHint);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cancel_doNotAcceptOk() {
|
|
||||||
for (Code code : Code.values()) {
|
|
||||||
ClientStreamListener listener = new NoopClientStreamListener();
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(listener);
|
|
||||||
if (code != Code.OK) {
|
|
||||||
stream.cancel(Status.fromCodeValue(code.value()));
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
stream.cancel(Status.fromCodeValue(code.value()));
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cancel_failsOnNull() {
|
|
||||||
ClientStreamListener listener = new NoopClientStreamListener();
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(listener);
|
|
||||||
thrown.expect(NullPointerException.class);
|
|
||||||
|
|
||||||
stream.cancel(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cancel_notifiesOnlyOnce() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator) {
|
|
||||||
@Override
|
|
||||||
protected void sendCancel(Status errorStatus) {
|
|
||||||
transportReportStatus(errorStatus, true/*stop delivery*/, new Metadata());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
stream.start(mockListener);
|
|
||||||
|
|
||||||
stream.cancel(Status.DEADLINE_EXCEEDED);
|
|
||||||
|
|
||||||
verify(mockListener).closed(isA(Status.class), isA(Metadata.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void startFailsOnNullListener() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
|
|
||||||
thrown.expect(NullPointerException.class);
|
|
||||||
|
|
||||||
stream.start(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cantCallStartTwice() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
thrown.expect(IllegalStateException.class);
|
|
||||||
|
|
||||||
stream.start(mockListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deframeFailed_notifiesListener() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator) {
|
|
||||||
@Override
|
|
||||||
protected void sendCancel(Status errorStatus) {
|
|
||||||
transportReportStatus(errorStatus, true/*stop delivery*/, new Metadata());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
stream.start(mockListener);
|
|
||||||
|
|
||||||
stream.deframeFailed(new RuntimeException("something bad"));
|
|
||||||
|
|
||||||
verify(mockListener).closed(statusCaptor.capture(), isA(Metadata.class));
|
|
||||||
assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundDataReceived_failsOnNullFrame() {
|
|
||||||
ClientStreamListener listener = new NoopClientStreamListener();
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(listener);
|
|
||||||
thrown.expect(NullPointerException.class);
|
|
||||||
|
|
||||||
stream.inboundDataReceived(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundDataReceived_failsOnNoHeaders() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
stream.inboundPhase(Phase.HEADERS);
|
|
||||||
|
|
||||||
stream.inboundDataReceived(ReadableBuffers.empty());
|
|
||||||
|
|
||||||
verify(mockListener).closed(statusCaptor.capture(), isA(Metadata.class));
|
|
||||||
assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundHeadersReceived_notifiesListener() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
Metadata headers = new Metadata();
|
|
||||||
|
|
||||||
stream.inboundHeadersReceived(headers);
|
|
||||||
verify(mockListener).headersRead(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundHeadersReceived_failsOnPhaseStatus() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
Metadata headers = new Metadata();
|
|
||||||
stream.inboundPhase(Phase.STATUS);
|
|
||||||
|
|
||||||
thrown.expect(IllegalStateException.class);
|
|
||||||
|
|
||||||
stream.inboundHeadersReceived(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundHeadersReceived_succeedsOnPhaseMessage() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
Metadata headers = new Metadata();
|
|
||||||
stream.inboundPhase(Phase.MESSAGE);
|
|
||||||
|
|
||||||
stream.inboundHeadersReceived(headers);
|
|
||||||
|
|
||||||
verify(mockListener).headersRead(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundHeadersReceived_acceptsGzipEncoding() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
Metadata headers = new Metadata();
|
|
||||||
headers.put(GrpcUtil.MESSAGE_ENCODING_KEY, new Codec.Gzip().getMessageEncoding());
|
|
||||||
|
|
||||||
stream.inboundHeadersReceived(headers);
|
|
||||||
verify(mockListener).headersRead(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundHeadersReceived_acceptsIdentityEncoding() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
Metadata headers = new Metadata();
|
|
||||||
headers.put(GrpcUtil.MESSAGE_ENCODING_KEY, Codec.Identity.NONE.getMessageEncoding());
|
|
||||||
|
|
||||||
stream.inboundHeadersReceived(headers);
|
|
||||||
verify(mockListener).headersRead(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void rstStreamClosesStream() {
|
|
||||||
AbstractClientStream stream = new BaseAbstractClientStream(allocator);
|
|
||||||
stream.start(mockListener);
|
|
||||||
// The application will call request when waiting for a message, which will in turn call this
|
|
||||||
// on the transport thread.
|
|
||||||
stream.requestMessagesFromDeframer(1);
|
|
||||||
// Send first byte of 2 byte message
|
|
||||||
stream.deframe(ReadableBuffers.wrap(new byte[] {0, 0, 0, 0, 2, 1}), false);
|
|
||||||
Status status = Status.INTERNAL;
|
|
||||||
// Simulate getting a reset
|
|
||||||
stream.transportReportStatus(status, false /*stop delivery*/, new Metadata());
|
|
||||||
|
|
||||||
assertTrue(stream.isClosed());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No-op base class for testing.
|
|
||||||
*/
|
|
||||||
private static class BaseAbstractClientStream extends AbstractClientStream {
|
|
||||||
protected BaseAbstractClientStream(WritableBufferAllocator allocator) {
|
|
||||||
super(allocator, DEFAULT_MAX_MESSAGE_SIZE, StatsTraceContext.NOOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAuthority(String authority) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Attributes getAttributes() {
|
|
||||||
return Attributes.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void request(int numMessages) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendFrame(WritableBuffer frame, boolean endOfStream, boolean flush) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendCancel(Status reason) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int id() {
|
|
||||||
return ABSENT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void returnProcessedBytes(int processedBytes) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015, Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above
|
|
||||||
* copyright notice, this list of conditions and the following disclaimer
|
|
||||||
* in the documentation and/or other materials provided with the
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of Google Inc. nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package io.grpc.internal;
|
|
||||||
|
|
||||||
import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import io.grpc.internal.AbstractStream.Phase;
|
|
||||||
import io.grpc.internal.MessageFramerTest.ByteWritableBuffer;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class AbstractStreamTest {
|
|
||||||
@Mock private StreamListener streamListener;
|
|
||||||
|
|
||||||
@Mock MessageFramer framer;
|
|
||||||
@Mock MessageDeframer deframer;
|
|
||||||
|
|
||||||
private final WritableBufferAllocator allocator = new WritableBufferAllocator() {
|
|
||||||
@Override
|
|
||||||
public WritableBuffer allocate(int capacityHint) {
|
|
||||||
return new ByteWritableBuffer(capacityHint);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onStreamAllocated_shouldNotifyReady() {
|
|
||||||
AbstractStream stream = new AbstractStreamBase(null);
|
|
||||||
|
|
||||||
stream.onStreamAllocated();
|
|
||||||
|
|
||||||
verify(streamListener).onReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setMessageCompression() {
|
|
||||||
AbstractStream as = new AbstractStreamBase(framer, deframer);
|
|
||||||
as.setMessageCompression(true);
|
|
||||||
|
|
||||||
verify(framer).setMessageCompression(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validPhaseTransitions() {
|
|
||||||
AbstractStream stream = new AbstractStreamBase(null);
|
|
||||||
Multimap<Phase, Phase> validTransitions = ImmutableMultimap.<Phase, Phase>builder()
|
|
||||||
.put(Phase.HEADERS, Phase.HEADERS)
|
|
||||||
.put(Phase.HEADERS, Phase.MESSAGE)
|
|
||||||
.put(Phase.HEADERS, Phase.STATUS)
|
|
||||||
.put(Phase.MESSAGE, Phase.MESSAGE)
|
|
||||||
.put(Phase.MESSAGE, Phase.STATUS)
|
|
||||||
.put(Phase.STATUS, Phase.STATUS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
for (Phase startPhase : Phase.values()) {
|
|
||||||
for (Phase endPhase : Phase.values()) {
|
|
||||||
if (validTransitions.containsEntry(startPhase, endPhase)) {
|
|
||||||
stream.verifyNextPhase(startPhase, endPhase);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
stream.verifyNextPhase(startPhase, endPhase);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException expected) {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for testing.
|
|
||||||
*/
|
|
||||||
private class AbstractStreamBase extends AbstractStream {
|
|
||||||
private AbstractStreamBase(WritableBufferAllocator bufferAllocator) {
|
|
||||||
super(allocator, DEFAULT_MAX_MESSAGE_SIZE, StatsTraceContext.NOOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractStreamBase(MessageFramer framer, MessageDeframer deframer) {
|
|
||||||
super(framer, deframer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void request(int numMessages) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int id() {
|
|
||||||
return ABSENT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected StreamListener listener() {
|
|
||||||
return streamListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void internalSendFrame(WritableBuffer frame, boolean endOfStream, boolean flush) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void receiveMessage(InputStream is) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inboundDeliveryPaused() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void remoteEndClosed() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void returnProcessedBytes(int processedBytes) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void deframeFailed(Throwable cause) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ import io.grpc.MethodDescriptor.MethodType;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
import io.grpc.StatusException;
|
import io.grpc.StatusException;
|
||||||
import io.grpc.internal.AbstractStream;
|
import io.grpc.internal.AbstractStream2;
|
||||||
import io.grpc.internal.ClientStreamListener;
|
import io.grpc.internal.ClientStreamListener;
|
||||||
import io.grpc.internal.ClientTransport;
|
import io.grpc.internal.ClientTransport;
|
||||||
import io.grpc.internal.GrpcUtil;
|
import io.grpc.internal.GrpcUtil;
|
||||||
|
|
@ -1121,7 +1121,8 @@ public class OkHttpClientTransportTest {
|
||||||
public void notifyOnReady() throws Exception {
|
public void notifyOnReady() throws Exception {
|
||||||
initTransport();
|
initTransport();
|
||||||
// exactly one byte below the threshold
|
// exactly one byte below the threshold
|
||||||
int messageLength = AbstractStream.DEFAULT_ONREADY_THRESHOLD - HEADER_LENGTH - 1;
|
int messageLength =
|
||||||
|
AbstractStream2.TransportState.DEFAULT_ONREADY_THRESHOLD - HEADER_LENGTH - 1;
|
||||||
setInitialWindowSize(0);
|
setInitialWindowSize(0);
|
||||||
MockStreamListener listener = new MockStreamListener();
|
MockStreamListener listener = new MockStreamListener();
|
||||||
OkHttpClientStream stream = clientTransport.newStream(method, new Metadata());
|
OkHttpClientStream stream = clientTransport.newStream(method, new Metadata());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue