diff options
author | gjoranv <gv@verizonmedia.com> | 2021-03-23 21:21:39 +0100 |
---|---|---|
committer | gjoranv <gv@verizonmedia.com> | 2021-03-23 23:13:01 +0100 |
commit | 5617a82f7a32ebcc37be226b27f6ff284f5c896d (patch) | |
tree | 45e8b3be33c372971c2b1349d245e223a1f46085 /jdisc_http_service/src/main/java/com/yahoo/jdisc | |
parent | 266046b2bb8bebbb683499f558a05aaf8a4f1ff3 (diff) |
Remove the jdisc_http_service module.
- It has been merged into container-core.
Diffstat (limited to 'jdisc_http_service/src/main/java/com/yahoo/jdisc')
90 files changed, 0 insertions, 9269 deletions
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/CertificateStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/CertificateStore.java deleted file mode 100644 index 3a63726b951..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/CertificateStore.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http; - -/** - * A store of certificates. An implementation can be plugged in to provide certificates to components who use it. - * - * @author bratseth - */ -public interface CertificateStore { - - /** Returns a certificate for a given appid, using the default TTL and retry time */ - default String getCertificate(String appid) { return getCertificate(appid, 0L, 0L); } - - /** Returns a certificate for a given appid, using a TTL and default retry time */ - default String getCertificate(String appid, long ttl) { return getCertificate(appid, ttl, 0L); } - - /** - * Returns a certificate for a given appid, using a TTL and default retry time - * - * @param ttl certificate TTL in ms. Use the default TTL if set to 0 - * @param retry if no certificate is found, allow access to cert DB again in - * "retry" ms. Use the default retry time if set to 0. - */ - String getCertificate(String appid, long ttl, long retry); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java deleted file mode 100644 index d882cf7a34a..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http; - -import org.eclipse.jetty.http.HttpCookie; -import org.eclipse.jetty.server.CookieCutter; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import static java.util.stream.Collectors.toList; - -/** - * A RFC 6265 compliant cookie. - * - * Note: RFC 2109 and RFC 2965 is no longer supported. All fields that are not part of RFC 6265 are deprecated. - * - * @author Einar M R Rosenvinge - * @author bjorncs - */ -public class Cookie { - - private final Set<Integer> ports = new HashSet<>(); - private String name; - private String value; - private String domain; - private String path; - private SameSite sameSite; - private long maxAgeSeconds = Integer.MIN_VALUE; - private boolean secure; - private boolean httpOnly; - private boolean discard; - - public Cookie() { - } - - public Cookie(Cookie cookie) { - ports.addAll(cookie.ports); - name = cookie.name; - value = cookie.value; - domain = cookie.domain; - path = cookie.path; - sameSite = cookie.sameSite; - maxAgeSeconds = cookie.maxAgeSeconds; - secure = cookie.secure; - httpOnly = cookie.httpOnly; - discard = cookie.discard; - } - - public Cookie(String name, String value) { - this.name = name; - this.value = value; - } - - public String getName() { - return name; - } - - public Cookie setName(String name) { - this.name = name; - return this; - } - - public String getValue() { - return value; - } - - public Cookie setValue(String value) { - this.value = value; - return this; - } - - public String getDomain() { - return domain; - } - - public Cookie setDomain(String domain) { - this.domain = domain; - return this; - } - - public String getPath() { - return path; - } - - public Cookie setPath(String path) { - this.path = path; - return this; - } - - public SameSite getSameSite() { - return sameSite; - } - - public Cookie setSameSite(SameSite sameSite) { - this.sameSite = sameSite; - return this; - } - - public int getMaxAge(TimeUnit unit) { - return (int)unit.convert(maxAgeSeconds, TimeUnit.SECONDS); - } - - public Cookie setMaxAge(int maxAge, TimeUnit unit) { - this.maxAgeSeconds = maxAge >= 0 ? unit.toSeconds(maxAge) : Integer.MIN_VALUE; - return this; - } - - public boolean isSecure() { - return secure; - } - - public Cookie setSecure(boolean secure) { - this.secure = secure; - return this; - } - - public boolean isHttpOnly() { - return httpOnly; - } - - public Cookie setHttpOnly(boolean httpOnly) { - this.httpOnly = httpOnly; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Cookie cookie = (Cookie) o; - return maxAgeSeconds == cookie.maxAgeSeconds && - secure == cookie.secure && - httpOnly == cookie.httpOnly && - discard == cookie.discard && - sameSite == cookie.sameSite && - Objects.equals(ports, cookie.ports) && - Objects.equals(name, cookie.name) && - Objects.equals(value, cookie.value) && - Objects.equals(domain, cookie.domain) && - Objects.equals(path, cookie.path); - } - - @Override - public int hashCode() { - return Objects.hash(ports, name, value, domain, path, sameSite, maxAgeSeconds, secure, httpOnly, discard); - } - - @Override - public String toString() { - StringBuilder ret = new StringBuilder(); - ret.append(name).append("=").append(value); - return ret.toString(); - } - // NOTE cookie encoding and decoding: - // The implementation uses Jetty for server-side (encoding of Set-Cookie and decoding of Cookie header), - // and java.net.HttpCookie for client-side (encoding of Cookie and decoding of Set-Cookie header). - // - // Implementation is RFC-6265 compliant. - - public static String toCookieHeader(Iterable<? extends Cookie> cookies) { - return StreamSupport.stream(cookies.spliterator(), false) - .map(cookie -> { - java.net.HttpCookie httpCookie = new java.net.HttpCookie(cookie.getName(), cookie.getValue()); - httpCookie.setDomain(cookie.getDomain()); - httpCookie.setHttpOnly(cookie.isHttpOnly()); - httpCookie.setMaxAge(cookie.getMaxAge(TimeUnit.SECONDS)); - httpCookie.setPath(cookie.getPath()); - httpCookie.setSecure(cookie.isSecure()); - httpCookie.setVersion(0); - return httpCookie.toString(); - }) - .collect(Collectors.joining(";")); - } - - public static List<Cookie> fromCookieHeader(String headerVal) { - CookieCutter cookieCutter = new CookieCutter(); - cookieCutter.addCookieField(headerVal); - return Arrays.stream(cookieCutter.getCookies()) - .map(servletCookie -> { - Cookie cookie = new Cookie(); - cookie.setName(servletCookie.getName()); - cookie.setValue(servletCookie.getValue()); - cookie.setPath(servletCookie.getPath()); - cookie.setDomain(servletCookie.getDomain()); - cookie.setMaxAge(servletCookie.getMaxAge(), TimeUnit.SECONDS); - cookie.setSecure(servletCookie.getSecure()); - cookie.setHttpOnly(servletCookie.isHttpOnly()); - return cookie; - }) - .collect(toList()); - } - - public static List<String> toSetCookieHeaders(Iterable<? extends Cookie> cookies) { - return StreamSupport.stream(cookies.spliterator(), false) - .map(cookie -> - new org.eclipse.jetty.http.HttpCookie( - cookie.getName(), - cookie.getValue(), - cookie.getDomain(), - cookie.getPath(), - cookie.getMaxAge(TimeUnit.SECONDS), - cookie.isHttpOnly(), - cookie.isSecure(), - null, /* comment */ - 0, /* version */ - Optional.ofNullable(cookie.getSameSite()).map(SameSite::jettySameSite).orElse(null) - ).getRFC6265SetCookie()) - .collect(toList()); - } - - @Deprecated // TODO Vespa 8 Remove - public static List<String> toSetCookieHeaderAll(Iterable<? extends Cookie> cookies) { - return toSetCookieHeaders(cookies); - } - - public static Cookie fromSetCookieHeader(String headerVal) { - return java.net.HttpCookie.parse(headerVal).stream() - .map(httpCookie -> { - Cookie cookie = new Cookie(); - cookie.setName(httpCookie.getName()); - cookie.setValue(httpCookie.getValue()); - cookie.setDomain(httpCookie.getDomain()); - cookie.setHttpOnly(httpCookie.isHttpOnly()); - cookie.setMaxAge((int) httpCookie.getMaxAge(), TimeUnit.SECONDS); - cookie.setPath(httpCookie.getPath()); - cookie.setSecure(httpCookie.getSecure()); - return cookie; - }) - .findFirst().get(); - } - - public enum SameSite { - NONE, STRICT, LAX; - - HttpCookie.SameSite jettySameSite() { - return HttpCookie.SameSite.valueOf(name()); - } - - static SameSite fromJettySameSite(HttpCookie.SameSite jettySameSite) { - return valueOf(jettySameSite.name()); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpHeaders.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpHeaders.java deleted file mode 100644 index 039966133e8..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpHeaders.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http; - -/** - * @author Anirudha Khanna - */ -@SuppressWarnings("UnusedDeclaration") -public class HttpHeaders { - - public static final class Names { - - public static final String ACCEPT = "Accept"; - public static final String ACCEPT_CHARSET = "Accept-Charset"; - public static final String ACCEPT_ENCODING = "Accept-Encoding"; - public static final String ACCEPT_LANGUAGE = "Accept-Language"; - public static final String ACCEPT_RANGES = "Accept-Ranges"; - public static final String ACCEPT_PATCH = "Accept-Patch"; - public static final String AGE = "Age"; - public static final String ALLOW = "Allow"; - public static final String AUTHORIZATION = "Authorization"; - public static final String CACHE_CONTROL = "Cache-Control"; - public static final String CONNECTION = "Connection"; - public static final String CONTENT_BASE = "Content-Base"; - public static final String CONTENT_ENCODING = "Content-Encoding"; - public static final String CONTENT_LANGUAGE = "Content-Language"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_LOCATION = "Content-Location"; - public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; - public static final String CONTENT_MD5 = "Content-MD5"; - public static final String CONTENT_RANGE = "Content-Range"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String COOKIE = "Cookie"; - public static final String DATE = "Date"; - public static final String ETAG = "ETag"; - public static final String EXPECT = "Expect"; - public static final String EXPIRES = "Expires"; - public static final String FROM = "From"; - public static final String HOST = "Host"; - public static final String IF_MATCH = "If-Match"; - public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; - public static final String IF_NONE_MATCH = "If-None-Match"; - public static final String IF_RANGE = "If-Range"; - public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; - public static final String LAST_MODIFIED = "Last-Modified"; - public static final String LOCATION = "Location"; - public static final String MAX_FORWARDS = "Max-Forwards"; - public static final String ORIGIN = "Origin"; - public static final String PRAGMA = "Pragma"; - public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; - public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; - public static final String RANGE = "Range"; - public static final String REFERER = "Referer"; - public static final String RETRY_AFTER = "Retry-After"; - public static final String SEC_WEBSOCKET_KEY1 = "Sec-WebSocket-Key1"; - public static final String SEC_WEBSOCKET_KEY2 = "Sec-WebSocket-Key2"; - public static final String SEC_WEBSOCKET_LOCATION = "Sec-WebSocket-Location"; - public static final String SEC_WEBSOCKET_ORIGIN = "Sec-WebSocket-Origin"; - public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; - public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; - public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; - public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; - public static final String SERVER = "Server"; - public static final String SET_COOKIE = "Set-Cookie"; - public static final String SET_COOKIE2 = "Set-Cookie2"; - public static final String TE = "TE"; - public static final String TRAILER = "Trailer"; - public static final String TRANSFER_ENCODING = "Transfer-Encoding"; - public static final String UPGRADE = "Upgrade"; - public static final String USER_AGENT = "User-Agent"; - public static final String VARY = "Vary"; - public static final String VIA = "Via"; - public static final String WARNING = "Warning"; - public static final String WEBSOCKET_LOCATION = "WebSocket-Location"; - public static final String WEBSOCKET_ORIGIN = "WebSocket-Origin"; - public static final String WEBSOCKET_PROTOCOL = "WebSocket-Protocol"; - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; - public static final String X_DISABLE_CHUNKING = "X-JDisc-Disable-Chunking"; - public static final String X_YAHOO_SERVING_HOST = "X-Yahoo-Serving-Host"; - - private Names() { - // hide - } - } - - public static final class Values { - - public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; - public static final String BASE64 = "base64"; - public static final String BINARY = "binary"; - public static final String BYTES = "bytes"; - public static final String CHARSET = "charset"; - public static final String CHUNKED = "chunked"; - public static final String CLOSE = "close"; - public static final String COMPRESS = "compress"; - public static final String CONTINUE = "100-continue"; - public static final String DEFLATE = "deflate"; - public static final String GZIP = "gzip"; - public static final String IDENTITY = "identity"; - public static final String KEEP_ALIVE = "keep-alive"; - public static final String MAX_AGE = "max-age"; - public static final String MAX_STALE = "max-stale"; - public static final String MIN_FRESH = "min-fresh"; - public static final String MUST_REVALIDATE = "must-revalidate"; - public static final String NO_CACHE = "no-cache"; - public static final String NO_STORE = "no-store"; - public static final String NO_TRANSFORM = "no-transform"; - public static final String NONE = "none"; - public static final String ONLY_IF_CACHED = "only-if-cached"; - public static final String PRIVATE = "private"; - public static final String PROXY_REVALIDATE = "proxy-revalidate"; - public static final String PUBLIC = "public"; - public static final String QUOTED_PRINTABLE = "quoted-printable"; - public static final String S_MAXAGE = "s-maxage"; - public static final String TRAILERS = "trailers"; - public static final String UPGRADE = "Upgrade"; - public static final String WEBSOCKET = "WebSocket"; - - private Values() { - // hide - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java deleted file mode 100644 index 118c34245c0..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpRequest; -import com.yahoo.jdisc.service.CurrentContainer; -import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.util.MultiMap; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.URI; -import java.security.Principal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * A HTTP request. - * - * @author Anirudha Khanna - * @author Einar M R Rosenvinge - */ -public class HttpRequest extends Request implements ServletOrJdiscHttpRequest { - - public enum Method { - OPTIONS, - GET, - HEAD, - POST, - PUT, - PATCH, - DELETE, - TRACE, - CONNECT - } - - public enum Version { - HTTP_1_0("HTTP/1.0"), - HTTP_1_1("HTTP/1.1"); - - private final String str; - - private Version(String str) { - this.str = str; - } - - @Override - public String toString() { - return str; - } - - public static Version fromString(String str) { - for (Version version : values()) { - if (version.str.equals(str)) { - return version; - } - } - throw new IllegalArgumentException(str); - } - } - - private final HeaderFields trailers = new HeaderFields(); - private final Map<String, List<String>> parameters = new HashMap<>(); - private Principal principal; - private final long connectedAt; - private Method method; - private Version version; - private SocketAddress remoteAddress; - private URI proxyServer; - private Long connectionTimeout; - - protected HttpRequest(CurrentContainer container, URI uri, Method method, Version version, - SocketAddress remoteAddress, Long connectedAtMillis) - { - super(container, uri); - try { - this.method = method; - this.version = version; - this.remoteAddress = remoteAddress; - this.parameters.putAll(getUriQueryParameters(uri)); - if (connectedAtMillis != null) { - this.connectedAt = connectedAtMillis; - } else { - this.connectedAt = creationTime(TimeUnit.MILLISECONDS); - } - } catch (RuntimeException e) { - release(); - throw e; - } - } - - private HttpRequest(Request parent, URI uri, Method method, Version version) { - super(parent, uri); - try { - this.method = method; - this.version = version; - this.remoteAddress = null; - this.parameters.putAll(getUriQueryParameters(uri)); - this.connectedAt = creationTime(TimeUnit.MILLISECONDS); - } catch (RuntimeException e) { - release(); - throw e; - } - } - - private static Map<String, List<String>> getUriQueryParameters(URI uri) { - MultiMap<String> queryParameters = new MultiMap<>(); - new HttpURI(uri).decodeQueryTo(queryParameters); - - // Do a deep copy so we do not leak Jetty classes outside - Map<String, List<String>> deepCopiedQueryParameters = new HashMap<>(); - for (Map.Entry<String, List<String>> entry : queryParameters.entrySet()) { - deepCopiedQueryParameters.put(entry.getKey(), new ArrayList<>(entry.getValue())); - } - return deepCopiedQueryParameters; - } - - public Method getMethod() { - return method; - } - - public void setMethod(Method method) { - this.method = method; - } - - public Version getVersion() { - return version; - } - - /** Returns the remote address, or null if unresolved */ - @Override - public String getRemoteHostAddress() { - if (remoteAddress instanceof InetSocketAddress) { - InetAddress remoteInetAddress = ((InetSocketAddress) remoteAddress).getAddress(); - if (remoteInetAddress == null) - return null; - return remoteInetAddress.getHostAddress(); - } - else { - throw new RuntimeException("Unknown SocketAddress class: " + remoteAddress.getClass().getName()); - } - } - - @Override - public String getRemoteHostName() { - if (remoteAddress instanceof InetSocketAddress) { - InetAddress remoteInetAddress = ((InetSocketAddress) remoteAddress).getAddress(); - if (remoteInetAddress == null) return null; // not resolved; we have no network - return remoteInetAddress.getHostName(); - } - else { - throw new RuntimeException("Unknown SocketAddress class: " + remoteAddress.getClass().getName()); - } - } - - @Override - public int getRemotePort() { - if (remoteAddress instanceof InetSocketAddress) - return ((InetSocketAddress) remoteAddress).getPort(); - else - throw new RuntimeException("Unknown SocketAddress class: " + remoteAddress.getClass().getName()); - } - - public void setVersion(Version version) { - this.version = version; - } - - public SocketAddress getRemoteAddress() { - return remoteAddress; - } - - public void setRemoteAddress(SocketAddress remoteAddress) { - this.remoteAddress = remoteAddress; - } - - public URI getProxyServer() { - return proxyServer; - } - - public void setProxyServer(URI proxyServer) { - this.proxyServer = proxyServer; - } - - /** - * <p>For server requests, this returns the timestamp of when the underlying HTTP channel was connected. - * - * <p>For client requests, this returns the same value as {@link #creationTime(java.util.concurrent.TimeUnit)}.</p> - * - * @param unit the unit to return the time in - * @return the timestamp of when the underlying HTTP channel was connected, or request creation time - */ - @Override - public long getConnectedAt(TimeUnit unit) { - return unit.convert(connectedAt, TimeUnit.MILLISECONDS); - } - - public Long getConnectionTimeout(TimeUnit unit) { - if (connectionTimeout == null) { - return null; - } - return unit.convert(connectionTimeout, TimeUnit.MILLISECONDS); - } - - /** - * <p>Sets the allocated time that this HttpRequest is allowed to spend trying to connect to a remote host. This has - * no effect on an HttpRequest received by a {@link RequestHandler}. If no connection timeout is assigned to an - * HttpRequest, it defaults the connection-timeout in the client configuration.</p> - * - * <p><b>NOTE:</b> Where {@link Request#setTimeout(long, TimeUnit)} sets the expiration time between calling a - * RequestHandler and a {@link ResponseHandler}, this method sets the expiration time of the connect-operation as - * performed by the client.</p> - * - * @param timeout The allocated amount of time. - * @param unit The time unit of the <em>timeout</em> argument. - */ - public void setConnectionTimeout(long timeout, TimeUnit unit) { - this.connectionTimeout = unit.toMillis(timeout); - } - - public Map<String, List<String>> parameters() { - return parameters; - } - - @Override - public void copyHeaders(HeaderFields target) { - target.addAll(headers()); - } - - public List<Cookie> decodeCookieHeader() { - List<String> cookies = headers().get(HttpHeaders.Names.COOKIE); - if (cookies == null) { - return Collections.emptyList(); - } - List<Cookie> ret = new LinkedList<>(); - for (String cookie : cookies) { - ret.addAll(Cookie.fromCookieHeader(cookie)); - } - return ret; - } - - public void encodeCookieHeader(List<Cookie> cookies) { - headers().put(HttpHeaders.Names.COOKIE, Cookie.toCookieHeader(cookies)); - } - - /** - * <p>Returns the set of trailer header fields of this HttpRequest. These are typically meta-data that should have - * been part of {@link #headers()}, but were not available prior to calling {@link #connect(ResponseHandler)}. You - * must NOT WRITE to these headers AFTER calling {@link ContentChannel#close(CompletionHandler)}, and you must NOT - * READ from these headers BEFORE {@link ContentChannel#close(CompletionHandler)} has been called.</p> - * - * <p><b>NOTE:</b> These headers are NOT thread-safe. You need to explicitly synchronized on the returned object to - * prevent concurrency issues such as ConcurrentModificationExceptions.</p> - * - * @return The trailer headers of this HttpRequest. - */ - public HeaderFields trailers() { - return trailers; - } - - /** - * Returns whether this request was <em>explicitly</em> chunked from the client. NOTE that there are cases - * where the underlying HTTP server library (Netty for the time being) will read the request in a chunked manner. An - * application MUST wait for {@link com.yahoo.jdisc.handler.ContentChannel#close(com.yahoo.jdisc.handler.CompletionHandler)} - * before it can actually know that it has received the entire request. - * - * @return true if this request was chunked from the client. - */ - public boolean isChunked() { - return version == Version.HTTP_1_1 && - headers().containsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); - } - - public boolean hasChunkedResponse() { - return version == Version.HTTP_1_1 && - !headers().isTrue(HttpHeaders.Names.X_DISABLE_CHUNKING); - } - - public boolean isKeepAlive() { - if (headers().containsIgnoreCase(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE)) { - return true; - } - if (headers().containsIgnoreCase(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE)) { - return false; - } - return version == Version.HTTP_1_1; - } - - public Principal getUserPrincipal() { - return principal; - } - - public void setUserPrincipal(Principal principal) { - this.principal = principal; - } - - public static HttpRequest newServerRequest(CurrentContainer container, URI uri) { - return newServerRequest(container, uri, Method.GET); - } - - public static HttpRequest newServerRequest(CurrentContainer container, URI uri, Method method) { - return newServerRequest(container, uri, method, Version.HTTP_1_1); - } - - public static HttpRequest newServerRequest(CurrentContainer container, URI uri, Method method, Version version) { - return newServerRequest(container, uri, method, version, null); - } - - public static HttpRequest newServerRequest(CurrentContainer container, URI uri, Method method, Version version, - SocketAddress remoteAddress) { - return new HttpRequest(container, uri, method, version, remoteAddress, null); - } - - public static HttpRequest newServerRequest(CurrentContainer container, URI uri, Method method, Version version, - SocketAddress remoteAddress, long connectedAtMillis) - { - return new HttpRequest(container, uri, method, version, remoteAddress, connectedAtMillis); - } - - public static HttpRequest newClientRequest(Request parent, URI uri) { - return newClientRequest(parent, uri, Method.GET); - } - - public static HttpRequest newClientRequest(Request parent, URI uri, Method method) { - return newClientRequest(parent, uri, method, Version.HTTP_1_1); - } - - public static HttpRequest newClientRequest(Request parent, URI uri, Method method, Version version) { - return new HttpRequest(parent, uri, method, version); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpResponse.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpResponse.java deleted file mode 100644 index f7138ba0e2b..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpResponse.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpResponse; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * A HTTP response. - * - * @author Einar M R Rosenvinge - */ -public class HttpResponse extends Response implements ServletOrJdiscHttpResponse { - - private final HeaderFields trailers = new HeaderFields(); - private boolean chunkedEncodingEnabled = true; - private String message; - - public interface Status extends Response.Status { - int REQUEST_ENTITY_TOO_LARGE = REQUEST_TOO_LONG; - int REQUEST_RANGE_NOT_SATISFIABLE = REQUESTED_RANGE_NOT_SATISFIABLE; - } - - protected HttpResponse(Request request, int status, String message, Throwable error) { - super(status, error); - this.message = message; - } - - public boolean isChunkedEncodingEnabled() { - if (headers().contains(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED)) { - return true; - } - if (headers().containsKey(HttpHeaders.Names.CONTENT_LENGTH)) { - return false; - } - return chunkedEncodingEnabled; - } - - public void setChunkedEncodingEnabled(boolean chunkedEncodingEnabled) { - this.chunkedEncodingEnabled = chunkedEncodingEnabled; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - @Override - public void copyHeaders(HeaderFields target) { - target.addAll(headers()); - } - - public List<Cookie> decodeSetCookieHeader() { - List<String> cookies = headers().get(HttpHeaders.Names.SET_COOKIE); - if (cookies == null) { - return Collections.emptyList(); - } - List<Cookie> ret = new LinkedList<>(); - for (String cookie : cookies) { - ret.add(Cookie.fromSetCookieHeader(cookie)); - } - return ret; - } - - public void encodeSetCookieHeader(List<Cookie> cookies) { - headers().remove(HttpHeaders.Names.SET_COOKIE); - for (Cookie cookie : cookies) { - headers().add(HttpHeaders.Names.SET_COOKIE, Cookie.toSetCookieHeaders(Arrays.asList(cookie))); - } - } - - /** - * <p>Returns the set of trailer header fields of this HttpResponse. These are typically meta-data that should have - * been part of {@link #headers()}, but were not available prior to calling {@link - * ResponseHandler#handleResponse(Response)}. You must NOT WRITE to these headers AFTER calling {@link - * ContentChannel#close(CompletionHandler)}, and you must NOT READ from these headers BEFORE {@link - * ContentChannel#close(CompletionHandler)} has been called.</p> - * - * <p><b>NOTE:</b> These headers are NOT thread-safe. You need to explicitly synchronized on the returned object to - * prevent concurrency issues such as ConcurrentModificationExceptions.</p> - * - * @return The trailer headers of this HttpRequest. - */ - public HeaderFields trailers() { - return trailers; - } - - public static boolean isServerError(Response response) { - return (response.getStatus() >= 500) && (response.getStatus() < 600); - } - - public static HttpResponse newInstance(int status) { - return new HttpResponse(null, status, null, null); - } - - public static HttpResponse newInstance(int status, String message) { - return new HttpResponse(null, status, message, null); - } - - public static HttpResponse newError(Request request, int status, Throwable error) { - return new HttpResponse(request, status, formatMessage(error), error); - } - - public static HttpResponse newInternalServerError(Request request, Throwable error) { - return new HttpResponse(request, Status.INTERNAL_SERVER_ERROR, formatMessage(error), error); - } - - private static String formatMessage(Throwable t) { - String msg = t.getMessage(); - return msg != null ? msg : t.toString(); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/SecretStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/SecretStore.java deleted file mode 100644 index 4f739c5bd78..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/SecretStore.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http; - -/** - * An abstraction of a secret store for e.g passwords. - * Implementations can be plugged in to provide passwords for various keys. - * - * @author bratseth - * @author bjorncs - * @deprecated Use com.yahoo.container.jdisc.secretstore.SecretStore - */ -@Deprecated // Vespa 8 -public interface SecretStore { - - /** Returns the secret for this key */ - String getSecret(String key); - - /** Returns the secret for this key and version */ - default String getSecret(String key, int version) { - throw new UnsupportedOperationException("SecretStore implementation does not support versioned secrets"); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/cloud/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/cloud/package-info.java deleted file mode 100644 index 43da1a82077..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/cloud/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@ExportPackage -package com.yahoo.jdisc.http.cloud; -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java deleted file mode 100644 index f7ab399574c..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java +++ /dev/null @@ -1,543 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.HttpRequest.Version; -import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpRequest; - -import java.net.InetSocketAddress; -import java.net.URI; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -/** - * The Request class on which all filters will operate upon. - * Test cases that need a concrete - * instance should create a {@link JdiscFilterRequest}. - */ -public abstract class DiscFilterRequest { - - protected static final String HTTPS_PREFIX = "https"; - protected static final int DEFAULT_HTTP_PORT = 80; - protected static final int DEFAULT_HTTPS_PORT = 443; - - private final ServletOrJdiscHttpRequest parent; - protected final Map<String, List<String>> untreatedParams; - private final HeaderFields untreatedHeaders; - private List<Cookie> untreatedCookies = null; - private String remoteUser = null; - private String[] roles = null; - private boolean overrideIsUserInRole = false; - - public DiscFilterRequest(ServletOrJdiscHttpRequest parent) { - this.parent = parent; - - // save untreated headers from parent - untreatedHeaders = new HeaderFields(); - parent.copyHeaders(untreatedHeaders); - - untreatedParams = new HashMap<>(parent.parameters()); - } - - public abstract String getMethod(); - - public Version getVersion() { - return parent.getVersion(); - } - - public URI getUri() { - return parent.getUri(); - } - - public abstract void setUri(URI uri); - - public HttpRequest getParentRequest() { - throw new UnsupportedOperationException("getParentRequest is not supported for " + parent.getClass().getName()); - } - - /** - * Returns the Internet Protocol (IP) address of the client - * or last proxy that sent the request. - */ - public String getRemoteAddr() { - return parent.getRemoteHostAddress(); - } - - /** - * Set the IP address of the remote client associated with this Request. - */ - public void setRemoteAddr(String remoteIpAddress) { - InetSocketAddress remoteAddress = new InetSocketAddress(remoteIpAddress, this.getRemotePort()); - parent.setRemoteAddress(remoteAddress); - } - - /** - * Returns the Internet Protocol (IP) address of the interface - * on which the request was received. - */ - public String getLocalAddr() { - InetSocketAddress localAddress = localAddress(); - if (localAddress.getAddress() == null) return null; - return localAddress.getAddress().getHostAddress(); - } - - private InetSocketAddress localAddress() { - int port = parent.getUri().getPort(); - if (port < 0) - port = 0; - return new InetSocketAddress(parent.getUri().getHost(), port); - } - - public Enumeration<String> getAttributeNames() { - return Collections.enumeration(parent.context().keySet()); - } - - public Object getAttribute(String name) { - return parent.context().get(name); - } - - public void setAttribute(String name, Object value) { - parent.context().put(name, value); - } - - public boolean containsAttribute(String name) { - return parent.context().containsKey(name); - } - - public void removeAttribute(String name) { - parent.context().remove(name); - } - - public abstract String getParameter(String name); - - public abstract Enumeration<String> getParameterNames(); - - public List<String> getParameterNamesAsList() { - return new ArrayList<String>(parent.parameters().keySet()); - } - - public Enumeration<String> getParameterValues(String name) { - return Collections.enumeration(parent.parameters().get(name)); - } - - public List<String> getParameterValuesAsList(String name) { - return parent.parameters().get(name); - } - - public Map<String,List<String>> getParameterMap() { - return parent.parameters(); - } - - - /** - * Returns the hostName of remoteHost, or null if none - */ - public String getRemoteHost() { - return parent.getRemoteHostName(); - } - - /** - * Returns the Internet Protocol (IP) port number of - * the interface on which the request was received. - */ - public int getLocalPort() { - return localAddress().getPort(); - } - - /** - * Returns the port of remote host - */ - public int getRemotePort() { - return parent.getRemotePort(); - } - - /** - * Returns a unmodifiable map of untreatedParameters from the - * parent request. - */ - public Map<String, List<String>> getUntreatedParams() { - return Collections.unmodifiableMap(untreatedParams); - } - - - /** - * Returns the untreatedHeaders from - * parent request - */ - public HeaderFields getUntreatedHeaders() { - return untreatedHeaders; - } - - /** - * Returns the untreatedCookies from - * parent request - */ - public List<Cookie> getUntreatedCookies() { - if (untreatedCookies == null) { - this.untreatedCookies = parent.decodeCookieHeader(); - } - return Collections.unmodifiableList(untreatedCookies); - } - - /** - * Sets a header with the given name and value. - * If the header had already been set, the new value overwrites the previous one. - */ - public abstract void addHeader(String name, String value); - - public long getDateHeader(String name) { - String value = getHeader(name); - if (value == null) - return -1L; - - Date date = null; - for (int i = 0; (date == null) && (i < formats.length); i++) { - try { - date = formats[i].parse(value); - } catch (ParseException e) { - } - } - if (date == null) { - return -1L; - } - - return date.getTime(); - } - - public abstract String getHeader(String name); - - public abstract Enumeration<String> getHeaderNames(); - - public abstract List<String> getHeaderNamesAsList(); - - public abstract Enumeration<String> getHeaders(String name); - - public abstract List<String> getHeadersAsList(String name); - - public abstract void removeHeaders(String name); - - /** - * Sets a header with the given name and value. - * If the header had already been set, the new value overwrites the previous one. - * - */ - public abstract void setHeaders(String name, String value); - - /** - * Sets a header with the given name and value. - * If the header had already been set, the new value overwrites the previous one. - * - */ - public abstract void setHeaders(String name, List<String> values); - - public int getIntHeader(String name) { - String value = getHeader(name); - if (value == null) { - return -1; - } else { - return Integer.parseInt(value); - } - } - - - public List<Cookie> getCookies() { - return parent.decodeCookieHeader(); - } - - public void setCookies(List<Cookie> cookies) { - parent.encodeCookieHeader(cookies); - } - - public long getConnectedAt(TimeUnit unit) { - return parent.getConnectedAt(unit); - } - - public String getProtocol() { - return getVersion().name(); - } - - /** - * Returns the query string that is contained in the request URL. - * Returns the undecoded value uri.getRawQuery() - */ - public String getQueryString() { - return getUri().getRawQuery(); - } - - /** - * Returns the login of the user making this request, - * if the user has been authenticated, or null if the user has not been authenticated. - */ - public String getRemoteUser() { - return remoteUser; - } - - public String getRequestURI() { - return getUri().getRawPath(); - } - - public String getRequestedSessionId() { - return null; - } - - public String getScheme() { - return getUri().getScheme(); - } - - public void setScheme(String scheme, boolean isSecure) { - String uri = getUri().toString(); - String arr [] = uri.split("://"); - URI newUri = URI.create(scheme + "://" + arr[1]); - setUri(newUri); - } - - public String getServerName() { - return getUri().getHost(); - } - - public int getServerPort() { - int port = getUri().getPort(); - if(port == -1) { - if(isSecure()) { - port = DEFAULT_HTTPS_PORT; - } - else { - port = DEFAULT_HTTP_PORT; - } - } - - return port; - } - - public abstract Principal getUserPrincipal(); - - public boolean isSecure() { - if(getScheme().equalsIgnoreCase(HTTPS_PREFIX)) { - return true; - } - return false; - } - - - /** - * Returns a boolean indicating whether the authenticated user - * is included in the specified logical "role". - */ - public boolean isUserInRole(String role) { - if (overrideIsUserInRole) { - if (roles != null) { - for (String role1 : roles) { - if (role1 != null && role1.trim().length() > 0) { - String userRole = role1.trim(); - if (userRole.equals(role)) { - return true; - } - } - } - } - return false; - } - else { - return false; - } - } - - public void setOverrideIsUserInRole(boolean overrideIsUserInRole) { - this.overrideIsUserInRole = overrideIsUserInRole; - } - - public void setRemoteHost(String remoteAddr) { } - - public void setRemoteUser(String remoteUser) { - this.remoteUser = remoteUser; - } - - public abstract void setUserPrincipal(Principal principal); - - /** - * @return The client certificate chain in ascending order of trust. The first certificate is the one sent from the client. - * Returns an empty list if the client did not provide a certificate. - */ - public abstract List<X509Certificate> getClientCertificateChain(); - - public void setUserRoles(String[] roles) { - this.roles = roles; - } - - /** - * Returns the content-type for the request - */ - public String getContentType() { - return getHeader(HttpHeaders.Names.CONTENT_TYPE); - } - - - /** - * Get character encoding - */ - public String getCharacterEncoding() { - return getCharsetFromContentType(this.getContentType()); - } - - /** - * Set character encoding - */ - public void setCharacterEncoding(String encoding) { - String charEncoding = setCharsetFromContentType(this.getContentType(), encoding); - if (charEncoding != null && !charEncoding.isEmpty()) { - removeHeaders(HttpHeaders.Names.CONTENT_TYPE); - setHeaders(HttpHeaders.Names.CONTENT_TYPE, charEncoding); - } - } - - /** - * Can be called multiple times to add Cookies - */ - public void addCookie(JDiscCookieWrapper cookie) { - if (cookie != null) { - List<Cookie> cookies = new ArrayList<>(); - // Get current set of cookies first - List<Cookie> c = getCookies(); - if (c != null && !c.isEmpty()) { - cookies.addAll(c); - } - cookies.add(cookie.getCookie()); - setCookies(cookies); - } - } - - public abstract void clearCookies(); - - public JDiscCookieWrapper[] getWrappedCookies() { - List<Cookie> cookies = getCookies(); - if (cookies == null) { - return null; - } - List<JDiscCookieWrapper> cookieWrapper = new ArrayList<>(cookies.size()); - for(Cookie cookie : cookies) { - cookieWrapper.add(JDiscCookieWrapper.wrap(cookie)); - } - - return cookieWrapper.toArray(new JDiscCookieWrapper[cookieWrapper.size()]); - } - - private String setCharsetFromContentType(String contentType,String charset) { - String newContentType = ""; - if (contentType == null) - return (null); - int start = contentType.indexOf("charset="); - if (start < 0) { - //No charset present: - newContentType = contentType + ";charset=" + charset; - return newContentType; - } - String encoding = contentType.substring(start + 8); - int end = encoding.indexOf(';'); - if (end >= 0) { - newContentType = contentType.substring(0,start); - newContentType = newContentType + "charset=" + charset; - newContentType = newContentType + encoding.substring(end,encoding.length()); - } - else { - newContentType = contentType.substring(0,start); - newContentType = newContentType + "charset=" + charset; - } - - return (newContentType.trim()); - - } - - private String getCharsetFromContentType(String contentType) { - - if (contentType == null) - return (null); - int start = contentType.indexOf("charset="); - if (start < 0) - return (null); - String encoding = contentType.substring(start + 8); - int end = encoding.indexOf(';'); - if (end >= 0) - encoding = encoding.substring(0, end); - encoding = encoding.trim(); - if ((encoding.length() > 2) && (encoding.startsWith("\"")) - && (encoding.endsWith("\""))) - encoding = encoding.substring(1, encoding.length() - 1); - return (encoding.trim()); - - } - - public static boolean isMultipart(DiscFilterRequest request) { - if (request == null) { - return false; - } - - String contentType = request.getContentType(); - - if (contentType == null) { - return false; - } - - String[] parts = Pattern.compile(";").split(contentType); - if (parts.length == 0) { - return false; - } - - for (String part : parts) { - if ("multipart/form-data".equals(part)) { - return true; - } - } - - return false; - } - - protected static ThreadLocalSimpleDateFormat formats[] = { - new ThreadLocalSimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), - new ThreadLocalSimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), - new ThreadLocalSimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) }; - - /** - * The set of SimpleDateFormat formats to use in getDateHeader(). - * - * Notice that because SimpleDateFormat is not thread-safe, we can't declare - * formats[] as a static variable. - */ - protected static final class ThreadLocalSimpleDateFormat extends ThreadLocal<SimpleDateFormat> { - - private final String format; - private final Locale locale; - - public ThreadLocalSimpleDateFormat(String format, Locale locale) { - super(); - this.format = format; - this.locale = locale; - } - - // @see java.lang.ThreadLocal#initialValue() - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(format, locale); - } - - public Date parse(String value) throws ParseException { - return get().parse(value); - } - - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java deleted file mode 100644 index 4e8b779c516..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; - -import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpResponse; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; - - -import com.yahoo.jdisc.http.HttpResponse; - -/** - * This class was made abstract from 5.27. Test cases that need - * a concrete instance should create a {@link JdiscFilterResponse}. - * - * @author tejalk - */ -public abstract class DiscFilterResponse { - - private final ServletOrJdiscHttpResponse parent; - private final HeaderFields untreatedHeaders; - private final List<Cookie> untreatedCookies; - - public DiscFilterResponse(ServletOrJdiscHttpResponse parent) { - this.parent = parent; - - this.untreatedHeaders = new HeaderFields(); - parent.copyHeaders(untreatedHeaders); - - this.untreatedCookies = getCookies(); - } - - /* Attributes on the response are only used for unit testing. - * There is no such thing as 'attributes' in the underlying response. */ - - public Enumeration<String> getAttributeNames() { - return Collections.enumeration(parent.context().keySet()); - } - - public Object getAttribute(String name) { - return parent.context().get(name); - } - - public void setAttribute(String name, Object value) { - parent.context().put(name, value); - } - - public void removeAttribute(String name) { - parent.context().remove(name); - } - - /** - * Returns the untreatedHeaders from the parent request - */ - public HeaderFields getUntreatedHeaders() { - return untreatedHeaders; - } - - /** - * Returns the untreatedCookies from the parent request - */ - public List<Cookie> getUntreatedCookies() { - return untreatedCookies; - } - - /** - * Sets a header with the given name and value. - * <p> - * If the header had already been set, the new value overwrites the previous one. - */ - public abstract void setHeader(String name, String value); - - public abstract void removeHeaders(String name); - - /** - * Sets a header with the given name and value. - * <p> - * If the header had already been set, the new value overwrites the previous one. - */ - public abstract void setHeaders(String name, String value); - - /** - * Sets a header with the given name and value. - * <p> - * If the header had already been set, the new value overwrites the previous one. - */ - public abstract void setHeaders(String name, List<String> values); - - /** - * Adds a header with the given name and value - * @see com.yahoo.jdisc.HeaderFields#add - */ - public abstract void addHeader(String name, String value); - - public abstract String getHeader(String name); - - public List<Cookie> getCookies() { - return parent.decodeSetCookieHeader(); - } - - public abstract void setCookies(List<Cookie> cookies); - - public int getStatus() { - return parent.getStatus(); - } - - public abstract void setStatus(int status); - - /** - * Return the parent HttpResponse - */ - public HttpResponse getParentResponse() { - if (parent instanceof HttpResponse) - return (HttpResponse)parent; - throw new UnsupportedOperationException( - "getParentResponse is not supported for " + parent.getClass().getName()); - } - - public void addCookie(JDiscCookieWrapper cookie) { - if(cookie != null) { - List<Cookie> cookies = new ArrayList<>(); - //Get current set of cookies first - List<Cookie> c = getCookies(); - if((c != null) && (! c.isEmpty())) { - cookies.addAll(c); - } - cookies.add(cookie.getCookie()); - setCookies(cookies); - } - } - - /** - * This method does not actually send the response as it - * does not have access to responseHandler but - * just sets the status. The methodName is misleading - * for historical reasons. - */ - public void sendError(int errorCode) throws IOException { - setStatus(errorCode); - } - - public void setCookie(String name, String value) { - Cookie cookie = new Cookie(name, value); - setCookies(Arrays.asList(cookie)); - } - - } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java deleted file mode 100644 index af9e2b5e99a..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import java.util.Collection; - -/** - * Legacy filter config. Prefer to use a regular stringly typed config class for new filters. - * - * @author tejalk - */ -public interface FilterConfig { - - /** Returns the filter-name of this filter */ - String getFilterName(); - - /** Returns the filter-class of this filter */ - String getFilterClass(); - - /** - * Returns a String containing the value of the - * named initialization parameter, or null if - * the parameter does not exist. - * - * @param name a String specifying the name of the initialization parameter - * @return a String containing the value of the initialization parameter - */ - String getInitParameter(String name); - - /** - * Returns the boolean value of the init parameter. If not present returns default value - * - * @return boolean value of init parameter - */ - boolean getBooleanInitParameter(String name, boolean defaultValue); - - /** - * Returns the names of the filter's initialization parameters as an Collection of String objects, - * or an empty Collection if the filter has no initialization parameters. - */ - Collection<String> getInitParameterNames(); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java deleted file mode 100644 index 2b9c650d545..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.http.Cookie; - -import java.util.concurrent.TimeUnit; - -/** - * Wrapper of Cookie. - * - * @author Tejal Knot - * - */ -public class JDiscCookieWrapper { - - private Cookie cookie; - - protected JDiscCookieWrapper(Cookie cookie) { - this.cookie = cookie; - } - - public static JDiscCookieWrapper wrap(Cookie cookie) { - return new JDiscCookieWrapper(cookie); - } - - public String getDomain() { - return cookie.getDomain(); - } - - public int getMaxAge() { - return cookie.getMaxAge(TimeUnit.SECONDS); - } - - public String getName() { - return cookie.getName(); - } - - public String getPath() { - return cookie.getPath(); - } - - public boolean getSecure() { - return cookie.isSecure(); - } - - public String getValue() { - return cookie.getValue(); - } - - public void setDomain(String pattern) { - cookie.setDomain(pattern); - } - - public void setMaxAge(int expiry) { - cookie.setMaxAge(expiry, TimeUnit.SECONDS); - } - - public void setPath(String uri) { - cookie.setPath(uri); - } - - public void setSecure(boolean flag) { - cookie.setSecure(flag); - } - - public void setValue(String newValue) { - cookie.setValue(newValue); - } - - /** - * Return com.yahoo.jdisc.http.Cookie - * - * @return - cookie - */ - public Cookie getCookie() { - return cookie; - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java deleted file mode 100644 index f8d9e6b2642..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.servlet.ServletRequest; - -import java.net.URI; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Optional; - -/** - * JDisc implementation of a filter request. - * - * @since 5.27 - */ -public class JdiscFilterRequest extends DiscFilterRequest { - - private final HttpRequest parent; - - public JdiscFilterRequest(HttpRequest parent) { - super(parent); - this.parent = parent; - } - - public HttpRequest getParentRequest() { - return parent; - } - - public void setUri(URI uri) { - parent.setUri(uri); - } - - @Override - public String getMethod() { - return parent.getMethod().name(); - } - - @Override - public String getParameter(String name) { - if(parent.parameters().containsKey(name)) { - return parent.parameters().get(name).get(0); - } - else { - return null; - } - } - - @Override - public Enumeration<String> getParameterNames() { - return Collections.enumeration(parent.parameters().keySet()); - } - - @Override - public void addHeader(String name, String value) { - parent.headers().add(name, value); - } - - @Override - public String getHeader(String name) { - List<String> values = parent.headers().get(name); - if (values == null || values.isEmpty()) { - return null; - } - return values.get(values.size() - 1); - } - - public Enumeration<String> getHeaderNames() { - return Collections.enumeration(parent.headers().keySet()); - } - - public List<String> getHeaderNamesAsList() { - return new ArrayList<String>(parent.headers().keySet()); - } - - @Override - public Enumeration<String> getHeaders(String name) { - return Collections.enumeration(getHeadersAsList(name)); - } - - public List<String> getHeadersAsList(String name) { - List<String> values = parent.headers().get(name); - if(values == null) { - return Collections.<String>emptyList(); - } - return parent.headers().get(name); - } - - @Override - public void removeHeaders(String name) { - parent.headers().remove(name); - } - - @Override - public void setHeaders(String name, String value) { - parent.headers().put(name, value); - } - - @Override - public void setHeaders(String name, List<String> values) { - parent.headers().put(name, values); - } - - @Override - public Principal getUserPrincipal() { - return parent.getUserPrincipal(); - } - - @Override - public void setUserPrincipal(Principal principal) { - this.parent.setUserPrincipal(principal); - } - - @Override - public List<X509Certificate> getClientCertificateChain() { - return Optional.ofNullable(parent.context().get(ServletRequest.JDISC_REQUEST_X509CERT)) - .map(X509Certificate[].class::cast) - .map(Arrays::asList) - .orElse(Collections.emptyList()); - } - - @Override - public void clearCookies() { - parent.headers().remove(HttpHeaders.Names.COOKIE); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java deleted file mode 100644 index ff81359f93c..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpResponse; - -import java.util.List; - -/** - * JDisc implementation of a filter request. - * - * @since 5.27 - */ -public class JdiscFilterResponse extends DiscFilterResponse { - - private final HttpResponse parent; - - public JdiscFilterResponse(HttpResponse parent) { - super(parent); - this.parent = parent; - } - - @Override - public void setStatus(int status) { - parent.setStatus(status); - } - - @Override - public void setHeader(String name, String value) { - parent.headers().put(name, value); - } - - @Override - public void removeHeaders(String name) { - parent.headers().remove(name); - } - - @Override - public void setHeaders(String name, String value) { - parent.headers().put(name, value); - } - - @Override - public void setHeaders(String name, List<String> values) { - parent.headers().put(name, values); - } - - @Override - public void addHeader(String name, String value) { - parent.headers().add(name, value); - } - - @Override - public String getHeader(String name) { - List<String> values = parent.headers().get(name); - if (values == null || values.isEmpty()) { - return null; - } - return values.get(values.size() - 1); - } - - @Override - public void setCookies(List<Cookie> cookies) { - parent.encodeSetCookieHeader(cookies); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java deleted file mode 100644 index 977e3ab5d1d..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; - -/** - * @author Einar M R Rosenvinge - */ -public interface RequestFilter extends com.yahoo.jdisc.SharedResource, RequestFilterBase { - - void filter(HttpRequest request, ResponseHandler handler); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java deleted file mode 100644 index 4eb7091f378..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -/** - * @author gjoranv - * @since 2.4 - */ -public interface RequestFilterBase { -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java deleted file mode 100644 index e5e7ae1ef56..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.http.HttpRequest.Method; - -import java.net.URI; -import java.util.List; -import java.util.Optional; - -/** - * Read-only view of the request for use by SecurityResponseFilters. - * - * @author Tony Vaagenes - */ -public interface RequestView { - - /** - * Returns a named attribute. - * - * @see <a href="http://docs.oracle.com/javaee/7/api/javax/servlet/ServletRequest.html#getAttribute%28java.lang.String%29">javax.servlet.ServletRequest.getAttribute(java.lang.String)</a> - * @see com.yahoo.jdisc.Request#context() - * @return the named data associated with the request that are private to this runtime (not exposed to the client) - */ - Object getAttribute(String name); - - /** - * Returns an immutable view of all values of a named header field. - * Returns an empty list if no such header is present. - */ - List<String> getHeaders(String name); - - /** - * Convenience method for retrieving the first value of a named header field. - * Returns empty if the header is not set, or if the value list is empty. - */ - Optional<String> getFirstHeader(String name); - - /** - * Returns the Http method. Only present if the underlying request has http-like semantics. - */ - Optional<Method> getMethod(); - - URI getUri(); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java deleted file mode 100644 index 44fe7d9fcf1..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.SharedResource; - -/** - * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> - */ -public interface ResponseFilter extends SharedResource, ResponseFilterBase { - - public void filter(Response response, Request request); -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java deleted file mode 100644 index b869c882351..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -/** - * @author gjoranv - * @since 2.4 - */ -public interface ResponseFilterBase { -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java deleted file mode 100644 index cbed273b7ee..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.google.common.annotations.Beta; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest.Method; -import com.yahoo.jdisc.http.servlet.ServletRequest; - -import com.yahoo.jdisc.http.servlet.ServletResponse; -import com.yahoo.jdisc.http.server.jetty.FilterInvoker; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.net.URI; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -/** - * Only intended for internal vespa use. - * - * Runs JDisc security filter without using JDisc request/response. - * Only intended to be used in a servlet context, as the error messages are tailored for that. - * - * Assumes that SecurityResponseFilters mutate DiscFilterResponse in the thread they are invoked from. - * - * @author Tony Vaagenes - */ -@Beta -public class SecurityFilterInvoker implements FilterInvoker { - - /** - * Returns the servlet request to be used in any servlets invoked after this. - */ - @Override - public HttpServletRequest invokeRequestFilterChain(RequestFilter requestFilterChain, - URI uri, HttpServletRequest httpRequest, - ResponseHandler responseHandler) { - - SecurityRequestFilterChain securityChain = cast(SecurityRequestFilterChain.class, requestFilterChain). - orElseThrow(SecurityFilterInvoker::newUnsupportedOperationException); - - ServletRequest wrappedRequest = new ServletRequest(httpRequest, uri); - securityChain.filter(new ServletFilterRequest(wrappedRequest), responseHandler); - return wrappedRequest; - } - - @Override - public void invokeResponseFilterChain( - ResponseFilter responseFilterChain, - URI uri, - HttpServletRequest request, - HttpServletResponse response) { - - SecurityResponseFilterChain securityChain = cast(SecurityResponseFilterChain.class, responseFilterChain). - orElseThrow(SecurityFilterInvoker::newUnsupportedOperationException); - - ServletFilterResponse wrappedResponse = new ServletFilterResponse(new ServletResponse(response)); - securityChain.filter(new ServletRequestView(uri, request), wrappedResponse); - } - - private static UnsupportedOperationException newUnsupportedOperationException() { - return new UnsupportedOperationException( - "Filter type not supported. If a request is handled by servlets or jax-rs, then any filters invoked for that request must be security filters."); - } - - private <T> Optional<T> cast(Class<T> securityFilterChainClass, Object filter) { - return (securityFilterChainClass.isInstance(filter))? - Optional.of(securityFilterChainClass.cast(filter)): - Optional.empty(); - } - - private static class ServletRequestView implements RequestView { - private final HttpServletRequest request; - private final URI uri; - - public ServletRequestView(URI uri, HttpServletRequest request) { - this.request = request; - this.uri = uri; - } - - @Override - public Object getAttribute(String name) { - return request.getAttribute(name); - } - - @Override - public List<String> getHeaders(String name) { - return Collections.unmodifiableList(Collections.list(request.getHeaders(name))); - } - - @Override - public Optional<String> getFirstHeader(String name) { - return getHeaders(name).stream().findFirst(); - } - - @Override - public Optional<Method> getMethod() { - return Optional.of(Method.valueOf(request.getMethod())); - } - - @Override - public URI getUri() { - return uri; - } - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java deleted file mode 100644 index e6f4add49de..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.handler.ResponseHandler; - -/** - * @author Simon Thoresen Hult - */ -public interface SecurityRequestFilter extends RequestFilterBase { - - void filter(DiscFilterRequest request, ResponseHandler handler); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java deleted file mode 100644 index 2d97bbdc494..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.AbstractResource; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; - -import com.yahoo.jdisc.http.HttpRequest; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Implementation of TypedFilterChain for DiscFilterRequest - * - * @author tejalk - */ -public final class SecurityRequestFilterChain extends AbstractResource implements RequestFilter { - - private final List<SecurityRequestFilter> filters = new ArrayList<>(); - - private SecurityRequestFilterChain(Iterable<? extends SecurityRequestFilter> filters) { - for (SecurityRequestFilter filter : filters) { - this.filters.add(filter); - } - } - - @Override - public void filter(HttpRequest request, ResponseHandler responseHandler) { - DiscFilterRequest discFilterRequest = new JdiscFilterRequest(request); - filter(discFilterRequest, responseHandler); - } - - public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { - ResponseHandlerGuard guard = new ResponseHandlerGuard(responseHandler); - for (int i = 0, len = filters.size(); i < len && !guard.isDone(); ++i) { - filters.get(i).filter(request, guard); - } - } - - public static RequestFilter newInstance(SecurityRequestFilter... filters) { - return newInstance(Arrays.asList(filters)); - } - - public static RequestFilter newInstance(List<? extends SecurityRequestFilter> filters) { - return new SecurityRequestFilterChain(filters); - } - - private static class ResponseHandlerGuard implements ResponseHandler { - - private final ResponseHandler responseHandler; - private boolean done = false; - - public ResponseHandlerGuard(ResponseHandler handler) { - this.responseHandler = handler; - } - - @Override - public ContentChannel handleResponse(Response response) { - done = true; - return responseHandler.handleResponse(response); - } - - public boolean isDone() { - return done; - } - } - - /** Returns an unmodifiable view of the filters in this */ - public List<SecurityRequestFilter> getFilters() { - return Collections.unmodifiableList(filters); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java deleted file mode 100644 index aa4f7d29b89..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -public interface SecurityResponseFilter extends ResponseFilterBase { - - void filter(DiscFilterResponse response, RequestView request); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java deleted file mode 100644 index d45b406a375..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import com.yahoo.jdisc.AbstractResource; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.HttpResponse; - -/** - * Implementation of TypedFilterChain for DiscFilterResponse - * @author tejalk - * - */ -public class SecurityResponseFilterChain extends AbstractResource implements ResponseFilter { - - private final List<SecurityResponseFilter> filters = new ArrayList<>(); - - private SecurityResponseFilterChain(Iterable<? extends SecurityResponseFilter> filters) { - for (SecurityResponseFilter filter : filters) { - this.filters.add(filter); - } - } - - @Override - public void filter(Response response, Request request) { - if(response instanceof HttpResponse) { - DiscFilterResponse discFilterResponse = new JdiscFilterResponse((HttpResponse)response); - RequestView requestView = new RequestViewImpl(request); - filter(requestView, discFilterResponse); - } - - } - - public void filter(RequestView requestView, DiscFilterResponse response) { - for (SecurityResponseFilter filter : filters) { - filter.filter(response, requestView); - } - } - - public static ResponseFilter newInstance(SecurityResponseFilter... filters) { - return newInstance(Arrays.asList(filters)); - } - - public static ResponseFilter newInstance(List<? extends SecurityResponseFilter> filters) { - return new SecurityResponseFilterChain(filters); - } - - /** Returns an unmodifiable view of the filters in this */ - public List<SecurityResponseFilter> getFilters() { - return Collections.unmodifiableList(filters); - } - - static class RequestViewImpl implements RequestView { - - private final Request request; - private final Optional<HttpRequest.Method> method; - - public RequestViewImpl(Request request) { - this.request = request; - method = request instanceof HttpRequest ? - Optional.of(((HttpRequest) request).getMethod()): - Optional.empty(); - } - - @Override - public Object getAttribute(String name) { - return request.context().get(name); - } - - @Override - public List<String> getHeaders(String name) { - List<String> headers = request.headers().get(name); - return headers == null ? Collections.emptyList() : Collections.unmodifiableList(headers); - } - - @Override - public Optional<String> getFirstHeader(String name) { - return getHeaders(name).stream().findFirst(); - } - - @Override - public Optional<HttpRequest.Method> getMethod() { - return method; - } - - @Override - public URI getUri() { - return request.getUri(); - } - - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java deleted file mode 100644 index f06f9e256ff..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.servlet.ServletRequest; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -/** - * Servlet implementation for JDisc filter requests. - */ -class ServletFilterRequest extends DiscFilterRequest { - - private final ServletRequest parent; - - public ServletFilterRequest(ServletRequest parent) { - super(parent); - this.parent = parent; - } - - ServletRequest getServletRequest() { - return parent; - } - - public void setUri(URI uri) { - parent.setUri(uri); - } - - @Override - public String getMethod() { - return parent.getRequest().getMethod(); - } - - @Override - public void setRemoteAddr(String remoteIpAddress) { - throw new UnsupportedOperationException( - "Setting remote address is not supported for " + this.getClass().getName()); - } - - @Override - public Enumeration<String> getAttributeNames() { - Set<String> names = new HashSet<>(Collections.list(super.getAttributeNames())); - names.addAll(Collections.list(parent.getRequest().getAttributeNames())); - return Collections.enumeration(names); - } - - @Override - public Object getAttribute(String name) { - Object jdiscAttribute = super.getAttribute(name); - return jdiscAttribute != null ? - jdiscAttribute : - parent.getRequest().getAttribute(name); - } - - @Override - public void setAttribute(String name, Object value) { - super.setAttribute(name, value); - parent.getRequest().setAttribute(name, value); - } - - @Override - public boolean containsAttribute(String name) { - return super.containsAttribute(name) - || parent.getRequest().getAttribute(name) != null; - } - - @Override - public void removeAttribute(String name) { - super.removeAttribute(name); - parent.getRequest().removeAttribute(name); - } - - @Override - public String getParameter(String name) { - return parent.getParameter(name); - } - - @Override - public Enumeration<String> getParameterNames() { - return parent.getParameterNames(); - } - - @Override - public void addHeader(String name, String value) { - parent.addHeader(name, value); - } - - @Override - public String getHeader(String name) { - return parent.getHeader(name); - } - - @Override - public Enumeration<String> getHeaderNames() { - return parent.getHeaderNames(); - } - - public List<String> getHeaderNamesAsList() { - return Collections.list(getHeaderNames()); - } - - @Override - public Enumeration<String> getHeaders(String name) { - return parent.getHeaders(name); - } - - @Override - public List<String> getHeadersAsList(String name) { - return Collections.list(getHeaders(name)); - } - - @Override - public void setHeaders(String name, String value) { - parent.setHeaders(name, value); - } - - @Override - public void setHeaders(String name, List<String> values) { - parent.setHeaders(name, values); - } - - @Override - public Principal getUserPrincipal() { - return parent.getUserPrincipal(); - } - - @Override - public void setUserPrincipal(Principal principal) { - parent.setUserPrincipal(principal); - } - - @Override - public List<X509Certificate> getClientCertificateChain() { - return Optional.ofNullable(parent.getRequest().getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT)) - .map(X509Certificate[].class::cast) - .map(Arrays::asList) - .orElse(Collections.emptyList()); - } - - @Override - public void removeHeaders(String name) { - parent.removeHeaders(name); - } - - @Override - public void clearCookies() { - parent.removeHeaders(HttpHeaders.Names.COOKIE); - } - - @Override - public void setCharacterEncoding(String encoding) { - super.setCharacterEncoding(encoding); - try { - parent.setCharacterEncoding(encoding); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Encoding not supported: " + encoding, e); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java deleted file mode 100644 index b603e7776f1..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter; - -import com.google.common.collect.Iterables; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.servlet.ServletResponse; - -import javax.servlet.http.HttpServletResponse; -import java.util.Collection; -import java.util.List; - -/** - * Servlet implementation for JDisc filter responses. - */ -class ServletFilterResponse extends DiscFilterResponse { - - private final ServletResponse parent; - - public ServletFilterResponse(ServletResponse parent) { - super(parent); - this.parent = parent; - } - - ServletResponse getServletResponse() { - return parent; - } - - public void setStatus(int status) { - parent.setStatus(status); - } - - @Override - public void setHeader(String name, String value) { - parent.setHeader(name, value); - } - - @Override - public void removeHeaders(String name) { - HttpServletResponse parentResponse = parent.getResponse(); - if (parentResponse instanceof org.eclipse.jetty.server.Response) { - org.eclipse.jetty.server.Response jettyResponse = (org.eclipse.jetty.server.Response)parentResponse; - jettyResponse.getHttpFields().remove(name); - } else { - throw new UnsupportedOperationException( - "Cannot remove headers for response of type " + parentResponse.getClass().getName()); - } - } - - // Why have a setHeaders that takes a single string? - @Override - public void setHeaders(String name, String value) { - parent.setHeader(name, value); - } - - @Override - public void setHeaders(String name, List<String> values) { - for (String value : values) - parent.addHeader(name, value); - } - - @Override - public void addHeader(String name, String value) { - parent.addHeader(name, value); - } - - @Override - public String getHeader(String name) { - Collection<String> headers = parent.getHeaders(name); - return headers.isEmpty() - ? null - : Iterables.getLast(headers); - } - - @Override - public void setCookies(List<Cookie> cookies) { - removeHeaders(HttpHeaders.Names.SET_COOKIE); - List<String> setCookieHeaders = Cookie.toSetCookieHeaders(cookies); - setCookieHeaders.forEach(cookie -> addHeader(HttpHeaders.Names.SET_COOKIE, cookie)); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java deleted file mode 100644 index e1834fd8b7d..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter.chain; - -import com.yahoo.jdisc.NoopSharedResource; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; - -/** - * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> - */ -public final class EmptyRequestFilter extends NoopSharedResource implements RequestFilter { - - public static final RequestFilter INSTANCE = new EmptyRequestFilter(); - - private EmptyRequestFilter() { - // hide - } - - @Override - public void filter(HttpRequest request, ResponseHandler handler) { - - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java deleted file mode 100644 index 5ce3f6a496f..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter.chain; - -import com.yahoo.jdisc.NoopSharedResource; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -/** - * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> - */ -public final class EmptyResponseFilter extends NoopSharedResource implements ResponseFilter { - - public static final ResponseFilter INSTANCE = new EmptyResponseFilter(); - - private EmptyResponseFilter() { - // hide - } - - @Override - public void filter(Response response, Request request) { - - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java deleted file mode 100644 index 85f71777cf3..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter.chain; - -import com.yahoo.jdisc.AbstractResource; -import com.yahoo.jdisc.application.ResourcePool; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> - */ -public final class RequestFilterChain extends AbstractResource implements RequestFilter { - - private final List<RequestFilter> filters = new ArrayList<>(); - private final ResourcePool filterReferences = new ResourcePool(); - - private RequestFilterChain(Iterable<? extends RequestFilter> filters) { - for (RequestFilter filter : filters) { - this.filters.add(filter); - filterReferences.retain(filter); - } - } - - @Override - public void filter(HttpRequest request, ResponseHandler responseHandler) { - ResponseHandlerGuard guard = new ResponseHandlerGuard(responseHandler); - for (int i = 0, len = filters.size(); i < len && !guard.isDone(); ++i) { - filters.get(i).filter(request, guard); - } - } - - @Override - protected void destroy() { - filterReferences.release(); - } - - public static RequestFilter newInstance(RequestFilter... filters) { - return newInstance(Arrays.asList(filters)); - } - - public static RequestFilter newInstance(List<? extends RequestFilter> filters) { - if (filters.size() == 0) { - return EmptyRequestFilter.INSTANCE; - } - if (filters.size() == 1) { - return filters.get(0); - } - return new RequestFilterChain(filters); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java deleted file mode 100644 index 5c5eda1f139..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter.chain; - -import com.yahoo.jdisc.AbstractResource; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.application.ResourcePool; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * @author Simon Thoresen Hult - */ -public final class ResponseFilterChain extends AbstractResource implements ResponseFilter { - - private final List<ResponseFilter> filters = new ArrayList<>(); - private final ResourcePool filterReferences = new ResourcePool(); - - private ResponseFilterChain(Iterable<? extends ResponseFilter> filters) { - for (ResponseFilter filter : filters) { - this.filters.add(filter); - filterReferences.retain(filter); - } - } - - @Override - public void filter(Response response, Request request) { - for (ResponseFilter filter : filters) { - filter.filter(response, request); - } - } - - @Override - protected void destroy() { - filterReferences.release(); - } - - public static ResponseFilter newInstance(ResponseFilter... filters) { - return newInstance(Arrays.asList(filters)); - } - - public static ResponseFilter newInstance(List<? extends ResponseFilter> filters) { - if (filters.size() == 0) { - return EmptyResponseFilter.INSTANCE; - } - if (filters.size() == 1) { - return filters.get(0); - } - return new ResponseFilterChain(filters); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java deleted file mode 100644 index 02600683e27..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.filter.chain; - -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; - -/** - * @author Simon Thoresen Hult - */ -final class ResponseHandlerGuard implements ResponseHandler { - - private final ResponseHandler responseHandler; - private boolean done = false; - - public ResponseHandlerGuard(ResponseHandler handler) { - this.responseHandler = handler; - } - - @Override - public ContentChannel handleResponse(Response response) { - done = true; - return responseHandler.handleResponse(response); - } - - public boolean isDone() { - return done; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java deleted file mode 100644 index 540a1be7b73..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@ExportPackage -package com.yahoo.jdisc.http.filter.chain; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/package-info.java deleted file mode 100644 index e97d447adbb..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@PublicApi -@ExportPackage -package com.yahoo.jdisc.http.filter; - -import com.yahoo.api.annotations.PublicApi; -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/package-info.java deleted file mode 100644 index b8bd76483cf..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@PublicApi -@ExportPackage -package com.yahoo.jdisc.http; - -import com.yahoo.api.annotations.PublicApi; -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java deleted file mode 100644 index 4de5e5e5387..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.common.base.Objects; -import com.yahoo.container.logging.AccessLog; -import com.yahoo.container.logging.AccessLogEntry; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.servlet.ServletRequest; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.util.component.AbstractLifeCycle; - -import javax.servlet.http.HttpServletRequest; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.OptionalInt; -import java.util.UUID; -import java.util.function.BiConsumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnectorLocalPort; - -/** - * This class is a bridge between Jetty's {@link org.eclipse.jetty.server.handler.RequestLogHandler} - * and our own configurable access logging in different formats provided by {@link AccessLog}. - * - * @author Oyvind Bakksjo - * @author bjorncs - */ -class AccessLogRequestLog extends AbstractLifeCycle implements org.eclipse.jetty.server.RequestLog { - - private static final Logger logger = Logger.getLogger(AccessLogRequestLog.class.getName()); - - // HTTP headers that are logged as extra key-value-pairs in access log entries - private static final List<String> LOGGED_REQUEST_HEADERS = List.of("Vespa-Client-Version"); - - private final RequestLog requestLog; - private final List<String> remoteAddressHeaders; - private final List<String> remotePortHeaders; - - AccessLogRequestLog(RequestLog requestLog, ServerConfig.AccessLog config) { - this.requestLog = requestLog; - this.remoteAddressHeaders = config.remoteAddressHeaders(); - this.remotePortHeaders = config.remotePortHeaders(); - } - - @Override - public void log(Request request, Response response) { - try { - RequestLogEntry.Builder builder = new RequestLogEntry.Builder(); - - String peerAddress = request.getRemoteAddr(); - int peerPort = request.getRemotePort(); - long startTime = request.getTimeStamp(); - long endTime = System.currentTimeMillis(); - builder.peerAddress(peerAddress) - .peerPort(peerPort) - .localPort(getLocalPort(request)) - .timestamp(Instant.ofEpochMilli(startTime)) - .duration(Duration.ofMillis(Math.max(0, endTime - startTime))) - .contentSize(response.getHttpChannel().getBytesWritten()) - .statusCode(response.getCommittedMetaData().getStatus()); - - addNonNullValue(builder, request.getMethod(), RequestLogEntry.Builder::httpMethod); - addNonNullValue(builder, request.getRequestURI(), RequestLogEntry.Builder::rawPath); - addNonNullValue(builder, request.getProtocol(), RequestLogEntry.Builder::httpVersion); - addNonNullValue(builder, request.getScheme(), RequestLogEntry.Builder::scheme); - addNonNullValue(builder, request.getHeader("User-Agent"), RequestLogEntry.Builder::userAgent); - addNonNullValue(builder, request.getHeader("Host"), RequestLogEntry.Builder::hostString); - addNonNullValue(builder, request.getHeader("Referer"), RequestLogEntry.Builder::referer); - addNonNullValue(builder, request.getQueryString(), RequestLogEntry.Builder::rawQuery); - - Principal principal = (Principal) request.getAttribute(ServletRequest.JDISC_REQUEST_PRINCIPAL); - addNonNullValue(builder, principal, RequestLogEntry.Builder::userPrincipal); - - String requestFilterId = (String) request.getAttribute(ServletRequest.JDISC_REQUEST_CHAIN); - addNonNullValue(builder, requestFilterId, (b, chain) -> b.addExtraAttribute("request-chain", chain)); - - String responseFilterId = (String) request.getAttribute(ServletRequest.JDISC_RESPONSE_CHAIN); - addNonNullValue(builder, responseFilterId, (b, chain) -> b.addExtraAttribute("response-chain", chain)); - - UUID connectionId = (UUID) request.getAttribute(JettyConnectionLogger.CONNECTION_ID_REQUEST_ATTRIBUTE); - addNonNullValue(builder, connectionId, (b, uuid) -> b.connectionId(uuid.toString())); - - String remoteAddress = getRemoteAddress(request); - if (!Objects.equal(remoteAddress, peerAddress)) { - builder.remoteAddress(remoteAddress); - } - int remotePort = getRemotePort(request); - if (remotePort != peerPort) { - builder.remotePort(remotePort); - } - LOGGED_REQUEST_HEADERS.forEach(header -> { - String value = request.getHeader(header); - if (value != null) { - builder.addExtraAttribute(header, value); - } - }); - X509Certificate[] clientCert = (X509Certificate[]) request.getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT); - if (clientCert != null && clientCert.length > 0) { - builder.sslPrincipal(clientCert[0].getSubjectX500Principal()); - } - - AccessLogEntry accessLogEntry = (AccessLogEntry) request.getAttribute(JDiscHttpServlet.ATTRIBUTE_NAME_ACCESS_LOG_ENTRY); - if (accessLogEntry != null) { - var extraAttributes = accessLogEntry.getKeyValues(); - if (extraAttributes != null) { - extraAttributes.forEach(builder::addExtraAttributes); - } - addNonNullValue(builder, accessLogEntry.getHitCounts(), RequestLogEntry.Builder::hitCounts); - addNonNullValue(builder, accessLogEntry.getTrace(), RequestLogEntry.Builder::traceNode); - } - - requestLog.log(builder.build()); - } catch (Exception e) { - // Catching any exceptions here as it is unclear how Jetty handles exceptions from a RequestLog. - logger.log(Level.SEVERE, "Failed to log access log entry: " + e.getMessage(), e); - } - } - - private String getRemoteAddress(HttpServletRequest request) { - for (String header : remoteAddressHeaders) { - String value = request.getHeader(header); - if (value != null) return value; - } - return request.getRemoteAddr(); - } - - private int getRemotePort(HttpServletRequest request) { - for (String header : remotePortHeaders) { - String value = request.getHeader(header); - if (value != null) { - OptionalInt maybePort = parsePort(value); - if (maybePort.isPresent()) return maybePort.getAsInt(); - } - } - return request.getRemotePort(); - } - - private static int getLocalPort(Request request) { - int connectorLocalPort = getConnectorLocalPort(request); - if (connectorLocalPort <= 0) return request.getLocalPort(); // If connector is already closed - return connectorLocalPort; - } - - private static OptionalInt parsePort(String port) { - try { - return OptionalInt.of(Integer.parseInt(port)); - } catch (IllegalArgumentException e) { - return OptionalInt.empty(); - } - } - - private static <T> void addNonNullValue( - RequestLogEntry.Builder builder, T value, BiConsumer<RequestLogEntry.Builder, T> setter) { - if (value != null) { - setter.accept(builder, value); - } - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java deleted file mode 100644 index 842ab75a312..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.common.base.Preconditions; -import com.yahoo.container.logging.AccessLogEntry; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; - -import java.util.Map; -import java.util.Optional; - -/** - * A wrapper RequestHandler that enables access logging. By wrapping the request handler, we are able to wrap the - * response handler as well. Hence, we can populate the access log entry with information from both the request - * and the response. This wrapper also adds the access log entry to the request context, so that request handlers - * may add information to it. - * - * Does not otherwise interfere with the request processing of the delegate request handler. - * - * @author bakksjo - */ -public class AccessLoggingRequestHandler extends AbstractRequestHandler { - public static final String CONTEXT_KEY_ACCESS_LOG_ENTRY - = AccessLoggingRequestHandler.class.getName() + "_access-log-entry"; - - public static Optional<AccessLogEntry> getAccessLogEntry(final HttpRequest jdiscRequest) { - final Map<String, Object> requestContextMap = jdiscRequest.context(); - return getAccessLogEntry(requestContextMap); - } - - public static Optional<AccessLogEntry> getAccessLogEntry(final Map<String, Object> requestContextMap) { - return Optional.ofNullable( - (AccessLogEntry) requestContextMap.get(CONTEXT_KEY_ACCESS_LOG_ENTRY)); - } - - private final RequestHandler delegate; - private final AccessLogEntry accessLogEntry; - - public AccessLoggingRequestHandler( - final RequestHandler delegateRequestHandler, - final AccessLogEntry accessLogEntry) { - this.delegate = delegateRequestHandler; - this.accessLogEntry = accessLogEntry; - } - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { - Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request); - final HttpRequest httpRequest = (HttpRequest) request; - httpRequest.context().put(CONTEXT_KEY_ACCESS_LOG_ENTRY, accessLogEntry); - return delegate.handleRequest(request, handler); - } - - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AsyncCompleteListener.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AsyncCompleteListener.java deleted file mode 100644 index 7dba217e01c..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AsyncCompleteListener.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import javax.servlet.AsyncEvent; -import javax.servlet.AsyncListener; -import java.io.IOException; - -/** - * Interface for async listeners only interested in onComplete. - * @author Tony Vaagenes - */ -@FunctionalInterface -interface AsyncCompleteListener extends AsyncListener { - @Override - default void onTimeout(AsyncEvent event) throws IOException {} - - @Override - default void onError(AsyncEvent event) throws IOException {} - - @Override - default void onStartAsync(AsyncEvent event) throws IOException {} -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/CompletionHandlerUtils.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/CompletionHandlerUtils.java deleted file mode 100644 index f436d5490d7..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/CompletionHandlerUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.handler.CompletionHandler; - -/** - * @author bjorncs - */ -public interface CompletionHandlerUtils { - CompletionHandler NOOP_COMPLETION_HANDLER = new CompletionHandler() { - @Override public void completed() {} - @Override public void failed(final Throwable t) {} - }; -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/CompletionHandlers.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/CompletionHandlers.java deleted file mode 100644 index 975d88f5c34..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/CompletionHandlers.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.handler.CompletionHandler; - -import java.util.Arrays; - -/** - * @author Simon Thoresen Hult - */ -public class CompletionHandlers { - - public static void tryComplete(CompletionHandler handler) { - if (handler == null) { - return; - } - try { - handler.completed(); - } catch (Exception e) { - // ignore - } - } - - public static void tryFail(CompletionHandler handler, Throwable t) { - if (handler == null) { - return; - } - try { - handler.failed(t); - } catch (Exception e) { - // ignore - } - } - - public static CompletionHandler wrap(CompletionHandler... handlers) { - return wrap(Arrays.asList(handlers)); - } - - public static CompletionHandler wrap(final Iterable<CompletionHandler> handlers) { - return new CompletionHandler() { - - @Override - public void completed() { - for (CompletionHandler handler : handlers) { - tryComplete(handler); - } - } - - @Override - public void failed(Throwable t) { - for (CompletionHandler handler : handlers) { - tryFail(handler, t); - } - } - }; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottler.java deleted file mode 100644 index b9001d187a9..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottler.java +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2019 Yahoo Holdings. 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.http.ConnectorConfig; -import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.SelectorManager; -import org.eclipse.jetty.server.AbstractConnector; -import org.eclipse.jetty.server.ConnectionLimit; -import org.eclipse.jetty.server.LowResourceMonitor; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.AbstractLifeCycle; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.util.statistic.RateStatistic; -import org.eclipse.jetty.util.thread.Scheduler; - -import java.nio.channels.SelectableChannel; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; - -import static java.util.stream.Collectors.toList; - -/** - * Monitor various resource constraints and throttles new connections once a threshold is exceeded. - * Implementation inspired by Jetty's {@link LowResourceMonitor}, {@link AcceptRateLimit} and {@link ConnectionLimit}. - * - * @author bjorncs - */ -@ManagedObject("Monitor various resource constraints and throttles new connections once a threshold is exceeded") -class ConnectionThrottler extends ContainerLifeCycle implements SelectorManager.AcceptListener { - - private static final Logger log = Logger.getLogger(ConnectionThrottler.class.getName()); - - private final Object monitor = new Object(); - private final Collection<ResourceLimit> resourceLimits = new ArrayList<>(); - private final AbstractConnector connector; - private final Duration idleTimeout; - private final Scheduler scheduler; - - private boolean isRegistered = false; - private boolean isThrottling = false; - - ConnectionThrottler(AbstractConnector connector, ConnectorConfig.Throttling config) { - this(Runtime.getRuntime(), new RateStatistic(1, TimeUnit.SECONDS), connector.getScheduler(), connector, config); - } - - // Intended for unit testing - ConnectionThrottler(Runtime runtime, - RateStatistic rateStatistic, - Scheduler scheduler, - AbstractConnector connector, - ConnectorConfig.Throttling config) { - this.connector = connector; - if (config.maxHeapUtilization() != -1) { - this.resourceLimits.add(new HeapResourceLimit(runtime, config.maxHeapUtilization())); - } - if (config.maxConnections() != -1) { - this.resourceLimits.add(new ConnectionLimitThreshold(config.maxConnections())); - } - if (config.maxAcceptRate() != -1) { - this.resourceLimits.add(new AcceptRateLimit(rateStatistic, config.maxAcceptRate())); - } - this.idleTimeout = config.idleTimeout() != -1 ? Duration.ofMillis((long) (config.idleTimeout()*1000)) : null; - this.scheduler = scheduler; - } - - void registerWithConnector() { - synchronized (monitor) { - if (isRegistered) return; - isRegistered = true; - resourceLimits.forEach(connector::addBean); - connector.addBean(this); - } - } - - @Override - public void onAccepting(SelectableChannel channel) { - throttleIfAnyThresholdIsExceeded(); - } - - private void throttleIfAnyThresholdIsExceeded() { - synchronized (monitor) { - if (isThrottling) return; - List<String> reasons = getThrottlingReasons(); - if (reasons.isEmpty()) return; - log.warning(String.format("Throttling new connection. Reasons: %s", reasons)); - isThrottling = true; - if (connector.isAccepting()) { - connector.setAccepting(false); - } - if (idleTimeout != null) { - log.warning(String.format("Applying idle timeout to existing connections: timeout=%sms", idleTimeout)); - connector.getConnectedEndPoints() - .forEach(endPoint -> endPoint.setIdleTimeout(idleTimeout.toMillis())); - } - scheduler.schedule(this::unthrottleIfBelowThresholds, 1, TimeUnit.SECONDS); - } - } - - private void unthrottleIfBelowThresholds() { - synchronized (monitor) { - if (!isThrottling) return; - List<String> reasons = getThrottlingReasons(); - if (!reasons.isEmpty()) { - log.warning(String.format("Throttling continued. Reasons: %s", reasons)); - scheduler.schedule(this::unthrottleIfBelowThresholds, 1, TimeUnit.SECONDS); - return; - } - if (idleTimeout != null) { - long originalTimeout = connector.getIdleTimeout(); - log.info(String.format("Reverting idle timeout for existing connections: timeout=%sms", originalTimeout)); - connector.getConnectedEndPoints() - .forEach(endPoint -> endPoint.setIdleTimeout(originalTimeout)); - } - log.info("Throttling disabled - resource thresholds no longer exceeded"); - if (!connector.isAccepting()) { - connector.setAccepting(true); - } - isThrottling = false; - } - } - - private List<String> getThrottlingReasons() { - synchronized (monitor) { - return resourceLimits.stream() - .map(ResourceLimit::isThresholdExceeded) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(toList()); - } - } - - private interface ResourceLimit extends LifeCycle, SelectorManager.AcceptListener, Connection.Listener { - /** - * @return A string containing the reason if threshold exceeded, empty otherwise. - */ - Optional<String> isThresholdExceeded(); - - @Override default void onOpened(Connection connection) {} - - @Override default void onClosed(Connection connection) {} - } - - /** - * Note: implementation inspired by Jetty's {@link LowResourceMonitor} - */ - private static class HeapResourceLimit extends AbstractLifeCycle implements ResourceLimit { - private final Runtime runtime; - private final double maxHeapUtilization; - - HeapResourceLimit(Runtime runtime, double maxHeapUtilization) { - this.runtime = runtime; - this.maxHeapUtilization = maxHeapUtilization; - } - - @Override - public Optional<String> isThresholdExceeded() { - double heapUtilization = (runtime.maxMemory() - runtime.freeMemory()) / (double) runtime.maxMemory(); - if (heapUtilization > maxHeapUtilization) { - return Optional.of(String.format("Max heap utilization exceeded: %f%%>%f%%", heapUtilization*100, maxHeapUtilization*100)); - } - return Optional.empty(); - } - } - - /** - * Note: implementation inspired by Jetty's {@link org.eclipse.jetty.server.AcceptRateLimit} - */ - private static class AcceptRateLimit extends AbstractLifeCycle implements ResourceLimit { - private final Object monitor = new Object(); - private final RateStatistic rateStatistic; - private final int maxAcceptRate; - - AcceptRateLimit(RateStatistic rateStatistic, int maxAcceptRate) { - this.rateStatistic = rateStatistic; - this.maxAcceptRate = maxAcceptRate; - } - - @Override - public Optional<String> isThresholdExceeded() { - synchronized (monitor) { - int acceptRate = rateStatistic.getRate(); - if (acceptRate > maxAcceptRate) { - return Optional.of(String.format("Max accept rate exceeded: %d>%d", acceptRate, maxAcceptRate)); - } - return Optional.empty(); - } - } - - @Override - public void onAccepting(SelectableChannel channel) { - synchronized (monitor) { - rateStatistic.record(); - } - } - - @Override - protected void doStop() { - synchronized (monitor) { - rateStatistic.reset(); - } - } - } - - /** - * Note: implementation inspired by Jetty's {@link ConnectionLimit}. - */ - private static class ConnectionLimitThreshold extends AbstractLifeCycle implements ResourceLimit { - private final Object monitor = new Object(); - private final int maxConnections; - private final Set<SelectableChannel> connectionsAccepting = new HashSet<>(); - private int connectionOpened; - - ConnectionLimitThreshold(int maxConnections) { - this.maxConnections = maxConnections; - } - - @Override - public Optional<String> isThresholdExceeded() { - synchronized (monitor) { - int totalConnections = connectionOpened + connectionsAccepting.size(); - if (totalConnections > maxConnections) { - return Optional.of(String.format("Max connection exceeded: %d>%d", totalConnections, maxConnections)); - } - return Optional.empty(); - } - } - - @Override - public void onOpened(Connection connection) { - synchronized (monitor) { - connectionsAccepting.remove(connection.getEndPoint().getTransport()); - ++connectionOpened; - } - } - - @Override - public void onClosed(Connection connection) { - synchronized (monitor) { - --connectionOpened; - } - } - - @Override - public void onAccepting(SelectableChannel channel) { - synchronized (monitor) { - connectionsAccepting.add(channel); - } - - } - - @Override - public void onAcceptFailed(SelectableChannel channel, Throwable cause) { - synchronized (monitor) { - connectionsAccepting.remove(channel); - } - } - - @Override - protected void doStop() { - synchronized (monitor) { - connectionsAccepting.clear(); - connectionOpened = 0; - } - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java deleted file mode 100644 index d7ad12a5c64..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.inject.Inject; -import com.yahoo.jdisc.Metric; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; -import com.yahoo.security.tls.MixedMode; -import com.yahoo.security.tls.TransportSecurityUtils; -import org.eclipse.jetty.server.ConnectionFactory; -import org.eclipse.jetty.server.DetectorConnectionFactory; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.ProxyConnectionFactory; -import org.eclipse.jetty.server.SecureRequestCustomizer; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import java.util.List; - -/** - * @author Einar M R Rosenvinge - * @author bjorncs - */ -public class ConnectorFactory { - - private final ConnectorConfig connectorConfig; - private final SslContextFactoryProvider sslContextFactoryProvider; - - @Inject - public ConnectorFactory(ConnectorConfig connectorConfig, - SslContextFactoryProvider sslContextFactoryProvider) { - runtimeConnectorConfigValidation(connectorConfig); - this.connectorConfig = connectorConfig; - this.sslContextFactoryProvider = sslContextFactoryProvider; - } - - // Perform extra connector config validation that can only be performed at runtime, - // e.g. due to TLS configuration through environment variables. - private static void runtimeConnectorConfigValidation(ConnectorConfig config) { - validateProxyProtocolConfiguration(config); - validateSecureRedirectConfig(config); - } - - private static void validateProxyProtocolConfiguration(ConnectorConfig config) { - ConnectorConfig.ProxyProtocol proxyProtocolConfig = config.proxyProtocol(); - if (proxyProtocolConfig.enabled()) { - boolean tlsMixedModeEnabled = TransportSecurityUtils.getInsecureMixedMode() != MixedMode.DISABLED; - if (!isSslEffectivelyEnabled(config) || tlsMixedModeEnabled) { - throw new IllegalArgumentException("Proxy protocol can only be enabled if connector is effectively HTTPS only"); - } - } - } - - private static void validateSecureRedirectConfig(ConnectorConfig config) { - if (config.secureRedirect().enabled() && isSslEffectivelyEnabled(config)) { - throw new IllegalArgumentException("Secure redirect can only be enabled on connectors without HTTPS"); - } - } - - public ConnectorConfig getConnectorConfig() { - return connectorConfig; - } - - public ServerConnector createConnector(final Metric metric, final Server server, JettyConnectionLogger connectionLogger) { - ServerConnector connector = new JDiscServerConnector( - connectorConfig, metric, server, connectionLogger, createConnectionFactories(metric).toArray(ConnectionFactory[]::new)); - connector.setPort(connectorConfig.listenPort()); - connector.setName(connectorConfig.name()); - connector.setAcceptQueueSize(connectorConfig.acceptQueueSize()); - connector.setReuseAddress(connectorConfig.reuseAddress()); - connector.setIdleTimeout((long)(connectorConfig.idleTimeout() * 1000.0)); - return connector; - } - - private List<ConnectionFactory> createConnectionFactories(Metric metric) { - HttpConnectionFactory httpFactory = newHttpConnectionFactory(); - if (!isSslEffectivelyEnabled(connectorConfig)) { - return List.of(httpFactory); - } else if (connectorConfig.ssl().enabled()) { - return connectionFactoriesForHttps(metric, httpFactory); - } else if (TransportSecurityUtils.isTransportSecurityEnabled()) { - switch (TransportSecurityUtils.getInsecureMixedMode()) { - case TLS_CLIENT_MIXED_SERVER: - case PLAINTEXT_CLIENT_MIXED_SERVER: - return List.of(new DetectorConnectionFactory(newSslConnectionFactory(metric, httpFactory)), httpFactory); - case DISABLED: - return connectionFactoriesForHttps(metric, httpFactory); - default: - throw new IllegalStateException(); - } - } else { - return List.of(httpFactory); - } - } - - private List<ConnectionFactory> connectionFactoriesForHttps(Metric metric, HttpConnectionFactory httpFactory) { - ConnectorConfig.ProxyProtocol proxyProtocolConfig = connectorConfig.proxyProtocol(); - SslConnectionFactory sslFactory = newSslConnectionFactory(metric, httpFactory); - if (proxyProtocolConfig.enabled()) { - if (proxyProtocolConfig.mixedMode()) { - return List.of(new DetectorConnectionFactory(sslFactory, new ProxyConnectionFactory(sslFactory.getProtocol())), sslFactory, httpFactory); - } else { - return List.of(new ProxyConnectionFactory(sslFactory.getProtocol()), sslFactory, httpFactory); - } - } else { - return List.of(sslFactory, httpFactory); - } - } - - private HttpConnectionFactory newHttpConnectionFactory() { - HttpConfiguration httpConfig = new HttpConfiguration(); - httpConfig.setSendDateHeader(true); - httpConfig.setSendServerVersion(false); - httpConfig.setSendXPoweredBy(false); - httpConfig.setHeaderCacheSize(connectorConfig.headerCacheSize()); - httpConfig.setOutputBufferSize(connectorConfig.outputBufferSize()); - httpConfig.setRequestHeaderSize(connectorConfig.requestHeaderSize()); - httpConfig.setResponseHeaderSize(connectorConfig.responseHeaderSize()); - if (isSslEffectivelyEnabled(connectorConfig)) { - httpConfig.addCustomizer(new SecureRequestCustomizer()); - } - return new HttpConnectionFactory(httpConfig); - } - - private SslConnectionFactory newSslConnectionFactory(Metric metric, HttpConnectionFactory httpFactory) { - SslContextFactory ctxFactory = sslContextFactoryProvider.getInstance(connectorConfig.name(), connectorConfig.listenPort()); - SslConnectionFactory connectionFactory = new SslConnectionFactory(ctxFactory, httpFactory.getProtocol()); - connectionFactory.addBean(new SslHandshakeFailedListener(metric, connectorConfig.name(), connectorConfig.listenPort())); - return connectionFactory; - } - - private static boolean isSslEffectivelyEnabled(ConnectorConfig config) { - return config.ssl().enabled() - || (config.implicitTlsEnabled() && TransportSecurityUtils.isTransportSecurityEnabled()); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreator.java deleted file mode 100644 index cd21dccde0e..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreator.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import org.eclipse.jetty.util.ByteArrayISO8859Writer; -import org.eclipse.jetty.util.StringUtil; - -import java.io.IOException; -import java.util.Optional; - -/** - * Creates HTML body having the status code, error message and request uri. - * The body is constructed from a template that is inspired by the default Jetty template (see {@link org.eclipse.jetty.server.Response#sendError(int, String)}). - * The content is written using the ISO-8859-1 charset. - * - * @author bjorncs - */ -public class ErrorResponseContentCreator { - - private final ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(2048); - - public byte[] createErrorContent(String requestUri, int statusCode, Optional<String> message) { - String sanitizedString = message.map(StringUtil::sanitizeXmlString).orElse(""); - String statusCodeString = Integer.toString(statusCode); - writer.resetWriter(); - try { - writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n<title>Error "); - writer.write(statusCodeString); - writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: "); - writer.write(statusCodeString); - writer.write("</h2>\n<p>Problem accessing "); - writer.write(StringUtil.sanitizeXmlString(requestUri)); - writer.write(". Reason:\n<pre> "); - writer.write(sanitizedString); - writer.write("</pre></p>\n<hr/>\n</body>\n</html>\n"); - } catch (IOException e) { - // IOException should not be thrown unless writer is constructed using byte[] parameter - throw new RuntimeException(e); - } - return writer.getByteArray(); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ExceptionWrapper.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ExceptionWrapper.java deleted file mode 100644 index ebc10482600..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ExceptionWrapper.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -/** - * A wrapper to make exceptions leaking into Jetty easier to track. Jetty - * swallows all information about where an exception was thrown, so this wrapper - * ensures some extra information is automatically added to the contents of - * getMessage(). - * - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> - */ -public class ExceptionWrapper extends RuntimeException { - private final String message; - - /** - * Update if serializable contents are added. - */ - private static final long serialVersionUID = 1L; - - public ExceptionWrapper(Throwable t) { - super(t); - this.message = formatMessage(t); - } - - // If calling methods from the constructor, it makes life easier if the - // methods are static... - private static String formatMessage(final Throwable t) { - StringBuilder b = new StringBuilder(); - Throwable cause = t; - while (cause != null) { - StackTraceElement[] trace = cause.getStackTrace(); - String currentMsg = cause.getMessage(); - - if (b.length() > 0) { - b.append(": "); - } - b.append(t.getClass().getSimpleName()).append('('); - if (currentMsg != null) { - b.append('"').append(currentMsg).append('"'); - } - b.append(')'); - if (trace.length > 0) { - b.append(" at ").append(trace[0].getClassName()).append('('); - if (trace[0].getFileName() != null) { - b.append(trace[0].getFileName()).append(':') - .append(trace[0].getLineNumber()); - } - b.append(')'); - } - cause = cause.getCause(); - } - return b.toString(); - } - - @Override - public String getMessage() { - return message; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java deleted file mode 100644 index 310f3c9a646..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.application.BindingRepository; -import com.yahoo.jdisc.application.BindingSet; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -import java.net.URI; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; - -/** - * Resolves request/response filter (chain) from a {@link URI} instance. - * - * @author Oyvind Bakksjo - * @author bjorncs - */ -public class FilterBindings { - - private final Map<String, RequestFilter> requestFilters; - private final Map<String, ResponseFilter> responseFilters; - private final Map<Integer, String> defaultRequestFilters; - private final Map<Integer, String> defaultResponseFilters; - private final BindingSet<String> requestFilterBindings; - private final BindingSet<String> responseFilterBindings; - - private FilterBindings( - Map<String, RequestFilter> requestFilters, - Map<String, ResponseFilter> responseFilters, - Map<Integer, String> defaultRequestFilters, - Map<Integer, String> defaultResponseFilters, - BindingSet<String> requestFilterBindings, - BindingSet<String> responseFilterBindings) { - this.requestFilters = requestFilters; - this.responseFilters = responseFilters; - this.defaultRequestFilters = defaultRequestFilters; - this.defaultResponseFilters = defaultResponseFilters; - this.requestFilterBindings = requestFilterBindings; - this.responseFilterBindings = responseFilterBindings; - } - - public Optional<String> resolveRequestFilter(URI uri, int localPort) { - String filterId = requestFilterBindings.resolve(uri); - if (filterId != null) return Optional.of(filterId); - return Optional.ofNullable(defaultRequestFilters.get(localPort)); - } - - public Optional<String> resolveResponseFilter(URI uri, int localPort) { - String filterId = responseFilterBindings.resolve(uri); - if (filterId != null) return Optional.of(filterId); - return Optional.ofNullable(defaultResponseFilters.get(localPort)); - } - - public RequestFilter getRequestFilter(String filterId) { return requestFilters.get(filterId); } - - public ResponseFilter getResponseFilter(String filterId) { return responseFilters.get(filterId); } - - public Collection<String> requestFilterIds() { return requestFilters.keySet(); } - - public Collection<String> responseFilterIds() { return responseFilters.keySet(); } - - public Collection<RequestFilter> requestFilters() { return requestFilters.values(); } - - public Collection<ResponseFilter> responseFilters() { return responseFilters.values(); } - - public static class Builder { - private final Map<String, RequestFilter> requestFilters = new TreeMap<>(); - private final Map<String, ResponseFilter> responseFilters = new TreeMap<>(); - private final Map<Integer, String> defaultRequestFilters = new TreeMap<>(); - private final Map<Integer, String> defaultResponseFilters = new TreeMap<>(); - private final BindingRepository<String> requestFilterBindings = new BindingRepository<>(); - private final BindingRepository<String> responseFilterBindings = new BindingRepository<>(); - - public Builder() {} - - public Builder addRequestFilter(String id, RequestFilter filter) { requestFilters.put(id, filter); return this; } - - public Builder addResponseFilter(String id, ResponseFilter filter) { responseFilters.put(id, filter); return this; } - - public Builder addRequestFilterBinding(String id, String binding) { requestFilterBindings.bind(binding, id); return this; } - - public Builder addResponseFilterBinding(String id, String binding) { responseFilterBindings.bind(binding, id); return this; } - - public Builder setRequestFilterDefaultForPort(String id, int port) { defaultRequestFilters.put(port, id); return this; } - - public Builder setResponseFilterDefaultForPort(String id, int port) { defaultResponseFilters.put(port, id); return this; } - - public FilterBindings build() { - return new FilterBindings( - Collections.unmodifiableMap(requestFilters), - Collections.unmodifiableMap(responseFilters), - Collections.unmodifiableMap(defaultRequestFilters), - Collections.unmodifiableMap(defaultResponseFilters), - requestFilterBindings.activate(), - responseFilterBindings.activate()); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvoker.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvoker.java deleted file mode 100644 index 0827ccdc39e..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvoker.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.inject.ImplementedBy; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.net.URI; - -/** - * Separate interface since DiscFilterRequest/Response and Security filter chains are not accessible in this bundle - */ -@ImplementedBy(UnsupportedFilterInvoker.class) -public interface FilterInvoker { - HttpServletRequest invokeRequestFilterChain(RequestFilter requestFilterChain, - URI uri, - HttpServletRequest httpRequest, - ResponseHandler responseHandler); - - void invokeResponseFilterChain( - ResponseFilter responseFilterChain, - URI uri, - HttpServletRequest request, - HttpServletResponse response); -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvokingPrintWriter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvokingPrintWriter.java deleted file mode 100644 index 3ebc7bbc551..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvokingPrintWriter.java +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.Locale; - -/** - * Invokes the response filter the first time anything is output to the underlying PrintWriter. - * The filter must be invoked before the first output call since this might cause the response - * to be committed, i.e. locked and potentially put on the wire. - * Any changes to the response after it has been committed might be ignored or cause exceptions. - * @author Tony Vaagenes - */ -final class FilterInvokingPrintWriter extends PrintWriter { - private final PrintWriter delegate; - private final OneTimeRunnable filterInvoker; - - public FilterInvokingPrintWriter(PrintWriter delegate, OneTimeRunnable filterInvoker) { - /* The PrintWriter class both - * 1) exposes new methods, the PrintWriter "interface" - * 2) implements PrintWriter and Writer methods that does some extra things before calling down to the writer methods. - * If super was invoked with the delegate PrintWriter, the superclass would behave as a PrintWriter(PrintWriter), - * i.e. the extra things in 2. would be done twice. - * To avoid this, all the methods of PrintWriter are overridden with versions that forward directly to the underlying delegate - * instead of going through super. - * The super class is initialized with a non-functioning writer to catch mistakenly non-overridden methods. - */ - super(new Writer() { - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - throwAssertionError(); - } - - private void throwAssertionError() { - throw new AssertionError(FilterInvokingPrintWriter.class.getName() + " failed to delegate to the underlying writer"); - } - - @Override - public void flush() throws IOException { - throwAssertionError(); - } - - @Override - public void close() throws IOException { - throwAssertionError(); - } - }); - - this.delegate = delegate; - this.filterInvoker = filterInvoker; - } - - @Override - public String toString() { - return getClass().getName() + " (" + super.toString() + ")"; - } - - private void runFilterIfFirstInvocation() { - filterInvoker.runIfFirstInvocation(); - } - - @Override - public void flush() { - runFilterIfFirstInvocation(); - delegate.flush(); - } - - @Override - public void close() { - runFilterIfFirstInvocation(); - delegate.close(); - } - - @Override - public boolean checkError() { - return delegate.checkError(); - } - - @Override - public void write(int c) { - runFilterIfFirstInvocation(); - delegate.write(c); - } - - @Override - public void write(char[] buf, int off, int len) { - runFilterIfFirstInvocation(); - delegate.write(buf, off, len); - } - - @Override - public void write(char[] buf) { - runFilterIfFirstInvocation(); - delegate.write(buf); - } - - @Override - public void write(String s, int off, int len) { - runFilterIfFirstInvocation(); - delegate.write(s, off, len); - } - - @Override - public void write(String s) { - runFilterIfFirstInvocation(); - delegate.write(s); - } - - @Override - public void print(boolean b) { - runFilterIfFirstInvocation(); - delegate.print(b); - } - - @Override - public void print(char c) { - runFilterIfFirstInvocation(); - delegate.print(c); - } - - @Override - public void print(int i) { - runFilterIfFirstInvocation(); - delegate.print(i); - } - - @Override - public void print(long l) { - runFilterIfFirstInvocation(); - delegate.print(l); - } - - @Override - public void print(float f) { - runFilterIfFirstInvocation(); - delegate.print(f); - } - - @Override - public void print(double d) { - runFilterIfFirstInvocation(); - delegate.print(d); - } - - @Override - public void print(char[] s) { - runFilterIfFirstInvocation(); - delegate.print(s); - } - - @Override - public void print(String s) { - runFilterIfFirstInvocation(); - delegate.print(s); - } - - @Override - public void print(Object obj) { - runFilterIfFirstInvocation(); - delegate.print(obj); - } - - @Override - public void println() { - runFilterIfFirstInvocation(); - delegate.println(); - } - - @Override - public void println(boolean x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(char x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(int x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(long x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(float x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(double x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(char[] x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(String x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public void println(Object x) { - runFilterIfFirstInvocation(); - delegate.println(x); - } - - @Override - public PrintWriter printf(String format, Object... args) { - runFilterIfFirstInvocation(); - return delegate.printf(format, args); - } - - @Override - public PrintWriter printf(Locale l, String format, Object... args) { - runFilterIfFirstInvocation(); - return delegate.printf(l, format, args); - } - - @Override - public PrintWriter format(String format, Object... args) { - runFilterIfFirstInvocation(); - return delegate.format(format, args); - } - - @Override - public PrintWriter format(Locale l, String format, Object... args) { - runFilterIfFirstInvocation(); - return delegate.format(l, format, args); - } - - @Override - public PrintWriter append(CharSequence csq) { - runFilterIfFirstInvocation(); - return delegate.append(csq); - } - - @Override - public PrintWriter append(CharSequence csq, int start, int end) { - runFilterIfFirstInvocation(); - return delegate.append(csq, start, end); - } - - @Override - public PrintWriter append(char c) { - runFilterIfFirstInvocation(); - return delegate.append(c); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvokingServletOutputStream.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvokingServletOutputStream.java deleted file mode 100644 index a605ccebfa7..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterInvokingServletOutputStream.java +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import java.io.IOException; - -/** - * Invokes the response filter the first time anything is output to the underlying ServletOutputStream. - * The filter must be invoked before the first output call since this might cause the response - * to be committed, i.e. locked and potentially put on the wire. - * Any changes to the response after it has been committed might be ignored or cause exceptions. - * - * @author Tony Vaagenes - */ -class FilterInvokingServletOutputStream extends ServletOutputStream { - private final ServletOutputStream delegate; - private final OneTimeRunnable filterInvoker; - - public FilterInvokingServletOutputStream(ServletOutputStream delegate, OneTimeRunnable filterInvoker) { - this.delegate = delegate; - this.filterInvoker = filterInvoker; - } - - @Override - public boolean isReady() { - return delegate.isReady(); - } - - @Override - public void setWriteListener(WriteListener writeListener) { - delegate.setWriteListener(writeListener); - } - - - private void runFilterIfFirstInvocation() { - filterInvoker.runIfFirstInvocation(); - } - - @Override - public void write(int b) throws IOException { - runFilterIfFirstInvocation(); - delegate.write(b); - } - - - @Override - public void write(byte[] b) throws IOException { - runFilterIfFirstInvocation(); - delegate.write(b); - } - - @Override - public void print(String s) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(s); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - runFilterIfFirstInvocation(); - delegate.write(b, off, len); - } - - @Override - public void print(boolean b) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(b); - } - - @Override - public void flush() throws IOException { - runFilterIfFirstInvocation(); - delegate.flush(); - } - - @Override - public void print(char c) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(c); - } - - @Override - public void close() throws IOException { - runFilterIfFirstInvocation(); - delegate.close(); - } - - @Override - public void print(int i) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(i); - } - - @Override - public void print(long l) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(l); - } - - @Override - public void print(float f) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(f); - } - - @Override - public void print(double d) throws IOException { - runFilterIfFirstInvocation(); - delegate.print(d); - } - - @Override - public void println() throws IOException { - runFilterIfFirstInvocation(); - delegate.println(); - } - - @Override - public void println(String s) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(s); - } - - @Override - public void println(boolean b) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(b); - } - - @Override - public void println(char c) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(c); - } - - @Override - public void println(int i) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(i); - } - - @Override - public void println(long l) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(l); - } - - @Override - public void println(float f) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(f); - } - - @Override - public void println(double d) throws IOException { - runFilterIfFirstInvocation(); - delegate.println(d); - } - - @Override - public String toString() { - return getClass().getCanonicalName() + " (" + delegate.toString() + ")"; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java deleted file mode 100644 index 1e2686aa184..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterResolver.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright Verizon Media. 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.Metric; -import com.yahoo.jdisc.NoopSharedResource; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.FastContentWriter; -import com.yahoo.jdisc.handler.ResponseDispatch; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; -import com.yahoo.jdisc.http.servlet.ServletRequest; - -import javax.servlet.http.HttpServletRequest; -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import static com.yahoo.jdisc.http.server.jetty.JDiscHttpServlet.getConnector; - -/** - * Resolve request/response filter (chain) based on {@link FilterBindings}. - * - * @author bjorncs - */ -class FilterResolver { - - private final FilterBindings bindings; - private final Metric metric; - private final boolean strictFiltering; - - FilterResolver(FilterBindings bindings, Metric metric, boolean strictFiltering) { - this.bindings = bindings; - this.metric = metric; - this.strictFiltering = strictFiltering; - } - - Optional<RequestFilter> resolveRequestFilter(HttpServletRequest servletRequest, URI jdiscUri) { - Optional<String> maybeFilterId = bindings.resolveRequestFilter(jdiscUri, getConnector(servletRequest).listenPort()); - if (maybeFilterId.isPresent()) { - metric.add(MetricDefinitions.FILTERING_REQUEST_HANDLED, 1L, createMetricContext(servletRequest, maybeFilterId.get())); - servletRequest.setAttribute(ServletRequest.JDISC_REQUEST_CHAIN, maybeFilterId.get()); - } else if (!strictFiltering) { - metric.add(MetricDefinitions.FILTERING_REQUEST_UNHANDLED, 1L, createMetricContext(servletRequest, null)); - } else { - String syntheticFilterId = RejectingRequestFilter.SYNTHETIC_FILTER_CHAIN_ID; - metric.add(MetricDefinitions.FILTERING_REQUEST_HANDLED, 1L, createMetricContext(servletRequest, syntheticFilterId)); - servletRequest.setAttribute(ServletRequest.JDISC_REQUEST_CHAIN, syntheticFilterId); - return Optional.of(RejectingRequestFilter.INSTANCE); - } - return maybeFilterId.map(bindings::getRequestFilter); - } - - Optional<ResponseFilter> resolveResponseFilter(HttpServletRequest servletRequest, URI jdiscUri) { - Optional<String> maybeFilterId = bindings.resolveResponseFilter(jdiscUri, getConnector(servletRequest).listenPort()); - if (maybeFilterId.isPresent()) { - metric.add(MetricDefinitions.FILTERING_RESPONSE_HANDLED, 1L, createMetricContext(servletRequest, maybeFilterId.get())); - servletRequest.setAttribute(ServletRequest.JDISC_RESPONSE_CHAIN, maybeFilterId.get()); - } else { - metric.add(MetricDefinitions.FILTERING_RESPONSE_UNHANDLED, 1L, createMetricContext(servletRequest, null)); - } - return maybeFilterId.map(bindings::getResponseFilter); - } - - private Metric.Context createMetricContext(HttpServletRequest request, String filterId) { - Map<String, String> extraDimensions = filterId != null - ? Map.of(MetricDefinitions.FILTER_CHAIN_ID_DIMENSION, filterId) - : Map.of(); - return JDiscHttpServlet.getConnector(request).createRequestMetricContext(request, extraDimensions); - } - - private static class RejectingRequestFilter extends NoopSharedResource implements RequestFilter { - - private static final RejectingRequestFilter INSTANCE = new RejectingRequestFilter(); - private static final String SYNTHETIC_FILTER_CHAIN_ID = "strict-reject"; - - @Override - public void filter(HttpRequest request, ResponseHandler handler) { - Response response = new Response(Response.Status.FORBIDDEN); - response.headers().add("Content-Type", "text/plain"); - try (FastContentWriter writer = ResponseDispatch.newInstance(response).connectFastWriter(handler)) { - writer.write("Request did not match any request filter chain"); - } - } - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java deleted file mode 100644 index de768f979a1..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.common.base.Preconditions; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.BindingNotFoundException; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestDeniedException; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -import javax.servlet.http.HttpServletRequest; -import java.nio.ByteBuffer; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Request handler that invokes request and response filters in addition to the bound request handler. - * - * @author Øyvind Bakksjø - */ -class FilteringRequestHandler extends AbstractRequestHandler { - - private static final ContentChannel COMPLETING_CONTENT_CHANNEL = new ContentChannel() { - - @Override - public void write(ByteBuffer buf, CompletionHandler handler) { - CompletionHandlers.tryComplete(handler); - } - - @Override - public void close(CompletionHandler handler) { - CompletionHandlers.tryComplete(handler); - } - - }; - - private final FilterResolver filterResolver; - private final HttpServletRequest servletRequest; - - public FilteringRequestHandler(FilterResolver filterResolver, HttpServletRequest servletRequest) { - this.filterResolver = filterResolver; - this.servletRequest = servletRequest; - } - - @Override - public ContentChannel handleRequest(Request request, ResponseHandler originalResponseHandler) { - Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request); - Objects.requireNonNull(originalResponseHandler, "responseHandler"); - - RequestFilter requestFilter = filterResolver.resolveRequestFilter(servletRequest, request.getUri()) - .orElse(null); - ResponseFilter responseFilter = filterResolver.resolveResponseFilter(servletRequest, request.getUri()) - .orElse(null); - - // Not using request.connect() here - it adds logic for error handling that we'd rather leave to the framework. - RequestHandler resolvedRequestHandler = request.container().resolveHandler(request); - - if (resolvedRequestHandler == null) { - throw new BindingNotFoundException(request.getUri()); - } - - RequestHandler requestHandler = new ReferenceCountingRequestHandler(resolvedRequestHandler); - - ResponseHandler responseHandler; - if (responseFilter != null) { - responseHandler = new FilteringResponseHandler(originalResponseHandler, responseFilter, request); - } else { - responseHandler = originalResponseHandler; - } - - if (requestFilter != null) { - InterceptingResponseHandler interceptingResponseHandler = new InterceptingResponseHandler(responseHandler); - requestFilter.filter(HttpRequest.class.cast(request), interceptingResponseHandler); - if (interceptingResponseHandler.hasProducedResponse()) { - return COMPLETING_CONTENT_CHANNEL; - } - } - - ContentChannel contentChannel = requestHandler.handleRequest(request, responseHandler); - if (contentChannel == null) { - throw new RequestDeniedException(request); - } - return contentChannel; - } - - private static class FilteringResponseHandler implements ResponseHandler { - - private final ResponseHandler delegate; - private final ResponseFilter responseFilter; - private final Request request; - - public FilteringResponseHandler(ResponseHandler delegate, ResponseFilter responseFilter, Request request) { - this.delegate = Objects.requireNonNull(delegate); - this.responseFilter = Objects.requireNonNull(responseFilter); - this.request = request; - } - - @Override - public ContentChannel handleResponse(Response response) { - responseFilter.filter(response, request); - return delegate.handleResponse(response); - } - - } - - private static class InterceptingResponseHandler implements ResponseHandler { - - private final ResponseHandler delegate; - private AtomicBoolean hasResponded = new AtomicBoolean(false); - - public InterceptingResponseHandler(ResponseHandler delegate) { - this.delegate = Objects.requireNonNull(delegate); - } - - @Override - public ContentChannel handleResponse(Response response) { - ContentChannel content = delegate.handleResponse(response); - hasResponded.set(true); - return content; - } - - public boolean hasProducedResponse() { - return hasResponded.get(); - } - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java deleted file mode 100644 index 38f84438526..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.common.base.Preconditions; -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.ResourceReference; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; - -import java.io.ByteArrayOutputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.IllegalCharsetNameException; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnsupportedCharsetException; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static com.yahoo.jdisc.Response.Status.UNSUPPORTED_MEDIA_TYPE; -import static com.yahoo.jdisc.http.server.jetty.CompletionHandlerUtils.NOOP_COMPLETION_HANDLER; - -/** - * Request handler that wraps POST requests of application/x-www-form-urlencoded data. - * - * The wrapper defers invocation of the "real" request handler until it has read the request content (body), - * parsed the form parameters and merged them into the request's parameters. - * - * @author bakksjo - * $Id$ - */ -class FormPostRequestHandler extends AbstractRequestHandler implements ContentChannel { - - private final ByteArrayOutputStream accumulatedRequestContent = new ByteArrayOutputStream(); - private final RequestHandler delegateHandler; - private final String contentCharsetName; - private final boolean removeBody; - - private Charset contentCharset; - private HttpRequest request; - private ResourceReference requestReference; - private ResponseHandler responseHandler; - - /** - * @param delegateHandler the "real" request handler that this handler wraps - * @param contentCharsetName name of the charset to use when interpreting the content data - */ - public FormPostRequestHandler( - final RequestHandler delegateHandler, - final String contentCharsetName, - final boolean removeBody) { - this.delegateHandler = Objects.requireNonNull(delegateHandler); - this.contentCharsetName = Objects.requireNonNull(contentCharsetName); - this.removeBody = removeBody; - } - - @Override - public ContentChannel handleRequest(final Request request, final ResponseHandler responseHandler) { - Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request); - Objects.requireNonNull(responseHandler, "responseHandler"); - - this.contentCharset = getCharsetByName(contentCharsetName); - this.responseHandler = responseHandler; - this.request = (HttpRequest) request; - this.requestReference = request.refer(); - - return this; - } - - @Override - public void write(final ByteBuffer buf, final CompletionHandler completionHandler) { - assert buf.hasArray(); - accumulatedRequestContent.write(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); - completionHandler.completed(); - } - - @SuppressWarnings("try") - @Override - public void close(final CompletionHandler completionHandler) { - try (final ResourceReference ref = requestReference) { - final byte[] requestContentBytes = accumulatedRequestContent.toByteArray(); - final String content = new String(requestContentBytes, contentCharset); - completionHandler.completed(); - final Map<String, List<String>> parameterMap = parseFormParameters(content); - mergeParameters(parameterMap, request.parameters()); - final ContentChannel contentChannel = delegateHandler.handleRequest(request, responseHandler); - if (contentChannel != null) { - if (!removeBody) { - final ByteBuffer byteBuffer = ByteBuffer.wrap(requestContentBytes); - contentChannel.write(byteBuffer, NOOP_COMPLETION_HANDLER); - } - contentChannel.close(NOOP_COMPLETION_HANDLER); - } - } - } - - /** - * Looks up a Charset given a charset name. - * - * @param charsetName the name of the charset to look up - * @return a valid Charset for the charset name (never returns null) - * @throws RequestException if the charset name is invalid or unsupported - */ - private static Charset getCharsetByName(final String charsetName) throws RequestException { - try { - final Charset charset = Charset.forName(charsetName); - if (charset == null) { - throw new RequestException(UNSUPPORTED_MEDIA_TYPE, "Unsupported charset " + charsetName); - } - return charset; - } catch (final IllegalCharsetNameException |UnsupportedCharsetException e) { - throw new RequestException(UNSUPPORTED_MEDIA_TYPE, "Unsupported charset " + charsetName, e); - } - } - - /** - * Parses application/x-www-form-urlencoded data into a map of parameters. - * - * @param formContent raw form content data (body) - * @return map of decoded parameters - */ - private static Map<String, List<String>> parseFormParameters(final String formContent) { - if (formContent.isEmpty()) { - return Collections.emptyMap(); - } - - final Map<String, List<String>> parameterMap = new HashMap<>(); - final String[] params = formContent.split("&"); - for (final String param : params) { - final String[] parts = param.split("="); - final String paramName = urlDecode(parts[0]); - final String paramValue = parts.length > 1 ? urlDecode(parts[1]) : ""; - List<String> currentValues = parameterMap.get(paramName); - if (currentValues == null) { - currentValues = new LinkedList<>(); - parameterMap.put(paramName, currentValues); - } - currentValues.add(paramValue); - } - return parameterMap; - } - - /** - * Percent-decoding method that doesn't throw. - * - * @param encoded percent-encoded data - * @return decoded data - */ - private static String urlDecode(final String encoded) { - try { - // Regardless of the charset used to transfer the request body, - // all percent-escaping of non-ascii characters should use UTF-8 code points. - return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name()); - } catch (final UnsupportedEncodingException e) { - // Unfortunately, there is no URLDecoder.decode() method that takes a Charset, so we have to deal - // with this exception. - throw new IllegalStateException("Whoa, JVM doesn't support UTF-8 today.", e); - } - } - - /** - * Merges source parameters into a destination map. - * - * @param source containing the parameters to copy into the destination - * @param destination receiver of parameters, possibly already containing data - */ - private static void mergeParameters( - final Map<String,List<String>> source, - final Map<String,List<String>> destination) { - for (Map.Entry<String, List<String>> entry : source.entrySet()) { - final List<String> destinationValues = destination.get(entry.getKey()); - if (destinationValues != null) { - destinationValues.addAll(entry.getValue()); - } else { - destination.put(entry.getKey(), entry.getValue()); - } - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java deleted file mode 100644 index 0f7ce77e4cd..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2019 Oath Inc. 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.concurrent.DaemonThreadFactory; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.security.SslContextBuilder; -import com.yahoo.security.tls.TransportSecurityOptions; -import com.yahoo.security.tls.TransportSecurityUtils; -import com.yahoo.security.tls.TrustAllX509TrustManager; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.util.EntityUtils; -import org.eclipse.jetty.server.DetectorConnectionFactory; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import javax.net.ssl.SSLContext; -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnectorLocalPort; - -/** - * A handler that proxies status.html health checks - * - * @author bjorncs - */ -class HealthCheckProxyHandler extends HandlerWrapper { - - private static final Logger log = Logger.getLogger(HealthCheckProxyHandler.class.getName()); - - private static final String HEALTH_CHECK_PATH = "/status.html"; - - private final Executor executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("health-check-proxy-client-")); - private final Map<Integer, ProxyTarget> portToProxyTargetMapping; - - HealthCheckProxyHandler(List<JDiscServerConnector> connectors) { - this.portToProxyTargetMapping = createPortToProxyTargetMapping(connectors); - } - - private static Map<Integer, ProxyTarget> createPortToProxyTargetMapping(List<JDiscServerConnector> connectors) { - var mapping = new HashMap<Integer, ProxyTarget>(); - for (JDiscServerConnector connector : connectors) { - ConnectorConfig.HealthCheckProxy proxyConfig = connector.connectorConfig().healthCheckProxy(); - if (proxyConfig.enable()) { - Duration targetTimeout = Duration.ofMillis((int) (proxyConfig.clientTimeout() * 1000)); - mapping.put(connector.listenPort(), createProxyTarget(proxyConfig.port(), targetTimeout, connectors)); - log.info(String.format("Port %1$d is configured as a health check proxy for port %2$d. " + - "HTTP requests to '%3$s' on %1$d are proxied as HTTPS to %2$d.", - connector.listenPort(), proxyConfig.port(), HEALTH_CHECK_PATH)); - } - } - return mapping; - } - - private static ProxyTarget createProxyTarget(int targetPort, Duration targetTimeout, List<JDiscServerConnector> connectors) { - JDiscServerConnector targetConnector = connectors.stream() - .filter(connector -> connector.listenPort() == targetPort) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("Could not find any connector with listen port " + targetPort)); - SslContextFactory.Server sslContextFactory = - Optional.ofNullable(targetConnector.getConnectionFactory(SslConnectionFactory.class)) - .or(() -> Optional.ofNullable(targetConnector.getConnectionFactory(DetectorConnectionFactory.class)) - .map(detectorConnFactory -> detectorConnFactory.getBean(SslConnectionFactory.class))) - .map(connFactory -> (SslContextFactory.Server) connFactory.getSslContextFactory()) - .orElseThrow(() -> new IllegalArgumentException("Health check proxy can only target https port")); - return new ProxyTarget(targetPort, targetTimeout, sslContextFactory); - } - - @Override - public void handle(String target, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException { - int localPort = getConnectorLocalPort(servletRequest); - ProxyTarget proxyTarget = portToProxyTargetMapping.get(localPort); - if (proxyTarget != null) { - AsyncContext asyncContext = servletRequest.startAsync(); - ServletOutputStream out = servletResponse.getOutputStream(); - if (servletRequest.getRequestURI().equals(HEALTH_CHECK_PATH)) { - executor.execute(new ProxyRequestTask(asyncContext, proxyTarget, servletResponse, out)); - } else { - servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); - asyncContext.complete(); - } - request.setHandled(true); - } else { - _handler.handle(target, request, servletRequest, servletResponse); - } - } - - @Override - protected void doStop() throws Exception { - for (ProxyTarget target : portToProxyTargetMapping.values()) { - target.close(); - } - super.doStop(); - } - - private static class ProxyRequestTask implements Runnable { - - final AsyncContext asyncContext; - final ProxyTarget target; - final HttpServletResponse servletResponse; - final ServletOutputStream output; - - ProxyRequestTask(AsyncContext asyncContext, ProxyTarget target, HttpServletResponse servletResponse, ServletOutputStream output) { - this.asyncContext = asyncContext; - this.target = target; - this.servletResponse = servletResponse; - this.output = output; - } - - @Override - public void run() { - StatusResponse statusResponse = target.requestStatusHtml(); - servletResponse.setStatus(statusResponse.statusCode); - if (statusResponse.contentType != null) { - servletResponse.setHeader("Content-Type", statusResponse.contentType); - } - servletResponse.setHeader("Vespa-Health-Check-Proxy-Target", Integer.toString(target.port)); - output.setWriteListener(new WriteListener() { - @Override - public void onWritePossible() throws IOException { - if (output.isReady()) { - if (statusResponse.content != null) { - output.write(statusResponse.content); - } - asyncContext.complete(); - } - } - - @Override - public void onError(Throwable t) { - log.log(Level.FINE, t, () -> "Failed to write status response: " + t.getMessage()); - asyncContext.complete(); - } - }); - } - } - - private static class ProxyTarget implements AutoCloseable { - final int port; - final Duration timeout; - final SslContextFactory.Server sslContextFactory; - volatile CloseableHttpClient client; - volatile StatusResponse lastResponse; - - ProxyTarget(int port, Duration timeout, SslContextFactory.Server sslContextFactory) { - this.port = port; - this.timeout = timeout; - this.sslContextFactory = sslContextFactory; - } - - StatusResponse requestStatusHtml() { - StatusResponse response = lastResponse; - if (response != null && !response.isExpired()) { - return response; - } - return this.lastResponse = getStatusResponse(); - } - - private StatusResponse getStatusResponse() { - try (CloseableHttpResponse clientResponse = client().execute(new HttpGet("https://localhost:" + port + HEALTH_CHECK_PATH))) { - int statusCode = clientResponse.getStatusLine().getStatusCode(); - HttpEntity entity = clientResponse.getEntity(); - if (entity != null) { - Header contentTypeHeader = entity.getContentType(); - String contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : null; - byte[] content = EntityUtils.toByteArray(entity); - return new StatusResponse(statusCode, contentType, content); - } else { - return new StatusResponse(statusCode, null, null); - } - } catch (Exception e) { - log.log(Level.FINE, e, () -> "Proxy request failed" + e.getMessage()); - return new StatusResponse(500, "text/plain", e.getMessage().getBytes()); - } - } - - // Client construction must be delayed to ensure that the SslContextFactory is started before calling getSslContext(). - private CloseableHttpClient client() { - if (client == null) { - synchronized (this) { - if (client == null) { - int timeoutMillis = (int) timeout.toMillis(); - client = HttpClientBuilder.create() - .disableAutomaticRetries() - .setMaxConnPerRoute(4) - .setSSLContext(getSslContext(sslContextFactory)) - .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // Certificate may not match "localhost" - .setUserTokenHandler(context -> null) // https://stackoverflow.com/a/42112034/1615280 - .setUserAgent("health-check-proxy-client") - .setDefaultRequestConfig( - RequestConfig.custom() - .setConnectTimeout(timeoutMillis) - .setConnectionRequestTimeout(timeoutMillis) - .setSocketTimeout(timeoutMillis) - .build()) - .build(); - } - } - } - return client; - } - - private SSLContext getSslContext(SslContextFactory.Server sslContextFactory) { - // A client certificate is only required if the server connector's ssl context factory is configured with "need-auth". - if (sslContextFactory.getNeedClientAuth()) { - log.info(String.format("Port %d requires client certificate - client will provide its node certificate", port)); - // We should ideally specify the client certificate through connector config, but the model has currently no knowledge of node certificate location on disk. - // Instead we assume that the server connector will accept its own node certificate. This will work for the current hosted use-case. - // The Vespa TLS config will provide us the location of certificate and key. - TransportSecurityOptions options = TransportSecurityUtils.getOptions() - .orElseThrow(() -> - new IllegalStateException("Vespa TLS configuration is required when using health check proxy to a port with client auth 'need'")); - return new SslContextBuilder() - .withKeyStore(options.getPrivateKeyFile().get(), options.getCertificatesFile().get()) - .withTrustManager(new TrustAllX509TrustManager()) - .build(); - } else { - log.info(String.format( - "Port %d does not require a client certificate - client will not provide a certificate", port)); - return new SslContextBuilder() - .withTrustManager(new TrustAllX509TrustManager()) - .build(); - } - } - - @Override - public void close() throws IOException { - synchronized (this) { - if (client != null) { - client.close(); - client = null; - } - } - } - } - - private static class StatusResponse { - final long createdAt = System.nanoTime(); - final int statusCode; - final String contentType; - final byte[] content; - - StatusResponse(int statusCode, String contentType, byte[] content) { - this.statusCode = statusCode; - this.contentType = contentType; - this.content = content; - } - - boolean isExpired() { return System.nanoTime() - createdAt > Duration.ofSeconds(1).toNanos(); } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java deleted file mode 100644 index 05715b13d10..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.container.logging.AccessLogEntry; -import com.yahoo.jdisc.Metric.Context; -import com.yahoo.jdisc.References; -import com.yahoo.jdisc.ResourceReference; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.BindingNotFoundException; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.OverloadException; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.HttpRequest; -import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.server.HttpConnection; -import org.eclipse.jetty.server.Request; - -import javax.servlet.AsyncContext; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.yahoo.jdisc.http.HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnection; -import static com.yahoo.jdisc.http.server.jetty.JDiscHttpServlet.getConnector; -import static com.yahoo.yolean.Exceptions.throwUnchecked; - -/** - * @author Simon Thoresen Hult - * @author bjorncs - */ -class HttpRequestDispatch { - - private static final Logger log = Logger.getLogger(HttpRequestDispatch.class.getName()); - - private final static String CHARSET_ANNOTATION = ";charset="; - - private final JDiscContext jDiscContext; - private final AsyncContext async; - private final Request jettyRequest; - - private final ServletResponseController servletResponseController; - private final RequestHandler requestHandler; - private final RequestMetricReporter metricReporter; - - public HttpRequestDispatch(JDiscContext jDiscContext, - AccessLogEntry accessLogEntry, - Context metricContext, - HttpServletRequest servletRequest, - HttpServletResponse servletResponse) throws IOException { - this.jDiscContext = jDiscContext; - - requestHandler = newRequestHandler(jDiscContext, accessLogEntry, servletRequest); - - this.jettyRequest = (Request) servletRequest; - this.metricReporter = new RequestMetricReporter(jDiscContext.metric, metricContext, jettyRequest.getTimeStamp()); - this.servletResponseController = new ServletResponseController(servletRequest, - servletResponse, - jDiscContext.janitor, - metricReporter, - jDiscContext.developerMode()); - markConnectionAsNonPersistentIfThresholdReached(servletRequest); - this.async = servletRequest.startAsync(); - async.setTimeout(0); - metricReporter.uriLength(jettyRequest.getOriginalURI().length()); - } - - public void dispatch() throws IOException { - ServletRequestReader servletRequestReader; - try { - servletRequestReader = handleRequest(); - } catch (Throwable throwable) { - servletResponseController.trySendError(throwable); - servletResponseController.finishedFuture().whenComplete((result, exception) -> - completeRequestCallback.accept(null, throwable)); - return; - } - - try { - onError(servletRequestReader.finishedFuture, servletResponseController::trySendError); - onError(servletResponseController.finishedFuture(), servletRequestReader::onError); - CompletableFuture.allOf(servletRequestReader.finishedFuture, servletResponseController.finishedFuture()) - .whenComplete(completeRequestCallback); - } catch (Throwable throwable) { - log.log(Level.WARNING, "Failed registering finished listeners.", throwable); - } - } - - private final BiConsumer<Void, Throwable> completeRequestCallback; - { - AtomicBoolean completeRequestCalled = new AtomicBoolean(false); - HttpRequestDispatch parent = this; //used to avoid binding uninitialized variables - - completeRequestCallback = (result, error) -> { - boolean alreadyCalled = completeRequestCalled.getAndSet(true); - if (alreadyCalled) { - AssertionError e = new AssertionError("completeRequest called more than once"); - log.log(Level.WARNING, "Assertion failed.", e); - throw e; - } - - boolean reportedError = false; - - if (error != null) { - if (isErrorOfType(error, EofException.class, IOException.class)) { - log.log(Level.FINE, - error, - () -> "Network connection was unexpectedly terminated: " + parent.jettyRequest.getRequestURI()); - parent.metricReporter.prematurelyClosed(); - } else if (!isErrorOfType(error, OverloadException.class, BindingNotFoundException.class, RequestException.class)) { - log.log(Level.WARNING, "Request failed: " + parent.jettyRequest.getRequestURI(), error); - } - reportedError = true; - parent.metricReporter.failedResponse(); - } else { - parent.metricReporter.successfulResponse(); - } - - try { - parent.async.complete(); - log.finest(() -> "Request completed successfully: " + parent.jettyRequest.getRequestURI()); - } catch (Throwable throwable) { - Level level = reportedError ? Level.FINE: Level.WARNING; - log.log(level, "Async.complete failed", throwable); - } - }; - } - - private static void markConnectionAsNonPersistentIfThresholdReached(HttpServletRequest request) { - ConnectorConfig connectorConfig = getConnector(request).connectorConfig(); - int maxRequestsPerConnection = connectorConfig.maxRequestsPerConnection(); - if (maxRequestsPerConnection > 0) { - HttpConnection connection = getConnection(request); - if (connection.getMessagesIn() >= maxRequestsPerConnection) { - connection.getGenerator().setPersistent(false); - } - } - double maxConnectionLifeInSeconds = connectorConfig.maxConnectionLife(); - if (maxConnectionLifeInSeconds > 0) { - HttpConnection connection = getConnection(request); - Instant expireAt = Instant.ofEpochMilli((long)(connection.getCreatedTimeStamp() + maxConnectionLifeInSeconds * 1000)); - if (Instant.now().isAfter(expireAt)) { - connection.getGenerator().setPersistent(false); - } - } - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static boolean isErrorOfType(Throwable throwable, Class<? extends Throwable>... handledTypes) { - return Arrays.stream(handledTypes) - .anyMatch( - exceptionType -> exceptionType.isInstance(throwable) - || throwable instanceof CompletionException && exceptionType.isInstance(throwable.getCause())); - } - - @SuppressWarnings("try") - private ServletRequestReader handleRequest() throws IOException { - HttpRequest jdiscRequest = HttpRequestFactory.newJDiscRequest(jDiscContext.container, jettyRequest); - ContentChannel requestContentChannel; - - try (ResourceReference ref = References.fromResource(jdiscRequest)) { - HttpRequestFactory.copyHeaders(jettyRequest, jdiscRequest); - requestContentChannel = requestHandler.handleRequest(jdiscRequest, servletResponseController.responseHandler); - } - - ServletInputStream servletInputStream = jettyRequest.getInputStream(); - - ServletRequestReader servletRequestReader = new ServletRequestReader(servletInputStream, - requestContentChannel, - jDiscContext.janitor, - metricReporter); - - servletInputStream.setReadListener(servletRequestReader); - return servletRequestReader; - } - - private static void onError(CompletableFuture<?> future, Consumer<Throwable> errorHandler) { - future.whenComplete((result, exception) -> { - if (exception != null) { - errorHandler.accept(exception); - } - }); - } - - ContentChannel handleRequestFilterResponse(Response response) { - try { - jettyRequest.getInputStream().close(); - ContentChannel responseContentChannel = servletResponseController.responseHandler.handleResponse(response); - servletResponseController.finishedFuture().whenComplete(completeRequestCallback); - return responseContentChannel; - } catch (IOException e) { - throw throwUnchecked(e); - } - } - - - private static RequestHandler newRequestHandler(JDiscContext context, - AccessLogEntry accessLogEntry, - HttpServletRequest servletRequest) { - RequestHandler requestHandler = wrapHandlerIfFormPost( - new FilteringRequestHandler(context.filterResolver, servletRequest), - servletRequest, context.serverConfig.removeRawPostBodyForWwwUrlEncodedPost()); - - return new AccessLoggingRequestHandler(requestHandler, accessLogEntry); - } - - private static RequestHandler wrapHandlerIfFormPost(RequestHandler requestHandler, - HttpServletRequest servletRequest, - boolean removeBodyForFormPost) { - if (!servletRequest.getMethod().equals("POST")) { - return requestHandler; - } - String contentType = servletRequest.getHeader(HttpHeaders.Names.CONTENT_TYPE); - if (contentType == null) { - return requestHandler; - } - if (!contentType.startsWith(APPLICATION_X_WWW_FORM_URLENCODED)) { - return requestHandler; - } - return new FormPostRequestHandler(requestHandler, getCharsetName(contentType), removeBodyForFormPost); - } - - private static String getCharsetName(String contentType) { - if (!contentType.startsWith(CHARSET_ANNOTATION, APPLICATION_X_WWW_FORM_URLENCODED.length())) { - return StandardCharsets.UTF_8.name(); - } - return contentType.substring(APPLICATION_X_WWW_FORM_URLENCODED.length() + CHARSET_ANNOTATION.length()); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java deleted file mode 100644 index e8d37cfadb5..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.http.HttpRequest; -import com.yahoo.jdisc.http.servlet.ServletRequest; -import com.yahoo.jdisc.service.CurrentContainer; -import org.eclipse.jetty.util.Utf8Appendable; - -import javax.servlet.http.HttpServletRequest; -import java.net.InetSocketAddress; -import java.net.URI; -import java.security.cert.X509Certificate; -import java.util.Enumeration; - -import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnection; -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnectorLocalPort; - -/** - * @author Simon Thoresen Hult - * @author bjorncs - */ -class HttpRequestFactory { - - public static HttpRequest newJDiscRequest(CurrentContainer container, HttpServletRequest servletRequest) { - try { - HttpRequest httpRequest = HttpRequest.newServerRequest( - container, - getUri(servletRequest), - HttpRequest.Method.valueOf(servletRequest.getMethod()), - HttpRequest.Version.fromString(servletRequest.getProtocol()), - new InetSocketAddress(servletRequest.getRemoteAddr(), servletRequest.getRemotePort()), - getConnection(servletRequest).getCreatedTimeStamp()); - httpRequest.context().put(ServletRequest.JDISC_REQUEST_X509CERT, getCertChain(servletRequest)); - return httpRequest; - } catch (Utf8Appendable.NotUtf8Exception e) { - throw createBadQueryException(e); - } - } - - // Implementation based on org.eclipse.jetty.server.Request.getRequestURL(), but with the connector's local port instead - public static URI getUri(HttpServletRequest servletRequest) { - try { - String scheme = servletRequest.getScheme(); - String host = servletRequest.getServerName(); - int port = getConnectorLocalPort(servletRequest); - String path = servletRequest.getRequestURI(); - String query = servletRequest.getQueryString(); - - URI uri = URI.create(scheme + "://" + - host + ":" + port + - (path != null ? path : "") + - (query != null ? "?" + query : "")); - - validateSchemeHostPort(scheme, host, port, uri); - return uri; - } - catch (IllegalArgumentException e) { - throw createBadQueryException(e); - } - } - - private static void validateSchemeHostPort(String scheme, String host, int port, URI uri) { - if ( ! scheme.equals(uri.getScheme())) - throw new IllegalArgumentException("Bad scheme: " + scheme); - - if ( ! host.equals(uri.getHost()) || port != uri.getPort()) - throw new IllegalArgumentException("Bad authority: " + uri.getRawAuthority() + " != " + host + ":" + port); - } - - private static RequestException createBadQueryException(IllegalArgumentException e) { - return new RequestException(BAD_REQUEST, "URL violates RFC 2396: " + e.getMessage(), e); - } - - public static void copyHeaders(HttpServletRequest from, HttpRequest to) { - for (Enumeration<String> it = from.getHeaderNames(); it.hasMoreElements(); ) { - String key = it.nextElement(); - for (Enumeration<String> value = from.getHeaders(key); value.hasMoreElements(); ) { - to.headers().add(key, value.nextElement()); - } - } - } - - private static X509Certificate[] getCertChain(HttpServletRequest servletRequest) { - return (X509Certificate[]) servletRequest.getAttribute("javax.servlet.request.X509Certificate"); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java deleted file mode 100644 index 82c445c7ca9..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2018 Yahoo Holdings. 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.http.HttpRequest; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.AsyncContextEvent; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpChannelState; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.FutureCallback; -import org.eclipse.jetty.util.component.Graceful; - -import javax.servlet.AsyncEvent; -import javax.servlet.AsyncListener; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.atomic.LongAdder; - -/** - * HttpResponseStatisticsCollector collects statistics about HTTP response types aggregated by category - * (1xx, 2xx, etc). It is similar to {@link org.eclipse.jetty.server.handler.StatisticsHandler} - * with the distinction that this class collects response type statistics grouped - * by HTTP method and only collects the numbers that are reported as metrics from Vespa. - * - * @author ollivir - */ -public class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful { - - static final String requestTypeAttribute = "requestType"; - - private final AtomicReference<FutureCallback> shutdown = new AtomicReference<>(); - private final List<String> monitoringHandlerPaths; - private final List<String> searchHandlerPaths; - - public enum HttpMethod { - GET, PATCH, POST, PUT, DELETE, OPTIONS, HEAD, OTHER - } - - public enum HttpScheme { - HTTP, HTTPS, OTHER - } - - private static final String[] HTTP_RESPONSE_GROUPS = { - MetricDefinitions.RESPONSES_1XX, - MetricDefinitions.RESPONSES_2XX, - MetricDefinitions.RESPONSES_3XX, - MetricDefinitions.RESPONSES_4XX, - MetricDefinitions.RESPONSES_5XX, - MetricDefinitions.RESPONSES_401, - MetricDefinitions.RESPONSES_403 - }; - - private final AtomicLong inFlight = new AtomicLong(); - private final LongAdder[][][][] statistics; - - public HttpResponseStatisticsCollector(List<String> monitoringHandlerPaths, List<String> searchHandlerPaths) { - this.monitoringHandlerPaths = monitoringHandlerPaths; - this.searchHandlerPaths = searchHandlerPaths; - statistics = new LongAdder[HttpScheme.values().length][HttpMethod.values().length][][]; - for (int scheme = 0; scheme < HttpScheme.values().length; ++scheme) { - for (int method = 0; method < HttpMethod.values().length; method++) { - statistics[scheme][method] = new LongAdder[HTTP_RESPONSE_GROUPS.length][]; - for (int group = 0; group < HTTP_RESPONSE_GROUPS.length; group++) { - statistics[scheme][method][group] = new LongAdder[HttpRequest.RequestType.values().length]; - for (int requestType = 0; requestType < HttpRequest.RequestType.values().length; requestType++) { - statistics[scheme][method][group][requestType] = new LongAdder(); - } - } - } - } - } - - private final AsyncListener completionWatcher = new AsyncListener() { - - @Override - public void onTimeout(AsyncEvent event) { } - - @Override - public void onStartAsync(AsyncEvent event) { - event.getAsyncContext().addListener(this); - } - - @Override - public void onError(AsyncEvent event) { } - - @Override - public void onComplete(AsyncEvent event) throws IOException { - HttpChannelState state = ((AsyncContextEvent) event).getHttpChannelState(); - Request request = state.getBaseRequest(); - - observeEndOfRequest(request, null); - } - }; - - @Override - public void handle(String path, Request baseRequest, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - inFlight.incrementAndGet(); - - try { - Handler handler = getHandler(); - if (handler != null && shutdown.get() == null && isStarted()) { - handler.handle(path, baseRequest, request, response); - } else if ( ! baseRequest.isHandled()) { - baseRequest.setHandled(true); - response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503); - } - } finally { - HttpChannelState state = baseRequest.getHttpChannelState(); - if (state.isSuspended()) { - if (state.isInitial()) { - state.addListener(completionWatcher); - } - } else if (state.isInitial()) { - observeEndOfRequest(baseRequest, response); - } - } - } - - private void observeEndOfRequest(Request request, HttpServletResponse flushableResponse) throws IOException { - int group = groupIndex(request); - if (group >= 0) { - HttpScheme scheme = getScheme(request); - HttpMethod method = getMethod(request); - HttpRequest.RequestType requestType = getRequestType(request); - - statistics[scheme.ordinal()][method.ordinal()][group][requestType.ordinal()].increment(); - if (group == 5 || group == 6) { // if 401/403, also increment 4xx - statistics[scheme.ordinal()][method.ordinal()][3][requestType.ordinal()].increment(); - } - } - - long live = inFlight.decrementAndGet(); - FutureCallback shutdownCb = shutdown.get(); - if (shutdownCb != null) { - if (flushableResponse != null) { - flushableResponse.flushBuffer(); - } - if (live == 0) { - shutdownCb.succeeded(); - } - } - } - - private int groupIndex(Request request) { - int index = request.getResponse().getStatus(); - if (index == 401) { - return 5; - } - if (index == 403) { - return 6; - } - - index = index / 100 - 1; // 1xx = 0, 2xx = 1 etc. - if (index < 0 || index >= statistics[0].length) { - return -1; - } else { - return index; - } - } - - private HttpScheme getScheme(Request request) { - switch (request.getScheme()) { - case "http": - return HttpScheme.HTTP; - case "https": - return HttpScheme.HTTPS; - default: - return HttpScheme.OTHER; - } - } - - private HttpMethod getMethod(Request request) { - switch (request.getMethod()) { - case "GET": - return HttpMethod.GET; - case "PATCH": - return HttpMethod.PATCH; - case "POST": - return HttpMethod.POST; - case "PUT": - return HttpMethod.PUT; - case "DELETE": - return HttpMethod.DELETE; - case "OPTIONS": - return HttpMethod.OPTIONS; - case "HEAD": - return HttpMethod.HEAD; - default: - return HttpMethod.OTHER; - } - } - - private HttpRequest.RequestType getRequestType(Request request) { - HttpRequest.RequestType requestType = (HttpRequest.RequestType)request.getAttribute(requestTypeAttribute); - if (requestType != null) return requestType; - - // Deduce from path and method: - String path = request.getRequestURI(); - for (String monitoringHandlerPath : monitoringHandlerPaths) { - if (path.startsWith(monitoringHandlerPath)) return HttpRequest.RequestType.MONITORING; - } - for (String searchHandlerPath : searchHandlerPaths) { - if (path.startsWith(searchHandlerPath)) return HttpRequest.RequestType.READ; - } - if ("GET".equals(request.getMethod())) { - return HttpRequest.RequestType.READ; - } else { - return HttpRequest.RequestType.WRITE; - } - } - - public List<StatisticsEntry> takeStatistics() { - var ret = new ArrayList<StatisticsEntry>(); - for (HttpScheme scheme : HttpScheme.values()) { - int schemeIndex = scheme.ordinal(); - for (HttpMethod method : HttpMethod.values()) { - int methodIndex = method.ordinal(); - for (int group = 0; group < HTTP_RESPONSE_GROUPS.length; group++) { - for (HttpRequest.RequestType type : HttpRequest.RequestType.values()) { - long value = statistics[schemeIndex][methodIndex][group][type.ordinal()].sumThenReset(); - if (value > 0) { - ret.add(new StatisticsEntry(scheme.name().toLowerCase(), method.name(), HTTP_RESPONSE_GROUPS[group], type.name().toLowerCase(), value)); - } - } - } - } - } - return ret; - } - - @Override - protected void doStart() throws Exception { - shutdown.set(null); - super.doStart(); - } - - @Override - protected void doStop() throws Exception { - super.doStop(); - FutureCallback shutdownCb = shutdown.get(); - if ( ! shutdownCb.isDone()) { - shutdownCb.failed(new TimeoutException()); - } - } - - @Override - public Future<Void> shutdown() { - FutureCallback shutdownCb = new FutureCallback(false); - shutdown.compareAndSet(null, shutdownCb); - shutdownCb = shutdown.get(); - if (inFlight.get() == 0) { - shutdownCb.succeeded(); - } - return shutdownCb; - } - - @Override - public boolean isShutdown() { - FutureCallback futureCallback = shutdown.get(); - return futureCallback != null && futureCallback.isDone(); - } - - public static class StatisticsEntry { - - public final String scheme; - public final String method; - public final String name; - public final String requestType; - public final long value; - - public StatisticsEntry(String scheme, String method, String name, String requestType, long value) { - this.scheme = scheme; - this.method = method; - this.name = name; - this.requestType = requestType; - this.value = value; - } - - @Override - public String toString() { - return "scheme: " + scheme + - ", method: " + method + - ", name: " + name + - ", requestType: " + requestType + - ", value: " + value; - } - - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpServletRequestUtils.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpServletRequestUtils.java deleted file mode 100644 index e7b9f459d2e..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpServletRequestUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import org.eclipse.jetty.server.HttpConnection; - -import javax.servlet.http.HttpServletRequest; - -/** - * @author bjorncs - */ -public class HttpServletRequestUtils { - private HttpServletRequestUtils() {} - - public static HttpConnection getConnection(HttpServletRequest request) { - return (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection"); - } - - /** - * Note: {@link HttpServletRequest#getLocalPort()} may return the local port of the load balancer / reverse proxy if proxy-protocol is enabled. - * @return the actual local port of the underlying Jetty connector - */ - public static int getConnectorLocalPort(HttpServletRequest request) { - JDiscServerConnector connector = (JDiscServerConnector) getConnection(request).getConnector(); - int actualLocalPort = connector.getLocalPort(); - int localPortIfConnectorUnopened = -1; - int localPortIfConnectorClosed = -2; - if (actualLocalPort == localPortIfConnectorUnopened || actualLocalPort == localPortIfConnectorClosed) { - int configuredLocalPort = connector.listenPort(); - int localPortEphemeralPort = 0; - if (configuredLocalPort == localPortEphemeralPort) { - throw new IllegalStateException("Unable to determine connector's listen port"); - } - return configuredLocalPort; - } - return actualLocalPort; - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java deleted file mode 100644 index b37a7352dc6..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscContext.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.Metric; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.service.CurrentContainer; - -import java.util.concurrent.Executor; - -public class JDiscContext { - final FilterResolver filterResolver; - final CurrentContainer container; - final Executor janitor; - final Metric metric; - final ServerConfig serverConfig; - - public JDiscContext(FilterBindings filterBindings, - CurrentContainer container, - Executor janitor, - Metric metric, - ServerConfig serverConfig) { - - this.filterResolver = new FilterResolver(filterBindings, metric, serverConfig.strictFiltering()); - this.container = container; - this.janitor = janitor; - this.metric = metric; - this.serverConfig = serverConfig; - } - - public boolean developerMode() { - return serverConfig.developerMode(); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java deleted file mode 100644 index a89c115a1c2..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.container.logging.AccessLogEntry; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.filter.RequestFilter; - -import javax.servlet.AsyncContext; -import javax.servlet.AsyncListener; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URI; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; - -import static com.yahoo.jdisc.http.server.jetty.JDiscHttpServlet.getConnector; -import static com.yahoo.yolean.Exceptions.throwUnchecked; - -/** - * Runs JDisc security filters for Servlets - * This component is split in two: - * 1) JDiscFilterInvokerFilter, which uses package private methods to support JDisc APIs - * 2) SecurityFilterInvoker, which is intended for use in a servlet context. - * - * @author Tony Vaagenes - */ -class JDiscFilterInvokerFilter implements Filter { - private final JDiscContext jDiscContext; - private final FilterInvoker filterInvoker; - - public JDiscFilterInvokerFilter(JDiscContext jDiscContext, - FilterInvoker filterInvoker) { - this.jDiscContext = jDiscContext; - this.filterInvoker = filterInvoker; - } - - - @Override - public void init(FilterConfig filterConfig) throws ServletException {} - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest httpRequest = (HttpServletRequest)request; - HttpServletResponse httpResponse = (HttpServletResponse)response; - - URI uri; - try { - uri = HttpRequestFactory.getUri(httpRequest); - } catch (RequestException e) { - httpResponse.sendError(e.getResponseStatus(), e.getMessage()); - return; - } - - AtomicReference<Boolean> responseReturned = new AtomicReference<>(null); - - HttpServletRequest newRequest = runRequestFilterWithMatchingBinding(responseReturned, uri, httpRequest, httpResponse); - assert newRequest != null; - responseReturned.compareAndSet(null, false); - - if (!responseReturned.get()) { - runChainAndResponseFilters(uri, newRequest, httpResponse, chain); - } - } - - private void runChainAndResponseFilters(URI uri, HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - Optional<OneTimeRunnable> responseFilterInvoker = - jDiscContext.filterResolver.resolveResponseFilter(request, uri) - .map(responseFilter -> - new OneTimeRunnable(() -> - filterInvoker.invokeResponseFilterChain(responseFilter, uri, request, response))); - - - HttpServletResponse responseForServlet = responseFilterInvoker - .<HttpServletResponse>map(invoker -> - new FilterInvokingResponseWrapper(response, invoker)) - .orElse(response); - - HttpServletRequest requestForServlet = responseFilterInvoker - .<HttpServletRequest>map(invoker -> - new FilterInvokingRequestWrapper(request, invoker, responseForServlet)) - .orElse(request); - - chain.doFilter(requestForServlet, responseForServlet); - - responseFilterInvoker.ifPresent(invoker -> { - boolean requestHandledSynchronously = !request.isAsyncStarted(); - - if (requestHandledSynchronously) { - invoker.runIfFirstInvocation(); - } - // For async requests, response filters will be invoked on AsyncContext.complete(). - }); - } - - private HttpServletRequest runRequestFilterWithMatchingBinding(AtomicReference<Boolean> responseReturned, URI uri, HttpServletRequest request, HttpServletResponse response) throws IOException { - try { - RequestFilter requestFilter = jDiscContext.filterResolver.resolveRequestFilter(request, uri).orElse(null); - if (requestFilter == null) - return request; - - ResponseHandler responseHandler = createResponseHandler(responseReturned, request, response); - return filterInvoker.invokeRequestFilterChain(requestFilter, uri, request, responseHandler); - } catch (Exception e) { - throw new RuntimeException("Failed running request filter chain for uri " + uri, e); - } - } - - private ResponseHandler createResponseHandler(AtomicReference<Boolean> responseReturned, HttpServletRequest httpRequest, HttpServletResponse httpResponse) { - return jdiscResponse -> { - boolean oldValueWasNull = responseReturned.compareAndSet(null, true); - if (!oldValueWasNull) - throw new RuntimeException("Can't return response from filter asynchronously"); - - HttpRequestDispatch requestDispatch = createRequestDispatch(httpRequest, httpResponse); - return requestDispatch.handleRequestFilterResponse(jdiscResponse); - }; - } - - private HttpRequestDispatch createRequestDispatch(HttpServletRequest request, HttpServletResponse response) { - try { - final AccessLogEntry accessLogEntry = null; // Not used in this context. - return new HttpRequestDispatch(jDiscContext, - accessLogEntry, - getConnector(request).createRequestMetricContext(request, Map.of()), - request, response); - } catch (IOException e) { - throw throwUnchecked(e); - } - } - - @Override - public void destroy() {} - - // ServletRequest wrapper that is necessary because we need to wrap AsyncContext. - private static class FilterInvokingRequestWrapper extends HttpServletRequestWrapper { - private final OneTimeRunnable filterInvoker; - private final HttpServletResponse servletResponse; - - public FilterInvokingRequestWrapper( - HttpServletRequest request, - OneTimeRunnable filterInvoker, - HttpServletResponse servletResponse) { - super(request); - this.filterInvoker = filterInvoker; - this.servletResponse = servletResponse; - } - - @Override - public AsyncContext startAsync() { - final AsyncContext asyncContext = super.startAsync(); - return new FilterInvokingAsyncContext(asyncContext, filterInvoker, this, servletResponse); - } - - @Override - public AsyncContext startAsync( - final ServletRequest wrappedRequest, - final ServletResponse wrappedResponse) { - // According to the documentation, the passed request/response parameters here must either - // _be_ or _wrap_ the original request/response objects passed to the servlet - which are - // our wrappers, so no need to wrap again - we can use the user-supplied objects. - final AsyncContext asyncContext = super.startAsync(wrappedRequest, wrappedResponse); - return new FilterInvokingAsyncContext(asyncContext, filterInvoker, this, wrappedResponse); - } - - @Override - public AsyncContext getAsyncContext() { - final AsyncContext asyncContext = super.getAsyncContext(); - return new FilterInvokingAsyncContext(asyncContext, filterInvoker, this, servletResponse); - } - } - - // AsyncContext wrapper that is necessary for two reasons: - // 1) Run response filters when AsyncContext.complete() is called. - // 2) Eliminate paths where application code can get its hands on un-wrapped response object, circumventing - // running of response filters. - private static class FilterInvokingAsyncContext implements AsyncContext { - private final AsyncContext delegate; - private final OneTimeRunnable filterInvoker; - private final ServletRequest servletRequest; - private final ServletResponse servletResponse; - - public FilterInvokingAsyncContext( - AsyncContext delegate, - OneTimeRunnable filterInvoker, - ServletRequest servletRequest, - ServletResponse servletResponse) { - this.delegate = delegate; - this.filterInvoker = filterInvoker; - this.servletRequest = servletRequest; - this.servletResponse = servletResponse; - } - - @Override - public ServletRequest getRequest() { - return servletRequest; - } - - @Override - public ServletResponse getResponse() { - return servletResponse; - } - - @Override - public boolean hasOriginalRequestAndResponse() { - return delegate.hasOriginalRequestAndResponse(); - } - - @Override - public void dispatch() { - delegate.dispatch(); - } - - @Override - public void dispatch(String s) { - delegate.dispatch(s); - } - - @Override - public void dispatch(ServletContext servletContext, String s) { - delegate.dispatch(servletContext, s); - } - - @Override - public void complete() { - // Completing may commit the response, so this is the last chance to run response filters. - filterInvoker.runIfFirstInvocation(); - delegate.complete(); - } - - @Override - public void start(Runnable runnable) { - delegate.start(runnable); - } - - @Override - public void addListener(AsyncListener asyncListener) { - delegate.addListener(asyncListener); - } - - @Override - public void addListener(AsyncListener asyncListener, ServletRequest servletRequest, ServletResponse servletResponse) { - delegate.addListener(asyncListener, servletRequest, servletResponse); - } - - @Override - public <T extends AsyncListener> T createListener(Class<T> aClass) throws ServletException { - return delegate.createListener(aClass); - } - - @Override - public void setTimeout(long l) { - delegate.setTimeout(l); - } - - @Override - public long getTimeout() { - return delegate.getTimeout(); - } - } - - private static class FilterInvokingResponseWrapper extends HttpServletResponseWrapper { - private final OneTimeRunnable filterInvoker; - - public FilterInvokingResponseWrapper(HttpServletResponse response, OneTimeRunnable filterInvoker) { - super(response); - this.filterInvoker = filterInvoker; - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - ServletOutputStream delegate = super.getOutputStream(); - return new FilterInvokingServletOutputStream(delegate, filterInvoker); - } - - @Override - public PrintWriter getWriter() throws IOException { - PrintWriter delegate = super.getWriter(); - return new FilterInvokingPrintWriter(delegate, filterInvoker); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java deleted file mode 100644 index 41a1ffc2709..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.container.logging.AccessLogEntry; -import com.yahoo.jdisc.Metric; -import com.yahoo.jdisc.handler.OverloadException; -import com.yahoo.jdisc.http.HttpRequest.Method; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Enumeration; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnection; - -/** - * @author Simon Thoresen Hult - * @author bjorncs - */ -@WebServlet(asyncSupported = true, description = "Bridge between Servlet and JDisc APIs") -class JDiscHttpServlet extends HttpServlet { - - public static final String ATTRIBUTE_NAME_ACCESS_LOG_ENTRY = JDiscHttpServlet.class.getName() + "_access-log-entry"; - - private final static Logger log = Logger.getLogger(JDiscHttpServlet.class.getName()); - private final JDiscContext context; - - private static final Set<String> servletSupportedMethods = - Stream.of(Method.OPTIONS, Method.GET, Method.HEAD, Method.POST, Method.PUT, Method.DELETE, Method.TRACE) - .map(Method::name) - .collect(Collectors.toSet()); - - public JDiscHttpServlet(JDiscContext context) { - this.context = context; - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - @Override - protected void doHead(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - @Override - protected void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - @Override - protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - @Override - protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - @Override - protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws IOException { - dispatchHttpRequest(request, response); - } - - /** - * Override to set connector attribute before the request becomes an upgrade request in the web socket case. - * (After the upgrade, the HttpConnection is no longer available.) - */ - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - request.setAttribute(JDiscServerConnector.REQUEST_ATTRIBUTE, getConnector(request)); - - Metric.Context metricContext = getMetricContext(request); - context.metric.add(MetricDefinitions.NUM_REQUESTS, 1, metricContext); - context.metric.add(MetricDefinitions.JDISC_HTTP_REQUESTS, 1, metricContext); - - String method = request.getMethod().toUpperCase(); - if (servletSupportedMethods.contains(method)) { - super.service(request, response); - } else if (method.equals(Method.PATCH.name())) { - // PATCH method is not handled by the Servlet spec - dispatchHttpRequest(request, response); - } else { - // Divergence from HTTP / Servlet spec: JDisc returns 405 for both unknown and known (but unsupported) methods. - response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - } - } - - static JDiscServerConnector getConnector(HttpServletRequest request) { - return (JDiscServerConnector)getConnection(request).getConnector(); - } - - private void dispatchHttpRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { - AccessLogEntry accessLogEntry = new AccessLogEntry(); - request.setAttribute(ATTRIBUTE_NAME_ACCESS_LOG_ENTRY, accessLogEntry); - try { - switch (request.getDispatcherType()) { - case REQUEST: - new HttpRequestDispatch(context, accessLogEntry, getMetricContext(request), request, response).dispatch(); - break; - default: - if (log.isLoggable(Level.INFO)) { - log.info("Unexpected " + request.getDispatcherType() + "; " + formatAttributes(request)); - } - break; - } - } catch (OverloadException e) { - // nop - } catch (RuntimeException e) { - throw new ExceptionWrapper(e); - } - } - - private static Metric.Context getMetricContext(HttpServletRequest request) { - return JDiscServerConnector.fromRequest(request).createRequestMetricContext(request, Map.of()); - } - - private static String formatAttributes(final HttpServletRequest request) { - StringBuilder out = new StringBuilder(); - out.append("attributes = {"); - for (Enumeration<String> names = request.getAttributeNames(); names.hasMoreElements(); ) { - String name = names.nextElement(); - out.append(" '").append(name).append("' = '").append(request.getAttribute(name)).append("'"); - if (names.hasMoreElements()) { - out.append(","); - } - } - out.append(" }"); - return out.toString(); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java deleted file mode 100644 index 99d0c5c8d8c..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.Metric; -import com.yahoo.jdisc.http.ConnectorConfig; -import org.eclipse.jetty.io.ConnectionStatistics; -import org.eclipse.jetty.server.ConnectionFactory; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; - -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; -import java.net.Socket; -import java.net.SocketException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -/** - * @author bjorncs - */ -class JDiscServerConnector extends ServerConnector { - - public static final String REQUEST_ATTRIBUTE = JDiscServerConnector.class.getName(); - private final Metric.Context metricCtx; - private final ConnectionStatistics statistics; - private final ConnectorConfig config; - private final boolean tcpKeepAlive; - private final boolean tcpNoDelay; - private final Metric metric; - private final String connectorName; - private final int listenPort; - - JDiscServerConnector(ConnectorConfig config, Metric metric, Server server, JettyConnectionLogger connectionLogger, ConnectionFactory... factories) { - super(server, factories); - this.config = config; - this.tcpKeepAlive = config.tcpKeepAliveEnabled(); - this.tcpNoDelay = config.tcpNoDelay(); - this.metric = metric; - this.connectorName = config.name(); - this.listenPort = config.listenPort(); - this.metricCtx = metric.createContext(createConnectorDimensions(listenPort, connectorName)); - - this.statistics = new ConnectionStatistics(); - addBean(statistics); - ConnectorConfig.Throttling throttlingConfig = config.throttling(); - if (throttlingConfig.enabled()) { - new ConnectionThrottler(this, throttlingConfig).registerWithConnector(); - } - addBean(connectionLogger); - } - - @Override - protected void configure(final Socket socket) { - super.configure(socket); - try { - socket.setKeepAlive(tcpKeepAlive); - socket.setTcpNoDelay(tcpNoDelay); - } catch (SocketException ignored) { - } - } - - public ConnectionStatistics getStatistics() { - return statistics; - } - - public Metric.Context getConnectorMetricContext() { - return metricCtx; - } - - public Metric.Context createRequestMetricContext(HttpServletRequest request, Map<String, String> extraDimensions) { - String method = request.getMethod(); - String scheme = request.getScheme(); - boolean clientAuthenticated = request.getAttribute(com.yahoo.jdisc.http.servlet.ServletRequest.SERVLET_REQUEST_X509CERT) != null; - Map<String, Object> dimensions = createConnectorDimensions(listenPort, connectorName); - dimensions.put(MetricDefinitions.METHOD_DIMENSION, method); - dimensions.put(MetricDefinitions.SCHEME_DIMENSION, scheme); - dimensions.put(MetricDefinitions.CLIENT_AUTHENTICATED_DIMENSION, Boolean.toString(clientAuthenticated)); - String serverName = Optional.ofNullable(request.getServerName()).orElse("unknown"); - dimensions.put(MetricDefinitions.REQUEST_SERVER_NAME_DIMENSION, serverName); - dimensions.putAll(extraDimensions); - return metric.createContext(dimensions); - } - - public static JDiscServerConnector fromRequest(ServletRequest request) { - return (JDiscServerConnector) request.getAttribute(REQUEST_ATTRIBUTE); - } - - ConnectorConfig connectorConfig() { - return config; - } - - int listenPort() { - return listenPort; - } - - private static Map<String, Object> createConnectorDimensions(int listenPort, String connectorName) { - Map<String, Object> props = new HashMap<>(); - props.put(MetricDefinitions.NAME_DIMENSION, connectorName); - props.put(MetricDefinitions.PORT_DIMENSION, listenPort); - return props; - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java deleted file mode 100644 index cd1ca490f61..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright Verizon Media. 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.container.logging.ConnectionLog; -import com.yahoo.container.logging.ConnectionLogEntry; -import com.yahoo.container.logging.ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry; -import com.yahoo.io.HexDump; -import com.yahoo.jdisc.http.ServerConfig; -import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.SocketChannelEndPoint; -import org.eclipse.jetty.io.ssl.SslConnection; -import org.eclipse.jetty.io.ssl.SslHandshakeListener; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpConnection; -import org.eclipse.jetty.server.ProxyConnectionFactory; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.component.AbstractLifeCycle; - -import javax.net.ssl.ExtendedSSLSession; -import javax.net.ssl.SNIHostName; -import javax.net.ssl.SNIServerName; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.StandardConstants; -import java.net.InetSocketAddress; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Jetty integration for jdisc connection log ({@link ConnectionLog}). - * - * @author bjorncs - */ -class JettyConnectionLogger extends AbstractLifeCycle implements Connection.Listener, HttpChannel.Listener, SslHandshakeListener { - - static final String CONNECTION_ID_REQUEST_ATTRIBUTE = "jdisc.request.connection.id"; - - private static final Logger log = Logger.getLogger(JettyConnectionLogger.class.getName()); - - private final ConcurrentMap<IdentityKey<SocketChannelEndPoint>, ConnectionInfo> connectionInfo = new ConcurrentHashMap<>(); - private final ConcurrentMap<IdentityKey<SSLEngine>, ConnectionInfo> sslToConnectionInfo = new ConcurrentHashMap<>(); - - private final boolean enabled; - private final ConnectionLog connectionLog; - - JettyConnectionLogger(ServerConfig.ConnectionLog config, ConnectionLog connectionLog) { - this.enabled = config.enabled(); - this.connectionLog = connectionLog; - log.log(Level.FINE, () -> "Jetty connection logger is " + (config.enabled() ? "enabled" : "disabled")); - } - - // - // AbstractLifeCycle methods start - // - @Override - protected void doStop() { - handleListenerInvocation("AbstractLifeCycle", "doStop", "", List.of(), () -> { - log.log(Level.FINE, () -> "Jetty connection logger is stopped"); - }); - } - - @Override - protected void doStart() { - handleListenerInvocation("AbstractLifeCycle", "doStart", "", List.of(), () -> { - log.log(Level.FINE, () -> "Jetty connection logger is started"); - }); - } - // - // AbstractLifeCycle methods stop - // - - // - // Connection.Listener methods start - // - @Override - public void onOpened(Connection connection) { - handleListenerInvocation("Connection.Listener", "onOpened", "%h", List.of(connection), () -> { - SocketChannelEndPoint endpoint = findUnderlyingSocketEndpoint(connection.getEndPoint()); - var endpointKey = IdentityKey.of(endpoint); - ConnectionInfo info = connectionInfo.get(endpointKey); - if (info == null) { - info = ConnectionInfo.from(endpoint); - connectionInfo.put(IdentityKey.of(endpoint), info); - } - if (connection instanceof SslConnection) { - SSLEngine sslEngine = ((SslConnection) connection).getSSLEngine(); - sslToConnectionInfo.put(IdentityKey.of(sslEngine), info); - } - if (connection.getEndPoint() instanceof ProxyConnectionFactory.ProxyEndPoint) { - InetSocketAddress remoteAddress = connection.getEndPoint().getRemoteAddress(); - info.setRemoteAddress(remoteAddress); - } - }); - } - - @Override - public void onClosed(Connection connection) { - handleListenerInvocation("Connection.Listener", "onClosed", "%h", List.of(connection), () -> { - SocketChannelEndPoint endpoint = findUnderlyingSocketEndpoint(connection.getEndPoint()); - var endpointKey = IdentityKey.of(endpoint); - ConnectionInfo info = connectionInfo.get(endpointKey); - if (info == null) return; // Closed connection already handled - if (connection instanceof HttpConnection) { - info.setHttpBytes(connection.getBytesIn(), connection.getBytesOut()); - } - if (!endpoint.isOpen()) { - info.setClosedAt(System.currentTimeMillis()); - connectionLog.log(info.toLogEntry()); - connectionInfo.remove(endpointKey); - } - }); - } - // - // Connection.Listener methods end - // - - // - // HttpChannel.Listener methods start - // - @Override - public void onRequestBegin(Request request) { - handleListenerInvocation("HttpChannel.Listener", "onRequestBegin", "%h", List.of(request), () -> { - SocketChannelEndPoint endpoint = findUnderlyingSocketEndpoint(request.getHttpChannel().getEndPoint()); - ConnectionInfo info = Objects.requireNonNull(connectionInfo.get(IdentityKey.of(endpoint))); - info.incrementRequests(); - request.setAttribute(CONNECTION_ID_REQUEST_ATTRIBUTE, info.uuid()); - }); - } - - @Override - public void onResponseBegin(Request request) { - handleListenerInvocation("HttpChannel.Listener", "onResponseBegin", "%h", List.of(request), () -> { - SocketChannelEndPoint endpoint = findUnderlyingSocketEndpoint(request.getHttpChannel().getEndPoint()); - ConnectionInfo info = Objects.requireNonNull(connectionInfo.get(IdentityKey.of(endpoint))); - info.incrementResponses(); - }); - } - // - // HttpChannel.Listener methods end - // - - // - // SslHandshakeListener methods start - // - @Override - public void handshakeSucceeded(Event event) { - SSLEngine sslEngine = event.getSSLEngine(); - handleListenerInvocation("SslHandshakeListener", "handshakeSucceeded", "sslEngine=%h", List.of(sslEngine), () -> { - ConnectionInfo info = sslToConnectionInfo.remove(IdentityKey.of(sslEngine)); - info.setSslSessionDetails(sslEngine.getSession()); - }); - } - - @Override - public void handshakeFailed(Event event, Throwable failure) { - SSLEngine sslEngine = event.getSSLEngine(); - handleListenerInvocation("SslHandshakeListener", "handshakeFailed", "sslEngine=%h,failure=%s", List.of(sslEngine, failure), () -> { - log.log(Level.FINE, failure, failure::toString); - ConnectionInfo info = sslToConnectionInfo.remove(IdentityKey.of(sslEngine)); - info.setSslHandshakeFailure((SSLHandshakeException)failure); - }); - } - // - // SslHandshakeListener methods end - // - - private void handleListenerInvocation( - String listenerType, String methodName, String methodArgumentsFormat, List<Object> methodArguments, ListenerHandler handler) { - if (!enabled) return; - try { - log.log(Level.FINE, () -> String.format(listenerType + "." + methodName + "(" + methodArgumentsFormat + ")", methodArguments.toArray())); - handler.run(); - } catch (Exception e) { - log.log(Level.WARNING, String.format("Exception in %s.%s listener: %s", listenerType, methodName, e.getMessage()), e); - } - } - - /** - * Protocol layers are connected through each {@link Connection}'s {@link EndPoint} reference. - * This methods iterates through the endpoints recursively to find the underlying socket endpoint. - */ - private static SocketChannelEndPoint findUnderlyingSocketEndpoint(EndPoint endpoint) { - if (endpoint instanceof SocketChannelEndPoint) { - return (SocketChannelEndPoint) endpoint; - } else if (endpoint instanceof SslConnection.DecryptedEndPoint) { - var decryptedEndpoint = (SslConnection.DecryptedEndPoint) endpoint; - return findUnderlyingSocketEndpoint(decryptedEndpoint.getSslConnection().getEndPoint()); - } else if (endpoint instanceof ProxyConnectionFactory.ProxyEndPoint) { - var proxyEndpoint = (ProxyConnectionFactory.ProxyEndPoint) endpoint; - return findUnderlyingSocketEndpoint(proxyEndpoint.unwrap()); - } else { - throw new IllegalArgumentException("Unknown connection endpoint type: " + endpoint.getClass().getName()); - } - } - - @FunctionalInterface private interface ListenerHandler { void run() throws Exception; } - - private static class ConnectionInfo { - private final UUID uuid; - private final long createdAt; - private final InetSocketAddress localAddress; - private final InetSocketAddress peerAddress; - - private long closedAt = 0; - private long httpBytesReceived = 0; - private long httpBytesSent = 0; - private long requests = 0; - private long responses = 0; - private InetSocketAddress remoteAddress; - private byte[] sslSessionId; - private String sslProtocol; - private String sslCipherSuite; - private String sslPeerSubject; - private Date sslPeerNotBefore; - private Date sslPeerNotAfter; - private List<SNIServerName> sslSniServerNames; - private SSLHandshakeException sslHandshakeException; - - private ConnectionInfo(UUID uuid, long createdAt, InetSocketAddress localAddress, InetSocketAddress peerAddress) { - this.uuid = uuid; - this.createdAt = createdAt; - this.localAddress = localAddress; - this.peerAddress = peerAddress; - } - - static ConnectionInfo from(SocketChannelEndPoint endpoint) { - return new ConnectionInfo( - UUID.randomUUID(), - endpoint.getCreatedTimeStamp(), - endpoint.getLocalAddress(), - endpoint.getRemoteAddress()); - } - - synchronized UUID uuid() { return uuid; } - - synchronized ConnectionInfo setClosedAt(long closedAt) { - this.closedAt = closedAt; - return this; - } - - synchronized ConnectionInfo setHttpBytes(long received, long sent) { - this.httpBytesReceived = received; - this.httpBytesSent = sent; - return this; - } - - synchronized ConnectionInfo incrementRequests() { ++this.requests; return this; } - - synchronized ConnectionInfo incrementResponses() { ++this.responses; return this; } - - synchronized ConnectionInfo setRemoteAddress(InetSocketAddress remoteAddress) { - this.remoteAddress = remoteAddress; - return this; - } - - synchronized ConnectionInfo setSslSessionDetails(SSLSession session) { - this.sslCipherSuite = session.getCipherSuite(); - this.sslProtocol = session.getProtocol(); - this.sslSessionId = session.getId(); - if (session instanceof ExtendedSSLSession) { - ExtendedSSLSession extendedSession = (ExtendedSSLSession) session; - this.sslSniServerNames = extendedSession.getRequestedServerNames(); - } - try { - this.sslPeerSubject = session.getPeerPrincipal().getName(); - X509Certificate peerCertificate = (X509Certificate) session.getPeerCertificates()[0]; - this.sslPeerNotBefore = peerCertificate.getNotBefore(); - this.sslPeerNotAfter = peerCertificate.getNotAfter(); - } catch (SSLPeerUnverifiedException e) { - // Throw if peer is not authenticated (e.g when client auth is disabled) - // JSSE provides no means of checking for client authentication without catching this exception - } - return this; - } - - synchronized ConnectionInfo setSslHandshakeFailure(SSLHandshakeException exception) { - this.sslHandshakeException = exception; - return this; - } - - synchronized ConnectionLogEntry toLogEntry() { - ConnectionLogEntry.Builder builder = ConnectionLogEntry.builder(uuid, Instant.ofEpochMilli(createdAt)); - if (closedAt > 0) { - builder.withDuration((closedAt - createdAt) / 1000D); - } - if (httpBytesReceived > 0) { - builder.withHttpBytesReceived(httpBytesReceived); - } - if (httpBytesSent > 0) { - builder.withHttpBytesSent(httpBytesSent); - } - if (requests > 0) { - builder.withRequests(requests); - } - if (responses > 0) { - builder.withResponses(responses); - } - if (peerAddress != null) { - builder.withPeerAddress(peerAddress.getHostString()) - .withPeerPort(peerAddress.getPort()); - } - if (localAddress != null) { - builder.withLocalAddress(localAddress.getHostString()) - .withLocalPort(localAddress.getPort()); - } - if (remoteAddress != null) { - builder.withRemoteAddress(remoteAddress.getHostString()) - .withRemotePort(remoteAddress.getPort()); - } - if (sslProtocol != null && sslCipherSuite != null && sslSessionId != null) { - builder.withSslProtocol(sslProtocol) - .withSslCipherSuite(sslCipherSuite) - .withSslSessionId(HexDump.toHexString(sslSessionId)); - } - if (sslSniServerNames != null) { - sslSniServerNames.stream() - .filter(name -> name instanceof SNIHostName && name.getType() == StandardConstants.SNI_HOST_NAME) - .map(name -> ((SNIHostName) name).getAsciiName()) - .findAny() - .ifPresent(builder::withSslSniServerName); - } - if (sslPeerSubject != null && sslPeerNotAfter != null && sslPeerNotBefore != null) { - builder.withSslPeerSubject(sslPeerSubject) - .withSslPeerNotAfter(sslPeerNotAfter.toInstant()) - .withSslPeerNotBefore(sslPeerNotBefore.toInstant()); - } - if (sslHandshakeException != null) { - List<ExceptionEntry> exceptionChain = new ArrayList<>(); - Throwable cause = sslHandshakeException; - while (cause != null) { - exceptionChain.add(new ExceptionEntry(cause.getClass().getName(), cause.getMessage())); - cause = cause.getCause(); - } - String type = SslHandshakeFailure.fromSslHandshakeException(sslHandshakeException) - .map(SslHandshakeFailure::failureType) - .orElse("UNKNOWN"); - builder.withSslHandshakeFailure(new ConnectionLogEntry.SslHandshakeFailure(type, exceptionChain)); - } - return builder.build(); - } - - } - - private static class IdentityKey<T> { - final T instance; - - IdentityKey(T instance) { this.instance = instance; } - - static <T> IdentityKey<T> of(T instance) { return new IdentityKey<>(instance); } - - @Override public int hashCode() { return System.identityHashCode(instance); } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof IdentityKey<?>)) return false; - IdentityKey<?> other = (IdentityKey<?>) obj; - return this.instance == other.instance; - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java deleted file mode 100644 index 510c561c10f..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2018 Yahoo Holdings. 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.google.inject.Inject; -import com.yahoo.component.ComponentId; -import com.yahoo.component.provider.ComponentRegistry; -import com.yahoo.concurrent.DaemonThreadFactory; -import com.yahoo.container.logging.AccessLog; -import com.yahoo.container.logging.ConnectionLog; -import com.yahoo.container.logging.RequestLog; -import com.yahoo.jdisc.Metric; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ServerConfig; -import com.yahoo.jdisc.http.ServletPathsConfig; -import com.yahoo.jdisc.service.AbstractServerProvider; -import com.yahoo.jdisc.service.CurrentContainer; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.jmx.ConnectorServer; -import org.eclipse.jetty.jmx.MBeanContainer; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.StatisticsHandler; -import org.eclipse.jetty.server.handler.gzip.GzipHandler; -import org.eclipse.jetty.server.handler.gzip.GzipHttpOutputInterceptor; -import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.log.JavaUtilLog; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.thread.QueuedThreadPool; - -import javax.management.remote.JMXServiceURL; -import javax.servlet.DispatcherType; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.net.BindException; -import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import static java.util.stream.Collectors.toList; - -/** - * @author Simon Thoresen Hult - * @author bjorncs - */ -public class JettyHttpServer extends AbstractServerProvider { - - private final static Logger log = Logger.getLogger(JettyHttpServer.class.getName()); - - private final ExecutorService janitor; - - private final Server server; - private final List<Integer> listenedPorts = new ArrayList<>(); - private final ServerMetricReporter metricsReporter; - - @Inject - public JettyHttpServer(CurrentContainer container, - Metric metric, - ServerConfig serverConfig, - ServletPathsConfig servletPathsConfig, - FilterBindings filterBindings, - ComponentRegistry<ConnectorFactory> connectorFactories, - ComponentRegistry<ServletHolder> servletHolders, - FilterInvoker filterInvoker, - RequestLog requestLog, - ConnectionLog connectionLog) { - super(container); - if (connectorFactories.allComponents().isEmpty()) - throw new IllegalArgumentException("No connectors configured."); - - initializeJettyLogging(); - - server = new Server(); - server.setStopTimeout((long)(serverConfig.stopTimeout() * 1000.0)); - server.setRequestLog(new AccessLogRequestLog(requestLog, serverConfig.accessLog())); - setupJmx(server, serverConfig); - configureJettyThreadpool(server, serverConfig); - JettyConnectionLogger connectionLogger = new JettyConnectionLogger(serverConfig.connectionLog(), connectionLog); - - for (ConnectorFactory connectorFactory : connectorFactories.allComponents()) { - ConnectorConfig connectorConfig = connectorFactory.getConnectorConfig(); - server.addConnector(connectorFactory.createConnector(metric, server, connectionLogger)); - listenedPorts.add(connectorConfig.listenPort()); - } - - janitor = newJanitor(); - - JDiscContext jDiscContext = new JDiscContext(filterBindings, - container, - janitor, - metric, - serverConfig); - - ServletHolder jdiscServlet = new ServletHolder(new JDiscHttpServlet(jDiscContext)); - FilterHolder jDiscFilterInvokerFilter = new FilterHolder(new JDiscFilterInvokerFilter(jDiscContext, filterInvoker)); - - List<JDiscServerConnector> connectors = Arrays.stream(server.getConnectors()) - .map(JDiscServerConnector.class::cast) - .collect(toList()); - - server.setHandler(getHandlerCollection(serverConfig, - servletPathsConfig, - connectors, - jdiscServlet, - servletHolders, - jDiscFilterInvokerFilter)); - this.metricsReporter = new ServerMetricReporter(metric, server); - } - - private static void initializeJettyLogging() { - // Note: Jetty is logging stderr if no logger is explicitly configured - try { - Log.setLog(new JavaUtilLog()); - } catch (Exception e) { - throw new RuntimeException("Unable to initialize logging framework for Jetty"); - } - } - - private static void setupJmx(Server server, ServerConfig serverConfig) { - if (serverConfig.jmx().enabled()) { - System.setProperty("java.rmi.server.hostname", "localhost"); - server.addBean(new MBeanContainer(ManagementFactory.getPlatformMBeanServer())); - server.addBean(new ConnectorServer(createJmxLoopbackOnlyServiceUrl(serverConfig.jmx().listenPort()), - "org.eclipse.jetty.jmx:name=rmiconnectorserver")); - } - } - - private static void configureJettyThreadpool(Server server, ServerConfig config) { - QueuedThreadPool pool = (QueuedThreadPool) server.getThreadPool(); - pool.setMaxThreads(config.maxWorkerThreads()); - pool.setMinThreads(config.minWorkerThreads()); - } - - private static JMXServiceURL createJmxLoopbackOnlyServiceUrl(int port) { - try { - return new JMXServiceURL("rmi", "localhost", port, "/jndi/rmi://localhost:" + port + "/jmxrmi"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - private HandlerCollection getHandlerCollection(ServerConfig serverConfig, - ServletPathsConfig servletPathsConfig, - List<JDiscServerConnector> connectors, - ServletHolder jdiscServlet, - ComponentRegistry<ServletHolder> servletHolders, - FilterHolder jDiscFilterInvokerFilter) { - ServletContextHandler servletContextHandler = createServletContextHandler(); - - servletHolders.allComponentsById().forEach((id, servlet) -> { - String path = getServletPath(servletPathsConfig, id); - servletContextHandler.addServlet(servlet, path); - servletContextHandler.addFilter(jDiscFilterInvokerFilter, path, EnumSet.allOf(DispatcherType.class)); - }); - - servletContextHandler.addServlet(jdiscServlet, "/*"); - - List<ConnectorConfig> connectorConfigs = connectors.stream().map(JDiscServerConnector::connectorConfig).collect(toList()); - var secureRedirectHandler = new SecuredRedirectHandler(connectorConfigs); - secureRedirectHandler.setHandler(servletContextHandler); - - var proxyHandler = new HealthCheckProxyHandler(connectors); - proxyHandler.setHandler(secureRedirectHandler); - - var authEnforcer = new TlsClientAuthenticationEnforcer(connectorConfigs); - authEnforcer.setHandler(proxyHandler); - - GzipHandler gzipHandler = newGzipHandler(serverConfig); - gzipHandler.setHandler(authEnforcer); - - HttpResponseStatisticsCollector statisticsCollector = - new HttpResponseStatisticsCollector(serverConfig.metric().monitoringHandlerPaths(), - serverConfig.metric().searchHandlerPaths()); - statisticsCollector.setHandler(gzipHandler); - - StatisticsHandler statisticsHandler = newStatisticsHandler(); - statisticsHandler.setHandler(statisticsCollector); - - HandlerCollection handlerCollection = new HandlerCollection(); - handlerCollection.setHandlers(new Handler[] { statisticsHandler }); - return handlerCollection; - } - - private static String getServletPath(ServletPathsConfig servletPathsConfig, ComponentId id) { - return "/" + servletPathsConfig.servlets(id.stringValue()).path(); - } - - private ServletContextHandler createServletContextHandler() { - ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS); - servletContextHandler.setContextPath("/"); - servletContextHandler.setDisplayName(getDisplayName(listenedPorts)); - return servletContextHandler; - } - - private static String getDisplayName(List<Integer> ports) { - return ports.stream().map(Object::toString).collect(Collectors.joining(":")); - } - - // Separate threadpool for tasks that cannot be executed on the jdisc default threadpool due to risk of deadlock - private static ExecutorService newJanitor() { - int threadPoolSize = Math.max(1, Runtime.getRuntime().availableProcessors()/8); - log.info("Creating janitor executor with " + threadPoolSize + " threads"); - return Executors.newFixedThreadPool( - threadPoolSize, - new DaemonThreadFactory(JettyHttpServer.class.getName() + "-Janitor-")); - } - - @Override - public void start() { - try { - server.start(); - metricsReporter.start(); - logEffectiveSslConfiguration(); - } catch (final Exception e) { - if (e instanceof IOException && e.getCause() instanceof BindException) { - throw new RuntimeException("Failed to start server due to BindException. ListenPorts = " + listenedPorts.toString(), e.getCause()); - } - throw new RuntimeException("Failed to start server.", e); - } - } - - private void logEffectiveSslConfiguration() { - if (!server.isStarted()) throw new IllegalStateException(); - for (Connector connector : server.getConnectors()) { - ServerConnector serverConnector = (ServerConnector) connector; - int localPort = serverConnector.getLocalPort(); - var sslConnectionFactory = serverConnector.getConnectionFactory(SslConnectionFactory.class); - if (sslConnectionFactory != null) { - var sslContextFactory = sslConnectionFactory.getSslContextFactory(); - log.info(String.format("Enabled SSL cipher suites for port '%d': %s", - localPort, Arrays.toString(sslContextFactory.getSelectedCipherSuites()))); - log.info(String.format("Enabled SSL protocols for port '%d': %s", - localPort, Arrays.toString(sslContextFactory.getSelectedProtocols()))); - } - } - } - - @Override - public void close() { - try { - log.log(Level.INFO, String.format("Shutting down server (graceful=%b, timeout=%.1fs)", isGracefulShutdownEnabled(), server.getStopTimeout()/1000d)); - server.stop(); - log.log(Level.INFO, "Server shutdown completed"); - } catch (final Exception e) { - log.log(Level.SEVERE, "Server shutdown threw an unexpected exception.", e); - } - - metricsReporter.shutdown(); - janitor.shutdown(); - } - - private boolean isGracefulShutdownEnabled() { - return server.getChildHandlersByClass(StatisticsHandler.class).length > 0 && server.getStopTimeout() > 0; - } - - public int getListenPort() { - return ((ServerConnector)server.getConnectors()[0]).getLocalPort(); - } - - Server server() { return server; } - - private StatisticsHandler newStatisticsHandler() { - StatisticsHandler statisticsHandler = new StatisticsHandler(); - statisticsHandler.statsReset(); - return statisticsHandler; - } - - private GzipHandler newGzipHandler(ServerConfig serverConfig) { - GzipHandler gzipHandler = new GzipHandlerWithVaryHeaderFixed(); - gzipHandler.setCompressionLevel(serverConfig.responseCompressionLevel()); - gzipHandler.setInflateBufferSize(8 * 1024); - gzipHandler.setIncludedMethods("GET", "POST", "PUT", "PATCH"); - return gzipHandler; - } - - /** A subclass which overrides Jetty's default behavior of including user-agent in the vary field */ - private static class GzipHandlerWithVaryHeaderFixed extends GzipHandler { - - @Override - public HttpField getVaryField() { - return GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING; - } - - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java deleted file mode 100644 index 5e953179b53..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/MetricDefinitions.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -/** - * Name and dimensions for jdisc/container metrics - * - * @author bjorncs - */ -class MetricDefinitions { - static final String NAME_DIMENSION = "serverName"; - static final String PORT_DIMENSION = "serverPort"; - static final String METHOD_DIMENSION = "httpMethod"; - static final String SCHEME_DIMENSION = "scheme"; - static final String REQUEST_TYPE_DIMENSION = "requestType"; - static final String CLIENT_IP_DIMENSION = "clientIp"; - static final String CLIENT_AUTHENTICATED_DIMENSION = "clientAuthenticated"; - static final String REQUEST_SERVER_NAME_DIMENSION = "requestServerName"; - static final String FILTER_CHAIN_ID_DIMENSION = "chainId"; - - static final String NUM_OPEN_CONNECTIONS = "serverNumOpenConnections"; - static final String NUM_CONNECTIONS_OPEN_MAX = "serverConnectionsOpenMax"; - static final String CONNECTION_DURATION_MAX = "serverConnectionDurationMax"; - static final String CONNECTION_DURATION_MEAN = "serverConnectionDurationMean"; - static final String CONNECTION_DURATION_STD_DEV = "serverConnectionDurationStdDev"; - static final String NUM_PREMATURELY_CLOSED_CONNECTIONS = "jdisc.http.request.prematurely_closed"; - - static final String NUM_BYTES_RECEIVED = "serverBytesReceived"; - static final String NUM_BYTES_SENT = "serverBytesSent"; - - static final String NUM_CONNECTIONS = "serverNumConnections"; - - /* For historical reasons, these are all aliases for the same metric. 'jdisc.http' should ideally be the only one. */ - static final String JDISC_HTTP_REQUESTS = "jdisc.http.requests"; - static final String NUM_REQUESTS = "serverNumRequests"; - - static final String NUM_SUCCESSFUL_RESPONSES = "serverNumSuccessfulResponses"; - static final String NUM_FAILED_RESPONSES = "serverNumFailedResponses"; - static final String NUM_SUCCESSFUL_WRITES = "serverNumSuccessfulResponseWrites"; - static final String NUM_FAILED_WRITES = "serverNumFailedResponseWrites"; - - static final String TOTAL_SUCCESSFUL_LATENCY = "serverTotalSuccessfulResponseLatency"; - static final String TOTAL_FAILED_LATENCY = "serverTotalFailedResponseLatency"; - static final String TIME_TO_FIRST_BYTE = "serverTimeToFirstByte"; - - static final String RESPONSES_1XX = "http.status.1xx"; - static final String RESPONSES_2XX = "http.status.2xx"; - static final String RESPONSES_3XX = "http.status.3xx"; - static final String RESPONSES_4XX = "http.status.4xx"; - static final String RESPONSES_5XX = "http.status.5xx"; - static final String RESPONSES_401 = "http.status.401"; - static final String RESPONSES_403 = "http.status.403"; - - static final String STARTED_MILLIS = "serverStartedMillis"; - - static final String URI_LENGTH = "jdisc.http.request.uri_length"; - static final String CONTENT_SIZE = "jdisc.http.request.content_size"; - - static final String SSL_HANDSHAKE_FAILURE_MISSING_CLIENT_CERT = "jdisc.http.ssl.handshake.failure.missing_client_cert"; - static final String SSL_HANDSHAKE_FAILURE_EXPIRED_CLIENT_CERT = "jdisc.http.ssl.handshake.failure.expired_client_cert"; - static final String SSL_HANDSHAKE_FAILURE_INVALID_CLIENT_CERT = "jdisc.http.ssl.handshake.failure.invalid_client_cert"; - static final String SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_PROTOCOLS = "jdisc.http.ssl.handshake.failure.incompatible_protocols"; - static final String SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_CIPHERS = "jdisc.http.ssl.handshake.failure.incompatible_ciphers"; - static final String SSL_HANDSHAKE_FAILURE_UNKNOWN = "jdisc.http.ssl.handshake.failure.unknown"; - - static final String JETTY_THREADPOOL_MAX_THREADS = "jdisc.http.jetty.threadpool.thread.max"; - static final String JETTY_THREADPOOL_MIN_THREADS = "jdisc.http.jetty.threadpool.thread.min"; - static final String JETTY_THREADPOOL_RESERVED_THREADS = "jdisc.http.jetty.threadpool.thread.reserved"; - static final String JETTY_THREADPOOL_BUSY_THREADS = "jdisc.http.jetty.threadpool.thread.busy"; - static final String JETTY_THREADPOOL_IDLE_THREADS = "jdisc.http.jetty.threadpool.thread.idle"; - static final String JETTY_THREADPOOL_TOTAL_THREADS = "jdisc.http.jetty.threadpool.thread.total"; - static final String JETTY_THREADPOOL_QUEUE_SIZE = "jdisc.http.jetty.threadpool.queue.size"; - - static final String FILTERING_REQUEST_HANDLED = "jdisc.http.filtering.request.handled"; - static final String FILTERING_REQUEST_UNHANDLED = "jdisc.http.filtering.request.unhandled"; - static final String FILTERING_RESPONSE_HANDLED = "jdisc.http.filtering.response.handled"; - static final String FILTERING_RESPONSE_UNHANDLED = "jdisc.http.filtering.response.unhandled"; - - private MetricDefinitions() {} -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/OneTimeRunnable.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/OneTimeRunnable.java deleted file mode 100644 index eb83d3d7d03..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/OneTimeRunnable.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * @author Tony Vaagenes - */ -public class OneTimeRunnable { - private final Runnable runnable; - private final AtomicBoolean hasRun = new AtomicBoolean(false); - - public OneTimeRunnable(Runnable runnable) { - this.runnable = runnable; - } - - public void runIfFirstInvocation() { - boolean previous = hasRun.getAndSet(true); - if (!previous) { - runnable.run(); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ReferenceCountingRequestHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ReferenceCountingRequestHandler.java deleted file mode 100644 index f2bf5b56d5c..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ReferenceCountingRequestHandler.java +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.Request; -import com.yahoo.jdisc.ResourceReference; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.SharedResource; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.NullContent; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.handler.ResponseHandler; - -import java.nio.ByteBuffer; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * This class wraps a request handler and does reference counting on the request for every object that depends on the - * request, such as the response handler, content channels and completion handlers. This ensures that requests (and - * hence the current container) will be referenced until the end of the request handling - even with async handling in - * non-framework threads - without requiring the application to handle this tedious work. - * - * @author bakksjo - */ -@SuppressWarnings("try") -class ReferenceCountingRequestHandler implements RequestHandler { - - private static final Logger log = Logger.getLogger(ReferenceCountingRequestHandler.class.getName()); - - final RequestHandler delegate; - - ReferenceCountingRequestHandler(RequestHandler delegate) { - Objects.requireNonNull(delegate, "delegate"); - this.delegate = delegate; - } - - @Override - public ContentChannel handleRequest(Request request, ResponseHandler responseHandler) { - try (final ResourceReference requestReference = request.refer()) { - ContentChannel contentChannel; - final ReferenceCountingResponseHandler referenceCountingResponseHandler - = new ReferenceCountingResponseHandler(request, new NullContentResponseHandler(responseHandler)); - try { - contentChannel = delegate.handleRequest(request, referenceCountingResponseHandler); - Objects.requireNonNull(contentChannel, "contentChannel"); - } catch (Throwable t) { - try { - // The response handler might never be invoked, due to the exception thrown from handleRequest(). - referenceCountingResponseHandler.unrefer(); - } catch (Throwable thrownFromUnrefer) { - log.log(Level.WARNING, "Unexpected problem", thrownFromUnrefer); - } - throw t; - } - return new ReferenceCountingContentChannel(request, contentChannel); - } - } - - @Override - public void handleTimeout(Request request, ResponseHandler responseHandler) { - delegate.handleTimeout(request, new NullContentResponseHandler(responseHandler)); - } - - @Override - public ResourceReference refer() { - return delegate.refer(); - } - - @Override - public void release() { - delegate.release(); - } - - @Override - public String toString() { - return delegate.toString(); - } - - private static class ReferenceCountingResponseHandler implements ResponseHandler { - - final SharedResource request; - final ResourceReference requestReference; - final ResponseHandler delegate; - final AtomicBoolean closed = new AtomicBoolean(false); - - ReferenceCountingResponseHandler(SharedResource request, ResponseHandler delegate) { - Objects.requireNonNull(request, "request"); - Objects.requireNonNull(delegate, "delegate"); - this.request = request; - this.delegate = delegate; - this.requestReference = request.refer(); - } - - @Override - public ContentChannel handleResponse(Response response) { - if (closed.getAndSet(true)) { - throw new IllegalStateException(delegate + " is already called."); - } - try (final ResourceReference ref = requestReference) { - ContentChannel contentChannel = delegate.handleResponse(response); - Objects.requireNonNull(contentChannel, "contentChannel"); - return new ReferenceCountingContentChannel(request, contentChannel); - } - } - - @Override - public String toString() { - return delegate.toString(); - } - - /** - * Close the reference that is normally closed by {@link #handleResponse(Response)}. - * - * This is to be used in error situations, where handleResponse() may not be invoked. - */ - public void unrefer() { - if (closed.getAndSet(true)) { - // This simply means that handleResponse() has been run, in which case we are - // guaranteed that the reference is closed. - return; - } - requestReference.close(); - } - } - - private static class ReferenceCountingContentChannel implements ContentChannel { - - final SharedResource request; - final ResourceReference requestReference; - final ContentChannel delegate; - - ReferenceCountingContentChannel(SharedResource request, ContentChannel delegate) { - Objects.requireNonNull(request, "request"); - Objects.requireNonNull(delegate, "delegate"); - this.request = request; - this.delegate = delegate; - this.requestReference = request.refer(); - } - - @Override - public void write(ByteBuffer buf, CompletionHandler completionHandler) { - final CompletionHandler referenceCountingCompletionHandler - = new ReferenceCountingCompletionHandler(request, completionHandler); - try { - delegate.write(buf, referenceCountingCompletionHandler); - } catch (Throwable t) { - try { - referenceCountingCompletionHandler.failed(t); - } catch (AlreadyCompletedException ignored) { - } catch (Throwable failFailure) { - log.log(Level.WARNING, "Failure during call to CompletionHandler.failed()", failFailure); - } - throw t; - } - } - - @Override - public void close(CompletionHandler completionHandler) { - final CompletionHandler referenceCountingCompletionHandler - = new ReferenceCountingCompletionHandler(request, completionHandler); - try (final ResourceReference ref = requestReference) { - delegate.close(referenceCountingCompletionHandler); - } catch (Throwable t) { - try { - referenceCountingCompletionHandler.failed(t); - } catch (AlreadyCompletedException ignored) { - } catch (Throwable failFailure) { - log.log(Level.WARNING, "Failure during call to CompletionHandler.failed()", failFailure); - } - throw t; - } - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - private static class AlreadyCompletedException extends IllegalStateException { - public AlreadyCompletedException(final CompletionHandler completionHandler) { - super(completionHandler + " is already called."); - } - } - - private static class ReferenceCountingCompletionHandler implements CompletionHandler { - - final ResourceReference requestReference; - final CompletionHandler delegate; - final AtomicBoolean closed = new AtomicBoolean(false); - - public ReferenceCountingCompletionHandler(SharedResource request, CompletionHandler delegate) { - this.delegate = delegate; - this.requestReference = request.refer(); - } - - @Override - public void completed() { - if (closed.getAndSet(true)) { - throw new AlreadyCompletedException(delegate); - } - try { - if (delegate != null) { - delegate.completed(); - } - } finally { - requestReference.close(); - } - } - - @Override - public void failed(Throwable t) { - if (closed.getAndSet(true)) { - throw new AlreadyCompletedException(delegate); - } - try (final ResourceReference ref = requestReference) { - if (delegate != null) { - delegate.failed(t); - } else { - log.log(Level.WARNING, "Uncaught completion failure.", t); - } - } - } - - @Override - public String toString() { - return String.valueOf(delegate); - } - } - - private static class NullContentResponseHandler implements ResponseHandler { - - final ResponseHandler delegate; - - NullContentResponseHandler(ResponseHandler delegate) { - Objects.requireNonNull(delegate, "delegate"); - this.delegate = delegate; - } - - @Override - public ContentChannel handleResponse(Response response) { - ContentChannel contentChannel = delegate.handleResponse(response); - if (contentChannel == null) { - contentChannel = NullContent.INSTANCE; - } - return contentChannel; - } - - @Override - public String toString() { - return delegate.toString(); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestException.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestException.java deleted file mode 100644 index eea69cd7f74..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestException.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -/** - * This exception may be thrown from a request handler to fail a request with a given response code and message. - * It is given some special treatment in {@link ServletResponseController}. - * - * @author bakksjo - */ -class RequestException extends RuntimeException { - - private final int responseStatus; - - /** - * @param responseStatus the response code to use for the http response - * @param message exception message - * @param cause chained throwable - */ - public RequestException(final int responseStatus, final String message, final Throwable cause) { - super(message, cause); - this.responseStatus = responseStatus; - } - - /** - * @param responseStatus the response code to use for the http response - * @param message exception message - */ - public RequestException(final int responseStatus, final String message) { - super(message); - this.responseStatus = responseStatus; - } - - /** - * Returns the response code to use for the http response. - */ - public int getResponseStatus() { - return responseStatus; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestMetricReporter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestMetricReporter.java deleted file mode 100644 index 7596be0415a..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestMetricReporter.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.Metric; -import com.yahoo.jdisc.Metric.Context; - -import java.util.concurrent.atomic.AtomicBoolean; - - -/** - * Responsible for metric reporting for JDisc http request handler support. - * @author Tony Vaagenes - */ -class RequestMetricReporter { - private final Metric metric; - private final Context context; - - private final long requestStartTime; - - //TODO: rename - private final AtomicBoolean firstSetOfTimeToFirstByte = new AtomicBoolean(true); - - - RequestMetricReporter(Metric metric, Context context, long requestStartTime) { - this.metric = metric; - this.context = context; - this.requestStartTime = requestStartTime; - } - - void successfulWrite(int numBytes) { - setTimeToFirstByteFirstTime(); - - metric.add(MetricDefinitions.NUM_SUCCESSFUL_WRITES, 1, context); - metric.set(MetricDefinitions.NUM_BYTES_SENT, numBytes, context); - } - - private void setTimeToFirstByteFirstTime() { - boolean isFirstWrite = firstSetOfTimeToFirstByte.getAndSet(false); - if (isFirstWrite) { - long timeToFirstByte = getRequestLatency(); - metric.set(MetricDefinitions.TIME_TO_FIRST_BYTE, timeToFirstByte, context); - } - } - - void failedWrite() { - metric.add(MetricDefinitions.NUM_FAILED_WRITES, 1, context); - } - - void successfulResponse() { - setTimeToFirstByteFirstTime(); - - long requestLatency = getRequestLatency(); - - metric.set(MetricDefinitions.TOTAL_SUCCESSFUL_LATENCY, requestLatency, context); - - metric.add(MetricDefinitions.NUM_SUCCESSFUL_RESPONSES, 1, context); - } - - void failedResponse() { - setTimeToFirstByteFirstTime(); - - metric.set(MetricDefinitions.TOTAL_FAILED_LATENCY, getRequestLatency(), context); - metric.add(MetricDefinitions.NUM_FAILED_RESPONSES, 1, context); - } - - void prematurelyClosed() { - metric.add(MetricDefinitions.NUM_PREMATURELY_CLOSED_CONNECTIONS, 1, context); - } - - void successfulRead(int bytes_received) { - metric.set(MetricDefinitions.NUM_BYTES_RECEIVED, bytes_received, context); - } - - private long getRequestLatency() { - return System.currentTimeMillis() - requestStartTime; - } - - void uriLength(int length) { - metric.set(MetricDefinitions.URI_LENGTH, length, context); - } - - void contentSize(int size) { - metric.set(MetricDefinitions.CONTENT_SIZE, size, context); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java deleted file mode 100644 index e32c9d46deb..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright Verizon Media. 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.http.ConnectorConfig; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.URIUtil; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnectorLocalPort; - -/** - * A secure redirect handler inspired by {@link org.eclipse.jetty.server.handler.SecuredRedirectHandler}. - * - * @author bjorncs - */ -class SecuredRedirectHandler extends HandlerWrapper { - - private static final String HEALTH_CHECK_PATH = "/status.html"; - - private final Map<Integer, Integer> redirectMap; - - SecuredRedirectHandler(List<ConnectorConfig> connectorConfigs) { - this.redirectMap = createRedirectMap(connectorConfigs); - } - - @Override - public void handle(String target, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException { - int localPort = getConnectorLocalPort(servletRequest); - if (!redirectMap.containsKey(localPort)) { - _handler.handle(target, request, servletRequest, servletResponse); - return; - } - servletResponse.setContentLength(0); - if (!servletRequest.getRequestURI().equals(HEALTH_CHECK_PATH)) { - servletResponse.sendRedirect( - URIUtil.newURI("https", request.getServerName(), redirectMap.get(localPort), request.getRequestURI(), request.getQueryString())); - } - request.setHandled(true); - } - - private static Map<Integer, Integer> createRedirectMap(List<ConnectorConfig> connectorConfigs) { - var redirectMap = new HashMap<Integer, Integer>(); - for (ConnectorConfig connectorConfig : connectorConfigs) { - if (connectorConfig.secureRedirect().enabled()) { - redirectMap.put(connectorConfig.listenPort(), connectorConfig.secureRedirect().port()); - } - } - return redirectMap; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServerMetricReporter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServerMetricReporter.java deleted file mode 100644 index ba3694ffc2f..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServerMetricReporter.java +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright Verizon Media. 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.concurrent.DaemonThreadFactory; -import com.yahoo.jdisc.Metric; -import org.eclipse.jetty.io.ConnectionStatistics; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandlerContainer; -import org.eclipse.jetty.server.handler.StatisticsHandler; -import org.eclipse.jetty.util.thread.QueuedThreadPool; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * Reports server/connector specific metrics for Jdisc and Jetty - * - * @author bjorncs - */ -class ServerMetricReporter { - - private final ScheduledExecutorService executor = - Executors.newScheduledThreadPool(1, new DaemonThreadFactory("jdisc-jetty-metric-reporter-")); - private final Metric metric; - private final Server jetty; - - ServerMetricReporter(Metric metric, Server jetty) { - this.metric = metric; - this.jetty = jetty; - } - - void start() { - executor.scheduleAtFixedRate(new ReporterTask(), 0, 2, TimeUnit.SECONDS); - } - - void shutdown() { - try { - executor.shutdownNow(); - executor.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private class ReporterTask implements Runnable { - - private final Instant timeStarted = Instant.now(); - - @Override - public void run() { - HttpResponseStatisticsCollector statisticsCollector = ((AbstractHandlerContainer) jetty.getHandler()) - .getChildHandlerByClass(HttpResponseStatisticsCollector.class); - if (statisticsCollector != null) { - setServerMetrics(statisticsCollector); - } - - // reset statisticsHandler to preserve earlier behavior - StatisticsHandler statisticsHandler = ((AbstractHandlerContainer) jetty.getHandler()) - .getChildHandlerByClass(StatisticsHandler.class); - if (statisticsHandler != null) { - statisticsHandler.statsReset(); - } - - for (Connector connector : jetty.getConnectors()) { - setConnectorMetrics((JDiscServerConnector)connector); - } - - setJettyThreadpoolMetrics(); - } - - private void setServerMetrics(HttpResponseStatisticsCollector statisticsCollector) { - long timeSinceStarted = System.currentTimeMillis() - timeStarted.toEpochMilli(); - metric.set(MetricDefinitions.STARTED_MILLIS, timeSinceStarted, null); - - addResponseMetrics(statisticsCollector); - } - - private void addResponseMetrics(HttpResponseStatisticsCollector statisticsCollector) { - for (var metricEntry : statisticsCollector.takeStatistics()) { - Map<String, Object> dimensions = new HashMap<>(); - dimensions.put(MetricDefinitions.METHOD_DIMENSION, metricEntry.method); - dimensions.put(MetricDefinitions.SCHEME_DIMENSION, metricEntry.scheme); - dimensions.put(MetricDefinitions.REQUEST_TYPE_DIMENSION, metricEntry.requestType); - metric.add(metricEntry.name, metricEntry.value, metric.createContext(dimensions)); - } - } - - private void setJettyThreadpoolMetrics() { - QueuedThreadPool threadpool = (QueuedThreadPool) jetty.getThreadPool(); - metric.set(MetricDefinitions.JETTY_THREADPOOL_MAX_THREADS, threadpool.getMaxThreads(), null); - metric.set(MetricDefinitions.JETTY_THREADPOOL_MIN_THREADS, threadpool.getMinThreads(), null); - metric.set(MetricDefinitions.JETTY_THREADPOOL_RESERVED_THREADS, threadpool.getReservedThreads(), null); - metric.set(MetricDefinitions.JETTY_THREADPOOL_BUSY_THREADS, threadpool.getBusyThreads(), null); - metric.set(MetricDefinitions.JETTY_THREADPOOL_IDLE_THREADS, threadpool.getIdleThreads(), null); - metric.set(MetricDefinitions.JETTY_THREADPOOL_TOTAL_THREADS, threadpool.getThreads(), null); - metric.set(MetricDefinitions.JETTY_THREADPOOL_QUEUE_SIZE, threadpool.getQueueSize(), null); - } - - private void setConnectorMetrics(JDiscServerConnector connector) { - ConnectionStatistics statistics = connector.getStatistics(); - metric.set(MetricDefinitions.NUM_CONNECTIONS, statistics.getConnectionsTotal(), connector.getConnectorMetricContext()); - metric.set(MetricDefinitions.NUM_OPEN_CONNECTIONS, statistics.getConnections(), connector.getConnectorMetricContext()); - metric.set(MetricDefinitions.NUM_CONNECTIONS_OPEN_MAX, statistics.getConnectionsMax(), connector.getConnectorMetricContext()); - metric.set(MetricDefinitions.CONNECTION_DURATION_MAX, statistics.getConnectionDurationMax(), connector.getConnectorMetricContext()); - metric.set(MetricDefinitions.CONNECTION_DURATION_MEAN, statistics.getConnectionDurationMean(), connector.getConnectorMetricContext()); - metric.set(MetricDefinitions.CONNECTION_DURATION_STD_DEV, statistics.getConnectionDurationStdDev(), connector.getConnectorMetricContext()); - } - - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java deleted file mode 100644 index b4d03385c3b..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.handler.CompletionHandler; - -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.yahoo.jdisc.http.server.jetty.CompletionHandlerUtils.NOOP_COMPLETION_HANDLER; - -/** - * @author Tony Vaagenes - * @author bjorncs - */ -public class ServletOutputStreamWriter { - /** Rules: - * 1) Don't modify the output stream without isReady returning true (write/flush/close). - * Multiple modification calls without interleaving isReady calls are not allowed. - * 2) If isReady returned false, no other calls should be made until the write listener is invoked. - * 3) If the write listener sees isReady == false, it must not do any modifications before its next invocation. - */ - - - private enum State { - NOT_STARTED, - WAITING_FOR_WRITE_POSSIBLE_CALLBACK, - WAITING_FOR_BUFFER, - WRITING_BUFFERS, - FINISHED_OR_ERROR - } - - private static final Logger log = Logger.getLogger(ServletOutputStreamWriter.class.getName()); - - // If so, application code could fake a close by writing such a byte buffer. - // The problem can be solved by filtering out zero-length byte buffers from application code. - // Other ways to express this are also possible, e.g. with a 'closed' state checked when queue goes empty. - private static final ByteBuffer CLOSE_STREAM_BUFFER = ByteBuffer.allocate(0); - - private final Object monitor = new Object(); - - // GuardedBy("monitor") - private State state = State.NOT_STARTED; - - // GuardedBy("state") - private final ServletOutputStream outputStream; - private final Executor executor; - - // GuardedBy("monitor") - private final Deque<ResponseContentPart> responseContentQueue = new ArrayDeque<>(); - - private final RequestMetricReporter metricReporter; - - /** - * When this future completes there will be no more calls against the servlet output stream or servlet response. - * The framework is still allowed to invoke us though. - * - * The future might complete in the servlet framework thread, user thread or executor thread. - */ - final CompletableFuture<Void> finishedFuture = new CompletableFuture<>(); - - - public ServletOutputStreamWriter(ServletOutputStream outputStream, Executor executor, RequestMetricReporter metricReporter) { - this.outputStream = outputStream; - this.executor = executor; - this.metricReporter = metricReporter; - } - - public void sendErrorContentAndCloseAsync(ByteBuffer errorContent) { - synchronized (monitor) { - // Assert that no content has been written as it is too late to write error response if the response is committed. - assertStateIs(state, State.NOT_STARTED); - queueErrorContent_holdingLock(errorContent); - state = State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK; - outputStream.setWriteListener(writeListener); - } - } - - private void queueErrorContent_holdingLock(ByteBuffer errorContent) { - responseContentQueue.addLast(new ResponseContentPart(errorContent, NOOP_COMPLETION_HANDLER)); - responseContentQueue.addLast(new ResponseContentPart(CLOSE_STREAM_BUFFER, NOOP_COMPLETION_HANDLER)); - } - - public void writeBuffer(ByteBuffer buf, CompletionHandler handler) { - boolean thisThreadShouldWrite = false; - - synchronized (monitor) { - if (state == State.FINISHED_OR_ERROR) { - executor.execute(() -> handler.failed(new IllegalStateException("ContentChannel already closed."))); - return; - } - responseContentQueue.addLast(new ResponseContentPart(buf, handler)); - switch (state) { - case NOT_STARTED: - state = State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK; - outputStream.setWriteListener(writeListener); - break; - case WAITING_FOR_WRITE_POSSIBLE_CALLBACK: - case WRITING_BUFFERS: - break; - case WAITING_FOR_BUFFER: - thisThreadShouldWrite = true; - state = State.WRITING_BUFFERS; - break; - default: - throw new IllegalStateException("Invalid state " + state); - } - } - - if (thisThreadShouldWrite) { - writeBuffersInQueueToOutputStream(); - } - } - - public void close(CompletionHandler handler) { - writeBuffer(CLOSE_STREAM_BUFFER, handler); - } - - public void close() { - close(NOOP_COMPLETION_HANDLER); - } - - private void writeBuffersInQueueToOutputStream() { - boolean lastOperationWasFlush = false; - - while (true) { - ResponseContentPart contentPart; - - synchronized (monitor) { - if (state == State.FINISHED_OR_ERROR) { - return; - } - assertStateIs(state, State.WRITING_BUFFERS); - - if (!outputStream.isReady()) { - state = State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK; - return; - } - - contentPart = responseContentQueue.pollFirst(); - - if (contentPart == null && lastOperationWasFlush) { - state = State.WAITING_FOR_BUFFER; - return; - } - } - - try { - boolean isFlush = contentPart == null; - if (isFlush) { - outputStream.flush(); - lastOperationWasFlush = true; - continue; - } - lastOperationWasFlush = false; - - if (contentPart.buf == CLOSE_STREAM_BUFFER) { - callCompletionHandlerWhenDone(contentPart.handler, outputStream::close); - setFinished(Optional.empty()); - return; - } else { - writeBufferToOutputStream(contentPart); - } - } catch (Throwable e) { - setFinished(Optional.of(e)); - return; - } - } - } - - private void setFinished(Optional<Throwable> e) { - synchronized (monitor) { - state = State.FINISHED_OR_ERROR; - if (!responseContentQueue.isEmpty()) { - failAllParts_holdingLock(e.orElse(new IllegalStateException("ContentChannel closed."))); - } - } - - assert !Thread.holdsLock(monitor); - if (e.isPresent()) { - finishedFuture.completeExceptionally(e.get()); - } else { - finishedFuture.complete(null); - } - } - - private void failAllParts_holdingLock(Throwable e) { - assert Thread.holdsLock(monitor); - - ArrayList<ResponseContentPart> failedParts = new ArrayList<>(responseContentQueue); - responseContentQueue.clear(); - - @SuppressWarnings("ThrowableInstanceNeverThrown") - RuntimeException failReason = new RuntimeException("Failing due to earlier ServletOutputStream write failure", e); - - Consumer<ResponseContentPart> failCompletionHandler = responseContentPart -> - runCompletionHandler_logOnExceptions( - () -> responseContentPart.handler.failed(failReason)); - - executor.execute( - () -> failedParts.forEach(failCompletionHandler)); - } - - private void writeBufferToOutputStream(ResponseContentPart contentPart) throws Throwable { - callCompletionHandlerWhenDone(contentPart.handler, () -> { - ByteBuffer buffer = contentPart.buf; - final int bytesToSend = buffer.remaining(); - try { - if (buffer.hasArray()) { - outputStream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining()); - } else { - final byte[] array = new byte[buffer.remaining()]; - buffer.get(array); - outputStream.write(array); - } - metricReporter.successfulWrite(bytesToSend); - } catch (Throwable throwable) { - metricReporter.failedWrite(); - throw throwable; - } - }); - } - - private static void callCompletionHandlerWhenDone(CompletionHandler handler, IORunnable runnable) throws Exception { - try { - runnable.run(); - } catch (Throwable e) { - runCompletionHandler_logOnExceptions(() -> handler.failed(e)); - throw e; - } - handler.completed(); //Might throw an exception, handling in the enclosing scope. - } - - private static void runCompletionHandler_logOnExceptions(Runnable runnable) { - try { - runnable.run(); - } catch (Throwable e) { - log.log(Level.WARNING, "Unexpected exception from CompletionHandler.", e); - } - } - - private static void assertStateIs(State currentState, State expectedState) { - if (currentState != expectedState) { - AssertionError error = new AssertionError("Expected state " + expectedState + ", got state " + currentState); - log.log(Level.WARNING, "Assertion failed.", error); - throw error; - } - } - - public void fail(Throwable t) { - setFinished(Optional.of(t)); - } - - private final WriteListener writeListener = new WriteListener() { - @Override - public void onWritePossible() throws IOException { - synchronized (monitor) { - if (state == State.FINISHED_OR_ERROR) { - return; - } - - assertStateIs(state, State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK); - state = State.WRITING_BUFFERS; - } - - writeBuffersInQueueToOutputStream(); - } - - @Override - public void onError(Throwable t) { - setFinished(Optional.of(t)); - } - }; - - private static class ResponseContentPart { - public final ByteBuffer buf; - public final CompletionHandler handler; - - public ResponseContentPart(ByteBuffer buf, CompletionHandler handler) { - this.buf = buf; - this.handler = handler; - } - } - - @FunctionalInterface - private interface IORunnable { - void run() throws IOException; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java deleted file mode 100644 index 1882448757a..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.google.common.base.Preconditions; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; - -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Finished when either - * 1) There was an error - * 2) There is no more data AND the number of pending completion handler invocations is 0 - * - * Stops reading when a failure has happened. - * - * The reason for not waiting for pending completions in error situations - * is that if the error is reported through the finishedFuture, - * error reporting might be async. - * Since we have tests that first reports errors and then closes the response content, - * it's important that errors are delivered synchronously. - */ -class ServletRequestReader implements ReadListener { - - private enum State { - READING, ALL_DATA_READ, REQUEST_CONTENT_CLOSED - } - - private static final Logger log = Logger.getLogger(ServletRequestReader.class.getName()); - - private static final int BUFFER_SIZE_BYTES = 8 * 1024; - - private final Object monitor = new Object(); - - private final ServletInputStream servletInputStream; - private final ContentChannel requestContentChannel; - - private final Executor executor; - private final RequestMetricReporter metricReporter; - - private int bytesRead; - - /** - * Rules: - * 1. If state != State.READING, then numberOfOutstandingUserCalls must not increase - * 2. The _first time_ (finishedFuture is completed OR all data is read) AND numberOfOutstandingUserCalls == 0, - * the request content channel should be closed - * 3. finishedFuture must not be completed when holding the monitor - * 4. completing finishedFuture with an exception must be done synchronously - * to prioritize failures being transported to the response. - * 5. All completion handlers (both for write and complete) must not be - * called from a user (request handler) owned thread - * (i.e. when being called from user code, don't call back into user code.) - */ - // GuardedBy("monitor") - private State state = State.READING; - - /** - * Number of calls that we're waiting for from user code. - * There are two classes of such calls: - * 1) calls to requestContentChannel.write that we're waiting for to complete - * 2) completion handlers given to requestContentChannel.write that the user must call. - * - * As long as we're waiting for such calls, we're not allowed to: - * - close the request content channel (currently only required by tests) - * - complete the finished future non-exceptionally, - * since then we would not be able to report writeCompletionHandler.failed(exception) calls - */ - // GuardedBy("monitor") - private int numberOfOutstandingUserCalls = 0; - - /** - * When this future completes there will be no more calls against the servlet input stream. - * The framework is still allowed to invoke us though. - * - * The future might complete in the servlet framework thread, user thread or executor thread. - * - * All completions of finishedFuture, except those done when closing the request content channel, - * must be followed by calls to either onAllDataRead or decreasePendingAndCloseRequestContentChannelConditionally. - * Those two functions will ensure that the request content channel is closed at the right time. - * If calls to those methods does not close the request content channel immediately, - * there is some outstanding completion callback that will later come in and complete the request. - */ - final CompletableFuture<Void> finishedFuture = new CompletableFuture<>(); - - public ServletRequestReader( - ServletInputStream servletInputStream, - ContentChannel requestContentChannel, - Executor executor, - RequestMetricReporter metricReporter) { - - Preconditions.checkNotNull(servletInputStream); - Preconditions.checkNotNull(requestContentChannel); - Preconditions.checkNotNull(executor); - Preconditions.checkNotNull(metricReporter); - - this.servletInputStream = servletInputStream; - this.requestContentChannel = requestContentChannel; - this.executor = executor; - this.metricReporter = metricReporter; - } - - @Override - public void onDataAvailable() throws IOException { - while (servletInputStream.isReady()) { - final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; - int numBytesRead; - - synchronized (monitor) { - numBytesRead = servletInputStream.read(buffer); - if (numBytesRead < 0) { - // End of stream; there should be no more data available, ever. - return; - } - if (state != State.READING) { - //We have a failure, so no point in giving the buffer to the user. - assert finishedFuture.isCompletedExceptionally(); - return; - } - //wait for both - // - requestContentChannel.write to finish - // - the write completion handler to be called - numberOfOutstandingUserCalls += 2; - bytesRead += numBytesRead; - } - - try { - requestContentChannel.write(ByteBuffer.wrap(buffer, 0, numBytesRead), writeCompletionHandler); - metricReporter.successfulRead(numBytesRead); - } - catch (Throwable t) { - finishedFuture.completeExceptionally(t); - } - finally { - //decrease due to this method completing. - decreaseOutstandingUserCallsAndCloseRequestContentChannelConditionally(); - } - } - } - - private void decreaseOutstandingUserCallsAndCloseRequestContentChannelConditionally() { - boolean shouldCloseRequestContentChannel; - - synchronized (monitor) { - assertStateNotEquals(state, State.REQUEST_CONTENT_CLOSED); - - - numberOfOutstandingUserCalls -= 1; - - shouldCloseRequestContentChannel = numberOfOutstandingUserCalls == 0 && - (finishedFuture.isDone() || state == State.ALL_DATA_READ); - - if (shouldCloseRequestContentChannel) { - state = State.REQUEST_CONTENT_CLOSED; - } - } - - if (shouldCloseRequestContentChannel) { - executor.execute(this::closeCompletionHandler_noThrow); - } - } - - private void assertStateNotEquals(State state, State notExpectedState) { - if (state == notExpectedState) { - AssertionError e = new AssertionError("State should not be " + notExpectedState); - log.log(Level.WARNING, - "Assertion failed. " + - "numberOfOutstandingUserCalls = " + numberOfOutstandingUserCalls + - ", isDone = " + finishedFuture.isDone(), - e); - throw e; - } - } - - @Override - public void onAllDataRead() { - doneReading(); - } - - private void doneReading() { - final boolean shouldCloseRequestContentChannel; - - int bytesRead; - synchronized (monitor) { - if (state != State.READING) { - return; - } - - state = State.ALL_DATA_READ; - - shouldCloseRequestContentChannel = numberOfOutstandingUserCalls == 0; - if (shouldCloseRequestContentChannel) { - state = State.REQUEST_CONTENT_CLOSED; - } - bytesRead = this.bytesRead; - } - - if (shouldCloseRequestContentChannel) { - closeCompletionHandler_noThrow(); - } - - metricReporter.contentSize(bytesRead); - } - - private void closeCompletionHandler_noThrow() { - //Cannot complete finishedFuture directly in completed(), as any exceptions after this fact will be ignored. - // E.g. - // close(CompletionHandler completionHandler) { - // completionHandler.completed(); - // throw new RuntimeException - // } - - CompletableFuture<Void> completedCalledFuture = new CompletableFuture<>(); - - CompletionHandler closeCompletionHandler = new CompletionHandler() { - @Override - public void completed() { - completedCalledFuture.complete(null); - } - - @Override - public void failed(final Throwable t) { - finishedFuture.completeExceptionally(t); - } - }; - - try { - requestContentChannel.close(closeCompletionHandler); - //if close did not cause an exception, - // is it safe to pipe the result of the completionHandlerInvokedFuture into finishedFuture - completedCalledFuture.whenComplete(this::setFinishedFuture); - } catch (final Throwable t) { - finishedFuture.completeExceptionally(t); - } - } - - private void setFinishedFuture(Void result, Throwable throwable) { - if (throwable != null) { - finishedFuture.completeExceptionally(throwable); - } else { - finishedFuture.complete(null); - } - } - - @Override - public void onError(final Throwable t) { - finishedFuture.completeExceptionally(t); - doneReading(); - } - - private final CompletionHandler writeCompletionHandler = new CompletionHandler() { - @Override - public void completed() { - decreaseOutstandingUserCallsAndCloseRequestContentChannelConditionally(); - } - - @Override - public void failed(final Throwable t) { - finishedFuture.completeExceptionally(t); - decreaseOutstandingUserCallsAndCloseRequestContentChannelConditionally(); - } - }; -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java deleted file mode 100644 index 60b7878156f..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.BindingNotFoundException; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.HttpResponse; -import com.yahoo.jdisc.service.BindingSetNotFoundException; -import org.eclipse.jetty.http.MimeTypes; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.ByteBuffer; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.yahoo.jdisc.http.server.jetty.CompletionHandlerUtils.NOOP_COMPLETION_HANDLER; - -/** - * @author Tony Vaagenes - * @author bjorncs - */ -public class ServletResponseController { - - private static Logger log = Logger.getLogger(ServletResponseController.class.getName()); - - /** - * The servlet spec does not require (Http)ServletResponse nor ServletOutputStream to be thread-safe. Therefore, - * we must provide our own synchronization, since we may attempt to access these objects simultaneously from - * different threads. (The typical cause of this is when one thread is writing a response while another thread - * throws an exception, causing the request to fail with an error response). - */ - private final Object monitor = new Object(); - - //servletResponse must not be modified after the response has been committed. - private final HttpServletRequest servletRequest; - private final HttpServletResponse servletResponse; - private final boolean developerMode; - private final ErrorResponseContentCreator errorResponseContentCreator = new ErrorResponseContentCreator(); - - //all calls to the servletOutputStreamWriter must hold the monitor first to ensure visibility of servletResponse changes. - private final ServletOutputStreamWriter servletOutputStreamWriter; - - // GuardedBy("monitor") - private boolean responseCommitted = false; - - public ServletResponseController( - HttpServletRequest servletRequest, - HttpServletResponse servletResponse, - Executor executor, - RequestMetricReporter metricReporter, - boolean developerMode) throws IOException { - - this.servletRequest = servletRequest; - this.servletResponse = servletResponse; - this.developerMode = developerMode; - this.servletOutputStreamWriter = - new ServletOutputStreamWriter(servletResponse.getOutputStream(), executor, metricReporter); - } - - - private static int getStatusCode(Throwable t) { - if (t instanceof BindingNotFoundException) { - return HttpServletResponse.SC_NOT_FOUND; - } else if (t instanceof BindingSetNotFoundException) { - return HttpServletResponse.SC_NOT_FOUND; - } else if (t instanceof RequestException) { - return ((RequestException)t).getResponseStatus(); - } else { - return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - } - } - - private static String getReasonPhrase(Throwable t, boolean developerMode) { - if (developerMode) { - final StringWriter out = new StringWriter(); - t.printStackTrace(new PrintWriter(out)); - return out.toString(); - } else if (t.getMessage() != null) { - return t.getMessage(); - } else { - return t.toString(); - } - } - - - public void trySendError(Throwable t) { - final boolean responseWasCommitted; - try { - synchronized (monitor) { - String reasonPhrase = getReasonPhrase(t, developerMode); - int statusCode = getStatusCode(t); - responseWasCommitted = responseCommitted; - if (!responseCommitted) { - responseCommitted = true; - sendErrorAsync(statusCode, reasonPhrase); - } - } - } catch (Throwable e) { - servletOutputStreamWriter.fail(t); - return; - } - - //Must be evaluated after state transition for test purposes(See ConformanceTestException) - //Done outside the monitor since it causes a callback in tests. - if (responseWasCommitted) { - RuntimeException exceptionWithStackTrace = new RuntimeException(t); - log.log(Level.FINE, "Response already committed, can't change response code", exceptionWithStackTrace); - // TODO: should always have failed here, but that breaks test assumptions. Doing soft close instead. - //assert !Thread.holdsLock(monitor); - //servletOutputStreamWriter.fail(t); - servletOutputStreamWriter.close(); - } - - } - - /** - * Async version of {@link org.eclipse.jetty.server.Response#sendError(int, String)}. - */ - private void sendErrorAsync(int statusCode, String reasonPhrase) { - servletResponse.setHeader(HttpHeaders.Names.EXPIRES, null); - servletResponse.setHeader(HttpHeaders.Names.LAST_MODIFIED, null); - servletResponse.setHeader(HttpHeaders.Names.CACHE_CONTROL, null); - servletResponse.setHeader(HttpHeaders.Names.CONTENT_TYPE, null); - servletResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, null); - setStatus(servletResponse, statusCode, Optional.of(reasonPhrase)); - - // If we are allowed to have a body - if (statusCode != HttpServletResponse.SC_NO_CONTENT && - statusCode != HttpServletResponse.SC_NOT_MODIFIED && - statusCode != HttpServletResponse.SC_PARTIAL_CONTENT && - statusCode >= HttpServletResponse.SC_OK) { - servletResponse.setHeader(HttpHeaders.Names.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); - servletResponse.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString()); - byte[] errorContent = errorResponseContentCreator - .createErrorContent(servletRequest.getRequestURI(), statusCode, Optional.ofNullable(reasonPhrase)); - servletResponse.setContentLength(errorContent.length); - servletOutputStreamWriter.sendErrorContentAndCloseAsync(ByteBuffer.wrap(errorContent)); - } else { - servletResponse.setContentLength(0); - servletOutputStreamWriter.close(); - } - } - - /** - * When this future completes there will be no more calls against the servlet output stream or servlet response. - * The framework is still allowed to invoke us though. - * - * The future might complete in the servlet framework thread, user thread or executor thread. - */ - public CompletableFuture<Void> finishedFuture() { - return servletOutputStreamWriter.finishedFuture; - } - - private void setResponse(Response jdiscResponse) { - synchronized (monitor) { - servletRequest.setAttribute(HttpResponseStatisticsCollector.requestTypeAttribute, jdiscResponse.getRequestType()); - if (responseCommitted) { - log.log(Level.FINE, - jdiscResponse.getError(), - () -> "Response already committed, can't change response code. " + - "From: " + servletResponse.getStatus() + ", To: " + jdiscResponse.getStatus()); - - //TODO: should throw an exception here, but this breaks unit tests. - //The failures will now instead happen when writing buffers. - servletOutputStreamWriter.close(); - return; - } - - setStatus_holdingLock(jdiscResponse, servletResponse); - setHeaders_holdingLock(jdiscResponse, servletResponse); - } - } - - private static void setHeaders_holdingLock(Response jdiscResponse, HttpServletResponse servletResponse) { - for (final Map.Entry<String, String> entry : jdiscResponse.headers().entries()) { - servletResponse.addHeader(entry.getKey(), entry.getValue()); - } - - if (servletResponse.getContentType() == null) { - servletResponse.setContentType("text/plain;charset=utf-8"); - } - } - - private static void setStatus_holdingLock(Response jdiscResponse, HttpServletResponse servletResponse) { - if (jdiscResponse instanceof HttpResponse) { - setStatus(servletResponse, jdiscResponse.getStatus(), Optional.ofNullable(((HttpResponse) jdiscResponse).getMessage())); - } else { - setStatus(servletResponse, jdiscResponse.getStatus(), getErrorMessage(jdiscResponse)); - } - } - - @SuppressWarnings("deprecation") - private static void setStatus(HttpServletResponse response, int statusCode, Optional<String> reasonPhrase) { - if (reasonPhrase.isPresent()) { - // Sets the status line: a status code along with a custom message. - // Using a custom status message is deprecated in the Servlet API. No alternative exist. - response.setStatus(statusCode, reasonPhrase.get()); // DEPRECATED - } else { - response.setStatus(statusCode); - } - } - - private static Optional<String> getErrorMessage(Response jdiscResponse) { - return Optional.ofNullable(jdiscResponse.getError()).flatMap( - error -> Optional.ofNullable(error.getMessage())); - } - - - private void commitResponse() { - synchronized (monitor) { - responseCommitted = true; - } - } - - public final ResponseHandler responseHandler = new ResponseHandler() { - @Override - public ContentChannel handleResponse(Response response) { - setResponse(response); - return responseContentChannel; - } - }; - - public final ContentChannel responseContentChannel = new ContentChannel() { - @Override - public void write(ByteBuffer buf, CompletionHandler handler) { - commitResponse(); - servletOutputStreamWriter.writeBuffer(buf, handlerOrNoopHandler(handler)); - } - - @Override - public void close(CompletionHandler handler) { - commitResponse(); - servletOutputStreamWriter.close(handlerOrNoopHandler(handler)); - } - - private CompletionHandler handlerOrNoopHandler(CompletionHandler handler) { - return handler != null ? handler : NOOP_COMPLETION_HANDLER; - } - }; -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListener.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListener.java deleted file mode 100644 index 822e1c2ffb8..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListener.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Oath Inc. 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.Metric; -import org.eclipse.jetty.io.ssl.SslHandshakeListener; - -import javax.net.ssl.SSLHandshakeException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -/** - * A {@link SslHandshakeListener} that reports metrics for SSL handshake failures. - * - * @author bjorncs - */ -class SslHandshakeFailedListener implements SslHandshakeListener { - - private final static Logger log = Logger.getLogger(SslHandshakeFailedListener.class.getName()); - - private final Metric metric; - private final String connectorName; - private final int listenPort; - - SslHandshakeFailedListener(Metric metric, String connectorName, int listenPort) { - this.metric = metric; - this.connectorName = connectorName; - this.listenPort = listenPort; - } - - @Override - public void handshakeFailed(Event event, Throwable throwable) { - log.log(Level.FINE, throwable, () -> "Ssl handshake failed: " + throwable.getMessage()); - String metricName = SslHandshakeFailure.fromSslHandshakeException((SSLHandshakeException) throwable) - .map(SslHandshakeFailure::metricName) - .orElse(MetricDefinitions.SSL_HANDSHAKE_FAILURE_UNKNOWN); - metric.add(metricName, 1L, metric.createContext(createDimensions(event))); - } - - private Map<String, Object> createDimensions(Event event) { - Map<String, Object> dimensions = new HashMap<>(); - dimensions.put(MetricDefinitions.NAME_DIMENSION, connectorName); - dimensions.put(MetricDefinitions.PORT_DIMENSION, listenPort); - Optional.ofNullable(event.getSSLEngine().getPeerHost()) - .ifPresent(clientIp -> dimensions.put(MetricDefinitions.CLIENT_IP_DIMENSION, clientIp)); - return Map.copyOf(dimensions); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailure.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailure.java deleted file mode 100644 index 64f70564137..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailure.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.server.jetty; - -import javax.net.ssl.SSLHandshakeException; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -/** - * Categorizes instances of {@link SSLHandshakeException} - * - * @author bjorncs - */ -enum SslHandshakeFailure { - INCOMPATIBLE_PROTOCOLS( - MetricDefinitions.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_PROTOCOLS, - "INCOMPATIBLE_CLIENT_PROTOCOLS", - "(Client requested protocol \\S+? is not enabled or supported in server context" + - "|The client supported protocol versions \\[.+?\\] are not accepted by server preferences \\[.+?\\])"), - INCOMPATIBLE_CIPHERS( - MetricDefinitions.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_CIPHERS, - "INCOMPATIBLE_CLIENT_CIPHER_SUITES", - "no cipher suites in common"), - MISSING_CLIENT_CERT( - MetricDefinitions.SSL_HANDSHAKE_FAILURE_MISSING_CLIENT_CERT, - "MISSING_CLIENT_CERTIFICATE", - "Empty (server|client) certificate chain"), - EXPIRED_CLIENT_CERTIFICATE( - MetricDefinitions.SSL_HANDSHAKE_FAILURE_EXPIRED_CLIENT_CERT, - "EXPIRED_CLIENT_CERTIFICATE", - // Note: this pattern will match certificates with too late notBefore as well - "PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed"), - INVALID_CLIENT_CERT( - MetricDefinitions.SSL_HANDSHAKE_FAILURE_INVALID_CLIENT_CERT, // Includes mismatch of client certificate and private key - "INVALID_CLIENT_CERTIFICATE", - "(PKIX path (building|validation) failed: .+)|(Invalid CertificateVerify signature)"); - - private final String metricName; - private final String failureType; - private final Predicate<String> messageMatcher; - - SslHandshakeFailure(String metricName, String failureType, String messagePattern) { - this.metricName = metricName; - this.failureType = failureType; - this.messageMatcher = Pattern.compile(messagePattern).asMatchPredicate(); - } - - String metricName() { return metricName; } - String failureType() { return failureType; } - - static Optional<SslHandshakeFailure> fromSslHandshakeException(SSLHandshakeException exception) { - String message = exception.getMessage(); - if (message == null || message.isBlank()) return Optional.empty(); - for (SslHandshakeFailure failure : values()) { - if (failure.messageMatcher.test(message)) { - return Optional.of(failure); - } - } - return Optional.empty(); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java deleted file mode 100644 index 10a6c4702b5..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019 Oath Inc. 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.http.ConnectorConfig; -import com.yahoo.jdisc.http.servlet.ServletRequest; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.HandlerWrapper; - -import javax.servlet.DispatcherType; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnectorLocalPort; - -/** - * A Jetty handler that enforces TLS client authentication with configurable white list. - * - * @author bjorncs - */ -class TlsClientAuthenticationEnforcer extends HandlerWrapper { - - private final Map<Integer, List<String>> portToWhitelistedPathsMapping; - - TlsClientAuthenticationEnforcer(List<ConnectorConfig> connectorConfigs) { - portToWhitelistedPathsMapping = createWhitelistMapping(connectorConfigs); - } - - @Override - public void handle(String target, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException { - if (isHttpsRequest(request) - && !isRequestToWhitelistedBinding(servletRequest) - && !isClientAuthenticated(servletRequest)) { - servletResponse.sendError( - Response.Status.UNAUTHORIZED, - "Client did not present a x509 certificate, " + - "or presented a certificate not issued by any of the CA certificates in trust store."); - } else { - _handler.handle(target, request, servletRequest, servletResponse); - } - } - - private static Map<Integer, List<String>> createWhitelistMapping(List<ConnectorConfig> connectorConfigs) { - var mapping = new HashMap<Integer, List<String>>(); - for (ConnectorConfig connectorConfig : connectorConfigs) { - var enforcerConfig = connectorConfig.tlsClientAuthEnforcer(); - if (enforcerConfig.enable()) { - mapping.put(connectorConfig.listenPort(), enforcerConfig.pathWhitelist()); - } - } - return mapping; - } - - private boolean isHttpsRequest(Request request) { - return request.getDispatcherType() == DispatcherType.REQUEST && request.getScheme().equalsIgnoreCase("https"); - } - - private boolean isRequestToWhitelistedBinding(HttpServletRequest servletRequest) { - int localPort = getConnectorLocalPort(servletRequest); - List<String> whiteListedPaths = getWhitelistedPathsForPort(localPort); - if (whiteListedPaths == null) { - return true; // enforcer not enabled - } - // Note: Same path definition as HttpRequestFactory.getUri() - return whiteListedPaths.contains(servletRequest.getRequestURI()); - } - - private List<String> getWhitelistedPathsForPort(int localPort) { - if (portToWhitelistedPathsMapping.containsKey(0) && portToWhitelistedPathsMapping.size() == 1) { - return portToWhitelistedPathsMapping.get(0); // for unit tests which uses 0 for listen port - } - return portToWhitelistedPathsMapping.get(localPort); - } - - private boolean isClientAuthenticated(HttpServletRequest servletRequest) { - return servletRequest.getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT) != null; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/UnsupportedFilterInvoker.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/UnsupportedFilterInvoker.java deleted file mode 100644 index ce52bccf52d..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/UnsupportedFilterInvoker.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.handler.ResponseHandler; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletRequest; -import java.net.URI; - -/** - * @author Tony Vaagenes - */ -public class UnsupportedFilterInvoker implements FilterInvoker { - @Override - public HttpServletRequest invokeRequestFilterChain(RequestFilter requestFilterChain, - URI uri, - HttpServletRequest httpRequest, - ResponseHandler responseHandler) { - throw new UnsupportedOperationException(); - } - - @Override - public void invokeResponseFilterChain( - ResponseFilter responseFilterChain, - URI uri, - HttpServletRequest request, - HttpServletResponse response) { - throw new UnsupportedOperationException(); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/VoidConnectionLog.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/VoidConnectionLog.java deleted file mode 100644 index 5d33cc0835e..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/VoidConnectionLog.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Verizon Media. 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.container.logging.ConnectionLog; -import com.yahoo.container.logging.ConnectionLogEntry; - -/** - * @author mortent - */ -public class VoidConnectionLog implements ConnectionLog { - - @Override - public void log(ConnectionLogEntry connectionLogEntry) { - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/VoidRequestLog.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/VoidRequestLog.java deleted file mode 100644 index 9db5ba99115..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/VoidRequestLog.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Verizon Media. 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.container.logging.RequestLog; -import com.yahoo.container.logging.RequestLogEntry; - -/** - * @author bjorncs - */ -public class VoidRequestLog implements RequestLog { - - @Override public void log(RequestLogEntry entry) {} - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/package-info.java deleted file mode 100644 index 189751aa9c0..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/package-info.java +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@com.yahoo.osgi.annotation.ExportPackage -package com.yahoo.jdisc.http.server.jetty; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletOrJdiscHttpRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletOrJdiscHttpRequest.java deleted file mode 100644 index eaac2b1c415..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletOrJdiscHttpRequest.java +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.servlet; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpRequest; - -import java.net.SocketAddress; -import java.net.URI; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * Common interface for JDisc and servlet http requests. - */ -public interface ServletOrJdiscHttpRequest { - - void copyHeaders(HeaderFields target); - - Map<String, List<String>> parameters(); - - URI getUri(); - - HttpRequest.Version getVersion(); - - String getRemoteHostAddress(); - String getRemoteHostName(); - int getRemotePort(); - - void setRemoteAddress(SocketAddress remoteAddress); - - Map<String, Object> context(); - - List<Cookie> decodeCookieHeader(); - - void encodeCookieHeader(List<Cookie> cookies); - - long getConnectedAt(TimeUnit unit); -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletOrJdiscHttpResponse.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletOrJdiscHttpResponse.java deleted file mode 100644 index a24ada05b3d..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletOrJdiscHttpResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.servlet; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; - -import java.util.List; -import java.util.Map; - -/** - * Common interface for JDisc and servlet http responses. - */ -public interface ServletOrJdiscHttpResponse { - - public void copyHeaders(HeaderFields target); - - public int getStatus(); - - public Map<String, Object> context(); - - public List<Cookie> decodeSetCookieHeader(); - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java deleted file mode 100644 index c945dc6d8b6..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.servlet; - -import com.google.common.collect.ImmutableMap; -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpHeaders; -import com.yahoo.jdisc.http.HttpRequest; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.URI; -import java.security.Principal; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnection; - -/** - * Mutable wrapper to use a {@link javax.servlet.http.HttpServletRequest} - * with JDisc security filters. - * <p> - * You might find it tempting to remove e.g. the getParameter... methods, - * but keep in mind that this IS-A servlet request and must provide the - * full api of such a request for use outside the "JDisc filter world". - */ -public class ServletRequest extends HttpServletRequestWrapper implements ServletOrJdiscHttpRequest { - - public static final String JDISC_REQUEST_PRINCIPAL = "jdisc.request.principal"; - public static final String JDISC_REQUEST_X509CERT = "jdisc.request.X509Certificate"; - public static final String JDISC_REQUEST_CHAIN = "jdisc.request.chain"; - public static final String JDISC_RESPONSE_CHAIN = "jdisc.response.chain"; - public static final String SERVLET_REQUEST_X509CERT = "javax.servlet.request.X509Certificate"; - public static final String SERVLET_REQUEST_SSL_SESSION_ID = "javax.servlet.request.ssl_session_id"; - public static final String SERVLET_REQUEST_CIPHER_SUITE = "javax.servlet.request.cipher_suite"; - - private final HttpServletRequest request; - private final HeaderFields headerFields; - private final Set<String> removedHeaders = new HashSet<>(); - private final Map<String, Object> context = new HashMap<>(); - private final Map<String, List<String>> parameters = new HashMap<>(); - private final long connectedAt; - - private URI uri; - private String remoteHostAddress; - private String remoteHostName; - private int remotePort; - - public ServletRequest(HttpServletRequest request, URI uri) { - super(request); - this.request = request; - - this.uri = uri; - - super.getParameterMap().forEach( - (key, values) -> parameters.put(key, Arrays.asList(values))); - - remoteHostAddress = request.getRemoteAddr(); - remoteHostName = request.getRemoteHost(); - remotePort = request.getRemotePort(); - connectedAt = getConnection(request).getCreatedTimeStamp(); - - headerFields = new HeaderFields(); - Enumeration<String> parentHeaders = request.getHeaderNames(); - while (parentHeaders.hasMoreElements()) { - String name = parentHeaders.nextElement(); - Enumeration<String> values = request.getHeaders(name); - while (values.hasMoreElements()) { - headerFields.add(name, values.nextElement()); - } - } - } - - public HttpServletRequest getRequest() { - return request; - } - - @Override - public Map<String, List<String>> parameters() { - return parameters; - } - - /* We cannot just return the parameter map from the request, as the map - * may have been modified by the JDisc filters. */ - @Override - public Map<String, String[]> getParameterMap() { - Map<String, String[]> parameterMap = new HashMap<>(); - parameters().forEach( - (key, values) -> - parameterMap.put(key, values.toArray(new String[values.size()])) - ); - return ImmutableMap.copyOf(parameterMap); - } - - @Override - public String getParameter(String name) { - return parameters().containsKey(name) ? - parameters().get(name).get(0) : - null; - } - - @Override - public Enumeration<String> getParameterNames() { - return Collections.enumeration(parameters.keySet()); - } - - @Override - public String[] getParameterValues(String name) { - List<String> values = parameters().get(name); - return values != null ? - values.toArray(new String[values.size()]) : - null; - } - - @Override - public void copyHeaders(HeaderFields target) { - target.addAll(headerFields); - } - - @Override - public Enumeration<String> getHeaders(String name) { - if (removedHeaders.contains(name)) - return null; - - /* We don't need to merge headerFields and the servlet request's headers - * because setHeaders() replaces the old value. There is no 'addHeader(s)'. */ - List<String> headerFields = this.headerFields.get(name); - return headerFields == null || headerFields.isEmpty() ? - super.getHeaders(name) : - Collections.enumeration(headerFields); - } - - @Override - public String getHeader(String name) { - if (removedHeaders.contains(name)) - return null; - - String headerField = headerFields.getFirst(name); - return headerField != null ? - headerField : - super.getHeader(name); - } - - @Override - public Enumeration<String> getHeaderNames() { - Set<String> names = new HashSet<>(Collections.list(super.getHeaderNames())); - names.addAll(headerFields.keySet()); - names.removeAll(removedHeaders); - return Collections.enumeration(names); - } - - public void addHeader(String name, String value) { - headerFields.add(name, value); - removedHeaders.remove(name); - } - - public void setHeaders(String name, String value) { - headerFields.put(name, value); - removedHeaders.remove(name); - } - - public void setHeaders(String name, List<String> values) { - headerFields.put(name, values); - removedHeaders.remove(name); - } - - public void removeHeaders(String name) { - headerFields.remove(name); - removedHeaders.add(name); - } - - @Override - public URI getUri() { - return uri; - } - - public void setUri(URI uri) { - this.uri = uri; - } - - @Override - public HttpRequest.Version getVersion() { - String protocol = request.getProtocol(); - try { - return HttpRequest.Version.fromString(protocol); - } catch (NullPointerException | IllegalArgumentException e) { - throw new RuntimeException("Servlet request protocol '" + protocol + - "' could not be mapped to a JDisc http version.", e); - } - } - - @Override - public String getRemoteHostAddress() { - return remoteHostAddress; - } - - @Override - public String getRemoteHostName() { - return remoteHostName; - } - - @Override - public int getRemotePort() { - return remotePort; - } - - @Override - public void setRemoteAddress(SocketAddress remoteAddress) { - if (remoteAddress instanceof InetSocketAddress) { - remoteHostAddress = ((InetSocketAddress) remoteAddress).getAddress().getHostAddress(); - remoteHostName = ((InetSocketAddress) remoteAddress).getAddress().getHostName(); - remotePort = ((InetSocketAddress) remoteAddress).getPort(); - } else - throw new RuntimeException("Unknown SocketAddress class: " + remoteHostAddress.getClass().getName()); - - } - - @Override - public Map<String, Object> context() { - return context; - } - - @Override - public javax.servlet.http.Cookie[] getCookies() { - return decodeCookieHeader().stream(). - map(jdiscCookie -> new javax.servlet.http.Cookie(jdiscCookie.getName(), jdiscCookie.getValue())). - toArray(javax.servlet.http.Cookie[]::new); - } - - @Override - public List<Cookie> decodeCookieHeader() { - Enumeration<String> cookies = getHeaders(HttpHeaders.Names.COOKIE); - if (cookies == null) - return Collections.emptyList(); - - List<Cookie> ret = new LinkedList<>(); - while(cookies.hasMoreElements()) - ret.addAll(Cookie.fromCookieHeader(cookies.nextElement())); - - return ret; - } - - @Override - public void encodeCookieHeader(List<Cookie> cookies) { - setHeaders(HttpHeaders.Names.COOKIE, Cookie.toCookieHeader(cookies)); - } - - @Override - public long getConnectedAt(TimeUnit unit) { - return unit.convert(connectedAt, TimeUnit.MILLISECONDS); - } - - @Override - public Principal getUserPrincipal() { - // NOTE: The principal from the underlying servlet request is ignored. JDisc filters are the source-of-truth. - return (Principal) request.getAttribute(JDISC_REQUEST_PRINCIPAL); - } - - public void setUserPrincipal(Principal principal) { - request.setAttribute(JDISC_REQUEST_PRINCIPAL, principal); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletResponse.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletResponse.java deleted file mode 100644 index 48c8f577de9..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletResponse.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.servlet; - -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpHeaders; - -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * JDisc wrapper to use a {@link javax.servlet.http.HttpServletResponse} - * with JDisc security filters. - */ -public class ServletResponse extends HttpServletResponseWrapper implements ServletOrJdiscHttpResponse { - - private final HttpServletResponse response; - private final Map<String, Object> context = new HashMap<>(); - - public ServletResponse(HttpServletResponse response) { - super(response); - this.response = response; - } - - public HttpServletResponse getResponse() { - return response; - } - - @Override - public int getStatus() { - return response.getStatus(); - } - - @Override - public Map<String, Object> context() { - return context; - } - - @Override - public void copyHeaders(HeaderFields target) { - response.getHeaderNames().forEach( header -> - target.add(header, new ArrayList<>(response.getHeaders(header))) - ); - } - - @Override - public List<Cookie> decodeSetCookieHeader() { - Collection<String> cookies = getHeaders(HttpHeaders.Names.SET_COOKIE); - if (cookies == null) { - return Collections.emptyList(); - } - List<Cookie> ret = new LinkedList<>(); - for (String cookie : cookies) { - ret.add(Cookie.fromSetCookieHeader(cookie)); - } - return ret; - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/package-info.java deleted file mode 100644 index 0120f164cae..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@ExportPackage -package com.yahoo.jdisc.http.servlet; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslContextFactoryProvider.java deleted file mode 100644 index c364116e0af..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslContextFactoryProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl; - -import org.eclipse.jetty.util.ssl.SslContextFactory; - -/** - * A provider that is used to configure SSL connectors in JDisc - * - * @author bjorncs - */ -public interface SslContextFactoryProvider extends AutoCloseable { - - /** - * This method is called once for each SSL connector. - * - * @return returns an instance of {@link SslContextFactory} for a given JDisc http server - */ - SslContextFactory getInstance(String containerId, int port); - - @Override default void close() {} -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java deleted file mode 100644 index 90848f1dfd4..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl.impl; - -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth; -import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; -import com.yahoo.security.KeyUtils; -import com.yahoo.security.SslContextBuilder; -import com.yahoo.security.X509CertificateUtils; -import com.yahoo.security.tls.AutoReloadingX509KeyManager; -import com.yahoo.security.tls.TlsContext; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import javax.net.ssl.SSLContext; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.yahoo.jdisc.http.ssl.impl.SslContextFactoryUtils.setEnabledCipherSuites; -import static com.yahoo.jdisc.http.ssl.impl.SslContextFactoryUtils.setEnabledProtocols; - -/** - * An implementation of {@link SslContextFactoryProvider} that uses the {@link ConnectorConfig} to construct a {@link SslContextFactory}. - * - * @author bjorncs - */ -public class ConfiguredSslContextFactoryProvider implements SslContextFactoryProvider { - - private volatile AutoReloadingX509KeyManager keyManager; - private final ConnectorConfig connectorConfig; - - public ConfiguredSslContextFactoryProvider(ConnectorConfig connectorConfig) { - validateConfig(connectorConfig.ssl()); - this.connectorConfig = connectorConfig; - } - - @Override - public SslContextFactory getInstance(String containerId, int port) { - ConnectorConfig.Ssl sslConfig = connectorConfig.ssl(); - if (!sslConfig.enabled()) throw new IllegalStateException(); - - SslContextBuilder builder = new SslContextBuilder(); - if (sslConfig.certificateFile().isBlank() || sslConfig.privateKeyFile().isBlank()) { - PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(getPrivateKey(sslConfig)); - List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(getCertificate(sslConfig)); - builder.withKeyStore(privateKey, certificates); - } else { - keyManager = AutoReloadingX509KeyManager.fromPemFiles(Paths.get(sslConfig.privateKeyFile()), Paths.get(sslConfig.certificateFile())); - builder.withKeyManager(keyManager); - } - List<X509Certificate> caCertificates = getCaCertificates(sslConfig) - .map(X509CertificateUtils::certificateListFromPem) - .orElse(List.of()); - builder.withTrustStore(caCertificates); - - SSLContext sslContext = builder.build(); - - SslContextFactory.Server factory = new SslContextFactory.Server(); - factory.setSslContext(sslContext); - - factory.setNeedClientAuth(sslConfig.clientAuth() == ClientAuth.Enum.NEED_AUTH); - factory.setWantClientAuth(sslConfig.clientAuth() == ClientAuth.Enum.WANT_AUTH); - - List<String> protocols = !sslConfig.enabledProtocols().isEmpty() - ? sslConfig.enabledProtocols() - : new ArrayList<>(TlsContext.getAllowedProtocols(sslContext)); - setEnabledProtocols(factory, sslContext, protocols); - - List<String> ciphers = !sslConfig.enabledCipherSuites().isEmpty() - ? sslConfig.enabledCipherSuites() - : new ArrayList<>(TlsContext.getAllowedCipherSuites(sslContext)); - setEnabledCipherSuites(factory, sslContext, ciphers); - - return factory; - } - - @Override - public void close() { - if (keyManager != null) { - keyManager.close(); - } - } - - private static void validateConfig(ConnectorConfig.Ssl config) { - if (!config.enabled()) return; - - if(hasBoth(config.certificate(), config.certificateFile())) - throw new IllegalArgumentException("Specified both certificate and certificate file."); - - if(hasBoth(config.privateKey(), config.privateKeyFile())) - throw new IllegalArgumentException("Specified both private key and private key file."); - - if(hasNeither(config.certificate(), config.certificateFile())) - throw new IllegalArgumentException("Specified neither certificate or certificate file."); - - if(hasNeither(config.privateKey(), config.privateKeyFile())) - throw new IllegalArgumentException("Specified neither private key or private key file."); - } - - private static boolean hasBoth(String a, String b) { return !a.isBlank() && !b.isBlank(); } - private static boolean hasNeither(String a, String b) { return a.isBlank() && b.isBlank(); } - - private static Optional<String> getCaCertificates(ConnectorConfig.Ssl sslConfig) { - if (!sslConfig.caCertificate().isBlank()) { - return Optional.of(sslConfig.caCertificate()); - } else if (!sslConfig.caCertificateFile().isBlank()) { - return Optional.of(readToString(sslConfig.caCertificateFile())); - } else { - return Optional.empty(); - } - } - - private static String getPrivateKey(ConnectorConfig.Ssl config) { - if(!config.privateKey().isBlank()) return config.privateKey(); - return readToString(config.privateKeyFile()); - } - - private static String getCertificate(ConnectorConfig.Ssl config) { - if(!config.certificate().isBlank()) return config.certificate(); - return readToString(config.certificateFile()); - } - - private static String readToString(String filename) { - try { - return Files.readString(Paths.get(filename), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java deleted file mode 100644 index 7395d2307af..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl.impl; - -import com.google.inject.Inject; -import com.yahoo.component.AbstractComponent; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; -import com.yahoo.security.tls.ConfigFileBasedTlsContext; -import com.yahoo.security.tls.PeerAuthentication; -import com.yahoo.security.tls.TlsContext; -import com.yahoo.security.tls.TransportSecurityUtils; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import java.nio.file.Path; - -/** - * The default implementation of {@link SslContextFactoryProvider} to be injected into connectors without explicit ssl configuration. - * - * @author bjorncs - */ -public class DefaultSslContextFactoryProvider extends AbstractComponent implements SslContextFactoryProvider { - - private final SslContextFactoryProvider instance; - - @Inject - public DefaultSslContextFactoryProvider(ConnectorConfig connectorConfig) { - this.instance = TransportSecurityUtils.getConfigFile() - .map(configFile -> createTlsContextBasedProvider(connectorConfig, configFile)) - .orElseGet(ThrowingSslContextFactoryProvider::new); - } - - private static SslContextFactoryProvider createTlsContextBasedProvider(ConnectorConfig connectorConfig, Path configFile) { - return new StaticTlsContextBasedProvider( - new ConfigFileBasedTlsContext( - configFile, TransportSecurityUtils.getInsecureAuthorizationMode(), getPeerAuthenticationMode(connectorConfig))); - } - - /** - * Allows white-listing of user provided uri paths. - * JDisc will delegate the enforcement of peer authentication from the TLS to the HTTP layer if {@link ConnectorConfig.TlsClientAuthEnforcer#enable()} is true. - */ - private static PeerAuthentication getPeerAuthenticationMode(ConnectorConfig connectorConfig) { - return connectorConfig.tlsClientAuthEnforcer().enable() - ? PeerAuthentication.WANT - : PeerAuthentication.NEED; - } - - @Override - public SslContextFactory getInstance(String containerId, int port) { - return instance.getInstance(containerId, port); - } - - @Override - public void deconstruct() { - instance.close(); - } - - private static class ThrowingSslContextFactoryProvider implements SslContextFactoryProvider { - @Override - public SslContextFactory getInstance(String containerId, int port) { - throw new UnsupportedOperationException(); - } - } - - private static class StaticTlsContextBasedProvider extends TlsContextBasedProvider { - final TlsContext tlsContext; - - StaticTlsContextBasedProvider(TlsContext tlsContext) { - this.tlsContext = tlsContext; - } - - @Override - protected TlsContext getTlsContext(String containerId, int port) { - return tlsContext; - } - - @Override public void deconstruct() { tlsContext.close(); } - } -}
\ No newline at end of file diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/JDiscSslContextFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/JDiscSslContextFactory.java deleted file mode 100644 index 006a282e1e0..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/JDiscSslContextFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl.impl; - -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.security.CertificateUtils; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import java.security.KeyStore; -import java.util.Objects; - -/** - * A modified {@link SslContextFactory} that allows passwordless truststore in combination with password protected keystore. - * - * @author bjorncs - */ -class JDiscSslContextFactory extends SslContextFactory.Server { - - private String trustStorePassword; - - @Override - public void setTrustStorePassword(String password) { - super.setTrustStorePassword(password); - this.trustStorePassword = password; - } - - - // Overriden to stop Jetty from using the keystore password if no truststore password is specified. - @Override - protected KeyStore loadTrustStore(Resource resource) throws Exception { - return CertificateUtils.getKeyStore( - resource != null ? resource : getKeyStoreResource(), - Objects.toString(getTrustStoreType(), getKeyStoreType()), - Objects.toString(getTrustStoreProvider(), getKeyStoreProvider()), - trustStorePassword); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/SslContextFactoryUtils.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/SslContextFactoryUtils.java deleted file mode 100644 index a0172668cbb..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/SslContextFactoryUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl.impl; - -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import javax.net.ssl.SSLContext; -import java.util.Arrays; -import java.util.List; - -/** - * @author bjorncs - */ -class SslContextFactoryUtils { - - static void setEnabledCipherSuites(SslContextFactory factory, SSLContext sslContext, List<String> enabledCiphers) { - String[] supportedCiphers = sslContext.getSupportedSSLParameters().getCipherSuites(); - factory.setIncludeCipherSuites(enabledCiphers.toArray(String[]::new)); - factory.setExcludeCipherSuites(createExclusionList(enabledCiphers, supportedCiphers)); - } - - static void setEnabledProtocols(SslContextFactory factory, SSLContext sslContext, List<String> enabledProtocols) { - String[] supportedProtocols = sslContext.getSupportedSSLParameters().getProtocols(); - factory.setIncludeProtocols(enabledProtocols.toArray(String[]::new)); - factory.setExcludeProtocols(createExclusionList(enabledProtocols, supportedProtocols)); - } - - private static String[] createExclusionList(List<String> enabledValues, String[] supportedValues) { - return Arrays.stream(supportedValues) - .filter(supportedValue -> !enabledValues.contains(supportedValue)) - .toArray(String[]::new); - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/TlsContextBasedProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/TlsContextBasedProvider.java deleted file mode 100644 index 93d4f1dca3f..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/TlsContextBasedProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl.impl; - -import com.yahoo.component.AbstractComponent; -import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; -import com.yahoo.security.tls.TlsContext; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import java.util.List; - -import static com.yahoo.jdisc.http.ssl.impl.SslContextFactoryUtils.setEnabledCipherSuites; -import static com.yahoo.jdisc.http.ssl.impl.SslContextFactoryUtils.setEnabledProtocols; - -/** - * A {@link SslContextFactoryProvider} that creates {@link SslContextFactory} instances from {@link TlsContext} instances. - * - * @author bjorncs - */ -public abstract class TlsContextBasedProvider extends AbstractComponent implements SslContextFactoryProvider { - - protected abstract TlsContext getTlsContext(String containerId, int port); - - @Override - public final SslContextFactory getInstance(String containerId, int port) { - TlsContext tlsContext = getTlsContext(containerId, port); - SSLContext sslContext = tlsContext.context(); - SSLParameters parameters = tlsContext.parameters(); - - SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setSslContext(sslContext); - - sslContextFactory.setNeedClientAuth(parameters.getNeedClientAuth()); - sslContextFactory.setWantClientAuth(parameters.getWantClientAuth()); - - setEnabledProtocols(sslContextFactory, sslContext, List.of(parameters.getProtocols())); - setEnabledCipherSuites(sslContextFactory, sslContext, List.of(parameters.getCipherSuites())); - - return sslContextFactory; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/package-info.java deleted file mode 100644 index f337e9d010b..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@ExportPackage -package com.yahoo.jdisc.http.ssl.impl; - -import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java deleted file mode 100644 index 085e9dedf20..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@PublicApi -@ExportPackage -package com.yahoo.jdisc.http.ssl; - -import com.yahoo.api.annotations.PublicApi; -import com.yahoo.osgi.annotation.ExportPackage; |