summaryrefslogtreecommitdiffstats
path: root/container-core
diff options
context:
space:
mode:
Diffstat (limited to 'container-core')
-rw-r--r--container-core/abi-spec.json6
-rw-r--r--container-core/src/main/java/com/yahoo/container/jdisc/HttpRequestBuilder.java21
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java6
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java21
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApi.java3
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiException.java2
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java12
-rw-r--r--container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def3
-rw-r--r--container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java14
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java36
10 files changed, 112 insertions, 12 deletions
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index aae5ea31fbb..d07d21ae71f 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -660,6 +660,10 @@
"public com.yahoo.container.jdisc.HttpRequestBuilder withRequestContent(java.io.InputStream)",
"public com.yahoo.container.jdisc.HttpRequestBuilder withScheme(java.lang.String)",
"public com.yahoo.container.jdisc.HttpRequestBuilder withHostname(java.lang.String)",
+ "public com.yahoo.container.jdisc.HttpRequestBuilder withPrincipal(java.security.Principal)",
+ "public com.yahoo.container.jdisc.HttpRequestBuilder withRemoteAddress(java.net.SocketAddress)",
+ "public com.yahoo.container.jdisc.HttpRequestBuilder withAttribute(java.lang.String, java.lang.Object)",
+ "public com.yahoo.container.jdisc.HttpRequestBuilder withPort(int)",
"public com.yahoo.container.jdisc.HttpRequest build()"
],
"fields" : [ ]
@@ -1089,6 +1093,7 @@
"public com.yahoo.jdisc.http.ConnectorConfig$Builder maxContentSizeErrorMessageTemplate(java.lang.String)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder reuseAddress(boolean)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder idleTimeout(double)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$Builder shutdownIdleTimeout(double)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder tcpKeepAliveEnabled(boolean)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder tcpNoDelay(boolean)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder throttling(com.yahoo.jdisc.http.ConnectorConfig$Throttling$Builder)",
@@ -1475,6 +1480,7 @@
"public java.lang.String maxContentSizeErrorMessageTemplate()",
"public boolean reuseAddress()",
"public double idleTimeout()",
+ "public double shutdownIdleTimeout()",
"public boolean tcpKeepAliveEnabled()",
"public boolean tcpNoDelay()",
"public com.yahoo.jdisc.http.ConnectorConfig$Throttling throttling()",
diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/HttpRequestBuilder.java b/container-core/src/main/java/com/yahoo/container/jdisc/HttpRequestBuilder.java
index a2d792e6ae0..147f388e08c 100644
--- a/container-core/src/main/java/com/yahoo/container/jdisc/HttpRequestBuilder.java
+++ b/container-core/src/main/java/com/yahoo/container/jdisc/HttpRequestBuilder.java
@@ -4,6 +4,8 @@ package com.yahoo.container.jdisc;
import com.yahoo.jdisc.http.HttpRequest.Method;
import java.io.InputStream;
+import java.net.SocketAddress;
+import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -19,9 +21,13 @@ public class HttpRequestBuilder {
private final String path;
private final Map<String, List<String>> queryParameters = new TreeMap<>();
private final Map<String, String> headers = new TreeMap<>();
+ private final Map<String, Object> attributes = new TreeMap<>();
private String scheme;
private String hostname;
private InputStream content;
+ private Principal principal;
+ private SocketAddress socketAddress;
+ private int port = -1;
private HttpRequestBuilder(Method method, String path) {
this.method = method;
@@ -43,10 +49,20 @@ public class HttpRequestBuilder {
public HttpRequestBuilder withHostname(String hostname) { this.hostname = hostname; return this; }
+ public HttpRequestBuilder withPrincipal(Principal p) { principal = p; return this; }
+
+ public HttpRequestBuilder withRemoteAddress(SocketAddress sa) { socketAddress = sa; return this; }
+
+ public HttpRequestBuilder withAttribute(String name, Object value) { attributes.put(name, value); return this; }
+
+ public HttpRequestBuilder withPort(int port) { this.port = port; return this; }
+
public HttpRequest build() {
String scheme = this.scheme != null ? this.scheme : "http";
String hostname = this.hostname != null ? this.hostname : "localhost";
- StringBuilder uriBuilder = new StringBuilder(scheme).append("://").append(hostname).append(path);
+ StringBuilder uriBuilder = new StringBuilder(scheme).append("://").append(hostname);
+ if (port > 0) uriBuilder.append(':').append(port);
+ uriBuilder.append(path);
if (queryParameters.size() > 0) {
uriBuilder.append('?');
queryParameters.forEach((name, values) -> {
@@ -66,6 +82,9 @@ public class HttpRequestBuilder {
request = HttpRequest.createTestRequest(uriBuilder.toString(), method);
}
headers.forEach((name, value) -> request.getJDiscRequest().headers().put(name, value));
+ if (principal != null) request.getJDiscRequest().setUserPrincipal(principal);
+ if (socketAddress != null) request.getJDiscRequest().setRemoteAddress(socketAddress);
+ request.getJDiscRequest().context().putAll(attributes);
return request;
}
}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
index 983adec034d..3159766981c 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
@@ -51,7 +51,11 @@ class JDiscServerConnector extends ServerConnector {
setName(config.name());
setAcceptQueueSize(config.acceptQueueSize());
setReuseAddress(config.reuseAddress());
- setIdleTimeout((long) (config.idleTimeout() * 1000));
+ long idleTimeout = (long)(config.idleTimeout() * 1000);
+ setIdleTimeout(idleTimeout);
+ long shutdownIdleTimeout = (long) (config.shutdownIdleTimeout() * 1000);
+ // Ensure shutdown idle timeout is less than idle timeout and stop timeout
+ setShutdownIdleTimeout(Math.min(shutdownIdleTimeout, Math.min(idleTimeout, server.getStopTimeout())));
}
public ConnectionStatistics getStatistics() {
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java
index a022d208d05..9fee54dd1d4 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java
@@ -1,7 +1,6 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.server.jetty;
-import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.handler.CompletionHandler;
import com.yahoo.jdisc.handler.ContentChannel;
import com.yahoo.jdisc.http.ConnectorConfig;
@@ -19,6 +18,8 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static com.yahoo.jdisc.Response.Status.REQUEST_TOO_LONG;
+
/**
* Finished when either
* 1) There was an error
@@ -111,7 +112,8 @@ class ServletRequestReader {
long maxContentSize = resolveMaxContentSize(cfg);
var msgTemplate = resolveMaxContentSizeErrorMessage(cfg);
this.requestContentChannel = maxContentSize >= 0
- ? new ByteLimitedContentChannel(Objects.requireNonNull(requestContentChannel), maxContentSize, msgTemplate)
+ ? new ByteLimitedContentChannel(
+ Objects.requireNonNull(requestContentChannel), maxContentSize, msgTemplate, req.getContentLengthLong())
: Objects.requireNonNull(requestContentChannel);
this.janitor = Objects.requireNonNull(janitor);
this.metricReporter = Objects.requireNonNull(metricReporter);
@@ -285,24 +287,29 @@ class ServletRequestReader {
private static class ByteLimitedContentChannel implements ContentChannel {
private final long maxContentSize;
private final String messageTemplate;
+ private final long contentLengthHeader;
private final AtomicLong bytesWritten = new AtomicLong();
private final ContentChannel delegate;
- ByteLimitedContentChannel(ContentChannel delegate, long maxContentSize, String messageTemplate) {
+ ByteLimitedContentChannel(ContentChannel delegate, long maxContentSize, String messageTemplate, long contentLengthHeader) {
this.delegate = delegate;
this.maxContentSize = maxContentSize;
this.messageTemplate = messageTemplate;
+ this.contentLengthHeader = contentLengthHeader;
}
@Override
public void write(ByteBuffer buf, CompletionHandler handler) {
long written = bytesWritten.addAndGet(buf.remaining());
- if (written > maxContentSize) {
+ if (contentLengthHeader != -1 && contentLengthHeader > maxContentSize) {
handler.failed(new RequestException(
- Response.Status.REQUEST_TOO_LONG, messageTemplate.formatted(written, maxContentSize)));
- return;
+ REQUEST_TOO_LONG, messageTemplate.formatted(contentLengthHeader, maxContentSize)));
+ } else if (written > maxContentSize) {
+ handler.failed(new RequestException(
+ REQUEST_TOO_LONG, messageTemplate.formatted(written, maxContentSize)));
+ } else {
+ delegate.write(buf, handler);
}
- delegate.write(buf, handler);
}
@Override public void close(CompletionHandler h) { delegate.close(h); }
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApi.java b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
index 18d8d8c49b4..ee5628988c9 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApi.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
@@ -15,6 +15,7 @@ import com.yahoo.security.tls.ConnectionAuthContext;
import javax.net.ssl.SSLSession;
import java.io.InputStream;
+import java.net.InetSocketAddress;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
@@ -153,6 +154,7 @@ public interface RestApi {
Principal userPrincipalOrThrow();
Optional<SSLSession> sslSession();
Optional<ConnectionAuthContext> connectionAuthContext();
+ InetSocketAddress remoteAddress();
interface Parameters {
Optional<String> getString(String name);
@@ -193,6 +195,7 @@ public interface RestApi {
interface FilterContext {
RequestContext requestContext();
String route();
+ void setPrincipal(Principal principal);
HttpResponse executeNext();
}
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiException.java b/container-core/src/main/java/com/yahoo/restapi/RestApiException.java
index e3acf4258f1..3a44bc4da5f 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiException.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiException.java
@@ -57,6 +57,7 @@ public class RestApiException extends RuntimeException {
}
public static class BadRequest extends RestApiException {
+ public BadRequest() { this("Bad request"); }
public BadRequest(String message) { this(message, null); }
public BadRequest(Throwable cause) { this(cause.getMessage(), cause); }
public BadRequest(String message, Throwable cause) { super(ErrorResponse::badRequest, message, cause); }
@@ -69,6 +70,7 @@ public class RestApiException extends RuntimeException {
}
public static class Forbidden extends RestApiException {
+ public Forbidden() { this("Forbidden"); }
public Forbidden(String message) { super(ErrorResponse::forbidden, message, null); }
public Forbidden(String message, Throwable cause) { super(ErrorResponse::forbidden, message, cause); }
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index 39dd17d3563..090e06c221f 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -21,6 +21,7 @@ import com.yahoo.security.tls.TransportSecurityUtils;
import javax.net.ssl.SSLSession;
import java.io.InputStream;
+import java.net.InetSocketAddress;
import java.net.URI;
import java.security.Principal;
import java.util.ArrayList;
@@ -76,7 +77,11 @@ class RestApiImpl implements RestApi {
resolvedRoute, requestContext, filters,
createFilterContextRecursive(resolvedRoute, requestContext, resolvedRoute.filters, null));
if (filterContext != null) {
- return filterContext.executeFirst();
+ try {
+ return filterContext.executeFirst();
+ } catch (RuntimeException e) {
+ return mapException(requestContext, e);
+ }
} else {
return dispatchToRoute(resolvedRoute, requestContext);
}
@@ -487,7 +492,7 @@ class RestApiImpl implements RestApi {
@Override public Optional<ConnectionAuthContext> connectionAuthContext() {
return sslSession().flatMap(TransportSecurityUtils::getConnectionAuthContext);
}
-
+ @Override public InetSocketAddress remoteAddress() { return (InetSocketAddress) request.getJDiscRequest().getRemoteAddress(); }
private class PathParametersImpl implements RestApi.RequestContext.PathParameters {
@Override
@@ -496,7 +501,7 @@ class RestApiImpl implements RestApi {
}
@Override public String getStringOrThrow(String name) {
return getString(name)
- .orElseThrow(() -> new RestApiException.BadRequest("Path parameter '" + name + "' is missing"));
+ .orElseThrow(() -> new RestApiException.NotFound("Path parameter '" + name + "' is missing"));
}
@Override public HttpURL.Path getFullPath() {
return pathMatcher.getPath();
@@ -554,6 +559,7 @@ class RestApiImpl implements RestApi {
@Override public RestApi.RequestContext requestContext() { return requestContext; }
@Override public String route() { return route.name != null ? route.name : route.pathPattern; }
+ @Override public void setPrincipal(Principal p) { requestContext.request.getJDiscRequest().setUserPrincipal(p); }
HttpResponse executeFirst() { return filter.filterRequest(this); }
diff --git a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
index d2f081fe7d5..5e59d998e86 100644
--- a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
+++ b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
@@ -33,6 +33,9 @@ reuseAddress bool default=true
# The maximum idle time for a connection, which roughly translates to the Socket.setSoTimeout(int).
idleTimeout double default=180.0
+# The idle timeout that takes effect during graceful shutdown of Jetty
+shutdownIdleTimeout double default=5.0
+
# TODO Vespa 9 Remove
# Has no effect since Jetty 11 upgrade
tcpKeepAliveEnabled bool default=false
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 6e218a6ab66..adb35db8ebf 100644
--- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -160,6 +160,20 @@ public class HttpServerTest {
}
@Test
+ void requireThatTooLargePayloadFailsWith413() throws Exception {
+ final JettyTestDriver driver = JettyTestDriver.newConfiguredInstance(
+ new EchoRequestHandler(),
+ new ServerConfig.Builder(),
+ new ConnectorConfig.Builder()
+ .maxContentSize(100));
+ driver.client().newPost("/status.html")
+ .setBinaryContent(new byte[200])
+ .execute()
+ .expectStatusCode(is(REQUEST_TOO_LONG));
+ assertTrue(driver.close());
+ }
+
+ @Test
void requireThatMultipleHostHeadersReturns400() throws Exception {
var metricConsumer = new MetricConsumerMock();
JettyTestDriver driver = JettyTestDriver.newConfiguredInstance(
diff --git a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
index bf0ccb95887..d27e04bbd7a 100644
--- a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
@@ -158,6 +158,42 @@ class RestApiImplTest {
assertRequiredCapability(restApi, Method.POST, "/api2", Capability.CONTENT__DOCUMENT_API);
}
+ @Test
+ void maps_exception_for_filter_throwing() {
+ RestApi.Filter throwingFilter = (ctx) -> {
+ throw new RestApiException.Forbidden("forbidden");
+ };
+ var restApi = RestApi.builder()
+ .setDefaultRoute(route("{*}").defaultHandler(ctx -> "hello world"))
+ .addFilter(throwingFilter)
+ .build();
+ verifyJsonResponse(restApi, Method.GET, "/", null, 403, "{\"error-code\":\"FORBIDDEN\",\"message\":\"forbidden\"}");
+ }
+
+ @Test
+ void missing_parameters_are_mapped_to_4xx_response() {
+ var restApi = RestApi.builder()
+ .addRoute(route("/missing-path-param").get(ctx -> ctx.pathParameters().getStringOrThrow("missing")))
+ .addRoute(route("/missing-query-param").get(ctx -> ctx.queryParameters().getStringOrThrow("missing")))
+ .build();
+ verifyJsonResponse(restApi, Method.GET, "/missing-path-param", null, 404,
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Path parameter 'missing' is missing\"}");
+ verifyJsonResponse(restApi, Method.GET, "/missing-query-param", null, 400,
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Query parameter 'missing' is missing\"}");
+ }
+
+ @Test
+ void principal_from_filter_is_visible_to_handler() {
+ var restApi = RestApi.builder()
+ .addRoute(route("/api1").get(ctx -> ctx.userPrincipalOrThrow().getName()))
+ .addFilter(ctx -> {
+ ctx.setPrincipal(() -> "my-principal-name");
+ return ctx.executeNext();
+ })
+ .build();
+ verifyJsonResponse(restApi, Method.GET, "/api1", null, 200, "{\"message\":\"my-principal-name\"}");
+ }
+
private static void verifyJsonResponse(
RestApi restApi, Method method, String path, String requestContent, int expectedStatusCode,
String expectedJson) {