Skip to content

Commit 8e3ffdf

Browse files
committed
add experimental RFC 9218 Priority header support.
Wire H2RequestPriority via H2AsyncClientBuilder.enablePriorityHeader(); omit on defaults, allow overwrite.
1 parent 26d51f6 commit 8e3ffdf

File tree

4 files changed

+392
-0
lines changed

4 files changed

+392
-0
lines changed

httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
6565
import org.apache.hc.client5.http.impl.nio.MultihomeConnectionInitiator;
6666
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
67+
import org.apache.hc.client5.http.protocol.H2RequestPriority;
6768
import org.apache.hc.client5.http.protocol.RedirectStrategy;
6869
import org.apache.hc.client5.http.protocol.RequestAddCookies;
6970
import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
@@ -699,6 +700,27 @@ public final H2AsyncClientBuilder evictIdleConnections(final TimeValue maxIdleTi
699700
return this;
700701
}
701702

703+
/**
704+
* Enables emission of the RFC 9218 {@code Priority} request header for HTTP/2 requests.
705+
* <p>
706+
* When enabled, {@link org.apache.hc.client5.http.protocol.H2RequestPriority} is added as the
707+
* last request interceptor. The header is omitted when the effective value equals the RFC
708+
* defaults ({@code u=3, i=false}). Per-request values can be supplied via
709+
* {@code HttpCoreContext} attribute
710+
* {@link org.apache.hc.client5.http.protocol.H2RequestPriority#ATTR_HTTP2_PRIORITY_VALUE}.
711+
* </p>
712+
* <p><b>Experimental:</b> API and behavior may change.</p>
713+
*
714+
* @return this builder
715+
* @see org.apache.hc.client5.http.protocol.H2RequestPriority
716+
* @since 5.6
717+
*/
718+
public H2AsyncClientBuilder enablePriorityHeader() {
719+
addRequestInterceptorLast(H2RequestPriority.INSTANCE);
720+
return this;
721+
}
722+
723+
702724
/**
703725
* Request exec chain customization and extension.
704726
* <p>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.client5.http.protocol;
29+
30+
import java.io.IOException;
31+
32+
import org.apache.hc.core5.annotation.Contract;
33+
import org.apache.hc.core5.annotation.Experimental;
34+
import org.apache.hc.core5.annotation.ThreadingBehavior;
35+
import org.apache.hc.core5.http.EntityDetails;
36+
import org.apache.hc.core5.http.Header;
37+
import org.apache.hc.core5.http.HttpException;
38+
import org.apache.hc.core5.http.HttpHeaders;
39+
import org.apache.hc.core5.http.HttpRequest;
40+
import org.apache.hc.core5.http.HttpRequestInterceptor;
41+
import org.apache.hc.core5.http.HttpVersion;
42+
import org.apache.hc.core5.http.ProtocolVersion;
43+
import org.apache.hc.core5.http.protocol.HttpContext;
44+
import org.apache.hc.core5.http.protocol.HttpCoreContext;
45+
import org.apache.hc.core5.http2.priority.PriorityFormatter;
46+
import org.apache.hc.core5.http2.priority.PriorityValue;
47+
import org.apache.hc.core5.util.Args;
48+
49+
/**
50+
* Emits {@code Priority} request header for HTTP/2+.
51+
* <p>
52+
* The priority value is taken from the request context attribute
53+
* {@link #ATTR_HTTP2_PRIORITY_VALUE}. If the formatted value equals
54+
* RFC defaults (u=3, i=false) the header is omitted.
55+
* <p>
56+
* If {@code overwrite} is {@code false} (default), an existing {@code Priority}
57+
* header set by the caller is preserved.
58+
*
59+
* @since 5.6
60+
*/
61+
@Experimental
62+
@Contract(threading = ThreadingBehavior.IMMUTABLE)
63+
public final class H2RequestPriority implements HttpRequestInterceptor {
64+
65+
/**
66+
* Context attribute to carry a {@link PriorityValue}.
67+
*/
68+
public static final String ATTR_HTTP2_PRIORITY_VALUE = "http2.priority.value";
69+
70+
/**
71+
* Singleton with {@code overwrite=false}.
72+
*/
73+
public static final H2RequestPriority INSTANCE = new H2RequestPriority(false);
74+
75+
private final boolean overwrite;
76+
77+
public H2RequestPriority() {
78+
this(false);
79+
}
80+
81+
public H2RequestPriority(final boolean overwrite) {
82+
this.overwrite = overwrite;
83+
}
84+
85+
@Override
86+
public void process(final HttpRequest request, final EntityDetails entity,
87+
final HttpContext context) throws HttpException, IOException {
88+
89+
Args.notNull(request, "HTTP request");
90+
Args.notNull(context, "HTTP context");
91+
92+
final ProtocolVersion ver = HttpCoreContext.cast(context).getProtocolVersion();
93+
if (ver == null || ver.compareToVersion(HttpVersion.HTTP_2) < 0) {
94+
return; // only for HTTP/2+
95+
}
96+
97+
final Header existing = request.getFirstHeader(HttpHeaders.PRIORITY);
98+
if (existing != null && !overwrite) {
99+
return; // respect caller-set header
100+
}
101+
102+
final PriorityValue pv = HttpCoreContext.cast(context)
103+
.getAttribute(ATTR_HTTP2_PRIORITY_VALUE, PriorityValue.class);
104+
if (pv == null) {
105+
return;
106+
}
107+
108+
final String value = PriorityFormatter.format(pv);
109+
if (value == null || value.trim().isEmpty()) {
110+
if (overwrite && request.getFirstHeader(HttpHeaders.PRIORITY) != null) {
111+
request.removeHeaders(HttpHeaders.PRIORITY);
112+
}
113+
return;
114+
}
115+
116+
if (overwrite && request.getFirstHeader(HttpHeaders.PRIORITY) != null) {
117+
request.removeHeaders(HttpHeaders.PRIORITY);
118+
}
119+
request.addHeader(HttpHeaders.PRIORITY, value);
120+
}
121+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.http.examples;
28+
29+
import java.util.concurrent.Future;
30+
31+
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
32+
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
33+
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
34+
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
35+
import org.apache.hc.client5.http.impl.async.H2AsyncClientBuilder;
36+
import org.apache.hc.client5.http.protocol.H2RequestPriority;
37+
import org.apache.hc.client5.http.protocol.HttpClientContext;
38+
import org.apache.hc.core5.annotation.Experimental;
39+
import org.apache.hc.core5.http2.config.H2Config;
40+
import org.apache.hc.core5.http2.priority.PriorityValue;
41+
42+
@Experimental
43+
public class ClientH2PriorityHeaderExample {
44+
45+
public static void main(final String[] args) throws Exception {
46+
try (CloseableHttpAsyncClient client = H2AsyncClientBuilder.create()
47+
.setH2Config(H2Config.custom()
48+
.setPushEnabled(false)
49+
.build())
50+
.enablePriorityHeader() // wires H2RequestPriority into the chain
51+
.build()) {
52+
53+
client.start();
54+
55+
// --- Request 1: non-default priority -> header sent
56+
final HttpClientContext ctx1 = HttpClientContext.create();
57+
ctx1.setAttribute(H2RequestPriority.ATTR_HTTP2_PRIORITY_VALUE, PriorityValue.of(0, true));
58+
59+
final SimpleHttpRequest req1 = SimpleRequestBuilder.get("https://nghttp2.org/httpbin/headers").build();
60+
final Future<SimpleHttpResponse> f1 = client.execute(req1, ctx1, null);
61+
final SimpleHttpResponse r1 = f1.get();
62+
System.out.println("[/httpbin/headers] -> " + r1.getCode());
63+
System.out.println(r1.getBodyText());
64+
65+
// --- Request 2: defaults -> header omitted
66+
final HttpClientContext ctx2 = HttpClientContext.create();
67+
ctx2.setAttribute(H2RequestPriority.ATTR_HTTP2_PRIORITY_VALUE, PriorityValue.defaults());
68+
69+
final SimpleHttpRequest req2 = SimpleRequestBuilder.get("https://nghttp2.org/httpbin/user-agent").build();
70+
final SimpleHttpResponse r2 = client.execute(req2, ctx2, null).get();
71+
System.out.println("[/httpbin/user-agent] -> " + r2.getCode());
72+
System.out.println(r2.getBodyText());
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)