From a963724b311f3d3c97c0c04122f89816cd091d0f Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Tue, 12 May 2026 14:45:20 +0200 Subject: [PATCH 01/12] feat: Migrate to java 21, migrate servlet imports to Jakarta EE and update dependencies --- pom.xml | 114 ++++++----- .../it/geosolutions/httpproxy/HTTPProxy.java | 99 ++++------ .../geosolutions/httpproxy/HostChecker.java | 4 +- .../httpproxy/HostNameChecker.java | 4 +- .../httpproxy/MethodsChecker.java | 4 +- .../httpproxy/MimeTypeChecker.java | 4 +- .../geosolutions/httpproxy/ProxyCallback.java | 4 +- .../geosolutions/httpproxy/ProxyConfig.java | 32 ++- .../httpproxy/RequestTypeChecker.java | 4 +- src/main/webapp/WEB-INF/web.xml | 2 +- .../httpproxy/HttpProxyIntegrationTests.java | 182 ++++++++---------- .../geosolutions/httpproxy/HttpProxyTest.java | 37 ++-- .../httpproxy/RequestHeaderFilterTest.java | 20 +- .../httpproxy/StubServletOutputStream.java | 8 +- .../geosolutions/httpproxy/jetty/Start.java | 63 ++---- 15 files changed, 259 insertions(+), 322 deletions(-) diff --git a/pom.xml b/pom.xml index ef7740e..39ad388 100644 --- a/pom.xml +++ b/pom.xml @@ -27,17 +27,17 @@ maven-compiler-plugin - 3.5.1 + 3.13.0 - 8 - 8 + 21 + 21 org.apache.maven.plugins maven-surefire-plugin - 2.19.1 + 3.5.2 false @@ -46,7 +46,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.6 + 3.4.2 make-a-jar @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.1.3 install @@ -82,7 +82,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.1.3 deploy @@ -107,7 +107,7 @@ org.apache.maven.plugins maven-war-plugin - 2.6 + 3.4.0 ${basedir}/src/main/webapp/WEB-INF/web.xml @@ -137,7 +137,7 @@ org.apache.maven.plugins maven-release-plugin - 2.2.2 + 3.1.1 v@{project.version} @@ -155,8 +155,9 @@ UTF-8 - 2.19.0 - 4.5.13 + 2.25.4 + 4.5.14 + 12.1.8 @@ -173,19 +174,19 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl ${log4j-version} - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter + 5.11.4 test - commons-fileupload - commons-fileupload - 1.5 + org.apache.commons + commons-fileupload2-jakarta-servlet6 + 2.0.0-M5 @@ -203,58 +204,56 @@ commons-io commons-io - 2.1 + 2.18.0 commons-codec commons-codec - 1.4 + 1.17.1 - org.mortbay.jetty - servlet-api-2.5 - 6.1.14 + jakarta.servlet + jakarta.servlet-api + 6.1.0 + provided - - org.mortbay.jetty - jetty - 6.1.23 - test + org.eclipse.jetty.ee10 + jetty-ee10-webapp + ${jetty-version} - org.mortbay.jetty - jetty-servlet-tester - 6.1.5 + org.mockito + mockito-core + 5.14.2 test org.mockito - mockito-all - 1.9.5 + mockito-junit-jupiter + 5.14.2 test - - com.github.tomakehurst - wiremock-jre8-standalone - 2.32.0 + org.wiremock + wiremock-standalone + 3.10.0 test - + - junit - junit + org.junit.jupiter + junit-jupiter test @@ -269,13 +268,13 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl - commons-fileupload - commons-fileupload + org.apache.commons + commons-fileupload2-jakarta-servlet6 @@ -299,33 +298,32 @@ - org.mortbay.jetty - servlet-api-2.5 + jakarta.servlet + jakarta.servlet-api - + - org.mortbay.jetty - jetty - test + org.eclipse.jetty.ee10 + jetty-ee10-webapp - - org.mortbay.jetty - jetty-servlet-tester - test - - + org.mockito - mockito-all + mockito-core test + + org.mockito + mockito-junit-jupiter + test + - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java index e1a56a5..a04a616 100644 --- a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java +++ b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java @@ -33,24 +33,22 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.StringTokenizer; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload2.core.DiskFileItemFactory; +import org.apache.commons.fileupload2.core.FileItem; +import org.apache.commons.fileupload2.core.FileUploadException; +import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -67,7 +65,6 @@ import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.slf4j.LoggerFactory; @@ -232,7 +229,7 @@ private HttpRoute getProxyRoute(HttpHost target) { }; } - private Boolean isNonProxyHost(String host) { + private boolean isNonProxyHost(String host) { boolean isNonProxyHost = false; String nonProxyHostProp = System.getProperty("http.nonProxyHosts"); if (nonProxyHostProp != null) { @@ -243,9 +240,8 @@ private Boolean isNonProxyHost(String host) { nonProxyHostProp = nonProxyHostProp.substring(0, nonProxyHostProp.length() - 1); } LOGGER.info("http.nonProxyHosts value: " + nonProxyHostProp); - StringTokenizer tokenizer = new StringTokenizer(nonProxyHostProp, "|"); - while (tokenizer.hasMoreTokens()) { - String str = tokenizer.nextToken().trim(); + for (String token : nonProxyHostProp.split("\\|")) { + String str = token.trim(); str = str.replace("*", "[\\w-]*"); Pattern pattern = Pattern.compile(str); Matcher matcher = pattern.matcher(host); @@ -304,12 +300,11 @@ public void doGet(HttpServletRequest httpServletRequest, HttpServletResponse htt URL url = null; String user = null, password = null; - Set entrySet = httpServletRequest.getParameterMap().entrySet(); + Set> entrySet = httpServletRequest.getParameterMap().entrySet(); - for (Object anEntrySet : entrySet) { - Map.Entry header = (Map.Entry) anEntrySet; - String key = (String) header.getKey(); - String value = ((String[]) header.getValue())[0]; + for (Map.Entry header : entrySet) { + String key = header.getKey(); + String value = header.getValue()[0]; if ("user".equals(key)) { user = value; @@ -405,7 +400,7 @@ public void doPost(HttpServletRequest httpServletRequest, // Check if this is a mulitpart (file upload) POST // ////////////////////////////////////////////////// - if (ServletFileUpload.isMultipartContent(httpServletRequest)) { + if (JakartaServletFileUpload.isMultipartContent(httpServletRequest)) { this.handleMultipart(postMethodProxyRequest, httpServletRequest); } else { this.handleStandard(postMethodProxyRequest, httpServletRequest); @@ -479,7 +474,7 @@ public void doPut(HttpServletRequest httpServletRequest, HttpServletResponse htt // Check if this is a mulitpart (file upload) PUT // ////////////////////////////////////////////////// - if (ServletFileUpload.isMultipartContent(httpServletRequest)) { + if (JakartaServletFileUpload.isMultipartContent(httpServletRequest)) { this.handleMultipart(putMethodProxyRequest, httpServletRequest); } else { this.handleStandard(putMethodProxyRequest, httpServletRequest); @@ -568,28 +563,25 @@ public void doDelete(HttpServletRequest httpServletRequest, * * @param postMethodProxyRequest The {@link PostMethod} that we are configuring to send a multipart POST request * @param httpServletRequest The {@link HttpServletRequest} that contains the mutlipart POST data to be sent via the {@link PostMethod} + * @throws IOException */ private void handleMultipart(HttpRequestBase methodProxyRequest, - HttpServletRequest httpServletRequest) throws ServletException { + HttpServletRequest httpServletRequest) throws ServletException, IOException { // //////////////////////////////////////////// // Create a factory for disk-based file items // //////////////////////////////////////////// - DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); - - // ///////////////////////////// - // Set factory constraints - // ///////////////////////////// - - diskFileItemFactory.setSizeThreshold(this.getMaxFileUploadSize()); - diskFileItemFactory.setRepository(Utils.DEFAULT_FILE_UPLOAD_TEMP_DIRECTORY); + DiskFileItemFactory diskFileItemFactory = DiskFileItemFactory.builder() + .setBufferSize(this.getMaxFileUploadSize()) + .setPath(Utils.DEFAULT_FILE_UPLOAD_TEMP_DIRECTORY.toPath()) + .get(); // ////////////////////////////////// // Create a new file upload handler // ////////////////////////////////// - ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); + JakartaServletFileUpload servletFileUpload = new JakartaServletFileUpload(diskFileItemFactory); // ////////////////////////// // Parse the request @@ -601,7 +593,7 @@ private void handleMultipart(HttpRequestBase methodProxyRequest, // Get the multipart items as a list // ///////////////////////////////////// - List listFileItems = (List) servletFileUpload + List listFileItems = servletFileUpload .parseRequest(httpServletRequest); // ///////////////////////////////////////// @@ -693,8 +685,6 @@ private void executeProxyRequest(HttpRequestBase httpMethodProxyRequest, httpClient = createHttpClient(); } - InputStream inputStreamServerResponse = null; - try { // ////////////////////////// @@ -788,29 +778,21 @@ else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) // Send the content to the client // /////////////////////////////////// - inputStreamServerResponse = response.getEntity().getContent(); + try (InputStream inputStreamServerResponse = response.getEntity().getContent()) { + if (inputStreamServerResponse != null) { + byte[] b = new byte[proxyConfig.getDefaultStreamByteSize()]; - if (inputStreamServerResponse != null) { - byte[] b = new byte[proxyConfig.getDefaultStreamByteSize()]; - - int read = 0; - ServletOutputStream out = httpServletResponse.getOutputStream(); - while ((read = inputStreamServerResponse.read(b)) > 0) { - out.write(b, 0, read); + int read; + ServletOutputStream out = httpServletResponse.getOutputStream(); + while ((read = inputStreamServerResponse.read(b)) > 0) { + out.write(b, 0, read); + } } } } catch (Exception e) { LOGGER.error("Error executing HTTP method", e); } finally { - try { - if (inputStreamServerResponse != null) - inputStreamServerResponse.close(); - } catch (IOException e) { - LOGGER.error("Error closing request input stream", e); - throw new ServletException(e.getMessage()); - } - httpMethodProxyRequest.releaseConnection(); } } @@ -831,7 +813,6 @@ int getStatusCode(HttpResponse response) { * @param httpMethodProxyRequest The request that we are about to send to the proxy host * @return ProxyInfo */ - @SuppressWarnings("rawtypes") private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServletRequest, HttpRequestBase httpMethodProxyRequest) { @@ -845,7 +826,7 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet // names sent by the client. // //////////////////////////////////////// - Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames(); + Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames(); // //////////////////////////////////////// // Load header whitelist/blacklist for @@ -856,7 +837,7 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet Set headerBlacklist = proxyConfig.getRequestHeaderBlacklist(); while (enumerationOfHeaderNames.hasMoreElements()) { - String stringHeaderName = (String) enumerationOfHeaderNames.nextElement(); + String stringHeaderName = enumerationOfHeaderNames.nextElement(); if (stringHeaderName.equalsIgnoreCase(Utils.CONTENT_LENGTH_HEADER_NAME)) continue; @@ -890,10 +871,10 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet // Thus, we get an Enumeration of the header values sent by the client // //////////////////////////////////////////////////////////////////////// - Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName); + Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName); while (enumerationOfHeaderValues.hasMoreElements()) { - String stringHeaderValue = (String) enumerationOfHeaderValues.nextElement(); + String stringHeaderValue = enumerationOfHeaderValues.nextElement(); // //////////////////////////////////////////////////////////////// // In case the proxy host is running multiple virtual servers, diff --git a/src/main/java/it/geosolutions/httpproxy/HostChecker.java b/src/main/java/it/geosolutions/httpproxy/HostChecker.java index 28dd4e1..822c5f4 100644 --- a/src/main/java/it/geosolutions/httpproxy/HostChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/HostChecker.java @@ -23,8 +23,8 @@ import java.net.URL; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.client.methods.HttpRequestBase; diff --git a/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java b/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java index 5b24080..8d85943 100644 --- a/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java @@ -23,8 +23,8 @@ import java.net.URL; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.client.methods.HttpRequestBase; diff --git a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java index 16a8d4d..821a893 100644 --- a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java @@ -23,8 +23,8 @@ import java.net.URL; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.client.methods.HttpRequestBase; diff --git a/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java b/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java index 3ec1c60..59d8478 100644 --- a/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java @@ -23,8 +23,8 @@ import java.net.URL; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.Header; import org.apache.http.client.methods.HttpRequestBase; diff --git a/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java b/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java index b7febfb..2b4c62e 100644 --- a/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java +++ b/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java @@ -22,8 +22,8 @@ import java.io.IOException; import java.net.URL; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.client.methods.HttpRequestBase; diff --git a/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java b/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java index 267a0ea..fefb1ec 100644 --- a/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java +++ b/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java @@ -26,10 +26,11 @@ import java.util.HashSet; import java.util.Properties; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.servlet.ServletContext; +import jakarta.servlet.ServletContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ProxyConfig class to define the proxy configuration. @@ -38,7 +39,7 @@ */ final class ProxyConfig { - private final static Logger LOGGER = Logger.getLogger(ProxyConfig.class.toString()); + private static final Logger LOGGER = LoggerFactory.getLogger(ProxyConfig.class); /** * A list of regular expressions describing hostnames the proxy is permitted to forward to @@ -215,9 +216,7 @@ private void configProxy() { : this.defaultMaxConnectionsPerHost); } catch (NumberFormatException e) { - if (LOGGER.isLoggable(Level.SEVERE)) - LOGGER.log(Level.SEVERE, - "Error parsing the proxy properties file using default", e); + LOGGER.error("Error parsing proxy configuration: {}", e.getMessage(), e); this.setSoTimeout(this.soTimeout); this.setConnectionTimeout(this.connectionTimeout); @@ -248,25 +247,16 @@ private void mergePropertiesConfig(String path, Properties properties) { try { inputStream = new FileInputStream(path); } catch (FileNotFoundException e) { - if (LOGGER.isLoggable(Level.WARNING)) - LOGGER.log(Level.WARNING, "The properties file " + path + " cannot be found"); + LOGGER.warn("The properties file {} cannot be found", path); } } if (inputStream != null) { - Properties props = new Properties(); - try { - props.load(inputStream); + try (InputStream is = inputStream) { + Properties props = new Properties(); + props.load(is); properties.putAll(props); } catch (IOException e) { - if (LOGGER.isLoggable(Level.SEVERE)) - LOGGER.log(Level.SEVERE, "Error loading the proxy properties file from " + path, e); - } finally { - if (inputStream != null) - try { - inputStream.close(); - } catch (IOException e) { - - } + LOGGER.error("Error loading the proxy properties file from {}", path, e); } } } diff --git a/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java b/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java index 901343d..b48a08d 100644 --- a/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java @@ -26,8 +26,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.client.methods.HttpRequestBase; diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index b04a3af..618282f 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -1,5 +1,5 @@ - + HTTP_PROXY diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java index 0cc2fe7..6f07770 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java @@ -2,7 +2,10 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import org.eclipse.jetty.ee10.webapp.WebAppContext; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -10,12 +13,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.junit.*; -import org.mortbay.jetty.Connector; -import org.mortbay.jetty.Server; -import org.mortbay.jetty.bio.SocketConnector; -import org.mortbay.jetty.webapp.WebAppContext; -import org.mortbay.thread.BoundedThreadPool; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.LoggerFactory; import java.io.File; @@ -30,13 +29,15 @@ public class HttpProxyIntegrationTests { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(HttpProxyIntegrationTests.class); - @Rule - public WireMockRule wireMockRule = - new WireMockRule(WireMockConfiguration.options().dynamicPort()); + @RegisterExtension + static WireMockExtension wireMockRule = WireMockExtension.newInstance() + .options(WireMockConfiguration.options().dynamicPort()) + .build(); - @Rule - public WireMockRule wireMockRule1 = - new WireMockRule(WireMockConfiguration.options().dynamicPort()); + @RegisterExtension + static WireMockExtension wireMockRule1 = WireMockExtension.newInstance() + .options(WireMockConfiguration.options().dynamicPort()) + .build(); static WireMockServer wireMockServer; @@ -45,7 +46,7 @@ public class HttpProxyIntegrationTests { private static String proxyHost; private static String proxyPort; - @BeforeClass + @BeforeAll public static void startHttpProxyServer() { try { @@ -56,23 +57,15 @@ public static void startHttpProxyServer() { wireMockServer.start(); jettyServer = new Server(); + ServerConnector connector = new ServerConnector(jettyServer); + connector.setPort(8080); + jettyServer.addConnector(connector); - BoundedThreadPool tp = new BoundedThreadPool(); - tp.setMaxThreads(50); - - SocketConnector conn = new SocketConnector(); - int port = 8080; - - conn.setPort(port); - conn.setThreadPool(tp); - conn.setAcceptQueueSize(100); - jettyServer.setConnectors(new Connector[]{conn}); - - WebAppContext wah = new WebAppContext(); - wah.setContextPath("/http_proxy"); - wah.setWar("src/main/webapp"); - jettyServer.setHandler(wah); - wah.setTempDirectory(new File("target/work")); + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/http_proxy"); + webapp.setWar(new File("src/main/webapp").getAbsolutePath()); + webapp.setTempDirectory(new File("target/work")); + jettyServer.setHandler(webapp); jettyServer.start(); @@ -103,21 +96,20 @@ private static void storeProxyConfig() { @Test public void testGETUsingWireMock() throws IOException { - wireMockRule.addStubMapping( - stubFor( - get(urlEqualTo("/geostore/users")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("Some content")))); + wireMockRule.stubFor( + get(urlEqualTo("/geostore/users")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("Some content"))); - String url = "http://localhost:" + wireMockRule.port() + "/geostore/users"; + String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/users"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpGet httpGet = new HttpGet(proxyURL); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpResponse httpResponse = httpClient.execute(httpGet); - Assert.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/users"))); } } @@ -129,17 +121,16 @@ public void testGETUsingWireMock() throws IOException { public void testPOSTUsingWireMockXML() throws IOException { String requestBody = "5Jane Doe"; - wireMockRule1.addStubMapping( - stubFor( - post(urlEqualTo("/geostore/users/create")) - .withRequestBody(equalToXml(requestBody)) - .willReturn( - aResponse() - .withStatus(201) - .withHeader("Content-Type", "text/xml") - .withBody("5")))); - - String url = "http://localhost:" + wireMockRule1.port() + "/geostore/users/create"; + wireMockRule1.stubFor( + post(urlEqualTo("/geostore/users/create")) + .withRequestBody(equalToXml(requestBody)) + .willReturn( + aResponse() + .withStatus(201) + .withHeader("Content-Type", "text/xml") + .withBody("5"))); + + String url = "http://localhost:" + wireMockRule1.getPort() + "/geostore/users/create"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpPost httpPost = new HttpPost(proxyURL); StringEntity stringEntity = new StringEntity(requestBody); @@ -147,8 +138,8 @@ public void testPOSTUsingWireMockXML() throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpResponse httpResponse = httpClient.execute(httpPost); - Assert.assertEquals("text/xml", httpResponse.getFirstHeader("Content-Type").getValue()); - Assert.assertEquals(201, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals("text/xml", httpResponse.getFirstHeader("Content-Type").getValue()); + Assertions.assertEquals(201, httpResponse.getStatusLine().getStatusCode()); wireMockRule1.verify(postRequestedFor(urlEqualTo("/geostore/users/create"))); } } @@ -165,19 +156,18 @@ public void testPOSTUsingWireMockJSON() throws IOException { " \"userName\": \"Jane Doe\"\n" + " }\n" + "}"; - wireMockRule1.addStubMapping( - stubFor( - post(urlEqualTo("/geostore/users/create")) - .withRequestBody(equalToJson(requestBody)) - .willReturn( - aResponse() - .withStatus(201) - .withHeader("Content-Type", "application/json") - .withBody("{\n" + - " \"response\": 5\n" + - "}")))); - - String url = "http://localhost:" + wireMockRule1.port() + "/geostore/users/create"; + wireMockRule1.stubFor( + post(urlEqualTo("/geostore/users/create")) + .withRequestBody(equalToJson(requestBody)) + .willReturn( + aResponse() + .withStatus(201) + .withHeader("Content-Type", "application/json") + .withBody("{\n" + + " \"response\": 5\n" + + "}"))); + + String url = "http://localhost:" + wireMockRule1.getPort() + "/geostore/users/create"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpPost httpPost = new HttpPost(proxyURL); StringEntity stringEntity = new StringEntity(requestBody); @@ -185,8 +175,8 @@ public void testPOSTUsingWireMockJSON() throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpResponse httpResponse = httpClient.execute(httpPost); - Assert.assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue()); - Assert.assertEquals(201, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue()); + Assertions.assertEquals(201, httpResponse.getStatusLine().getStatusCode()); wireMockRule1.verify(postRequestedFor(urlEqualTo("/geostore/users/create"))); } } @@ -198,17 +188,16 @@ public void testPOSTUsingWireMockJSON() throws IOException { public void testPUTUsingWireMock() throws IOException { String requestBody = "5John Doe"; - wireMockRule.addStubMapping( - stubFor( - put(urlEqualTo("/geostore/users/5")) - .withRequestBody(equalToXml(requestBody)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("5")))); - - String url = "http://localhost:" + wireMockRule.port() + "/geostore/users/5"; + wireMockRule.stubFor( + put(urlEqualTo("/geostore/users/5")) + .withRequestBody(equalToXml(requestBody)) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("5"))); + + String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/users/5"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpPut httpPut = new HttpPut(proxyURL); StringEntity stringEntity = new StringEntity(requestBody); @@ -216,7 +205,7 @@ public void testPUTUsingWireMock() throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpResponse httpResponse = httpClient.execute(httpPut); - Assert.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); wireMockRule.verify(putRequestedFor(urlEqualTo("/geostore/users/5"))); } } @@ -231,19 +220,18 @@ public void testNullProxyConfig() throws IOException { System.setProperty("http.proxyHost", ""); System.setProperty("http.proxyPort", ""); - wireMockRule.addStubMapping( - stubFor( - get(urlEqualTo("/geostore/resources")) - .willReturn( - aResponse() - .withStatus(200)))); + wireMockRule.stubFor( + get(urlEqualTo("/geostore/resources")) + .willReturn( + aResponse() + .withStatus(200))); - String url = "http://localhost:" + wireMockRule.port() + "/geostore/resources"; + String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/resources"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpGet httpGet = new HttpGet(proxyURL); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpResponse httpResponse = httpClient.execute(httpGet); - Assert.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/resources"))); } } @@ -255,10 +243,10 @@ public void testNullProxyConfig() throws IOException { public void testHttpDefaultPort() throws IOException { String urlStr = "http://localhost/geoserver/wms?service=wms%26version=1.3.0&request=GetCapabilities"; URL url = Utils.buildURL(urlStr); - Assert.assertEquals(url.getPort(), Utils.DEFAULT_HTTP_PORT); - Assert.assertEquals(url.getProtocol(), "http"); - Assert.assertEquals(url.getHost(), "localhost"); - Assert.assertEquals(url.getFile(), "/geoserver/wms?service=wms%26version=1.3.0&request=GetCapabilities"); + Assertions.assertEquals(url.getPort(), Utils.DEFAULT_HTTP_PORT); + Assertions.assertEquals(url.getProtocol(), "http"); + Assertions.assertEquals(url.getHost(), "localhost"); + Assertions.assertEquals(url.getFile(), "/geoserver/wms?service=wms%26version=1.3.0&request=GetCapabilities"); } /** @@ -268,14 +256,14 @@ public void testHttpDefaultPort() throws IOException { public void testHttpsDefaultPort() throws IOException { String urlStr = "https://georchestra.geo-solutions.it/geoserver/wms?service=WMS%26version=1.3.0&request=GetCapabilities"; URL url = Utils.buildURL(urlStr); - Assert.assertEquals(url.getPort(), Utils.DEFAULT_HTTPS_PORT); - Assert.assertEquals(url.getProtocol(), "https"); - Assert.assertEquals(url.getHost(), "georchestra.geo-solutions.it"); - Assert.assertEquals(url.getFile(), "/geoserver/wms?service=WMS%26version=1.3.0&request=GetCapabilities"); + Assertions.assertEquals(url.getPort(), Utils.DEFAULT_HTTPS_PORT); + Assertions.assertEquals(url.getProtocol(), "https"); + Assertions.assertEquals(url.getHost(), "georchestra.geo-solutions.it"); + Assertions.assertEquals(url.getFile(), "/geoserver/wms?service=WMS%26version=1.3.0&request=GetCapabilities"); } - @AfterClass + @AfterAll public static void stopServer() { try { diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java index d1371aa..59a33b4 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java @@ -29,11 +29,11 @@ import java.util.List; import java.util.Map; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -44,28 +44,29 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; /** * HttpProxyTest class. Test Cases for the HTTPProxy servlet. * * @author Lorenzo Natali at lorenzo.natali@geo-solutions.it */ -public class HttpProxyTest extends Mockito { +public class HttpProxyTest { final ServletConfig servletConfig = mock(ServletConfig.class); ServletContext ctx = mock(ServletContext.class); - Map parameters = new HashMap(); - private List
headers = new ArrayList
(); + Map parameters = new HashMap<>(); + private List headers = new ArrayList<>(); org.apache.http.client.HttpClient mockHttpClient; HTTPProxy proxy; String fakeLocation; - @Before + @BeforeEach public void setUp() { File f = new File(getClass().getClassLoader() .getResource("test-proxy.properties").getFile()); @@ -123,8 +124,8 @@ public HttpGet getGetMethod(URL url) { "http://proxy.com/http-proxy/proxy?url=" + URLEncoder.encode(fakeLocation, "UTF-8")); final byte[] data = servletOutputStream.baos.toByteArray(); - Assert.assertNotNull(data); - Assert.assertTrue(data.length == 0); + Assertions.assertNotNull(data); + Assertions.assertTrue(data.length == 0); } @Test @@ -159,7 +160,7 @@ public HttpPost getPostMethod(URL url) { when(postRequest.getQueryString()).thenReturn("url=https://jsonplaceholder.typicode.com/test/createUser"); when(postRequest.getMethod()).thenReturn("post"); - Enumeration enumeration = Collections.enumeration(Collections.emptyList()); + Enumeration enumeration = Collections.enumeration(Collections.emptyList()); when(postRequest.getHeaderNames()).thenReturn(enumeration); ServletInputStream stream = mock(ServletInputStream.class); @@ -173,8 +174,8 @@ public HttpPost getPostMethod(URL url) { proxy.doPost(postRequest, getResponse); verify(getResponse).setStatus(200); final byte[] data = servletOutputStream.baos.toByteArray(); - Assert.assertNotNull(data); - Assert.assertTrue(data.length != 0); + Assertions.assertNotNull(data); + Assertions.assertTrue(data.length != 0); } } \ No newline at end of file diff --git a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java index ffd6e6a..f95aea1 100644 --- a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java +++ b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java @@ -28,10 +28,10 @@ import java.util.List; import java.util.Map; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -40,23 +40,19 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.entity.StringEntity; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; /** * Tests for request header whitelist and blacklist filtering. */ -public class RequestHeaderFilterTest extends Mockito { +public class RequestHeaderFilterTest { Map parameters = new HashMap<>(); - private List
headers = new ArrayList<>(); - @Before + @BeforeEach public void setUp() { // URL must match one of the reqtypeWhitelist patterns in proxy.properties parameters.put("url", new String[]{"http://sample.com/csw"}); diff --git a/src/test/java/it/geosolutions/httpproxy/StubServletOutputStream.java b/src/test/java/it/geosolutions/httpproxy/StubServletOutputStream.java index d296996..936c990 100644 --- a/src/test/java/it/geosolutions/httpproxy/StubServletOutputStream.java +++ b/src/test/java/it/geosolutions/httpproxy/StubServletOutputStream.java @@ -3,11 +3,17 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import javax.servlet.ServletOutputStream; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; public class StubServletOutputStream extends ServletOutputStream { public ByteArrayOutputStream baos = new ByteArrayOutputStream(); public void write(int i) throws IOException { baos.write(i); + } + public boolean isReady() { + return true; + } + public void setWriteListener(WriteListener writeListener) { } } \ No newline at end of file diff --git a/src/test/java/it/geosolutions/httpproxy/jetty/Start.java b/src/test/java/it/geosolutions/httpproxy/jetty/Start.java index a3d0753..f103864 100644 --- a/src/test/java/it/geosolutions/httpproxy/jetty/Start.java +++ b/src/test/java/it/geosolutions/httpproxy/jetty/Start.java @@ -19,24 +19,20 @@ */ package it.geosolutions.httpproxy.jetty; -import java.io.File; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.mortbay.jetty.Connector; -import org.mortbay.jetty.Server; -import org.mortbay.jetty.bio.SocketConnector; -import org.mortbay.jetty.webapp.WebAppContext; -import org.mortbay.thread.BoundedThreadPool; +import org.eclipse.jetty.ee10.webapp.WebAppContext; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Start class for test using JETTY server. + * Start class for test using embedded Jetty server. * * @author Tobia di Pisa at tobia.dipisa@geo-solutions.it */ public class Start { - private final static Logger LOGGER = Logger.getLogger(Start.class.toString()); + private static final Logger LOGGER = LoggerFactory.getLogger(Start.class); /** * @param args @@ -47,16 +43,7 @@ public static void main(String[] args) { try { jettyServer = new Server(); - // ///////////////////////////////////////////////////// - // Don't even think of serving more than XX requests - // in parallel... we have a limit in our processing - // and memory capacities. - // ///////////////////////////////////////////////////// - - BoundedThreadPool tp = new BoundedThreadPool(); - tp.setMaxThreads(50); - - SocketConnector conn = new SocketConnector(); + ServerConnector connector = new ServerConnector(jettyServer); String portVariable = System.getProperty("jetty.port"); int port = parsePort(portVariable); @@ -64,37 +51,27 @@ public static void main(String[] args) { port = 8080; } - conn.setPort(port); - conn.setThreadPool(tp); - conn.setAcceptQueueSize(100); - jettyServer.setConnectors(new Connector[] { conn }); + connector.setPort(port); + jettyServer.addConnector(connector); - WebAppContext wah = new WebAppContext(); - wah.setContextPath("/http_proxy"); - wah.setWar("src/main/webapp"); - jettyServer.setHandler(wah); - wah.setTempDirectory(new File("target/work")); + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/http_proxy"); + webapp.setWar("src/main/webapp"); + webapp.setTempDirectory(new java.io.File("target/work")); + jettyServer.setHandler(webapp); jettyServer.start(); - - // //////////////////////////////////////////////////////////////////////// - // Use this to test normal stop behavior, that is, to check stuff that - // need to be done on container shutdown (and yes, this will make - // jetty stop just after you started it...) - // jettyServer.stop(); - // //////////////////////////////////////////////////////////////////////// + LOGGER.info("Jetty started on port {}", port); + jettyServer.join(); } catch (Exception e) { - if (LOGGER.isLoggable(Level.SEVERE)) - LOGGER.log(Level.SEVERE, "Could not start the Jetty server: " + e.getMessage(), e); + LOGGER.error("Could not start the Jetty server: " + e.getMessage(), e); if (jettyServer != null) { try { jettyServer.stop(); } catch (Exception e1) { - if (LOGGER.isLoggable(Level.SEVERE)) - LOGGER.log(Level.SEVERE, - "Unable to stop the " + "Jetty server:" + e1.getMessage(), e1); + LOGGER.error("Unable to stop the Jetty server: " + e1.getMessage(), e1); } } } @@ -110,7 +87,7 @@ private static int parsePort(String portVariable) { } try { - return Integer.valueOf(portVariable).intValue(); + return Integer.parseInt(portVariable); } catch (NumberFormatException e) { return -1; } From abb1485d15711f15179217009657631052837236 Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Tue, 12 May 2026 14:46:15 +0200 Subject: [PATCH 02/12] feat: Add Docker support and main entry point for HTTP Proxy --- .gitignore | 3 +- Dockerfile | 22 +++++++ pom.xml | 61 +++++++++++++------ src/main/assembly/fat-jar.xml | 23 +++++++ .../java/it/geosolutions/httpproxy/Main.java | 60 ++++++++++++++++++ 5 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 Dockerfile create mode 100644 src/main/assembly/fat-jar.xml create mode 100644 src/main/java/it/geosolutions/httpproxy/Main.java diff --git a/.gitignore b/.gitignore index 611a9d8..2f4a097 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .project .settings */target/* -target/ \ No newline at end of file +target/ +.idea/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5020776 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# --- Build stage --- +FROM eclipse-temurin:21-jdk-alpine AS build + +RUN apk add --no-cache maven + +WORKDIR /app +COPY pom.xml . +RUN mvn dependency:go-offline -B + +COPY src ./src +RUN mvn clean package -Pdocker -DskipTests -B + +# --- Runtime stage --- +FROM gcr.io/distroless/java21-debian12 + +COPY --from=build /app/target/http-proxy.jar /app/http-proxy.jar + +ENV PORT=8080 +EXPOSE 8080 + +USER nonroot:nonroot +ENTRYPOINT ["java", "-jar", "/app/http-proxy.jar"] diff --git a/pom.xml b/pom.xml index 39ad388..906bd34 100644 --- a/pom.xml +++ b/pom.xml @@ -116,23 +116,6 @@ - - org.mortbay.jetty - maven-jetty-plugin - 6.1.19 - - http_proxy - - - 8080 - 10000 - - - http_proxy - ${project.build.directory}/${project.artifactId}-${project.version} - - - org.apache.maven.plugins @@ -329,6 +312,50 @@ + + + docker + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + compile + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + + it.geosolutions.httpproxy.Main + + + + src/main/assembly/fat-jar.xml + + http-proxy + false + + + + make-assembly + package + + single + + + + + + + + + diff --git a/src/main/assembly/fat-jar.xml b/src/main/assembly/fat-jar.xml new file mode 100644 index 0000000..da608f4 --- /dev/null +++ b/src/main/assembly/fat-jar.xml @@ -0,0 +1,23 @@ + + fat-jar + + jar + + false + + + ${project.build.outputDirectory} + / + + + + + / + true + false + runtime + + + diff --git a/src/main/java/it/geosolutions/httpproxy/Main.java b/src/main/java/it/geosolutions/httpproxy/Main.java new file mode 100644 index 0000000..3a7b878 --- /dev/null +++ b/src/main/java/it/geosolutions/httpproxy/Main.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 - 2011 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.httpproxy; + +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Main entry point for running the HTTP Proxy with embedded Jetty. + * Used for standalone deployment (fat JAR / Docker). + */ +public class Main { + + private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); + + public static void main(String[] args) throws Exception { + int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080")); + + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(port); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + + // Set the proxyPropPath init parameter + String proxyPropPath = System.getenv().getOrDefault("PROXY_PROP_PATH", "/proxy.properties"); + context.setInitParameter("proxyPropPath", proxyPropPath); + + // Register the proxy servlet + context.addServlet(new ServletHolder(new HTTPProxy()), "/proxy/*"); + + server.setHandler(context); + server.start(); + LOGGER.info("HTTP Proxy started on port {}", port); + server.join(); + } +} From a18073bdb7bd20019e66b923d6cfa97f48b63809 Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Tue, 12 May 2026 15:42:31 +0200 Subject: [PATCH 03/12] feat: Add Jetty EE11 Maven plugin to pom.xml --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 906bd34..0e065d6 100644 --- a/pom.xml +++ b/pom.xml @@ -116,6 +116,12 @@ + + org.eclipse.jetty.ee11 + jetty-ee11-maven-plugin + 12.1.9 + + org.apache.maven.plugins From 2fd9c7ad492ee77971a6b8aebc5eb9bc79886010 Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Tue, 12 May 2026 15:43:52 +0200 Subject: [PATCH 04/12] chore: Update .gitignore to include main/assembly directory --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2f4a097..7441674 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .settings */target/* target/ -.idea/ \ No newline at end of file +.idea/ +main/assembly/* \ No newline at end of file From f781a8a42e144768ecf76f87daa87b4250e0217e Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Wed, 20 May 2026 14:53:34 +0200 Subject: [PATCH 05/12] chore: fix indent --- .../httpproxy/HttpProxyIntegrationTests.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java index 6f07770..9370b32 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java @@ -97,12 +97,12 @@ private static void storeProxyConfig() { public void testGETUsingWireMock() throws IOException { wireMockRule.stubFor( - get(urlEqualTo("/geostore/users")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("Some content"))); + get(urlEqualTo("/geostore/users")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("Some content"))); String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/users"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; @@ -189,13 +189,13 @@ public void testPUTUsingWireMock() throws IOException { String requestBody = "5John Doe"; wireMockRule.stubFor( - put(urlEqualTo("/geostore/users/5")) - .withRequestBody(equalToXml(requestBody)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("5"))); + put(urlEqualTo("/geostore/users/5")) + .withRequestBody(equalToXml(requestBody)) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("5"))); String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/users/5"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; @@ -221,10 +221,10 @@ public void testNullProxyConfig() throws IOException { System.setProperty("http.proxyPort", ""); wireMockRule.stubFor( - get(urlEqualTo("/geostore/resources")) - .willReturn( - aResponse() - .withStatus(200))); + get(urlEqualTo("/geostore/resources")) + .willReturn( + aResponse() + .withStatus(200))); String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/resources"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; From f1c018e2945a5ee3403ae997314e95d5bab9bfb4 Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Fri, 29 May 2026 14:51:59 +0200 Subject: [PATCH 06/12] feat: Update CI configuration to use JDK 21 and setup-java@v5 --- .github/workflows/CI.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 12df014..24cc08a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v5 with: - java-version: '11' - distribution: 'adopt' + java-version: '21' + distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B clean install @@ -36,9 +36,9 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Maven Central Repository - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - java-version: '11' + java-version: '21' distribution: 'adopt' server-id: geosolutions server-username: MAVEN_USERNAME From f39f5e76c3dc4a25b0ad888c81b86414586853c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ricchiuti Date: Tue, 16 Jun 2026 15:59:37 +0200 Subject: [PATCH 07/12] Downgrade to JDK 17. --- .github/workflows/CI.yml | 6 +++--- Dockerfile | 4 ++-- pom.xml | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 24cc08a..c4e8566 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up JDK 21 + - name: Set up JDK 17 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '17' distribution: 'temurin' cache: maven - name: Build with Maven @@ -38,7 +38,7 @@ jobs: - name: Set up Maven Central Repository uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '17' distribution: 'adopt' server-id: geosolutions server-username: MAVEN_USERNAME diff --git a/Dockerfile b/Dockerfile index 5020776..5fcb159 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # --- Build stage --- -FROM eclipse-temurin:21-jdk-alpine AS build +FROM eclipse-temurin:17-jdk-alpine AS build RUN apk add --no-cache maven @@ -11,7 +11,7 @@ COPY src ./src RUN mvn clean package -Pdocker -DskipTests -B # --- Runtime stage --- -FROM gcr.io/distroless/java21-debian12 +FROM gcr.io/distroless/java17-debian12 COPY --from=build /app/target/http-proxy.jar /app/http-proxy.jar diff --git a/pom.xml b/pom.xml index 0e065d6..8891042 100644 --- a/pom.xml +++ b/pom.xml @@ -29,8 +29,8 @@ maven-compiler-plugin 3.13.0 - 21 - 21 + 17 + 17 @@ -117,8 +117,8 @@ - org.eclipse.jetty.ee11 - jetty-ee11-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin 12.1.9 @@ -205,7 +205,7 @@ jakarta.servlet jakarta.servlet-api - 6.1.0 + 6.0.0 provided @@ -325,7 +325,7 @@ jakarta.servlet jakarta.servlet-api - 6.1.0 + 6.0.0 compile From d91362a52edbbefb7a8e465d83a411844d8ed4ab Mon Sep 17 00:00:00 2001 From: Alessandro Ricchiuti Date: Tue, 16 Jun 2026 17:38:45 +0200 Subject: [PATCH 08/12] Upgraded http client to version 5. --- pom.xml | 25 +--- .../it/geosolutions/httpproxy/HTTPProxy.java | 130 +++++++++--------- .../geosolutions/httpproxy/HostChecker.java | 4 +- .../httpproxy/HostNameChecker.java | 4 +- .../httpproxy/MethodsChecker.java | 4 +- .../httpproxy/MimeTypeChecker.java | 6 +- .../geosolutions/httpproxy/ProxyCallback.java | 4 +- .../httpproxy/RequestTypeChecker.java | 4 +- .../httpproxy/HttpProxyIntegrationTests.java | 34 ++--- .../geosolutions/httpproxy/HttpProxyTest.java | 38 +++-- .../httpproxy/RequestHeaderFilterTest.java | 33 ++--- 11 files changed, 131 insertions(+), 155 deletions(-) diff --git a/pom.xml b/pom.xml index 8891042..ec79c87 100644 --- a/pom.xml +++ b/pom.xml @@ -145,8 +145,8 @@ UTF-8 2.25.4 - 4.5.14 - 12.1.8 + 5.6.1 + 12.1.10 @@ -179,15 +179,9 @@ - org.apache.httpcomponents - httpclient - ${http-client-version} - - - - org.apache.httpcomponents - httpmime - ${http-client-version} + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5-version} @@ -267,13 +261,8 @@ - org.apache.httpcomponents - httpclient - - - - org.apache.httpcomponents - httpmime + org.apache.httpcomponents.client5 + httpclient5 diff --git a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java index a04a616..a07a5a2 100644 --- a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java +++ b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java @@ -27,6 +27,7 @@ import java.net.URLEncoder; import java.util.ArrayList; import java.util.Enumeration; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -47,26 +48,26 @@ import org.apache.commons.fileupload2.core.FileUploadException; import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.*; -import org.apache.http.conn.routing.HttpRoute; -import org.apache.http.conn.routing.HttpRoutePlanner; -import org.apache.http.entity.InputStreamEntity; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.ContentBody; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.protocol.HttpContext; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.Credentials; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.*; +import org.apache.hc.client5.http.entity.mime.ContentBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.protocol.HttpContext; import org.slf4j.LoggerFactory; /** @@ -101,7 +102,7 @@ public class HTTPProxy extends HttpServlet { /** * An HTTP "user-agent", containing an HTTP state and one or more HTTP connections, to which HTTP methods can be applied. */ - private HttpClient httpClient; + private CloseableHttpClient httpClient; /** @@ -153,7 +154,7 @@ public void init(ServletConfig servletConfig) throws ServletException { * Creates the HttpClient * @return HttpClient */ - public HttpClient createHttpClient() { + public CloseableHttpClient createHttpClient() { if (httpClient != null) return httpClient; @@ -194,8 +195,7 @@ private HttpRoutePlanner getRoutePlanner(final HttpHost httpHost, final HttpHost return new HttpRoutePlanner() { public HttpRoute determineRoute( HttpHost target, - HttpRequest request, - HttpContext context) { + HttpContext context) throws HttpException { LOGGER.info("HTTP proxy target host: " + target); if (isNonProxyHost(target.getHostName())) { LOGGER.info("Returning direct route"); @@ -271,7 +271,7 @@ void onInit(HttpServletRequest request, HttpServletResponse response, URL url) * @param method * @throws IOException */ - void onRemoteResponse(HttpRequestBase method) throws IOException { + void onRemoteResponse(HttpUriRequestBase method) throws IOException { for (ProxyCallback callback : callbacks) { callback.onRemoteResponse(method); } @@ -565,7 +565,7 @@ public void doDelete(HttpServletRequest httpServletRequest, * @param httpServletRequest The {@link HttpServletRequest} that contains the mutlipart POST data to be sent via the {@link PostMethod} * @throws IOException */ - private void handleMultipart(HttpRequestBase methodProxyRequest, + private void handleMultipart(HttpUriRequestBase methodProxyRequest, HttpServletRequest httpServletRequest) throws ServletException, IOException { // //////////////////////////////////////////// @@ -635,7 +635,10 @@ private void handleMultipart(HttpRequestBase methodProxyRequest, // content-type string to reflect the new chunk boundary string // //////////////////////////////////////////////////////////////////////// - methodProxyRequest.setHeader(entity.getContentType()); + String contentType = entity.getContentType(); + if (contentType != null) { + methodProxyRequest.setHeader("Content-Type", contentType); + } } catch (FileUploadException fileUploadException) { throw new ServletException(fileUploadException); @@ -649,14 +652,20 @@ private void handleMultipart(HttpRequestBase methodProxyRequest, * @param httpServletRequest The {@link HttpServletRequest} that contains the POST data to be sent via the {@link PostMethod} * @throws IOException */ - private void handleStandard(HttpRequestBase methodProxyRequest, + private void handleStandard(HttpUriRequestBase methodProxyRequest, HttpServletRequest httpServletRequest) throws IOException { try { + String incomingCT = httpServletRequest.getContentType(); + ContentType contentType = incomingCT != null + ? ContentType.parse(incomingCT) + : ContentType.DEFAULT_BINARY; if (methodProxyRequest instanceof HttpPost) { - ((HttpPost) methodProxyRequest).setEntity(new InputStreamEntity(httpServletRequest.getInputStream())); + ((HttpPost) methodProxyRequest).setEntity( + new InputStreamEntity(httpServletRequest.getInputStream(), contentType)); } else if (methodProxyRequest instanceof HttpPut) { - ((HttpPut) methodProxyRequest).setEntity(new InputStreamEntity(httpServletRequest.getInputStream())); + ((HttpPut) methodProxyRequest).setEntity( + new InputStreamEntity(httpServletRequest.getInputStream(), contentType)); } } catch (IOException e) { @@ -672,26 +681,20 @@ private void handleStandard(HttpRequestBase methodProxyRequest, * @param digest * @throws ServletException Can be thrown to indicate that another error has occurred */ - private void executeProxyRequest(HttpRequestBase httpMethodProxyRequest, + private void executeProxyRequest(HttpUriRequestBase httpMethodProxyRequest, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String user, String password) throws ServletException { if (user != null && password != null) { - Credentials credentials = new UsernamePasswordCredentials(user, password); + Credentials credentials = new UsernamePasswordCredentials(user, password.toCharArray()); credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(AuthScope.ANY, credentials); + credsProvider.setCredentials(new AuthScope(null, -1), credentials); clientBuilder.setDefaultCredentialsProvider(credsProvider); httpClient = null; httpClient = createHttpClient(); } - try { - - // ////////////////////////// - // Execute the request - // ////////////////////////// - - HttpResponse response = httpClient.execute(httpMethodProxyRequest); + try (ClassicHttpResponse response = httpClient.execute(httpMethodProxyRequest)) { onRemoteResponse(httpMethodProxyRequest); @@ -752,26 +755,24 @@ && getStatusCode(response) < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) { // Pass response headers back to the client // ///////////////////////////////////////////// - if (response.getAllHeaders() != null) { - Header[] headerArrayResponse = response.getAllHeaders(); - - for (Header header : headerArrayResponse) { + Iterator
headerIt = response.headerIterator(); + while (headerIt.hasNext()) { + Header header = headerIt.next(); - // ///////////////////////// - // Skip GZIP Responses - // ///////////////////////// + // ///////////////////////// + // Skip GZIP Responses + // ///////////////////////// - if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_ACCEPT_ENCODING) - && header.getValue().toLowerCase().contains("gzip")) - continue; - else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_CONTENT_ENCODING) - && header.getValue().toLowerCase().contains("gzip")) - continue; - else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) - continue; - else - httpServletResponse.setHeader(header.getName(), header.getValue()); - } + if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_ACCEPT_ENCODING) + && header.getValue().toLowerCase().contains("gzip")) + continue; + else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_CONTENT_ENCODING) + && header.getValue().toLowerCase().contains("gzip")) + continue; + else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) + continue; + else + httpServletResponse.setHeader(header.getName(), header.getValue()); } // /////////////////////////////////// @@ -792,15 +793,12 @@ else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) } catch (Exception e) { LOGGER.error("Error executing HTTP method", e); - } finally { - httpMethodProxyRequest.releaseConnection(); } } - int getStatusCode(HttpResponse response) { + int getStatusCode(ClassicHttpResponse response) { if (response != null) { - StatusLine statusLine = response.getStatusLine(); - return statusLine.getStatusCode(); + return response.getCode(); } else { return -1; } @@ -814,7 +812,7 @@ int getStatusCode(HttpResponse response) { * @return ProxyInfo */ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServletRequest, - HttpRequestBase httpMethodProxyRequest) { + HttpUriRequestBase httpMethodProxyRequest) { final String proxyHost = url.getHost(); final int proxyPort = url.getPort(); @@ -932,7 +930,7 @@ public int getMaxFileUploadSize() { /** * @return the client */ - public HttpClient getHttpClient() { + public CloseableHttpClient getHttpClient() { return httpClient; } @@ -941,7 +939,7 @@ public HttpClient getHttpClient() { * * @param httpClient the client to set */ - public void setHttpClient(HttpClient httpClient) { + public void setHttpClient(CloseableHttpClient httpClient) { this.httpClient = httpClient; } } diff --git a/src/main/java/it/geosolutions/httpproxy/HostChecker.java b/src/main/java/it/geosolutions/httpproxy/HostChecker.java index 822c5f4..8133f65 100644 --- a/src/main/java/it/geosolutions/httpproxy/HostChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/HostChecker.java @@ -26,7 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; /** * HostChecker class for host check. @@ -72,7 +72,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) throws IOException { } /* diff --git a/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java b/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java index 8d85943..3f3cc01 100644 --- a/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java @@ -26,7 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; /** * HostNameChecker class for hostname check. @@ -72,7 +72,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) throws IOException { } /* diff --git a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java index 821a893..84c116f 100644 --- a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java @@ -26,7 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; /** * MethodsChecker class for http methods check. @@ -72,7 +72,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) throws IOException { } /* diff --git a/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java b/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java index 59d8478..25e4d4d 100644 --- a/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java @@ -26,8 +26,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.Header; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.Header; /** * MimeTypeChecker class for the mimetype check. @@ -60,7 +60,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) throws IOException { Set mimeTypes = config.getMimetypeWhitelist(); if (mimeTypes != null && mimeTypes.size() > 0) { diff --git a/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java b/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java index 2b4c62e..fae28af 100644 --- a/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java +++ b/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java @@ -25,7 +25,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; /** * A pluggable callback that can perform checks or alter the request. @@ -48,7 +48,7 @@ void onRequest(HttpServletRequest request, HttpServletResponse response, URL url * @param method * @throws IOException */ - void onRemoteResponse(HttpRequestBase method) throws IOException; + void onRemoteResponse(HttpUriRequestBase method) throws IOException; /** * Called when the request is fully proxied, can be used for cleanup actions diff --git a/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java b/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java index b48a08d..ef96999 100644 --- a/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java @@ -29,7 +29,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; /** * RequestTypeChecker class for request type check. @@ -93,7 +93,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) throws IOException { } /* diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java index 9370b32..049f92e 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java @@ -6,13 +6,13 @@ import org.eclipse.jetty.ee10.webapp.WebAppContext; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.LoggerFactory; @@ -108,8 +108,8 @@ public void testGETUsingWireMock() throws IOException { String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpGet httpGet = new HttpGet(proxyURL); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = httpClient.execute(httpGet); - Assertions.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + ClassicHttpResponse httpResponse = httpClient.execute(httpGet); + Assertions.assertEquals(200, httpResponse.getCode()); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/users"))); } } @@ -137,9 +137,9 @@ public void testPOSTUsingWireMockXML() throws IOException { httpPost.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = httpClient.execute(httpPost); + ClassicHttpResponse httpResponse = httpClient.execute(httpPost); Assertions.assertEquals("text/xml", httpResponse.getFirstHeader("Content-Type").getValue()); - Assertions.assertEquals(201, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals(201, httpResponse.getCode()); wireMockRule1.verify(postRequestedFor(urlEqualTo("/geostore/users/create"))); } } @@ -174,9 +174,9 @@ public void testPOSTUsingWireMockJSON() throws IOException { httpPost.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = httpClient.execute(httpPost); + ClassicHttpResponse httpResponse = httpClient.execute(httpPost); Assertions.assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue()); - Assertions.assertEquals(201, httpResponse.getStatusLine().getStatusCode()); + Assertions.assertEquals(201, httpResponse.getCode()); wireMockRule1.verify(postRequestedFor(urlEqualTo("/geostore/users/create"))); } } @@ -204,8 +204,8 @@ public void testPUTUsingWireMock() throws IOException { httpPut.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = httpClient.execute(httpPut); - Assertions.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + ClassicHttpResponse httpResponse = httpClient.execute(httpPut); + Assertions.assertEquals(200, httpResponse.getCode()); wireMockRule.verify(putRequestedFor(urlEqualTo("/geostore/users/5"))); } } @@ -230,8 +230,8 @@ public void testNullProxyConfig() throws IOException { String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpGet httpGet = new HttpGet(proxyURL); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = httpClient.execute(httpGet); - Assertions.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + ClassicHttpResponse httpResponse = httpClient.execute(httpGet); + Assertions.assertEquals(200, httpResponse.getCode()); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/resources"))); } } diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java index 59a33b4..47afd20 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java @@ -35,15 +35,13 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.message.BasicHeader; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -62,7 +60,7 @@ public class HttpProxyTest { Map parameters = new HashMap<>(); private List headers = new ArrayList<>(); - org.apache.http.client.HttpClient mockHttpClient; + CloseableHttpClient mockHttpClient; HTTPProxy proxy; String fakeLocation; @@ -84,12 +82,10 @@ public void testRedirectGet() throws Exception { // mock redirect response final HttpGet mockGetMethod = mock(HttpGet.class); - HttpResponse response = mock(HttpResponse.class); - StatusLine statusLine = mock(StatusLine.class); - when(statusLine.getStatusCode()).thenReturn(302); - when(response.getStatusLine()).thenReturn(statusLine); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getCode()).thenReturn(302); - mockHttpClient = mock(HttpClient.class); + mockHttpClient = mock(CloseableHttpClient.class); when(mockHttpClient.execute(mockGetMethod)).thenReturn(response); fakeLocation = "http://newURL.com/"; @@ -133,15 +129,13 @@ public void testPost() throws Exception { // mock post response final HttpPost mockPostMethod = mock(HttpPost.class); - HttpResponse response = mock(HttpResponse.class); - StatusLine statusLine = mock(StatusLine.class); - when(statusLine.getStatusCode()).thenReturn(200); - when(response.getStatusLine()).thenReturn(statusLine); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getCode()).thenReturn(200); HttpEntity stringEntity = new StringEntity("user created"); when(response.getEntity()).thenReturn(stringEntity); - when(mockPostMethod.getAllHeaders()).thenReturn(new Header[]{}); - mockHttpClient = mock(HttpClient.class); + when(response.headerIterator()).thenReturn(Collections.emptyList().iterator()); + mockHttpClient = mock(CloseableHttpClient.class); when(mockHttpClient.execute(mockPostMethod)).thenReturn(response); proxy = new HTTPProxy() { @@ -178,4 +172,4 @@ public HttpPost getPostMethod(URL url) { Assertions.assertTrue(data.length != 0); } -} \ No newline at end of file +} diff --git a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java index f95aea1..27e51e4 100644 --- a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java +++ b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java @@ -33,13 +33,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.entity.StringEntity; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.io.entity.StringEntity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -62,7 +60,7 @@ public void setUp() { * Helper to create a proxy configured with the given properties file resource. */ private HTTPProxy createProxy(String propertiesResource, final HttpGet mockGetMethod, - HttpClient mockHttpClient) throws Exception { + CloseableHttpClient mockHttpClient) throws Exception { File f = new File(getClass().getClassLoader() .getResource(propertiesResource).getFile()); @@ -87,22 +85,19 @@ public HttpGet getGetMethod(URL url) { /** * Build a mock GET that returns a 200 with the given body. */ - private HttpGet buildMockGet(HttpClient mockHttpClient) throws Exception { + private HttpGet buildMockGet(CloseableHttpClient mockHttpClient) throws Exception { HttpGet mockGetMethod = mock(HttpGet.class); - HttpResponse response = mock(HttpResponse.class); - StatusLine statusLine = mock(StatusLine.class); - when(statusLine.getStatusCode()).thenReturn(200); - when(response.getStatusLine()).thenReturn(statusLine); - HttpEntity entity = new StringEntity("OK"); - when(response.getEntity()).thenReturn(entity); - when(response.getAllHeaders()).thenReturn(new Header[]{}); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getCode()).thenReturn(200); + when(response.getEntity()).thenReturn(new StringEntity("OK")); + when(response.headerIterator()).thenReturn(Collections.
emptyList().iterator()); when(mockHttpClient.execute(mockGetMethod)).thenReturn(response); return mockGetMethod; } @Test public void testBlacklistRemovesHeaders() throws Exception { - HttpClient mockHttpClient = mock(HttpClient.class); + CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); HTTPProxy proxy = createProxy("proxy.properties", @@ -138,7 +133,7 @@ public void testBlacklistRemovesHeaders() throws Exception { @Test public void testWhitelistOnlyForwardsAllowedHeaders() throws Exception { - HttpClient mockHttpClient = mock(HttpClient.class); + CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); HTTPProxy proxy = createProxy("proxy.properties", @@ -178,7 +173,7 @@ public void testBlacklistTakesPrecedenceOverWhitelist() throws Exception { // and blacklist (X-Secret,Cookie). // A header NOT in either list should be blocked by the whitelist. // A header in the blacklist should always be blocked even if it were whitelisted. - HttpClient mockHttpClient = mock(HttpClient.class); + CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); HTTPProxy proxy = createProxy("proxy.properties", From c89887caadd1e1fa9963190b39e259d1053a287b Mon Sep 17 00:00:00 2001 From: Alessandro Ricchiuti Date: Tue, 16 Jun 2026 17:40:22 +0200 Subject: [PATCH 09/12] Upgraded project dependencies. --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index ec79c87..434d2cc 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ maven-compiler-plugin - 3.13.0 + 3.15.0 17 17 @@ -46,7 +46,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.4.2 + 3.5.0 make-a-jar @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-install-plugin - 3.1.3 + 3.1.4 install @@ -82,7 +82,7 @@ org.apache.maven.plugins maven-deploy-plugin - 3.1.3 + 3.1.4 deploy @@ -107,7 +107,7 @@ org.apache.maven.plugins maven-war-plugin - 3.4.0 + 3.5.1 ${basedir}/src/main/webapp/WEB-INF/web.xml @@ -119,14 +119,14 @@ org.eclipse.jetty.ee10 jetty-ee10-maven-plugin - 12.1.9 + 12.1.10 org.apache.maven.plugins maven-release-plugin - 3.1.1 + 3.3.1 v@{project.version} @@ -187,13 +187,13 @@ commons-io commons-io - 2.18.0 + 2.22.0 commons-codec commons-codec - 1.17.1 + 1.22.0 @@ -212,14 +212,14 @@ org.mockito mockito-core - 5.14.2 + 5.23.0 test org.mockito mockito-junit-jupiter - 5.14.2 + 5.23.0 test @@ -323,7 +323,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.7.1 + 3.8.0 From 338f28b08fac86e469bb2bbe6ad194b2e033bb56 Mon Sep 17 00:00:00 2001 From: Alessandro Ricchiuti Date: Thu, 18 Jun 2026 11:18:54 +0200 Subject: [PATCH 10/12] Refactoring and cleanup. --- .../it/geosolutions/httpproxy/HTTPProxy.java | 304 +++++++++--------- .../geosolutions/httpproxy/HostChecker.java | 26 +- .../httpproxy/HostNameChecker.java | 23 +- .../httpproxy/MethodsChecker.java | 23 +- .../httpproxy/MimeTypeChecker.java | 38 ++- .../geosolutions/httpproxy/ProxyCallback.java | 15 +- .../geosolutions/httpproxy/ProxyConfig.java | 81 +++-- .../it/geosolutions/httpproxy/ProxyInfo.java | 13 +- .../httpproxy/RequestTypeChecker.java | 36 +-- .../java/it/geosolutions/httpproxy/Utils.java | 17 +- .../httpproxy/HttpProxyIntegrationTests.java | 135 ++++---- .../geosolutions/httpproxy/HttpProxyTest.java | 62 ++-- .../httpproxy/RequestHeaderFilterTest.java | 51 +-- .../geosolutions/httpproxy/jetty/Start.java | 4 +- 14 files changed, 414 insertions(+), 414 deletions(-) diff --git a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java index a07a5a2..6bf5127 100644 --- a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java +++ b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java @@ -19,22 +19,6 @@ */ package it.geosolutions.httpproxy; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; @@ -42,27 +26,28 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.commons.fileupload2.core.DiskFileItemFactory; import org.apache.commons.fileupload2.core.FileItem; import org.apache.commons.fileupload2.core.FileUploadException; import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; - +import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; -import org.apache.hc.client5.http.classic.methods.*; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.entity.mime.ContentBody; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; @@ -70,6 +55,21 @@ import org.apache.hc.core5.http.protocol.HttpContext; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * HTTPProxy class. * @@ -87,11 +87,6 @@ public class HTTPProxy extends HttpServlet { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(HTTPProxy.class); - /** - * The maximum size for uploaded files in bytes. Default value is 5MB. - */ - private int maxFileUploadSize = Utils.DEFAULT_MAX_FILE_UPLOAD_SIZE; - /** * An Apache commons HTTP client backed by a multithreaded connection manager that allows to reuse connections to the backing server and to limit * the max number of concurrent connections. @@ -122,6 +117,7 @@ public class HTTPProxy extends HttpServlet { * * @param servletConfig The Servlet configuration passed in by the servlet conatiner */ + @Override public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); @@ -142,7 +138,7 @@ public void init(ServletConfig servletConfig) throws ServletException { // will be a pluggable lookup). // ////////////////////////////////////////// - callbacks = new ArrayList(); + callbacks = new ArrayList<>(); callbacks.add(new MimeTypeChecker(proxyConfig)); callbacks.add(new HostNameChecker(proxyConfig)); callbacks.add(new RequestTypeChecker(proxyConfig)); @@ -159,9 +155,9 @@ public CloseableHttpClient createHttpClient() { return httpClient; final HttpHost httpHost = getHost("http.proxyHost", "http.proxyPort"); - LOGGER.debug("HTTP proxy host: " + httpHost); + LOGGER.debug("HTTP proxy host: {}", httpHost); final HttpHost httpsHost = getHost("https.proxyHost", "https.proxyPort"); - LOGGER.debug("HTTPS proxy host: " + httpsHost); + LOGGER.debug("HTTPS proxy host: {}", httpsHost); HttpRoutePlanner routePlanner = getRoutePlanner(httpHost, httpsHost); @@ -196,7 +192,7 @@ private HttpRoutePlanner getRoutePlanner(final HttpHost httpHost, final HttpHost public HttpRoute determineRoute( HttpHost target, HttpContext context) throws HttpException { - LOGGER.info("HTTP proxy target host: " + target); + LOGGER.info("HTTP proxy target host: {}", target); if (isNonProxyHost(target.getHostName())) { LOGGER.info("Returning direct route"); // Return direct route @@ -239,14 +235,14 @@ private boolean isNonProxyHost(String host) { if (nonProxyHostProp.endsWith("\"")) { nonProxyHostProp = nonProxyHostProp.substring(0, nonProxyHostProp.length() - 1); } - LOGGER.info("http.nonProxyHosts value: " + nonProxyHostProp); + LOGGER.info("http.nonProxyHosts value: {}", nonProxyHostProp); for (String token : nonProxyHostProp.split("\\|")) { String str = token.trim(); str = str.replace("*", "[\\w-]*"); Pattern pattern = Pattern.compile(str); Matcher matcher = pattern.matcher(host); if (matcher.matches()) { - LOGGER.info("Non proxy host matched for: " + str); + LOGGER.info("Non proxy host matched for: {}", str); isNonProxyHost = true; break; } @@ -292,13 +288,15 @@ void onFinish() throws IOException { * @param httpServletRequest The {@link HttpServletRequest} object passed in by the servlet engine representing the client request to be proxied * @param httpServletResponse The {@link HttpServletResponse} object by which we can send a proxied response to the client */ + @Override public void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { try { URL url = null; - String user = null, password = null; + String user = null; + String password = null; Set> entrySet = httpServletRequest.getParameterMap().entrySet(); @@ -356,12 +354,14 @@ protected HttpGet getGetMethod(URL url) { * @param httpServletRequest The {@link HttpServletRequest} object passed in by the servlet engine representing the client request to be proxied * @param httpServletResponse The {@link HttpServletResponse} object by which we can send a proxied response to the client */ + @Override public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { try { URL url = null; - String user = null, password = null; + String user = null; + String password = null; //Parse the queryString to not read the request body calling getParameter from httpServletRequest // so the method can simply forward the request body Map pars = splitQuery(httpServletRequest.getQueryString()); @@ -431,13 +431,15 @@ protected HttpPost getPostMethod(URL url) { * @param httpServletRequest The {@link HttpServletRequest} object passed in by the servlet engine representing the client request to be proxied * @param httpServletResponse The {@link HttpServletResponse} object by which we can send a proxied response to the client */ + @Override public void doPut(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { try { URL url = null; - String user = null, password = null; + String user = null; + String password = null; //Parse the queryString to not read the request body calling getParameter from httpServletRequest // so the method can simply forward the request body Map pars = splitQuery(httpServletRequest.getQueryString()); @@ -503,12 +505,14 @@ public void doPut(HttpServletRequest httpServletRequest, HttpServletResponse htt * @param httpServletRequest The {@link HttpServletRequest} object passed in by the servlet engine representing the client request to be proxied * @param httpServletResponse The {@link HttpServletResponse} object by which we can send a proxied response to the client */ + @Override public void doDelete(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { try { URL url = null; - String user = null, password = null; + String user = null; + String password = null; //Parse the queryString to not read the request body calling getParameter from httpServletRequest Map pars = splitQuery(httpServletRequest.getQueryString()); @@ -559,11 +563,11 @@ public void doDelete(HttpServletRequest httpServletRequest, } /** - * Sets up the given {@link PostMethod} to send the same multipart POST data as was sent in the given {@link HttpServletRequest} + * Sets up the given {@link HttpUriRequestBase} to send the same multipart POST data as was sent in the given {@link HttpServletRequest} * - * @param postMethodProxyRequest The {@link PostMethod} that we are configuring to send a multipart POST request - * @param httpServletRequest The {@link HttpServletRequest} that contains the mutlipart POST data to be sent via the {@link PostMethod} - * @throws IOException + * @param methodProxyRequest The {@link HttpUriRequestBase} that we are configuring to send a multipart POST request + * @param httpServletRequest The {@link HttpServletRequest} that contains the mutlipart POST data to be sent via the {@link HttpUriRequestBase} + * @throws IOException */ private void handleMultipart(HttpUriRequestBase methodProxyRequest, HttpServletRequest httpServletRequest) throws ServletException, IOException { @@ -618,10 +622,10 @@ private void handleMultipart(HttpUriRequestBase methodProxyRequest, HttpEntity entity = multipartEntityBuilder.build(); - if (methodProxyRequest instanceof HttpPost) { - ((HttpPost) methodProxyRequest).setEntity(entity); - } else if (methodProxyRequest instanceof HttpPut) { - ((HttpPut) methodProxyRequest).setEntity(entity); + if (methodProxyRequest instanceof HttpPost httpPost) { + httpPost.setEntity(entity); + } else if (methodProxyRequest instanceof HttpPut httpPut) { + httpPut.setEntity(entity); } // //////////////////////////////////////////////////////////////////////// @@ -654,22 +658,15 @@ private void handleMultipart(HttpUriRequestBase methodProxyRequest, */ private void handleStandard(HttpUriRequestBase methodProxyRequest, HttpServletRequest httpServletRequest) throws IOException { - try { - String incomingCT = httpServletRequest.getContentType(); - ContentType contentType = incomingCT != null - ? ContentType.parse(incomingCT) - : ContentType.DEFAULT_BINARY; - - if (methodProxyRequest instanceof HttpPost) { - ((HttpPost) methodProxyRequest).setEntity( - new InputStreamEntity(httpServletRequest.getInputStream(), contentType)); - } else if (methodProxyRequest instanceof HttpPut) { - ((HttpPut) methodProxyRequest).setEntity( - new InputStreamEntity(httpServletRequest.getInputStream(), contentType)); - } - - } catch (IOException e) { - throw new IOException(e); + String incomingCT = httpServletRequest.getContentType(); + ContentType contentType = incomingCT != null + ? ContentType.parse(incomingCT) + : ContentType.DEFAULT_BINARY; + + if (methodProxyRequest instanceof HttpPost httpPost) { + httpPost.setEntity(new InputStreamEntity(httpServletRequest.getInputStream(), contentType)); + } else if (methodProxyRequest instanceof HttpPut httpPut) { + httpPut.setEntity(new InputStreamEntity(httpServletRequest.getInputStream(), contentType)); } } @@ -679,11 +676,10 @@ private void handleStandard(HttpUriRequestBase methodProxyRequest, * @param httpMethodProxyRequest An object representing the proxy request to be made * @param httpServletResponse An object by which we can send the proxied response back to the client * @param digest - * @throws ServletException Can be thrown to indicate that another error has occurred */ private void executeProxyRequest(HttpUriRequestBase httpMethodProxyRequest, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, - String user, String password) throws ServletException { + String user, String password) { if (user != null && password != null) { Credentials credentials = new UsernamePasswordCredentials(user, password.toCharArray()); @@ -694,103 +690,105 @@ private void executeProxyRequest(HttpUriRequestBase httpMethodProxyRequest, httpClient = createHttpClient(); } - try (ClassicHttpResponse response = httpClient.execute(httpMethodProxyRequest)) { + try { + httpClient.execute(httpMethodProxyRequest, response -> { - onRemoteResponse(httpMethodProxyRequest); + onRemoteResponse(httpMethodProxyRequest); - // //////////////////////////////////////////////////////////////////////////////// - // Check if the proxy response is a redirect - // The following code is adapted from - // org.tigris.noodle.filters.CheckForRedirect - // Hooray for open source software - // //////////////////////////////////////////////////////////////////////////////// + // //////////////////////////////////////////////////////////////////////////////// + // Check if the proxy response is a redirect + // The following code is adapted from + // org.tigris.noodle.filters.CheckForRedirect + // Hooray for open source software + // //////////////////////////////////////////////////////////////////////////////// - if (getStatusCode(response) >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */ - && getStatusCode(response) < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) { + if (getStatusCode(response) >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */ + && getStatusCode(response) < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) { - String stringStatusCode = Integer.toString(getStatusCode(response)); - String stringLocation = httpMethodProxyRequest.getFirstHeader( - Utils.LOCATION_HEADER).getValue(); + String stringStatusCode = Integer.toString(getStatusCode(response)); + String stringLocation = httpMethodProxyRequest.getFirstHeader( + Utils.LOCATION_HEADER).getValue(); - if (stringLocation == null) { - throw new ServletException("Received status code: " + stringStatusCode - + " but no " + Utils.LOCATION_HEADER - + " header was found in the response"); + if (stringLocation == null) { + throw new IOException("Received status code: " + stringStatusCode + + " but no " + Utils.LOCATION_HEADER + + " header was found in the response"); + } + + // ///////////////////////////////////////////// + // Modify the redirect to go to this proxy + // servlet rather that the proxied host + // ///////////////////////////////////////////// + + String redirectURL = httpServletRequest.getRequestURL() + "?url=" + URLEncoder.encode(stringLocation, StandardCharsets.UTF_8); + httpServletResponse.sendRedirect(redirectURL); + LOGGER.info("redirected to: {}", redirectURL); + return null; + + } else if (getStatusCode(response) == HttpServletResponse.SC_NOT_MODIFIED) { + + // /////////////////////////////////////////////////////////////// + // 304 needs special handling. See: + // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304 + // We get a 304 whenever passed an 'If-Modified-Since' + // header and the data on disk has not changed; server + // responds w/ a 304 saying I'm not going to send the + // body because the file has not changed. + // /////////////////////////////////////////////////////////////// + + httpServletResponse.setIntHeader(Utils.CONTENT_LENGTH_HEADER_NAME, 0); + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return null; } // ///////////////////////////////////////////// - // Modify the redirect to go to this proxy - // servlet rather that the proxied host + // Pass the response code back to the client // ///////////////////////////////////////////// - String redirectURL = httpServletRequest.getRequestURL() + "?url=" + URLEncoder.encode(stringLocation, "UTF-8"); - httpServletResponse.sendRedirect(redirectURL); - LOGGER.info("redirected to:" + redirectURL); - return; - - } else if (getStatusCode(response) == HttpServletResponse.SC_NOT_MODIFIED) { - - // /////////////////////////////////////////////////////////////// - // 304 needs special handling. See: - // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304 - // We get a 304 whenever passed an 'If-Modified-Since' - // header and the data on disk has not changed; server - // responds w/ a 304 saying I'm not going to send the - // body because the file has not changed. - // /////////////////////////////////////////////////////////////// - - httpServletResponse.setIntHeader(Utils.CONTENT_LENGTH_HEADER_NAME, 0); - httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - - return; - } - - // ///////////////////////////////////////////// - // Pass the response code back to the client - // ///////////////////////////////////////////// + httpServletResponse.setStatus(getStatusCode(response)); - httpServletResponse.setStatus(getStatusCode(response)); - - // ///////////////////////////////////////////// - // Pass response headers back to the client - // ///////////////////////////////////////////// - - Iterator
headerIt = response.headerIterator(); - while (headerIt.hasNext()) { - Header header = headerIt.next(); - - // ///////////////////////// - // Skip GZIP Responses - // ///////////////////////// + // ///////////////////////////////////////////// + // Pass response headers back to the client + // ///////////////////////////////////////////// - if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_ACCEPT_ENCODING) - && header.getValue().toLowerCase().contains("gzip")) - continue; - else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_CONTENT_ENCODING) - && header.getValue().toLowerCase().contains("gzip")) - continue; - else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) - continue; - else - httpServletResponse.setHeader(header.getName(), header.getValue()); - } + var headerIt = response.headerIterator(); + while (headerIt.hasNext()) { + var header = headerIt.next(); + + // ///////////////////////// + // Skip GZIP Responses + // ///////////////////////// + + if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_ACCEPT_ENCODING) + && header.getValue().toLowerCase().contains("gzip")) + continue; + else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_CONTENT_ENCODING) + && header.getValue().toLowerCase().contains("gzip")) + continue; + else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) + continue; + else + httpServletResponse.setHeader(header.getName(), header.getValue()); + } - // /////////////////////////////////// - // Send the content to the client - // /////////////////////////////////// + // /////////////////////////////////// + // Send the content to the client + // /////////////////////////////////// - try (InputStream inputStreamServerResponse = response.getEntity().getContent()) { - if (inputStreamServerResponse != null) { - byte[] b = new byte[proxyConfig.getDefaultStreamByteSize()]; + try (InputStream inputStreamServerResponse = response.getEntity().getContent()) { + if (inputStreamServerResponse != null) { + byte[] b = new byte[proxyConfig.getDefaultStreamByteSize()]; - int read; - ServletOutputStream out = httpServletResponse.getOutputStream(); - while ((read = inputStreamServerResponse.read(b)) > 0) { - out.write(b, 0, read); + int read; + ServletOutputStream out = httpServletResponse.getOutputStream(); + while ((read = inputStreamServerResponse.read(b)) > 0) { + out.write(b, 0, read); + } } } - } + return null; + }); } catch (Exception e) { LOGGER.error("Error executing HTTP method", e); } @@ -824,8 +822,6 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet // names sent by the client. // //////////////////////////////////////// - Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames(); - // //////////////////////////////////////// // Load header whitelist/blacklist for // filtering forwarded request headers. @@ -834,8 +830,7 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet Set headerWhitelist = proxyConfig.getRequestHeaderWhitelist(); Set headerBlacklist = proxyConfig.getRequestHeaderBlacklist(); - while (enumerationOfHeaderNames.hasMoreElements()) { - String stringHeaderName = enumerationOfHeaderNames.nextElement(); + for (String stringHeaderName : Collections.list(httpServletRequest.getHeaderNames())) { if (stringHeaderName.equalsIgnoreCase(Utils.CONTENT_LENGTH_HEADER_NAME)) continue; @@ -869,10 +864,7 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet // Thus, we get an Enumeration of the header values sent by the client // //////////////////////////////////////////////////////////////////////// - Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName); - - while (enumerationOfHeaderValues.hasMoreElements()) { - String stringHeaderValue = enumerationOfHeaderValues.nextElement(); + for (String stringHeaderValue : Collections.list(httpServletRequest.getHeaders(stringHeaderName))) { // //////////////////////////////////////////////////////////////// // In case the proxy host is running multiple virtual servers, @@ -909,13 +901,15 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet return proxyInfo; } - private Map splitQuery(String query) throws UnsupportedEncodingException { - Map query_pairs = new LinkedHashMap(); + private Map splitQuery(String query) { + Map query_pairs = new LinkedHashMap<>(); - String[] pairs = query.split("&"); - for (String pair : pairs) { + for (String pair : query.split("&")) { int idx = pair.indexOf("="); - query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + query_pairs.put( + URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8), + URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8) + ); } return query_pairs; } @@ -924,7 +918,7 @@ private Map splitQuery(String query) throws UnsupportedEncodingE * @return int the maximum file upload size. */ public int getMaxFileUploadSize() { - return maxFileUploadSize; + return Utils.DEFAULT_MAX_FILE_UPLOAD_SIZE; } /** diff --git a/src/main/java/it/geosolutions/httpproxy/HostChecker.java b/src/main/java/it/geosolutions/httpproxy/HostChecker.java index 8133f65..718b1ae 100644 --- a/src/main/java/it/geosolutions/httpproxy/HostChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/HostChecker.java @@ -19,18 +19,17 @@ */ package it.geosolutions.httpproxy; -import java.io.IOException; -import java.net.URL; -import java.util.Set; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import java.io.IOException; +import java.net.URL; +import java.util.Set; + /** * HostChecker class for host check. - * + * * @author Tobia Di Pisa at tobia.dipisa@geo-solutions.it */ public class HostChecker implements ProxyCallback { @@ -46,7 +45,7 @@ public HostChecker(ProxyConfig config) { /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) @@ -57,30 +56,30 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, // Check the whitelist of hosts // //////////////////////////////// - if (hosts != null && hosts.size() > 0) { + if (hosts != null && !hosts.isEmpty()) { String host = getRemoteAddr(request); if (!hosts.contains(host)) { throw new HttpErrorException(403, "Client Host " + host - + " is not among the ones allowed for this proxy"); + + " is not among the ones allowed for this proxy"); } } } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpUriRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) { } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onFinish() */ - public void onFinish() throws IOException { + public void onFinish() { } /** @@ -96,5 +95,4 @@ private String getRemoteAddr(HttpServletRequest req) { return req.getRemoteAddr(); } } - } diff --git a/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java b/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java index 3f3cc01..429a9c7 100644 --- a/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/HostNameChecker.java @@ -19,18 +19,17 @@ */ package it.geosolutions.httpproxy; -import java.io.IOException; -import java.net.URL; -import java.util.Set; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import java.io.IOException; +import java.net.URL; +import java.util.Set; + /** * HostNameChecker class for hostname check. - * + * * @author Tobia Di Pisa at tobia.dipisa@geo-solutions.it */ public class HostNameChecker implements ProxyCallback { @@ -46,7 +45,7 @@ public HostNameChecker(ProxyConfig config) { /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) @@ -57,7 +56,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, // Check the whitelist of hosts // //////////////////////////////// - if (hostNames != null && hostNames.size() > 0) { + if (hostNames != null && !hostNames.isEmpty()) { String hostName = url.getHost(); if (!hostNames.contains(hostName)) { @@ -69,18 +68,18 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpUriRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) { } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onFinish() */ - public void onFinish() throws IOException { + public void onFinish() { } } diff --git a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java index 84c116f..e42f3f4 100644 --- a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java @@ -19,18 +19,17 @@ */ package it.geosolutions.httpproxy; -import java.io.IOException; -import java.net.URL; -import java.util.Set; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import java.io.IOException; +import java.net.URL; +import java.util.Set; + /** * MethodsChecker class for http methods check. - * + * * @author Tobia Di Pisa at tobia.dipisa@geo-solutions.it */ public class MethodsChecker implements ProxyCallback { @@ -46,7 +45,7 @@ public MethodsChecker(ProxyConfig config) { /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) @@ -57,7 +56,7 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, // Check the whitelist of methods // //////////////////////////////// - if (methods != null && methods.size() > 0) { + if (methods != null && !methods.isEmpty()) { String method = request.getMethod(); if (!methods.contains(method)) { @@ -69,17 +68,17 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpUriRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) { } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onFinish() */ - public void onFinish() throws IOException { + public void onFinish() { } } diff --git a/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java b/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java index 25e4d4d..ccd97aa 100644 --- a/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/MimeTypeChecker.java @@ -19,21 +19,20 @@ */ package it.geosolutions.httpproxy; -import java.io.IOException; -import java.net.URL; -import java.util.Set; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.core5.http.Header; +import java.io.IOException; +import java.net.URL; +import java.util.Set; + /** * MimeTypeChecker class for the mimetype check. - * + * * @author Andrea Aime - GeoSolutions - * + * */ public class MimeTypeChecker implements ProxyCallback { @@ -48,28 +47,27 @@ public MimeTypeChecker(ProxyConfig config) { /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ - public void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) - throws IOException { + public void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) { } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ public void onRemoteResponse(HttpUriRequestBase method) throws IOException { Set mimeTypes = config.getMimetypeWhitelist(); - if (mimeTypes != null && mimeTypes.size() > 0) { + if (mimeTypes != null && !mimeTypes.isEmpty()) { Header header = method.getFirstHeader("Content-type"); - - - if(header != null){ - String contentType = header.getValue(); - + + + if (header != null) { + String contentType = header.getValue(); + // ////////////////////////////////// // Trim off extraneous information // ////////////////////////////////// @@ -78,7 +76,7 @@ public void onRemoteResponse(HttpUriRequestBase method) throws IOException { if (!mimeTypes.contains(firstType)) { throw new HttpErrorException(403, "Content-type " + firstType - + " is not among the ones allowed for this proxy"); + + " is not among the ones allowed for this proxy"); } } @@ -88,10 +86,10 @@ public void onRemoteResponse(HttpUriRequestBase method) throws IOException { /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onFinish() */ - public void onFinish() throws IOException { + public void onFinish() { } } diff --git a/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java b/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java index fae28af..ef6e57b 100644 --- a/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java +++ b/src/main/java/it/geosolutions/httpproxy/ProxyCallback.java @@ -19,24 +19,23 @@ */ package it.geosolutions.httpproxy; -import java.io.IOException; -import java.net.URL; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import java.io.IOException; +import java.net.URL; + /** * A pluggable callback that can perform checks or alter the request. - * + * * @author Andrea Aime - GeoSolutions */ public interface ProxyCallback { /** * First to be called, can be used to initialize the callback status and disallow certain requests by throwing an {@link HttpErrorException} - * + * * @throws IOException */ void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) @@ -44,7 +43,7 @@ void onRequest(HttpServletRequest request, HttpServletResponse response, URL url /** * Second to be called, can be used to check the remote server response - * + * * @param method * @throws IOException */ @@ -52,7 +51,7 @@ void onRequest(HttpServletRequest request, HttpServletResponse response, URL url /** * Called when the request is fully proxied, can be used for cleanup actions - * + * * @throws IOException */ void onFinish() throws IOException; diff --git a/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java b/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java index fefb1ec..dd4ff29 100644 --- a/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java +++ b/src/main/java/it/geosolutions/httpproxy/ProxyConfig.java @@ -19,6 +19,10 @@ */ package it.geosolutions.httpproxy; +import jakarta.servlet.ServletContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -27,14 +31,9 @@ import java.util.Properties; import java.util.Set; -import jakarta.servlet.ServletContext; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * ProxyConfig class to define the proxy configuration. - * + * * @author Tobia Di Pisa at tobia.dipisa@geo-solutions.it */ final class ProxyConfig { @@ -44,39 +43,39 @@ final class ProxyConfig { /** * A list of regular expressions describing hostnames the proxy is permitted to forward to */ - private Set hostnameWhitelist = new HashSet(); + private Set hostnameWhitelist = new HashSet<>(); /** * A list of regular expressions describing MIMETypes the proxy is permitted to forward */ - private Set mimetypeWhitelist = new HashSet(); + private Set mimetypeWhitelist = new HashSet<>(); /** * A list of regular expressions describing Request Types the proxy is permitted to forward */ - private Set reqtypeWhitelist = new HashSet(); + private Set reqtypeWhitelist = new HashSet<>(); /** * A list of regular expressions describing request METHODS the proxy is permitted to forward */ - private Set methodsWhitelist = new HashSet(); + private Set methodsWhitelist = new HashSet<>(); /** * A list of regular expressions describing request HOSTS the proxy is permitted to forward */ - private Set hostsWhitelist = new HashSet(); + private Set hostsWhitelist = new HashSet<>(); /** * A list of request header names (case-insensitive) that the proxy is permitted to forward. * If non-empty, only headers in this set will be forwarded. */ - private Set requestHeaderWhitelist = new HashSet(); + private Set requestHeaderWhitelist = new HashSet<>(); /** * A list of request header names (case-insensitive) that the proxy must NOT forward. * Headers in this set will always be removed, even if they appear in the whitelist. */ - private Set requestHeaderBlacklist = new HashSet(); + private Set requestHeaderBlacklist = new HashSet<>(); /** * The servlet context @@ -107,10 +106,10 @@ final class ProxyConfig { * The maximum connections available per host */ private int defaultMaxConnectionsPerHost = 6; - + private int defaultStreamByteSize = 1024; - /** + /** * @param context * @param propertiesFilePath */ @@ -123,8 +122,6 @@ public ProxyConfig(ServletContext context, String propertiesFilePath) { /** * Provide the proxy configuration - * - * @throws IOException */ private void configProxy() { Properties props = propertiesLoader(); @@ -162,7 +159,7 @@ private void configProxy() { // Read various request type properties // //////////////////////////////////////// - Set rt = new HashSet(); + Set rt = new HashSet<>(); String s = props.getProperty("reqtypeWhitelist.capabilities"); if (s != null) rt.add(s); @@ -174,11 +171,11 @@ private void configProxy() { s = props.getProperty("reqtypeWhitelist.csw"); if (s != null) rt.add(s); - + s = props.getProperty("reqtypeWhitelist.featureinfo"); if (s != null) rt.add(s); - + s = props.getProperty("reqtypeWhitelist.generic"); if (s != null) rt.add(s); @@ -190,16 +187,16 @@ private void configProxy() { // Load byte size configuration from // properties file. // ///////////////////////////////////////////////// - + String bytesSize = props.getProperty("defaultStreamByteSize"); - this.setDefaultStreamByteSize(bytesSize != null ? Integer.parseInt(bytesSize) : - this.defaultStreamByteSize); - + this.setDefaultStreamByteSize(bytesSize != null ? Integer.parseInt(bytesSize) : + this.defaultStreamByteSize); + // ///////////////////////////////////////////////// // Load connection manager configuration from // properties file. // ///////////////////////////////////////////////// - + String timeout = props.getProperty("timeout"); this.setSoTimeout(timeout != null ? Integer.parseInt(timeout) : this.soTimeout); @@ -229,13 +226,13 @@ private void configProxy() { /** * Read the proxy properties file. - * + * * @return Properties */ public Properties propertiesLoader() { Properties props = new Properties(); // can specify more paths, comma separated, all are read, if they exist - for (String path : propertiesFilePath.split(",") ) { + for (String path : propertiesFilePath.split(",")) { mergePropertiesConfig(path, props); } return props; @@ -368,7 +365,7 @@ public Set getReqtypeWhitelist() { Properties props = propertiesLoader(); if (props != null) { - Set rt = new HashSet(); + Set rt = new HashSet<>(); String s = props.getProperty("reqtypeWhitelist.capabilities"); if (s != null) rt.add(s); @@ -380,11 +377,11 @@ public Set getReqtypeWhitelist() { s = props.getProperty("reqtypeWhitelist.csw"); if (s != null) rt.add(s); - + s = props.getProperty("reqtypeWhitelist.featureinfo"); if (s != null) rt.add(s); - + s = props.getProperty("reqtypeWhitelist.generic"); if (s != null) rt.add(s); @@ -517,19 +514,19 @@ public String getPropertiesFilePath() { public void setPropertiesFilePath(String propertiesFilePath) { this.propertiesFilePath = propertiesFilePath; } - + /** - * @return the defaultStreamByteSize - */ - public int getDefaultStreamByteSize() { - return defaultStreamByteSize; - } + * @return the defaultStreamByteSize + */ + public int getDefaultStreamByteSize() { + return defaultStreamByteSize; + } - /** - * @param defaultStreamByteSize the defaultStreamByteSize to set - */ - public void setDefaultStreamByteSize(int defaultStreamByteSize) { - this.defaultStreamByteSize = defaultStreamByteSize; - } + /** + * @param defaultStreamByteSize the defaultStreamByteSize to set + */ + public void setDefaultStreamByteSize(int defaultStreamByteSize) { + this.defaultStreamByteSize = defaultStreamByteSize; + } } diff --git a/src/main/java/it/geosolutions/httpproxy/ProxyInfo.java b/src/main/java/it/geosolutions/httpproxy/ProxyInfo.java index c369489..e8766e3 100644 --- a/src/main/java/it/geosolutions/httpproxy/ProxyInfo.java +++ b/src/main/java/it/geosolutions/httpproxy/ProxyInfo.java @@ -21,13 +21,11 @@ /** * Simple placeholder class for the proxy information. - * + * * @author Simone Giannecchini, GeoSolutions SAS */ final class ProxyInfo { - private static final String DEFAULT_PROXY_APTH = ""; - // Proxy host params /** * The host to which we are proxying requests @@ -35,17 +33,16 @@ final class ProxyInfo { private String proxyHost; /** - * The (optional) path on the proxy host to wihch we are proxying requests. Default value is "". + * The (optional) path on the proxy host to which we are proxying requests. Default value is "". */ - private String proxyPath = DEFAULT_PROXY_APTH; + private String proxyPath; /** - * The port on the proxy host to wihch we are proxying requests. Default value is 80. + * The port on the proxy host to which we are proxying requests. Default value is 80. */ - private int proxyPort = Utils.DEFAULT_PROXY_PORT; + private int proxyPort; public ProxyInfo(String proxyHost, String proxyPath, int proxyPort) { - super(); this.proxyHost = proxyHost; this.proxyPath = proxyPath; this.proxyPort = proxyPort; diff --git a/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java b/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java index ef96999..54aef45 100644 --- a/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java +++ b/src/main/java/it/geosolutions/httpproxy/RequestTypeChecker.java @@ -19,21 +19,19 @@ */ package it.geosolutions.httpproxy; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; + import java.io.IOException; import java.net.URL; -import java.util.Iterator; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; - /** * RequestTypeChecker class for request type check. - * + * * @author Tobia Di Pisa at tobia.dipisa@geo-solutions.it */ public class RequestTypeChecker implements ProxyCallback { @@ -49,7 +47,7 @@ public RequestTypeChecker(ProxyConfig config) { /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public void onRequest(HttpServletRequest request, HttpServletResponse response, URL url) @@ -61,18 +59,11 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, // provided vs. permitted request types // ////////////////////////////////////// - if (reqTypes != null && reqTypes.size() > 0) { - Iterator iterator = reqTypes.iterator(); - + if (reqTypes != null && !reqTypes.isEmpty()) { String urlExtForm = url.toExternalForm(); - /*if (urlExtForm.indexOf("?") != -1) { - urlExtForm = urlExtForm.split("\\?")[1]; - }*/ boolean check = false; - while (iterator.hasNext()) { - String regex = iterator.next(); - + for (String regex : reqTypes) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(urlExtForm); @@ -84,24 +75,23 @@ public void onRequest(HttpServletRequest request, HttpServletResponse response, if (!check) throw new HttpErrorException(403, "Request Type" - + " is not among the ones allowed for this proxy"); + + " is not among the ones allowed for this proxy"); } } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onRemoteResponse(org.apache.commons.httpclient.HttpMethod) */ - public void onRemoteResponse(HttpUriRequestBase method) throws IOException { + public void onRemoteResponse(HttpUriRequestBase method) { } /* * (non-Javadoc) - * + * * @see it.geosolutions.httpproxy.ProxyCallback#onFinish() */ - public void onFinish() throws IOException { + public void onFinish() { } - } diff --git a/src/main/java/it/geosolutions/httpproxy/Utils.java b/src/main/java/it/geosolutions/httpproxy/Utils.java index bb80bb4..69eba5b 100644 --- a/src/main/java/it/geosolutions/httpproxy/Utils.java +++ b/src/main/java/it/geosolutions/httpproxy/Utils.java @@ -91,7 +91,7 @@ private Utils() { * @param ch * @return */ - final static int escapeHtmlFull(int ch) { + static int escapeHtmlFull(int ch) { if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') { // safe return ch; @@ -122,7 +122,7 @@ final static int escapeHtmlFull(int ch) { * @param proxyInfo * @return String */ - static final String getProxyHostAndPort(ProxyInfo proxyInfo) { + static String getProxyHostAndPort(ProxyInfo proxyInfo) { if (proxyInfo.getProxyPort() == 80) { return proxyInfo.getProxyHost(); } else { @@ -134,14 +134,11 @@ static final String getProxyHostAndPort(ProxyInfo proxyInfo) { * @param property * @return Set */ - static final Set parseWhiteList(String property) { + static Set parseWhiteList(String property) { if (property != null) { - Set set = new HashSet(); + Set set = new HashSet<>(); - String[] array = property.split(","); - - for (int i = 0; i < array.length; i++) { - String element = array[i]; + for (String element : property.split(",")) { if (element != null) set.add(element); } @@ -173,12 +170,12 @@ static URL buildURL(String value) throws MalformedURLException { if (url.getPort() == -1) { if (url.getProtocol().equals("https")) { if (LOGGER.isDebugEnabled()) { - LOGGER.info("Using default HTTPS port: " + DEFAULT_HTTPS_PORT); + LOGGER.info("Using default HTTPS port: {}", DEFAULT_HTTPS_PORT); } return new URL(url.getProtocol(), url.getHost(), DEFAULT_HTTPS_PORT, url.getFile()); } else { if (LOGGER.isDebugEnabled()) { - LOGGER.info("Using default HTTP port: " + DEFAULT_HTTP_PORT); + LOGGER.info("Using default HTTP port: {}", DEFAULT_HTTP_PORT); } return new URL(url.getProtocol(), url.getHost(), DEFAULT_HTTP_PORT, url.getFile()); } diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java index 049f92e..03e4403 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java @@ -6,7 +6,6 @@ import org.eclipse.jetty.ee10.webapp.WebAppContext; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPut; @@ -22,8 +21,9 @@ import java.net.URL; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; -public class HttpProxyIntegrationTests { +class HttpProxyIntegrationTests { private final int localPort = 8080; @@ -47,7 +47,7 @@ public class HttpProxyIntegrationTests { private static String proxyPort; @BeforeAll - public static void startHttpProxyServer() { + static void startHttpProxyServer() { try { // store system proxy config @@ -70,12 +70,12 @@ public static void startHttpProxyServer() { jettyServer.start(); } catch (Exception e1) { - LOGGER.error("Could not start HTTP Proxy Server: " + e1.getMessage(), e1); + LOGGER.error("Could not start HTTP Proxy Server: {}", e1.getMessage(), e1); if (jettyServer != null) { try { jettyServer.stop(); } catch (Exception e2) { - LOGGER.error("Could not stop HTTP Proxy Server: " + e2.getMessage(), e2); + LOGGER.error("Could not stop HTTP Proxy Server: {}", e2.getMessage(), e2); } } } @@ -94,22 +94,24 @@ private static void storeProxyConfig() { * Validates that HTTP GET requests is correctly handled by the HTTP Proxy */ @Test - public void testGETUsingWireMock() throws IOException { + void testGETUsingWireMock() throws IOException { wireMockRule.stubFor( - get(urlEqualTo("/geostore/users")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("Some content"))); + get(urlEqualTo("/geostore/users")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("Some content"))); String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/users"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpGet httpGet = new HttpGet(proxyURL); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - ClassicHttpResponse httpResponse = httpClient.execute(httpGet); - Assertions.assertEquals(200, httpResponse.getCode()); + httpClient.execute(httpGet, response -> { + assertEquals(200, response.getCode()); + return null; + }); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/users"))); } } @@ -118,7 +120,7 @@ public void testGETUsingWireMock() throws IOException { * Validates that HTTP POST request is correctly handled by the HTTP Proxy using text/xml content type */ @Test - public void testPOSTUsingWireMockXML() throws IOException { + void testPOSTUsingWireMockXML() throws IOException { String requestBody = "5Jane Doe"; wireMockRule1.stubFor( @@ -137,9 +139,11 @@ public void testPOSTUsingWireMockXML() throws IOException { httpPost.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - ClassicHttpResponse httpResponse = httpClient.execute(httpPost); - Assertions.assertEquals("text/xml", httpResponse.getFirstHeader("Content-Type").getValue()); - Assertions.assertEquals(201, httpResponse.getCode()); + httpClient.execute(httpPost, response -> { + assertEquals("text/xml", response.getFirstHeader("Content-Type").getValue()); + assertEquals(201, response.getCode()); + return null; + }); wireMockRule1.verify(postRequestedFor(urlEqualTo("/geostore/users/create"))); } } @@ -148,14 +152,15 @@ public void testPOSTUsingWireMockXML() throws IOException { * Validates that HTTP POST request is correctly handled by the HTTP Proxy using application/json content type */ @Test - public void testPOSTUsingWireMockJSON() throws IOException { - - String requestBody = "{\n" + - " \"user\": {\n" + - " \"userId\": 5,\n" + - " \"userName\": \"Jane Doe\"\n" + - " }\n" + - "}"; + void testPOSTUsingWireMockJSON() throws IOException { + + String requestBody = """ + { + "user": { + "userId": 5, + "userName": "Jane Doe" + } + }"""; wireMockRule1.stubFor( post(urlEqualTo("/geostore/users/create")) .withRequestBody(equalToJson(requestBody)) @@ -163,9 +168,10 @@ public void testPOSTUsingWireMockJSON() throws IOException { aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") - .withBody("{\n" + - " \"response\": 5\n" + - "}"))); + .withBody(""" + { + "response": 5 + }"""))); String url = "http://localhost:" + wireMockRule1.getPort() + "/geostore/users/create"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; @@ -174,9 +180,11 @@ public void testPOSTUsingWireMockJSON() throws IOException { httpPost.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - ClassicHttpResponse httpResponse = httpClient.execute(httpPost); - Assertions.assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue()); - Assertions.assertEquals(201, httpResponse.getCode()); + httpClient.execute(httpPost, response -> { + assertEquals("application/json", response.getFirstHeader("Content-Type").getValue()); + assertEquals(201, response.getCode()); + return null; + }); wireMockRule1.verify(postRequestedFor(urlEqualTo("/geostore/users/create"))); } } @@ -185,17 +193,17 @@ public void testPOSTUsingWireMockJSON() throws IOException { * Validates that HTTP PUT request is correctly handled by the HTTP Proxy */ @Test - public void testPUTUsingWireMock() throws IOException { + void testPUTUsingWireMock() throws IOException { String requestBody = "5John Doe"; wireMockRule.stubFor( - put(urlEqualTo("/geostore/users/5")) - .withRequestBody(equalToXml(requestBody)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("5"))); + put(urlEqualTo("/geostore/users/5")) + .withRequestBody(equalToXml(requestBody)) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("5"))); String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/users/5"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; @@ -204,8 +212,10 @@ public void testPUTUsingWireMock() throws IOException { httpPut.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - ClassicHttpResponse httpResponse = httpClient.execute(httpPut); - Assertions.assertEquals(200, httpResponse.getCode()); + httpClient.execute(httpPut, response -> { + assertEquals(200, response.getCode()); + return null; + }); wireMockRule.verify(putRequestedFor(urlEqualTo("/geostore/users/5"))); } } @@ -214,24 +224,26 @@ public void testPUTUsingWireMock() throws IOException { * Test the request for Empty (i.e. Null) proxy configuration */ @Test - public void testNullProxyConfig() throws IOException { + void testNullProxyConfig() throws IOException { // remove the proxy config System.setProperty("http.proxyHost", ""); System.setProperty("http.proxyPort", ""); wireMockRule.stubFor( - get(urlEqualTo("/geostore/resources")) - .willReturn( - aResponse() - .withStatus(200))); + get(urlEqualTo("/geostore/resources")) + .willReturn( + aResponse() + .withStatus(200))); String url = "http://localhost:" + wireMockRule.getPort() + "/geostore/resources"; String proxyURL = "http://localhost:" + localPort + "/http_proxy/proxy?url=" + url; HttpGet httpGet = new HttpGet(proxyURL); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - ClassicHttpResponse httpResponse = httpClient.execute(httpGet); - Assertions.assertEquals(200, httpResponse.getCode()); + httpClient.execute(httpGet, response -> { + assertEquals(200, response.getCode()); + return null; + }); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/resources"))); } } @@ -240,31 +252,31 @@ public void testNullProxyConfig() throws IOException { * Validates that HTTP URL pot set to 80 if no port is specified */ @Test - public void testHttpDefaultPort() throws IOException { + void testHttpDefaultPort() throws IOException { String urlStr = "http://localhost/geoserver/wms?service=wms%26version=1.3.0&request=GetCapabilities"; URL url = Utils.buildURL(urlStr); - Assertions.assertEquals(url.getPort(), Utils.DEFAULT_HTTP_PORT); - Assertions.assertEquals(url.getProtocol(), "http"); - Assertions.assertEquals(url.getHost(), "localhost"); - Assertions.assertEquals(url.getFile(), "/geoserver/wms?service=wms%26version=1.3.0&request=GetCapabilities"); + assertEquals(Utils.DEFAULT_HTTP_PORT, url.getPort()); + assertEquals("http", url.getProtocol()); + assertEquals("localhost", url.getHost()); + assertEquals("/geoserver/wms?service=wms%26version=1.3.0&request=GetCapabilities", url.getFile()); } /** * Validates that HTTP URL pot set to 80 if no port is specified */ @Test - public void testHttpsDefaultPort() throws IOException { + void testHttpsDefaultPort() throws IOException { String urlStr = "https://georchestra.geo-solutions.it/geoserver/wms?service=WMS%26version=1.3.0&request=GetCapabilities"; URL url = Utils.buildURL(urlStr); - Assertions.assertEquals(url.getPort(), Utils.DEFAULT_HTTPS_PORT); - Assertions.assertEquals(url.getProtocol(), "https"); - Assertions.assertEquals(url.getHost(), "georchestra.geo-solutions.it"); - Assertions.assertEquals(url.getFile(), "/geoserver/wms?service=WMS%26version=1.3.0&request=GetCapabilities"); + assertEquals(Utils.DEFAULT_HTTPS_PORT, url.getPort()); + assertEquals("https", url.getProtocol()); + assertEquals("georchestra.geo-solutions.it", url.getHost()); + assertEquals("/geoserver/wms?service=WMS%26version=1.3.0&request=GetCapabilities", url.getFile()); } @AfterAll - public static void stopServer() { + static void stopServer() { try { // restore system proxy config @@ -273,7 +285,7 @@ public static void stopServer() { wireMockServer.stop(); jettyServer.stop(); } catch (Exception e) { - LOGGER.error("Could not stop HTTP Proxy Server: " + e.getMessage(), e); + LOGGER.error("Could not stop HTTP Proxy Server: {}", e.getMessage(), e); } } @@ -284,5 +296,4 @@ private static void restoreProxyConfig() { System.setProperty("http.proxyHost", proxyHost); System.setProperty("http.proxyPort", proxyPort); } - } diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java index 47afd20..fd3ef2d 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java @@ -19,41 +19,48 @@ */ package it.geosolutions.httpproxy; -import java.io.File; -import java.net.URL; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicHeader; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.*; +import java.io.File; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * HttpProxyTest class. Test Cases for the HTTPProxy servlet. * * @author Lorenzo Natali at lorenzo.natali@geo-solutions.it */ -public class HttpProxyTest { +class HttpProxyTest { final ServletConfig servletConfig = mock(ServletConfig.class); ServletContext ctx = mock(ServletContext.class); @@ -65,7 +72,7 @@ public class HttpProxyTest { String fakeLocation; @BeforeEach - public void setUp() { + void setUp() { File f = new File(getClass().getClassLoader() .getResource("test-proxy.properties").getFile()); when(ctx.getInitParameter("proxyPropPath")).thenReturn( @@ -78,7 +85,7 @@ public void setUp() { } @Test - public void testRedirectGet() throws Exception { + void testRedirectGet() throws Exception { // mock redirect response final HttpGet mockGetMethod = mock(HttpGet.class); @@ -86,7 +93,10 @@ public void testRedirectGet() throws Exception { when(response.getCode()).thenReturn(302); mockHttpClient = mock(CloseableHttpClient.class); - when(mockHttpClient.execute(mockGetMethod)).thenReturn(response); + doAnswer(invocation -> { + HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response); + }).when(mockHttpClient).execute(eq(mockGetMethod), any(HttpClientResponseHandler.class)); fakeLocation = "http://newURL.com/"; when(mockGetMethod.getFirstHeader(Utils.LOCATION_HEADER)) @@ -118,14 +128,14 @@ public HttpGet getGetMethod(URL url) { proxy.doGet(getRequest, getResponse); verify(getResponse).sendRedirect( "http://proxy.com/http-proxy/proxy?url=" - + URLEncoder.encode(fakeLocation, "UTF-8")); + + URLEncoder.encode(fakeLocation, "UTF-8")); final byte[] data = servletOutputStream.baos.toByteArray(); - Assertions.assertNotNull(data); - Assertions.assertTrue(data.length == 0); + assertNotNull(data); + assertEquals(0, data.length); } @Test - public void testPost() throws Exception { + void testPost() throws Exception { // mock post response final HttpPost mockPostMethod = mock(HttpPost.class); @@ -136,7 +146,10 @@ public void testPost() throws Exception { when(response.getEntity()).thenReturn(stringEntity); when(response.headerIterator()).thenReturn(Collections.emptyList().iterator()); mockHttpClient = mock(CloseableHttpClient.class); - when(mockHttpClient.execute(mockPostMethod)).thenReturn(response); + doAnswer(invocation -> { + HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response); + }).when(mockHttpClient).execute(eq(mockPostMethod), any(HttpClientResponseHandler.class)); proxy = new HTTPProxy() { private static final long serialVersionUID = 1L; @@ -168,8 +181,7 @@ public HttpPost getPostMethod(URL url) { proxy.doPost(postRequest, getResponse); verify(getResponse).setStatus(200); final byte[] data = servletOutputStream.baos.toByteArray(); - Assertions.assertNotNull(data); - Assertions.assertTrue(data.length != 0); + assertNotNull(data); + assertNotEquals(0, data.length); } - } diff --git a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java index 27e51e4..5d2517e 100644 --- a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java +++ b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java @@ -19,39 +19,45 @@ */ package it.geosolutions.httpproxy; -import java.io.File; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.StringEntity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.*; +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Tests for request header whitelist and blacklist filtering. */ -public class RequestHeaderFilterTest { +class RequestHeaderFilterTest { Map parameters = new HashMap<>(); @BeforeEach - public void setUp() { + void setUp() { // URL must match one of the reqtypeWhitelist patterns in proxy.properties parameters.put("url", new String[]{"http://sample.com/csw"}); } @@ -91,12 +97,15 @@ private HttpGet buildMockGet(CloseableHttpClient mockHttpClient) throws Exceptio when(response.getCode()).thenReturn(200); when(response.getEntity()).thenReturn(new StringEntity("OK")); when(response.headerIterator()).thenReturn(Collections.
emptyList().iterator()); - when(mockHttpClient.execute(mockGetMethod)).thenReturn(response); + doAnswer(invocation -> { + HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response); + }).when(mockHttpClient).execute(eq(mockGetMethod), any(HttpClientResponseHandler.class)); return mockGetMethod; } @Test - public void testBlacklistRemovesHeaders() throws Exception { + void testBlacklistRemovesHeaders() throws Exception { CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); @@ -126,13 +135,13 @@ public void testBlacklistRemovesHeaders() throws Exception { verify(mockGetMethod, never()).setHeader(eq("X-Secret"), anyString()); verify(mockGetMethod, never()).setHeader(eq("Cookie"), anyString()); // Accept should have been forwarded - verify(mockGetMethod).setHeader(eq("Accept"), eq("value-Accept")); + verify(mockGetMethod).setHeader("Accept", "value-Accept"); // Host is rewritten to the target host verify(mockGetMethod).setHeader(eq("Host"), anyString()); } @Test - public void testWhitelistOnlyForwardsAllowedHeaders() throws Exception { + void testWhitelistOnlyForwardsAllowedHeaders() throws Exception { CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); @@ -159,8 +168,8 @@ public void testWhitelistOnlyForwardsAllowedHeaders() throws Exception { proxy.doGet(request, response); // Whitelisted: Accept, Content-Type, Host should be forwarded - verify(mockGetMethod).setHeader(eq("Accept"), eq("value-Accept")); - verify(mockGetMethod).setHeader(eq("Content-Type"), eq("value-Content-Type")); + verify(mockGetMethod).setHeader("Accept", "value-Accept"); + verify(mockGetMethod).setHeader("Content-Type", "value-Content-Type"); verify(mockGetMethod).setHeader(eq("Host"), anyString()); // NOT whitelisted: X-Custom, Authorization should be removed verify(mockGetMethod, never()).setHeader(eq("X-Custom"), anyString()); @@ -168,7 +177,7 @@ public void testWhitelistOnlyForwardsAllowedHeaders() throws Exception { } @Test - public void testBlacklistTakesPrecedenceOverWhitelist() throws Exception { + void testBlacklistTakesPrecedenceOverWhitelist() throws Exception { // proxy.properties has both whitelist (Accept,Content-Type,Host) // and blacklist (X-Secret,Cookie). // A header NOT in either list should be blocked by the whitelist. @@ -202,7 +211,7 @@ public void testBlacklistTakesPrecedenceOverWhitelist() throws Exception { proxy.doGet(request, response); // Accept: whitelisted and not blacklisted -> forwarded - verify(mockGetMethod).setHeader(eq("Accept"), eq("value-Accept")); + verify(mockGetMethod).setHeader("Accept", "value-Accept"); // X-Secret: blacklisted -> blocked verify(mockGetMethod, never()).setHeader(eq("X-Secret"), anyString()); // Authorization: not in whitelist -> blocked diff --git a/src/test/java/it/geosolutions/httpproxy/jetty/Start.java b/src/test/java/it/geosolutions/httpproxy/jetty/Start.java index f103864..e869b28 100644 --- a/src/test/java/it/geosolutions/httpproxy/jetty/Start.java +++ b/src/test/java/it/geosolutions/httpproxy/jetty/Start.java @@ -65,13 +65,13 @@ public static void main(String[] args) { jettyServer.join(); } catch (Exception e) { - LOGGER.error("Could not start the Jetty server: " + e.getMessage(), e); + LOGGER.error("Could not start the Jetty server: {}", e.getMessage(), e); if (jettyServer != null) { try { jettyServer.stop(); } catch (Exception e1) { - LOGGER.error("Unable to stop the Jetty server: " + e1.getMessage(), e1); + LOGGER.error("Unable to stop the Jetty server: {}", e1.getMessage(), e1); } } } From ffd2cc6b7d50f6aee388ff1ffbe6d7df82af7d88 Mon Sep 17 00:00:00 2001 From: Alessandro Ricchiuti Date: Tue, 23 Jun 2026 12:03:43 +0200 Subject: [PATCH 11/12] Added owasp dependency check maven profile. --- pom.xml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pom.xml b/pom.xml index 916df03..a4d579a 100644 --- a/pom.xml +++ b/pom.xml @@ -349,6 +349,29 @@ + + owasp + + + owasp + + + + + + org.owasp + dependency-check-maven + + + + check + + + + + + + From d8cf74a780695e95b0697a5f004ad391bd094715 Mon Sep 17 00:00:00 2001 From: afabiani Date: Tue, 23 Jun 2026 13:34:52 +0200 Subject: [PATCH 12/12] Fix proxy redirect handling; align CI Java distribution HTTPProxy.executeProxyRequest: - Read the Location header from the response instead of the outgoing request object (the request never carries it), and null-check the header before dereferencing it to avoid an NPE on 3xx responses lacking a Location header. - Treat 307 (Temporary Redirect) and 308 (Permanent Redirect) as redirects in addition to 300-303. - Resolve the status code once into a local instead of calling getStatusCode(response) repeatedly. Tests: - Update testRedirectGet to stub the response's Location header. - Add testRedirectGet307 covering the newly handled 307 case. CI: use the 'temurin' distribution in the publish job to match the build job ('adopt' is a deprecated setup-java alias). Co-Authored-By: Claude Opus 4.8 --- .github/workflows/CI.yml | 2 +- .../it/geosolutions/httpproxy/HTTPProxy.java | 17 ++++--- .../geosolutions/httpproxy/HttpProxyTest.java | 48 ++++++++++++++++++- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c4e8566..e7e4818 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,7 +39,7 @@ jobs: uses: actions/setup-java@v5 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' server-id: geosolutions server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java index 6bf5127..038af43 100644 --- a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java +++ b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java @@ -702,12 +702,15 @@ private void executeProxyRequest(HttpUriRequestBase httpMethodProxyRequest, // Hooray for open source software // //////////////////////////////////////////////////////////////////////////////// - if (getStatusCode(response) >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */ - && getStatusCode(response) < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) { + int statusCode = getStatusCode(response); + if ((statusCode >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */ + && statusCode <= HttpServletResponse.SC_SEE_OTHER /* 303 */) + || statusCode == HttpServletResponse.SC_TEMPORARY_REDIRECT /* 307 */ + || statusCode == 308 /* Permanent Redirect (no servlet-api constant) */) { - String stringStatusCode = Integer.toString(getStatusCode(response)); - String stringLocation = httpMethodProxyRequest.getFirstHeader( - Utils.LOCATION_HEADER).getValue(); + String stringStatusCode = Integer.toString(statusCode); + var locationHeader = response.getFirstHeader(Utils.LOCATION_HEADER); + String stringLocation = locationHeader != null ? locationHeader.getValue() : null; if (stringLocation == null) { throw new IOException("Received status code: " + stringStatusCode @@ -725,7 +728,7 @@ && getStatusCode(response) < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) { LOGGER.info("redirected to: {}", redirectURL); return null; - } else if (getStatusCode(response) == HttpServletResponse.SC_NOT_MODIFIED) { + } else if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) { // /////////////////////////////////////////////////////////////// // 304 needs special handling. See: @@ -745,7 +748,7 @@ && getStatusCode(response) < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) { // Pass the response code back to the client // ///////////////////////////////////////////// - httpServletResponse.setStatus(getStatusCode(response)); + httpServletResponse.setStatus(statusCode); // ///////////////////////////////////////////// // Pass response headers back to the client diff --git a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java index fd3ef2d..65f3721 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java @@ -99,7 +99,7 @@ void testRedirectGet() throws Exception { }).when(mockHttpClient).execute(eq(mockGetMethod), any(HttpClientResponseHandler.class)); fakeLocation = "http://newURL.com/"; - when(mockGetMethod.getFirstHeader(Utils.LOCATION_HEADER)) + when(response.getFirstHeader(Utils.LOCATION_HEADER)) .thenReturn(new BasicHeader("Location", fakeLocation)); proxy = new HTTPProxy() { @@ -134,6 +134,52 @@ public HttpGet getGetMethod(URL url) { assertEquals(0, data.length); } + @Test + void testRedirectGet307() throws Exception { + + // 307 Temporary Redirect must be handled as a redirect as well + final HttpGet mockGetMethod = mock(HttpGet.class); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getCode()).thenReturn(307); + + mockHttpClient = mock(CloseableHttpClient.class); + doAnswer(invocation -> { + HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response); + }).when(mockHttpClient).execute(eq(mockGetMethod), any(HttpClientResponseHandler.class)); + fakeLocation = "http://newURL.com/"; + + // the Location header is read from the response, not the request + when(response.getFirstHeader(Utils.LOCATION_HEADER)) + .thenReturn(new BasicHeader("Location", fakeLocation)); + + proxy = new HTTPProxy() { + private static final long serialVersionUID = 1L; + + @Override + public HttpGet getGetMethod(URL url) { + return mockGetMethod; + } + }; + proxy.setHttpClient(mockHttpClient); + proxy.init(servletConfig); + + HttpServletRequest getRequest = mock(HttpServletRequest.class); + when(getRequest.getParameterMap()).thenReturn(parameters); + when(getRequest.getHeaderNames()).thenReturn( + Collections.enumeration(headers)); + when(getRequest.getRequestURL()).thenReturn( + new StringBuffer("http://proxy.com/http-proxy/proxy")); + HttpServletResponse getResponse = mock(HttpServletResponse.class); + final StubServletOutputStream servletOutputStream = new StubServletOutputStream(); + when(getResponse.getOutputStream()).thenReturn(servletOutputStream); + + proxy.doGet(getRequest, getResponse); + verify(getResponse).sendRedirect( + "http://proxy.com/http-proxy/proxy?url=" + + URLEncoder.encode(fakeLocation, "UTF-8")); + } + @Test void testPost() throws Exception {