Skip to content

Commit 1a43923

Browse files
committed
Headers Performance Boost and Interface Simplification
Motivation: A degradation in performance has been observed from the 4.0 branch as documented in netty#3962. Modifications: - Simplify Headers class hierarchy. - Restore the DefaultHeaders to be based upon DefaultHttpHeaders from 4.0. - Make various other modifications that are causing hot spots. Result: Performance is now on par with 4.0.
1 parent 36d682b commit 1a43923

102 files changed

Lines changed: 2841 additions & 4093 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2015 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.handler.codec.http;
17+
18+
import io.netty.util.internal.StringUtil;
19+
20+
import java.util.Collection;
21+
import java.util.Iterator;
22+
23+
import static io.netty.util.internal.StringUtil.COMMA;
24+
25+
/**
26+
* Will add multiple values for the same header as single header with a comma separated list of values.
27+
* <p>
28+
* Please refer to section <a href="https://tools.ietf.org/html/rfc7230#section-3.2.2">RFC 7230, 3.2.2</a>.
29+
*/
30+
public class CombinedHttpHeaders extends DefaultHttpHeaders {
31+
/**
32+
* An estimate of the size of a header value.
33+
*/
34+
private static final int VALUE_LENGTH_ESTIMATE = 10;
35+
private CsvValueEscaper<Object> objectEscaper;
36+
private CsvValueEscaper<CharSequence> charSequenceEscaper;
37+
38+
public CombinedHttpHeaders(boolean validate) {
39+
super(validate);
40+
}
41+
42+
private CsvValueEscaper<Object> objectEscaper() {
43+
if (objectEscaper == null) {
44+
objectEscaper = new CsvValueEscaper<Object>() {
45+
@Override
46+
public CharSequence escape(Object value) {
47+
return StringUtil.escapeCsv(valueConverter().convertObject(value));
48+
}
49+
};
50+
}
51+
return objectEscaper;
52+
}
53+
54+
private CsvValueEscaper<CharSequence> charSequenceEscaper() {
55+
if (charSequenceEscaper == null) {
56+
charSequenceEscaper = new CsvValueEscaper<CharSequence>() {
57+
@Override
58+
public CharSequence escape(CharSequence value) {
59+
return StringUtil.escapeCsv(value);
60+
}
61+
};
62+
}
63+
return charSequenceEscaper;
64+
}
65+
66+
@Override
67+
public CombinedHttpHeaders add(CharSequence name, CharSequence value) {
68+
return addEscapedValue(name, StringUtil.escapeCsv(value));
69+
}
70+
71+
@Override
72+
public CombinedHttpHeaders add(CharSequence name, CharSequence... values) {
73+
return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values));
74+
}
75+
76+
@Override
77+
public CombinedHttpHeaders add(CharSequence name, Iterable<? extends CharSequence> values) {
78+
return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values));
79+
}
80+
81+
@Override
82+
public CombinedHttpHeaders addObject(CharSequence name, Iterable<?> values) {
83+
return addEscapedValue(name, commaSeparate(objectEscaper(), values));
84+
}
85+
86+
@Override
87+
public CombinedHttpHeaders addObject(CharSequence name, Object... values) {
88+
return addEscapedValue(name, commaSeparate(objectEscaper(), values));
89+
}
90+
91+
@Override
92+
public CombinedHttpHeaders set(CharSequence name, CharSequence... values) {
93+
super.set(name, commaSeparate(charSequenceEscaper(), values));
94+
return this;
95+
}
96+
97+
@Override
98+
public CombinedHttpHeaders set(CharSequence name, Iterable<? extends CharSequence> values) {
99+
super.set(name, commaSeparate(charSequenceEscaper(), values));
100+
return this;
101+
}
102+
103+
@Override
104+
public CombinedHttpHeaders setObject(CharSequence name, Object... values) {
105+
super.set(name, commaSeparate(objectEscaper(), values));
106+
return this;
107+
}
108+
109+
@Override
110+
public CombinedHttpHeaders setObject(CharSequence name, Iterable<?> values) {
111+
super.set(name, commaSeparate(objectEscaper(), values));
112+
return this;
113+
}
114+
115+
private CombinedHttpHeaders addEscapedValue(CharSequence name, CharSequence escapedValue) {
116+
CharSequence currentValue = super.get(name);
117+
if (currentValue == null) {
118+
super.add(name, escapedValue);
119+
} else {
120+
super.set(name, commaSeparateEscapedValues(currentValue, escapedValue));
121+
}
122+
return this;
123+
}
124+
125+
private static <T> CharSequence commaSeparate(CsvValueEscaper<T> escaper, T... values) {
126+
StringBuilder sb = new StringBuilder(values.length * VALUE_LENGTH_ESTIMATE);
127+
if (values.length > 0) {
128+
int end = values.length - 1;
129+
for (int i = 0; i < end; i++) {
130+
sb.append(escaper.escape(values[i])).append(COMMA);
131+
}
132+
sb.append(escaper.escape(values[end]));
133+
}
134+
return sb;
135+
}
136+
137+
private static <T> CharSequence commaSeparate(CsvValueEscaper<T> escaper, Iterable<? extends T> values) {
138+
@SuppressWarnings("rawtypes")
139+
final StringBuilder sb = values instanceof Collection
140+
? new StringBuilder(((Collection) values).size() * VALUE_LENGTH_ESTIMATE) : new StringBuilder();
141+
Iterator<? extends T> iterator = values.iterator();
142+
if (iterator.hasNext()) {
143+
T next = iterator.next();
144+
while (iterator.hasNext()) {
145+
sb.append(escaper.escape(next)).append(COMMA);
146+
next = iterator.next();
147+
}
148+
sb.append(escaper.escape(next));
149+
}
150+
return sb;
151+
}
152+
153+
private CharSequence commaSeparateEscapedValues(CharSequence currentValue, CharSequence value) {
154+
return new StringBuilder(currentValue.length() + 1 + value.length())
155+
.append(currentValue)
156+
.append(COMMA)
157+
.append(value);
158+
}
159+
160+
/**
161+
* Escapes comma separated values (CSV).
162+
*
163+
* @param <T> The type that a concrete implementation handles
164+
*/
165+
private interface CsvValueEscaper<T> {
166+
/**
167+
* Appends the value to the specified {@link StringBuilder}, escaping if necessary.
168+
*
169+
* @param value the value to be appended, escaped if necessary
170+
*/
171+
CharSequence escape(T value);
172+
}
173+
}

codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpResponse.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
*/
1616
package io.netty.handler.codec.http;
1717

18+
import static io.netty.util.internal.ObjectUtil.checkNotNull;
1819
import io.netty.buffer.ByteBuf;
1920
import io.netty.buffer.Unpooled;
2021

21-
2222
/**
2323
* Default implementation of a {@link FullHttpResponse}.
2424
*/
@@ -33,7 +33,7 @@ public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status) {
3333
}
3434

3535
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content) {
36-
this(version, status, content, false);
36+
this(version, status, content, true);
3737
}
3838

3939
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, boolean validateHeaders) {
@@ -46,18 +46,16 @@ public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, b
4646
}
4747

4848
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status,
49-
ByteBuf content, boolean singleFieldHeaders) {
50-
this(version, status, content, true, singleFieldHeaders);
49+
ByteBuf content, boolean validateHeaders) {
50+
this(version, status, content, validateHeaders, false);
5151
}
5252

5353
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status,
5454
ByteBuf content, boolean validateHeaders, boolean singleFieldHeaders) {
5555
super(version, status, validateHeaders, singleFieldHeaders);
56-
if (content == null) {
57-
throw new NullPointerException("content");
58-
}
59-
this.content = content;
60-
trailingHeaders = new DefaultHttpHeaders(validateHeaders, singleFieldHeaders);
56+
this.content = checkNotNull(content, "content");
57+
this.trailingHeaders = singleFieldHeaders ? new CombinedHttpHeaders(validateHeaders)
58+
: new DefaultHttpHeaders(validateHeaders);
6159
this.validateHeaders = validateHeaders;
6260
}
6361

0 commit comments

Comments
 (0)