Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override HttpRequestDecoder.createMessage() for perfomance #4863

Merged
merged 29 commits into from Apr 11, 2024

Conversation

echo304
Copy link
Contributor

@echo304 echo304 commented May 6, 2023

Motivation:

By overriding HttpRequestDecoder.createMessage(), performance can be improved because there is no need to copy original netty header.

Modifications:

  • TBD

Result:

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Left some preliminary comments

@jrhee17 jrhee17 added this to the 1.24.0 milestone May 8, 2023
@echo304 echo304 marked this pull request as draft May 8, 2023 03:05
@echo304
Copy link
Contributor Author

echo304 commented May 8, 2023

@jrhee17 This PR is not ready to be reviewed. Sorry about that 🙇
I changed it to draft PR!

@trustin
Copy link
Member

trustin commented May 8, 2023

Thanks @echo304 for this 🙇 Looking forward to some performance boost!


@Override
protected HttpMessage createMessage(String[] initialLine) throws Exception {
return new DefaultHttpRequest(
Copy link
Contributor Author

@echo304 echo304 May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some investigation, I found the logic that converts Netty request to Armeria headers.

public static RequestHeaders toArmeria(
ChannelHandlerContext ctx, HttpRequest in,
ServerConfig cfg, String scheme, RequestTarget reqTarget) throws URISyntaxException {
final io.netty.handler.codec.http.HttpHeaders inHeaders = in.headers();
final RequestHeadersBuilder out = RequestHeaders.builder();
out.sizeHint(inHeaders.size());
out.method(firstNonNull(HttpMethod.tryParse(in.method().name()), HttpMethod.UNKNOWN))
.scheme(scheme)
.path(reqTarget.toString());
// Add the HTTP headers which have not been consumed above
toArmeria(inHeaders, out);
if (!out.contains(HttpHeaderNames.HOST)) {
// The client violates the spec that the request headers must contain a Host header.
// But we just add Host header to allow the request.
// https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
final String defaultHostname = cfg.defaultVirtualHost().defaultHostname();
final int port = ((InetSocketAddress) ctx.channel().localAddress()).getPort();
out.add(HttpHeaderNames.HOST, defaultHostname + ':' + port);
}
return out.build();
}

At the very beginning, I thought that we can implements io.netty.handler.codec.http.HttpRequest and create our own HttpRequest implementation to replace DefaultHttpRequest.
But with this design, we still need ArmeriaHttpUtil.toArmeria some part of the logic because this class(HttpServerCodec) depends on netty's interface.

Do you have any advice on it? @minwoox @ikhoon

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that we can implements io.netty.handler.codec.http.HttpRequest and create our own HttpRequest implementation to replace DefaultHttpRequest.

I think it's the way to go. 👍 We can probably create RequestHeadersBuilder in it and passes it in Http1RequestDecoder.

we still need ArmeriaHttpUtil.toArmeria some part of the logic because this class(HttpServerCodec) depends on netty's interface.

Yeah, we can apply the logic at once in the Http1RequestDecoder or we can just use the logic inside of the HttpHeaders that is returned by HttpMessage.headers().

Does this make sense? 🤔

Copy link
Contributor Author

@echo304 echo304 May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to double check. ✅

Is it what we want to improve/replace?

// Convert the Netty HttpHeaders into Armeria RequestHeaders.
final RequestHeaders headers =
ArmeriaHttpUtil.toArmeria(ctx, nettyReq, cfg, scheme.toString(), reqTarget);

It's a little bit difficult to understand task than I expected so I wanted to make sure I'm on the right direction. 😏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly. 👍
We can define our own DefaultHttpRequest and return it in createMessage:

@Override
protected HttpMessage createMessage(String[] initialLine) throws Exception {
    return new ArmeriaDefaultHttpRequest(HttpMethod.valueOf(initialLine[0]), initialLine[1]);
}

static final class ArmeriaDefaultHttpRequest implements HttpRequest {

    private final HttpMethod method;
    private final RequestHeadersBuilder builder;
    private final HttpHeaders headers;

    ArmeriaDefaultHttpRequest(HttpMethod method, String uri) {
        this.method = method;
        builder = RequestHeaders.builder(
                com.linecorp.armeria.common.HttpMethod.valueOf(method.name()), uri);
        headers = new ArmeriaHttpHeaders(builder);
    }

    @Override
    public HttpMethod method() {
        return method;
    }

    @Override
    public HttpHeaders headers() {
        return headers;
    }
    ...
}

We also need ArmeriaHttpHeaders class and it will prevents adding not allowed headers:

 final class ArmeriaHttpHeaders extends HttpHeaders {

    private final RequestHeadersBuilder builder;

    ArmeriaHttpHeaders(RequestHeadersBuilder builder) {
        this.builder = builder;
    }

    @Override
    public String get(String name) {
        return builder.get(name);
    }

    @Override
    public Integer getInt(CharSequence name) {
        return builder.getInt(name);
    }

    @Override
    public HttpHeaders add(String name, Object value) {
        // We can add a logic that prevents adding invalid headers that is defined in ArmeriaHttpUtil.toArmeria()
        return builder.add(name, value);
    }
    ...
}

In Http1RequestDecoder, we will bring the RequestHeadersBuilder from the Netty request and add a host header if needed:

if (nettyReq instanceof ArmeriaDefaultHttpRequest) {
    final RequestHeadersBuilder builder =
            ((ArmeriaDefaultHttpRequest) nettyReq).requestHeadersBuilder();
}

This is what I have imagined. 😉
Please let me know if it works or not.

Copy link
Contributor Author

@echo304 echo304 May 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I started to understand the way we have to go. 😏
Let me clarify what you're saying:

with ArmeriaHttpHeaders which extends io.netty.handler.codec.http.HttpHeaders,
we can store actual data inside HttpHeaders with RequestHeadersBuilder builder.

And we can build com.linecorp.armeria.common.RequestHeaders inside Http1RequestDecoder with ((ArmeriaDefaultHttpRequest) nettyReq).requestHeadersBuilder();

And hopefully, RequestHeadersBuilder builder already contains most of the data that we need, many redundant process inside toArmeria can be removed. (especially, here 👇 )

public static void toArmeria(io.netty.handler.codec.http.HttpHeaders inHeaders, HttpHeadersBuilder out) {
final Iterator<Entry<CharSequence, CharSequence>> iter = inHeaders.iteratorCharSequence();
// Choose 8 as a default size because it is unlikely we will see more than 4 Connection headers values,
// but still allowing for "enough" space in the map to reduce the chance of hash code collision.
final CaseInsensitiveMap connectionDisallowedList =
toLowercaseMap(inHeaders.valueCharSequenceIterator(HttpHeaderNames.CONNECTION), 8);
StringJoiner cookieJoiner = null;
while (iter.hasNext()) {
final Entry<CharSequence, CharSequence> entry = iter.next();
final AsciiString aName = HttpHeaderNames.of(entry.getKey()).toLowerCase();
if (HTTP_TO_HTTP2_HEADER_DISALLOWED_LIST.contains(aName) ||
connectionDisallowedList.contains(aName)) {
final CharSequence value = entry.getValue();
if (!maybeWebSocketUpgrade(aName, value)) {
continue;
}
}
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 makes a special exception for TE
if (aName.equals(HttpHeaderNames.TE)) {
toHttp2HeadersFilterTE(entry, out);
continue;
}
// Cookies must be concatenated into a single octet string.
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.5
final CharSequence value = entry.getValue();
if (aName.equals(HttpHeaderNames.COOKIE)) {
if (cookieJoiner == null) {
cookieJoiner = new StringJoiner(COOKIE_SEPARATOR);
}
COOKIE_SPLITTER.split(value).forEach(cookieJoiner::add);
} else {
out.add(aName, convertHeaderValue(aName, value));
}
}
if (cookieJoiner != null && cookieJoiner.length() != 0) {
out.add(HttpHeaderNames.COOKIE, cookieJoiner.toString());
}
}

Is it correct?

But my concern is that implementing own ArmeriaHttpHeaders is pretty huge task. (Well, obviously this is unavoidable)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct?

That's correct. 😄

But my concern is that implementing own ArmeriaHttpHeaders is pretty huge task. (Well, obviously this is unavoidable)

Yes, it is. But we don't have to implement all of the methods. It's only used in the pipeline so we can implement the methods that are called in the handler. 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙆‍♂️
Let me show you some code as soon as possible even if it's not completed!

- Custom Headers for Armeria which extends `HttpHeaders`
- It delegates most of the header value storing to given `RequestHeadersBuilder`
- Custom `HttpRequest` that replaces `DefaultHttpRequest` from `HttpServerCodec`
@trustin
Copy link
Member

trustin commented May 30, 2023

Gentle ping, @echo304. Looking forward to this improvement! 🤞

@echo304 echo304 marked this pull request as ready for review June 2, 2023 12:23
# Conflicts:
#	core/src/main/java/com/linecorp/armeria/internal/common/ArmeriaHttpUtil.java
Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. 😉

final RequestHeaders headers =
ArmeriaHttpUtil.toArmeria(ctx, nettyReq, cfg, scheme.toString(), reqTarget);
final RequestHeaders headers;
if (nettyReq instanceof ArmeriaDefaultHttpRequest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably do
assert nettyReq instanceof ArmeriaDefaultHttpRequest

Comment on lines 180 to 184

final ArmeriaDefaultHttpRequest armeriaDefaultHttpRequest = (ArmeriaDefaultHttpRequest) nettyReq;
final HttpHeaders inHeaders = armeriaDefaultHttpRequest.headers();

builder.sizeHint(inHeaders.size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to set the sizeHint:

Suggested change
final ArmeriaDefaultHttpRequest armeriaDefaultHttpRequest = (ArmeriaDefaultHttpRequest) nettyReq;
final HttpHeaders inHeaders = armeriaDefaultHttpRequest.headers();
builder.sizeHint(inHeaders.size());

import io.netty.handler.codec.http.HttpStatusClass;
import io.netty.handler.codec.http.HttpVersion;

public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use this class instead of Netty's one from here?

ctx.pipeline().remove(HttpServerCodec.class);

@@ -0,0 +1,199 @@
package com.linecorp.armeria.internal.common;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing copyright

import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.AsciiString;

public final class ArmeriaHttpHeaders extends HttpHeaders {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can put this method in the package com.linecorp.armeria.server and remove the public modifier.

import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.internal.ObjectUtil;

public class ArmeriaDefaultHttpRequest extends DefaultHttpMessage implements HttpRequest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class ArmeriaDefaultHttpRequest extends DefaultHttpMessage implements HttpRequest {
final class ArmeriaDefaultHttpRequest extends DefaultHttpMessage implements HttpRequest {

We can remove the public modifiers in this class.

* @param method the HTTP method of the request
* @param uri the URI or path of the request
*/
public ArmeriaDefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the constructor which isn't used.

* @param uri the URI or path of the request
* @param givenHeaders the Headers for this Request
*/
public ArmeriaDefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, HttpHeaders givenHeaders) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

// @Override
// public String toString() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use MoreObjects.toStringHelper() for toString().

@ikhoon ikhoon modified the milestones: 1.24.0, 1.25.0 Jun 6, 2023
splitByCommaAndAdd(connectionDisallowedList, lowerCased);
}

if (HTTP_TO_HTTP2_HEADER_DISALLOWED_LIST.contains(asciiName) ||
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this line of code, this headers won't contain Transfer-Encoding header.
As a result, below test failed because it considers corresponding request as if endOfStream.
스크린샷 2023-06-08 오후 8 51 41

final boolean endOfStream = contentEmpty && !HttpUtil.isTransferEncodingChunked(nettyReq);

So I'm trying to solve these kind of regression without copying another header.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I missed this comment. If so, I think we can remove the Transfer-Encoding header in Http1RequestDecoder after isTransferEncodingChunked is executed. WDYT?

Copy link
Contributor Author

@echo304 echo304 Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minwoox Yeah, you're right.
I tried another approach that moved isTransferEncodingChunked up before filtering out disallowed headers.
a2a2f48#diff-a3306f0289f199b1657bb0e32849fd97502c82a91318adc373f8e750a96f84ecR187-R188

Comment on lines 55 to 56
builder = RequestHeaders.builder(
com.linecorp.armeria.common.HttpMethod.valueOf(method.name()), uri);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I couldn't figure out 1)why the request is mapped with DefaultFullHttpRequest and 2)the location where it's mapped.
@minwoox Can you provide some advice regarding it?

It seems like an exception is raised while converting the method. We need to fix it:

Suggested change
builder = RequestHeaders.builder(
com.linecorp.armeria.common.HttpMethod.valueOf(method.name()), uri);
final com.linecorp.armeria.common.HttpMethod armeriaMethod =
com.linecorp.armeria.common.HttpMethod.tryParse(method.name());
builder = RequestHeaders.builder(armeriaMethod != null ? armeriaMethod
: com.linecorp.armeria.common.HttpMethod.UNKNOWN,
uri);

import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.internal.ObjectUtil;

final class ArmeriaDefaultHttpRequest extends DefaultHttpMessage implements HttpRequest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason to extend DefaultHttpMessage? That internally creates DefaultHttpHeaders again. Duplicate headers may result in performance degression.

Suggested change
final class ArmeriaDefaultHttpRequest extends DefaultHttpMessage implements HttpRequest {
final class ArmeriaHttp1Request implements HttpRequest {

*/
public ArmeriaHttpHeaders(RequestHeadersBuilder builder, HttpHeaders headers) {
this.builder = builder;
headers.forEach(e -> add(e.getKey(), e.getValue()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies the Netty headers to Armeria Headers. Unfortunately, it does not match with the goal of this PR.

there is no need to copy original netty header.

What do you think of lazily creating or mutating fields when necessary?

private final HttpHeaders nettyHeaders;
@Nullable 
private RequestHeaders armeriaHeaders; 

public ArmeriaHttpHeaders(RequestHeadersBuilder builder, HttpHeaders headers) {
   this.headers = headers;
}

@Override
public String get(String name) {
    if (armeriaHeaders != null) {
        return armeriaHeaders.get(name);
    }
    if (needsValidation(name)) {
        // Create Armeria headers and return;
    }
    // Use Netty Headers as is.
    return headers.get(name);
}

public List<Entry<String, String>> entries() {
    // Create Armeria Headers if null
}
public Iterator<Entry<String, String>> iterator() {
    // Create Armeria Headers if null
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing out!
Let me take a look into it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed your idea and bring some thoughts.

Firstly, I totally agree that current approach doesn't resolve the problem of copying headers 🤦

And specifically against your advice, Http1RequestDecoder has plenty of logic that utilize RequestHeaders. So I think we need to convert(create) HttpHeaders to RequestHeaders most of the time even with 'lazy initializing' strategy. Which means it goes back to copying headers situation again.
I'm not sure if 'lazy initialization' strategy could provide significant value. 🤔 Correct me if I'm wrong.

With above idea, I'm afraid we need to copy headers somewhere in our system anyway.

This copies the Netty headers to Armeria Headers. Unfortunately, it does not match with the goal of this PR.

there is no need to copy original netty header.

What do you think of lazily creating or mutating fields when necessary?

private final HttpHeaders nettyHeaders;
@Nullable 
private RequestHeaders armeriaHeaders; 

public ArmeriaHttpHeaders(RequestHeadersBuilder builder, HttpHeaders headers) {
   this.headers = headers;
}

@Override
public String get(String name) {
    if (armeriaHeaders != null) {
        return armeriaHeaders.get(name);
    }
    if (needsValidation(name)) {
        // Create Armeria headers and return;
    }
    // Use Netty Headers as is.
    return headers.get(name);
}

public List<Entry<String, String>> entries() {
    // Create Armeria Headers if null
}
public Iterator<Entry<String, String>> iterator() {
    // Create Armeria Headers if null
}

@jrhee17 jrhee17 modified the milestones: 1.25.0, 1.26.0 Jul 18, 2023
@minwoox minwoox modified the milestones: 1.26.0, 1.27.0 Oct 11, 2023
@ikhoon ikhoon modified the milestones: 1.27.0, 1.28.0 Jan 5, 2024
Copy link

codecov bot commented Mar 25, 2024

Codecov Report

Attention: Patch coverage is 56.04396% with 80 lines in your changes are missing coverage. Please review.

Project coverage is 74.04%. Comparing base (c6b8906) to head (1eaad3a).
Report is 42 commits behind head on main.

Files Patch % Lines
...com/linecorp/armeria/server/NettyHttp1Headers.java 31.25% 44 Missing ⚠️
...com/linecorp/armeria/server/NettyHttp1Request.java 30.30% 23 Missing ⚠️
...ecorp/armeria/internal/common/ArmeriaHttpUtil.java 81.39% 5 Missing and 3 partials ⚠️
...a/com/linecorp/armeria/server/HttpServerCodec.java 90.32% 2 Missing and 1 partial ⚠️
...corp/armeria/server/ArmeriaHttpHeadersFactory.java 75.00% 1 Missing ⚠️
...m/linecorp/armeria/server/Http1RequestDecoder.java 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4863      +/-   ##
============================================
+ Coverage     74.01%   74.04%   +0.02%     
- Complexity    20799    21068     +269     
============================================
  Files          1803     1825      +22     
  Lines         76617    77775    +1158     
  Branches       9772     9937     +165     
============================================
+ Hits          56708    57587     +879     
- Misses        15289    15513     +224     
- Partials       4620     4675      +55     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jrhee17
Copy link
Contributor

jrhee17 commented Mar 26, 2024

This PR has been stale for a while - I've added a commit to get it moving forward. Changes include:

  • Now, only a single header map is created, followed by a "purge" of http1 only headers
    • This design is necessary because netty/armeria assumes the nettyReq still has a CONNECTION header in some places. Hence, the purge is done when the netty request is transformed to an armeria request. Note that from this time, http1-only headers won't be available.
    • The design depends on the fact that there are less HTTP1-only headers than the overall headers (which I think should be the majority of the case anyways)
  • Renamed the classes from ArmeriaHttpHeaders, ArmeriaDefaultHttpRequest to NettyHttp1Headers, NettyHttp1Request
    • Also introduced ArmeriaHttpHeadersFactory.java to save more allocation
  • Cleaned up some code

Let me know if anything doesn't make sense 🙇

@@ -696,6 +693,8 @@ public static HttpHeaders toArmeria(io.netty.handler.codec.http.HttpHeaders inHe

/**
* Converts the specified Netty HTTP/1 headers into Armeria HTTP/2 headers.
* Functionally, this method is expected to behavior in the same way as
* {@link #purgeHttp1OnlyHeaders(io.netty.handler.codec.http.HttpHeaders, HttpHeadersBuilder)}.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I haven't migrated this method to use purgeHttp1OnlyHeaders since this is probably slower for other code paths using this method without zero-copy

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this PR is ready for review by other maintainers - changes look good to me for merging

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also put a patch that

  • hides static methods in ArmeriaHttpUtil
  • changes ArmeriaHttpHeadersFactory to enum

Thanks!

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @echo304!
I'm looking forward to seeing benchmark results. What do you think of sending a PR to https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/frameworks/Java/armeria/pom.xml#L19 when a new version is released?

@jrhee17 jrhee17 merged commit 8a99f23 into line:main Apr 11, 2024
17 of 18 checks passed
jrhee17 added a commit to jrhee17/armeria that referenced this pull request Apr 29, 2024
@jrhee17
Copy link
Contributor

jrhee17 commented Apr 29, 2024

I ran com.linecorp.armeria.core.HttpServerBenchmark.plainText a couple times

./gradlew :benchmarks:jmh:jmh \
      -Pjmh.includes='com.linecorp.armeria.core.HttpServerBenchmark.plainText' -Pjmh.params='protocol=H1C' -Pjmh.profilers="async:libPath=$HOME/Projects/async-profiler/build/lib/libasyncProfiler.dylib;output=flamegraph;dir=$HOME/result"

base

Benchmark                            (chunkCount)  (protocol)   Mode  Cnt      Score      Error  Units
HttpServerBenchmark.plainText                 100         H1C  thrpt    5  59030.256 ± 4749.908  ops/s
HttpServerBenchmark.plainText:async           100         H1C  thrpt             NaN               ---

feat

Benchmark                            (chunkCount)  (protocol)   Mode  Cnt      Score      Error  Units
HttpServerBenchmark.plainText                 100         H1C  thrpt    5  60595.652 ± 5476.196  ops/s
HttpServerBenchmark.plainText:async           100         H1C  thrpt             NaN               ---

I don't think there is a significant difference in the simple case. Having said this, Assuming there are more custom headers (which I think better represents the real world) I suppose the performance improvement is more obvious.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Override HttpRequestDecoder.createMessage() for perfomance
5 participants