View Javadoc
1   /*
2    * Copyright 2026 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    *   https://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.http2;
17  
18  import io.netty.handler.codec.http.DefaultHttpHeaders;
19  import io.netty.handler.codec.http.DefaultHttpRequest;
20  import io.netty.handler.codec.http.HttpHeaderNames;
21  import io.netty.handler.codec.http.HttpHeaders;
22  import io.netty.handler.codec.http.HttpMethod;
23  import io.netty.handler.codec.http.HttpRequest;
24  import io.netty.handler.codec.http.HttpScheme;
25  import io.netty.handler.codec.http.HttpUtil;
26  import io.netty.handler.codec.http.HttpVersion;
27  import io.netty.microbench.util.AbstractMicrobenchmark;
28  import io.netty.util.AsciiString;
29  import org.openjdk.jmh.annotations.Benchmark;
30  import org.openjdk.jmh.annotations.BenchmarkMode;
31  import org.openjdk.jmh.annotations.Measurement;
32  import org.openjdk.jmh.annotations.Mode;
33  import org.openjdk.jmh.annotations.OutputTimeUnit;
34  import org.openjdk.jmh.annotations.Param;
35  import org.openjdk.jmh.annotations.Scope;
36  import org.openjdk.jmh.annotations.Setup;
37  import org.openjdk.jmh.annotations.State;
38  import org.openjdk.jmh.annotations.Warmup;
39  import org.openjdk.jmh.infra.Blackhole;
40  
41  import java.net.URI;
42  import java.util.concurrent.TimeUnit;
43  
44  import static io.netty.util.internal.StringUtil.isNullOrEmpty;
45  
46  @State(Scope.Benchmark)
47  @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
48  @Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
49  @BenchmarkMode(Mode.AverageTime)
50  @OutputTimeUnit(TimeUnit.NANOSECONDS)
51  public class Http2RequestTargetConversionBenchmark extends AbstractMicrobenchmark {
52  
53      @Param
54      public RequestTargetType requestTargetType;
55  
56      private HttpRequest request;
57  
58      @Setup
59      public void setup() {
60          request = new DefaultHttpRequest(
61                  HttpVersion.HTTP_1_1,
62                  HttpMethod.GET,
63                  requestTargetType.requestTarget,
64                  new DefaultHttpHeaders(),
65                  false);
66          request.headers().set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), HttpScheme.HTTP.name());
67      }
68  
69      @Benchmark
70      public void newConversion(Blackhole bh) {
71          bh.consume(HttpConversionUtil.toHttp2Headers(request, false));
72      }
73  
74      @Benchmark
75      public void oldUriConversion(Blackhole bh) {
76          bh.consume(oldToHttp2Headers(request));
77      }
78  
79      public enum RequestTargetType {
80          ORIGIN("/orders/123/items?expand=details"),
81          ABSOLUTE("http://example.com/orders/123/items?expand=details#section"),
82          ABSOLUTE_NO_PATH("http://example.com?next=/home#section"),
83          ABSOLUTE_NO_AUTHORITY("http://?x=1#frag"),
84          SCHEME_ONLY_ABSOLUTE_PATH("http:/orders/123/items?expand=details");
85  
86          final String requestTarget;
87  
88          RequestTargetType(String requestTarget) {
89              this.requestTarget = requestTarget;
90          }
91      }
92  
93      private static Http2Headers oldToHttp2Headers(final HttpRequest request) {
94          HttpHeaders inHeaders = request.headers();
95          Http2Headers out = new DefaultHttp2Headers(false, inHeaders.size());
96          String host = inHeaders.getAsString(HttpHeaderNames.HOST);
97          if (HttpUtil.isOriginForm(request.uri()) || HttpUtil.isAsteriskForm(request.uri())) {
98              out.path(new AsciiString(request.uri()));
99              oldSetHttp2Scheme(inHeaders, URI.create(""), out);
100         } else {
101             URI requestTargetUri = URI.create(request.uri());
102             out.path(oldToHttp2Path(requestTargetUri));
103             host = isNullOrEmpty(host) ? requestTargetUri.getAuthority() : host;
104             oldSetHttp2Scheme(inHeaders, requestTargetUri, out);
105         }
106         HttpConversionUtil.setHttp2Authority(host, out);
107         out.method(request.method().asciiName());
108         HttpConversionUtil.toHttp2Headers(inHeaders, out);
109         return out;
110     }
111 
112     private static AsciiString oldToHttp2Path(final URI uri) {
113         StringBuilder pathBuilder = new StringBuilder();
114         if (!isNullOrEmpty(uri.getRawPath())) {
115             pathBuilder.append(uri.getRawPath());
116         }
117         if (!isNullOrEmpty(uri.getRawQuery())) {
118             pathBuilder.append('?');
119             pathBuilder.append(uri.getRawQuery());
120         }
121         if (!isNullOrEmpty(uri.getRawFragment())) {
122             pathBuilder.append('#');
123             pathBuilder.append(uri.getRawFragment());
124         }
125         String path = pathBuilder.toString();
126         return path.isEmpty() ? new AsciiString("/") : new AsciiString(path);
127     }
128 
129     private static void oldSetHttp2Scheme(final HttpHeaders in, final URI uri, final Http2Headers out) {
130         String value = uri.getScheme();
131         if (!isNullOrEmpty(value)) {
132             out.scheme(new AsciiString(value));
133             return;
134         }
135 
136         CharSequence cValue = in.get(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text());
137         if (cValue != null) {
138             out.scheme(AsciiString.of(cValue));
139             return;
140         }
141 
142         if (uri.getPort() == HttpScheme.HTTPS.port()) {
143             out.scheme(HttpScheme.HTTPS.name());
144         } else if (uri.getPort() == HttpScheme.HTTP.port()) {
145             out.scheme(HttpScheme.HTTP.name());
146         } else {
147             throw new IllegalArgumentException(
148                     ":scheme must be specified. see https://tools.ietf.org/html/rfc7540#section-8.1.2.3");
149         }
150     }
151 }