From 9b563c8a2829a43de19734b92a7eedd764a750d2 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 11 Feb 2012 12:52:56 +0100 Subject: [PATCH 1/5] SPDY Protocol HTTP Layer. See #184 --- .../handler/codec/spdy/SpdyCodecUtil.java | 8 + .../handler/codec/spdy/SpdyFrameCodec.java | 21 +- .../handler/codec/spdy/SpdyFrameDecoder.java | 73 +++- .../handler/codec/spdy/SpdyFrameEncoder.java | 16 +- .../handler/codec/spdy/SpdyHttpCodec.java | 64 ++++ .../handler/codec/spdy/SpdyHttpDecoder.java | 296 ++++++++++++++++ .../handler/codec/spdy/SpdyHttpEncoder.java | 325 ++++++++++++++++++ .../handler/codec/spdy/SpdyHttpHeaders.java | 162 +++++++++ .../codec/spdy/SpdySessionHandler.java | 6 +- 9 files changed, 956 insertions(+), 15 deletions(-) create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java index 599cf9fb73..38adfcbd1e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java @@ -139,6 +139,14 @@ final class SpdyCodecUtil { (buf.getByte(offset + 3) & 0xFF)); } + /** + * Returns {@code true} if ID is for a server initiated stream or ping. + */ + static boolean isServerID(int ID) { + // Server initiated streams and pings have even IDs + return ID % 2 == 0; + } + /** * Validate a SPDY header name. */ diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java index fd424f5ef5..5d47322ae5 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameCodec.java @@ -43,10 +43,27 @@ import io.netty.channel.ChannelUpstreamHandler; public class SpdyFrameCodec implements ChannelUpstreamHandler, ChannelDownstreamHandler { - private final SpdyFrameDecoder decoder = new SpdyFrameDecoder(); - private final SpdyFrameEncoder encoder = new SpdyFrameEncoder(); + private final SpdyFrameDecoder decoder; + private final SpdyFrameEncoder encoder; + /** + * Creates a new instance with the default decoder and encoder options + * ({@code maxChunkSize (8192)}, {@code maxFrameSize (65536)}, + * {@code maxHeaderSize (16384)}, {@code compressionLevel (6)}, + * {@code windowBits (15)}, and {@code memLevel (8)}). + */ public SpdyFrameCodec() { + this(8192, 65536, 16384, 6, 15, 8); + } + + /** + * Creates a new instance with the specified decoder and encoder options. + */ + public SpdyFrameCodec( + int maxChunkSize, int maxFrameSize, int maxHeaderSize, + int compressionLevel, int windowBits, int memLevel) { + decoder = new SpdyFrameDecoder(maxChunkSize, maxFrameSize, maxHeaderSize); + encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel); } public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java index 001d507c8f..6758e395a9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java @@ -44,11 +44,42 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; */ public class SpdyFrameDecoder extends FrameDecoder { + private final int maxChunkSize; + private final int maxFrameSize; + private final int maxHeaderSize; + private final DecoderEmbedder headerBlockDecompressor = new DecoderEmbedder(new ZlibDecoder(SPDY_DICT)); + /** + * Creates a new instance with the default {@code maxChunkSize (8192)}, + * {@code maxFrameSize (65536)}, and {@code maxHeaderSize (16384)}. + */ public SpdyFrameDecoder() { - super(); + this(8192, 65536, 16384); + } + + /** + * Creates a new instance with the specified parameters. + */ + public SpdyFrameDecoder( + int maxChunkSize, int maxFrameSize, int maxHeaderSize) { + super(true); // Enable unfold for data frames + if (maxChunkSize <= 0) { + throw new IllegalArgumentException( + "maxChunkSize must be a positive integer: " + maxChunkSize); + } + if (maxFrameSize <= 0) { + throw new IllegalArgumentException( + "maxFrameSize must be a positive integer: " + maxFrameSize); + } + if (maxHeaderSize <= 0) { + throw new IllegalArgumentException( + "maxHeaderSize must be a positive integer: " + maxHeaderSize); + } + this.maxChunkSize = maxChunkSize; + this.maxFrameSize = maxFrameSize; + this.maxHeaderSize = maxHeaderSize; } @Override @@ -67,6 +98,12 @@ public class SpdyFrameDecoder extends FrameDecoder { int dataLength = getUnsignedMedium(buffer, lengthOffset); int frameLength = SPDY_HEADER_SIZE + dataLength; + // Throw exception if frameLength exceeds maxFrameSize + if (frameLength > maxFrameSize) { + throw new SpdyProtocolException( + "Frame length exceeds " + maxFrameSize + ": " + frameLength); + } + // Wait until entire frame is readable if (buffer.readableBytes() < frameLength) { return null; @@ -98,12 +135,25 @@ public class SpdyFrameDecoder extends FrameDecoder { int streamID = getUnsignedInt(buffer, frameOffset); buffer.skipBytes(SPDY_HEADER_SIZE); - SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); - spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0); - spdyDataFrame.setCompressed((flags & SPDY_DATA_FLAG_COMPRESS) != 0); - spdyDataFrame.setData(buffer.readBytes(dataLength)); + // Generate data frames that do not exceed maxChunkSize + int numFrames = dataLength / maxChunkSize; + if (dataLength % maxChunkSize != 0) { + numFrames ++; + } + SpdyDataFrame[] frames = new SpdyDataFrame[numFrames]; + for (int i = 0; i < numFrames; i++) { + int chunkSize = Math.min(maxChunkSize, dataLength); + SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); + spdyDataFrame.setCompressed((flags & SPDY_DATA_FLAG_COMPRESS) != 0); + spdyDataFrame.setData(buffer.readBytes(chunkSize)); + dataLength -= chunkSize; + if (dataLength == 0) { + spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0); + } + frames[i] = spdyDataFrame; + } - return spdyDataFrame; + return frames; } } @@ -276,6 +326,7 @@ public class SpdyFrameDecoder extends FrameDecoder { throw new SpdyProtocolException( "Received invalid header block"); } + int headerSize = 0; int numEntries = getUnsignedShort(headerBlock, headerBlock.readerIndex()); headerBlock.skipBytes(2); for (int i = 0; i < numEntries; i ++) { @@ -289,6 +340,11 @@ public class SpdyFrameDecoder extends FrameDecoder { headerFrame.setInvalid(); return; } + headerSize += nameLength; + if (headerSize > maxHeaderSize) { + throw new SpdyProtocolException( + "Header block exceeds " + maxHeaderSize); + } if (headerBlock.readableBytes() < nameLength) { throw new SpdyProtocolException( "Received invalid header block"); @@ -310,6 +366,11 @@ public class SpdyFrameDecoder extends FrameDecoder { headerFrame.setInvalid(); return; } + headerSize += valueLength; + if (headerSize > maxHeaderSize) { + throw new SpdyProtocolException( + "Header block exceeds " + maxHeaderSize); + } if (headerBlock.readableBytes() < valueLength) { throw new SpdyProtocolException( "Received invalid header block"); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java index 19e51fea4c..ea9f2e469b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java @@ -48,11 +48,23 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; */ public class SpdyFrameEncoder extends OneToOneEncoder { - private final EncoderEmbedder headerBlockCompressor = - new EncoderEmbedder(new ZlibEncoder(9, SPDY_DICT)); + private final EncoderEmbedder headerBlockCompressor; + /** + * Creates a new instance with the default {@code compressionLevel (6)}, + * {@code windowBits (15)}, and {@code memLevel (8)}. + */ public SpdyFrameEncoder() { + this(6, 15, 8); + } + + /** + * Creates a new instance with the specified parameters. + */ + public SpdyFrameEncoder(int compressionLevel, int windowBits, int memLevel) { super(); + headerBlockCompressor = new EncoderEmbedder( + new ZlibEncoder(compressionLevel, windowBits, memLevel, SPDY_DICT)); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java new file mode 100644 index 0000000000..75bfcdceb4 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpCodec.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.netty.handler.codec.spdy; + +import io.netty.channel.ChannelDownstreamHandler; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelUpstreamHandler; + +/** + * A combination of {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder} + * @apiviz.has io.netty.handler.codec.sdpy.SpdyHttpDecoder + * @apiviz.has io.netty.handler.codec.spdy.SpdyHttpEncoder + */ +public class SpdyHttpCodec implements ChannelUpstreamHandler, ChannelDownstreamHandler { + + private final SpdyHttpDecoder decoder; + private final SpdyHttpEncoder encoder = new SpdyHttpEncoder(); + + /** + * Creates a new instance with the specified decoder options. + */ + public SpdyHttpCodec(int maxContentLength) { + decoder = new SpdyHttpDecoder(maxContentLength); + } + + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + decoder.handleUpstream(ctx, e); + } + + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + encoder.handleDownstream(ctx, e); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java new file mode 100644 index 0000000000..fdd1f49e4d --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -0,0 +1,296 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.netty.handler.codec.spdy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.Channels; +import io.netty.handler.codec.frame.TooLongFrameException; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMessage; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.oneone.OneToOneDecoder; + +/** + * Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s, + * and {@link SpdyDataFrame}s into {@link HttpRequest}s and {@link HttpResponse}s. + */ +public class SpdyHttpDecoder extends OneToOneDecoder { + + private final int maxContentLength; + private final Map messageMap = new HashMap(); + + /** + * Creates a new instance. + * + * @param maxContentLength the maximum length of the message content. + * If the length of the message content exceeds this value, + * a {@link TooLongFrameException} will be raised. + */ + public SpdyHttpDecoder(int maxContentLength) { + super(); + if (maxContentLength <= 0) { + throw new IllegalArgumentException( + "maxContentLength must be a positive integer: " + maxContentLength); + } + this.maxContentLength = maxContentLength; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) + throws Exception { + + if (msg instanceof SpdySynStreamFrame) { + + // HTTP requests/responses are mapped one-to-one to SPDY streams. + SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; + int streamID = spdySynStreamFrame.getStreamID(); + + if (SpdyCodecUtil.isServerID(streamID)) { + // SYN_STREAM frames inititated by the server are pushed resources + int associatedToStreamID = spdySynStreamFrame.getAssociatedToStreamID(); + + // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0 + // it must reply with a RST_STREAM with error code INVALID_STREAM + if (associatedToStreamID == 0) { + SpdyRstStreamFrame spdyRstStreamFrame = + new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.INVALID_STREAM); + Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame); + } + + String URL = SpdyHeaders.getUrl(spdySynStreamFrame); + + // If a client receives a SYN_STREAM without a 'url' header + // it must reply with a RST_STREAM with error code PROTOCOL_ERROR + if (URL == null) { + SpdyRstStreamFrame spdyRstStreamFrame = + new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.PROTOCOL_ERROR); + Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame); + } + + try { + HttpResponse httpResponse = createHttpResponse(spdySynStreamFrame); + + // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers + SpdyHttpHeaders.setStreamID(httpResponse, streamID); + SpdyHttpHeaders.setAssociatedToStreamID(httpResponse, associatedToStreamID); + SpdyHttpHeaders.setPriority(httpResponse, spdySynStreamFrame.getPriority()); + SpdyHttpHeaders.setUrl(httpResponse, URL); + + if (spdySynStreamFrame.isLast()) { + HttpHeaders.setContentLength(httpResponse, 0); + return httpResponse; + } else { + // Response body will follow in a series of Data Frames + messageMap.put(new Integer(streamID), httpResponse); + } + } catch (Exception e) { + SpdyRstStreamFrame spdyRstStreamFrame = + new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.PROTOCOL_ERROR); + Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame); + } + + } else { + // SYN_STREAM frames initiated by the client are HTTP requests + try { + HttpRequest httpRequest = createHttpRequest(spdySynStreamFrame); + + // Set the Stream-ID as a header + SpdyHttpHeaders.setStreamID(httpRequest, streamID); + + if (spdySynStreamFrame.isLast()) { + return httpRequest; + } else { + // Request body will follow in a series of Data Frames + messageMap.put(new Integer(streamID), httpRequest); + } + } catch (Exception e) { + // If a client sends a SYN_STREAM without method, url, and version headers + // the server must reply with a HTTP 400 BAD REQUEST reply + // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid + SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); + spdySynReplyFrame.setLast(true); + SpdyHeaders.setStatus(spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST); + SpdyHeaders.setVersion(spdySynReplyFrame, HttpVersion.HTTP_1_0); + Channels.write(ctx, Channels.future(channel), spdySynReplyFrame); + } + } + + } else if (msg instanceof SpdySynReplyFrame) { + + SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; + int streamID = spdySynReplyFrame.getStreamID(); + + try { + HttpResponse httpResponse = createHttpResponse(spdySynReplyFrame); + + // Set the Stream-ID as a header + SpdyHttpHeaders.setStreamID(httpResponse, streamID); + + if (spdySynReplyFrame.isLast()) { + HttpHeaders.setContentLength(httpResponse, 0); + return httpResponse; + } else { + // Response body will follow in a series of Data Frames + messageMap.put(new Integer(streamID), httpResponse); + } + } catch (Exception e) { + // If a client receives a SYN_REPLY without valid status and version headers + // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR + SpdyRstStreamFrame spdyRstStreamFrame = + new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.PROTOCOL_ERROR); + Channels.write(ctx, Channels.future(channel), spdyRstStreamFrame); + } + + } else if (msg instanceof SpdyHeadersFrame) { + + SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; + Integer streamID = new Integer(spdyHeadersFrame.getStreamID()); + HttpMessage httpMessage = messageMap.get(streamID); + + // If message is not in map discard HEADERS frame. + // SpdySessionHandler should prevent this from happening. + if (httpMessage == null) { + return null; + } + + for (Map.Entry e: spdyHeadersFrame.getHeaders()) { + httpMessage.addHeader(e.getKey(), e.getValue()); + } + + } else if (msg instanceof SpdyDataFrame) { + + SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; + Integer streamID = new Integer(spdyDataFrame.getStreamID()); + HttpMessage httpMessage = messageMap.get(streamID); + + // If message is not in map discard Data Frame. + // SpdySessionHandler should prevent this from happening. + if (httpMessage == null) { + return null; + } + + ChannelBuffer content = httpMessage.getContent(); + if (content.readableBytes() > maxContentLength - spdyDataFrame.getData().readableBytes()) { + messageMap.remove(streamID); + throw new TooLongFrameException( + "HTTP content length exceeded " + maxContentLength + " bytes."); + } + + if (content == ChannelBuffers.EMPTY_BUFFER) { + content = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory()); + content.writeBytes(spdyDataFrame.getData()); + httpMessage.setContent(content); + } else { + content.writeBytes(spdyDataFrame.getData()); + } + + if (spdyDataFrame.isLast()) { + HttpHeaders.setContentLength(httpMessage, content.readableBytes()); + messageMap.remove(streamID); + return httpMessage; + } + } + + return null; + } + + private HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame) + throws Exception { + // Create the first line of the request from the name/value pairs + HttpMethod method = SpdyHeaders.getMethod(requestFrame); + String url = SpdyHeaders.getUrl(requestFrame); + HttpVersion version = SpdyHeaders.getVersion(requestFrame); + SpdyHeaders.removeMethod(requestFrame); + SpdyHeaders.removeUrl(requestFrame); + SpdyHeaders.removeVersion(requestFrame); + + HttpRequest httpRequest = new DefaultHttpRequest(version, method, url); + for (Map.Entry e: requestFrame.getHeaders()) { + httpRequest.addHeader(e.getKey(), e.getValue()); + } + + // Chunked encoding is no longer valid + List encodings = httpRequest.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); + encodings.remove(HttpHeaders.Values.CHUNKED); + if (encodings.isEmpty()) { + httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + } else { + httpRequest.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings); + } + + // The Connection and Keep-Alive headers are no longer valid + HttpHeaders.setKeepAlive(httpRequest, true); + + return httpRequest; + } + + private HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame) + throws Exception { + // Create the first line of the response from the name/value pairs + HttpResponseStatus status = SpdyHeaders.getStatus(responseFrame); + HttpVersion version = SpdyHeaders.getVersion(responseFrame); + SpdyHeaders.removeStatus(responseFrame); + SpdyHeaders.removeVersion(responseFrame); + + HttpResponse httpResponse = new DefaultHttpResponse(version, status); + for (Map.Entry e: responseFrame.getHeaders()) { + httpResponse.addHeader(e.getKey(), e.getValue()); + } + + // Chunked encoding is no longer valid + List encodings = httpResponse.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); + encodings.remove(HttpHeaders.Values.CHUNKED); + if (encodings.isEmpty()) { + httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + } else { + httpResponse.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, encodings); + } + httpResponse.removeHeader(HttpHeaders.Names.TRAILER); + + // The Connection and Keep-Alive headers are no longer valid + HttpHeaders.setKeepAlive(httpResponse, true); + + return httpResponse; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java new file mode 100644 index 0000000000..30ce7a6352 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java @@ -0,0 +1,325 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.netty.handler.codec.spdy; + +import java.util.List; +import java.util.Map; + +import io.netty.channel.ChannelDownstreamHandler; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.Channels; +import io.netty.channel.MessageEvent; +import io.netty.handler.codec.http.HttpChunk; +import io.netty.handler.codec.http.HttpChunkTrailer; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMessage; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + +/** + * Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpChunk}s + * into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s. + * + *

Request Annotations

+ * + * SPDY specific headers must be added to {@link HttpRequest}s: + * + * + * + * + * + * + * + * + * + * + * + * + *
Header NameHeader Value
{@code "X-SPDY-Stream-ID"}The Stream-ID for this request. + * Stream-IDs must be odd, positive integers, and must increase monotonically.
{@code "X-SPDY-Priority"}The priority value for this request. + * The priority should be between 0 and 3 inclusive. + * 0 represents the highest priority and 3 represents the lowest. + * This header is optional and defaults to 0.
+ * + *

Response Annotations

+ * + * SPDY specific headers must be added to {@link HttpResponse}s: + * + * + * + * + * + * + * + * + *
Header NameHeader Value
{@code "X-SPDY-Stream-ID"}The Stream-ID of the request corresponding to this response.
+ * + *

Pushed Resource Annotations

+ * + * SPDY specific headers must be added to pushed {@link HttpResponse}s: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Header NameHeader Value
{@code "X-SPDY-Stream-ID"}The Stream-ID for this resource. + * Stream-IDs must be even, positive integers, and must increase monotonically.
{@code "X-SPDY-Associated-To-Stream-ID"}The Stream-ID of the request that inititated this pushed resource.
{@code "X-SPDY-Priority"}The priority value for this resource. + * The priority should be between 0 and 3 inclusive. + * 0 represents the highest priority and 3 represents the lowest. + * This header is optional and defaults to 0.
{@code "X-SPDY-URL"}The full URL for the resource being pushed.
+ * + *

Chunked Content

+ * + * This encoder associates all {@link HttpChunk}s that it receives + * with the most recently received 'chunked' {@link HttpRequest} + * or {@link HttpResponse}. + * + *

Pushed Resources

+ * + * All pushed resources should be sent before sending the response + * that corresponds to the initial request. + */ +public class SpdyHttpEncoder implements ChannelDownstreamHandler { + + private volatile int currentStreamID; + + public SpdyHttpEncoder() { + } + + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt) + throws Exception { + if (!(evt instanceof MessageEvent)) { + ctx.sendDownstream(evt); + return; + } + + MessageEvent e = (MessageEvent) evt; + Object msg = e.getMessage(); + + if (msg instanceof HttpRequest) { + + HttpRequest httpRequest = (HttpRequest) msg; + SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest); + int streamID = spdySynStreamFrame.getStreamID(); + ChannelFuture future = getContentFuture(ctx, e, streamID, httpRequest); + Channels.write(ctx, future, spdySynStreamFrame, e.getRemoteAddress()); + + } else if (msg instanceof HttpResponse) { + + HttpResponse httpResponse = (HttpResponse) msg; + if (httpResponse.containsHeader(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) { + SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse); + int streamID = spdySynStreamFrame.getStreamID(); + ChannelFuture future = getContentFuture(ctx, e, streamID, httpResponse); + Channels.write(ctx, future, spdySynStreamFrame, e.getRemoteAddress()); + } else { + SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse); + int streamID = spdySynReplyFrame.getStreamID(); + ChannelFuture future = getContentFuture(ctx, e, streamID, httpResponse); + Channels.write(ctx, future, spdySynReplyFrame, e.getRemoteAddress()); + } + + } else if (msg instanceof HttpChunk) { + + HttpChunk chunk = (HttpChunk) msg; + SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamID); + spdyDataFrame.setData(chunk.getContent()); + spdyDataFrame.setLast(chunk.isLast()); + + if (chunk instanceof HttpChunkTrailer) { + HttpChunkTrailer trailer = (HttpChunkTrailer) chunk; + List> trailers = trailer.getHeaders(); + if (trailers.isEmpty()) { + Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress()); + } else { + // Create SPDY HEADERS frame out of trailers + SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamID); + for (Map.Entry entry: trailers) { + spdyHeadersFrame.addHeader(entry.getKey(), entry.getValue()); + } + + // Write HEADERS frame and append Data Frame + ChannelFuture future = Channels.future(e.getChannel()); + future.addListener(new SpdyFrameWriter(ctx, e, spdyDataFrame)); + Channels.write(ctx, future, spdyHeadersFrame, e.getRemoteAddress()); + } + } else { + Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress()); + } + } else { + // Unknown message type + ctx.sendDownstream(evt); + } + } + + private ChannelFuture getContentFuture( + ChannelHandlerContext ctx, MessageEvent e, int streamID, HttpMessage httpMessage) { + if (httpMessage.getContent().readableBytes() == 0) { + return e.getFuture(); + } + + // Create SPDY Data Frame out of message content + SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); + spdyDataFrame.setData(httpMessage.getContent()); + spdyDataFrame.setLast(true); + + // Create new future and add listener + ChannelFuture future = Channels.future(e.getChannel()); + future.addListener(new SpdyFrameWriter(ctx, e, spdyDataFrame)); + + return future; + } + + private class SpdyFrameWriter implements ChannelFutureListener { + + private final ChannelHandlerContext ctx; + private final MessageEvent e; + private final Object spdyFrame; + + SpdyFrameWriter(ChannelHandlerContext ctx, MessageEvent e, Object spdyFrame) { + this.ctx = ctx; + this.e = e; + this.spdyFrame = spdyFrame; + } + + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + Channels.write(ctx, e.getFuture(), spdyFrame, e.getRemoteAddress()); + } else if (future.isCancelled()) { + e.getFuture().cancel(); + } else { + e.getFuture().setFailure(future.getCause()); + } + } + } + + private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) + throws Exception { + boolean chunked = httpMessage.isChunked(); + + // Get the Stream-ID, Associated-To-Stream-ID, Priority, and URL from the headers + int streamID = SpdyHttpHeaders.getStreamID(httpMessage); + int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage); + byte priority = SpdyHttpHeaders.getPriority(httpMessage); + String URL = SpdyHttpHeaders.getUrl(httpMessage); + SpdyHttpHeaders.removeStreamID(httpMessage); + SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage); + SpdyHttpHeaders.removePriority(httpMessage); + SpdyHttpHeaders.removeUrl(httpMessage); + + // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding + // headers are not valid and MUST not be sent. + httpMessage.removeHeader(HttpHeaders.Names.CONNECTION); + httpMessage.removeHeader("Keep-Alive"); + httpMessage.removeHeader("Proxy-Connection"); + httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + + SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority); + for (Map.Entry entry: httpMessage.getHeaders()) { + spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue()); + } + + // Unfold the first line of the message into name/value pairs + SpdyHeaders.setVersion(spdySynStreamFrame, httpMessage.getProtocolVersion()); + if (httpMessage instanceof HttpRequest) { + HttpRequest httpRequest = (HttpRequest) httpMessage; + SpdyHeaders.setMethod(spdySynStreamFrame, httpRequest.getMethod()); + SpdyHeaders.setUrl(spdySynStreamFrame, httpRequest.getUri()); + } + if (httpMessage instanceof HttpResponse) { + HttpResponse httpResponse = (HttpResponse) httpMessage; + SpdyHeaders.setStatus(spdySynStreamFrame, httpResponse.getStatus()); + SpdyHeaders.setUrl(spdySynStreamFrame, URL); + spdySynStreamFrame.setUnidirectional(true); + } + + if (chunked) { + currentStreamID = streamID; + spdySynStreamFrame.setLast(false); + } else { + spdySynStreamFrame.setLast(httpMessage.getContent().readableBytes() == 0); + } + + return spdySynStreamFrame; + } + + private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) + throws Exception { + boolean chunked = httpResponse.isChunked(); + + // Get the Stream-ID from the headers + int streamID = SpdyHttpHeaders.getStreamID(httpResponse); + SpdyHttpHeaders.removeStreamID(httpResponse); + + // The Connection, Keep-Alive, Proxy-Connection, and Transfer-ENcoding + // headers are not valid and MUST not be sent. + httpResponse.removeHeader(HttpHeaders.Names.CONNECTION); + httpResponse.removeHeader("Keep-Alive"); + httpResponse.removeHeader("Proxy-Connection"); + httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + + SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); + for (Map.Entry entry: httpResponse.getHeaders()) { + spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue()); + } + + // Unfold the first line of the repsonse into name/value pairs + SpdyHeaders.setStatus(spdySynReplyFrame, httpResponse.getStatus()); + SpdyHeaders.setVersion(spdySynReplyFrame, httpResponse.getProtocolVersion()); + + if (chunked) { + currentStreamID = streamID; + spdySynReplyFrame.setLast(false); + } else { + spdySynReplyFrame.setLast(httpResponse.getContent().readableBytes() == 0); + } + + return spdySynReplyFrame; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java new file mode 100644 index 0000000000..6b13986af4 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpHeaders.java @@ -0,0 +1,162 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.netty.handler.codec.spdy; + +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMessage; + +/** + * Provides the constants for the header names and the utility methods + * used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}. + * @apiviz.sterotype static + */ +public final class SpdyHttpHeaders { + + /** + * SPDY HTTP header names + * @apiviz.sterotype static + */ + public static final class Names { + /** + * {@code "X-SPDY-Stream-ID"} + */ + public static final String STREAM_ID = "X-SPDY-Stream-ID"; + /** + * {@code "X-SPDY-Associated-To-Stream-ID"} + */ + public static final String ASSOCIATED_TO_STREAM_ID = "X-SPDY-Associated-To-Stream-ID"; + /** + * {@code "X-SPDY-Priority"} + */ + public static final String PRIORITY = "X-SPDY-Priority"; + /** + * {@code "X-SPDY-URL"} + */ + public static final String URL = "X-SPDY-URL"; + + private Names() { + super(); + } + } + + private SpdyHttpHeaders() { + } + + /** + * Removes the {@code "X-SPDY-Stream-ID"} header. + */ + public static void removeStreamID(HttpMessage message) { + message.removeHeader(Names.STREAM_ID); + } + + /** + * Returns the value of the {@code "X-SPDY-Stream-ID"} header. + */ + public static int getStreamID(HttpMessage message) { + return HttpHeaders.getIntHeader(message, Names.STREAM_ID); + } + + /** + * Sets the {@code "X-SPDY-Stream-ID"} header. + */ + public static void setStreamID(HttpMessage message, int streamID) { + HttpHeaders.setIntHeader(message, Names.STREAM_ID, streamID); + } + + /** + * Removes the {@code "X-SPDY-Associated-To-Stream-ID"} header. + */ + public static void removeAssociatedToStreamID(HttpMessage message) { + message.removeHeader(Names.ASSOCIATED_TO_STREAM_ID); + } + + /** + * Returns the value of the {@code "X-SPDY-Associated-To-Stream-ID"} header. + * + * @return the header value or {@code 0} if there is no such header or + * if the header value is not a number + */ + public static int getAssociatedToStreamID(HttpMessage message) { + return HttpHeaders.getIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, 0); + } + + /** + * Sets the {@code "X-SPDY-Associated-To-Stream-ID"} header. + */ + public static void setAssociatedToStreamID(HttpMessage message, int associatedToStreamID) { + HttpHeaders.setIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamID); + } + + /** + * Removes the {@code "X-SPDY-Priority"} header. + */ + public static void removePriority(HttpMessage message) { + message.removeHeader(Names.PRIORITY); + } + + /** + * Returns the value of the {@code "X-SPDY-Priority"} header. + * + * @return the header value or {@code 0} if there is no such header or + * if the header value is not a number + */ + public static byte getPriority(HttpMessage message) { + return (byte) HttpHeaders.getIntHeader(message, Names.PRIORITY, 0); + } + + /** + * Sets the {@code "X-SPDY-Priority"} header. + */ + public static void setPriority(HttpMessage message, byte priority) { + HttpHeaders.setIntHeader(message, Names.PRIORITY, priority); + } + + /** + * Removes the {@code "X-SPDY-URL"} header. + */ + public static void removeUrl(HttpMessage message) { + message.removeHeader(Names.URL); + } + + /** + * Returns the value of the {@code "X-SPDY-URL"} header. + */ + public static String getUrl(HttpMessage message) { + return message.getHeader(Names.URL); + } + + /** + * Sets the {@code "X-SPDY-URL"} header. + */ + public static void setUrl(HttpMessage message, String url) { + message.setHeader(Names.URL, url); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java index 70c32c963d..82e54afe6a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java @@ -393,12 +393,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler * Helper functions */ - private boolean isServerID(int ID) { - return ID % 2 == 0; - } - private boolean isRemoteInitiatedID(int ID) { - boolean serverID = isServerID(ID); + boolean serverID = SpdyCodecUtil.isServerID(ID); return (server && !serverID) || (!server && serverID); } From 46363095970e1bd8f6046414fcf15b41a9c25452 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 12 Feb 2012 12:36:04 +0100 Subject: [PATCH 2/5] Fix HttpSnoopClient. See #183 --- .../main/java/io/netty/example/http/snoop/HttpSnoopClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java index 436da5b6d8..a69feece17 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java @@ -83,7 +83,7 @@ public class HttpSnoopClient { // Prepare the HTTP request. HttpRequest request = new DefaultHttpRequest( - HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString()); + HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath()); request.setHeader(HttpHeaders.Names.HOST, host); request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP); From c46b083c1f0eb0f5e42baeb98f2cfc308c1bfb2a Mon Sep 17 00:00:00 2001 From: norman Date: Wed, 15 Feb 2012 08:30:22 +0100 Subject: [PATCH 3/5] Correctly decode URI in QueryStringDecoder. See #189 --- .../codec/http/QueryStringDecoder.java | 14 ++- .../codec/http/QueryStringDecoderTest.java | 90 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index d78198f273..62f210a112 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -154,12 +154,22 @@ public class QueryStringDecoder { throw new IllegalArgumentException( "maxParams: " + maxParams + " (expected: a positive integer)"); } + + String rawPath = uri.getRawPath(); + if (rawPath != null) { + hasPath = true; + } else { + rawPath =""; + hasPath = false; + } + // Also take care of cut of things like "http://localhost" + String newUri = rawPath + "?" + uri.getRawQuery(); // http://en.wikipedia.org/wiki/Query_string - this.uri = uri.toASCIIString().replace(';', '&'); + this.uri = newUri.replace(';', '&'); this.charset = charset; this.maxParams = maxParams; - hasPath = false; + } /** diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java index 14f93c73c0..78bc16227f 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java @@ -15,8 +15,11 @@ */ package io.netty.handler.codec.http; +import java.net.URI; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import io.netty.util.CharsetUtil; import org.junit.Assert; @@ -165,4 +168,91 @@ public class QueryStringDecoderTest { Assert.assertEquals(ed.getPath(), ad.getPath()); Assert.assertEquals(ed.getParameters(), ad.getParameters()); } + + // See #189 + @Test + public void testURI() { + URI uri = URI.create("http://localhost:8080/foo?param1=value1¶m2=value2¶m3=value3"); + QueryStringDecoder decoder = new QueryStringDecoder(uri); + Assert.assertEquals("/foo", decoder.getPath()); + Map> params = decoder.getParameters(); + Assert.assertEquals(3, params.size()); + Iterator>> entries = params.entrySet().iterator(); + + Entry> entry = entries.next(); + Assert.assertEquals("param1", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value1", entry.getValue().get(0)); + + + entry = entries.next(); + Assert.assertEquals("param2", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value2", entry.getValue().get(0)); + + entry = entries.next(); + Assert.assertEquals("param3", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value3", entry.getValue().get(0)); + + Assert.assertFalse(entries.hasNext()); + } + + // See #189 + @Test + public void testURISlashPath() { + URI uri = URI.create("http://localhost:8080/?param1=value1¶m2=value2¶m3=value3"); + QueryStringDecoder decoder = new QueryStringDecoder(uri); + Assert.assertEquals("/", decoder.getPath()); + Map> params = decoder.getParameters(); + Assert.assertEquals(3, params.size()); + Iterator>> entries = params.entrySet().iterator(); + + Entry> entry = entries.next(); + Assert.assertEquals("param1", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value1", entry.getValue().get(0)); + + + entry = entries.next(); + Assert.assertEquals("param2", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value2", entry.getValue().get(0)); + + entry = entries.next(); + Assert.assertEquals("param3", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value3", entry.getValue().get(0)); + + Assert.assertFalse(entries.hasNext()); + } + + // See #189 + @Test + public void testURINoPath() { + URI uri = URI.create("http://localhost:8080?param1=value1¶m2=value2¶m3=value3"); + QueryStringDecoder decoder = new QueryStringDecoder(uri); + Assert.assertEquals("", decoder.getPath()); + Map> params = decoder.getParameters(); + Assert.assertEquals(3, params.size()); + Iterator>> entries = params.entrySet().iterator(); + + Entry> entry = entries.next(); + Assert.assertEquals("param1", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value1", entry.getValue().get(0)); + + + entry = entries.next(); + Assert.assertEquals("param2", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value2", entry.getValue().get(0)); + + entry = entries.next(); + Assert.assertEquals("param3", entry.getKey()); + Assert.assertEquals(1, entry.getValue().size()); + Assert.assertEquals("value3", entry.getValue().get(0)); + + Assert.assertFalse(entries.hasNext()); + } } From 1b099acde0231c6e2ae163f9c119e1b2f143283a Mon Sep 17 00:00:00 2001 From: norman Date: Thu, 16 Feb 2012 15:40:32 +0100 Subject: [PATCH 4/5] Introduce a new interface that specify methods for ChannelConfig that are used in the scope of NIO. This allows to share some code and make it easier later to cast. See #186 --- .../channel/socket/nio/NioChannelConfig.java | 82 +++++++++++++++++++ .../socket/nio/NioDatagramChannelConfig.java | 57 +------------ .../socket/nio/NioSocketChannelConfig.java | 58 +------------ 3 files changed, 84 insertions(+), 113 deletions(-) create mode 100644 transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java new file mode 100644 index 0000000000..65dd184993 --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.socket.nio; + +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; + +/** + * Special {@link ChannelConfig} sub-type which offers extra methods which are useful for NIO. + * + */ +public interface NioChannelConfig extends ChannelConfig{ + + /** + * Returns the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code true}. + */ + int getWriteBufferHighWaterMark(); + + /** + * Sets the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code true}. + */ + void setWriteBufferHighWaterMark(int writeBufferHighWaterMark); + + /** + * Returns the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will return + * {@code false} again. + */ + int getWriteBufferLowWaterMark(); + + /** + * Sets the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will return + * {@code false} again. + */ + void setWriteBufferLowWaterMark(int writeBufferLowWaterMark); + + /** + * Returns the maximum loop count for a write operation until + * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. + * It is similar to what a spin lock is used for in concurrency programming. + * It improves memory utilization and write throughput depending on + * the platform that JVM runs on. The default value is {@code 16}. + */ + int getWriteSpinCount(); + + /** + * Sets the maximum loop count for a write operation until + * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. + * It is similar to what a spin lock is used for in concurrency programming. + * It improves memory utilization and write throughput depending on + * the platform that JVM runs on. The default value is {@code 16}. + * + * @throws IllegalArgumentException + * if the specified value is {@code 0} or less than {@code 0} + */ + void setWriteSpinCount(int writeSpinCount); +} diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java index 153971a208..486ba68815 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java @@ -15,10 +15,6 @@ */ package io.netty.channel.socket.nio; -import java.nio.ByteBuffer; -import java.nio.channels.WritableByteChannel; - -import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannelConfig; @@ -44,58 +40,7 @@ import io.netty.channel.socket.DatagramChannelConfig; * * */ -public interface NioDatagramChannelConfig extends DatagramChannelConfig { +public interface NioDatagramChannelConfig extends DatagramChannelConfig, NioChannelConfig { - /** - * Returns the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code true}. - */ - int getWriteBufferHighWaterMark(); - /** - * Sets the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code true}. - */ - void setWriteBufferHighWaterMark(int writeBufferHighWaterMark); - - /** - * Returns the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code false} again. - */ - int getWriteBufferLowWaterMark(); - - /** - * Sets the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code false} again. - */ - void setWriteBufferLowWaterMark(int writeBufferLowWaterMark); - - /** - * Returns the maximum loop count for a write operation until - * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. - * It is similar to what a spin lock is used for in concurrency programming. - * It improves memory utilization and write throughput depending on - * the platform that JVM runs on. The default value is {@code 16}. - */ - int getWriteSpinCount(); - - /** - * Sets the maximum loop count for a write operation until - * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. - * It is similar to what a spin lock is used for in concurrency programming. - * It improves memory utilization and write throughput depending on - * the platform that JVM runs on. The default value is {@code 16}. - * - * @throws IllegalArgumentException - * if the specified value is {@code 0} or less than {@code 0} - */ - void setWriteSpinCount(int writeSpinCount); } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannelConfig.java index e949db6293..bb0fd64e62 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannelConfig.java @@ -15,12 +15,8 @@ */ package io.netty.channel.socket.nio; -import java.nio.ByteBuffer; -import java.nio.channels.WritableByteChannel; - import io.netty.channel.AdaptiveReceiveBufferSizePredictor; import io.netty.channel.AdaptiveReceiveBufferSizePredictorFactory; -import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.ReceiveBufferSizePredictor; import io.netty.channel.ReceiveBufferSizePredictorFactory; @@ -52,60 +48,8 @@ import io.netty.channel.socket.SocketChannelConfig; * * */ -public interface NioSocketChannelConfig extends SocketChannelConfig { +public interface NioSocketChannelConfig extends SocketChannelConfig, NioChannelConfig { - /** - * Returns the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code false}. - */ - int getWriteBufferHighWaterMark(); - - /** - * Sets the high water mark of the write buffer. If the number of bytes - * queued in the write buffer exceeds this value, {@link Channel#isWritable()} - * will start to return {@code false}. - */ - void setWriteBufferHighWaterMark(int writeBufferHighWaterMark); - - /** - * Returns the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code true} again. - */ - int getWriteBufferLowWaterMark(); - - /** - * Sets the low water mark of the write buffer. Once the number of bytes - * queued in the write buffer exceeded the - * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then - * dropped down below this value, {@link Channel#isWritable()} will return - * {@code true} again. - */ - void setWriteBufferLowWaterMark(int writeBufferLowWaterMark); - - /** - * Returns the maximum loop count for a write operation until - * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. - * It is similar to what a spin lock is used for in concurrency programming. - * It improves memory utilization and write throughput depending on - * the platform that JVM runs on. The default value is {@code 16}. - */ - int getWriteSpinCount(); - - /** - * Sets the maximum loop count for a write operation until - * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. - * It is similar to what a spin lock is used for in concurrency programming. - * It improves memory utilization and write throughput depending on - * the platform that JVM runs on. The default value is {@code 16}. - * - * @throws IllegalArgumentException - * if the specified value is {@code 0} or less than {@code 0} - */ - void setWriteSpinCount(int writeSpinCount); /** * Returns the {@link ReceiveBufferSizePredictor} which predicts the From 479def20bdbba61f8ae0d2fb6c46e935761932da Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 17 Feb 2012 10:37:41 +0100 Subject: [PATCH 5/5] Check if logging level is enabled before log. See #192 --- .../java/io/netty/util/HashedWheelTimer.java | 9 ++- .../SharedResourceMisuseDetector.java | 14 +++-- .../autobahn/AutobahnServerHandler.java | 7 ++- .../server/WebSocketServerHandler.java | 4 +- .../sslserver/WebSocketSslServerHandler.java | 4 +- .../WebSocketSslServerSslContext.java | 4 +- .../java/io/netty/handler/ipfilter/CIDR6.java | 4 +- .../handler/ipfilter/IpFilterRuleList.java | 12 +++- .../netty/handler/ipfilter/PatternRule.java | 8 ++- .../java/io/netty/handler/ssl/SslHandler.java | 40 ++++++++----- .../handler/stream/ChunkedWriteHandler.java | 8 ++- .../socket/AbstractSocketSslEchoTest.java | 12 ++-- .../sctp/DefaultNioSctpChannelConfig.java | 12 ++-- .../netty/channel/sctp/SctpClientChannel.java | 8 ++- .../channel/sctp/SctpClientPipelineSink.java | 12 ++-- .../channel/sctp/SctpNotificationHandler.java | 5 -- .../channel/sctp/SctpProviderMetadata.java | 56 +++++++++++++------ .../channel/sctp/SctpServerChannelImpl.java | 6 +- .../channel/sctp/SctpServerPipelineSink.java | 24 +++++--- .../io/netty/channel/sctp/SctpWorker.java | 21 ++++--- .../io/netty/channel/sctp/SelectorUtil.java | 11 ++-- .../netty/channel/CompleteChannelFuture.java | 8 ++- .../netty/channel/DefaultChannelFuture.java | 26 +++++---- .../netty/channel/DefaultChannelPipeline.java | 25 ++++++--- .../io/netty/channel/DefaultFileRegion.java | 4 +- .../group/DefaultChannelGroupFuture.java | 8 ++- .../channel/local/LocalClientChannelSink.java | 6 +- .../nio/DefaultNioDatagramChannelConfig.java | 11 ++-- .../nio/DefaultNioSocketChannelConfig.java | 13 +++-- .../socket/nio/NioClientSocketChannel.java | 9 ++- .../nio/NioClientSocketPipelineSink.java | 18 ++++-- .../channel/socket/nio/NioDatagramWorker.java | 4 +- .../socket/nio/NioProviderMetadata.java | 51 +++++++++++------ .../socket/nio/NioServerSocketChannel.java | 7 ++- .../nio/NioServerSocketPipelineSink.java | 24 +++++--- .../netty/channel/socket/nio/NioWorker.java | 17 ++++-- .../channel/socket/nio/SelectorUtil.java | 9 ++- .../socket/oio/OioServerSocketChannel.java | 7 ++- .../oio/OioServerSocketPipelineSink.java | 21 ++++--- 39 files changed, 366 insertions(+), 183 deletions(-) diff --git a/common/src/main/java/io/netty/util/HashedWheelTimer.java b/common/src/main/java/io/netty/util/HashedWheelTimer.java index c8f200c2ec..701f01623e 100644 --- a/common/src/main/java/io/netty/util/HashedWheelTimer.java +++ b/common/src/main/java/io/netty/util/HashedWheelTimer.java @@ -518,9 +518,12 @@ public class HashedWheelTimer implements Timer { try { task.run(this); } catch (Throwable t) { - logger.warn( - "An exception was thrown by " + - TimerTask.class.getSimpleName() + ".", t); + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by " + + TimerTask.class.getSimpleName() + ".", t); + } + } } diff --git a/common/src/main/java/io/netty/util/internal/SharedResourceMisuseDetector.java b/common/src/main/java/io/netty/util/internal/SharedResourceMisuseDetector.java index 7daf3a1e77..f15e38fb0d 100644 --- a/common/src/main/java/io/netty/util/internal/SharedResourceMisuseDetector.java +++ b/common/src/main/java/io/netty/util/internal/SharedResourceMisuseDetector.java @@ -43,12 +43,14 @@ public class SharedResourceMisuseDetector { public void increase() { if (activeInstances.incrementAndGet() > MAX_ACTIVE_INSTANCES) { - if (logged.compareAndSet(false, true)) { - logger.warn( - "You are creating too many " + type.getSimpleName() + - " instances. " + type.getSimpleName() + - " is a shared resource that must be reused across the" + - " application, so that only a few instances are created."); + if (logger.isWarnEnabled()) { + if (logged.compareAndSet(false, true)) { + logger.warn( + "You are creating too many " + type.getSimpleName() + + " instances. " + type.getSimpleName() + + " is a shared resource that must be reused across the" + + " application, so that only a few instances are created."); + } } } } diff --git a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java index 83d54025bc..0729d0b5ad 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServerHandler.java @@ -81,8 +81,11 @@ public class AutobahnServerHandler extends SimpleChannelUpstreamHandler { } private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { - logger.debug(String - .format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); + if (logger.isDebugEnabled()) { + logger.debug(String + .format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); + } + if (frame instanceof CloseWebSocketFrame) { this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame); diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java index 64fdcc6000..f07a1d0d8e 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java @@ -116,7 +116,9 @@ public class WebSocketServerHandler extends SimpleChannelUpstreamHandler { // Send the uppercase string back. String request = ((TextWebSocketFrame) frame).getText(); - logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); + } ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase())); } diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java index c2a6bf6289..5218846ffe 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java @@ -116,7 +116,9 @@ public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler { // Send the uppercase string back. String request = ((TextWebSocketFrame) frame).getText(); - logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request)); + } ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase())); } diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java index b21cd26502..77df475075 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java @@ -86,7 +86,9 @@ public final class WebSocketSslServerSslContext { } _serverContext = serverContext; } catch (Exception ex) { - logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex); + if (logger.isErrorEnabled()) { + logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex); + } System.exit(1); } diff --git a/handler/src/main/java/io/netty/handler/ipfilter/CIDR6.java b/handler/src/main/java/io/netty/handler/ipfilter/CIDR6.java index 2ee9c7bd29..fe2e81ea3f 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/CIDR6.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/CIDR6.java @@ -58,7 +58,9 @@ public class CIDR6 extends CIDR { try { return bigIntToIPv6Address(addressEndBigInt); } catch (UnknownHostException e) { - logger.error("invalid ip address calculated as an end address"); + if (logger.isErrorEnabled()) { + logger.error("invalid ip address calculated as an end address"); + } return null; } } diff --git a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleList.java b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleList.java index 020c35aeae..4d0141f489 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleList.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/IpFilterRuleList.java @@ -69,7 +69,9 @@ public class IpFilterRuleList extends ArrayList { return; } if (!(rule.startsWith("+") || rule.startsWith("-"))) { - logger.error("syntax error in ip filter rule:" + rule); + if (logger.isErrorEnabled()) { + logger.error("syntax error in ip filter rule:" + rule); + } return; } @@ -80,10 +82,14 @@ public class IpFilterRuleList extends ArrayList { try { this.add(new IpSubnetFilterRule(allow, rule.substring(3))); } catch (UnknownHostException e) { - logger.error("error parsing ip filter " + rule, e); + if (logger.isErrorEnabled()) { + logger.error("error parsing ip filter " + rule, e); + } } } else { - logger.error("syntax error in ip filter rule:" + rule); + if (logger.isErrorEnabled()) { + logger.error("syntax error in ip filter rule:" + rule); + } } } } diff --git a/handler/src/main/java/io/netty/handler/ipfilter/PatternRule.java b/handler/src/main/java/io/netty/handler/ipfilter/PatternRule.java index 04d6be559b..9f04e463be 100644 --- a/handler/src/main/java/io/netty/handler/ipfilter/PatternRule.java +++ b/handler/src/main/java/io/netty/handler/ipfilter/PatternRule.java @@ -158,7 +158,9 @@ public class PatternRule implements IpFilterRule, Comparable { return true; } } catch (UnknownHostException e) { - logger.info("error getting ip of localhost", e); + if (logger.isInfoEnabled()) { + logger.info("error getting ip of localhost", e); + } } try { InetAddress[] addrs = InetAddress.getAllByName("127.0.0.1"); @@ -168,7 +170,9 @@ public class PatternRule implements IpFilterRule, Comparable { } } } catch (UnknownHostException e) { - logger.info("error getting ip of localhost", e); + if (logger.isInfoEnabled()) { + logger.info("error getting ip of localhost", e); + } } return false; diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 059d524c0d..0b37456911 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -497,7 +497,9 @@ public class SslHandler extends FrameDecoder try { engine.closeInbound(); } catch (SSLException ex) { - logger.debug("Failed to clean up SSLEngine.", ex); + if (logger.isDebugEnabled()) { + logger.debug("Failed to clean up SSLEngine.", ex); + } } } } @@ -513,9 +515,12 @@ public class SslHandler extends FrameDecoder synchronized (ignoreClosedChannelExceptionLock) { if (ignoreClosedChannelException > 0) { ignoreClosedChannelException --; - logger.debug( - "Swallowing an exception raised while " + - "writing non-app data", cause); + if (logger.isDebugEnabled()) { + logger.debug( + "Swallowing an exception raised while " + + "writing non-app data", cause); + } + return; } } @@ -524,10 +529,12 @@ public class SslHandler extends FrameDecoder if (IGNORABLE_ERROR_MESSAGE.matcher(message).matches()) { // It is safe to ignore the 'connection reset by peer' or // 'broken pipe' error after sending closure_notify. - logger.debug( - "Swallowing a 'connection reset by peer / " + - "broken pipe' error occurred while writing " + - "'closure_notify'", cause); + if (logger.isDebugEnabled()) { + logger.debug( + "Swallowing a 'connection reset by peer / " + + "broken pipe' error occurred while writing " + + "'closure_notify'", cause); + } // Close the connection explicitly just in case the transport // did not close the connection automatically. @@ -1085,9 +1092,12 @@ public class SslHandler extends FrameDecoder try { engine.closeInbound(); } catch (SSLException e) { - logger.debug( - "SSLEngine.closeInbound() raised an exception after " + - "a handshake failure.", e); + if (logger.isDebugEnabled()) { + logger.debug( + "SSLEngine.closeInbound() raised an exception after " + + "a handshake failure.", e); + } + } } @@ -1106,7 +1116,9 @@ public class SslHandler extends FrameDecoder try { unwrap(context, e.getChannel(), ChannelBuffers.EMPTY_BUFFER, 0, 0); } catch (SSLException ex) { - logger.debug("Failed to unwrap before sending a close_notify message", ex); + if (logger.isDebugEnabled()) { + logger.debug("Failed to unwrap before sending a close_notify message", ex); + } } if (!engine.isInboundDone()) { @@ -1118,7 +1130,9 @@ public class SslHandler extends FrameDecoder new ClosingChannelFutureListener(context, e)); success = true; } catch (SSLException ex) { - logger.debug("Failed to encode a close_notify message", ex); + if (logger.isDebugEnabled()) { + logger.debug("Failed to encode a close_notify message", ex); + } } } } else { diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index a071a618db..c5387a33e9 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -92,7 +92,9 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns try { flush(ctx); } catch (Exception e) { - logger.warn("Unexpected exception while sending chunks.", e); + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception while sending chunks.", e); + } } } @@ -270,7 +272,9 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns try { chunks.close(); } catch (Throwable t) { - logger.warn("Failed to close a chunked input.", t); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a chunked input.", t); + } } } } diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java index ef9b6819fe..a9906d3917 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSocketSslEchoTest.java @@ -129,7 +129,9 @@ public abstract class AbstractSocketSslEchoTest { ChannelFuture ccf = cb.connect(new InetSocketAddress(SocketAddresses.LOCALHOST, port)); ccf.awaitUninterruptibly(); if (!ccf.isSuccess()) { - logger.error("Connection attempt failed", ccf.getCause()); + if(logger.isErrorEnabled()) { + logger.error("Connection attempt failed", ccf.getCause()); + } sc.close().awaitUninterruptibly(); } assertTrue(ccf.isSuccess()); @@ -238,9 +240,11 @@ public abstract class AbstractSocketSslEchoTest { @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - logger.warn( - "Unexpected exception from the " + - (server? "server" : "client") + " side", e.getCause()); + if (logger.isWarnEnabled()) { + logger.warn( + "Unexpected exception from the " + + (server? "server" : "client") + " side", e.getCause()); + } exception.compareAndSet(null, e.getCause()); e.getChannel().close(); diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultNioSctpChannelConfig.java b/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultNioSctpChannelConfig.java index 7fc830413f..0598804db8 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultNioSctpChannelConfig.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultNioSctpChannelConfig.java @@ -53,11 +53,13 @@ class DefaultNioSctpChannelConfig extends DefaultSctpChannelConfig implements Ni if (getWriteBufferHighWaterMark() < getWriteBufferLowWaterMark()) { // Recover the integrity of the configuration with a sensible value. setWriteBufferLowWaterMark0(getWriteBufferHighWaterMark() >>> 1); - // Notify the user about misconfiguration. - logger.warn( - "writeBufferLowWaterMark cannot be greater than " + - "writeBufferHighWaterMark; setting to the half of the " + - "writeBufferHighWaterMark."); + if (logger.isWarnEnabled()) { + // Notify the user about misconfiguration. + logger.warn( + "writeBufferLowWaterMark cannot be greater than " + + "writeBufferHighWaterMark; setting to the half of the " + + "writeBufferHighWaterMark."); + } } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java index e875e67bb3..37b03eef73 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java @@ -55,9 +55,11 @@ final class SctpClientChannel extends SctpChannelImpl { try { underlayingChannel.close(); } catch (IOException e) { - logger.warn( - "Failed to close a partially initialized socket.", - e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", + e); + } } } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java index cdd483beb2..a571f4e263 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java @@ -289,8 +289,10 @@ class SctpClientPipelineSink extends AbstractChannelSink { try { selector.close(); } catch (IOException e) { - logger.warn( - "Failed to close a selector.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a selector.", e); + } } finally { this.selector = null; } @@ -307,8 +309,10 @@ class SctpClientPipelineSink extends AbstractChannelSink { shutdown = false; } } catch (Throwable t) { - logger.warn( - "Unexpected exception in the selector loop.", t); + if (logger.isWarnEnabled()) { + logger.warn( + "Unexpected exception in the selector loop.", t); + } // Prevent possible consecutive immediate failures. try { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java index 0fe6ab4094..aa2ecac47f 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java @@ -24,17 +24,12 @@ import com.sun.nio.sctp.SendFailedNotification; import com.sun.nio.sctp.ShutdownNotification; import io.netty.channel.Channels; -import io.netty.logging.InternalLogger; -import io.netty.logging.InternalLoggerFactory; /** */ class SctpNotificationHandler extends AbstractNotificationHandler { - private static final InternalLogger logger = - InternalLoggerFactory.getInstance(SctpNotificationHandler.class); - private final SctpChannelImpl sctpChannel; private final SctpWorker sctpWorker; diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpProviderMetadata.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpProviderMetadata.java index 51902620c8..cafe5c30b4 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpProviderMetadata.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpProviderMetadata.java @@ -61,8 +61,10 @@ final class SctpProviderMetadata { } if (constraintLevel >= 0) { - logger.debug( - "Setting the NIO constraint level to: " + constraintLevel); + if (logger.isDebugEnabled()) { + logger.debug( + "Setting the NIO constraint level to: " + constraintLevel); + } } if (constraintLevel < 0) { @@ -70,18 +72,24 @@ final class SctpProviderMetadata { if (constraintLevel < 0) { constraintLevel = 2; - logger.debug( - "Couldn't determine the NIO constraint level from " + - "the system properties; using the safest level (2)"); + if (logger.isDebugEnabled()) { + logger.debug( + "Couldn't determine the NIO constraint level from " + + "the system properties; using the safest level (2)"); + } } else if (constraintLevel != 0) { - logger.info( - "Using the autodetected NIO constraint level: " + - constraintLevel + - " (Use better NIO provider for better performance)"); + if (logger.isInfoEnabled()) { + logger.info( + "Using the autodetected NIO constraint level: " + + constraintLevel + + " (Use better NIO provider for better performance)"); + } } else { - logger.debug( - "Using the autodetected NIO constraint level: " + - constraintLevel); + if (logger.isDebugEnabled()) { + logger.debug( + "Using the autodetected NIO constraint level: " + + constraintLevel); + } } } @@ -236,7 +244,9 @@ final class SctpProviderMetadata { ch.bind(new InetSocketAddress(0)); ch.configureBlocking(false); } catch (Throwable e) { - logger.warn("Failed to configure a temporary socket.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to configure a temporary socket.", e); + } return -1; } @@ -244,7 +254,9 @@ final class SctpProviderMetadata { try { loop = new SelectorLoop(); } catch (Throwable e) { - logger.warn("Failed to open a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to open a temporary selector.", e); + } return -1; } @@ -252,7 +264,9 @@ final class SctpProviderMetadata { try { ch.register(loop.selector, 0); } catch (Throwable e) { - logger.warn("Failed to register a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to register a temporary selector.", e); + } return -1; } @@ -338,7 +352,9 @@ final class SctpProviderMetadata { try { ch.close(); } catch (Throwable e) { - logger.warn("Failed to close a temporary socket.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a temporary socket.", e); + } } } @@ -368,7 +384,9 @@ final class SctpProviderMetadata { try { loop.selector.close(); } catch (Throwable e) { - logger.warn("Failed to close a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a temporary selector.", e); + } } } } @@ -406,7 +424,9 @@ final class SctpProviderMetadata { } keys.clear(); } catch (IOException e) { - logger.warn("Failed to wait for a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to wait for a temporary selector.", e); + } } } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java index 335be780bb..4739e49019 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java @@ -71,8 +71,10 @@ class SctpServerChannelImpl extends AbstractServerChannel try { serverChannel.close(); } catch (IOException e2) { - logger.warn( - "Failed to close a partially initialized socket.", e2); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", e2); + } } throw new ChannelException("Failed to enter non-blocking mode.", e); diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java index d39ca7b688..9d21e54633 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java @@ -246,8 +246,10 @@ class SctpServerPipelineSink extends AbstractChannelSink { // Closed as requested. break; } catch (Throwable e) { - logger.warn( - "Failed to accept a connection.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to accept a connection.", e); + } try { Thread.sleep(1000); } catch (InterruptedException e1) { @@ -271,14 +273,18 @@ class SctpServerPipelineSink extends AbstractChannelSink { SctpServerPipelineSink.this, acceptedSocket, worker, currentThread), null); } catch (Exception e) { - logger.warn( - "Failed to initialize an accepted socket.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to initialize an accepted socket.", e); + } try { acceptedSocket.close(); } catch (IOException e2) { - logger.warn( - "Failed to close a partially accepted socket.", - e2); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially accepted socket.", + e2); + } } } } @@ -288,7 +294,9 @@ class SctpServerPipelineSink extends AbstractChannelSink { try { selector.close(); } catch (Exception e) { - logger.warn("Failed to close a selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a selector.", e); + } } } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java index e18d6b85df..2c9d51980f 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java @@ -110,7 +110,9 @@ class SctpWorker implements Runnable { try { selector.close(); } catch (Throwable t) { - logger.warn("Failed to close a selector.", t); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a selector.", t); + } } this.selector = selector = null; // The method will return to the caller at this point. @@ -204,8 +206,10 @@ class SctpWorker implements Runnable { try { selector.close(); } catch (IOException e) { - logger.warn( - "Failed to close a selector.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a selector.", e); + } } finally { this.selector = null; } @@ -222,9 +226,10 @@ class SctpWorker implements Runnable { shutdown = false; } } catch (Throwable t) { - logger.warn( - "Unexpected exception in the selector loop.", t); - + if (logger.isWarnEnabled()) { + logger.warn( + "Unexpected exception in the selector loop.", t); + } // Prevent possible consecutive immediate failures that lead to // excessive CPU consumption. try { @@ -313,7 +318,9 @@ class SctpWorker implements Runnable { if (messageInfo.isComplete()) { failure = false; } else { - logger.error("Received incomplete sctp packet, can not continue! Expected SCTP_EXPLICIT_COMPLETE message"); + if (logger.isErrorEnabled()) { + logger.error("Received incomplete sctp packet, can not continue! Expected SCTP_EXPLICIT_COMPLETE message"); + } failure = true; } } else { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SelectorUtil.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SelectorUtil.java index 500b5473e1..77c4035cb5 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SelectorUtil.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SelectorUtil.java @@ -32,10 +32,13 @@ final class SelectorUtil { try { selector.select(10); // does small timeout give more throughput + less CPU usage? } catch (CancelledKeyException e) { - // Harmless exception - log anyway - logger.debug( - CancelledKeyException.class.getSimpleName() + - " raised by a Selector - JDK bug?", e); + if (logger.isDebugEnabled()) { + // Harmless exception - log anyway + logger.debug( + CancelledKeyException.class.getSimpleName() + + " raised by a Selector - JDK bug?", e); + } + } } diff --git a/transport/src/main/java/io/netty/channel/CompleteChannelFuture.java b/transport/src/main/java/io/netty/channel/CompleteChannelFuture.java index 59971fb768..7ca47cdd1b 100644 --- a/transport/src/main/java/io/netty/channel/CompleteChannelFuture.java +++ b/transport/src/main/java/io/netty/channel/CompleteChannelFuture.java @@ -48,9 +48,11 @@ public abstract class CompleteChannelFuture implements ChannelFuture { try { listener.operationComplete(this); } catch (Throwable t) { - logger.warn( - "An exception was thrown by " + - ChannelFutureListener.class.getSimpleName() + ".", t); + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by " + + ChannelFutureListener.class.getSimpleName() + ".", t); + } } } diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java b/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java index c9fbc72445..493ad4d799 100644 --- a/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java @@ -56,10 +56,12 @@ public class DefaultChannelFuture implements ChannelFuture { public static void setUseDeadLockChecker(boolean useDeadLockChecker) { if (!useDeadLockChecker && !disabledDeadLockCheckerOnce) { disabledDeadLockCheckerOnce = true; - logger.debug( - "The dead lock checker in " + - DefaultChannelFuture.class.getSimpleName() + - " has been disabled as requested at your own risk."); + if (logger.isDebugEnabled()) { + logger.debug( + "The dead lock checker in " + + DefaultChannelFuture.class.getSimpleName() + + " has been disabled as requested at your own risk."); + } } DefaultChannelFuture.useDeadLockChecker = useDeadLockChecker; } @@ -413,9 +415,11 @@ public class DefaultChannelFuture implements ChannelFuture { try { l.operationComplete(this); } catch (Throwable t) { - logger.warn( - "An exception was thrown by " + - ChannelFutureListener.class.getSimpleName() + ".", t); + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by " + + ChannelFutureListener.class.getSimpleName() + ".", t); + } } } @@ -453,9 +457,11 @@ public class DefaultChannelFuture implements ChannelFuture { try { l.operationProgressed(this, amount, current, total); } catch (Throwable t) { - logger.warn( - "An exception was thrown by " + - ChannelFutureProgressListener.class.getSimpleName() + ".", t); + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by " + + ChannelFutureProgressListener.class.getSimpleName() + ".", t); + } } } } diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java index 312431a796..50487ae36e 100644 --- a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java @@ -357,7 +357,9 @@ public class DefaultChannelPipeline implements ChannelPipeline { remove((DefaultChannelHandlerContext) ctx); removed = true; } catch (Throwable t2) { - logger.warn("Failed to remove a handler: " + ctx.getName(), t2); + if (logger.isWarnEnabled()) { + logger.warn("Failed to remove a handler: " + ctx.getName(), t2); + } } if (removed) { @@ -564,8 +566,9 @@ public class DefaultChannelPipeline implements ChannelPipeline { public void sendUpstream(ChannelEvent e) { DefaultChannelHandlerContext head = getActualUpstreamContext(this.head); if (head == null) { - logger.warn( - "The pipeline contains no upstream handlers; discarding: " + e); + if (logger.isWarnEnabled()) { + logger.warn("The pipeline contains no upstream handlers; discarding: " + e); + } return; } @@ -648,9 +651,11 @@ public class DefaultChannelPipeline implements ChannelPipeline { protected void notifyHandlerException(ChannelEvent e, Throwable t) { if (e instanceof ExceptionEvent) { - logger.warn( - "An exception was thrown by a user handler " + - "while handling an exception event (" + e + ")", t); + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by a user handler " + + "while handling an exception event (" + e + ")", t); + } return; } @@ -664,7 +669,9 @@ public class DefaultChannelPipeline implements ChannelPipeline { try { sink.exceptionCaught(this, e, pe); } catch (Exception e1) { - logger.warn("An exception was thrown by an exception handler.", e1); + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown by an exception handler.", e1); + } } } @@ -815,7 +822,9 @@ public class DefaultChannelPipeline implements ChannelPipeline { @Override public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) { - logger.warn("Not attached yet; discarding: " + e); + if (logger.isWarnEnabled()) { + logger.warn("Not attached yet; discarding: " + e); + } } @Override diff --git a/transport/src/main/java/io/netty/channel/DefaultFileRegion.java b/transport/src/main/java/io/netty/channel/DefaultFileRegion.java index 096a944507..4ceb20feb9 100644 --- a/transport/src/main/java/io/netty/channel/DefaultFileRegion.java +++ b/transport/src/main/java/io/netty/channel/DefaultFileRegion.java @@ -81,7 +81,9 @@ public class DefaultFileRegion implements FileRegion { try { file.close(); } catch (IOException e) { - logger.warn("Failed to close a file.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a file.", e); + } } } } diff --git a/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java b/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java index d9e42aae11..b11f3b7520 100644 --- a/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java +++ b/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java @@ -382,9 +382,11 @@ public class DefaultChannelGroupFuture implements ChannelGroupFuture { try { l.operationComplete(this); } catch (Throwable t) { - logger.warn( - "An exception was thrown by " + - ChannelFutureListener.class.getSimpleName() + ".", t); + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by " + + ChannelFutureListener.class.getSimpleName() + ".", t); + } } } } diff --git a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java index 0ee8cbd265..9b6bd455de 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java @@ -116,8 +116,10 @@ final class LocalClientChannelSink extends AbstractChannelSink { } catch (Exception e) { future.setFailure(e); fireExceptionCaught(channel, e); - logger.warn( - "Failed to initialize an accepted socket.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to initialize an accepted socket.", e); + } return; } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java index 42c6db96ca..42fb4e3656 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioDatagramChannelConfig.java @@ -47,10 +47,13 @@ class DefaultNioDatagramChannelConfig extends DefaultDatagramChannelConfig if (getWriteBufferHighWaterMark() < getWriteBufferLowWaterMark()) { // Recover the integrity of the configuration with a sensible value. setWriteBufferLowWaterMark0(getWriteBufferHighWaterMark() >>> 1); - // Notify the user about misconfiguration. - logger.warn("writeBufferLowWaterMark cannot be greater than " - + "writeBufferHighWaterMark; setting to the half of the " - + "writeBufferHighWaterMark."); + if (logger.isWarnEnabled()) { + // Notify the user about misconfiguration. + logger.warn("writeBufferLowWaterMark cannot be greater than " + + "writeBufferHighWaterMark; setting to the half of the " + + "writeBufferHighWaterMark."); + } + } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioSocketChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioSocketChannelConfig.java index e0228af325..b637f012ae 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioSocketChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/DefaultNioSocketChannelConfig.java @@ -55,11 +55,14 @@ class DefaultNioSocketChannelConfig extends DefaultSocketChannelConfig if (getWriteBufferHighWaterMark() < getWriteBufferLowWaterMark()) { // Recover the integrity of the configuration with a sensible value. setWriteBufferLowWaterMark0(getWriteBufferHighWaterMark() >>> 1); - // Notify the user about misconfiguration. - logger.warn( - "writeBufferLowWaterMark cannot be greater than " + - "writeBufferHighWaterMark; setting to the half of the " + - "writeBufferHighWaterMark."); + if (logger.isWarnEnabled()) { + // Notify the user about misconfiguration. + logger.warn( + "writeBufferLowWaterMark cannot be greater than " + + "writeBufferHighWaterMark; setting to the half of the " + + "writeBufferHighWaterMark."); + } + } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketChannel.java index 571a832e29..756f9d0cc5 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketChannel.java @@ -52,9 +52,12 @@ final class NioClientSocketChannel extends NioSocketChannel { try { socket.close(); } catch (IOException e) { - logger.warn( - "Failed to close a partially initialized socket.", - e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", + e); + } + } } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java index 0b269857e7..32ffb4d5ce 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java @@ -208,7 +208,9 @@ class NioClientSocketPipelineSink extends AbstractChannelSink { try { selector.close(); } catch (Throwable t) { - logger.warn("Failed to close a selector.", t); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a selector.", t); + } } this.selector = selector = null; // The method will return to the caller at this point. @@ -302,8 +304,11 @@ class NioClientSocketPipelineSink extends AbstractChannelSink { try { selector.close(); } catch (IOException e) { - logger.warn( - "Failed to close a selector.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a selector.", e); + } + } finally { this.selector = null; } @@ -320,8 +325,11 @@ class NioClientSocketPipelineSink extends AbstractChannelSink { shutdown = false; } } catch (Throwable t) { - logger.warn( - "Unexpected exception in the selector loop.", t); + if (logger.isWarnEnabled()) { + logger.warn( + "Unexpected exception in the selector loop.", t); + } + // Prevent possible consecutive immediate failures. try { diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java index eff53e3433..1e8583e856 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java @@ -155,7 +155,9 @@ class NioDatagramWorker implements Runnable { // Release the Selector if the execution fails. selector.close(); } catch (final Throwable t) { - logger.warn("Failed to close a selector.", t); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a selector.", t); + } } this.selector = selector = null; // The method will return to the caller at this point. diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java b/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java index b4152e7b89..f2993f52b2 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioProviderMetadata.java @@ -70,18 +70,25 @@ final class NioProviderMetadata { if (constraintLevel < 0) { constraintLevel = 2; - logger.debug( - "Couldn't determine the NIO constraint level from " + - "the system properties; using the safest level (2)"); + if (logger.isDebugEnabled()) { + logger.debug( + "Couldn't determine the NIO constraint level from " + + "the system properties; using the safest level (2)"); + } } else if (constraintLevel != 0) { - logger.info( - "Using the autodetected NIO constraint level: " + - constraintLevel + - " (Use better NIO provider for better performance)"); + if (logger.isInfoEnabled()) { + logger.info( + "Using the autodetected NIO constraint level: " + + constraintLevel + + " (Use better NIO provider for better performance)"); + } } else { - logger.debug( - "Using the autodetected NIO constraint level: " + - constraintLevel); + if (logger.isDebugEnabled()) { + logger.debug( + "Using the autodetected NIO constraint level: " + + constraintLevel); + } + } } @@ -235,7 +242,9 @@ final class NioProviderMetadata { ch.socket().bind(new InetSocketAddress(0)); ch.configureBlocking(false); } catch (Throwable e) { - logger.warn("Failed to configure a temporary socket.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to configure a temporary socket.", e); + } return -1; } @@ -243,7 +252,9 @@ final class NioProviderMetadata { try { loop = new SelectorLoop(); } catch (Throwable e) { - logger.warn("Failed to open a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to open a temporary selector.", e); + } return -1; } @@ -251,7 +262,9 @@ final class NioProviderMetadata { try { ch.register(loop.selector, 0); } catch (Throwable e) { - logger.warn("Failed to register a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to register a temporary selector.", e); + } return -1; } @@ -337,7 +350,9 @@ final class NioProviderMetadata { try { ch.close(); } catch (Throwable e) { - logger.warn("Failed to close a temporary socket.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a temporary socket.", e); + } } } @@ -367,7 +382,9 @@ final class NioProviderMetadata { try { loop.selector.close(); } catch (Throwable e) { - logger.warn("Failed to close a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a temporary selector.", e); + } } } } @@ -405,7 +422,9 @@ final class NioProviderMetadata { } keys.clear(); } catch (IOException e) { - logger.warn("Failed to wait for a temporary selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to wait for a temporary selector.", e); + } } } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java index 91b6ca0cb2..4c96469ac9 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java @@ -73,8 +73,11 @@ final class NioServerSocketChannel extends AbstractServerChannel try { socket.close(); } catch (IOException e2) { - logger.warn( - "Failed to close a partially initialized socket.", e2); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", e2); + } + } throw new ChannelException("Failed to enter non-blocking mode.", e); diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java index 7663886d52..ce25a36f91 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java @@ -243,8 +243,10 @@ class NioServerSocketPipelineSink extends AbstractChannelSink { // Closed as requested. break; } catch (Throwable e) { - logger.warn( - "Failed to accept a connection.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to accept a connection.", e); + } try { Thread.sleep(1000); } catch (InterruptedException e1) { @@ -266,14 +268,18 @@ class NioServerSocketPipelineSink extends AbstractChannelSink { worker.register(NioAcceptedSocketChannel.create(channel.getFactory(), pipeline, channel, NioServerSocketPipelineSink.this, acceptedSocket, worker, currentThread), null); } catch (Exception e) { - logger.warn( - "Failed to initialize an accepted socket.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to initialize an accepted socket.", e); + } try { acceptedSocket.close(); } catch (IOException e2) { - logger.warn( - "Failed to close a partially accepted socket.", - e2); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially accepted socket.", + e2); + } } } } @@ -283,7 +289,9 @@ class NioServerSocketPipelineSink extends AbstractChannelSink { try { selector.close(); } catch (Exception e) { - logger.warn("Failed to close a selector.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a selector.", e); + } } } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java index 942d0d133d..35f07a512d 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java @@ -103,7 +103,9 @@ class NioWorker implements Runnable { try { selector.close(); } catch (Throwable t) { - logger.warn("Failed to close a selector.", t); + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a selector.", t); + } } this.selector = selector = null; // The method will return to the caller at this point. @@ -197,8 +199,10 @@ class NioWorker implements Runnable { try { selector.close(); } catch (IOException e) { - logger.warn( - "Failed to close a selector.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a selector.", e); + } } finally { this.selector = null; } @@ -215,8 +219,11 @@ class NioWorker implements Runnable { shutdown = false; } } catch (Throwable t) { - logger.warn( - "Unexpected exception in the selector loop.", t); + if (logger.isWarnEnabled()) { + logger.warn( + "Unexpected exception in the selector loop.", t); + } + // Prevent possible consecutive immediate failures that lead to // excessive CPU consumption. diff --git a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java index 79ade13920..51c8bbaa8c 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java @@ -32,10 +32,13 @@ final class SelectorUtil { try { selector.select(500); } catch (CancelledKeyException e) { + if (logger.isDebugEnabled()) { + logger.debug( + CancelledKeyException.class.getSimpleName() + + " raised by a Selector - JDK bug?", e); + } // Harmless exception - log anyway - logger.debug( - CancelledKeyException.class.getSimpleName() + - " raised by a Selector - JDK bug?", e); + } } diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketChannel.java index 87b256f4a2..ab2f6ede7f 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketChannel.java @@ -72,8 +72,11 @@ final class OioServerSocketChannel extends AbstractServerChannel try { socket.close(); } catch (IOException e2) { - logger.warn( - "Failed to close a partially initialized socket.", e2); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", e2); + } + } throw new ChannelException( "Failed to set the server socket timeout.", e); diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java index 1e594ace15..e8c4c3278f 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java @@ -203,14 +203,18 @@ class OioServerSocketPipelineSink extends AbstractChannelSink { workerExecutor, new OioWorker(acceptedChannel)); } catch (Exception e) { - logger.warn( - "Failed to initialize an accepted socket.", e); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to initialize an accepted socket.", e); + } try { acceptedSocket.close(); } catch (IOException e2) { - logger.warn( - "Failed to close a partially accepted socket.", - e2); + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially accepted socket.", + e2); + } } } } catch (SocketTimeoutException e) { @@ -221,9 +225,12 @@ class OioServerSocketPipelineSink extends AbstractChannelSink { if (!channel.socket.isBound() || channel.socket.isClosed()) { break; } + + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to accept a connection.", e); + } - logger.warn( - "Failed to accept a connection.", e); try { Thread.sleep(1000); } catch (InterruptedException e1) {