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

Introduce quarkus.http.header #21102

Merged
merged 2 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,30 @@ values:

NOTE: if you use `redirect` or `disabled` and have not added a SSL certificate or keystore, your server will not start!

== Additional HTTP Headers

To enable HTTP headers to be sent on every response, add the following properties:

[source, properties]
----
quarkus.http.header."X-Content-Type-Options".value=nosniff
----

This will include the `X-Content-Type-Options: nosniff` HTTP Header on responses for requests performed on any resource in the application.

You can also specify a `path` pattern and the HTTP `methods` the header needs to be applied:

[source, properties]
----
quarkus.http.header.Pragma.value=no-cache
quarkus.http.header.Pragma.path=/headers/pragma
quarkus.http.header.Pragma.methods=GET,HEAD
----

This will apply the `Pragma` header only when the `/headers/pragma` resource is called with a `GET` or a `HEAD` method

include::{generated-dir}/config/quarkus-vertx-http-config-group-header-config.adoc[leveloffset=+1, opts=optional]

== HTTP/2 Support

HTTP/2 is enabled by default, and will be used by browsers if SSL is in use on JDK11 or higher. JDK8 does not support
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.vertx.http.runtime;

import java.util.List;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

/**
* Configuration that allows for setting an HTTP header
*/
@ConfigGroup
public class HeaderConfig {

/**
* The path this header should be applied
*/
@ConfigItem(defaultValue = "/*")
public String path;

/**
* The value for this header configuration
*/
@ConfigItem
public String value;

/**
* The HTTP methods for this header configuration
*/
@ConfigItem
public Optional<List<String>> methods;
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ public class HttpConfiguration {
@ConfigItem
public Optional<PayloadHint> unhandledErrorContentTypeDefault;

/**
* Additional HTTP Headers always sent in the response
*/
@ConfigItem
public Map<String, HeaderConfig> header;

public ProxyConfig proxy;

public int determinePort(LaunchMode launchMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -227,7 +228,8 @@ public static void startServerAfterFailedStart() {
doServerStart(vertx, buildConfig, config, LaunchMode.DEVELOPMENT, new Supplier<Integer>() {
@Override
public Integer get() {
return ProcessorInfo.availableProcessors() * 2; //this is dev mode, so the number of IO threads not always being 100% correct does not really matter in this case
return ProcessorInfo.availableProcessors()
* 2; //this is dev mode, so the number of IO threads not always being 100% correct does not really matter in this case
}
}, null, false);
} catch (Exception e) {
Expand Down Expand Up @@ -360,6 +362,38 @@ public void handle(Void e) {
}
});
}
// Headers sent on any request, regardless of the response
Map<String, HeaderConfig> headers = httpConfiguration.header;
if (!headers.isEmpty()) {
// Creates a handler for each header entry
for (Map.Entry<String, HeaderConfig> entry : headers.entrySet()) {
var name = entry.getKey();
var config = entry.getValue();
if (config.methods.isEmpty()) {
httpRouteRouter.route(config.path)
.order(Integer.MIN_VALUE)
.handler(new Handler<RoutingContext>() {
@Override
public void handle(RoutingContext event) {
event.response().headers().add(name, config.value);
event.next();
}
});
} else {
for (String method : config.methods.get()) {
httpRouteRouter.route(HttpMethod.valueOf(method.toUpperCase(Locale.ROOT)), config.path)
.order(Integer.MIN_VALUE)
.handler(new Handler<RoutingContext>() {
@Override
public void handle(RoutingContext event) {
event.response().headers().add(name, config.value);
event.next();
}
});
}
}
}
}

Handler<HttpServerRequest> root;
if (rootPath.equals("/")) {
Expand Down Expand Up @@ -564,30 +598,30 @@ public void handle(AsyncResult<Void> event) {
private static void setHttpServerTiming(InsecureRequests insecureRequests, HttpServerOptions httpServerOptions,
HttpServerOptions sslConfig,
HttpServerOptions domainSocketOptions, boolean auxiliaryApplication) {
String serverListeningMessage = "Listening on: ";
StringBuilder serverListeningMessage = new StringBuilder("Listening on: ");
int socketCount = 0;

if (httpServerOptions != null && !InsecureRequests.DISABLED.equals(insecureRequests)) {
serverListeningMessage += String.format(
"http://%s:%s", httpServerOptions.getHost(), actualHttpPort);
serverListeningMessage.append(String.format(
"http://%s:%s", httpServerOptions.getHost(), actualHttpPort));
socketCount++;
}

if (sslConfig != null) {
if (socketCount > 0) {
serverListeningMessage += " and ";
serverListeningMessage.append(" and ");
}
serverListeningMessage += String.format("https://%s:%s", sslConfig.getHost(), actualHttpsPort);
serverListeningMessage.append(String.format("https://%s:%s", sslConfig.getHost(), actualHttpsPort));
socketCount++;
}

if (domainSocketOptions != null) {
if (socketCount > 0) {
serverListeningMessage += " and ";
serverListeningMessage.append(" and ");
}
serverListeningMessage += String.format("unix:%s", domainSocketOptions.getHost());
serverListeningMessage.append(String.format("unix:%s", domainSocketOptions.getHost()));
}
Timing.setHttpServer(serverListeningMessage, auxiliaryApplication);
Timing.setHttpServer(serverListeningMessage.toString(), auxiliaryApplication);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.it.vertx;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path("/headers")
public class HeaderResource {

@GET
@Path("/any")
public String headers() {
return "ok";
}

@GET
@Path("/pragma")
public String pragmaHeaderMustBeSet() {
return "ok";
}

@GET
@Path("/override")
public Response headersOverride() {
return Response.ok("ok").header("foo", "abc").build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ quarkus.http.access-log.enabled=true
quarkus.http.access-log.log-to-file=true
quarkus.http.access-log.base-file-name=quarkus-access-log
quarkus.http.access-log.log-directory=target
quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks,-H:EnableURLProtocols=http\\,https
quarkus.http.header.foo.value=bar
quarkus.http.header.Pragma.value=no-cache
quarkus.http.header.Pragma.path=/headers/pragma
quarkus.http.header.Pragma.methods=GET,HEAD
quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks,-H:EnableURLProtocols=http\\,https
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.quarkus.it.vertx;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class HeadersTestCase {

@Test
void testAdditionalHeaders() {
given()
.get("/headers/any")
.then()
.header("foo", is("bar"))
.header("Pragma", emptyOrNullString())
.body(is("ok"));

}

@Test
void testAdditionalHeadersOverride() {
given()
.get("/headers/override")
.then()
.header("foo", is("abc"))
.header("Pragma", emptyOrNullString())
.body(is("ok"));
}

@Test
void testPragmaIsSent() {
given()
.get("/headers/pragma")
.then()
.header("foo", is("bar"))
.header("Pragma", is("no-cache"))
.body(is("ok"));

}

}