io.helidon.http.ServerRequestHeaders Maven / Gradle / Ivy
Show all versions of helidon-http Show documentation
/*
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.http;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import io.helidon.common.media.type.MediaType;
import io.helidon.common.media.type.MediaTypes;
import io.helidon.common.parameters.Parameters;
/**
* HTTP headers of a server request.
*/
public interface ServerRequestHeaders extends Headers {
/**
* Header value of the non compliant {@code Accept} header sent by
* {@link java.net.HttpURLConnection} when none is set.
*
* @see JDK-8163921
*/
Header HUC_ACCEPT_DEFAULT = HeaderValues.create(HeaderNames.ACCEPT,
"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");
/**
* Accepted types for {@link #HUC_ACCEPT_DEFAULT}.
*/
List HUC_ACCEPT_DEFAULT_TYPES = List.of(
HttpMediaType.create(MediaTypes.TEXT_HTML),
HttpMediaType.create(MediaTypes.create("image", "gif")),
HttpMediaType.create(MediaTypes.create("image", "jpeg")),
HttpMediaType.builder().mediaType(MediaTypes.WILDCARD)
.q(0.2)
.build());
/**
* Create a new instance from headers.
*
* @param headers headers parsed from server request
* @return immutable instance of request headers
*/
static ServerRequestHeaders create(Headers headers) {
return new ServerRequestHeadersImpl(headers);
}
/**
* Create new empty server request headers.
*
* @return empty headers
*/
static ServerRequestHeaders create() {
return new ServerRequestHeadersImpl(WritableHeaders.create());
}
/**
* Optionally returns a value of {@link HeaderNames#IF_MODIFIED_SINCE} header.
*
* Allows a 304 Not Modified to be returned if content is unchanged.
*
* @return Content of {@link HeaderNames#IF_MODIFIED_SINCE} header.
*/
default Optional ifModifiedSince() {
if (contains(HeaderNames.IF_MODIFIED_SINCE)) {
return Optional.of(get(HeaderNames.IF_MODIFIED_SINCE))
.map(Header::get)
.map(DateTime::parse);
}
return Optional.empty();
}
/**
* Optionally returns a value of {@link HeaderNames#IF_UNMODIFIED_SINCE} header.
*
* Only send the response if the entity has not been modified since a specific time.
*
* @return Content of {@link HeaderNames#IF_UNMODIFIED_SINCE} header.
*/
default Optional ifUnmodifiedSince() {
if (contains(HeaderNames.IF_UNMODIFIED_SINCE)) {
return Optional.of(get(HeaderNames.IF_UNMODIFIED_SINCE))
.map(Header::get)
.map(DateTime::parse);
}
return Optional.empty();
}
/**
* Test if the given media type is acceptable as a response for this request.
* A media type is accepted if the {@code Accept} header is not present in the
* request or if it contains the provided media type.
*
* @param mediaType the media type to test
* @return {@code true} if provided type is acceptable, {@code false} otherwise
* @throws NullPointerException if the provided type is {@code null}.
*/
default boolean isAccepted(MediaType mediaType) {
List accepted = acceptedTypes();
if (accepted.isEmpty()) {
return true;
}
for (HttpMediaType acceptedType : accepted) {
if (acceptedType.test(mediaType)) {
return true;
}
}
return false;
}
/**
* Optionally returns a single media type from the given media types that is the
* best one accepted by the client.
* Method uses content negotiation {@link HeaderNames#ACCEPT}
* header parameter and returns an empty value in case nothing matches.
*
* @param mediaTypes media type candidates, never null
* @return an accepted media type.
*/
default Optional bestAccepted(MediaType... mediaTypes) {
if (mediaTypes.length == 0) {
return Optional.empty();
}
try {
List accepted = acceptedTypes();
if (accepted.isEmpty()) {
return Optional.of(mediaTypes[0]);
}
double best = 0;
MediaType result = null;
for (MediaType mt : mediaTypes) {
for (HttpMediaType acc : accepted) {
double q = acc.qualityFactor();
if (q > best && acc.test(mt)) {
if (q == 1) {
return Optional.of(mt);
} else {
best = q;
result = mt;
}
}
}
}
return Optional.ofNullable(result);
} catch (IllegalArgumentException e) {
throw new BadRequestException("Unable to parse Accept header", e);
}
}
/**
* Returns cookies (parsed from '{@code Cookie:}' header) based on RFC6265.
* It parses also older formats including RFC2965 but skips parameters. Only cookie {@code name} and {@code value}
* is returned.
*
* Multiple cookies can be returned in a single headers and a single cookie-name can have multiple values.
* Note that base on RFC6265 an order of cookie values has no semantics.
*
* @return An unmodifiable cookies represented by {@link Parameters} interface where key is a name of the cookie and
* values are cookie values.
*/
default Parameters cookies() {
if (contains(HeaderNames.COOKIE)) {
return CookieParser.parse(get(HeaderNames.COOKIE));
} else {
return CookieParser.empty();
}
}
/**
* Optionally returns acceptedTypes version in time ({@link HeaderNames#ACCEPT_DATETIME} header).
*
* @return Acceptable version in time.
*/
default Optional acceptDatetime() {
if (contains(HeaderNames.ACCEPT_DATETIME)) {
return Optional.of(get(HeaderNames.ACCEPT_DATETIME))
.map(Header::get)
.map(DateTime::parse);
}
return Optional.empty();
}
/**
* Optionally returns request date ({@link HeaderNames#DATE} header).
*
* @return Request date.
*/
default Optional date() {
if (contains(HeaderNames.DATE)) {
return Optional.of(get(HeaderNames.DATE))
.map(Header::get)
.map(DateTime::parse);
}
return Optional.empty();
}
/**
* Optionally returns the address of the previous web page (header {@link HeaderNames#REFERER}) from which a link
* to the currently requested page was followed.
*
* The word {@code referrer} has been misspelled in the RFC as well as in most implementations to the point that it
* has become standard usage and is considered correct terminology
*
* @return Referrers URI
*/
default Optional referer() {
if (contains(HeaderNames.REFERER)) {
return Optional.of(get(HeaderNames.REFERER))
.map(Header::get)
.map(URI::create);
}
return Optional.empty();
}
}