diff --git a/carapace-server/src/main/java/org/carapaceproxy/SimpleHTTPResponse.java b/carapace-server/src/main/java/org/carapaceproxy/SimpleHTTPResponse.java index 9805089fb..d9f71e957 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/SimpleHTTPResponse.java +++ b/carapace-server/src/main/java/org/carapaceproxy/SimpleHTTPResponse.java @@ -19,7 +19,7 @@ */ package org.carapaceproxy; -import java.util.Collections; +import io.netty.handler.codec.http.HttpResponseStatus; import java.util.List; import org.carapaceproxy.server.mapper.CustomHeader; @@ -28,53 +28,29 @@ * * @author paolo.venturi */ -public class SimpleHTTPResponse { +public record SimpleHTTPResponse(int errorCode, String resource, List customHeaders) { - private final int errorcode; - private final String resource; - private final List customHeaders; - - public SimpleHTTPResponse(int errorcode, String resource, List customHeaders) { - this.errorcode = errorcode; - this.resource = resource; - this.customHeaders = customHeaders == null ? Collections.emptyList() : Collections.unmodifiableList(customHeaders); - } - - public int getErrorcode() { - return errorcode; - } - - public String getResource() { - return resource; + public SimpleHTTPResponse { + customHeaders = List.copyOf(customHeaders); } - public List getCustomHeaders() { - return customHeaders; + public SimpleHTTPResponse(final int errorCode, final String resource) { + this(errorCode, resource, List.of()); } - public static final SimpleHTTPResponse NOT_FOUND(String res) { - return new SimpleHTTPResponse(404, res, null); + public static SimpleHTTPResponse notFound(final String resource) { + return new SimpleHTTPResponse(HttpResponseStatus.NOT_FOUND.code(), resource); } - public static final SimpleHTTPResponse INTERNAL_ERROR(String res) { - return new SimpleHTTPResponse(500, res, null); + public static SimpleHTTPResponse internalError(final String resource) { + return new SimpleHTTPResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), resource); } - public static final SimpleHTTPResponse MAINTENANCE_MODE(String res) { - return new SimpleHTTPResponse(500, res, null); + public static SimpleHTTPResponse badRequest(final String resource) { + return new SimpleHTTPResponse(HttpResponseStatus.BAD_REQUEST.code(), resource); } - public static final SimpleHTTPResponse BAD_REQUEST(String res) { - return new SimpleHTTPResponse(400, res, null); + public static SimpleHTTPResponse serviceUnavailable(final String resource) { + return new SimpleHTTPResponse(HttpResponseStatus.SERVICE_UNAVAILABLE.code(), resource); } - - public static final SimpleHTTPResponse SERVICE_UNAVAILABLE(String res) { - return new SimpleHTTPResponse(503, res, null); - } - - @Override - public String toString() { - return "SimpleHTTPResponse{" + "errorcode=" + errorcode + ", resource=" + resource + ", customHeaders=" + customHeaders + '}'; - } - } diff --git a/carapace-server/src/main/java/org/carapaceproxy/api/BackendsResource.java b/carapace-server/src/main/java/org/carapaceproxy/api/BackendsResource.java index 41cd05e0a..191146858 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/api/BackendsResource.java +++ b/carapace-server/src/main/java/org/carapaceproxy/api/BackendsResource.java @@ -101,10 +101,10 @@ public Map getAll() { bean.reportedAsUnreachableTs = bhs.getReportedAsUnreachableTs(); BackendHealthCheck lastProbe = bhs.getLastProbe(); if (lastProbe != null) { - bean.lastProbeTs = lastProbe.getEndTs(); + bean.lastProbeTs = lastProbe.endTs(); bean.lastProbeSuccess = lastProbe.isOk(); - bean.httpResponse = lastProbe.getHttpResponse(); - bean.httpBody = lastProbe.getHttpBody(); + bean.httpResponse = lastProbe.httpResponse(); + bean.httpBody = lastProbe.httpBody(); } } res.put(id, bean); diff --git a/carapace-server/src/main/java/org/carapaceproxy/api/RoutesResource.java b/carapace-server/src/main/java/org/carapaceproxy/api/RoutesResource.java index 97080720c..2de48a705 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/api/RoutesResource.java +++ b/carapace-server/src/main/java/org/carapaceproxy/api/RoutesResource.java @@ -19,6 +19,7 @@ */ package org.carapaceproxy.api; +import static org.carapaceproxy.server.mapper.StandardEndpointMapper.ACME_CHALLENGE_ROUTE_ACTION_ID; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletContext; @@ -26,7 +27,6 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.carapaceproxy.core.HttpProxyServer; -import static org.carapaceproxy.server.mapper.StandardEndpointMapper.ACME_CHALLENGE_ROUTE_ACTION_ID; /** * Access to configured routes @@ -77,24 +77,25 @@ public String getMatcher() { return matcher; } - public String getMaintenanceAction() { return maintenanceAction; } + public String getMaintenanceAction() { + return maintenanceAction; + } } @GET public List getAll() { - final List routes = new ArrayList(); + final List routes = new ArrayList<>(); HttpProxyServer server = (HttpProxyServer) context.getAttribute("server"); server.getMapper().getRoutes().stream() .filter(r -> !r.getId().equals(ACME_CHALLENGE_ROUTE_ACTION_ID)) - .forEach(route -> { - routes.add(new RouteBean( - route.getId(), - route.getAction(), - route.getErrorAction(), - route.isEnabled(), - route.getMatcher().getDescription(), - route.getMaintenanceModeAction())); - }); + .forEach(route -> routes.add(new RouteBean( + route.getId(), + route.getAction(), + route.getErrorAction(), + route.isEnabled(), + route.getMatcher().getDescription(), + route.getMaintenanceModeAction() + ))); return routes; } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java index 9666aa37c..3fbf03bf7 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java @@ -369,13 +369,13 @@ public Future map(String sniHostname, Promise promise) { sslContext = sslContexts.computeIfAbsent(key, (k) -> { try { - SSLCertificateConfiguration choosen = chooseCertificate(sniHostname, config.getDefaultCertificate()); - if (choosen == null) { + SSLCertificateConfiguration chosen = chooseCertificate(sniHostname, config.getDefaultCertificate()); + if (chosen == null) { throw new ConfigurationNotValidException("cannot find a certificate for snihostname " + sniHostname + ", with default cert for listener as '" + config.getDefaultCertificate() + "', available " + currentConfiguration.getCertificates().keySet()); } - return bootSslContext(config, choosen); + return bootSslContext(config, chosen); } catch (ConfigurationNotValidException ex) { throw new RuntimeException(ex); } @@ -464,16 +464,16 @@ public SSLCertificateConfiguration chooseCertificate(String sniHostname, String } } } - SSLCertificateConfiguration choosen = null; + SSLCertificateConfiguration chosen = null; if (certificateMatchExact != null) { - choosen = certificateMatchExact; + chosen = certificateMatchExact; } else if (certificateMatchNoExact != null) { - choosen = certificateMatchNoExact; + chosen = certificateMatchNoExact; } - if (choosen == null) { - choosen = certificates.get(defaultCertificate); + if (chosen == null) { + chosen = certificates.get(defaultCertificate); } - return choosen; + return chosen; } private static boolean certificateMatches(String hostname, SSLCertificateConfiguration c, boolean exact) { diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java index 0489be936..17790078f 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java @@ -32,12 +32,15 @@ import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.ssl.SslHandler; import java.net.InetSocketAddress; +import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import lombok.Data; import org.carapaceproxy.server.filters.UrlEncodedQueryString; import org.carapaceproxy.server.mapper.MapResult; import org.carapaceproxy.server.mapper.requestmatcher.MatchingContext; +import org.carapaceproxy.utils.StringUtils; import org.reactivestreams.Publisher; import reactor.netty.ByteBufFlux; import reactor.netty.http.server.HttpServerRequest; @@ -51,9 +54,6 @@ @Data public class ProxyRequest implements MatchingContext { - private static final Pattern NETTY_HTTP_1_VERSION_PATTERN = - Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)"); - // All properties name have been converted to lowercase during parsing public static final String PROPERTY_URI = "request.uri"; public static final String PROPERTY_METHOD = "request.method"; @@ -61,7 +61,7 @@ public class ProxyRequest implements MatchingContext { public static final String PROPERTY_HEADERS = "request.headers."; public static final String PROPERTY_LISTENER_HOST_PORT = "listener.hostport"; public static final String PROPERTY_LISTENER_IPADDRESS = "listener.ipaddress"; - + private static final Pattern NETTY_HTTP_1_VERSION_PATTERN = Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)"); private static final int HEADERS_SUBSTRING_INDEX = PROPERTY_HEADERS.length(); private static final AtomicLong REQUESTS_ID_GENERATOR = new AtomicLong(); @@ -69,6 +69,9 @@ public class ProxyRequest implements MatchingContext { private final HttpServerRequest request; private final HttpServerResponse response; private final EndpointKey listener; + private final HttpVersion httpProtocol; + private final String sslProtocol; + private final String cipherSuite; private MapResult action; private String userId; private String sessionId; @@ -77,12 +80,11 @@ public class ProxyRequest implements MatchingContext { private volatile long lastActivity; private String uri; private UrlEncodedQueryString queryString; - private String sslProtocol; - private String cipherSuite; private boolean servedFromCache; - private HttpVersion httpProtocol; - public ProxyRequest(HttpServerRequest request, HttpServerResponse response, EndpointKey listener) { + public ProxyRequest( + final HttpServerRequest request, final HttpServerResponse response, final EndpointKey listener + ) { this.request = request; this.response = response; this.listener = listener; @@ -94,13 +96,27 @@ public ProxyRequest(HttpServerRequest request, HttpServerResponse response, Endp } else { throw new IllegalArgumentException("Unsupported request protocol: " + protocol); } - request.withConnection(conn -> { - SslHandler handler = conn.channel().pipeline().get(SslHandler.class); - if (handler != null) { - sslProtocol = handler.engine().getSession().getProtocol(); - cipherSuite = handler.engine().getSession().getCipherSuite(); - } - }); + final SslHandler handler = getSslHandler(); + if (handler != null) { + this.sslProtocol = handler.engine().getSession().getProtocol(); + this.cipherSuite = handler.engine().getSession().getCipherSuite(); + } else { + this.sslProtocol = null; + this.cipherSuite = null; + } + } + + public static UrlEncodedQueryString parseQueryString(final String uri) { + int pos = uri.indexOf('?'); + return pos < 0 || pos == uri.length() - 1 + ? UrlEncodedQueryString.create() + : UrlEncodedQueryString.parse(uri.substring(pos + 1)); + } + + private SslHandler getSslHandler() { + final AtomicReference sslHandlerRef = new AtomicReference<>(); + request.withConnection(conn -> sslHandlerRef.set(conn.channel().pipeline().get(SslHandler.class))); + return sslHandlerRef.get(); } @Override @@ -121,15 +137,15 @@ public String getProperty(String name) { @Override public boolean isSecure() { - return request.scheme().equalsIgnoreCase(HttpScheme.HTTPS + ""); + return HttpScheme.HTTPS.name().contentEqualsIgnoreCase(request.scheme()); } public InetSocketAddress getLocalAddress() { - return request.hostAddress(); + return Objects.requireNonNull(request.hostAddress()); } public InetSocketAddress getRemoteAddress() { - return request.remoteAddress(); + return Objects.requireNonNull(request.remoteAddress()); } /** @@ -149,18 +165,18 @@ public String getUri() { uri = uri.substring(schemePrefix.length()); } String fullPath = request.fullPath(); - if (fullPath != null && !fullPath.isBlank()) { - int pos = uri.indexOf(request.fullPath()); - if (pos > 0) { - uri = uri.substring(pos); - } - } else { + if (StringUtils.isBlank(fullPath)) { int queryStringPos = uri.indexOf("?"); if (queryStringPos >= 0) { uri = "/" + uri.substring(queryStringPos); } else { uri = "/"; } + } else { + int pos = uri.indexOf(request.fullPath()); + if (pos > 0) { + uri = uri.substring(pos); + } } return uri; @@ -192,7 +208,7 @@ public String getRequestHostname() { return request.requestHeaders().get(HttpHeaderNames.HOST); } - public boolean isValidHostAndPort(String hostAndPort) { + public boolean isValidHostAndPort(final String hostAndPort) { try { if (hostAndPort == null) { return false; @@ -221,13 +237,6 @@ public UrlEncodedQueryString getQueryString() { return queryString; } - public static UrlEncodedQueryString parseQueryString(String uri) { - int pos = uri.indexOf('?'); - return pos < 0 || pos == uri.length() - 1 - ? UrlEncodedQueryString.create() - : UrlEncodedQueryString.parse(uri.substring(pos + 1)); - } - public HttpHeaders getRequestHeaders() { return request.requestHeaders(); } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java index 46c1bda2e..65c3d10d4 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java @@ -37,6 +37,7 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.timeout.ReadTimeoutException; import io.prometheus.client.Counter; import io.prometheus.client.Gauge; import java.net.ConnectException; @@ -46,9 +47,12 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; @@ -65,6 +69,7 @@ import org.carapaceproxy.server.mapper.MapResult; import org.carapaceproxy.utils.HttpUtils; import org.carapaceproxy.utils.PrometheusUtils; +import org.carapaceproxy.utils.StringUtils; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,10 +132,6 @@ public Publisher processRequest(ProxyRequest request) { parent.getFilters().forEach(filter -> filter.apply(request)); MapResult action = parent.getMapper().map(request); - if (action == null) { - LOGGER.info("Mapper returned NULL action for {}", this); - action = MapResult.internalError(MapResult.NO_ROUTE); - } request.setAction(action); @@ -139,7 +140,7 @@ public Publisher processRequest(ProxyRequest request) { } try { - return switch (action.action) { + return switch (action.getAction()) { case NOTFOUND -> serveNotFoundMessage(request); case INTERNAL_ERROR -> serveInternalErrorMessage(request); case SERVICE_UNAVAILABLE -> serveServiceUnavailable(request); @@ -149,7 +150,7 @@ public Publisher processRequest(ProxyRequest request) { case REDIRECT -> serveRedirect(request); case PROXY -> forward(request, false); case CACHE -> serveFromCache(request); // cached content - default -> throw new IllegalStateException("Action " + action.action + " not supported"); + default -> throw new IllegalStateException("Action " + action.getAction() + " not supported"); }; } finally { parent.getRequestsLogger().logRequest(request); @@ -161,15 +162,11 @@ private Publisher serveNotFoundMessage(ProxyRequest request) { return Mono.empty(); } - SimpleHTTPResponse res = parent.getMapper().mapPageNotFound(request.getAction().routeId); - int code = 0; - String resource = null; - List customHeaders = null; - if (res != null) { - code = res.getErrorcode(); - resource = res.getResource(); - customHeaders = res.getCustomHeaders(); - } + final MapResult action = Objects.requireNonNull(request.getAction()); + SimpleHTTPResponse res = parent.getMapper().mapPageNotFound(action.getRouteId()); + int code = res.errorCode(); + String resource = res.resource(); + List customHeaders = res.customHeaders(); if (resource == null) { resource = StaticContentsManager.DEFAULT_NOT_FOUND; } @@ -187,15 +184,11 @@ private Publisher serveInternalErrorMessage(ProxyRequest request) { return Mono.empty(); } - SimpleHTTPResponse res = parent.getMapper().mapInternalError(request.getAction().routeId); - int code = 0; - String resource = null; - List customHeaders = null; - if (res != null) { - code = res.getErrorcode(); - resource = res.getResource(); - customHeaders = res.getCustomHeaders(); - } + final MapResult action = Objects.requireNonNull(request.getAction()); + SimpleHTTPResponse res = parent.getMapper().mapInternalError(action.getRouteId()); + int code = res.errorCode(); + String resource = res.resource(); + List customHeaders = res.customHeaders(); if (resource == null) { resource = StaticContentsManager.DEFAULT_INTERNAL_SERVER_ERROR; } @@ -213,15 +206,11 @@ private Publisher serveMaintenanceMessage(ProxyRequest request) { return Mono.empty(); } - SimpleHTTPResponse res = parent.getMapper().mapMaintenanceMode(request.getAction().routeId); - int code = 0; - String resource = null; - List customHeaders = null; - if (res != null) { - code = res.getErrorcode(); - resource = res.getResource(); - customHeaders = res.getCustomHeaders(); - } + final MapResult action = Objects.requireNonNull(request.getAction()); + SimpleHTTPResponse res = parent.getMapper().mapMaintenanceMode(action.getRouteId()); + int code = res.errorCode(); + String resource = res.resource(); + List customHeaders = res.customHeaders(); if (resource == null) { resource = StaticContentsManager.DEFAULT_MAINTENANCE_MODE_ERROR; } @@ -240,14 +229,9 @@ private Publisher serveBadRequestMessage(ProxyRequest request) { } SimpleHTTPResponse res = parent.getMapper().mapBadRequest(); - int code = 0; - String resource = null; - List customHeaders = null; - if (res != null) { - code = res.getErrorcode(); - resource = res.getResource(); - customHeaders = res.getCustomHeaders(); - } + int code = res.errorCode(); + String resource = res.resource(); + List customHeaders = res.customHeaders(); if (resource == null) { resource = StaticContentsManager.DEFAULT_BAD_REQUEST; } @@ -264,10 +248,11 @@ private Publisher serveStaticMessage(ProxyRequest request) { if (request.getResponse().hasSentHeaders()) { return Mono.empty(); } + final MapResult action = Objects.requireNonNull(request.getAction()); FullHttpResponse response = parent .getStaticContentsManager() - .buildResponse(request.getAction().errorCode, request.getAction().resource, request.getHttpProtocol()); - return writeSimpleResponse(request, response, request.getAction().customHeaders); + .buildResponse(action.getErrorCode(), action.getResource(), request.getHttpProtocol()); + return writeSimpleResponse(request, response, action.getCustomHeaders()); } private Publisher serveRedirect(ProxyRequest request) { @@ -275,42 +260,46 @@ private Publisher serveRedirect(ProxyRequest request) { return Mono.empty(); } - MapResult action = request.getAction(); + MapResult action = Objects.requireNonNull(request.getAction()); DefaultFullHttpResponse response = new DefaultFullHttpResponse( request.getHttpProtocol(), // redirect: 3XX - HttpResponseStatus.valueOf(action.errorCode < 0 ? HttpStatus.SC_MOVED_TEMPORARILY : action.errorCode) + HttpResponseStatus.valueOf(action.getErrorCode() < 0 ? HttpStatus.SC_MOVED_TEMPORARILY : action.getErrorCode()) ); response.headers().set(HttpHeaderNames.CACHE_CONTROL, "no-cache"); - String location = action.redirectLocation; - String host = request.getRequestHostname(); - String port = host.contains(":") ? host.replaceFirst(".*:", ":") : ""; - host = host.split(":")[0]; + String location = action.getRedirectLocation(); + final String hostname = request.getRequestHostname(); + String host = null; + String port = null; + if (hostname != null) { + host = hostname.split(":")[0]; + port = hostname.contains(":") ? hostname.replaceFirst(".*:", ":") : ""; + } String path = request.getUri(); if (location == null || location.isEmpty()) { - if (!action.host.isEmpty()) { - host = action.host; + if (!StringUtils.isBlank(action.getHost())) { + host = action.getHost(); } - if (action.port > 0) { - port = ":" + action.port; - } else if (REDIRECT_PROTO_HTTPS.equals(action.redirectProto)) { + if (action.getPort() > 0) { + port = ":" + action.getPort(); + } else if (REDIRECT_PROTO_HTTPS.equals(action.getRedirectProto())) { port = ""; // default https port } - if (!action.redirectPath.isEmpty()) { - path = action.redirectPath; + if (!StringUtils.isBlank(action.getRedirectPath())) { + path = action.getRedirectPath(); } - location = host + port + path; // - custom redirection + location = Objects.requireNonNull(host) + Objects.requireNonNull(port) + path; // - custom redirection } else if (location.startsWith("/")) { location = host + port + location; // - relative redirection } // else: implicit absolute redirection // - redirect to https - location = (REDIRECT_PROTO_HTTPS.equals(action.redirectProto) ? REDIRECT_PROTO_HTTPS : REDIRECT_PROTO_HTTP) + location = (REDIRECT_PROTO_HTTPS.equals(action.getRedirectProto()) ? REDIRECT_PROTO_HTTPS : REDIRECT_PROTO_HTTP) + "://" + location.replaceFirst("http.?://", ""); response.headers().set(HttpHeaderNames.LOCATION, location); - return writeSimpleResponse(request, response, request.getAction().customHeaders); + return writeSimpleResponse(request, response, request.getAction().getCustomHeaders()); } private static Publisher writeSimpleResponse(ProxyRequest request, FullHttpResponse response, List customHeaders) { @@ -326,25 +315,29 @@ private static Publisher writeSimpleResponse(ProxyRequest request, FullHtt } request.setResponseStatus(response.status()); request.setResponseHeaders(response.headers().copy()); - addCustomResponseHeaders(request, customHeaders); + addCustomResponseHeaders(request.getResponseHeaders(), customHeaders); // Write the response return request.sendResponseData(Mono.just(response.content()).doFinally(f -> request.setLastActivity(System.currentTimeMillis()))); } - private static void addCustomResponseHeaders(ProxyRequest request, List customHeaders) { + private static void applyCustomResponseHeaders(final ProxyRequest request) { + final MapResult action = Objects.requireNonNull(request.getAction()); + addCustomResponseHeaders(request.getResponseHeaders(), action.getCustomHeaders()); + } + + private static void addCustomResponseHeaders(final HttpHeaders responseHeaders, final List customHeaders) { if (customHeaders == null || customHeaders.isEmpty()) { return; } - HttpHeaders headers = request.getResponseHeaders(); customHeaders.forEach(customHeader -> { if (CustomHeader.HeaderMode.SET.equals(customHeader.getMode()) || CustomHeader.HeaderMode.REMOVE.equals(customHeader.getMode())) { - headers.remove(customHeader.getName()); + responseHeaders.remove(customHeader.getName()); } if (CustomHeader.HeaderMode.SET.equals(customHeader.getMode()) || CustomHeader.HeaderMode.ADD.equals(customHeader.getMode())) { - headers.add(customHeader.getName(), customHeader.getValue()); + responseHeaders.add(customHeader.getName(), customHeader.getValue()); } }); } @@ -352,13 +345,14 @@ private static void addCustomResponseHeaders(ProxyRequest request, List forward(ProxyRequest request, boolean cache) { - final String endpointHost = request.getAction().host; - final int endpointPort = request.getAction().port; + Objects.requireNonNull(request.getAction()); + final String endpointHost = request.getAction().getHost(); + final int endpointPort = request.getAction().getPort(); EndpointKey key = EndpointKey.make(endpointHost, endpointPort); EndpointStats endpointStats = endpointsStats.computeIfAbsent(key, EndpointStats::new); @@ -463,18 +457,18 @@ public Publisher forward(ProxyRequest request, boolean cache) { connectionConfig.getId(), request.getUri(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss.SSS")), - request.getAction().host + request.getAction().getHost() ); } request.setResponseStatus(resp.status()); request.setResponseHeaders(resp.responseHeaders().copy()); // headers from endpoint to client - if (cacheable.get() && parent.getCache().isCacheable(resp) && cacheReceiver.receivedFromRemote(resp)) { + if (cacheable.get() && parent.getCache().isCacheable(resp) && Objects.requireNonNull(cacheReceiver).receivedFromRemote(resp)) { addCachedResponseHeaders(request); } else { cacheable.set(false); } - addCustomResponseHeaders(request, request.getAction().customHeaders); + applyCustomResponseHeaders(request); if (aggregateChunksForLegacyHttp(request)) { return request.sendResponseData(flux.aggregate().retain().map(ByteBuf::asByteBuf) @@ -482,7 +476,7 @@ public Publisher forward(ProxyRequest request, boolean cache) { request.setLastActivity(System.currentTimeMillis()); endpointStats.getLastActivity().set(System.currentTimeMillis()); if (cacheable.get()) { - cacheReceiver.receivedFromRemote(data, parent.getCachePoolAllocator()); + Objects.requireNonNull(cacheReceiver).receivedFromRemote(data, parent.getCachePoolAllocator()); } }).doOnSuccess(data -> { if (cacheable.get()) { @@ -495,7 +489,7 @@ public Publisher forward(ProxyRequest request, boolean cache) { request.setLastActivity(System.currentTimeMillis()); endpointStats.getLastActivity().set(System.currentTimeMillis()); if (cacheable.get()) { - cacheReceiver.receivedFromRemote(data, parent.getCachePoolAllocator()); + Objects.requireNonNull(cacheReceiver).receivedFromRemote(data, parent.getCachePoolAllocator()); } }).doOnComplete(() -> { if (LOGGER.isDebugEnabled()) { @@ -506,7 +500,7 @@ public Publisher forward(ProxyRequest request, boolean cache) { connectionConfig.getId(), request.getUri(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss.SSS")), - request.getAction().host + request.getAction().getHost() ); } if (cacheable.get()) { @@ -516,8 +510,8 @@ public Publisher forward(ProxyRequest request, boolean cache) { }).onErrorResume(err -> { // custom endpoint request/response error handling PENDING_REQUESTS_GAUGE.dec(); - EndpointKey endpoint = EndpointKey.make(request.getAction().host, request.getAction().port); - if (err instanceof io.netty.handler.timeout.ReadTimeoutException) { + EndpointKey endpoint = EndpointKey.make(request.getAction().getHost(), request.getAction().getPort()); + if (err instanceof ReadTimeoutException) { STUCK_REQUESTS_COUNTER.inc(); LOGGER.error("Read timeout error occurred for endpoint {}; request: {}", endpoint, request); if (parent.getCurrentConfiguration().isBackendsUnreachableOnStuckRequests()) { @@ -548,15 +542,11 @@ private Publisher serveServiceUnavailable(ProxyRequest request) { return Mono.empty(); } - SimpleHTTPResponse res = parent.getMapper().mapServiceUnavailableError(request.getAction().routeId); - int code = 0; - String resource = null; - List customHeaders = null; - if (res != null) { - code = res.getErrorcode(); - resource = res.getResource(); - customHeaders = res.getCustomHeaders(); - } + final MapResult action = Objects.requireNonNull(request.getAction()); + SimpleHTTPResponse res = parent.getMapper().mapServiceUnavailableError(action.getRouteId()); + int code = res.errorCode(); + String resource = res.resource(); + List customHeaders = res.customHeaders(); if (resource == null) { resource = StaticContentsManager.DEFAULT_SERVICE_UNAVAILABLE_ERROR; } @@ -583,7 +573,7 @@ private static void cleanRequestFromCacheValidators(ProxyRequest request) { private void addCachedResponseHeaders(ProxyRequest request) { HttpHeaders headers = request.getResponseHeaders(); if (!headers.contains(HttpHeaderNames.EXPIRES)) { - headers.add(HttpHeaderNames.EXPIRES, HttpUtils.formatDateHeader(new java.util.Date(parent.getCache().computeDefaultExpireDate()))); + headers.add(HttpHeaderNames.EXPIRES, HttpUtils.formatDateHeader(new Date(parent.getCache().computeDefaultExpireDate()))); } } @@ -606,9 +596,9 @@ private Publisher serveFromCache(ProxyRequest request) { headers.remove(HttpHeaderNames.ACCEPT_RANGES); headers.remove(HttpHeaderNames.ETAG); headers.add("X-Cached", "yes; ts=" + content.getCreationTs()); - headers.add(HttpHeaderNames.EXPIRES, HttpUtils.formatDateHeader(new java.util.Date(content.getExpiresTs()))); + headers.add(HttpHeaderNames.EXPIRES, HttpUtils.formatDateHeader(new Date(content.getExpiresTs()))); request.setResponseHeaders(headers); - addCustomResponseHeaders(request, request.getAction().customHeaders); + applyCustomResponseHeaders(request); // If the request is http 1.0, we make sure to send without chunked if (aggregateChunksForLegacyHttp(request)) { return request.sendResponseData(Mono.from(ByteBufFlux.fromIterable(content.getChunks()))); @@ -622,17 +612,17 @@ private Publisher serveFromCache(ProxyRequest request) { // content not modified request.setResponseStatus(HttpResponseStatus.NOT_MODIFIED); HttpHeaders headers = new DefaultHttpHeaders(); - headers.set(HttpHeaderNames.LAST_MODIFIED, HttpUtils.formatDateHeader(new java.util.Date(content.getLastModified()))); - headers.set(HttpHeaderNames.EXPIRES, HttpUtils.formatDateHeader(new java.util.Date(content.getExpiresTs()))); + headers.set(HttpHeaderNames.LAST_MODIFIED, HttpUtils.formatDateHeader(new Date(content.getLastModified()))); + headers.set(HttpHeaderNames.EXPIRES, HttpUtils.formatDateHeader(new Date(content.getExpiresTs()))); headers.add("X-Cached", "yes; ts=" + content.getCreationTs()); request.setResponseHeaders(headers); return request.send(); } - public static class ConnectionsManager implements AutoCloseable, Function> { + public static class ConnectionsManager implements AutoCloseable, Function> { private final Map connectionPools = new ConcurrentHashMap<>(); - private volatile Map.Entry defaultConnectionPool; + private volatile Entry defaultConnectionPool; public void reloadConfiguration(RuntimeServerConfiguration newConfiguration, Collection newEndpoints) { close(); @@ -685,7 +675,9 @@ public void close() { connectionPools.clear(); if (defaultConnectionPool != null) { - defaultConnectionPool.getValue().dispose(); // graceful shutdown according to disposeTimeout + // being it volatile, we don't have the compile-time certainty that it won't become null after the check + // still, it is reasonably safe to assume that it stay not null + Objects.requireNonNull(defaultConnectionPool).getValue().dispose(); // graceful shutdown according to disposeTimeout } // reset connections provider metrics @@ -697,13 +689,14 @@ public void close() { } @Override - public Map.Entry apply(ProxyRequest request) { + public Entry apply(ProxyRequest request) { String hostName = request.getRequestHostname(); - Map.Entry selectedPool = connectionPools.entrySet().stream() + Entry selectedPool = connectionPools.entrySet().stream() .filter(e -> Pattern.matches(e.getKey().getDomain(), hostName)) .findFirst() .orElse(defaultConnectionPool); + Objects.requireNonNull(selectedPool); LOGGER.debug("Using connection {} for domain {}", selectedPool.getKey().getId(), hostName); return selectedPool; @@ -717,9 +710,9 @@ public ConnectionsManager getConnectionsManager() { @VisibleForTesting public Map getConnectionPools() { - HashMap pools = new HashMap<>(connectionsManager.connectionPools); - pools.put(connectionsManager.defaultConnectionPool.getKey(), connectionsManager.defaultConnectionPool.getValue()); - + final HashMap pools = new HashMap<>(connectionsManager.connectionPools); + final Entry defaultConnectionPool = Objects.requireNonNull(connectionsManager.defaultConnectionPool); + pools.put(defaultConnectionPool.getKey(), defaultConnectionPool.getValue()); return pools; } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/RequestsLogger.java b/carapace-server/src/main/java/org/carapaceproxy/core/RequestsLogger.java index 5ede9a05e..dcb318c1b 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/RequestsLogger.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/RequestsLogger.java @@ -208,7 +208,7 @@ public void reloadConfiguration(RuntimeServerConfiguration newConfiguration) { this.newConfiguration = newConfiguration; } - private void _reloadConfiguration() throws IOException { + private void reloadConfiguration() throws IOException { if (newConfiguration == null) { return; } @@ -286,13 +286,12 @@ public void run() { Entry currentEntry = null; while (!closed) { try { - _reloadConfiguration(); + reloadConfiguration(); try { ensureAccessLogFileOpened(); } catch (IOException ex) { - LOG.error("Exception while trying to open access log file"); - LOG.error(null, ex); + LOG.error("Exception while trying to open access log file", ex); Thread.sleep(currentConfiguration.getAccessLogWaitBetweenFailures()); lastFlush = System.currentTimeMillis(); if (breakRunForTests) { @@ -334,18 +333,16 @@ public void run() { try { closeAccessLogFile(); } catch (IOException ex1) { - LOG.error(null, ex1); + LOG.error("Failed to close access log after interrupt", ex1); } closed = true; } catch (IOException ex) { - LOG.error("Exception while writing on access log file"); - LOG.error(null, ex); + LOG.error("Exception while writing on access log file", ex); try { closeAccessLogFile(); } catch (IOException ex1) { - LOG.error("Exception while trying to close access log file"); - LOG.error(null, ex1); + LOG.error("Exception while trying to close access log file", ex1); } // File opening will be retried at next cycle start @@ -389,8 +386,8 @@ public Entry(final ProxyRequest request, final String format, final DateTimeForm this.format.add("uri", request.getUri()); this.format.add("timestamp", tsFormatter.format(Instant.ofEpochMilli(request.getStartTs()))); this.format.add("total_time", request.getLastActivity() - request.getStartTs()); - this.format.add("action_id", request.getAction().action); - this.format.add("route_id", request.getAction().routeId); + this.format.add("action_id", request.getAction().getAction()); + this.format.add("route_id", request.getAction().getRouteId()); this.format.add("user_id", request.getUserId()); this.format.add("session_id", request.getSessionId()); this.format.add("http_protocol_version", request.getRequest().version()); @@ -398,7 +395,7 @@ public Entry(final ProxyRequest request, final String format, final DateTimeForm this.format.add("backend_id", "CACHED"); this.format.add("backend_time", "0"); } else { - this.format.add("backend_id", String.format("%s:%s", request.getAction().host, request.getAction().port)); + this.format.add("backend_id", String.format("%s:%s", request.getAction().getHost(), request.getAction().getPort())); this.format.add("backend_time", request.getBackendStartTs() - request.getStartTs()); } formatSSLProperties(request); diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java index c68592ebb..0d99a81c9 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java @@ -74,6 +74,7 @@ public class RuntimeServerConfiguration { private static final Logger LOG = LoggerFactory.getLogger(RuntimeServerConfiguration.class); + private static final int DEFAULT_PROBE_PERIOD = 0; private final List listeners = new ArrayList<>(); private final Map certificates = new HashMap<>(); @@ -113,7 +114,7 @@ public class RuntimeServerConfiguration { private boolean accessLogAdvancedEnabled = false; private int accessLogAdvancedBodySize = 1_000; // bytes private String userRealmClassname; - private int healthProbePeriod = 0; + private int healthProbePeriod = DEFAULT_PROBE_PERIOD; private int healthConnectTimeout = 5_000; private int dynamicCertificatesManagerPeriod = 0; private int keyPairsSize = DEFAULT_KEYPAIRS_SIZE; @@ -224,9 +225,9 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali configureFilters(properties); configureConnectionPools(properties); - healthProbePeriod = properties.getInt("healthmanager.period", 0); + healthProbePeriod = properties.getInt("healthmanager.period", DEFAULT_PROBE_PERIOD); LOG.info("healthmanager.period={}", healthProbePeriod); - if (healthProbePeriod <= 0) { + if (healthProbePeriod <= DEFAULT_PROBE_PERIOD) { LOG.warn("BACKEND-HEALTH-MANAGER DISABLED"); } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthCheck.java b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthCheck.java index 35678a89e..b7faea733 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthCheck.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthCheck.java @@ -23,151 +23,106 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.util.Objects; +import javax.ws.rs.core.UriBuilder; import org.carapaceproxy.utils.IOUtils; -/** - * - * @author francesco.caliumi - */ -public class BackendHealthCheck { - - public static final int RESULT_SUCCESS = 1; - public static final int RESULT_FAILURE_CONNECTION = 2; - public static final int RESULT_FAILURE_STATUS = 3; - - private final String path; - private final long startTs; - private final long endTs; - private final int result; - private final String httpResponse; - private final String httpBody; - - public BackendHealthCheck(String path, long startTs, long endTs, int result, String httpResponse, String httpBody) { - this.path = path; - this.startTs = startTs; - this.endTs = endTs; - this.result = result; - this.httpResponse = httpResponse; - this.httpBody = httpBody; - } - - @Override - public String toString() { - return "BackendHealthCheck{" + "path=" + path + ", startTs=" + startTs + ", endTs=" + endTs + ", result=" + result + ", httpResponse=" + httpResponse + ", httpBody=" + httpBody + '}'; - } - - public String getPath() { - return path; - } - - public long getStartTs() { - return startTs; - } - - public long getEndTs() { - return endTs; +public record BackendHealthCheck( + String path, + long startTs, + long endTs, + Result result, + String httpResponse, + String httpBody +) { + private enum Result { + SUCCESS, // 1 + FAILURE_CONNECTION, // 2 + FAILURE_STATUS // 3 } public long getResponseTime() { return endTs - startTs; } - public int getResult() { - return result; - } - - public String getHttpResponse() { - return httpResponse; - } - - public String getHttpBody() { - return httpBody; - } - public boolean isOk() { - return result == RESULT_SUCCESS; + return result == Result.SUCCESS; } public static BackendHealthCheck check(String host, int port, String path, int timeoutMillis) { - - if (path == null || path.isEmpty()) { + if (path.isEmpty()) { long now = System.currentTimeMillis(); - return new BackendHealthCheck(path, now, now, RESULT_SUCCESS, "OK", "MOCK OK"); - } else { - long startts = System.currentTimeMillis(); - URL url; - HttpURLConnection httpConn = null; - try { - url = new URI("http", null, host, port, path, null, null).toURL(); - URLConnection conn = url.openConnection(); - conn.setConnectTimeout(timeoutMillis); - conn.setReadTimeout(timeoutMillis); - conn.setUseCaches(false); - - if (!(conn instanceof HttpURLConnection)) { - throw new IllegalStateException("Only HttpURLConnection is supported"); - } - httpConn = (HttpURLConnection) conn; - httpConn.setRequestMethod("GET"); - httpConn.setInstanceFollowRedirects(true); - - try (InputStream is = httpConn.getInputStream()) { - int httpCode = httpConn.getResponseCode(); - String httpResponse = httpCode + " " + Objects.toString(httpConn.getResponseMessage(), ""); - String httpBody = IOUtils.toString(is, StandardCharsets.UTF_8); - return new BackendHealthCheck( - path, - startts, - System.currentTimeMillis(), - httpCode >= 200 && httpCode <= 299 ? RESULT_SUCCESS : RESULT_FAILURE_STATUS, - httpResponse, - httpBody - ); - } - - } catch (MalformedURLException | URISyntaxException ex) { - throw new RuntimeException(ex); - - } catch (IOException | RuntimeException ex) { - int result = RESULT_FAILURE_STATUS; - int httpCode = 0; - String httpResponse = ""; - String httpErrorBody = ""; - - if (httpConn != null) { - try { - httpCode = httpConn.getResponseCode(); - httpResponse = httpCode + " " + Objects.toString(httpConn.getResponseMessage(), ""); - } catch (IOException ex2) { - // Ignore - } - - try { - httpErrorBody = IOUtils.toString(httpConn.getErrorStream(), StandardCharsets.UTF_8); - } catch (IOException ex2) { - // Ignore - } - } - - if (httpCode <= 0) { - result = RESULT_FAILURE_CONNECTION; - httpResponse = ex.getMessage(); - } + return new BackendHealthCheck(path, now, now, Result.SUCCESS, "OK", "MOCK OK"); + } + final long startTs = System.currentTimeMillis(); + HttpURLConnection httpConn = null; + try { + final URL url = UriBuilder.fromPath(path).scheme("http").host(host).port(port).build().toURL(); + URLConnection conn = url.openConnection(); + conn.setConnectTimeout(timeoutMillis); + conn.setReadTimeout(timeoutMillis); + conn.setUseCaches(false); + + if (!(conn instanceof HttpURLConnection)) { + throw new IllegalStateException("Only HttpURLConnection is supported"); + } + httpConn = (HttpURLConnection) conn; + httpConn.setRequestMethod("GET"); + httpConn.setInstanceFollowRedirects(true); + + try (InputStream is = httpConn.getInputStream()) { + int httpCode = httpConn.getResponseCode(); + String httpResponse = httpCode + " " + Objects.toString(httpConn.getResponseMessage(), ""); + String httpBody = IOUtils.toString(is, StandardCharsets.UTF_8); return new BackendHealthCheck( path, - startts, + startTs, System.currentTimeMillis(), - result, + httpCode >= 200 && httpCode <= 299 ? Result.SUCCESS : Result.FAILURE_STATUS, httpResponse, - httpErrorBody + httpBody ); } + + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + + } catch (IOException | RuntimeException ex) { + Result result = Result.FAILURE_STATUS; + int httpCode = 0; + String httpResponse = ""; + String httpErrorBody = ""; + + if (httpConn != null) { + try { + httpCode = httpConn.getResponseCode(); + httpResponse = httpCode + " " + Objects.toString(httpConn.getResponseMessage(), ""); + } catch (IOException ex2) { + // Ignore + } + + try { + httpErrorBody = IOUtils.toString(httpConn.getErrorStream(), StandardCharsets.UTF_8); + } catch (IOException ex2) { + // Ignore + } + } + + if (httpCode <= 0) { + result = Result.FAILURE_CONNECTION; + httpResponse = ex.getMessage(); + } + return new BackendHealthCheck( + path, + startTs, + System.currentTimeMillis(), + result, + httpResponse, + httpErrorBody + ); } } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java index 6fa6ada94..bd023b0b1 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java @@ -39,7 +39,9 @@ import org.slf4j.LoggerFactory; /** - * Keeps status about backends + * Track status about backends. + * In conjunction with a {@link org.carapaceproxy.server.config.BackendSelector}, + * it helps {@link org.carapaceproxy.core.ProxyRequestsManager} to choose the right backend to route a request to. * * @author enrico.olivelli */ @@ -48,8 +50,9 @@ public class BackendHealthManager implements Runnable { public static final int DEFAULT_PERIOD = 60; // seconds private static final Logger LOG = LoggerFactory.getLogger(BackendHealthManager.class); - private static final Gauge BACKEND_UPSTATUS_GAUGE = PrometheusUtils.createGauge("health", "backend_status", - "backend status", "host").register(); + private static final Gauge BACKEND_UPSTATUS_GAUGE = PrometheusUtils + .createGauge("health", "backend_status", "backend status", "host") + .register(); private EndpointMapper mapper; @@ -60,18 +63,17 @@ public class BackendHealthManager implements Runnable { private volatile int period; // can change at runtime private volatile int connectTimeout; - private volatile boolean started; // keep track of start() calling + // keep track of start() calling + private volatile boolean started; private final ConcurrentHashMap backends = new ConcurrentHashMap<>(); - public BackendHealthManager(RuntimeServerConfiguration conf, EndpointMapper mapper) { - - this.mapper = mapper; // may be null + public BackendHealthManager(final RuntimeServerConfiguration conf, final EndpointMapper mapper) { + this.mapper = mapper; // will be overridden before start this.period = DEFAULT_PERIOD; this.connectTimeout = conf.getHealthConnectTimeout(); - } public int getPeriod() { @@ -109,9 +111,9 @@ public synchronized void stop() { } public synchronized void reloadConfiguration(RuntimeServerConfiguration newConfiguration, EndpointMapper mapper) { - int newPeriod = newConfiguration.getHealthProbePeriod(); - boolean changePeriod = period != newPeriod; - boolean restart = scheduledFuture != null && changePeriod; + final int newPeriod = newConfiguration.getHealthProbePeriod(); + final boolean changePeriod = period != newPeriod; + final boolean restart = scheduledFuture != null && changePeriod; if (restart) { scheduledFuture.cancel(true); @@ -147,30 +149,29 @@ public void run() { bconf.host(), bconf.port(), bconf.probePath(), connectTimeout); if (checkResult.isOk()) { + final var responseTime = checkResult.endTs() - checkResult.startTs(); if (status.isReportedAsUnreachable()) { - LOG.warn("backend {} was unreachable, setting again to reachable. Response time {}ms", status.getHostPort(), checkResult.getEndTs() - checkResult.getStartTs()); + LOG.warn("backend {} was unreachable, setting again to reachable. Response time {}ms", status.getHostPort(), responseTime); reportBackendReachable(status.getHostPort()); } else { - LOG.debug("backend {} seems reachable. Response time {}ms", status.getHostPort(), checkResult.getEndTs() - checkResult.getStartTs()); + LOG.debug("backend {} seems reachable. Response time {}ms", status.getHostPort(), responseTime); } } else { if (status.isReportedAsUnreachable()) { - LOG.debug("backend {} still unreachable. Cause: {}", status.getHostPort(), checkResult.getHttpResponse()); + LOG.debug("backend {} still unreachable. Cause: {}", status.getHostPort(), checkResult.httpResponse()); } else { - LOG.warn("backend {} became unreachable. Cause: {}", status.getHostPort(), checkResult.getHttpResponse()); - reportBackendUnreachable(status.getHostPort(), checkResult.getEndTs(), checkResult.getHttpResponse()); + LOG.warn("backend {} became unreachable. Cause: {}", status.getHostPort(), checkResult.httpResponse()); + reportBackendUnreachable(status.getHostPort(), checkResult.endTs(), checkResult.httpResponse()); } } status.setLastProbe(checkResult); - if (status.isReportedAsUnreachable()) { - BACKEND_UPSTATUS_GAUGE.labels(bconf.host() + "_" + bconf.port()).set(0); - } else { - BACKEND_UPSTATUS_GAUGE.labels(bconf.host() + "_" + bconf.port()).set(1); - } + BACKEND_UPSTATUS_GAUGE + .labels(bconf.host() + "_" + bconf.port()) + .set(status.isReportedAsUnreachable() ? 0 : 1); } List toRemove = new ArrayList<>(); - for (EndpointKey key : backends.keySet()) { + for (final EndpointKey key : backends.keySet()) { boolean found = false; for (BackendConfiguration bconf : backendConfigurations) { if (bconf.hostPort().equals(key)) { @@ -188,31 +189,24 @@ public void run() { } } - public void reportBackendUnreachable(EndpointKey hostPort, long timestamp, String cause) { - BackendHealthStatus backend = getBackendStatus(hostPort); - backend.reportAsUnreachable(timestamp); + public void reportBackendUnreachable(final EndpointKey hostPort, final long timestamp, final String cause) { + getBackendStatus(hostPort).reportAsUnreachable(timestamp, cause); } - private BackendHealthStatus getBackendStatus(EndpointKey hostPort) { - BackendHealthStatus status = backends.computeIfAbsent(hostPort, BackendHealthStatus::new); - if (status == null) { - throw new RuntimeException("Unknown backend " + hostPort); - } - return status; + private BackendHealthStatus getBackendStatus(final EndpointKey hostPort) { + return backends.computeIfAbsent(hostPort, BackendHealthStatus::new); } - public void reportBackendReachable(EndpointKey hostPort) { - BackendHealthStatus backend = getBackendStatus(hostPort); - backend.reportAsReachable(); + public void reportBackendReachable(final EndpointKey hostPort) { + getBackendStatus(hostPort).reportAsReachable(); } public Map getBackendsSnapshot() { return Map.copyOf(backends); } - public boolean isAvailable(EndpointKey hostPort) { - BackendHealthStatus backend = getBackendStatus(hostPort); - return backend != null && backend.isAvailable(); + public boolean isAvailable(final EndpointKey hostPort) { + return getBackendStatus(hostPort).isAvailable(); } @VisibleForTesting diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthStatus.java b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthStatus.java index 202d78bb4..e0a6afe57 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthStatus.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthStatus.java @@ -19,6 +19,7 @@ */ package org.carapaceproxy.server.backends; +import java.sql.Timestamp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.carapaceproxy.core.EndpointKey; @@ -39,7 +40,7 @@ public class BackendHealthStatus { private BackendHealthCheck lastProbe; - public BackendHealthStatus(EndpointKey hostPort) { + public BackendHealthStatus(final EndpointKey hostPort) { this.hostPort = hostPort; } @@ -71,8 +72,8 @@ public void setReportedAsUnreachableTs(long reportedAsUnreachableTs) { this.reportedAsUnreachableTs = reportedAsUnreachableTs; } - void reportAsUnreachable(long timestamp) { - LOG.info("{}: reportAsUnreachable {}", hostPort, new java.sql.Timestamp(timestamp)); + void reportAsUnreachable(long timestamp, final String cause) { + LOG.info("{}: reportAsUnreachable {}, cause {}", hostPort, new Timestamp(timestamp), cause); reportedAsUnreachableTs = timestamp; reportedAsUnreachable = true; } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendConfiguration.java index e73bc127b..7fc42e453 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendConfiguration.java @@ -26,7 +26,7 @@ */ public record BackendConfiguration(String id, EndpointKey hostPort, String probePath) { - public BackendConfiguration(String id, String host, int port, String probePath) { + public BackendConfiguration(final String id, final String host, final int port, final String probePath) { this(id, new EndpointKey(host, port), probePath); } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendSelector.java b/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendSelector.java index 93d13f0f6..f157178b5 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendSelector.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/config/BackendSelector.java @@ -26,5 +26,5 @@ */ public interface BackendSelector { - public List selectBackends(String userId, String sessionId, String director); + List selectBackends(String userId, String sessionId, String director); } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/EndpointMapper.java b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/EndpointMapper.java index 916d437f8..14ef5df9d 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/EndpointMapper.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/EndpointMapper.java @@ -20,12 +20,17 @@ package org.carapaceproxy.server.mapper; import java.util.List; -import java.util.Map; +import java.util.Objects; +import java.util.SequencedMap; import org.carapaceproxy.SimpleHTTPResponse; import org.carapaceproxy.configstore.ConfigurationStore; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.core.ProxyRequest; +import org.carapaceproxy.core.ProxyRequestsManager; +import org.carapaceproxy.core.RuntimeServerConfiguration; import org.carapaceproxy.core.StaticContentsManager; +import org.carapaceproxy.server.backends.BackendHealthManager; +import org.carapaceproxy.server.certificates.DynamicCertificatesManager; import org.carapaceproxy.server.config.ActionConfiguration; import org.carapaceproxy.server.config.BackendConfiguration; import org.carapaceproxy.server.config.ConfigurationNotValidException; @@ -39,43 +44,94 @@ */ public abstract class EndpointMapper { - HttpProxyServer parent; - - public void setParent(HttpProxyServer parent) { - this.parent = parent; - } - - public abstract Map getBackends(); - + private HttpProxyServer parent; + + /** + * Get the pool of {@link BackendConfiguration backends} to choose from. + * + * @return a map where the key is the {@link BackendConfiguration#id() id of the backend}, + * and sorted according to configuration order + */ + public abstract SequencedMap getBackends(); + + /** + * Get all the available {@link RouteConfiguration routes}. + * + * @return a list of routes, sorted according to configuration order + */ public abstract List getRoutes(); + /** + * Get all the configured {@link ActionConfiguration actions}. + * + * @return a list of actions, sorted according to configuration order + */ public abstract List getActions(); + /** + * Get all the configured {@link DirectorConfiguration directors} for the {@link BackendConfiguration backends}. + * + * @return a list of directors, sorted according to configuration order + */ public abstract List getDirectors(); + /** + * Get all the {@link CustomHeader custom headers} that can be applied to the requests. + * + * @return a list of custom headers, sorted according to configuration order + */ public abstract List getHeaders(); + /** + * Process a request for a {@link ProxyRequestsManager}. + *

+ * According to the incoming request and the underlying configuration, + * it computes an {@link MapResult#getAction() action} to execute on a specific {@link MapResult#routeId route}. + * + * @param request the request of a resource to be proxied + * @return the result of the mapping process + * @see ProxyRequestsManager#processRequest(ProxyRequest) + */ public abstract MapResult map(ProxyRequest request); - public SimpleHTTPResponse mapPageNotFound(String routeId) { - return SimpleHTTPResponse.NOT_FOUND(StaticContentsManager.DEFAULT_NOT_FOUND); + public SimpleHTTPResponse mapPageNotFound(final String routeId) { + return SimpleHTTPResponse.notFound(StaticContentsManager.DEFAULT_NOT_FOUND); } - public SimpleHTTPResponse mapInternalError(String routeId) { - return SimpleHTTPResponse.INTERNAL_ERROR(StaticContentsManager.DEFAULT_INTERNAL_SERVER_ERROR); + public SimpleHTTPResponse mapInternalError(final String routeId) { + return SimpleHTTPResponse.internalError(StaticContentsManager.DEFAULT_INTERNAL_SERVER_ERROR); } - public SimpleHTTPResponse mapServiceUnavailableError(String routeId) { - return SimpleHTTPResponse.SERVICE_UNAVAILABLE(StaticContentsManager.DEFAULT_SERVICE_UNAVAILABLE_ERROR); + public SimpleHTTPResponse mapServiceUnavailableError(final String routeId) { + return SimpleHTTPResponse.serviceUnavailable(StaticContentsManager.DEFAULT_SERVICE_UNAVAILABLE_ERROR); } - public SimpleHTTPResponse mapMaintenanceMode(String routeId) { - return SimpleHTTPResponse.MAINTENANCE_MODE(StaticContentsManager.DEFAULT_MAINTENANCE_MODE_ERROR); + + public SimpleHTTPResponse mapMaintenanceMode(final String routeId) { + return SimpleHTTPResponse.internalError(StaticContentsManager.DEFAULT_MAINTENANCE_MODE_ERROR); } public SimpleHTTPResponse mapBadRequest() { - return SimpleHTTPResponse.BAD_REQUEST(StaticContentsManager.DEFAULT_BAD_REQUEST); + return SimpleHTTPResponse.badRequest(StaticContentsManager.DEFAULT_BAD_REQUEST); } public abstract void configure(ConfigurationStore properties) throws ConfigurationNotValidException; + public final void setParent(final HttpProxyServer parent) { + this.parent = parent; + } + + protected final DynamicCertificatesManager getDynamicCertificatesManager() { + Objects.requireNonNull(parent); + return parent.getDynamicCertificatesManager(); + } + + protected final BackendHealthManager getBackendHealthManager() { + Objects.requireNonNull(parent); + return parent.getBackendHealthManager(); + } + + protected final RuntimeServerConfiguration getCurrentConfiguration() { + Objects.requireNonNull(parent); + return parent.getCurrentConfiguration(); + } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/MapResult.java b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/MapResult.java index 882ed1803..addbfb2b6 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/MapResult.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/MapResult.java @@ -78,16 +78,17 @@ public enum Action { public static final String REDIRECT_PROTO_HTTPS = "https"; public static final String REDIRECT_PROTO_HTTP = "http"; - public String host; - public int port; - public Action action; - public String routeId; - public int errorCode; - public String resource; - public List customHeaders; - public String redirectLocation; - public String redirectProto; - public String redirectPath; + // todo we don't actually want to have these nullable: probably we should have different classes for each case + private String host; + private int port; + private Action action; + private String routeId; + private int errorCode; + private String resource; + private List customHeaders; + private String redirectLocation; + private String redirectProto; + private String redirectPath; public static MapResult notFound(String routeId) { return MapResult.builder() @@ -122,6 +123,4 @@ public static MapResult badRequest() { .action(Action.BAD_REQUEST) .build(); } - - } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/RandomBackendSelector.java b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/RandomBackendSelector.java new file mode 100644 index 000000000..6dfbfb997 --- /dev/null +++ b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/RandomBackendSelector.java @@ -0,0 +1,59 @@ +package org.carapaceproxy.server.mapper; + +import static org.carapaceproxy.server.config.DirectorConfiguration.ALL_BACKENDS; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.random.RandomGenerator; +import org.carapaceproxy.server.config.BackendSelector; +import org.carapaceproxy.server.config.DirectorConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The selector chooses an available backend randomly. + * This means that backend preference is determined by shuffling the resulting list. + * + * @see Collections#shuffle(List, RandomGenerator) + * @see SecureRandom + */ +class RandomBackendSelector implements BackendSelector { + private static final Logger LOG = LoggerFactory.getLogger(RandomBackendSelector.class); + private static final RandomGenerator RANDOM = new SecureRandom(); + + private final List allBackendIds; + private final Map directors; + + public RandomBackendSelector(final List allBackendIds, final Map directors) { + this.allBackendIds = allBackendIds; + this.directors = directors; + } + + @Override + public List selectBackends(final String userId, final String sessionId, final String director) { + if (!directors.containsKey(director)) { + LOG.error("Director \"{}\" not configured, while handling request userId={} sessionId={}", director, userId, sessionId); + return List.of(); + } + final DirectorConfiguration directorConfig = directors.get(director); + if (directorConfig.getBackends().contains(ALL_BACKENDS)) { + return shuffleCopy(allBackendIds); + } + return shuffleCopy(directorConfig.getBackends()); + } + + public List shuffleCopy(final List ids) { + if (ids.isEmpty()) { + return List.of(); + } + if (ids.size() == 1) { + return List.copyOf(ids); + } + final List result = new ArrayList<>(ids); + Collections.shuffle(result, RANDOM); + return List.copyOf(result); + } + +} diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/StandardEndpointMapper.java b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/StandardEndpointMapper.java index e6486010a..beb32d277 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/mapper/StandardEndpointMapper.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/mapper/StandardEndpointMapper.java @@ -23,15 +23,16 @@ import static org.carapaceproxy.core.StaticContentsManager.DEFAULT_MAINTENANCE_MODE_ERROR; import static org.carapaceproxy.core.StaticContentsManager.DEFAULT_NOT_FOUND; import static org.carapaceproxy.core.StaticContentsManager.IN_MEMORY_RESOURCE; -import static org.carapaceproxy.server.config.DirectorConfiguration.ALL_BACKENDS; import io.netty.handler.codec.http.HttpResponseStatus; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.SequencedMap; import java.util.Set; import org.carapaceproxy.SimpleHTTPResponse; import org.carapaceproxy.configstore.ConfigurationStore; @@ -58,20 +59,28 @@ public class StandardEndpointMapper extends EndpointMapper { public static final String ACME_CHALLENGE_ROUTE_ACTION_ID = "acme-challenge"; - private final Map backends = new HashMap<>(); // wiped out whenever a new configuration is applied + private static final String DEFAULT_NOT_FOUND_ACTION = "not-found"; + private static final String DEFAULT_INTERNAL_ERROR_ACTION = "internal-error"; + private static final String DEFAULT_MAINTENANCE_ACTION = "maintenance"; + private static final String DEFAULT_BAD_REQUEST_ACTION = "bad-request"; + private static final String DEFAULT_SERVICE_UNAVAILABLE_ACTION = "service-unavailable"; + // The map is wiped out whenever a new configuration is applied + private final SequencedMap backends = new LinkedHashMap<>(); private final Map directors = new HashMap<>(); private final List allbackendids = new ArrayList<>(); private final List routes = new ArrayList<>(); private final Map actions = new HashMap<>(); public final Map headers = new HashMap<>(); private final BackendSelector backendSelector; - private String defaultNotFoundAction = "not-found"; - private String defaultInternalErrorAction = "internal-error"; + + private String defaultNotFoundAction = DEFAULT_NOT_FOUND_ACTION; + private String defaultInternalErrorAction = DEFAULT_INTERNAL_ERROR_ACTION; + private String defaultMaintenanceAction = DEFAULT_MAINTENANCE_ACTION; + private String defaultBadRequestAction = DEFAULT_BAD_REQUEST_ACTION; + private String defaultServiceUnavailable = DEFAULT_SERVICE_UNAVAILABLE_ACTION; + private String forceDirectorParameter = "x-director"; private String forceBackendParameter = "x-backend"; - private String defaultMaintenanceAction = "maintenance"; - private String defaultBadRequestAction = "bad-request"; - private String defaultServiceUnavailable = "service-unavailable"; private static final Logger LOG = LoggerFactory.getLogger(StandardEndpointMapper.class); private static final String ACME_CHALLENGE_URI_PATTERN = "/\\.well-known/acme-challenge/"; @@ -86,31 +95,7 @@ public StandardEndpointMapper(BackendSelector backendSelector) { } public StandardEndpointMapper() { - this.backendSelector = new RandomBackendSelector(); - } - - private final class RandomBackendSelector implements BackendSelector { - - @Override - public List selectBackends(String userId, String sessionId, String director) { - DirectorConfiguration directorConfig = directors.get(director); - if (directorConfig == null) { - LOG.error("Director \"{}\" not configured, while handling request + userId={} sessionId={}", director, userId, sessionId); - return Collections.emptyList(); - } - if (directorConfig.getBackends().contains(ALL_BACKENDS)) { - ArrayList result = new ArrayList<>(allbackendids); - Collections.shuffle(result); - return result; - } else if (directorConfig.getBackends().size() == 1) { - return directorConfig.getBackends(); - } else { - ArrayList result = new ArrayList<>(directorConfig.getBackends()); - Collections.shuffle(result); - return result; - } - } - + this.backendSelector = new RandomBackendSelector(allbackendids, directors); } @Override @@ -122,41 +107,44 @@ public MapResult map(ProxyRequest request) { LOG.trace("Request {} header host is null or empty", request.getUri()); } return MapResult.badRequest(); - } else if (!request.isValidHostAndPort(request.getRequestHostname())) { //Invalid header host + } + if (!request.isValidHostAndPort(request.getRequestHostname())) { //Invalid header host if (LOG.isTraceEnabled()) { LOG.trace("Invalid header host {} for request {}", request.getRequestHostname(), request.getUri()); } return MapResult.badRequest(); } - for (RouteConfiguration route : routes) { + for (final RouteConfiguration route : routes) { if (!route.isEnabled()) { continue; } - - boolean matchResult = route.matches(request); + final boolean matchResult = route.matches(request); if (LOG.isTraceEnabled()) { LOG.trace("route {}, map {} -> {}", route.getId(), request.getUri(), matchResult); } + if (!matchResult) { + continue; + } - if (matchResult) { - if (parent.getCurrentConfiguration().isMaintenanceModeEnabled()) { - if (LOG.isTraceEnabled()) { - LOG.trace("Maintenance mode is enable: request uri: {}", request.getUri()); - } - return MapResult.maintenanceMode(route.getId()); + if (getCurrentConfiguration().isMaintenanceModeEnabled()) { + if (LOG.isTraceEnabled()) { + LOG.trace("Maintenance mode is enable: request uri: {}", request.getUri()); } + return MapResult.maintenanceMode(route.getId()); + } - ActionConfiguration action = actions.get(route.getAction()); - if (action == null) { - LOG.info("no action \"{}\" -> not-found for {}, valid {}", route.getAction(), request.getUri(), actions.keySet()); - return MapResult.internalError(route.getId()); - } - if (ActionConfiguration.TYPE_REDIRECT.equals(action.getType())) { + final ActionConfiguration action = actions.get(route.getAction()); + if (action == null) { + LOG.info("no action \"{}\" -> not-found for {}, valid {}", route.getAction(), request.getUri(), actions.keySet()); + return MapResult.internalError(route.getId()); + } + switch (action.getType()) { + case ActionConfiguration.TYPE_REDIRECT -> { return MapResult.builder() .host(action.getRedirectHost()) .port(action.getRedirectPort()) - .action(MapResult.Action.REDIRECT) + .action(Action.REDIRECT) .routeId(route.getId()) .redirectLocation(action.getRedirectLocation()) .redirectProto(action.getRedirectProto()) @@ -165,112 +153,105 @@ public MapResult map(ProxyRequest request) { .customHeaders(action.getCustomHeaders()) .build(); } - if (ActionConfiguration.TYPE_STATIC.equals(action.getType())) { + case ActionConfiguration.TYPE_STATIC -> { return MapResult.builder() - .action(MapResult.Action.STATIC) + .action(Action.STATIC) .routeId(route.getId()) .resource(action.getFile()) .errorCode(action.getErrorCode()) .customHeaders(action.getCustomHeaders()) .build(); } - if (ActionConfiguration.TYPE_ACME_CHALLENGE.equals(action.getType())) { + case ActionConfiguration.TYPE_ACME_CHALLENGE -> { String tokenName = request.getUri().replaceFirst(".*" + ACME_CHALLENGE_URI_PATTERN, ""); - String tokenData = parent.getDynamicCertificatesManager().getChallengeToken(tokenName); + String tokenData = getDynamicCertificatesManager().getChallengeToken(tokenName); if (tokenData == null) { return MapResult.notFound(route.getId()); } return MapResult.builder() - .action(MapResult.Action.ACME_CHALLENGE) + .action(Action.ACME_CHALLENGE) .routeId(route.getId()) .resource(IN_MEMORY_RESOURCE + tokenData) .errorCode(action.getErrorCode()) .build(); } - UrlEncodedQueryString queryString = request.getQueryString(); - String director = action.getDirector(); - String forceBackendParameterValue = queryString.get(forceBackendParameter); - - final List selectedBackends; - if (forceBackendParameterValue != null) { - LOG.info("forcing backend = {} for {}", forceBackendParameterValue, request.getUri()); - selectedBackends = Collections.singletonList(forceBackendParameterValue); + } + final UrlEncodedQueryString queryString = request.getQueryString(); + + final List selectedBackends; + if (queryString.contains(forceBackendParameter)) { + final String forceBackendParameterValue = queryString.get(forceBackendParameter); + LOG.info("forcing backend = {} for {}", forceBackendParameterValue, request.getUri()); + selectedBackends = List.of(forceBackendParameterValue); + LOG.trace("selected {} backends for {}", selectedBackends, request.getUri()); + } else { + final String director; + if (queryString.contains(forceDirectorParameter)) { + director = queryString.get(forceDirectorParameter); + LOG.info("forcing director = {} for {}", director, request.getUri()); } else { - String forceDirectorParameterValue = queryString.get(forceDirectorParameter); - if (forceDirectorParameterValue != null) { - director = forceDirectorParameterValue; - LOG.info("forcing director = {} for {}", director, request.getUri()); - } - selectedBackends = backendSelector.selectBackends(request.getUserId(), request.getSessionId(), director); + director = action.getDirector(); } - + selectedBackends = backendSelector.selectBackends(request.getUserId(), request.getSessionId(), director); LOG.trace("selected {} backends for {}, director is {}", selectedBackends, request.getUri(), director); - for (String backendId : selectedBackends) { - Action selectedAction; - switch (action.getType()) { - case ActionConfiguration.TYPE_PROXY: - selectedAction = MapResult.Action.PROXY; - break; - case ActionConfiguration.TYPE_CACHE: - selectedAction = MapResult.Action.CACHE; - break; - default: - return MapResult.internalError(route.getId()); - } + } - BackendConfiguration backend = this.backends.get(backendId); - if (backend != null && parent.getBackendHealthManager().isAvailable(backend.hostPort())) { - List customHeaders = action.getCustomHeaders(); - if (this.debuggingHeaderEnabled) { - customHeaders = new ArrayList<>(customHeaders); - String routingPath = route.getId() + ";" - + action.getId() + ";" - + action.getDirector() + ";" - + backendId; - customHeaders.add(new CustomHeader(DEBUGGING_HEADER_ID, debuggingHeaderName, routingPath, HeaderMode.ADD)); - } - return MapResult.builder() - .host(backend.host()) - .port(backend.port()) - .action(selectedAction) - .routeId(route.getId()) - .customHeaders(customHeaders) - .build(); + for (final String backendId : selectedBackends) { + final Action selectedAction; + switch (action.getType()) { + case ActionConfiguration.TYPE_PROXY -> selectedAction = Action.PROXY; + case ActionConfiguration.TYPE_CACHE -> selectedAction = Action.CACHE; + default -> { + return MapResult.internalError(route.getId()); } } - // none of selected backends available - // return service unavailable if all backend is unavailable - if (!selectedBackends.isEmpty()) { - return MapResult.serviceUnavailable(route.getId()); + + final BackendConfiguration backend = this.backends.get(backendId); + if (backend != null && getBackendHealthManager().isAvailable(backend.hostPort())) { + List customHeaders = action.getCustomHeaders(); + if (this.debuggingHeaderEnabled) { + customHeaders = new ArrayList<>(customHeaders); + String routingPath = route.getId() + ";" + + action.getId() + ";" + + action.getDirector() + ";" + + backendId; + customHeaders.add(new CustomHeader(DEBUGGING_HEADER_ID, debuggingHeaderName, routingPath, HeaderMode.ADD)); + } + return MapResult.builder() + .host(backend.host()) + .port(backend.port()) + .action(selectedAction) + .routeId(route.getId()) + .customHeaders(customHeaders) + .build(); } } + // none of selected backends available + // return service unavailable if all backend is unavailable + if (!selectedBackends.isEmpty()) { + return MapResult.serviceUnavailable(route.getId()); + } } // no one route matched return MapResult.notFound(MapResult.NO_ROUTE); } - private ActionConfiguration getErrorActionConfiguration(String routeId, String defaultAction) { + private ActionConfiguration getErrorActionConfiguration(final String routeId, final String defaultAction) { // Attempt to find a route-specific configuration first - Optional config = routes.stream() + return routes.stream() .filter(r -> r.getId().equalsIgnoreCase(routeId)) - .findFirst(); - if (config.isPresent()) { - String action = config.get().getErrorAction(); - if (action != null && actions.containsKey(action)) { - return actions.get(action); - } - } - // If no route-specific configuration or action is found, fallback to the global default - if (defaultAction != null && actions.containsKey(defaultAction)) { - return actions.get(defaultAction); - } - // Return null if no appropriate action configuration is found - return null; + .findFirst() + .map(RouteConfiguration::getErrorAction) + .filter(actions::containsKey) + // If no route-specific configuration or action is found, fallback to the global default + .or(() -> Optional.of(defaultAction)) + .map(actions::get) + // Return null if no appropriate action configuration is found + .orElse(null); } - @Override - public SimpleHTTPResponse mapInternalError(String routeid) { + public SimpleHTTPResponse mapInternalError(final String routeid) { ActionConfiguration errorAction = getErrorActionConfiguration(routeid, defaultInternalErrorAction); if (errorAction != null) { return new SimpleHTTPResponse(errorAction.getErrorCode(), errorAction.getFile(), errorAction.getCustomHeaders()); @@ -280,7 +261,7 @@ public SimpleHTTPResponse mapInternalError(String routeid) { } @Override - public SimpleHTTPResponse mapServiceUnavailableError(String routeId) { + public SimpleHTTPResponse mapServiceUnavailableError(final String routeId) { ActionConfiguration errorAction = getErrorActionConfiguration(routeId, defaultServiceUnavailable); if (errorAction != null) { return new SimpleHTTPResponse(errorAction.getErrorCode(), errorAction.getFile(), errorAction.getCustomHeaders()); @@ -290,7 +271,7 @@ public SimpleHTTPResponse mapServiceUnavailableError(String routeId) { } @Override - public SimpleHTTPResponse mapMaintenanceMode(String routeId) { + public SimpleHTTPResponse mapMaintenanceMode(final String routeId) { ActionConfiguration maintenanceAction = null; // custom for route Optional config = routes.stream().filter(r -> r.getId().equalsIgnoreCase(routeId)).findFirst(); @@ -301,7 +282,7 @@ public SimpleHTTPResponse mapMaintenanceMode(String routeId) { } } // custom global - if (maintenanceAction == null && defaultMaintenanceAction != null) { + if (maintenanceAction == null) { maintenanceAction = actions.get(defaultMaintenanceAction); } if (maintenanceAction != null) { @@ -314,24 +295,20 @@ public SimpleHTTPResponse mapMaintenanceMode(String routeId) { @Override public SimpleHTTPResponse mapBadRequest() { // custom global - if (defaultBadRequestAction != null) { - ActionConfiguration errorAction = actions.get(defaultBadRequestAction); - if (errorAction != null) { - return new SimpleHTTPResponse(errorAction.getErrorCode(), errorAction.getFile(), errorAction.getCustomHeaders()); - } + ActionConfiguration errorAction = actions.get(defaultBadRequestAction); + if (errorAction != null) { + return new SimpleHTTPResponse(errorAction.getErrorCode(), errorAction.getFile(), errorAction.getCustomHeaders()); } // fallback return super.mapBadRequest(); } @Override - public SimpleHTTPResponse mapPageNotFound(String routeId) { + public SimpleHTTPResponse mapPageNotFound(final String routeId) { // custom global - if (defaultNotFoundAction != null) { - ActionConfiguration errorAction = actions.get(defaultNotFoundAction); - if (errorAction != null) { - return new SimpleHTTPResponse(errorAction.getErrorCode(), errorAction.getFile(), errorAction.getCustomHeaders()); - } + ActionConfiguration errorAction = actions.get(defaultNotFoundAction); + if (errorAction != null) { + return new SimpleHTTPResponse(errorAction.getErrorCode(), errorAction.getFile(), errorAction.getCustomHeaders()); } // fallback return super.mapPageNotFound(routeId); @@ -339,12 +316,11 @@ public SimpleHTTPResponse mapPageNotFound(String routeId) { @Override public void configure(ConfigurationStore properties) throws ConfigurationNotValidException { - addAction(new ActionConfiguration("proxy-all", ActionConfiguration.TYPE_PROXY, DirectorConfiguration.DEFAULT, null, -1)); addAction(new ActionConfiguration("cache-if-possible", ActionConfiguration.TYPE_CACHE, DirectorConfiguration.DEFAULT, null, -1)); - addAction(new ActionConfiguration("not-found", ActionConfiguration.TYPE_STATIC, null, DEFAULT_NOT_FOUND, 404)); - addAction(new ActionConfiguration("internal-error", ActionConfiguration.TYPE_STATIC, null, DEFAULT_INTERNAL_SERVER_ERROR, 500)); - addAction(new ActionConfiguration("maintenance", ActionConfiguration.TYPE_STATIC, null, DEFAULT_MAINTENANCE_MODE_ERROR, 500)); + addAction(new ActionConfiguration(DEFAULT_NOT_FOUND_ACTION, ActionConfiguration.TYPE_STATIC, null, DEFAULT_NOT_FOUND, 404)); + addAction(new ActionConfiguration(DEFAULT_INTERNAL_ERROR_ACTION, ActionConfiguration.TYPE_STATIC, null, DEFAULT_INTERNAL_SERVER_ERROR, 500)); + addAction(new ActionConfiguration(DEFAULT_MAINTENANCE_ACTION, ActionConfiguration.TYPE_STATIC, null, DEFAULT_MAINTENANCE_MODE_ERROR, 500)); // Route+Action configuration for Let's Encrypt ACME challenging addAction(new ActionConfiguration( @@ -356,21 +332,26 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali new RegexpRequestMatcher(ProxyRequest.PROPERTY_URI, ".*" + ACME_CHALLENGE_URI_PATTERN + ".*") )); - this.defaultNotFoundAction = properties.getString("default.action.notfound", "not-found"); + this.defaultNotFoundAction = properties.getString("default.action.notfound", DEFAULT_NOT_FOUND_ACTION); LOG.info("configured default.action.notfound={}", defaultNotFoundAction); - this.defaultInternalErrorAction = properties.getString("default.action.internalerror", "internal-error"); + + this.defaultInternalErrorAction = properties.getString("default.action.internalerror", DEFAULT_INTERNAL_ERROR_ACTION); LOG.info("configured default.action.internalerror={}", defaultInternalErrorAction); - this.defaultMaintenanceAction = properties.getString("default.action.maintenance", "maintenance"); + + this.defaultMaintenanceAction = properties.getString("default.action.maintenance", DEFAULT_MAINTENANCE_ACTION); LOG.info("configured default.action.maintenance={}", defaultMaintenanceAction); - this.defaultBadRequestAction = properties.getString("default.action.badrequest", "bad-request"); + + this.defaultBadRequestAction = properties.getString("default.action.badrequest", DEFAULT_BAD_REQUEST_ACTION); LOG.info("configured default.action.badrequest={}", defaultBadRequestAction); - this.defaultServiceUnavailable = properties.getString("default.action.serviceunavailable", "service-unavailable"); + + this.defaultServiceUnavailable = properties.getString("default.action.serviceunavailable", DEFAULT_SERVICE_UNAVAILABLE_ACTION); LOG.info("configured default.action.serviceunavailable={}", defaultServiceUnavailable); this.forceDirectorParameter = properties.getString("mapper.forcedirector.parameter", forceDirectorParameter); LOG.info("configured mapper.forcedirector.parameter={}", forceDirectorParameter); this.forceBackendParameter = properties.getString("mapper.forcebackend.parameter", forceBackendParameter); LOG.info("configured mapper.forcebackend.parameter={}", forceBackendParameter); - // To add custom debugging header for request choosen mapping-path + + // To add custom debugging header for request chosen mapping-path this.debuggingHeaderEnabled = properties.getBoolean("mapper.debug", false); LOG.info("configured mapper.debug={}", debuggingHeaderEnabled); this.debuggingHeaderName = properties.getString("mapper.debug.name", DEBUGGING_HEADER_DEFAULT_NAME); @@ -401,14 +382,14 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali String id = properties.getString(prefix + "id", ""); boolean enabled = properties.getBoolean(prefix + "enabled", false); if (!id.isEmpty() && enabled) { - String action = properties.getString(prefix + "type", ActionConfiguration.TYPE_PROXY); + String actionType = properties.getString(prefix + "type", ActionConfiguration.TYPE_PROXY); String file = properties.getString(prefix + "file", ""); String director = properties.getString(prefix + "director", DirectorConfiguration.DEFAULT); int code = properties.getInt(prefix + "code", -1); // Headers List customHeaders = new ArrayList<>(); Set usedIds = new HashSet<>(); - final var headersIds = properties.getValues(prefix + "headers"); + final Set headersIds = properties.getValues(prefix + "headers"); for (String headerId : headersIds) { if (usedIds.contains(headerId)) { throw new ConfigurationNotValidException("while configuring action '" + id + "': header '" + headerId + "' duplicated"); @@ -423,26 +404,29 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali } } - ActionConfiguration _action = new ActionConfiguration(id, action, director, file, code).setCustomHeaders(customHeaders); + ActionConfiguration action = new ActionConfiguration(id, actionType, director, file, code).setCustomHeaders(customHeaders); // Action of type REDIRECT String redirectLocation = properties.getString(prefix + "redirect.location", ""); - _action.setRedirectLocation(redirectLocation); + action.setRedirectLocation(redirectLocation); if (redirectLocation.isEmpty()) { - _action.setRedirectProto(properties.getString(prefix + "redirect.proto", "")); - _action.setRedirectHost(properties.getString(prefix + "redirect.host", "")); - _action.setRedirectPort(properties.getInt(prefix + "redirect.port", -1)); - _action.setRedirectPath(properties.getString(prefix + "redirect.path", "")); - if (action.equals(ActionConfiguration.TYPE_REDIRECT) && _action.getRedirectProto().isEmpty() && _action.getRedirectHost().isEmpty() - && _action.getRedirectPort() == -1 && _action.getRedirectPath().isEmpty()) { + action.setRedirectProto(properties.getString(prefix + "redirect.proto", "")); + action.setRedirectHost(properties.getString(prefix + "redirect.host", "")); + action.setRedirectPort(properties.getInt(prefix + "redirect.port", -1)); + action.setRedirectPath(properties.getString(prefix + "redirect.path", "")); + if (actionType.equals(ActionConfiguration.TYPE_REDIRECT) + && action.getRedirectProto().isEmpty() + && action.getRedirectHost().isEmpty() + && action.getRedirectPort() == -1 + && action.getRedirectPath().isEmpty()) { throw new ConfigurationNotValidException("while configuring action '" + id + "': at least redirect.location or redirect.proto|.host|.port|.path have to be defined" ); } } - addAction(_action); - LOG.info("configured action {} type={} enabled:{} headers:{} redirect location:{} redirect proto:{} redirect host:{} redirect port:{} redirect path:{}", id, action, enabled, headersIds, redirectLocation, _action.getRedirectProto(), _action.getRedirectHost(), _action.getRedirectPort(), _action.getRedirectPath()); + addAction(action); + LOG.info("configured action {} type={} enabled:{} headers:{} redirect location:{} redirect proto:{} redirect host:{} redirect port:{} redirect path:{}", id, actionType, enabled, headersIds, redirectLocation, action.getRedirectProto(), action.getRedirectHost(), action.getRedirectPort(), action.getRedirectPath()); } } @@ -516,7 +500,8 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali } } config.setErrorAction(errorAction); - //Maintenance action + + // Maintenance action String maintenanceAction = properties.getString(prefix + "maintenanceaction", ""); if (!maintenanceAction.isEmpty()) { ActionConfiguration defined = actions.get(maintenanceAction); @@ -535,14 +520,13 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali } private void addHeader(String id, String name, String value, String mode) throws ConfigurationNotValidException { - final HeaderMode _mode = switch (mode) { + final HeaderMode headerMode = switch (mode) { case "set" -> HeaderMode.SET; case "add" -> HeaderMode.ADD; case "remove" -> HeaderMode.REMOVE; default -> throw new ConfigurationNotValidException("invalid value of mode " + mode + " for header " + id); }; - - if (headers.put(id, new CustomHeader(id, name, value, _mode)) != null) { + if (headers.put(id, new CustomHeader(id, name, value, headerMode)) != null) { throw new ConfigurationNotValidException("header " + id + " is already configured"); } } @@ -574,8 +558,8 @@ public void addRoute(RouteConfiguration route) throws ConfigurationNotValidExcep } @Override - public Map getBackends() { - return backends; + public SequencedMap getBackends() { + return Collections.unmodifiableSequencedMap(backends); } @Override diff --git a/carapace-server/src/main/java/org/carapaceproxy/utils/StringUtils.java b/carapace-server/src/main/java/org/carapaceproxy/utils/StringUtils.java index 80e4e23a3..9019fef6b 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/utils/StringUtils.java +++ b/carapace-server/src/main/java/org/carapaceproxy/utils/StringUtils.java @@ -25,7 +25,7 @@ */ public class StringUtils { - private static String htmlEncodeCharacters(String s) { + private static String htmlEncodeCharacters(final String s) { StringBuilder res = new StringBuilder(); for (char c : s.toCharArray()) { switch (c) { @@ -48,7 +48,7 @@ private static String htmlEncodeCharacters(String s) { return res.toString(); } - public static String htmlEncode(String s) { + public static String htmlEncode(final String s) { if (s == null || s.isEmpty()) { return s; } diff --git a/carapace-server/src/test/java/org/carapaceproxy/BigUploadTest.java b/carapace-server/src/test/java/org/carapaceproxy/BigUploadTest.java index 014ae1e67..e41e2ad85 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/BigUploadTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/BigUploadTest.java @@ -35,7 +35,6 @@ import java.util.concurrent.Executors; import java.util.function.Supplier; import org.apache.commons.io.IOUtils; -import org.carapaceproxy.core.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.utils.TestEndpointMapper; import org.junit.Rule; @@ -172,7 +171,6 @@ public void testConnectionResetByPeerDuringWriteToEndpoint() throws Exception { mockServer.start(); TestEndpointMapper mapper = new TestEndpointMapper("localhost", mockServer.getPort()); - EndpointKey key = new EndpointKey("localhost", mockServer.getPort()); int size = 20_000_000; @@ -203,16 +201,16 @@ public void testConnectionResetByPeerDuringWriteToEndpoint() throws Exception { @Test public void testBlockingServerWorks() throws Exception { - - try (SimpleBlockingTcpServer mockServer = - new SimpleBlockingTcpServer(() -> { - return new StaticResponseHandler("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nit works!\r\n".getBytes(StandardCharsets.US_ASCII)); - })) { - + SimpleBlockingTcpServer mockServer = new SimpleBlockingTcpServer(() -> new StaticResponseHandler(""" + HTTP/1.1 200 OK\r + Content-Type: text/plain\r + \r + it works!\r + """.getBytes(StandardCharsets.US_ASCII))); + try (mockServer) { mockServer.start(); TestEndpointMapper mapper = new TestEndpointMapper("localhost", mockServer.getPort()); - EndpointKey key = new EndpointKey("localhost", mockServer.getPort()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); diff --git a/carapace-server/src/test/java/org/carapaceproxy/ConcurrentClientsTest.java b/carapace-server/src/test/java/org/carapaceproxy/ConcurrentClientsTest.java index 25ae19096..207161a3b 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/ConcurrentClientsTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/ConcurrentClientsTest.java @@ -35,7 +35,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.io.IOUtils; -import org.carapaceproxy.core.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.utils.TestEndpointMapper; import org.junit.Rule; @@ -64,11 +63,10 @@ public void test() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); int size = 100; int concurrentClients = 4; - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); diff --git a/carapace-server/src/test/java/org/carapaceproxy/DatabaseConfigurationTest.java b/carapace-server/src/test/java/org/carapaceproxy/DatabaseConfigurationTest.java index 7b3d35033..130fa5c28 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/DatabaseConfigurationTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/DatabaseConfigurationTest.java @@ -19,17 +19,18 @@ under the License. */ + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.File; -import org.carapaceproxy.core.HttpProxyServer; import java.util.Properties; import org.carapaceproxy.configstore.HerdDBConfigurationStore; import org.carapaceproxy.configstore.PropertiesConfigurationStore; +import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.server.filters.RegexpMapUserIdFilter; import org.carapaceproxy.server.filters.XForwardedForRequestFilter; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; diff --git a/carapace-server/src/test/java/org/carapaceproxy/RawClientTest.java b/carapace-server/src/test/java/org/carapaceproxy/RawClientTest.java index 6058f1f6f..cfe660a5f 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/RawClientTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/RawClientTest.java @@ -71,7 +71,6 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -147,7 +146,7 @@ public void clientsKeepAliveSimpleTest() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); try (RawHttpClient client = new RawHttpClient("localhost", port)) { @@ -193,9 +192,8 @@ public void downloadSmallPayloadsTest() throws Exception { .withBody("a"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); @@ -234,7 +232,7 @@ public void testManyInflightRequests() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); assertTrue(port > 0); @@ -246,68 +244,68 @@ public void testManyInflightRequests() throws Exception { clients.add(client); } - int i = 0; for (RawHttpClient client : clients) { client.close(); - i++; } } } @Test public void testKeepAliveTimeout() throws Exception { - RawHttpServer httpServer = new RawHttpServer(new HttpServlet() { + final RawHttpServer httpServer = new RawHttpServer(new HttpServlet() { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("it works !!"); } }); - httpServer.setIdleTimeout(5); - int httpServerPort = httpServer.start(); - - TestEndpointMapper mapper = new TestEndpointMapper("localhost", httpServerPort); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { - server.start(); - int port = server.getLocalPort(); - assertTrue(port > 0); + try (httpServer) { + httpServer.setIdleTimeout(5); + int httpServerPort = httpServer.start(); + + TestEndpointMapper mapper = new TestEndpointMapper("localhost", httpServerPort); + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { + server.start(); + int port = server.getLocalPort(); + assertTrue(port > 0); - try (RawHttpClient client = new RawHttpClient("localhost", port)) { - for (int j = 0; j < 2; j++) { - RawHttpClient.HttpResponse res = client.get("/index.html"); - String resp = res.getBodyString(); - System.out.println("RESP: " + resp + "; HEADERS: " + String.join("; ", res.getHeaderLines())); - Thread.sleep(10_000); + try (RawHttpClient client = new RawHttpClient("localhost", port)) { + for (int j = 0; j < 2; j++) { + RawHttpClient.HttpResponse res = client.get("/index.html"); + String resp = res.getBodyString(); + System.out.println("RESP: " + resp + "; HEADERS: " + String.join("; ", res.getHeaderLines())); + Thread.sleep(10_000); + } + } catch (Exception e) { + System.out.println("EXCEPTION: " + e); } - } catch (Exception e) { - System.out.println("EXCEPTION: " + e); } } } @Test public void testEmptyDataFromServer() throws Exception { - - RawHttpServer httpServer = new RawHttpServer(new HttpServlet() { + final RawHttpServer httpServer = new RawHttpServer(new HttpServlet() { public void doGet(HttpServletRequest request, HttpServletResponse response) { } }); - int httpServerPort = httpServer.start(); - - TestEndpointMapper mapper = new TestEndpointMapper("localhost", httpServerPort); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { - server.start(); - int port = server.getLocalPort(); - assertTrue(port > 0); + try (httpServer) { + int httpServerPort = httpServer.start(); + TestEndpointMapper mapper = new TestEndpointMapper("localhost", httpServerPort); + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { + server.start(); + int port = server.getLocalPort(); + assertTrue(port > 0); - try (RawHttpClient client = new RawHttpClient("localhost", port)) { - for (int j = 0; j < 2; j++) { - RawHttpClient.HttpResponse res = client.get("/index.html"); - String resp = res.getBodyString(); - System.out.println("RESP: " + resp + "; HEADERS: " + String.join("; ", res.getHeaderLines())); + try (RawHttpClient client = new RawHttpClient("localhost", port)) { + for (int j = 0; j < 2; j++) { + RawHttpClient.HttpResponse res = client.get("/index.html"); + String resp = res.getBodyString(); + System.out.println("RESP: " + resp + "; HEADERS: " + String.join("; ", res.getHeaderLines())); + } + } catch (Exception e) { + System.out.println("EXCEPTION: " + e); } - } catch (Exception e) { - System.out.println("EXCEPTION: " + e); } } } @@ -315,12 +313,13 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) { @Test public void testServerRequestContinue() throws Exception { AtomicBoolean responseEnabled = new AtomicBoolean(); - try (DummyServer server = new DummyServer("localhost", 8086, responseEnabled)) { + final int dummyServerPort = 8086; + try (DummyServer server = new DummyServer("localhost", dummyServerPort, responseEnabled)) { - TestEndpointMapper mapper = new TestEndpointMapper("localhost", 8086); + TestEndpointMapper mapper = new TestEndpointMapper("localhost", dummyServerPort); ExecutorService ex = Executors.newFixedThreadPool(2); - List futures = new ArrayList<>(); + List> futures = new ArrayList<>(); try (HttpProxyServer proxy = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { ConnectionPoolConfiguration defaultConnectionPool = proxy.getCurrentConfiguration().getDefaultConnectionPool(); @@ -396,7 +395,7 @@ public void testServerRequestContinue() throws Exception { } })); } finally { - for (Future future : futures) { + for (Future future : futures) { try { future.get(); } catch (InterruptedException | ExecutionException e) { @@ -434,15 +433,14 @@ public DummyServer(String host, int port, AtomicBoolean responseEnabled) throws public void initChannel(SocketChannel channel) { channel.pipeline().addLast(new HttpRequestDecoder()); channel.pipeline().addLast(new HttpResponseEncoder()); - channel.pipeline().addLast(new SimpleChannelInboundHandler() { + channel.pipeline().addLast(new SimpleChannelInboundHandler<>() { private boolean keepAlive; private boolean continueRequest; @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof HttpRequest) { - HttpRequest request = (HttpRequest) msg; + if (msg instanceof final HttpRequest request) { System.out.println("[DummyServer] HttpRequest: " + request); keepAlive = HttpUtil.isKeepAlive(request); continueRequest = HttpUtil.is100ContinueExpected(request); @@ -453,7 +451,7 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) { ); ctx.write(response); } - } else if (msg instanceof LastHttpContent) { + } else if (msg instanceof final LastHttpContent lastContent) { try { while (!responseEnabled.get()) { Thread.sleep(1_000); @@ -462,13 +460,12 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) { } - LastHttpContent lastContent = (LastHttpContent) msg; - String trailer = lastContent.content().asReadOnly().readCharSequence(lastContent.content().readableBytes(), Charset.forName("utf-8")).toString(); + String trailer = lastContent.content().asReadOnly().readCharSequence(lastContent.content().readableBytes(), StandardCharsets.UTF_8).toString(); System.out.println("[DummyServer] LastHttpContent: " + trailer); DefaultFullHttpResponse response = new DefaultFullHttpResponse( HTTP_1_1, HttpResponseStatus.OK, - Unpooled.copiedBuffer("resp=" + (continueRequest ? "client1" : "client2"), Charset.forName("utf-8")) + Unpooled.copiedBuffer("resp=" + (continueRequest ? "client1" : "client2"), StandardCharsets.UTF_8) ); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); @@ -479,9 +476,8 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) { if (!keepAlive) { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } - } else if (msg instanceof HttpContent) { - HttpContent content = (HttpContent) msg; - String httpContent = content.content().asReadOnly().readCharSequence(content.content().readableBytes(), Charset.forName("utf-8")).toString(); + } else if (msg instanceof final HttpContent content) { + String httpContent = content.content().asReadOnly().readCharSequence(content.content().readableBytes(), StandardCharsets.UTF_8).toString(); System.out.println("[DummyServer] HttpContent: " + httpContent); } } @@ -531,10 +527,8 @@ public void testMultiClientTimeout() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - ExecutorService ex = Executors.newFixedThreadPool(2); - List futures = new ArrayList<>(); + List> futures = new ArrayList<>(); try (HttpProxyServer proxy = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { ConnectionPoolConfiguration defaultConnectionPool = proxy.getCurrentConfiguration().getDefaultConnectionPool(); @@ -597,7 +591,7 @@ public void testMultiClientTimeout() throws Exception { } })); } finally { - for (Future future : futures) { + for (Future future : futures) { try { future.get(); } catch (InterruptedException | ExecutionException e) { @@ -615,7 +609,7 @@ public void testMultiClientTimeout() throws Exception { @Test public void testMaxConnectionsAndBorrowTimeout() throws Exception { ExecutorService ex = Executors.newFixedThreadPool(2); - List futures = new ArrayList<>(); + List> futures = new ArrayList<>(); AtomicBoolean responseEnabled = new AtomicBoolean(); try (DummyServer server = new DummyServer("localhost", 8086, responseEnabled)) { @@ -721,7 +715,7 @@ public void testInvalidUriChars() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); try (RawHttpClient client = new RawHttpClient("localhost", port)) { @@ -735,13 +729,12 @@ public void testInvalidUriChars() throws Exception { @Test @Parameters({"http", "https"}) public void testClosedProxy(String scheme) throws Exception { - String certificate = TestUtils.deployResource("localhost.p12", tmpDir.getRoot()); + TestUtils.deployResource("localhost.p12", tmpDir.getRoot()); // Proxy requests have to use "localhost:port" as endpoint instead of the one in the url (ex yahoo.com) // in order to avoid open proxy vulnerability TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) { + try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) { server.addCertificate(new SSLCertificateConfiguration("localhost", null, "localhost.p12", "testproxy", STATIC)); server.addListener(new NetworkListenerConfiguration("localhost", 0, scheme.equals("https"), null, "localhost", DEFAULT_SSL_PROTOCOLS, 128, true, 300, 60, 8, 100, DEFAULT_FORWARDED_STRATEGY, Set.of(), Set.of(HTTP11.name()))); @@ -813,7 +806,7 @@ public void testCookies() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); try (RawHttpClient client = new RawHttpClient("localhost", port)) { diff --git a/carapace-server/src/test/java/org/carapaceproxy/RealBackendsTest.java b/carapace-server/src/test/java/org/carapaceproxy/RealBackendsTest.java index f1cce0eaa..1026c77f3 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/RealBackendsTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/RealBackendsTest.java @@ -45,34 +45,30 @@ public class RealBackendsTest { private static RawHttpClient.HttpResponse doGet(RawHttpClient client, String host, String uri) throws IOException { - RawHttpClient.HttpResponse response = - client.executeRequest("GET " + uri + " HTTP/1.1" - + "\r\nHost: " + host - + "\r\nAccept-Encoding: gzip, deflate, br" - + "\r\nCache-Control: no-cache" - + "\r\nPragma: no-cache" - + "\r\nConnection: keep-alive" - + "\r\n\r\n" - ); - return response; + return client.executeRequest("GET " + uri + " HTTP/1.1" + + "\r\nHost: " + host + + "\r\nAccept-Encoding: gzip, deflate, br" + + "\r\nCache-Control: no-cache" + + "\r\nPragma: no-cache" + + "\r\nConnection: keep-alive" + + "\r\n\r\n" + ); } private static RawHttpClient.HttpResponse doPost(RawHttpClient client, String host, String auth, String uri, String body) throws IOException { - RawHttpClient.HttpResponse response = - client.executeRequest("POST " + uri + " HTTP/1.1" - + "\r\nHost: " + host - + "\r\nAuthorization: Bearer " + auth - + "\r\nAccept: application/json" - + "\r\nAccept-Encoding: gzip, deflate, br" - + "\r\nCache-Control: no-cache" - + "\r\nConnection: keep-alive" - + "\r\nContent-Type: application/json" - + "\r\nContent-Length: " + body.length() - + "\r\n\r\n" - + body - ); - return response; + return client.executeRequest("POST " + uri + " HTTP/1.1" + + "\r\nHost: " + host + + "\r\nAuthorization: Bearer " + auth + + "\r\nAccept: application/json" + + "\r\nAccept-Encoding: gzip, deflate, br" + + "\r\nCache-Control: no-cache" + + "\r\nConnection: keep-alive" + + "\r\nContent-Type: application/json" + + "\r\nContent-Length: " + body.length() + + "\r\n\r\n" + + body + ); } @@ -87,23 +83,18 @@ public void testRequestsRealBackend() throws Exception { final int requestsPerClient = 1000; TestEndpointMapper mapper = new TestEndpointMapper(host, 8443); ExecutorService ex = Executors.newFixedThreadPool(threads); - List futures = new ArrayList<>(); + List> futures = new ArrayList<>(); AtomicInteger countOk = new AtomicInteger(); AtomicInteger countError = new AtomicInteger(); AtomicBoolean stop = new AtomicBoolean(); final String carapaceHost = "localhost"; - int port = 443; - boolean isLocal = carapaceHost.equals("localhost"); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { RuntimeServerConfiguration config = new RuntimeServerConfiguration(); config.setMaxConnectionsPerEndpoint(1); server.getProxyRequestsManager().reloadConfiguration(config, mapper.getBackends().values()); server.start(); - if (isLocal) { - port = server.getLocalPort(); - } - final int carapaceport = port; + final int carapaceport = server.getLocalPort(); try { @@ -111,7 +102,7 @@ public void testRequestsRealBackend() throws Exception { futures.add(ex.submit(() -> { try { - try (RawHttpClient client = new RawHttpClient(carapaceHost, carapaceport, !isLocal)) { + try (RawHttpClient client = new RawHttpClient(carapaceHost, carapaceport, false)) { client.getSocket().setKeepAlive(true); client.getSocket().setSoTimeout(1000 * 30); for (int rq = 0; rq < requestsPerClient; rq++) { @@ -142,7 +133,7 @@ public void testRequestsRealBackend() throws Exception { } } finally { - for (Future future : futures) { + for (Future future : futures) { try { future.get(); } catch (Throwable e) { diff --git a/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java b/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java index 87086681f..91cc6dd66 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java +++ b/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java @@ -19,18 +19,17 @@ */ package org.carapaceproxy.api; +import static org.carapaceproxy.core.HttpProxyServer.buildForTests; +import static org.junit.Assert.assertNull; import java.io.File; import java.io.IOException; import java.util.Properties; import org.carapaceproxy.configstore.PropertiesConfigurationStore; import org.carapaceproxy.core.HttpProxyServer; -import static org.carapaceproxy.core.HttpProxyServer.buildForTests; import org.carapaceproxy.server.config.ConfigurationChangeInProgressException; -import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.utils.RawHttpClient; import org.carapaceproxy.utils.TestEndpointMapper; import org.junit.After; -import static org.junit.Assert.assertNull; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TemporaryFolder; @@ -67,7 +66,7 @@ public void buildNewServer() throws Exception { } @After - public void stopServer() throws Exception { + public void stopServer() { if (server != null) { server.close(); server = null; @@ -94,7 +93,7 @@ public void startAdmin() throws Exception { startServer(new Properties(HTTP_ADMIN_SERVER_CONFIG)); } - public void changeDynamicConfiguration(Properties configuration) throws ConfigurationNotValidException, ConfigurationChangeInProgressException, InterruptedException, IOException { + public void changeDynamicConfiguration(Properties configuration) throws ConfigurationChangeInProgressException, InterruptedException, IOException { if (server != null) { fixAccessLogFileConfiguration(configuration); PropertiesConfigurationStore config = new PropertiesConfigurationStore(configuration); diff --git a/carapace-server/src/test/java/org/carapaceproxy/backends/ChunckedEncodingRequestsTest.java b/carapace-server/src/test/java/org/carapaceproxy/backends/ChunckedEncodingRequestsTest.java index 1e7ef8abc..8cee0ba66 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/backends/ChunckedEncodingRequestsTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/backends/ChunckedEncodingRequestsTest.java @@ -19,16 +19,15 @@ */ package org.carapaceproxy.backends; -import org.carapaceproxy.utils.TestEndpointMapper; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.junit.Assert.assertTrue; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import org.carapaceproxy.core.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.utils.RawHttpClient; -import static org.junit.Assert.assertTrue; +import org.carapaceproxy.utils.TestEndpointMapper; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -70,9 +69,8 @@ public void testSimple() throws Exception { ); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); @@ -100,9 +98,8 @@ public void testClientAbortsUpload() throws Exception { ); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); diff --git a/carapace-server/src/test/java/org/carapaceproxy/backends/RestartEndpointTest.java b/carapace-server/src/test/java/org/carapaceproxy/backends/RestartEndpointTest.java index 5fa13ad4f..873de3f33 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/backends/RestartEndpointTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/backends/RestartEndpointTest.java @@ -45,7 +45,7 @@ public class RestartEndpointTest { // in order to be restartable this must be fixed private static int tryDiscoverEmptyPort() { - try (ServerSocket s = new ServerSocket();) { + try (ServerSocket s = new ServerSocket()) { s.bind(null); return s.getLocalPort(); } catch (IOException err) { @@ -74,7 +74,7 @@ public void testClientsSendsRequestOnDownBackendAtSendRequest() throws Exception )); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); @@ -109,7 +109,7 @@ public void testClientsSendsRequestOnDownBackendAtSendRequestWithCache() throws )); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); @@ -149,7 +149,7 @@ public void testClientsSendsRequestBackendRestart() throws Exception { )); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { + try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); diff --git a/carapace-server/src/test/java/org/carapaceproxy/core/RequestsLoggerTest.java b/carapace-server/src/test/java/org/carapaceproxy/core/RequestsLoggerTest.java index 2b45aecd7..488c8e32e 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/core/RequestsLoggerTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/core/RequestsLoggerTest.java @@ -23,11 +23,18 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.common.io.Files; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; @@ -37,21 +44,13 @@ import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.List; - -import io.netty.handler.codec.http.HttpVersion; import org.carapaceproxy.server.mapper.MapResult; import org.carapaceproxy.utils.RawHttpClient; import org.carapaceproxy.utils.TestEndpointMapper; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import reactor.netty.http.server.HttpServerRequest; /** @@ -512,7 +511,6 @@ public void test() throws Exception { @Test public void testWithServer() throws Exception { - stubFor(get(urlEqualTo("/index.html")) .willReturn(aResponse() .withStatus(200) @@ -521,7 +519,6 @@ public void testWithServer() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.getCurrentConfiguration().setAccessLogPath(tmpDir.getRoot().getAbsolutePath() + "/access.log"); @@ -565,7 +562,6 @@ public void testAccessLogRotation() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.getCurrentConfiguration().setAccessLogPath(tmpDir.getRoot().getAbsolutePath() + "/access.log"); @@ -574,39 +570,39 @@ public void testAccessLogRotation() throws Exception { server.start(); int port = server.getLocalPort(); System.out.println("CurrentAccessLogPath " + currentAccessLogPath); - FileChannel logFileChannel = FileChannel.open(currentAccessLogPath); - - while (logFileChannel.size() < 1024) { - try (RawHttpClient client = new RawHttpClient("localhost", port)) { - RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"); - String s = resp.toString(); - assertTrue(s.contains("it works !!")); - } - - try (RawHttpClient client = new RawHttpClient("localhost", port)) { - { - RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n"); + try (FileChannel logFileChannel = FileChannel.open(currentAccessLogPath)) { + while (logFileChannel.size() < 1024) { + try (RawHttpClient client = new RawHttpClient("localhost", port)) { + RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"); String s = resp.toString(); assertTrue(s.contains("it works !!")); } - { - RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n"); - String s = resp.toString(); - assertTrue(s.contains("it works !!")); + try (RawHttpClient client = new RawHttpClient("localhost", port)) { + { + RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n"); + String s = resp.toString(); + assertTrue(s.contains("it works !!")); + } + + { + RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n"); + String s = resp.toString(); + assertTrue(s.contains("it works !!")); + } } } + Thread.sleep(3000); + //check if gzip file exist + File[] f = new File(tmpDir.getRoot().getAbsolutePath()).listFiles((dir, name) -> name.startsWith("access") && name.contains(".gzip")); + assertTrue(f.length == 1); + try (RawHttpClient client = new RawHttpClient("localhost", port)) { + RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"); + String s = resp.toString(); + assertTrue(s.contains("it works !!")); + } + assertTrue(logFileChannel.size() > 0); } - Thread.sleep(3000); - //check if gzip file exist - File[] f = new File(tmpDir.getRoot().getAbsolutePath()).listFiles((dir, name) -> name.startsWith("access") && name.contains(".gzip")); - assertTrue(f.length == 1); - try (RawHttpClient client = new RawHttpClient("localhost", port)) { - RawHttpClient.HttpResponse resp = client.executeRequest("GET /index.html HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"); - String s = resp.toString(); - assertTrue(s.contains("it works !!")); - } - assertTrue(logFileChannel.size() > 0); } } diff --git a/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java b/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java index a4c21fe34..d34c096f5 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java @@ -6,10 +6,10 @@ import java.util.Map; import java.util.Properties; import org.carapaceproxy.configstore.PropertiesConfigurationStore; +import org.carapaceproxy.core.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.core.Listeners; import org.carapaceproxy.server.config.ConfigurationChangeInProgressException; -import org.carapaceproxy.core.EndpointKey; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; diff --git a/carapace-server/src/test/java/org/carapaceproxy/listeners/SSLSNITest.java b/carapace-server/src/test/java/org/carapaceproxy/listeners/SSLSNITest.java index 41067a718..685e1e88b 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/listeners/SSLSNITest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/listeners/SSLSNITest.java @@ -35,7 +35,6 @@ import java.security.cert.X509Certificate; import java.util.Set; import javax.net.ssl.SSLSession; -import org.carapaceproxy.core.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.server.config.NetworkListenerConfiguration; @@ -70,9 +69,8 @@ public void testSelectCertWithoutSNI() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - - try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) { + + try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) { server.addCertificate(new SSLCertificateConfiguration(nonLocalhost, null, certificate, "testproxy", STATIC)); server.addListener(new NetworkListenerConfiguration(nonLocalhost, 0, true, null, nonLocalhost /* default */, DEFAULT_SSL_PROTOCOLS, 128, true, 300, 60, 8, 1000, DEFAULT_FORWARDED_STRATEGY, Set.of(), Set.of(HTTP11.name()))); server.start(); @@ -91,7 +89,7 @@ public void testSelectCertWithoutSNI() throws Exception { public void testChooseCertificate() throws Exception { TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) { + try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) { server.addCertificate(new SSLCertificateConfiguration("other", null, "cert", "pwd", STATIC)); server.addCertificate(new SSLCertificateConfiguration("*.example.com", Set.of("example.com", "*.example2.com"), "cert", "pwd", STATIC)); @@ -135,7 +133,7 @@ public void testChooseCertificate() throws Exception { assertEquals("*.example.com", server.getListeners().chooseCertificate("test.example2.com", "no-default").getId()); } - try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) { + try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) { // full wildcard server.addCertificate(new SSLCertificateConfiguration("*", null, "cert", "pwd", STATIC)); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheContentLengthLimitTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheContentLengthLimitTest.java index 54af84804..6f60ebd9f 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheContentLengthLimitTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheContentLengthLimitTest.java @@ -23,6 +23,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.IOException; import org.carapaceproxy.EndpointStats; @@ -30,10 +34,6 @@ import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.utils.RawHttpClient; import org.carapaceproxy.utils.TestEndpointMapper; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -80,7 +80,6 @@ public void testWithoutContentLengthHeader() throws Exception { } private void testFileSizeCache(String body, boolean chunked) throws Exception { - TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheTest.java index 3a41a2fff..1164a5d3b 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/cache/CacheTest.java @@ -76,7 +76,6 @@ public void testServeFromCache() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -146,7 +145,6 @@ public void testNotServeFromCacheIfCachableButClientsDisablesCache() throws Exce .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -203,9 +201,7 @@ public void testBootSslRelativeCertificatePath() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); - EndpointStats stats; try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) { server.addCertificate(new SSLCertificateConfiguration("localhost", null, "localhost.p12", "testproxy", STATIC)); server.addListener(new NetworkListenerConfiguration("localhost", 0, true, null, "localhost", @@ -229,7 +225,6 @@ public void testServeFromCacheSsl(boolean cacheDisabledForSecureRequestsWithoutP .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) { server.addCertificate(new SSLCertificateConfiguration("localhost", null, "localhost.p12", "testproxy", STATIC)); @@ -359,7 +354,6 @@ public void testServeFromCacheWithRequestProtocol() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); int httpPort = 1234; int httpsPort = 1235; @@ -473,7 +467,6 @@ public void testServeFromCacheChunked() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -531,7 +524,6 @@ public void testServeFromCacheWithConnectionClose() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -589,7 +581,6 @@ public void testNotCachableResourceWithQueryString() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -624,7 +615,6 @@ public void testImagesCachableWithQueryString() throws Exception { .withBody("it works !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -652,7 +642,6 @@ public void testImagesCachableWithQueryString() throws Exception { @Test public void testNoCacheResponse() throws Exception { TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.start(); int port = server.getLocalPort(); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/cache/NotModifiedTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/cache/NotModifiedTest.java index 6e305e4bb..1b42ecf5f 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/cache/NotModifiedTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/cache/NotModifiedTest.java @@ -19,19 +19,20 @@ under the License. */ -import org.carapaceproxy.utils.HttpUtils; -import org.carapaceproxy.utils.TestEndpointMapper; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import org.carapaceproxy.core.HttpProxyServer; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import org.carapaceproxy.core.EndpointKey; -import org.carapaceproxy.utils.RawHttpClient; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import org.carapaceproxy.core.EndpointKey; +import org.carapaceproxy.core.HttpProxyServer; +import org.carapaceproxy.utils.HttpUtils; +import org.carapaceproxy.utils.RawHttpClient; +import org.carapaceproxy.utils.TestEndpointMapper; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -57,7 +58,6 @@ public void testServeFromCacheAnswer304() throws Exception { )); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); @@ -80,7 +80,7 @@ public void testServeFromCacheAnswer304() throws Exception { + "Host: localhost\r\n" + "If-Modified-Since: " + HttpUtils.formatDateHeader(new java.util.Date(System.currentTimeMillis())) + "\r\n" + "\r\n"); - assertTrue(resp.getStatusLine().trim().equals("HTTP/1.1 304 Not Modified")); + assertEquals("HTTP/1.1 304 Not Modified", resp.getStatusLine().trim()); resp.getHeaderLines().forEach(h -> { System.out.println("HEADER LINE :" + h); }); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/filters/XForwardedForFilterTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/filters/XForwardedForFilterTest.java index 5b5bbad31..8a6b94efb 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/filters/XForwardedForFilterTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/filters/XForwardedForFilterTest.java @@ -102,7 +102,6 @@ public void testNoXForwardedForFilter() throws Exception { .withBody("No X-Forwarded-For"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder());) { server.start(); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsCipherFilterTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsCipherFilterTest.java index f1a65a0a1..e8bcd2281 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsCipherFilterTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsCipherFilterTest.java @@ -53,7 +53,6 @@ public void TestXTlsProtocol() throws Exception { .withBody("it absent !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.addCertificate(new SSLCertificateConfiguration("*", null, certificate, "testproxy", STATIC)); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsProtocolFilterTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsProtocolFilterTest.java index 1d62390a5..82921f79e 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsProtocolFilterTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/filters/XTlsProtocolFilterTest.java @@ -13,7 +13,6 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.util.Collections; import java.util.Set; -import org.carapaceproxy.core.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.server.config.NetworkListenerConfiguration; import org.carapaceproxy.server.config.RequestFilterConfiguration; @@ -52,7 +51,6 @@ public void TestXTlsProtocol() throws Exception { .withBody("it absent !!"))); TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port()); - EndpointKey key = new EndpointKey("localhost", wireMockRule.port()); try (HttpProxyServer server = HttpProxyServer.buildForTests("localhost", 0, mapper, tmpDir.newFolder())) { server.addCertificate(new SSLCertificateConfiguration("*", null, certificate, "testproxy", STATIC)); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/mapper/HealthCheckTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/mapper/HealthCheckTest.java index 79b4596e0..de0728841 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/mapper/HealthCheckTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/mapper/HealthCheckTest.java @@ -55,76 +55,74 @@ public class HealthCheckTest { @Test public void test() throws Exception { - - Map backends = new HashMap<>(); - BackendConfiguration b1conf = new BackendConfiguration("myid", "localhost", wireMockRule.port(), "/status.html"); + final Map backends = new HashMap<>(); + final BackendConfiguration b1conf = new BackendConfiguration("myid", "localhost", wireMockRule.port(), "/status.html"); backends.put(b1conf.hostPort().toString(), b1conf); - - EndpointMapper mapper = new TestEndpointMapper(null, 0, false, backends); - RuntimeServerConfiguration conf = new RuntimeServerConfiguration(); - - BackendHealthManager hman = new BackendHealthManager(conf, mapper); - + final EndpointMapper mapper = new TestEndpointMapper(null, 0, false, backends); + final RuntimeServerConfiguration conf = new RuntimeServerConfiguration(); + final BackendHealthManager hman = new BackendHealthManager(conf, mapper); { + // Backend returns 200 OK, making it available. stubFor(get(urlEqualTo("/status.html")) .willReturn(aResponse() .withStatus(200) .withBody("Ok...")) ); - long startTs = System.currentTimeMillis(); + final long startTs = System.currentTimeMillis(); hman.run(); - long endTs = System.currentTimeMillis(); + final long endTs = System.currentTimeMillis(); - Map status = hman.getBackendsSnapshot(); + final Map status = hman.getBackendsSnapshot(); System.out.println("status=" + status); assertThat(status.size(), is(1)); - BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); + final BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); assertThat(bconf.id(), is("myid")); assertThat(bconf.host(), is("localhost")); assertThat(bconf.port(), is(wireMockRule.port())); assertThat(bconf.probePath(), is("/status.html")); - BackendHealthStatus _status = status.get(b1conf.hostPort()); + final BackendHealthStatus _status = status.get(b1conf.hostPort()); assertThat(_status, is(not(nullValue()))); assertThat(_status.getHostPort(), is(b1conf.hostPort())); assertThat(_status.isAvailable(), is(true)); assertThat(_status.isReportedAsUnreachable(), is(false)); assertThat(_status.getReportedAsUnreachableTs(), is(0L)); - BackendHealthCheck lastProbe = _status.getLastProbe(); - assertThat(lastProbe.getPath(), is("/status.html")); - assertThat(lastProbe.getEndTs() >= startTs, is(true)); - assertThat(lastProbe.getEndTs() <= endTs, is(true)); + final BackendHealthCheck lastProbe = _status.getLastProbe(); + assertThat(lastProbe, is(not(nullValue()))); + assertThat(lastProbe.path(), is("/status.html")); + assertThat(lastProbe.endTs() >= startTs, is(true)); + assertThat(lastProbe.endTs() <= endTs, is(true)); assertThat(lastProbe.isOk(), is(true)); - assertThat(lastProbe.getHttpResponse(), is("200 OK")); - assertThat(lastProbe.getHttpBody(), is("Ok...")); + assertThat(lastProbe.httpResponse(), is("200 OK")); + assertThat(lastProbe.httpBody(), is("Ok...")); } - - long reportedAsUnreachableTs; + final long reportedAsUnreachableTs; { + // Backend returns 500, marking it unavailable. stubFor(get(urlEqualTo("/status.html")) .willReturn(aResponse() .withStatus(500) .withBody("ERROR")) ); - long startTs = System.currentTimeMillis(); + final long startTs = System.currentTimeMillis(); hman.run(); - long endTs = System.currentTimeMillis(); + final long endTs = System.currentTimeMillis(); - Map status = hman.getBackendsSnapshot(); + final Map status = hman.getBackendsSnapshot(); System.out.println("status=" + status); assertThat(status.size(), is(1)); - BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); + final BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); assertThat(bconf.id(), is("myid")); assertThat(bconf.host(), is("localhost")); assertThat(bconf.port(), is(wireMockRule.port())); assertThat(bconf.probePath(), is("/status.html")); - BackendHealthStatus _status = status.get(b1conf.hostPort()); + final BackendHealthStatus _status = status.get(b1conf.hostPort()); assertThat(_status, is(not(nullValue()))); assertThat(_status.getHostPort(), is(b1conf.hostPort())); assertThat(_status.isAvailable(), is(false)); @@ -133,94 +131,96 @@ public void test() throws Exception { assertThat(_status.getReportedAsUnreachableTs() <= endTs, is(true)); reportedAsUnreachableTs = _status.getReportedAsUnreachableTs(); - BackendHealthCheck lastProbe = _status.getLastProbe(); - assertThat(lastProbe.getPath(), is("/status.html")); - assertThat(lastProbe.getEndTs() >= startTs, is(true)); - assertThat(lastProbe.getEndTs() <= endTs, is(true)); + final BackendHealthCheck lastProbe = _status.getLastProbe(); + assertThat(lastProbe, is(not(nullValue()))); + assertThat(lastProbe.path(), is("/status.html")); + assertThat(lastProbe.endTs() >= startTs, is(true)); + assertThat(lastProbe.endTs() <= endTs, is(true)); assertThat(lastProbe.isOk(), is(false)); - System.out.println("HTTP MESSAGE: " + lastProbe.getHttpResponse()); - System.out.println("STATUS INFO: " + lastProbe.getHttpBody()); - assertThat(lastProbe.getHttpResponse(), is("500 Server Error")); - assertThat(lastProbe.getHttpBody(), is("ERROR")); + System.out.println("HTTP MESSAGE: " + lastProbe.httpResponse()); + System.out.println("STATUS INFO: " + lastProbe.httpBody()); + assertThat(lastProbe.httpResponse(), is("500 Server Error")); + assertThat(lastProbe.httpBody(), is("ERROR")); } - { + // Backend remains in error, keeping it unreachable. stubFor(get(urlEqualTo("/status.html")) .willReturn(aResponse() .withStatus(500) .withBody("ERROR")) ); - long startTs = System.currentTimeMillis(); + final long startTs = System.currentTimeMillis(); hman.run(); - long endTs = System.currentTimeMillis(); + final long endTs = System.currentTimeMillis(); - Map status = hman.getBackendsSnapshot(); + final Map status = hman.getBackendsSnapshot(); System.out.println("status=" + status); assertThat(status.size(), is(1)); - BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); + final BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); assertThat(bconf.id(), is("myid")); assertThat(bconf.host(), is("localhost")); assertThat(bconf.port(), is(wireMockRule.port())); assertThat(bconf.probePath(), is("/status.html")); - BackendHealthStatus _status = status.get(b1conf.hostPort()); + final BackendHealthStatus _status = status.get(b1conf.hostPort()); assertThat(_status, is(not(nullValue()))); assertThat(_status.getHostPort(), is(b1conf.hostPort())); assertThat(_status.isAvailable(), is(false)); assertThat(_status.isReportedAsUnreachable(), is(true)); assertThat(_status.getReportedAsUnreachableTs(), is(reportedAsUnreachableTs)); - BackendHealthCheck lastProbe = _status.getLastProbe(); - assertThat(lastProbe.getPath(), is("/status.html")); - assertThat(lastProbe.getEndTs() >= startTs, is(true)); - assertThat(lastProbe.getEndTs() <= endTs, is(true)); + final BackendHealthCheck lastProbe = _status.getLastProbe(); + assertThat(lastProbe, is(not(nullValue()))); + assertThat(lastProbe.path(), is("/status.html")); + assertThat(lastProbe.endTs() >= startTs, is(true)); + assertThat(lastProbe.endTs() <= endTs, is(true)); assertThat(lastProbe.isOk(), is(false)); - System.out.println("HTTP MESSAGE: " + lastProbe.getHttpResponse()); - System.out.println("STATUS INFO: " + lastProbe.getHttpBody()); - assertThat(lastProbe.getHttpResponse(), is("500 Server Error")); - assertThat(lastProbe.getHttpBody(), is("ERROR")); + System.out.println("HTTP MESSAGE: " + lastProbe.httpResponse()); + System.out.println("STATUS INFO: " + lastProbe.httpBody()); + assertThat(lastProbe.httpResponse(), is("500 Server Error")); + assertThat(lastProbe.httpBody(), is("ERROR")); } - { + // Backend recovers and returns 201, marking it available again. stubFor(get(urlEqualTo("/status.html")) .willReturn(aResponse() .withStatus(201) .withBody("Ok...")) ); - long startTs = System.currentTimeMillis(); + final long startTs = System.currentTimeMillis(); hman.run(); - long endTs = System.currentTimeMillis(); + final long endTs = System.currentTimeMillis(); - Map status = hman.getBackendsSnapshot(); + final Map status = hman.getBackendsSnapshot(); System.out.println("status=" + status); assertThat(status.size(), is(1)); - BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); + final BackendConfiguration bconf = mapper.getBackends().get(b1conf.hostPort().toString()); assertThat(bconf.id(), is("myid")); assertThat(bconf.host(), is("localhost")); assertThat(bconf.port(), is(wireMockRule.port())); assertThat(bconf.probePath(), is("/status.html")); - BackendHealthStatus _status = status.get(b1conf.hostPort()); + final BackendHealthStatus _status = status.get(b1conf.hostPort()); assertThat(_status, is(not(nullValue()))); assertThat(_status.getHostPort(), is(b1conf.hostPort())); assertThat(_status.isAvailable(), is(true)); assertThat(_status.isReportedAsUnreachable(), is(false)); assertThat(_status.getReportedAsUnreachableTs(), is(0L)); - BackendHealthCheck lastProbe = _status.getLastProbe(); - assertThat(lastProbe.getPath(), is("/status.html")); - assertThat(lastProbe.getEndTs() >= startTs, is(true)); - assertThat(lastProbe.getEndTs() <= endTs, is(true)); + final BackendHealthCheck lastProbe = _status.getLastProbe(); + assertThat(lastProbe, is(not(nullValue()))); + assertThat(lastProbe.path(), is("/status.html")); + assertThat(lastProbe.endTs() >= startTs, is(true)); + assertThat(lastProbe.endTs() <= endTs, is(true)); assertThat(lastProbe.isOk(), is(true)); - System.out.println("HTTP MESSAGE: " + lastProbe.getHttpResponse()); - System.out.println("STATUS INFO: " + lastProbe.getHttpBody()); - assertThat(lastProbe.getHttpResponse(), is("201 Created")); - assertThat(lastProbe.getHttpBody(), is("Ok...")); + System.out.println("HTTP MESSAGE: " + lastProbe.httpResponse()); + System.out.println("STATUS INFO: " + lastProbe.httpBody()); + assertThat(lastProbe.httpResponse(), is("201 Created")); + assertThat(lastProbe.httpBody(), is("Ok...")); } - } } diff --git a/carapace-server/src/test/java/org/carapaceproxy/users/FileUserRealmTest.java b/carapace-server/src/test/java/org/carapaceproxy/users/FileUserRealmTest.java index efa83b82c..de197517a 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/users/FileUserRealmTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/users/FileUserRealmTest.java @@ -19,6 +19,11 @@ */ package org.carapaceproxy.users; +import static org.carapaceproxy.core.HttpProxyServer.buildForTests; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; @@ -29,14 +34,9 @@ import org.carapaceproxy.configstore.ConfigurationStore; import org.carapaceproxy.configstore.PropertiesConfigurationStore; import org.carapaceproxy.core.HttpProxyServer; -import static org.carapaceproxy.core.HttpProxyServer.buildForTests; import org.carapaceproxy.user.FileUserRealm; import org.carapaceproxy.user.UserRealm; import org.carapaceproxy.utils.TestEndpointMapper; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; diff --git a/carapace-server/src/test/java/org/carapaceproxy/utils/TestEndpointMapper.java b/carapace-server/src/test/java/org/carapaceproxy/utils/TestEndpointMapper.java index 74bc2d733..5caea9b93 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/utils/TestEndpointMapper.java +++ b/carapace-server/src/test/java/org/carapaceproxy/utils/TestEndpointMapper.java @@ -20,13 +20,14 @@ package org.carapaceproxy.utils; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.SequencedMap; import org.carapaceproxy.configstore.ConfigurationStore; import org.carapaceproxy.core.ProxyRequest; import org.carapaceproxy.server.config.ActionConfiguration; import org.carapaceproxy.server.config.BackendConfiguration; -import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.server.config.DirectorConfiguration; import org.carapaceproxy.server.config.RouteConfiguration; import org.carapaceproxy.server.mapper.CustomHeader; @@ -38,7 +39,7 @@ public class TestEndpointMapper extends EndpointMapper { private final String host; private final int port; private final boolean cacheAll; - private final Map backends; + private final SequencedMap backends; private final List routes = new ArrayList<>(); private final List actions = new ArrayList<>(); private final List directors = new ArrayList<>(); @@ -56,7 +57,7 @@ public TestEndpointMapper(String host, int port, boolean cacheAll, Map(backends); } @Override @@ -87,7 +88,7 @@ public MapResult map(ProxyRequest request) { } @Override - public Map getBackends() { + public SequencedMap getBackends() { return backends; } @@ -112,8 +113,6 @@ public List getHeaders() { } @Override - public void configure(ConfigurationStore properties) throws ConfigurationNotValidException { - + public void configure(ConfigurationStore properties) { } - } diff --git a/pom.xml b/pom.xml index 9e475bc5a..4ffb2a0c7 100644 --- a/pom.xml +++ b/pom.xml @@ -352,6 +352,7 @@ --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED