/** * Writes response metadata to the channel if not already written previously and channel is * active. * * @param responseMetadata the {@link HttpResponse} that needs to be written. * @param listener the {@link GenericFutureListener} that needs to be attached to the write. * @return {@code true} if response metadata was written to the channel in this call. {@code * false} otherwise. */ private boolean maybeWriteResponseMetadata( HttpResponse responseMetadata, GenericFutureListener<ChannelFuture> listener) { long writeProcessingStartTime = System.currentTimeMillis(); boolean writtenThisTime = false; if (responseMetadataWritten.compareAndSet(false, true)) { // we do some manipulation here for chunking. According to the HTTP spec, we can have either a // Content-Length // or Transfer-Encoding:chunked, never both. So we check for Content-Length - if it is not // there, we add // Transfer-Encoding:chunked. Note that sending HttpContent chunks data anyway - we are just // explicitly specifying // this in the header. if (!HttpHeaders.isContentLengthSet(responseMetadata)) { // This makes sure that we don't stomp on any existing transfer-encoding. HttpHeaders.setTransferEncodingChunked(responseMetadata); } logger.trace( "Sending response with status {} on channel {}", responseMetadata.getStatus(), ctx.channel()); ChannelPromise writePromise = ctx.newPromise().addListener(listener); ctx.writeAndFlush(responseMetadata, writePromise); writtenThisTime = true; long writeProcessingTime = System.currentTimeMillis() - writeProcessingStartTime; nettyMetrics.responseMetadataProcessingTimeInMs.update(writeProcessingTime); } return writtenThisTime; }
/** * Handler for a connection error. Sends a GO_AWAY frame to the remote endpoint. Once all streams * are closed, the connection is shut down. * * @param ctx the channel context * @param cause the exception that was caught * @param http2Ex the {@link Http2Exception} that is embedded in the causality chain. This may be * {@code null} if it's an unknown exception. */ protected void onConnectionError( ChannelHandlerContext ctx, Throwable cause, Http2Exception http2Ex) { if (http2Ex == null) { http2Ex = new Http2Exception(INTERNAL_ERROR, cause.getMessage(), cause); } goAway(ctx, http2Ex).addListener(new ClosingChannelFutureListener(ctx, ctx.newPromise())); }
/* * SPDY Stream Error Handling: * * Upon a stream error, the endpoint must send a RST_STREAM frame which contains * the Stream-ID for the stream where the error occurred and the error getStatus which * caused the error. * * After sending the RST_STREAM, the stream is closed to the sending endpoint. * * Note: this is only called by the worker thread */ private void issueStreamError(ChannelHandlerContext ctx, int streamId, SpdyStreamStatus status) { boolean fireChannelRead = !spdySession.isRemoteSideClosed(streamId); ChannelPromise promise = ctx.newPromise(); removeStream(streamId, promise); SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, status); ctx.writeAndFlush(spdyRstStreamFrame, promise); if (fireChannelRead) { ctx.fireChannelRead(spdyRstStreamFrame); } }
/** * Sends the HTTP/2 connection preface upon establishment of the connection, if not already * sent. */ private void sendPreface(ChannelHandlerContext ctx) { if (prefaceSent || !ctx.channel().isActive()) { return; } prefaceSent = true; if (!connection().isServer()) { // Clients must send the preface string as the first bytes on the connection. ctx.write(connectionPrefaceBuf()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } // Both client and server must send their initial settings. encoder .writeSettings(ctx, initialSettings(), ctx.newPromise()) .addListener(ChannelFutureListener.CLOSE_ON_FAILURE); }
private void closeOutboundAndChannel( final ChannelHandlerContext ctx, final ChannelPromise promise, boolean disconnect) throws Exception { if (!ctx.channel().isActive()) { if (disconnect) { ctx.disconnect(promise); } else { ctx.close(promise); } return; } engine.closeOutbound(); ChannelPromise closeNotifyFuture = ctx.newPromise(); write(ctx, Unpooled.EMPTY_BUFFER, closeNotifyFuture); flush(ctx); safeClose(ctx, closeNotifyFuture, promise); }
@Override public void flush(ChannelHandlerContext ctx) throws Exception { // Do not encrypt the first write request if this handler is // created with startTLS flag turned on. if (startTls && !sentFirstMessage) { sentFirstMessage = true; pendingUnencryptedWrites.removeAndWriteAll(); ctx.flush(); return; } if (pendingUnencryptedWrites.isEmpty()) { // It's important to NOT use a voidPromise here as the user // may want to add a ChannelFutureListener to the ChannelPromise later. // // See https://github.com/netty/netty/issues/3364 pendingUnencryptedWrites.add(Unpooled.EMPTY_BUFFER, ctx.newPromise()); } if (!handshakePromise.isDone()) { flushedBeforeHandshake = true; } wrap(ctx, false); ctx.flush(); }
/* * SPDY Session Error Handling: * * When a session error occurs, the endpoint encountering the error must first * send a GOAWAY frame with the Stream-ID of the most recently received stream * from the remote endpoint, and the error code for why the session is terminating. * * After sending the GOAWAY frame, the endpoint must close the TCP connection. */ private void issueSessionError(ChannelHandlerContext ctx, SpdySessionStatus status) { sendGoAwayFrame(ctx, status) .addListener(new ClosingChannelFutureListener(ctx, ctx.newPromise())); }
/** * Sends an SSL {@code close_notify} message to the specified channel and destroys the underlying * {@link SSLEngine}. */ public ChannelFuture close() { return close(ctx.newPromise()); }
/** * Close the remote endpoint with with a {@code GO_AWAY} frame. Does <strong>not</strong> flush * immediately, this is the responsibility of the caller. */ private ChannelFuture goAway(ChannelHandlerContext ctx, Http2Exception cause) { long errorCode = cause != null ? cause.error().code() : NO_ERROR.code(); ByteBuf debugData = Http2CodecUtil.toByteBuf(ctx, cause); int lastKnownStream = connection().remote().lastStreamCreated(); return goAway(ctx, lastKnownStream, errorCode, debugData, ctx.newPromise()); }
/** * Handler for a stream error. Sends a {@code RST_STREAM} frame to the remote endpoint and closes * the stream. * * @param ctx the channel context * @param cause the exception that was caught * @param http2Ex the {@link StreamException} that is embedded in the causality chain. */ protected void onStreamError( ChannelHandlerContext ctx, Throwable cause, StreamException http2Ex) { resetStream(ctx, http2Ex.streamId(), http2Ex.error().code(), ctx.newPromise()); }
@Override protected ChannelFuture doOnContent(ChannelHandlerContext context, HttpContent content) { if (_writeChannel != null) { try { ByteBuf data = content.content(); while (data.isReadable()) { data.readBytes(_writeChannel, data.readableBytes()); } if (content instanceof LastHttpContent) { checkContentHeader( ((LastHttpContent) content).trailingHeaders().names(), Collections.singletonList(CONTENT_LENGTH)); context.channel().config().setAutoRead(false); NettyTransferService<HttpProtocolInfo>.NettyMoverChannel writeChannel = _writeChannel; _writeChannel = null; _files.remove(writeChannel); long size = writeChannel.size(); URI location = writeChannel.getProtocolInfo().getLocation(); ChannelPromise promise = context.newPromise(); Futures.addCallback( writeChannel.release(), new FutureCallback<Void>() { @Override public void onSuccess(Void result) { try { context.writeAndFlush(new HttpPutResponse(size, location), promise); } catch (IOException e) { context.writeAndFlush( createErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage()), promise); } context.channel().config().setAutoRead(true); } @Override public void onFailure(Throwable t) { if (t instanceof FileCorruptedCacheException) { context.writeAndFlush( createErrorResponse(BAD_REQUEST, t.getMessage()), promise); } else if (t instanceof CacheException) { context.writeAndFlush( createErrorResponse(INTERNAL_SERVER_ERROR, t.getMessage()), promise); } else { context.writeAndFlush( createErrorResponse(INTERNAL_SERVER_ERROR, t.toString()), promise); } context.channel().config().setAutoRead(true); } }, MoreExecutors.directExecutor()); return promise; } } catch (IOException e) { _writeChannel.release(e); _files.remove(_writeChannel); _writeChannel = null; return context.writeAndFlush(createErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage())); } catch (HttpException e) { _writeChannel.release(e); _files.remove(_writeChannel); _writeChannel = null; return context.writeAndFlush( createErrorResponse(HttpResponseStatus.valueOf(e.getErrorCode()), e.getMessage())); } } return null; }
@Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { handshakeFuture = ctx.newPromise(); }