diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 12df014..e7e4818 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 17 + uses: actions/setup-java@v5 with: - java-version: '11' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B clean install @@ -36,10 +36,10 @@ 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' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' server-id: geosolutions server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/.gitignore b/.gitignore index 611a9d8..7441674 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .project .settings */target/* -target/ \ No newline at end of file +target/ +.idea/ +main/assembly/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5fcb159 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# --- Build stage --- +FROM eclipse-temurin:17-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/java17-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 1328437..a4d579a 100644 --- a/pom.xml +++ b/pom.xml @@ -27,17 +27,17 @@ maven-compiler-plugin - 3.5.1 + 3.15.0 - 8 - 8 + 17 + 17 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.5.0 make-a-jar @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.1.4 install @@ -82,7 +82,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.1.4 deploy @@ -107,7 +107,7 @@ org.apache.maven.plugins maven-war-plugin - 2.6 + 3.5.1 ${basedir}/src/main/webapp/WEB-INF/web.xml @@ -117,27 +117,16 @@ - org.mortbay.jetty - maven-jetty-plugin - 6.1.19 - - http_proxy - - - 8080 - 10000 - - - http_proxy - ${project.build.directory}/${project.artifactId}-${project.version} - + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + 12.1.10 org.apache.maven.plugins maven-release-plugin - 2.2.2 + 3.3.1 v@{project.version} @@ -155,8 +144,9 @@ UTF-8 - 2.19.0 - 4.5.13 + 2.25.4 + 5.6.1 + 12.1.10 @@ -173,88 +163,80 @@ 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.6.0 - - - - org.apache.httpcomponents - httpclient - ${http-client-version} + org.apache.commons + commons-fileupload2-jakarta-servlet6 + 2.0.0-M5 - org.apache.httpcomponents - httpmime - ${http-client-version} + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5-version} commons-io commons-io - 2.1 + 2.22.0 commons-codec commons-codec - 1.4 + 1.22.0 - org.mortbay.jetty - servlet-api-2.5 - 6.1.14 + jakarta.servlet + jakarta.servlet-api + 6.0.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.23.0 test org.mockito - mockito-all - 1.9.5 + mockito-junit-jupiter + 5.23.0 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,23 +251,18 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl - commons-fileupload - commons-fileupload + org.apache.commons + commons-fileupload2-jakarta-servlet6 - org.apache.httpcomponents - httpclient - - - - org.apache.httpcomponents - httpmime + org.apache.httpcomponents.client5 + httpclient5 @@ -299,38 +276,104 @@ - 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 + + + docker + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + compile + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.8.0 + + + + it.geosolutions.httpproxy.Main + + + + src/main/assembly/fat-jar.xml + + http-proxy + false + + + + make-assembly + package + + single + + + + + + + + + owasp + + + owasp + + + + + + org.owasp + dependency-check-maven + + + + check + + + + + + + + + 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/HTTPProxy.java b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java index e1a56a5..038af43 100644 --- a/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java +++ b/src/main/java/it/geosolutions/httpproxy/HTTPProxy.java @@ -19,58 +19,56 @@ */ package it.geosolutions.httpproxy; +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.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.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.routing.HttpRoutePlanner; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +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; + 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.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Enumeration; +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; -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 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.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; -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.DefaultProxyRoutePlanner; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.protocol.HttpContext; -import org.slf4j.LoggerFactory; /** * HTTPProxy class. @@ -89,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. @@ -104,7 +97,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; /** @@ -124,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); @@ -144,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)); @@ -156,14 +150,14 @@ public void init(ServletConfig servletConfig) throws ServletException { * Creates the HttpClient * @return HttpClient */ - public HttpClient createHttpClient() { + public CloseableHttpClient createHttpClient() { if (httpClient != null) 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); @@ -197,9 +191,8 @@ private HttpRoutePlanner getRoutePlanner(final HttpHost httpHost, final HttpHost return new HttpRoutePlanner() { public HttpRoute determineRoute( HttpHost target, - HttpRequest request, - HttpContext context) { - LOGGER.info("HTTP proxy target host: " + target); + HttpContext context) throws HttpException { + LOGGER.info("HTTP proxy target host: {}", target); if (isNonProxyHost(target.getHostName())) { LOGGER.info("Returning direct route"); // Return direct route @@ -232,7 +225,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) { @@ -242,15 +235,14 @@ private Boolean isNonProxyHost(String host) { if (nonProxyHostProp.endsWith("\"")) { 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(); + 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; } @@ -275,7 +267,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); } @@ -296,20 +288,21 @@ 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(); + 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; @@ -361,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()); @@ -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); @@ -436,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()); @@ -479,7 +476,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); @@ -508,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()); @@ -564,32 +563,29 @@ 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} + * @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(HttpRequestBase methodProxyRequest, - HttpServletRequest httpServletRequest) throws ServletException { + private void handleMultipart(HttpUriRequestBase methodProxyRequest, + 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 +597,7 @@ private void handleMultipart(HttpRequestBase methodProxyRequest, // Get the multipart items as a list // ///////////////////////////////////// - List listFileItems = (List) servletFileUpload + List listFileItems = servletFileUpload .parseRequest(httpServletRequest); // ///////////////////////////////////////// @@ -626,10 +622,10 @@ private void handleMultipart(HttpRequestBase 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); } // //////////////////////////////////////////////////////////////////////// @@ -643,7 +639,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); @@ -657,18 +656,17 @@ 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 { - - if (methodProxyRequest instanceof HttpPost) { - ((HttpPost) methodProxyRequest).setEntity(new InputStreamEntity(httpServletRequest.getInputStream())); - } else if (methodProxyRequest instanceof HttpPut) { - ((HttpPut) methodProxyRequest).setEntity(new InputStreamEntity(httpServletRequest.getInputStream())); - } - - } 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)); } } @@ -678,94 +676,87 @@ private void handleStandard(HttpRequestBase 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(HttpRequestBase httpMethodProxyRequest, + 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); + 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(); } - InputStream inputStreamServerResponse = null; - try { - - // ////////////////////////// - // Execute the request - // ////////////////////////// - - HttpResponse response = httpClient.execute(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 - // //////////////////////////////////////////////////////////////////////////////// - - 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(); - - if (stringLocation == null) { - throw new ServletException("Received status code: " + stringStatusCode - + " but no " + Utils.LOCATION_HEADER - + " header was found in the response"); + httpClient.execute(httpMethodProxyRequest, response -> { + + 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 + // //////////////////////////////////////////////////////////////////////////////// + + 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(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 + + " 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 (statusCode == 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)); - - // ///////////////////////////////////////////// - // Pass response headers back to the client - // ///////////////////////////////////////////// + httpServletResponse.setStatus(statusCode); - if (response.getAllHeaders() != null) { - Header[] headerArrayResponse = response.getAllHeaders(); + // ///////////////////////////////////////////// + // Pass response headers back to the client + // ///////////////////////////////////////////// - for (Header header : headerArrayResponse) { + var headerIt = response.headerIterator(); + while (headerIt.hasNext()) { + var header = headerIt.next(); // ///////////////////////// // Skip GZIP Responses @@ -782,43 +773,33 @@ else if (header.getName().equalsIgnoreCase(Utils.HTTP_HEADER_TRANSFER_ENCODING)) else httpServletResponse.setHeader(header.getName(), header.getValue()); } - } - // /////////////////////////////////// - // Send the content to the client - // /////////////////////////////////// + // /////////////////////////////////// + // 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); + } + } } - } + return null; + }); } 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(); } } - int getStatusCode(HttpResponse response) { + int getStatusCode(ClassicHttpResponse response) { if (response != null) { - StatusLine statusLine = response.getStatusLine(); - return statusLine.getStatusCode(); + return response.getCode(); } else { return -1; } @@ -831,9 +812,8 @@ 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) { + HttpUriRequestBase httpMethodProxyRequest) { final String proxyHost = url.getHost(); final int proxyPort = url.getPort(); @@ -845,8 +825,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. @@ -855,8 +833,7 @@ private ProxyInfo setProxyRequestHeaders(URL url, HttpServletRequest httpServlet Set headerWhitelist = proxyConfig.getRequestHeaderWhitelist(); Set headerBlacklist = proxyConfig.getRequestHeaderBlacklist(); - while (enumerationOfHeaderNames.hasMoreElements()) { - String stringHeaderName = (String) enumerationOfHeaderNames.nextElement(); + for (String stringHeaderName : Collections.list(httpServletRequest.getHeaderNames())) { if (stringHeaderName.equalsIgnoreCase(Utils.CONTENT_LENGTH_HEADER_NAME)) continue; @@ -890,10 +867,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 = (String) enumerationOfHeaderValues.nextElement(); + for (String stringHeaderValue : Collections.list(httpServletRequest.getHeaders(stringHeaderName))) { // //////////////////////////////////////////////////////////////// // In case the proxy host is running multiple virtual servers, @@ -930,13 +904,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; } @@ -945,13 +921,13 @@ 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; } /** * @return the client */ - public HttpClient getHttpClient() { + public CloseableHttpClient getHttpClient() { return httpClient; } @@ -960,7 +936,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 28dd4e1..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 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; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.client.methods.HttpRequestBase; - /** * 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(HttpRequestBase 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 5b24080..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 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; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.client.methods.HttpRequestBase; - /** * 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(HttpRequestBase 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/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(); + } +} diff --git a/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java b/src/main/java/it/geosolutions/httpproxy/MethodsChecker.java index 16a8d4d..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 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; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.client.methods.HttpRequestBase; - /** * 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(HttpRequestBase 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 3ec1c60..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 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; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.Header; -import org.apache.http.client.methods.HttpRequestBase; - /** * 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(HttpRequestBase method) throws IOException { + 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(HttpRequestBase 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(HttpRequestBase 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 b7febfb..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 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 javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.client.methods.HttpRequestBase; - /** * 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,15 +43,15 @@ 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 */ - 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 - * + * * @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 267a0ea..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; @@ -26,56 +30,52 @@ 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; /** * ProxyConfig class to define the proxy configuration. - * + * * @author Tobia Di Pisa at tobia.dipisa@geo-solutions.it */ 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 */ - 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 @@ -106,10 +106,10 @@ final class ProxyConfig { * The maximum connections available per host */ private int defaultMaxConnectionsPerHost = 6; - + private int defaultStreamByteSize = 1024; - /** + /** * @param context * @param propertiesFilePath */ @@ -122,8 +122,6 @@ public ProxyConfig(ServletContext context, String propertiesFilePath) { /** * Provide the proxy configuration - * - * @throws IOException */ private void configProxy() { Properties props = propertiesLoader(); @@ -161,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); @@ -173,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); @@ -189,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); @@ -215,9 +213,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); @@ -230,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; @@ -248,25 +244,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); } } } @@ -378,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); @@ -390,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); @@ -527,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 901343d..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 javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.client.methods.HttpRequestBase; - /** * 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(HttpRequestBase 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/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..03e4403 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyIntegrationTests.java @@ -2,20 +2,18 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -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.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 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.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; import java.io.File; @@ -23,20 +21,23 @@ 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; 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,8 +46,8 @@ public class HttpProxyIntegrationTests { private static String proxyHost; private static String proxyPort; - @BeforeClass - public static void startHttpProxyServer() { + @BeforeAll + static void startHttpProxyServer() { try { // store system proxy config @@ -56,33 +57,25 @@ 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(); } 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); } } } @@ -101,23 +94,24 @@ private static void storeProxyConfig() { * Validates that HTTP GET requests is correctly handled by the HTTP Proxy */ @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")))); - - String url = "http://localhost:" + wireMockRule.port() + "/geostore/users"; + void testGETUsingWireMock() throws IOException { + + wireMockRule.stubFor( + 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()) { - HttpResponse httpResponse = httpClient.execute(httpGet); - Assert.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + httpClient.execute(httpGet, response -> { + assertEquals(200, response.getCode()); + return null; + }); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/users"))); } } @@ -126,29 +120,30 @@ 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.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); httpPost.setEntity(stringEntity); 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()); + 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"))); } } @@ -157,36 +152,39 @@ 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" + - "}"; - 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"; + void testPOSTUsingWireMockJSON() throws IOException { + + String requestBody = """ + { + "user": { + "userId": 5, + "userName": "Jane Doe" + } + }"""; + wireMockRule1.stubFor( + post(urlEqualTo("/geostore/users/create")) + .withRequestBody(equalToJson(requestBody)) + .willReturn( + aResponse() + .withStatus(201) + .withHeader("Content-Type", "application/json") + .withBody(""" + { + "response": 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); httpPost.setEntity(stringEntity); 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()); + 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"))); } } @@ -195,28 +193,29 @@ 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.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); httpPut.setEntity(stringEntity); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = httpClient.execute(httpPut); - Assert.assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + httpClient.execute(httpPut, response -> { + assertEquals(200, response.getCode()); + return null; + }); wireMockRule.verify(putRequestedFor(urlEqualTo("/geostore/users/5"))); } } @@ -225,25 +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.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()); + httpClient.execute(httpGet, response -> { + assertEquals(200, response.getCode()); + return null; + }); wireMockRule.verify(getRequestedFor(urlEqualTo("/geostore/resources"))); } } @@ -252,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); - 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"); + 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); - 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"); + 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()); } - @AfterClass - public static void stopServer() { + @AfterAll + static void stopServer() { try { // restore system proxy config @@ -285,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); } } @@ -296,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 d1371aa..65f3721 100644 --- a/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java +++ b/src/test/java/it/geosolutions/httpproxy/HttpProxyTest.java @@ -19,6 +19,22 @@ */ package it.geosolutions.httpproxy; +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.BeforeEach; +import org.junit.jupiter.api.Test; + import java.io.File; import java.net.URL; import java.net.URLEncoder; @@ -29,44 +45,34 @@ 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 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.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; +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 extends Mockito { +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; + CloseableHttpClient mockHttpClient; HTTPProxy proxy; String fakeLocation; - @Before - public void setUp() { + @BeforeEach + void setUp() { File f = new File(getClass().getClassLoader() .getResource("test-proxy.properties").getFile()); when(ctx.getInitParameter("proxyPropPath")).thenReturn( @@ -79,20 +85,21 @@ public void setUp() { } @Test - public void testRedirectGet() throws Exception { + 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); - - mockHttpClient = mock(HttpClient.class); - when(mockHttpClient.execute(mockGetMethod)).thenReturn(response); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getCode()).thenReturn(302); + + 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/"; - when(mockGetMethod.getFirstHeader(Utils.LOCATION_HEADER)) + when(response.getFirstHeader(Utils.LOCATION_HEADER)) .thenReturn(new BasicHeader("Location", fakeLocation)); proxy = new HTTPProxy() { @@ -121,27 +128,74 @@ 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(); - Assert.assertNotNull(data); - Assert.assertTrue(data.length == 0); + assertNotNull(data); + 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 - public void testPost() throws Exception { + 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(mockHttpClient.execute(mockPostMethod)).thenReturn(response); + when(response.headerIterator()).thenReturn(Collections.emptyList().iterator()); + mockHttpClient = mock(CloseableHttpClient.class); + 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; @@ -159,7 +213,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 +227,7 @@ 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); + assertNotNull(data); + assertNotEquals(0, data.length); } - -} \ 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..5d2517e 100644 --- a/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java +++ b/src/test/java/it/geosolutions/httpproxy/RequestHeaderFilterTest.java @@ -19,45 +19,45 @@ */ package it.geosolutions.httpproxy; +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 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 javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.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.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import static org.mockito.Mockito.*; +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 extends Mockito { +class RequestHeaderFilterTest { Map parameters = new HashMap<>(); - private List
headers = new ArrayList<>(); - @Before - public void setUp() { + @BeforeEach + void setUp() { // URL must match one of the reqtypeWhitelist patterns in proxy.properties parameters.put("url", new String[]{"http://sample.com/csw"}); } @@ -66,7 +66,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()); @@ -91,22 +91,22 @@ 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[]{}); - when(mockHttpClient.execute(mockGetMethod)).thenReturn(response); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getCode()).thenReturn(200); + when(response.getEntity()).thenReturn(new StringEntity("OK")); + when(response.headerIterator()).thenReturn(Collections.
emptyList().iterator()); + 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 { - HttpClient mockHttpClient = mock(HttpClient.class); + void testBlacklistRemovesHeaders() throws Exception { + CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); HTTPProxy proxy = createProxy("proxy.properties", @@ -135,14 +135,14 @@ 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 { - HttpClient mockHttpClient = mock(HttpClient.class); + void testWhitelistOnlyForwardsAllowedHeaders() throws Exception { + CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); final HttpGet mockGetMethod = buildMockGet(mockHttpClient); HTTPProxy proxy = createProxy("proxy.properties", @@ -168,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()); @@ -177,12 +177,12 @@ 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. // 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", @@ -211,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/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..e869b28 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; }