public FullHttpRequest getHttpRequest() { if (h1Request != null) { return h1Request; } if (h2Headers != null) { try { // Fake out a full HTTP request. FullHttpRequest synthesizedRequest = HttpConversionUtil.toFullHttpRequest(0, h2Headers, alloc, true); if (data != null) { synthesizedRequest.replace(data); } return synthesizedRequest; } catch (Http2Exception e) { // TODO(JR): Do something more meaningful with this exception e.printStackTrace(); } } throw new IllegalStateException("Cannot get the http request for an empty XrpcRequest"); }
@Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { String path = headers.path().toString(); RequestResponder responder; if (path.startsWith(ECHO_STREAM_PATH)) { responder = new EchoStreamResponder(); } else if (path.startsWith(ECHO_TRAILERS_PATH)) { responder = new EchoTrailersResponder(); } else if (path.startsWith(ECHO_ALL_HEADERS_PATH)) { responder = new EchoAllHeadersResponder(); } else if (path.startsWith(ECHO_HEADER_PATH)) { responder = new EchoHeaderResponder(); } else if (path.startsWith(ECHO_METHOD_PATH)) { responder = new EchoMethodResponder(); } else { responder = new RequestResponder(); } responder.onHeadersRead(ctx, streamId, endOfStream, headers); if (!endOfStream) { mResponderMap.put(streamId, responder); } }
@Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { final HttpResponseWrapper res = removeResponse(streamIdToId(streamId)); if (res == null) { if (conn.streamMayHaveExisted(streamId)) { if (logger.isDebugEnabled()) { logger.debug("{} Received a late RST_STREAM frame for a closed stream: {}", ctx.channel(), streamId); } } else { throw connectionError(PROTOCOL_ERROR, "received a RST_STREAM frame for an unknown stream: %d", streamId); } return; } res.close(ClosedSessionException.get()); }
private static String goAwayDebugData(Http2Exception http2Ex, Throwable cause) { final StringBuilder buf = new StringBuilder(256); final String type; final String message; if (http2Ex != null) { type = http2Ex.getClass().getName(); message = http2Ex.getMessage(); } else { type = null; message = null; } buf.append("type: "); buf.append(MoreObjects.firstNonNull(type, "n/a")); buf.append(", message: "); buf.append(MoreObjects.firstNonNull(message, "n/a")); buf.append(", cause: "); buf.append(cause != null ? Throwables.getStackTraceAsString(cause) : "n/a"); return buf.toString(); }
@Test public void outboundCookiesMustBeMergedForHttp1() throws Http2Exception { final HttpHeaders in = new DefaultHttpHeaders(); in.add(HttpHeaderNames.COOKIE, "a=b; c=d"); in.add(HttpHeaderNames.COOKIE, "e=f;g=h"); in.addObject(HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8); in.add(HttpHeaderNames.COOKIE, "i=j"); in.add(HttpHeaderNames.COOKIE, "k=l;"); final io.netty.handler.codec.http.HttpHeaders out = new io.netty.handler.codec.http.DefaultHttpHeaders(); toNettyHttp1(0, in, out, HttpVersion.HTTP_1_1, false, true); assertThat(out.getAll(HttpHeaderNames.COOKIE)) .containsExactly("a=b; c=d; e=f; g=h; i=j; k=l"); }
@Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { HTTPCarbonMessage cMsg = streamIdRequestMap.get(streamId); if (cMsg != null) { cMsg.addHttpContent(new DefaultLastHttpContent(data.retain())); if (endOfStream) { cMsg.setEndOfMsgAdded(true); // if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { // HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestSending(cMsg); // } } } return data.readableBytes() + padding; }
@Override public int onDataRead( ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { if (isServer) { ctx.fireChannelRead( Http2Request.build( streamId, new DefaultHttp2DataFrame(data.retain(), endOfStream, padding), endOfStream)); } else { ctx.fireChannelRead( Http2Response.build( streamId, new DefaultHttp2DataFrame(data.retain(), endOfStream, padding), endOfStream)); } return data.readableBytes() + padding; }
@Override public void onHeadersRead( ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception { if (isServer) { ctx.fireChannelRead(Http2Request.build(streamId, headers, endStream)); } else { ctx.fireChannelRead(Http2Response.build(streamId, headers, endStream)); } }
private void forcefulClose(final ChannelHandlerContext ctx, final ForcefulCloseCommand msg, ChannelPromise promise) throws Exception { close(ctx, promise); connection().forEachActiveStream(new Http2StreamVisitor() { @Override public boolean visit(Http2Stream stream) throws Http2Exception { NettyServerStream.TransportState serverStream = serverStream(stream); if (serverStream != null) { serverStream.transportReportStatus(msg.getStatus()); resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise()); } stream.close(); return true; } }); }
@Override public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception { if (keepAliveManager != null) { keepAliveManager.onDataReceived(); } if (!keepAliveEnforcer.pingAcceptable()) { ByteBuf debugData = ByteBufUtil.writeAscii(ctx.alloc(), "too_many_pings"); goAway(ctx, connection().remote().lastStreamCreated(), Http2Error.ENHANCE_YOUR_CALM.code(), debugData, ctx.newPromise()); Status status = Status.RESOURCE_EXHAUSTED.withDescription("Too many pings from client"); try { forcefulClose(ctx, new ForcefulCloseCommand(status), ctx.newPromise()); } catch (Exception ex) { onError(ctx, ex); } } }
private void forcefulClose(final ChannelHandlerContext ctx, final ForcefulCloseCommand msg, ChannelPromise promise) throws Exception { // close() already called by NettyClientTransport, so just need to clean up streams connection().forEachActiveStream(new Http2StreamVisitor() { @Override public boolean visit(Http2Stream stream) throws Http2Exception { NettyClientStream.TransportState clientStream = clientStream(stream); if (clientStream != null) { clientStream.transportReportStatus(msg.getStatus(), true, new Metadata()); resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise()); } stream.close(); return true; } }); }
/** * Handler for a GOAWAY being either sent or received. Fails any streams created after the * last known stream. */ private void goingAway(Status status) { lifecycleManager.notifyShutdown(status); final Status goAwayStatus = lifecycleManager.getShutdownStatus(); final int lastKnownStream = connection().local().lastStreamKnownByPeer(); try { connection().forEachActiveStream(new Http2StreamVisitor() { @Override public boolean visit(Http2Stream stream) throws Http2Exception { if (stream.id() > lastKnownStream) { NettyClientStream.TransportState clientStream = clientStream(stream); if (clientStream != null) { clientStream.transportReportStatus(goAwayStatus, false, new Metadata()); } stream.close(); } return true; } }); } catch (Http2Exception e) { throw new RuntimeException(e); } }
@Override public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception { Http2Ping p = ping; if (data.getLong(data.readerIndex()) == flowControlPing().payload()) { flowControlPing().updateWindow(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, String.format("Window: %d", decoder().flowController().initialWindowSize(connection().connectionStream()))); } } else if (p != null) { long ackPayload = data.readLong(); if (p.payload() == ackPayload) { p.complete(); ping = null; } else { logger.log(Level.WARNING, String.format( "Received unexpected ping ack. Expecting %d, got %d", p.payload(), ackPayload)); } } else { logger.warning("Received unexpected ping ack. No ping outstanding"); } if (keepAliveManager != null) { keepAliveManager.onDataReceived(); } }
public void updateWindow() throws Http2Exception { if (!autoTuneFlowControlOn) { return; } pingReturn++; long elapsedTime = (System.nanoTime() - lastPingTime); if (elapsedTime == 0) { elapsedTime = 1; } long bandwidth = (getDataSincePing() * TimeUnit.SECONDS.toNanos(1)) / elapsedTime; Http2LocalFlowController fc = decoder().flowController(); // Calculate new window size by doubling the observed BDP, but cap at max window int targetWindow = Math.min(getDataSincePing() * 2, MAX_WINDOW_SIZE); setPinging(false); int currentWindow = fc.initialWindowSize(connection().connectionStream()); if (targetWindow > currentWindow && bandwidth > lastBandwidth) { lastBandwidth = bandwidth; int increase = targetWindow - currentWindow; fc.incrementWindowSize(connection().connectionStream(), increase); fc.initialWindowSize(targetWindow); Http2Settings settings = new Http2Settings(); settings.initialWindowSize(targetWindow); frameWriter().writeSettings(ctx(), settings, ctx().newPromise()); } }
public static Status statusFromThrowable(Throwable t) { Status s = Status.fromThrowable(t); if (s.getCode() != Status.Code.UNKNOWN) { return s; } if (t instanceof ClosedChannelException) { // ClosedChannelException is used any time the Netty channel is closed. Proper error // processing requires remembering the error that occurred before this one and using it // instead. // // Netty uses an exception that has no stack trace, while we would never hope to show this to // users, if it happens having the extra information may provide a small hint of where to // look. ClosedChannelException extraT = new ClosedChannelException(); extraT.initCause(t); return Status.UNKNOWN.withDescription("channel closed").withCause(extraT); } if (t instanceof IOException) { return Status.UNAVAILABLE.withDescription("io exception").withCause(t); } if (t instanceof Http2Exception) { return Status.INTERNAL.withDescription("http2 exception").withCause(t); } return s; }
@Test public void decode_responseHeaders() throws Http2Exception { Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(DEFAULT_MAX_HEADER_LIST_SIZE); Http2HeadersEncoder encoder = new DefaultHttp2HeadersEncoder(NEVER_SENSITIVE); Http2Headers headers = new DefaultHttp2Headers(false); headers.add(of(":status"), of("200")).add(of("custom"), of("header")); encodedHeaders = Unpooled.buffer(); encoder.encodeHeaders(1 /* randomly chosen */, headers, encodedHeaders); Http2Headers decodedHeaders = decoder.decodeHeaders(3 /* randomly chosen */, encodedHeaders); assertEquals(headers.get(of(":status")), decodedHeaders.get(of(":status"))); assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom"))); assertEquals(headers.size(), decodedHeaders.size()); String toString = decodedHeaders.toString(); assertContainsKeyAndValue(toString, ":status", decodedHeaders.get(of(":status"))); assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom"))); }
@Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { RequestResponder responder = mResponderMap.get(streamId); if (endOfStream) { mResponderMap.remove(streamId); } return responder.onDataRead(ctx, streamId, data, padding, endOfStream); }
@Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception { encoder.writeHeaders(ctx, streamId, new DefaultHttp2Headers().status(HttpResponseStatus.OK.codeAsText()), 0, false, ctx.newPromise()); encoder.writeData( ctx, streamId, ctx.alloc().buffer() .writeBytes("HTTP/2 DTP".getBytes(StandardCharsets.UTF_8)), 0, true, ctx.newPromise()); }
/** * If receive a frame with end-of-stream set, send a pre-canned response. */ @Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { int processed = data.readableBytes() + padding; if (endOfStream) { sendResponse(ctx, streamId, data.retain()); } return processed; }
/** * If receive a frame with end-of-stream set, send a pre-canned response. */ @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception { System.out.println("aaa"); if (endStream) { sendResponse(ctx, streamId, RESPONSE_BYTES.duplicate()); } }
/** * Converts the {@link Throwable} to a {@link Status}, taking into account exceptions specific to Armeria as * well. */ public static Status fromThrowable(Throwable t) { requireNonNull(t, "t"); Status s = Status.fromThrowable(t); if (s.getCode() != Code.UNKNOWN) { return s; } if (t instanceof StreamException) { StreamException streamException = (StreamException) t; if (streamException.getMessage() != null && streamException.getMessage().contains("RST_STREAM")) { return Status.CANCELLED; } } if (t instanceof ClosedChannelException) { // ClosedChannelException is used any time the Netty channel is closed. Proper error // processing requires remembering the error that occurred before this one and using it // instead. return Status.UNKNOWN.withCause(t); } if (t instanceof IOException) { return Status.UNAVAILABLE.withCause(t); } if (t instanceof Http2Exception) { return Status.INTERNAL.withCause(t); } if (t instanceof ResponseTimeoutException) { return Status.DEADLINE_EXCEEDED.withCause(t); } return s; }
private static void incrementLocalWindowSize(ChannelPipeline pipeline, int delta) { try { final Http2Connection connection = pipeline.get(Http2ClientConnectionHandler.class).connection(); connection.local().flowController().incrementWindowSize(connection.connectionStream(), delta); } catch (Http2Exception e) { logger.warn("Failed to increment local flowController window size: {}", delta, e); } }
@Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { HttpResponseWrapper res = getResponse(streamIdToId(streamId), endOfStream); if (res == null) { if (conn.streamMayHaveExisted(streamId)) { if (logger.isDebugEnabled()) { logger.debug("{} Received a late HEADERS frame for a closed stream: {}", ctx.channel(), streamId); } return; } throw connectionError(PROTOCOL_ERROR, "received a HEADERS frame for an unknown stream: %d", streamId); } final HttpHeaders converted = ArmeriaHttpUtil.toArmeria(headers); try { res.scheduleTimeout(ctx); res.write(converted); } catch (Throwable t) { res.close(t); throw connectionError(INTERNAL_ERROR, t, "failed to consume a HEADERS frame"); } if (endOfStream) { res.close(); } }
@Override public void onHeadersRead( ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { onHeadersRead(ctx, streamId, headers, padding, endOfStream); }
@Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { final HttpRequestWriter req = requests.get(streamId); if (req == null) { throw connectionError(PROTOCOL_ERROR, "received a RST_STREAM frame for an unknown stream: %d", streamId); } req.close(streamError( streamId, Http2Error.valueOf(errorCode), "received a RST_STREAM frame")); }
private ChannelFuture writeClientHeaders( ChannelHandlerContext ctx, int id, int streamId, HttpHeaders headers, boolean endStream) throws Http2Exception { return writeNonInformationalHeaders( ctx, id, convertClientHeaders(streamId, headers, endStream), endStream); }
private HttpObject convertClientHeaders(int streamId, HttpHeaders headers, boolean endStream) throws Http2Exception { // Leading headers will always have :method, trailers will never have it. final HttpMethod method = headers.method(); if (method == null) { return convertTrailingHeaders(streamId, headers); } // Convert leading headers. final HttpRequest req = new DefaultHttpRequest( HttpVersion.HTTP_1_1, io.netty.handler.codec.http.HttpMethod.valueOf(method.name()), headers.path(), false); convert(streamId, headers, req.headers(), false); if (endStream) { req.headers().remove(HttpHeaderNames.TRANSFER_ENCODING); req.headers().remove(HttpHeaderNames.CONTENT_LENGTH); } else if (HttpUtil.getContentLength(req, -1L) >= 0) { // Avoid the case where both 'content-length' and 'transfer-encoding' are set. req.headers().remove(HttpHeaderNames.TRANSFER_ENCODING); } else { req.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); } return req; }
private void convert( int streamId, HttpHeaders inHeaders, io.netty.handler.codec.http.HttpHeaders outHeaders, boolean trailer) throws Http2Exception { ArmeriaHttpUtil.toNettyHttp1( streamId, inHeaders, outHeaders, HttpVersion.HTTP_1_1, trailer, false); outHeaders.remove(ExtensionHeaderNames.STREAM_ID.text()); if (server) { outHeaders.remove(ExtensionHeaderNames.SCHEME.text()); } else { outHeaders.remove(ExtensionHeaderNames.PATH.text()); } }
/** * Returns {@code true} if the specified exception is expected to occur in well-known circumstances. * <ul> * <li>{@link ClosedChannelException}</li> * <li>{@link ClosedSessionException}</li> * <li>{@link IOException} - 'Connection reset/closed/aborted by peer'</li> * <li>'Broken pipe'</li> * <li>{@link Http2Exception} - 'Stream closed'</li> * </ul> */ public static boolean isExpected(Throwable cause) { if (Flags.verboseExceptions()) { return true; } // We do not need to log every exception because some exceptions are expected to occur. if (cause instanceof ClosedChannelException || cause instanceof ClosedSessionException) { // Can happen when attempting to write to a channel closed by the other end. return true; } final String msg = cause.getMessage(); if (msg != null) { if ((cause instanceof IOException || cause instanceof ChannelException) && IGNORABLE_SOCKET_ERROR_MESSAGE.matcher(msg).find()) { // Can happen when socket error occurs. return true; } if (cause instanceof Http2Exception && IGNORABLE_HTTP2_ERROR_MESSAGE.matcher(msg).find()) { // Can happen when disconnected prematurely. return true; } } return false; }
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { HTTPCarbonMessage cMsg = publishToMessageProcessor(streamId, headers); if (endOfStream) { cMsg.setEndOfMsgAdded(true); // if (HTTPTransportContextHolder.getInstance().getHandlerExecutor() != null) { // HTTPTransportContextHolder.getInstance().getHandlerExecutor().executeAtSourceRequestSending(cMsg); // } } }
@Override public void onHeadersRead( ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endStream) throws Http2Exception { if (isServer) { ctx.fireChannelRead(Http2Request.build(streamId, headers, endStream)); } else { ctx.fireChannelRead(Http2Response.build(streamId, headers, endStream)); } }
@Override public void onPriorityRead( ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive) throws Http2Exception { // TODO(CK): We don't currently have a use case for these frames }
@Override public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception { // h2 clients need to know that server settings have been received before they can write if (!isServer) { ctx.fireUserEventTriggered(RequestBuffer.WriteReady.INSTANCE); } }
@Override public void onPushPromiseRead( ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception { // TODO(CK): We don't currently have a use case for these frames }
public HttpResponseStatus status() { try { return HttpConversionUtil.parseStatus(delegate.status()); } catch (Http2Exception e) { throw new RuntimeException(e); } }
/** * Throws a RuntimeException if the underlying status cannot be converted to an HttpResponseStatus */ @Override public HttpResponseStatus status() { try { return HttpConversionUtil.parseStatus(delegate.status()); } catch (Http2Exception e) { throw new RuntimeException(e); } }
/** Return an Http1 Headers object based on the values in the underlying Http2Headers object. */ @Override public HttpHeaders http1Headers(boolean isTrailer, boolean isRequest) { try { HttpHeaders headers = new DefaultHttpHeaders(); HttpConversionUtil.addHttp2ToHttpHeaders( -1, delegate, headers, HttpVersion.HTTP_1_1, isTrailer, isRequest); return headers; } catch (Http2Exception e) { throw new RuntimeException(e); } }
private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { flowControlPing().onDataRead(data.readableBytes(), padding); try { NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId)); stream.inboundDataReceived(data, endOfStream); } catch (Throwable e) { logger.log(Level.WARNING, "Exception in onDataRead()", e); // Throw an exception that will get handled by onStreamError. throw newStreamException(streamId, e); } }
private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception { try { NettyServerStream.TransportState stream = serverStream(connection().stream(streamId)); if (stream != null) { stream.transportReportStatus( Status.CANCELLED.withDescription("RST_STREAM received for code " + errorCode)); } } catch (Throwable e) { logger.log(Level.WARNING, "Exception in onRstStreamRead()", e); // Throw an exception that will get handled by onStreamError. throw newStreamException(streamId, e); } }