Skip to content

Commit 03daf27

Browse files
authored
Header and Media type API consistency (helidon-io#7351)
* Rename `Header` to `HeaderNames` * Rename `HeaderValue` to `Header` * Rename `HeaderValues` to `Headers` * Added `from(Headers)` method * HttpMediaTypes now hosts only constants with parameters * Removed Http.Version, as it is no longer needed (it is a String, not an enum) * Fixes in archetypes to use new Header API * Fixes in HTTP template for APT to use new Header API
1 parent 7b05975 commit 03daf27

255 files changed

Lines changed: 1962 additions & 1695 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.

archetypes/helidon/src/main/archetype/common/media.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
<list key="Main-routing-builder" if="${flavor} == 'nima'">
181181
<value order="1"><![CDATA[.any("/", (req, res) -> {
182182
res.status(Http.Status.MOVED_PERMANENTLY_301);
183-
res.header(Header.createCached(Header.LOCATION, "/ui"));
183+
res.header(Http.Headers.createCached(Http.HeaderNames.LOCATION, "/ui"));
184184
res.send();
185185
})
186186
.register("/ui", StaticContentService.builder("WEB")

archetypes/helidon/src/main/archetype/mp/custom/files/src/test/java/__pkg__/TestCORS.java.mustache

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,46 +29,46 @@ class TestCORS {
2929
void testAnonymousGreetWithCors() {
3030
Response r = target.path("/simple-greet")
3131
.request()
32-
.header(Http.Header.ORIGIN.defaultCase(), "http://foo.com")
33-
.header(Http.Header.HOST.defaultCase(), "here.com")
32+
.header(Http.HeaderNames.ORIGIN.defaultCase(), "http://foo.com")
33+
.header(Http.HeaderNames.HOST.defaultCase(), "here.com")
3434
.get();
3535
3636
assertThat("HTTP response", r.getStatus(), is(200));
3737
String payload = fromPayload(r);
3838
assertThat("HTTP response payload", payload.contains("Hello World!"), is(true));
3939
assertThat("CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN,
40-
r.getHeaders().getFirst(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN.defaultCase()),
40+
r.getHeaders().getFirst(Http.HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.defaultCase()),
4141
is("http://foo.com"));
4242
}
4343

4444
@Test
4545
void testCustomGreetingWithCors() {
4646
Response r = target.path("/simple-greet")
4747
.request()
48-
.header(Http.Header.ORIGIN.defaultCase(), "http://foo.com")
49-
.header(Http.Header.HOST.defaultCase(), "here.com")
48+
.header(Http.HeaderNames.ORIGIN.defaultCase(), "http://foo.com")
49+
.header(Http.HeaderNames.HOST.defaultCase(), "here.com")
5050
.header("Access-Control-Request-Method", "PUT")
5151
.options();
5252
5353
assertThat("pre-flight status", r.getStatus(), is(200));
5454
MultivaluedMap<String, Object> responseHeaders = r.getHeaders();
5555
assertThat("Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS,
56-
r.getHeaders().getFirst(Http.Header.ACCESS_CONTROL_ALLOW_METHODS.defaultCase()),
56+
r.getHeaders().getFirst(Http.HeaderNames.ACCESS_CONTROL_ALLOW_METHODS.defaultCase()),
5757
is("PUT"));
5858
assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN,
59-
r.getHeaders().getFirst(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN.defaultCase()),
59+
r.getHeaders().getFirst(Http.HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.defaultCase()),
6060
is("http://foo.com"));
6161
6262
Invocation.Builder builder = target.path("/simple-greet")
6363
.request()
6464
.headers(responseHeaders)
65-
.header(Http.Header.ORIGIN.defaultCase(), "http://foo.com")
66-
.header(Http.Header.HOST.defaultCase(), "here.com");
65+
.header(Http.HeaderNames.ORIGIN.defaultCase(), "http://foo.com")
66+
.header(Http.HeaderNames.HOST.defaultCase(), "here.com");
6767
6868
r = putResponse("Cheers", builder);
6969
assertThat("HTTP response3", r.getStatus(), is(200));
7070
assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN,
71-
r.getHeaders().getFirst(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN.defaultCase()),
71+
r.getHeaders().getFirst(Http.HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.defaultCase()),
7272
is("http://foo.com"));
7373
assertThat(fromPayload(r), containsString("Cheers World!"));
7474
}
@@ -77,8 +77,8 @@ class TestCORS {
7777
void testGreetingChangeWithCorsAndOtherOrigin() {
7878
Invocation.Builder builder = target.path("/simple-greet")
7979
.request()
80-
.header(Http.Header.ORIGIN.defaultCase(), "http://other.com")
81-
.header(Http.Header.HOST.defaultCase(), "here.com");
80+
.header(Http.HeaderNames.ORIGIN.defaultCase(), "http://other.com")
81+
.header(Http.HeaderNames.HOST.defaultCase(), "here.com");
8282
8383
Response r = putResponse("Ahoy", builder);
8484
boolean isOverriding = Config.create().get("cors").exists();

archetypes/helidon/src/main/archetype/nima/custom/files/src/main/java/__pkg__/FileService.java.multipart.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import static io.helidon.common.http.Http.Status.NOT_FOUND_404;
3333
* File service.
3434
*/
3535
final class FileService implements HttpService {
36-
private static final Http.HeaderValue UI_LOCATION = Header.createCached(Header.LOCATION, "/ui");
36+
private static final Http.Header UI_LOCATION = Http.Headers.createCached(Http.HeaderNames.LOCATION, "/ui");
3737
private final JsonBuilderFactory jsonFactory;
3838
private final Path storage;
3939

common/http/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
HTTP
2+
----
3+
4+
# HTTP Header name and value
5+
6+
Abstraction of a single header name (`Connection`), or a header value (`Connection: keep-alive`).
7+
8+
9+
## Types
10+
11+
- `Http.HeaderName` - abstraction of a name of a header (such as `Content-Length`)
12+
- `Http.HeaderNames` - constants with "known" header names (such as `HeaderNames.CONTENT_LENGTH`)
13+
- `Http.Header` - abstraction of a header with a value (such as `Content-Length: 0`)
14+
- `Http.Headers` - constants with commonly used headers with values (such as `Headers.CONTENT_LENGTH_ZERO`)
15+
16+
Internal types:
17+
- `Http.HeaderNameEnum` - "known" headers, optimized for performance in header containers
18+
- `Http.HeaderNameImpl` - custom headers
19+
20+
## Factory methods
21+
22+
Factory methods to create header names and values are located on their relevant type that contains constants (aligned
23+
with how we treat `MediaTypes`:
24+
25+
Header names:
26+
1. `Http.HeaderNames.create(String)` - create a header name from the provided name (uses known header if possible)
27+
2. `Http.HeaderNames.create(String, String)` - create a header name (optimized) with lower case and name
28+
3. `Http.HeaderNames.createFromLowercase(String)` - create a header name when we have a guaranteed lowercase name (such as in HTTP/2)
29+
30+
Headers (name and value):
31+
1. `Http.Headers.create(....)` - create a header with the provided name and value
32+
2. `Http.Headers.createCached(...)` - create a header that caches its HTTP/1.1 bytes (optimization for heavily used headers)
33+
34+
Methods with `changing` and `sensitive` - these options are used for HTTP/2 (and HTTP/3) to correctly cache names/values when talking over the network
35+
36+
# HTTP Header containers
37+
38+
Abstraction of a collection of headers, as used in server and client requests and responses.
39+
The containers are optimized for read and write speed by using an array for "known" headers (Headers that are part of our
40+
`HeaderNameEnum`). Other headers are stored in a map, keyed by `HeaderName`.
41+
42+
## Types
43+
44+
- `Headers` - a collection of HTTP headers that is read-only
45+
- `WritableHeader` - a collection of HTTP headers that is mutable
46+
- `ClientRequestHeaders` - writable headers to create client request
47+
- `ClientResponseHeaders` - read-only headers with response from the server
48+
- `ServerRequestHeaders` - read-only headers with request from client
49+
- `ServerResponseHeaders` - writable header to create server response

common/http/src/main/java/io/helidon/common/http/ClientRequestHeaders.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ default ClientRequestHeaders accept(MediaType... accepted) {
5656
MediaType mediaType = accepted[i];
5757
values[i] = mediaType.text();
5858
}
59-
set(Http.Header.create(Http.Header.ACCEPT, values));
59+
set(Http.Headers.create(Http.HeaderNames.ACCEPT, values));
6060
return this;
6161
}
6262
}

common/http/src/main/java/io/helidon/common/http/ClientRequestHeadersImpl.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,9 +23,7 @@
2323
import java.util.function.Consumer;
2424
import java.util.function.Supplier;
2525

26-
import io.helidon.common.http.Http.Header;
2726
import io.helidon.common.http.Http.HeaderName;
28-
import io.helidon.common.http.Http.HeaderValue;
2927

3028
/**
3129
* Client request headers.
@@ -50,12 +48,12 @@ public boolean contains(HeaderName name) {
5048
}
5149

5250
@Override
53-
public boolean contains(HeaderValue headerWithValue) {
51+
public boolean contains(Http.Header headerWithValue) {
5452
return delegate.contains(headerWithValue);
5553
}
5654

5755
@Override
58-
public HeaderValue get(HeaderName name) {
56+
public Http.Header get(HeaderName name) {
5957
return delegate.get(name);
6058
}
6159

@@ -67,8 +65,8 @@ public int size() {
6765
@Override
6866
public List<HttpMediaType> acceptedTypes() {
6967
if (mediaTypes == null) {
70-
if (delegate.contains(Header.ACCEPT)) {
71-
List<String> accepts = delegate.get(Header.ACCEPT).allValues(true);
68+
if (delegate.contains(Http.HeaderNames.ACCEPT)) {
69+
List<String> accepts = delegate.get(Http.HeaderNames.ACCEPT).allValues(true);
7270

7371
List<HttpMediaType> mediaTypes = new ArrayList<>(accepts.size());
7472
for (String accept : accepts) {
@@ -85,13 +83,13 @@ public List<HttpMediaType> acceptedTypes() {
8583
}
8684

8785
@Override
88-
public ClientRequestHeaders setIfAbsent(HeaderValue header) {
86+
public ClientRequestHeaders setIfAbsent(Http.Header header) {
8987
delegate.setIfAbsent(header);
9088
return this;
9189
}
9290

9391
@Override
94-
public ClientRequestHeaders add(HeaderValue header) {
92+
public ClientRequestHeaders add(Http.Header header) {
9593
delegate.add(header);
9694
return this;
9795
}
@@ -103,19 +101,19 @@ public ClientRequestHeaders remove(HeaderName name) {
103101
}
104102

105103
@Override
106-
public ClientRequestHeaders remove(HeaderName name, Consumer<HeaderValue> removedConsumer) {
104+
public ClientRequestHeaders remove(HeaderName name, Consumer<Http.Header> removedConsumer) {
107105
delegate.remove(name, removedConsumer);
108106
return this;
109107
}
110108

111109
@Override
112-
public ClientRequestHeaders set(HeaderValue header) {
110+
public ClientRequestHeaders set(Http.Header header) {
113111
delegate.set(header);
114112
return this;
115113
}
116114

117115
@Override
118-
public Iterator<HeaderValue> iterator() {
116+
public Iterator<Http.Header> iterator() {
119117
return delegate.iterator();
120118
}
121119

@@ -129,4 +127,10 @@ public ClientRequestHeaders clear() {
129127
delegate.clear();
130128
return this;
131129
}
130+
131+
@Override
132+
public ClientRequestHeaders from(Headers headers) {
133+
headers.forEach(this::set);
134+
return this;
135+
}
132136
}

common/http/src/main/java/io/helidon/common/http/ClientResponseHeaders.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@
2323
import java.util.Optional;
2424

2525
import io.helidon.common.http.Http.DateTime;
26-
import io.helidon.common.http.Http.HeaderValue;
2726
import io.helidon.common.media.type.ParserMode;
2827

29-
import static io.helidon.common.http.Http.Header.ACCEPT_PATCH;
30-
import static io.helidon.common.http.Http.Header.EXPIRES;
31-
import static io.helidon.common.http.Http.Header.LAST_MODIFIED;
32-
import static io.helidon.common.http.Http.Header.LOCATION;
28+
import static io.helidon.common.http.Http.HeaderNames.ACCEPT_PATCH;
29+
import static io.helidon.common.http.Http.HeaderNames.EXPIRES;
30+
import static io.helidon.common.http.Http.HeaderNames.LAST_MODIFIED;
31+
import static io.helidon.common.http.Http.HeaderNames.LOCATION;
3332

3433
/**
3534
* HTTP Headers of a client response.
@@ -72,7 +71,7 @@ default List<HttpMediaType> acceptPatches() {
7271
}
7372

7473
/**
75-
* Optionally gets the value of {@link io.helidon.common.http.Http.Header#LOCATION} header.
74+
* Optionally gets the value of {@link io.helidon.common.http.Http.HeaderNames#LOCATION} header.
7675
* <p>
7776
* Used in redirection, or when a new resource has been created.
7877
*
@@ -81,14 +80,14 @@ default List<HttpMediaType> acceptPatches() {
8180
default Optional<URI> location() {
8281
if (contains(LOCATION)) {
8382
return Optional.of(get(LOCATION))
84-
.map(HeaderValue::value)
83+
.map(Http.Header::value)
8584
.map(URI::create);
8685
}
8786
return Optional.empty();
8887
}
8988

9089
/**
91-
* Optionally gets the value of {@link io.helidon.common.http.Http.Header#LAST_MODIFIED} header.
90+
* Optionally gets the value of {@link io.helidon.common.http.Http.HeaderNames#LAST_MODIFIED} header.
9291
* <p>
9392
* The last modified date for the requested object.
9493
*
@@ -97,14 +96,14 @@ default Optional<URI> location() {
9796
default Optional<ZonedDateTime> lastModified() {
9897
if (contains(LAST_MODIFIED)) {
9998
return Optional.of(get(LAST_MODIFIED))
100-
.map(HeaderValue::value)
99+
.map(Http.Header::value)
101100
.map(DateTime::parse);
102101
}
103102
return Optional.empty();
104103
}
105104

106105
/**
107-
* Optionally gets the value of {@link io.helidon.common.http.Http.Header#EXPIRES} header.
106+
* Optionally gets the value of {@link io.helidon.common.http.Http.HeaderNames#EXPIRES} header.
108107
* <p>
109108
* Gives the date/time after which the response is considered stale.
110109
*
@@ -113,7 +112,7 @@ default Optional<ZonedDateTime> lastModified() {
113112
default Optional<ZonedDateTime> expires() {
114113
if (contains(EXPIRES)) {
115114
return Optional.of(get(EXPIRES))
116-
.map(HeaderValue::value)
115+
.map(Http.Header::value)
117116
.map(DateTime::parse);
118117
}
119118
return Optional.empty();

common/http/src/main/java/io/helidon/common/http/ClientResponseHeadersImpl.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ public boolean contains(Http.HeaderName name) {
4343
}
4444

4545
@Override
46-
public boolean contains(Http.HeaderValue headerWithValue) {
46+
public boolean contains(Http.Header headerWithValue) {
4747
return headers.contains(headerWithValue);
4848
}
4949

5050
@Override
51-
public Http.HeaderValue get(Http.HeaderName name) {
51+
public Http.Header get(Http.HeaderName name) {
5252
return headers.get(name);
5353
}
5454

5555
@Override
5656
public Optional<HttpMediaType> contentType() {
5757
if (parserMode == ParserMode.RELAXED) {
58-
return contains(HeaderEnum.CONTENT_TYPE)
59-
? Optional.of(HttpMediaType.create(get(HeaderEnum.CONTENT_TYPE).value(), parserMode))
58+
return contains(HeaderNameEnum.CONTENT_TYPE)
59+
? Optional.of(HttpMediaType.create(get(HeaderNameEnum.CONTENT_TYPE).value(), parserMode))
6060
: Optional.empty();
6161
}
6262
return headers.contentType();
@@ -68,7 +68,7 @@ public int size() {
6868
}
6969

7070
@Override
71-
public Iterator<Http.HeaderValue> iterator() {
71+
public Iterator<Http.Header> iterator() {
7272
return headers.iterator();
7373
}
7474

common/http/src/main/java/io/helidon/common/http/ContentDisposition.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@
4747
* </li>
4848
* </ul>
4949
*/
50-
public class ContentDisposition implements Http.HeaderValue {
50+
public class ContentDisposition implements Http.Header {
5151
private static final String NAME_PARAMETER = "name";
5252
private static final String FILENAME_PARAMETER = "filename";
5353
private static final String CREATION_DATE_PARAMETER = "creation-date";
@@ -126,12 +126,12 @@ public static ContentDisposition empty() {
126126

127127
@Override
128128
public String name() {
129-
return Http.Header.CONTENT_DISPOSITION.defaultCase();
129+
return Http.HeaderNames.CONTENT_DISPOSITION.defaultCase();
130130
}
131131

132132
@Override
133133
public Http.HeaderName headerName() {
134-
return Http.Header.CONTENT_DISPOSITION;
134+
return Http.HeaderNames.CONTENT_DISPOSITION;
135135
}
136136

137137
@Override

common/http/src/main/java/io/helidon/common/http/CookieParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ private CookieParser() {
4343
* @param httpHeader cookie header
4444
* @return a cookie name and values parsed into a parameter format.
4545
*/
46-
static Parameters parse(Http.HeaderValue httpHeader) {
46+
static Parameters parse(Http.Header httpHeader) {
4747
Map<String, List<String>> allCookies = new HashMap<>();
4848
for (String value : httpHeader.allValues()) {
4949
parse(allCookies, value);

0 commit comments

Comments
 (0)