From bd7e8482fbb029c1458a3ae5e173442927dc12ba Mon Sep 17 00:00:00 2001 From: Ivan Olexyn Date: Sat, 2 May 2020 14:31:59 +0200 Subject: [PATCH] ~ resume --- copy-restart.sh | 10 +- misp-mirror/pom.xml | 5 +- misp-rev/src/main/webapp/WEB-INF/web.xml | 2 +- mispmock/README.md | 16 + .../src/com/olexyn/misp/mock/AdapterMock.java | 99 ++ .../com/olexyn/misp/mock/AdapterRunnable.java | 20 + .../src/com/olexyn/misp/mock/BridgeMock.java | 84 ++ .../com/olexyn/misp/mock/BridgeRunnable.java | 21 + mispmock/src/com/olexyn/misp/mock/Main.java | 42 + .../src/com/olexyn/misp/mock/MockSet.java | 11 + .../olexyn/misp/mock/actor/ActorRunnable.java | 57 + .../com/olexyn/misp/mock/actor/AppMock.java | 56 + .../com/olexyn/misp/mock/actor/UserMock.java | 91 ++ .../misp/mock/exchange/ExchangeMock.java | 50 + .../misp/mock/exchange/RequestMock.java | 84 ++ .../misp/mock/exchange/ResponseMock.java | 209 +++ mispmock/threads-in-mock.png | Bin 0 -> 26372 bytes mispmock/threads-in-mock.uxf | 340 +++++ onion-diagram.uxf | 204 +++ test-proxy/currentconfig.xml | 113 ++ test-proxy/install-locally.sh | 10 + test-proxy/pom.xml | 63 +- test-proxy/someconfig.xml | 56 + test-proxy/src/main/java/module-info.java | 36 - test-proxy/src/main/java/servlets/CGI.java | 572 ------- .../java/servlets/CloseableDoSFilter.java | 39 - .../src/main/java/servlets/ConcatServlet.java | 147 -- .../main/java/servlets/CrossOriginFilter.java | 505 ------- .../java/servlets/DataRateLimitedServlet.java | 313 ---- .../src/main/java/servlets/DoSFilter.java | 1329 ----------------- .../src/main/java/servlets/EventSource.java | 108 -- .../java/servlets/EventSourceServlet.java | 236 --- .../src/main/java/servlets/HeaderFilter.java | 194 --- .../servlets/IncludeExcludeBasedFilter.java | 185 --- .../main/java/servlets/PushCacheFilter.java | 306 ---- .../java/servlets/PushSessionCacheFilter.java | 194 --- .../src/main/java/servlets/PutFilter.java | 356 ----- .../src/main/java/servlets/QoSFilter.java | 380 ----- .../src/main/java/servlets/WelcomeFilter.java | 69 - .../src/main/java/servlets/package-info.java | 23 - test-proxy/src/main/webapp/WEB-INF/web.xml | 23 +- test-proxy/src/main/webapp/index.jsp | 19 +- 42 files changed, 1669 insertions(+), 5008 deletions(-) create mode 100644 mispmock/README.md create mode 100644 mispmock/src/com/olexyn/misp/mock/AdapterMock.java create mode 100644 mispmock/src/com/olexyn/misp/mock/AdapterRunnable.java create mode 100644 mispmock/src/com/olexyn/misp/mock/BridgeMock.java create mode 100644 mispmock/src/com/olexyn/misp/mock/BridgeRunnable.java create mode 100644 mispmock/src/com/olexyn/misp/mock/Main.java create mode 100644 mispmock/src/com/olexyn/misp/mock/MockSet.java create mode 100644 mispmock/src/com/olexyn/misp/mock/actor/ActorRunnable.java create mode 100644 mispmock/src/com/olexyn/misp/mock/actor/AppMock.java create mode 100644 mispmock/src/com/olexyn/misp/mock/actor/UserMock.java create mode 100644 mispmock/src/com/olexyn/misp/mock/exchange/ExchangeMock.java create mode 100644 mispmock/src/com/olexyn/misp/mock/exchange/RequestMock.java create mode 100644 mispmock/src/com/olexyn/misp/mock/exchange/ResponseMock.java create mode 100644 mispmock/threads-in-mock.png create mode 100644 mispmock/threads-in-mock.uxf create mode 100644 onion-diagram.uxf create mode 100644 test-proxy/currentconfig.xml create mode 100755 test-proxy/install-locally.sh create mode 100644 test-proxy/someconfig.xml delete mode 100644 test-proxy/src/main/java/module-info.java delete mode 100644 test-proxy/src/main/java/servlets/CGI.java delete mode 100644 test-proxy/src/main/java/servlets/CloseableDoSFilter.java delete mode 100644 test-proxy/src/main/java/servlets/ConcatServlet.java delete mode 100644 test-proxy/src/main/java/servlets/CrossOriginFilter.java delete mode 100644 test-proxy/src/main/java/servlets/DataRateLimitedServlet.java delete mode 100644 test-proxy/src/main/java/servlets/DoSFilter.java delete mode 100644 test-proxy/src/main/java/servlets/EventSource.java delete mode 100644 test-proxy/src/main/java/servlets/EventSourceServlet.java delete mode 100644 test-proxy/src/main/java/servlets/HeaderFilter.java delete mode 100644 test-proxy/src/main/java/servlets/IncludeExcludeBasedFilter.java delete mode 100644 test-proxy/src/main/java/servlets/PushCacheFilter.java delete mode 100644 test-proxy/src/main/java/servlets/PushSessionCacheFilter.java delete mode 100644 test-proxy/src/main/java/servlets/PutFilter.java delete mode 100644 test-proxy/src/main/java/servlets/QoSFilter.java delete mode 100644 test-proxy/src/main/java/servlets/WelcomeFilter.java delete mode 100644 test-proxy/src/main/java/servlets/package-info.java diff --git a/copy-restart.sh b/copy-restart.sh index ef3f38d..618fa08 100755 --- a/copy-restart.sh +++ b/copy-restart.sh @@ -1,7 +1,8 @@ #!/bin/bash - tomcat_webapps="${HOME}/app/tomcat/webapps" +jetty_webapps="${HOME}/app/jetty9.4/webapps" +webapps=$jetty_webapps cwd=$(pwd) /home/user/app/tomcat/bin/shutdown.sh @@ -10,14 +11,15 @@ echo "================" echo "END TOMCAT STOP " echo "================" -cp -v "${cwd}/misp-mirror/target/misp-mirror-0.1.war" "${tomcat_webapps}" -cp -v "${cwd}/misp-rev/target/misp-rev-0.1.war" "${tomcat_webapps}" +cp -v "${cwd}/misp-mirror/target/misp-mirror-0.1.war" "${webapps}" +cp -v "${cwd}/misp-rev/target/misp-rev-0.1.war" "${webapps}" +cp -v "${cwd}/test-proxy/target/test-proxy-0.1.war" "${webapps}" echo "================" echo "END COPY" echo "================" -/home/user/app/tomcat/bin/startup.sh +# /home/user/app/tomcat/bin/startup.sh echo "================" echo "END TOMCAT START " diff --git a/misp-mirror/pom.xml b/misp-mirror/pom.xml index fcc6d6d..4f9c0ad 100644 --- a/misp-mirror/pom.xml +++ b/misp-mirror/pom.xml @@ -15,8 +15,8 @@ UTF-8 - 1.7 - 1.7 + 1.11 + 1.11 @@ -53,6 +53,7 @@ + ${project.artifactId} diff --git a/misp-rev/src/main/webapp/WEB-INF/web.xml b/misp-rev/src/main/webapp/WEB-INF/web.xml index 717dad2..a99e628 100644 --- a/misp-rev/src/main/webapp/WEB-INF/web.xml +++ b/misp-rev/src/main/webapp/WEB-INF/web.xml @@ -7,7 +7,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> - misp-fwd + misp-rev misp-rev diff --git a/mispmock/README.md b/mispmock/README.md new file mode 100644 index 0000000..742e46c --- /dev/null +++ b/mispmock/README.md @@ -0,0 +1,16 @@ +#### About +* Mockup for easy debugging. + * Mock *requests* and *responses*. + * Teleport them between *actors*. +* There are 4 actors which are mocked: + * `AppMock` : the App hosted on *localhost*. + * `ClientMock` : the *mispclient* Servlet. + * `BridgeMock` : the *mispbridge* Servlet. + * `UserMock` : the user agent accessing the *mispbridge* from the internet. +* `MockSet` knows all 4 actors, all the 4 actors know `MockSet`. + * Thus all 4 actors know each other. + +
+ +##### Threads in Mock + ![](threads-in-mock.png) \ No newline at end of file diff --git a/mispmock/src/com/olexyn/misp/mock/AdapterMock.java b/mispmock/src/com/olexyn/misp/mock/AdapterMock.java new file mode 100644 index 0000000..f8cfe9b --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/AdapterMock.java @@ -0,0 +1,99 @@ +package com.olexyn.misp.mock; + +import com.olexyn.misp.helper.Ride; +import com.olexyn.misp.mock.exchange.ExchangeMock; +import com.olexyn.misp.adapter.Adapter; + +import javax.servlet.ServletException; +import java.io.IOException; + + +/** + * Wraps a ClientServlet so it can be debugged easily, i.e. without running Tomcat. + */ +public class AdapterMock extends Adapter { + + private MockSet mockSet; + + + public AdapterMock(MockSet mockSet) { + super(); + mockSet.adapterMock = this; + this.mockSet = mockSet; + } + + /** + * Send POST (Ride). + * Parse response. + */ + @Override + protected Ride doSendPostRide(Ride ride) throws IOException, InterruptedException, ServletException { + + // Mock Exchange + final ExchangeMock exchange = new ExchangeMock(); + exchange.request.setMethod("POST"); + exchange.request.setContentType("application/json"); + exchange.request.setContent(ride.json().getBytes()); + + synchronized (exchange) { + // Mock POST (Ride) + exchange.notify(); + mockSet.bridgeMock.doPost(exchange.request, exchange.response); + exchange.wait(); + exchange.notify(); + } + + // handle OK (Ride)(Request) + return new Ride(exchange.response.getContentAsString()); + } + + + /** + * Send GET (Request) to App. + * Parse response. + */ + @Override + protected String doSendGetRequest(String request) throws IOException { + + // Mock Exchange + final ExchangeMock exchange = new ExchangeMock(); + + exchange.request.setMethod("GET"); + exchange.request.setContent(request.getBytes()); + + synchronized (exchange) { + // Mock GET (Request) + exchange.notify(); + mockSet.appMock.doGet(exchange.request, exchange.response); + + // handle OK (Data) + exchange.notify(); + } + + return exchange.response.getContentAsString(); + } + + + /** + * Send GET (Ride)(Request)(Data). + * Parse response. + */ + @Override + protected void doSendGetRideRequest(Ride ride) throws IOException, InterruptedException { + + // Mock Exchange + final ExchangeMock exchange = new ExchangeMock(); + exchange.request.setMethod("GET"); + exchange.request.setContentType("application/json"); + exchange.request.setContent(ride.json().getBytes()); + + synchronized (exchange) { + // Mock GET (Ride)(Request)(Data) + exchange.notify(); + mockSet.bridgeMock.doGet(exchange.request, exchange.response); + exchange.wait(); + exchange.notify(); + } + } + +} \ No newline at end of file diff --git a/mispmock/src/com/olexyn/misp/mock/AdapterRunnable.java b/mispmock/src/com/olexyn/misp/mock/AdapterRunnable.java new file mode 100644 index 0000000..7cf08fa --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/AdapterRunnable.java @@ -0,0 +1,20 @@ +package com.olexyn.misp.mock; + +/** + * Pass the MockSet. + * Provide a Runnable. + */ +public class AdapterRunnable implements Runnable { + + private MockSet mockSet; + + public AdapterRunnable(MockSet mockSet){ + super(); + this.mockSet = mockSet; + } + + @Override + public void run() { + new AdapterMock(mockSet); + } +} diff --git a/mispmock/src/com/olexyn/misp/mock/BridgeMock.java b/mispmock/src/com/olexyn/misp/mock/BridgeMock.java new file mode 100644 index 0000000..4948a5c --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/BridgeMock.java @@ -0,0 +1,84 @@ +package com.olexyn.misp.mock; + + +import com.olexyn.misp.mock.exchange.ExchangeMock; +import com.olexyn.misp.mock.exchange.RequestMock; +import com.olexyn.misp.bridge.BridgeServlet; + + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class BridgeMock extends BridgeServlet { + + public BridgeMock(MockSet mockSet) { + super(); + mockSet.bridgeMock = this; + } + + + /** + * handle GET (Request)
+ * - wait for availableRides to have an entry
+ * - move move Ride to deliveredRides
+ * - wait for Ride to have Data
+ * - respond to the original request with Data + */ + @Override + protected void handleGetRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + + final ExchangeMock exchange; + + synchronized (exchange = ((RequestMock) request).exchange) { + + super.handleGetRequest(request,response); + + exchange.notify(); + } + } + + + /** + * handle GET (Ride)(Data) + * if Ride in ForwardedRequest + * remove Ride from ForwardedRequest + * add Ride to NewData + * send OK (Ride)(Data) + * remove Ride from NewData + * add Ride to ForwardedData + * send OK (EOL) + * remove Ride from ForwardedData + * add Ride to EOL + */ + protected void handleGetRideRequestData(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + + final ExchangeMock exchange; + + synchronized (exchange = ((RequestMock) request).exchange) { + + super.handleGetRideRequestData(request,response); + + exchange.notify(); + } + } + + + /** + * handle POST (Ride) + * - wait for Ride to be booked + * - send OK to Client + */ + @Override + protected void handlePostRide(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + + final ExchangeMock exchange; + + synchronized (exchange = ((RequestMock) request).exchange) { + + super.handlePostRide(request,response); + + exchange.notify(); + } + } +} diff --git a/mispmock/src/com/olexyn/misp/mock/BridgeRunnable.java b/mispmock/src/com/olexyn/misp/mock/BridgeRunnable.java new file mode 100644 index 0000000..8874863 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/BridgeRunnable.java @@ -0,0 +1,21 @@ +package com.olexyn.misp.mock; + +/** + * Pass the MockSet. + * Provide a Runnable. + */ +public class BridgeRunnable implements Runnable { + + MockSet mockSet; + + public BridgeRunnable(MockSet mockSet){ + super(); + this.mockSet = mockSet; + } + + @Override + public void run() { + new BridgeMock(mockSet); + } + +} diff --git a/mispmock/src/com/olexyn/misp/mock/Main.java b/mispmock/src/com/olexyn/misp/mock/Main.java new file mode 100644 index 0000000..5274457 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/Main.java @@ -0,0 +1,42 @@ +package com.olexyn.misp.mock; + +import com.olexyn.misp.mock.actor.AppMock; +import com.olexyn.misp.mock.actor.UserMock; + +public class Main { + + + + + + public static void main(String... args){ + + MockSet mockSet = new MockSet(); + + Runnable publicRunnable = new UserMock(mockSet); + Runnable bridgeRunable = new BridgeRunnable(mockSet); + Runnable adapterRunnable = new AdapterRunnable(mockSet); + //Runnable clientRunnable = new ClientRunnable(mockSet); + Runnable appRunnable = new AppMock(mockSet); + + Thread userThread = new Thread(publicRunnable); + Thread bridgeThread = new Thread(bridgeRunable); + Thread adapterThread = new Thread(adapterRunnable); + //Thread clientThread = new Thread(clientRunnable); + Thread appThread = new Thread(appRunnable); + + userThread.setName("userThread"); + userThread.start(); + bridgeThread.setName("bridgeThread"); + bridgeThread.start(); + adapterThread.setName("adapterThread"); + adapterThread.start(); + //clientThread.setName("clientThread"); + //clientThread.start(); + appThread.setName("appThread"); + appThread.start(); + } +} + + + diff --git a/mispmock/src/com/olexyn/misp/mock/MockSet.java b/mispmock/src/com/olexyn/misp/mock/MockSet.java new file mode 100644 index 0000000..533a1ec --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/MockSet.java @@ -0,0 +1,11 @@ +package com.olexyn.misp.mock; + +import com.olexyn.misp.mock.actor.AppMock; +import com.olexyn.misp.mock.actor.UserMock; + +public class MockSet { + public UserMock userMock; + public BridgeMock bridgeMock; + public AdapterMock adapterMock; + public AppMock appMock; +} \ No newline at end of file diff --git a/mispmock/src/com/olexyn/misp/mock/actor/ActorRunnable.java b/mispmock/src/com/olexyn/misp/mock/actor/ActorRunnable.java new file mode 100644 index 0000000..115db66 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/actor/ActorRunnable.java @@ -0,0 +1,57 @@ +package com.olexyn.misp.mock.actor; + +import com.olexyn.misp.mock.MockSet; +import com.olexyn.misp.mock.exchange.ExchangeMock; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + + +/** + * Generic Runnable. + * Serves as basis for Actors that are not Servlets. + */ +public abstract class ActorRunnable implements Runnable { + + + List exchanges = new ArrayList<>(); + + protected MockSet mockSet; + + + + public ActorRunnable(MockSet mockSet){ + this.mockSet = mockSet; + + } + + + + + @Override + public void run() { + // + } + + + public void processExchange(ExchangeMock exchange) throws IOException, ServletException { + + + if (exchange.request.getMethod().equalsIgnoreCase("GET")) { + doGet(exchange.request, exchange.response); + } else if (exchange.request.getMethod().equalsIgnoreCase("POST")) { + doPost(exchange.request, exchange.response); + } + } + + + public abstract void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; + + public abstract void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; + +} diff --git a/mispmock/src/com/olexyn/misp/mock/actor/AppMock.java b/mispmock/src/com/olexyn/misp/mock/actor/AppMock.java new file mode 100644 index 0000000..b3463ea --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/actor/AppMock.java @@ -0,0 +1,56 @@ +package com.olexyn.misp.mock.actor; + + + +import com.olexyn.misp.mock.MockSet; +import com.olexyn.misp.mock.exchange.ExchangeMock; +import com.olexyn.misp.mock.exchange.RequestMock; +import org.apache.commons.io.IOUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +public class AppMock extends ActorRunnable { + + public AppMock(MockSet mockSet) { + super(mockSet); + mockSet.appMock = this; + } + + + @Override + public void run() { + while (true) { + + + } + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + + RequestMock mockRequest = (RequestMock) request; + ExchangeMock exchange = mockRequest.exchange; + + synchronized (exchange) { + String parsedRequest = IOUtils.toString(request.getReader()); + + String dataString = "DATA-" + parsedRequest; + + exchange.response.setStatus(200); + PrintWriter writer = exchange.response.getWriter(); + writer.write(dataString); + writer.flush(); + writer.close(); + + exchange.notify(); + } + } + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) { + + } +} diff --git a/mispmock/src/com/olexyn/misp/mock/actor/UserMock.java b/mispmock/src/com/olexyn/misp/mock/actor/UserMock.java new file mode 100644 index 0000000..cb384c1 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/actor/UserMock.java @@ -0,0 +1,91 @@ +package com.olexyn.misp.mock.actor; + +import com.olexyn.misp.mock.MockSet; +import com.olexyn.misp.mock.exchange.ExchangeMock; + + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + + + +public class UserMock extends ActorRunnable { + + + + final String longRequest; + + int requestCount = 0; + + public UserMock(MockSet mockSet){ + super(mockSet); + mockSet.userMock = this; + + + StringBuilder sb = new StringBuilder(); + for (int i=0;i<100;i++){ + sb.append("foo"); + } + longRequest = sb.toString(); + + + } + + @Override + public void run() { + while (true){ + try { + sendGetRequest(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + } + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + + } + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) { + + } + + + + /** + * # send GET (Request) + * Generated by Loop + */ + void sendGetRequest() throws IOException, InterruptedException { + + // Mock Exchange + ExchangeMock exchange = new ExchangeMock(); + + exchange.request.setMethod("GET"); + //exchange.request.setContentType("application/json"); + + //String requestBody = longRequest+"-"+(++requestCount); + String requestBody = "REQUEST-"+(++requestCount); + String jsonString = "{\"request\":\""+requestBody+ "\"}"; + jsonString = "asdfasdfa"; + exchange.request.setContent(jsonString.getBytes()); + + synchronized (exchange){ + // Mock GET (Request) + exchange.notify(); + mockSet.bridgeMock.doGet(exchange.request,exchange.response); + exchange.wait(); + + // handle OK (Data) + String data = exchange.response.getContentAsString(); + System.out.println(data + " of "+requestBody); + exchange.notify(); + } + } + +} diff --git a/mispmock/src/com/olexyn/misp/mock/exchange/ExchangeMock.java b/mispmock/src/com/olexyn/misp/mock/exchange/ExchangeMock.java new file mode 100644 index 0000000..1e0c0e3 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/exchange/ExchangeMock.java @@ -0,0 +1,50 @@ +package com.olexyn.misp.mock.exchange; + + +import java.util.ArrayList; +import java.util.List; + +/** + * How "exchange" mocks an exchange:
+ *
+ * An "exchange" corresponds to request & reply pair.
+ * - i.e. a GET & OK pair.
+ * Steps:
+ * 1. new Exchange()
+ * 2. set properties of .request
+ * 3. "send" request by using exchange.notify(); recipient.doGet();
+ * 4. recipient does someting with request
+ * - - recipient sets proerties of .response
+ * - - recipient sends reply using exchange.notify();
+ * 5. "receive" response by using exchange.wait(); + */ +public class ExchangeMock { + + public static List exchangeList = new ArrayList<>(); + + + private static int next_id=0; + public int id; + + public RequestMock request =new RequestMock(this); + public ResponseMock response = new ResponseMock(this); + + public ExchangeMock(){ + id = next_id++; + exchangeList.add(this); + } + + + + + + + + + +} + + + + + diff --git a/mispmock/src/com/olexyn/misp/mock/exchange/RequestMock.java b/mispmock/src/com/olexyn/misp/mock/exchange/RequestMock.java new file mode 100644 index 0000000..0665303 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/exchange/RequestMock.java @@ -0,0 +1,84 @@ +package com.olexyn.misp.mock.exchange; + +import org.springframework.mock.web.MockHttpServletRequest; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; +import java.io.IOException; +import java.util.Collection; + +/** + * - Contains reference to ExchangeMock + * - Rest is dummy code. + */ +public class RequestMock extends MockHttpServletRequest implements HttpServletRequest { + + public final ExchangeMock exchange; + + public RequestMock(ExchangeMock exchange){ + super(); + this.exchange = exchange; + } + + @Override + public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { + return false; + } + + @Override + public void login(String s, String s1) throws ServletException { + + } + + @Override + public void logout() throws ServletException { + + } + + @Override + public Collection getParts() throws IOException, ServletException { + return null; + } + + @Override + public Part getPart(String s) throws IOException, ServletException { + return null; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } +} \ No newline at end of file diff --git a/mispmock/src/com/olexyn/misp/mock/exchange/ResponseMock.java b/mispmock/src/com/olexyn/misp/mock/exchange/ResponseMock.java new file mode 100644 index 0000000..fb7ff89 --- /dev/null +++ b/mispmock/src/com/olexyn/misp/mock/exchange/ResponseMock.java @@ -0,0 +1,209 @@ +package com.olexyn.misp.mock.exchange; + + +import org.springframework.mock.web.MockHttpServletResponse; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * - Contains reference to ExchangeMock + * - Rest is dummy code. + */ +public class ResponseMock extends MockHttpServletResponse implements HttpServletResponse { + + public ExchangeMock exchange; + + public ResponseMock(ExchangeMock exchange){ + super(); + this.exchange = exchange; + } + + + @Override + public void addCookie(Cookie cookie) { + + } + + @Override + public boolean containsHeader(String s) { + return false; + } + + @Override + public String encodeURL(String s) { + return null; + } + + @Override + public String encodeRedirectURL(String s) { + return null; + } + + @Override + public String encodeUrl(String s) { + return null; + } + + @Override + public String encodeRedirectUrl(String s) { + return null; + } + + @Override + public void sendError(int i, String s) throws IOException { + + } + + @Override + public void sendError(int i) throws IOException { + + } + + @Override + public void sendRedirect(String s) throws IOException { + + } + + @Override + public void setDateHeader(String s, long l) { + + } + + @Override + public void addDateHeader(String s, long l) { + + } + + @Override + public void setHeader(String s, String s1) { + + } + + @Override + public void addHeader(String s, String s1) { + + } + + @Override + public void setIntHeader(String s, int i) { + + } + + @Override + public void addIntHeader(String s, int i) { + + } + + @Override + public void setStatus(int i) { + + } + + @Override + public void setStatus(int i, String s) { + + } + + @Override + public int getStatus() { + return 0; + } + + @Override + public String getHeader(String s) { + return null; + } + + @Override + public List getHeaders(String s) { + return null; + } + + @Override + public Set getHeaderNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() { + return null; + } + + @Override + public PrintWriter getWriter() throws UnsupportedEncodingException { + return super.getWriter(); + } + + @Override + public void setCharacterEncoding(String s) { + + } + + @Override + public void setContentLength(int i) { + + } + + @Override + public void setContentType(String s) { + + } + + @Override + public void setBufferSize(int i) { + + } + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public void flushBuffer() { + + } + + @Override + public void resetBuffer() { + + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() { + + } + + @Override + public void setLocale(Locale locale) { + + } + + @Override + public Locale getLocale() { + return null; + } +} diff --git a/mispmock/threads-in-mock.png b/mispmock/threads-in-mock.png new file mode 100644 index 0000000000000000000000000000000000000000..af9ac6ef9745a8ac3cc392304d0e9c55c1c55e2f GIT binary patch literal 26372 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

b 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]H^F5N?S*+kkvrz^+#WVZWmC1xpXyduE zM%I<=JP=ILq|hI8oV1S*vAvze#FaK4e0|0kl7|RS8bq`_6nv`@syeR%^vQ`=6#&Z`2`sdx5krnvGFWMw%OzlamyNpY`1hQVJ)oo9ex%E>m7W> zY?~kqZGI%50?S)iyw>4OfoPQsgg(9gQgBD2W}cSEKDl>DNwPlErqT^^sM$30^mRgunc@&9m6Xay`>Hf5rZ`@%BAKs8@WYuhTf^BRlthFsS z{Tyk2M7JeB&}E&ExQuk>iP3%Gn@Lm|@h{sKwB5v3(C4@!Y2h(FX72~dYuxpwAd`m6 zFqe9$tYj{-dCP>Et8a8Cr^z)*-=Dn-!620*HDEIA*1h6}V^SPsLrC6GOwB98E<}6A zops`E-@29+k9IcFU?3;>L;W51GfMrwF%~~FdfXGzd+)FrDY)D)z7X)N(#Nyh6udl(+;K64<0~HK+MwboN>UDEmaI?;sl&1Q{pf zG;GXd>XYLQpD;VN6PI+wGt5*|xun4N3+5Z63w`I&DL)-2Zw-Y+zf%<66JfgwolG*DFYs^pk#8w z5Z67UCd$Y(_Rw-3v%TWsaqH|a{qvucV8&3|yousLS)XZk)xM_%2}Wz`Z%XDDUZ6_I z`Za5>t?%Amf4OLJBA&vu{D6v|!oZoM;zjP7#NnKjv6T?b`uDftLb zfzhf(lQjF7xV`Ty=NzSrhD*QUl~1O2AVs3)D%P^PW|G2_;2Om_G*3LYJMjtsaLmVD zwF6{jC4PlhTGom*_QQ2m&+~;@8*{{R`PTJI&a7SzOmOI}T_+K(Iv!P#|5`M1$chsq z`-Z=iDf!q&OLnuYqXHxkVzHh^`5$rfXpKuqx1YK$CsfZ^KF<63H*??K8~o5VqV@QT#hg~jV+{8!qS+e%L@*!Eyq5E zX42q9h0E`-IOZ^rDfKwYYBRYQO6;)Sc^5Dvskvovs!~KndwE|j?x=nJo@Wh}UU&(d z>I~rJejRub6ca{eG-6}iRC~W4kE_JNh+de_AK0v3)k(R?Owya?WdninjqDyXeoZZl z*PS@OBxmyRlNf&JRsIZ=%$105VO5zY-oHFc_`oPnEIku#TZWw2s;0A30M2GWauZE4 z#7hDr`^d6>D4JIxF+QJu`H|X9wNiW2%>~r9$|4|oG?T^7S13Zor7$U$7WS>2m z>cYY=dK9#@nm7p(&x7wygPgO}WQ!HjSy5m;a?mLYouFr`b;zvJsjogbg_VR4cq}IG zTz-cg^2#o}zm-{ZpYp@`EvEB~WeKSo+RRe7Q%{c&X%gh0RwBBQnZae+@c~dr=s`qU zYAO-Uy-Yy3P_pZEiH`#rG|>41?KdAgdk{R25li_}+VI9|5E3SL$zUnpet^Y*_^?wY zn?DV5gMst)>(`0HD_Il8fv-0k6o!G5oD%#b+jHi9p|&T+K{6)W-m=(t?}T^r%(`!aVGaY9TpLJ@O=M&OcUQjv)h-68 znA5G>edO~T=2S|{0Lm^ccUd*2(Dh#gR55d2FMznFovDC9ww;Eo0Bw14Qc~5oZ)Nr~ zN$|2LK&!v)*#qq7TEMwp4*K%P{8lOH>C@X&@yLbJIgq1|z%d@K4FLT1iTo1m_o3I; z>|LjUtbJ`+fviW@4b8=Sux5sP`dkA1^ydwb%;g&-iNi@Z(y`-hy=r=75U25~>uQA_ z^_LQG{A0PLH$k3Dgu)~L0HmiEZ#>c?-3H3z3P8btm5X}+KE+J|4@(K?Ri5-9q!a_G zwTu(OaxgXP2grDS^8s^RA4A4$VS#>rAd^;p{P?ks&TyVlBa@Ta=(|KBxz=@7jj{@Z z3bzSMmiG4c<|r{}pWV@peF z8kl;qk#yqZ&|83;=>uhtq$G$Xo&n5}Hx|gtG>V^IEh__Af9=F?GTI!Zcz?)fEWbPQ zbES)8q9s8WHT%JTRZy3aq2s zpRl5P-op%ql`I%!_4O+~q*z73F~1G00zv1qs=YE*v>bVc4q%dB-YVKyr?Jcm-D0RJAfs4`DkY=>ba} z#HOJ@=sgrSsqIbQt65~B4vJDB=PktcOhFGMrj)E2-xp@t3w-_kfs8!L)2JNqSM55zz(&=eu_ovaYkU%VK`I z3U0`I9D44RUE)MM2cvgKk>V;uWP<6wzIO4`=kGcJYw-=5@CuIE9RY=@F3~v666DSXh`sS;e5MR;~Nm8`OF+`Oac~tDq)uOm%g2#L&IA zM87(vvq6G8Prxz_6qx2eX@~eMNR9AUknKksf>TU@G^#k*nOiL1)Y(X8b!Ab_wg3zr z*!Gu8fylf(Xx8rG@eO;-x6D3LTb*h}j=rK{vd!(xuXPaa2L9~BUdtW4PQXAcXYn4O zFL^urgqvT@R2&t&Df26&9NO-&7(#v{6Hqwd43D-Ox-dLx*=Wb%%BhF1>ne|b;iSk> zNG`e9bFjbbwl%O`QZnS( zmW{apzL-b4h<4=R@j6X#tWS<7@5Zf>(FmLO#L5+gm$|)isa3|jhRse)zP)>AY5y6h z84{dJqz&U+S35yUbyK{@%K;z*;GcVel0W|JeRtgxSL80gGd^8GPog2GdgE+O*N;Jc zDXit!#eN2dA;dwuX8$?&O0X_dm3pBl!eg`~_tooJxUfXITUU_SxavQlNh0 zzsNLKdKTxzbr0ibZ@ms17lWPFBa8gplcPHVh zw>$q6cQ9MxslF$|RtQDRUhZuP`!IiD!6@L3WDIKRgS*>LNSUUVhU3$|m9ohCR!_%; zUc2gr*0;=akC`3j)5am$eYf^zm{qbHGMi_zu#% zb(L7{Vz5uneQu7?7mlD#Tx^1ZWH5C}}}LWiH?P}+uWLw-e2coGV3BBj#XN9I~c%F+^B_hpUFQNi)` z!Fk&vxyL;^Xf-j8!kFeyTLE^Y4Kiv4)FY>Go+r&W;Rv1o?LR^H-~y@?>lK2NqfsYV zAhtd;5*U=z1WKF@vM8uX5$qJp)>vk^Ok4twh+Z*J&S#Vf6V|c8?pF$#a_Y95m;Drx&Bk+guxR4_V&f;>OzAffpK-pPxr3{A!r{oj zU4E!3t2I7s*?lQTbh7Gax7hfQFk^Y$q%M2;%Q@Six=^Qh4V#5*Vg)}bcI_uNyP4x# zxT>7fAdhSs4i@2dZI(_6ZK8JCQ|ZAR!Ge5-v=N_=8ho`*u!TG9Tt$1LnE>|V_}{+s zjQp=&#(UL9!~oHKZ+KC+a1vLvJkEwWq~Q*S zfhlZb-=@49e`Qccn%8do&QopU>+q#=AB5@0Z*~IOPnH{^RyAr;C|ChYj|`>5n@FF| z-^uB$W54M(IA~C0j45a$` zM;-6zSJ}uy6@D!2dVX>sjqG?T%Z?4Ut#g~rvNXFBDLK7rTOtctcivqLq!_2*B~GKn z*p1;i2G=w#=9RTd@Pw+wL@BOJGg4{1(Owf_Tc3L9Y9ppIqYGu-T`>lg3%8HOu{?yE zm#cy526|MU6?+^Gq(JnyZ*yvfhl46Zl-9jnY zYJss*SakUaTqR(B0NB>wS@$AD#>aNpxUO7mywk_k?>a|BxfT^Ii6Ju0-t9mrr1|N* zIWQfq$j0bboy1`{?7p(z8T4&Po^>O)*x-NqQ3Z z_CK#9X?|9D;o-#T8RM}0L2@gqZ}u0)tAQb8JsukFU+?w9XJ#hl(YvjG;u)#&>CY!; z8?GJE5f@~0eLDT{S!Jv*+D*h7zzk25_AkD$sXF5$T}WV#U4N;KrSsZ?#iy`evcXd>UimW`KQEke-V zR7@zh6I~xi_WnL_(c2r}MYFuisaZia2(=nt0Q~WFa%5AA$$qHTcjymVbfY%?hio3D zuh0aXe=)$oVc_WjRQR&{NFO-%>F!9UniR6I`|G4U06DbP&IH|(Xl3Ulwb0Ifzq>jk zx|U&P@Yoh z!o%kjW`u8IMFi`&R)4My2`g?#oYS?jdR&F)LAV*W)^m|^W?^(K50TM25nhoyDvapE z;xRhycL!2t>Q_LCMt_laj?$HM!#+s1{W7T1yl*vrKTV_j?DsY}w&|qJWYjNpSFvpG zAM)3?y0Uk!V_r{C5#M^$TJq!`eF#x!$Uzyq>`0a9_)i6s45t>hzE~;5h-{O3G2^DY zkm*Ks2RE`M>4#L%Y`l>>Wq+;WrCFSHt&yaY|2CPE4>U-t+bkJ#w7B;nBZoArDkdm+XPBemzP8E!0h;mWl8z#~&+YRe+nL+aybE{nFJ( z%dtJZ)w#pVH{rIPz(e`a1zv0jFUF5pv^$$s8i{MxWz+?M1Y1w0b<@yO2lqNBac zz_CGTVa(Mh$6c#cnNFmvCe>d@d)Z0uHo2#k^<42HjKvm0x@LKx(9}x0Yafff8T3 zoU6PXi-X?qN2rmN< zSK3b}lYUtr3!a5h@QRG|PCVvAOMkGUJCB6IU8J=qwPh)YbZ5mIUla>2X7g#anOm7D z6%2+~Y&%PS?Cjp$SMosMzC!Coe>4G=lV*=^r(Yg3=0n5;<~4V~%mw;j|MQ2D^ZMXa z3`M&{Y$_u=rXFo(K?yp|UwI65V#81os5ccAyx|$V!#2T(QuY30O~+!&UZ7uHcy^hu z1a-mv*!{pO$3}mcwifw{Tmbp5u>ZE2=75aAuCRM5E*4|_3Dn*Gbr7cICFxY@kuYh( z-p7xW3iJvSG@JWcSpVc0zgjF(vI7!=t4$HF3nu{N0KTObprpS-8Vfi`&ChwmKm+7l z6-HtmCKJ$gpFerSQH<2PsC9qgE&* z>#?p36hDCT)+{t5r=`_aP}qo`!fMfr{rvorH-PBe4rFqjY1_RHs=tNhSt(dyxhqg% zojy-(zqO=~y0b?WEF~p{)q2dN#jg-w;#JYl8Rvx`@H=41BdxL^+EanJ0NLPxDaNF=cso8^^MadV?km zkk&#!eu3ZLRM66NmvnNkND*e@PRa9IXJ~PxCto9V*;$*BmzM|fOYKtocBFPQwt%2=0161jj@br+uGP*% zlJfHHfDMF$N`z_f9eO>E67G5OYQj}anZH% z1n%iJ}DL4)^ZO@=cZj78`rj24L+yb~mmAHgf?rs1E_j7PGL^f7J*a!&{w#wRIdx?*9UY zzZs%L+uQoePk=N~-wSZCXRIJx=QvF4YvR9>H(bK zx{?&DIZc1sRTQ9(O;M#>DTY0D!zT}A1I(O84P-ke&o6~nt&hxLCES4swCvD1l2=ZTh7?B0HA3I1WJ@nv#(4%vu5W7eY8nN3kV{ zN-v|+*KfZ`1XnmUHZ}1BZe5xiWeBvZZ4x{kAeNOhj9CMz8#w)vjqRBskd_@u^X_lj zl=t=ZfpR9$Hpj%ofX&`%Hk8r|s$c@HYi)?zfZN_|7*x3-9o6;4WffbyVP^hlJyNkl z#%I!UnO6?Ew<2X`HVbr+IietUpSZa=JKvuVF5i)YhyEh)Gh=^UBtt+*xE{c^$j5}| zx3Xsal8};ZDX9L{Ay0xOcd`8HZT-jsU_F4GjIDYSazmO9zGoA5AL#XIpq^h2)WH#k zes+5xlGQOEtzuFycXa@k#w_hP*Ap;U>iC)!uK^q^0e5P3X2=KHPgks2{-^vel{gqw zm)1!3-5!{?Kc!u5XL0NuQ<+A#3hr8fatu&`L-&?MeApn0%KSUi$$*LnQut;-_g*e- z6?b)KR^2A21^Q_-a9t4)^T7dgpXXQ2^&iB2?#sN-(97N3K&HPQUlrQ&Jo3b?KU5F_ z;IHv7hM9qrLm;DwV~*aK?e?=SZ$i>0ljH=|Js)+$PthZdY=hpmDjt2cy*jmO{_w>w zGU-TNM-q&6AJ55va^XdRZ<7|F?t#ArlFFaGg!hk81DrU3)cf!NW^BK_^#*%w*L1r9 zkk{@FoESgymwy{H;RA>V=cJdn_jxML@utW-dv+(Q{^|Okh7G0Fa1!o&4r#QKddZ*8 zCOB4?BYzjhrafC5)DwvS0!~2AuV(#-)x6=Lim=k=jR2}gpi|h!J#|=_`W^hRCH1y% z6$z|YuRcc^LAk_15VkIv2%DaMf@ZwuzPe1~FdP~d22NZZmF{ua`{G|+q@toCC9U+_ zt3E0n!=GKj7UlkOWQiurKdZq}lrGQ7m)B9)%f==qEQgA7!2tpwcK6!7!=C|z-QeKh z02TXj%}PQFF)r}GvQ%@1bq`j;LH*hitLqMOcx7D}hkHi^lq-%%Lu_~vCf+n?*MJ#2 z0VnamsULA&j)>++j}I=&+FhGT-q)xFXhHp9hXM5KD4ID6)Wtg*koy}ny+m}TJ_zj5 z4sfv%P`MuX81!Ke3ee460HscR<5zM%ze&R*OHr8yDpPFqDgbp#wwpIg!C715mKX^H zxN40Tcs;wR7EsyK2N!OUxvxW)CYQAhM`ZQWap~cE9KC1ga&HfSa|NJ5P;6pjXKyV& z3H8kM@wM>Y!rdljD_w))pPcz8`3?GAivV{aVi$%aBsNys#|ad~H3%t#B|vQ!iU(s`>Jz;B|jl{Q)3A-Y2`aC5FEQE>BDI{bJHecaUrQs~E{H@v)O@J314n z$E^6cOzRk_YB}G0QF9afzNjA(Ttl$TEBc zE@A8$?lI>-jxr&*vmz;;)AHpGTgvC}AA{;#L|c3WWk6ZS)ie0D-kdGaQOdF>=_o?$uZlpRg1vx=jje~RpctIQ@;OIk zJKM#a)>*GISn9yC31frodJ-f$&VB{=&aDUhXa?t&wr@E7p3(pFiMz{wxY}1RuT3FG zhg^>|CyBM2wd?n+hNhV?#X442Y^c5xJ4W-70@I7(POB>`mlu$v+M|kfzz?S{1Dv`e zmVWn8ptQXoew8cjmcQBP`#n!b$)+$13NzLHcbM-h{`eHBQ(AYUgCq{?DpQV$Je0wH zQn6XXJY{-LY$#7ChskK%&?;U^Kc&>j9407Ynk-&*L*078x6FksqRIFhT^d>I7i*p4d8W~}6b*N@JB z?NE?h{iRUiy!?*a&^P27gtNw^BVeDFHGg8MzDEulU z1Xy?6igcLOd(NqqMisuN+uHipr0PL#1f3>2X7hoUkGh!wIb{5Pfe4nh&+$a<6@31p zh8HL(dcaO+R!k+M4Bv0n$^!rs+`QKdx(FZxkl@#RDf@kOS+mWy)QCGYrJ$ey+)}nP z7bo=gP~f$9(D-2Voy^BU6vIl!9JpS^Z7R* z4R-d3>IuS|j1un2ejKiyq1 z%xmh#D`gF5MMy+yth939D@LZ+pE$BVFLzdBYn%u`H^=(#M~)1vjxHZqsOd3U!YHFX z>n(hoKxc1#JFn2+B!BzsAQF>mX8(*+Ow43=)sY%AX2 zJ-hwlPYxTElFJ;{@A}v|`i#XykeUEnytxlczL0rh8)3*6idzKt$2!#vN1Imu6)#I3 z;}0-xd-ca@siX4>UK-v4n4$$Enzes=P4pP+g-=3P^o6e>HJ?y)lk?k!2bzm_$Fj~D zB*anZhc(RD_HOkLu5j1`Oj}p^^mpb9<3G<2MPV9#SvY3^FjdYJxg8+B#O;Dc#CNb6 zboU@*euwItlSffq@B4hNtbqNzYee7qwmlmUDfpDmRgkmjMLyjd3gi8Jhl_h&S)Jao zJMTfD1aCU5348|d_M?9eFtjgc$1wQY#RZ3Py4HGW_w~=2r4K!QPB0q~>?m>1IK+dw zar2WezuFi~Ttn(SMXH$lC~vw58c$ZYcFZ$19WXfq^acLX%yZYJ1N+6LYIx-(|f~NrtnN*;l zRM}V~K5B6rFy)TMbqbx=KqcX8oIe_^yXmmh+6hw|z}0J~OMQqG1_87$Y6Odk6rqNX zhWhRSlyDQiBPuM<4DG78@t?8OQRJem%d>p=_?MX2%r1cvkY zqu&|0Z$ujwyUIU!S)cz2#3sI__IxI;EYgwSfs%C`G4`bec*eN@0~~;4b4>3F(ENIk zGMR_pH!=+Lf2Y5kx3aN8*jrHCf<*xWDn*0=>H&B3@|fK>_*wRHTLkn%-o{Q30wfu6F&Gp|ODb#ibYRPcBDL@UlDzK?Vpp;r(-lUjH*)g%U4}6|4QtQ<;8G87tEApEoe9t0 zrK*~l-qDN}v(>nz7rM*D=lTo1-H(fPOfWKfjh%d*Wd5P(*jV6fb!)I^Kir)g2esCk zLrx!Ab^sS6u2vEd6s(~eoq1b-4TelCNq945a3vl@I_V`L)v9?1Yk9B}>Z7 z?aTdPZ9=H-7PlFNoT;>Qj&`fJx7QI(*1)+_A77A>k$LJwDJ--InFMu-V7i5Pa+2nG zfuZi+6nT|t#1FN0wStJwT${i$`E>$d12#`!?uO;KS`KEz{Um z^CV9fu&ISd32;OwoOX~bZg~J^4r;l&y1MM;Ht5KR2%rs)K{iZ%4h!yOt4azArh$&l zt^+9tHQ&)Vj@i+C$7)EEFS$nXdY`V|;DTO9wUteeS&4Q!!#C0#v@1!QkXvETpwk%5 z83+M3GPV(J%JPCbz`DV70@PbitEvM1?ix~GKYg7yq5x~c`30u;#8M2YoQ&1JFj29;OZs9 zUX}bp-QS!H-kQJfHg>APujv0ggi9;@nE)g2M5vn%o*e+*b!krOqf)6fT3*G@=;-KP zhq<&@6&G#GAN!8k%;s2?Q(_i^DPCjRtuL}j$e=fYV>nd!tEs;uuQc@??M``8ke5gA(s((+9*Qa|+RcfGX?zA{ z^2y1`_VSqR4&yA^n<=gR*^@XZ`hvpoa3nx_jnNCS6;LNT{pLvslgaGp=l~-PU#Bw3 z+JvmB>W2L;yrG8Kek@HzOZSeAdRSD>l5FjU!9gh8xc+e`lhSlZnI)o^URd}slD%X& z0}}RZn?ghFtJ8mXX>W(x?o~Y#)TL^L=TLIC{Cj6b#W!eXWMu(#o4Bzd>9uNL!spKK z{E;lL^7^4_V0jtN$ETyKYo46la+xgUY5Jt?E7xe|y2d@%Q@PVKGcZj{l9P2f`vTe7 zf%s{AlG5KZ(`_^5P5JV(r-yXHyu@1pH}7)&MDxkh4~AWj6kwX1?b5<&&T6~I`^1b< z5JQAnwdDMHa#DCz+ppvCqOIfO$tt(rd zR4%-(3Ibh$(o%KFwfRcDePVImS>9juGyI3t#Kc5J5`=R@H2<1|wc`TD;8I+xI=Tmk z4o*yX9yxN;u-{1~lOU#JV%#z=Q2!$H59KEFyL#6*!hE|N*CIE^n#ZxE0`1~k`otnG z!DQS?5~E|Hju;8<+o2cq=s1oWbmh+q83~p&pqBqd>=Qc$eXcQ9HSC||S|)TW%4*bo z(c}t3iJHphcK1bv{_%7}`1&VcWo2Gt5W{vHxhAO=fY>WoPNa5q*}Ar`-9~nqu2h(k z@1A=p-joCAEBWW5@Vv(A7u~yK*ix^$@x{+3p=#b84&E6hHOl=g)4ze&7&mx#Y$d&I z$h^-l_d}|V-NQ0&?D_9>7bK&@)*<79oCTsI%k@g`l_YXHiWfxL{o%-YDMC(2pAoHS zAwA@T5m|T9onRT{P>~?6dvUJ}kXCB19ZUOh6LP4EY!s3J>_M(jNGpMlxNd~#R4=2% zH-<8}S7@Xa)Sl4z)3*>^>OdWCFyLNE$WLl3toN*1BFKA@9d>*rIVTJsR$=9lzd-Hd ho_8Ym|9vy5y~JX0zWOCY7jA*?H+>Vm+ + + 10 + + UMLClass + + 910 + 50 + 90 + 30 + + inet +lt=- + + + + UMLClass + + 680 + 50 + 100 + 30 + + webhost +lt=- +layer=-1 + + + + UMLClass + + 910 + 90 + 90 + 30 + + user +bg=#90CAF9 + + + + UMLClass + + 680 + 90 + 100 + 30 + + mispbridge +bg=#B39DDB +layer=-1 + + + + Relation + + 410 + 140 + 300 + 50 + + lt=<<<- +POST (Ride) +Generated by Loop + 280.0;20.0;10.0;20.0 + + + Relation + + 760 + 180 + 200 + 40 + + lt=<<<- +GET (Request) +fg=#1E88E5 + 10.0;20.0;180.0;20.0 + + + UMLClass + + 350 + 90 + 100 + 30 + + mispclient +bg=#B39DDB +layer=-1 + + + + Relation + + 410 + 290 + 300 + 50 + + lt=<<<- +GET (Ride)(Request) +(Data) + 280.0;20.0;10.0;20.0 + + + Relation + + 760 + 310 + 190 + 40 + + lt=<<<. +OK (Data) +fg=#1E88E5 + 170.0;20.0;10.0;20.0 + + + Relation + + 410 + 350 + 300 + 40 + + lt=<<<. +OK (Ride) + 10.0;20.0;280.0;20.0 + + + UMLClass + + 150 + 90 + 80 + 30 + + app +bg=#90CAF9 + + + + Relation + + 200 + 220 + 200 + 40 + + lt=<<<- +GET (Request) +fg=#1E88E5 + 10.0;20.0;180.0;20.0 + + + Relation + + 200 + 270 + 200 + 40 + + lt=<<<. +OK (Data) +fg=#1E88E5 + 180.0;20.0;10.0;20.0 + + + Relation + + 410 + 200 + 300 + 40 + + lt=<<<. +OK (Ride)(Request) + 10.0;20.0;280.0;20.0 + + + UMLClass + + 150 + 50 + 300 + 30 + + localhost +lt=- + + + + UMLClass + + 120 + 20 + 910 + 400 + + +lt=.. +layer=-10 + + + + Relation + + 940 + 110 + 30 + 310 + + lt=- +fg=#1E88E5 + 10.0;10.0;10.0;290.0 + + + Relation + + 180 + 110 + 30 + 310 + + lt=- +fg=#1E88E5 + 10.0;10.0;10.0;290.0 + + + UMLClass + + 930 + 200 + 40 + 130 + + +bg=#F6F6F6 +transparency=0 +layer=4 + + + + UMLClass + + 170 + 240 + 40 + 50 + + +bg=#F6F6F6 +transparency=0 +layer=4 + + + + Relation + + 720 + 110 + 30 + 310 + + lt=- +fg=#5E35B1 +layer=-4 + 10.0;10.0;10.0;290.0 + + + Relation + + 390 + 110 + 30 + 310 + + lt=- +fg=#5E35B1 +layer=-4 + 10.0;10.0;10.0;290.0 + + + UMLClass + + 380 + 160 + 40 + 210 + + +bg=#F6F6F6 +transparency=0 +layer=4 + + + + UMLClass + + 690 + 160 + 40 + 60 + + +bg=#F6F6F6 +transparency=0 +layer=4 + + + + UMLClass + + 690 + 310 + 40 + 60 + + +bg=#F6F6F6 +transparency=0 +layer=4 + + + + UMLClass + + 730 + 200 + 40 + 130 + + +bg=#F6F6F6 +transparency=0 +layer=4 + + + diff --git a/onion-diagram.uxf b/onion-diagram.uxf new file mode 100644 index 0000000..a0a6a87 --- /dev/null +++ b/onion-diagram.uxf @@ -0,0 +1,204 @@ + + + 10 + + UMLClass + + 670 + 1040 + 130 + 30 + + TinyVNC + + + + UMLClass + + 670 + 1010 + 130 + 30 + + Guacamole + + + + UMLClass + + 650 + 970 + 170 + 150 + + Debian VM + + + + UMLClass + + 630 + 910 + 210 + 230 + + VirtualBox +layer=-1 + + + + UMLClass + + 670 + 1070 + 130 + 30 + + JavaFX + + + + UMLClass + + 650 + 940 + 170 + 30 + + Bridged Connection + + + + UMLClass + + 610 + 880 + 250 + 280 + + Debian Host +layer=-1 + + + + UMLClass + + 670 + 760 + 130 + 30 + + 4G Modem + + + + UMLClass + + 910 + 610 + 100 + 30 + + URL + + + + UMLClass + + 650 + 580 + 170 + 80 + + Hosting Provider + + + + UMLClass + + 910 + 760 + 100 + 30 + + IP / Port + + + + UMLClass + + 910 + 940 + 100 + 30 + + IP / Port + + + + Relation + + 960 + 780 + 30 + 180 + + lt=<-> + 10.0;10.0;10.0;160.0 + + + UMLClass + + 670 + 610 + 130 + 30 + + WordPress + + + + Relation + + 810 + 940 + 120 + 30 + + lt=- + 100.0;10.0;10.0;10.0 + + + Relation + + 790 + 760 + 140 + 30 + + lt=- + 120.0;10.0;10.0;10.0 + + + Relation + + 960 + 630 + 30 + 150 + + lt=<-> + 10.0;10.0;10.0;130.0 + + + Relation + + 790 + 610 + 140 + 30 + + lt=- + 120.0;10.0;10.0;10.0 + + diff --git a/test-proxy/currentconfig.xml b/test-proxy/currentconfig.xml new file mode 100644 index 0000000..a956c02 --- /dev/null +++ b/test-proxy/currentconfig.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-proxy/install-locally.sh b/test-proxy/install-locally.sh new file mode 100755 index 0000000..aeb5dc0 --- /dev/null +++ b/test-proxy/install-locally.sh @@ -0,0 +1,10 @@ +#!/bin/bash +version="0.1" +file="target/test-proxy-${version}.war" +groupId="com.olexyn.test.proxy" +artifactId="test-proxy" + + + +mvn package +mvn install:install-file -Dfile=${file} -DgroupId=${groupId} -DartifactId=${artifactId} -Dversion=${version} -Dpackaging=war -DgeneratePom=true diff --git a/test-proxy/pom.xml b/test-proxy/pom.xml index ec1059b..80b2939 100644 --- a/test-proxy/pom.xml +++ b/test-proxy/pom.xml @@ -15,8 +15,8 @@ UTF-8 - 1.7 - 1.7 + 1.11 + 1.11 @@ -26,10 +26,61 @@ 4.11 test + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + org.eclipse.jetty + jetty-servlet + 9.4.28.v20200408 + provided + + + org.eclipse.jetty + jetty-util + 9.4.28.v20200408 + + + org.eclipse.jetty + jetty-client + 9.4.28.v20200408 + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + org.eclipse.jetty.toolchain + jetty-test-helper + test + + + org.eclipse.jetty.toolchain + jetty-test-helper + 5.3 + test + + + org.eclipse.jetty + jetty-http + 9.4.28.v20200408 + test + + + org.eclipse.jetty + jetty-io + 9.4.28.v20200408 + test + + - test-proxy @@ -44,6 +95,12 @@ maven-compiler-plugin 3.8.0 + + + 11 + 11 + true + maven-surefire-plugin diff --git a/test-proxy/someconfig.xml b/test-proxy/someconfig.xml new file mode 100644 index 0000000..ab71816 --- /dev/null +++ b/test-proxy/someconfig.xml @@ -0,0 +1,56 @@ + + + + + + + org.eclipse.jetty.servlets.ProxyServlet$Transparent + /proxy/* + + maxThreads + + + + + + maxConnections + + + + + + idleTimeout + + + + + + timeout + + + + + + ProxyTo + http://localhost:8080/misp-mirror + + + Prefix + /proxy + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test-proxy/src/main/java/module-info.java b/test-proxy/src/main/java/module-info.java deleted file mode 100644 index 7457afe..0000000 --- a/test-proxy/src/main/java/module-info.java +++ /dev/null @@ -1,36 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -// This module is a mixed bag of things. -// There are some utility classes that only depend on Servlet APIs, -// but other utility classes that depend on some Jetty module. -module org.eclipse.jetty.servlets -{ - exports org.eclipse.jetty.servlets; - - requires transitive jetty.servlet.api; - requires org.slf4j; - - // Only required if using CloseableDoSFilter. - requires static org.eclipse.jetty.io; - // Only required if using DoSFilter, PushCacheFilter, etc. - requires static org.eclipse.jetty.http; - requires static org.eclipse.jetty.server; - // Only required if using CrossOriginFilter, DoSFilter, etc. - requires static org.eclipse.jetty.util; -} diff --git a/test-proxy/src/main/java/servlets/CGI.java b/test-proxy/src/main/java/servlets/CGI.java deleted file mode 100644 index c462e7a..0000000 --- a/test-proxy/src/main/java/servlets/CGI.java +++ /dev/null @@ -1,572 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.MultiMap; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.UrlEncoded; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/** - * CGI Servlet. - *

- * The following init parameters are used to configure this servlet: - *

- *
cgibinResourceBase
- *
Path to the cgi bin directory if set or it will default to the resource base of the context.
- *
resourceBase
- *
An alias for cgibinResourceBase.
- *
cgibinResourceBaseIsRelative
- *
If true then cgibinResourceBase is relative to the webapp (eg "WEB-INF/cgi")
- *
commandPrefix
- *
may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a - * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.
- *
Path
- *
passed to the exec environment as PATH.
- *
ENV_*
- *
used to set an arbitrary environment variable with the name stripped of the leading ENV_ and using the init parameter value
- *
useFullPath
- *
If true, the full URI path within the context is used for the exec command, otherwise a search is done for a partial URL that matches an exec Command
- *
ignoreExitState
- *
If true then do not act on a non-zero exec exit status")
- *
- */ -public class CGI extends HttpServlet -{ - private static final long serialVersionUID = -6182088932884791074L; - - private static final Logger LOG = LoggerFactory.getLogger(CGI.class); - - private boolean _ok; - private File _docRoot; - private boolean _cgiBinProvided; - private String _path; - private String _cmdPrefix; - private boolean _useFullPath; - private EnvList _env; - private boolean _ignoreExitState; - private boolean _relative; - - @Override - public void init() throws ServletException - { - _env = new EnvList(); - _cmdPrefix = getInitParameter("commandPrefix"); - _useFullPath = Boolean.parseBoolean(getInitParameter("useFullPath")); - _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative")); - - String tmp = getInitParameter("cgibinResourceBase"); - if (tmp != null) - _cgiBinProvided = true; - else - { - tmp = getInitParameter("resourceBase"); - if (tmp != null) - _cgiBinProvided = true; - else - tmp = getServletContext().getRealPath("/"); - } - - if (_relative && _cgiBinProvided) - { - tmp = getServletContext().getRealPath(tmp); - } - - if (tmp == null) - { - LOG.warn("CGI: no CGI bin !"); - return; - } - - File dir = new File(tmp); - if (!dir.exists()) - { - LOG.warn("CGI: CGI bin does not exist - " + dir); - return; - } - - if (!dir.canRead()) - { - LOG.warn("CGI: CGI bin is not readable - " + dir); - return; - } - - if (!dir.isDirectory()) - { - LOG.warn("CGI: CGI bin is not a directory - " + dir); - return; - } - - try - { - _docRoot = dir.getCanonicalFile(); - } - catch (IOException e) - { - LOG.warn("CGI: CGI bin failed - " + dir, e); - return; - } - - _path = getInitParameter("Path"); - if (_path != null) - _env.set("PATH", _path); - - _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState")); - Enumeration e = getInitParameterNames(); - while (e.hasMoreElements()) - { - String n = e.nextElement(); - if (n != null && n.startsWith("ENV_")) - _env.set(n.substring(4), getInitParameter(n)); - } - if (!_env.envMap.containsKey("SystemRoot")) - { - String os = System.getProperty("os.name"); - if (os != null && os.toLowerCase(Locale.ENGLISH).contains("windows")) - { - _env.set("SystemRoot", "C:\\WINDOWS"); - } - } - - _ok = true; - } - - @Override - public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException - { - if (!_ok) - { - res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - return; - } - - if (LOG.isDebugEnabled()) - { - LOG.debug("CGI: ContextPath : " + req.getContextPath()); - LOG.debug("CGI: ServletPath : " + req.getServletPath()); - LOG.debug("CGI: PathInfo : " + req.getPathInfo()); - LOG.debug("CGI: _docRoot : " + _docRoot); - LOG.debug("CGI: _path : " + _path); - LOG.debug("CGI: _ignoreExitState: " + _ignoreExitState); - } - - // pathInContext may actually comprises scriptName/pathInfo...We will - // walk backwards up it until we find the script - the rest must - // be the pathInfo; - String pathInContext = (_relative ? "" : StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo()); - File execCmd = new File(_docRoot, pathInContext); - String pathInfo = pathInContext; - - if (!_useFullPath) - { - String path = pathInContext; - String info = ""; - - // Search docroot for a matching execCmd - while ((path.endsWith("/") || !execCmd.exists()) && path.length() >= 0) - { - int index = path.lastIndexOf('/'); - path = path.substring(0, index); - info = pathInContext.substring(index, pathInContext.length()); - execCmd = new File(_docRoot, path); - } - - if (path.length() == 0 || !execCmd.exists() || execCmd.isDirectory() || !execCmd.getCanonicalPath().equals(execCmd.getAbsolutePath())) - { - res.sendError(404); - } - - pathInfo = info; - } - exec(execCmd, pathInfo, req, res); - } - - /** - * executes the CGI process - * - * @param command the command to execute, this command is prefixed by - * the context parameter "commandPrefix". - * @param pathInfo The PATH_INFO to process, - * see http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getPathInfo%28%29. Cannot be null - * @param req the HTTP request - * @param res the HTTP response - * @throws IOException if the execution of the CGI process throws - */ - private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException - { - assert req != null; - assert res != null; - assert pathInfo != null; - assert command != null; - - if (LOG.isDebugEnabled()) - { - LOG.debug("CGI: script is " + command); - LOG.debug("CGI: pathInfo is " + pathInfo); - } - - String bodyFormEncoded = null; - if ((HttpMethod.POST.is(req.getMethod()) || HttpMethod.PUT.is(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType())) - { - MultiMap parameterMap = new MultiMap<>(); - Enumeration names = req.getParameterNames(); - while (names.hasMoreElements()) - { - String parameterName = names.nextElement(); - parameterMap.addValues(parameterName, req.getParameterValues(parameterName)); - } - - String characterEncoding = req.getCharacterEncoding(); - Charset charset = characterEncoding != null - ? Charset.forName(characterEncoding) : StandardCharsets.UTF_8; - bodyFormEncoded = UrlEncoded.encode(parameterMap, charset, true); - } - - EnvList env = new EnvList(_env); - // these ones are from "The WWW Common Gateway Interface Version 1.1" - // look at : - // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1 - env.set("AUTH_TYPE", req.getAuthType()); - - int contentLen = req.getContentLength(); - if (contentLen < 0) - contentLen = 0; - if (bodyFormEncoded != null) - { - env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length())); - } - else - { - env.set("CONTENT_LENGTH", Integer.toString(contentLen)); - } - env.set("CONTENT_TYPE", req.getContentType()); - env.set("GATEWAY_INTERFACE", "CGI/1.1"); - if (pathInfo.length() > 0) - { - env.set("PATH_INFO", pathInfo); - } - - String pathTranslated = req.getPathTranslated(); - if ((pathTranslated == null) || (pathTranslated.length() == 0)) - pathTranslated = pathInfo; - env.set("PATH_TRANSLATED", pathTranslated); - env.set("QUERY_STRING", req.getQueryString()); - env.set("REMOTE_ADDR", req.getRemoteAddr()); - env.set("REMOTE_HOST", req.getRemoteHost()); - - // The identity information reported about the connection by a - // RFC 1413 [11] request to the remote agent, if - // available. Servers MAY choose not to support this feature, or - // not to request the data for efficiency reasons. - // "REMOTE_IDENT" => "NYI" - env.set("REMOTE_USER", req.getRemoteUser()); - env.set("REQUEST_METHOD", req.getMethod()); - - String scriptPath; - String scriptName; - // use docRoot for scriptPath, too - if (_cgiBinProvided) - { - scriptPath = command.getAbsolutePath(); - scriptName = scriptPath.substring(_docRoot.getAbsolutePath().length()); - } - else - { - String requestURI = req.getRequestURI(); - scriptName = requestURI.substring(0, requestURI.length() - pathInfo.length()); - scriptPath = getServletContext().getRealPath(scriptName); - } - env.set("SCRIPT_FILENAME", scriptPath); - env.set("SCRIPT_NAME", scriptName); - - env.set("SERVER_NAME", req.getServerName()); - env.set("SERVER_PORT", Integer.toString(req.getServerPort())); - env.set("SERVER_PROTOCOL", req.getProtocol()); - env.set("SERVER_SOFTWARE", getServletContext().getServerInfo()); - - Enumeration enm = req.getHeaderNames(); - while (enm.hasMoreElements()) - { - String name = enm.nextElement(); - if (name.equalsIgnoreCase("Proxy")) - continue; - String value = req.getHeader(name); - env.set("HTTP_" + StringUtil.replace(name.toUpperCase(Locale.ENGLISH), '-', '_'), value); - } - - // these extra ones were from printenv on www.dev.nomura.co.uk - env.set("HTTPS", (req.isSecure() ? "ON" : "OFF")); - // "DOCUMENT_ROOT" => root + "/docs", - // "SERVER_URL" => "NYI - http://us0245", - // "TZ" => System.getProperty("user.timezone"), - - // are we meant to decode args here? or does the script get them - // via PATH_INFO? if we are, they should be decoded and passed - // into exec here... - String absolutePath = command.getAbsolutePath(); - String execCmd = absolutePath; - - // escape the execCommand - if (execCmd.length() > 0 && execCmd.charAt(0) != '"' && execCmd.contains(" ")) - execCmd = "\"" + execCmd + "\""; - - if (_cmdPrefix != null) - execCmd = _cmdPrefix + " " + execCmd; - - LOG.debug("Environment: " + env.getExportString()); - LOG.debug("Command: " + execCmd); - - final Process p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), _docRoot); - - // hook processes input to browser's output (async) - if (bodyFormEncoded != null) - writeProcessInput(p, bodyFormEncoded); - else if (contentLen > 0) - writeProcessInput(p, req.getInputStream(), contentLen); - - // hook processes output to browser's input (sync) - // if browser closes stream, we should detect it and kill process... - OutputStream os = null; - AsyncContext async = req.startAsync(); - try - { - async.start(new Runnable() - { - @Override - public void run() - { - try - { - IO.copy(p.getErrorStream(), System.err); - } - catch (IOException e) - { - LOG.warn("Unable to copy error stream", e); - } - } - }); - - // read any headers off the top of our input stream - // NOTE: Multiline header items not supported! - String line = null; - InputStream inFromCgi = p.getInputStream(); - - // br=new BufferedReader(new InputStreamReader(inFromCgi)); - // while ((line=br.readLine())!=null) - while ((line = getTextLineFromStream(inFromCgi)).length() > 0) - { - if (!line.startsWith("HTTP")) - { - int k = line.indexOf(':'); - if (k > 0) - { - String key = line.substring(0, k).trim(); - String value = line.substring(k + 1).trim(); - if ("Location".equals(key)) - { - res.sendRedirect(res.encodeRedirectURL(value)); - } - else if ("Status".equals(key)) - { - String[] token = value.split(" "); - int status = Integer.parseInt(token[0]); - res.setStatus(status); - } - else - { - // add remaining header items to our response header - res.addHeader(key, value); - } - } - } - } - // copy cgi content to response stream... - os = res.getOutputStream(); - IO.copy(inFromCgi, os); - p.waitFor(); - - if (!_ignoreExitState) - { - int exitValue = p.exitValue(); - if (0 != exitValue) - { - LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + absolutePath); - if (!res.isCommitted()) - res.sendError(500, "Failed to exec CGI"); - } - } - } - catch (IOException e) - { - // browser has probably closed its input stream - we - // terminate and clean up... - LOG.debug("CGI: Client closed connection!", e); - } - catch (InterruptedException ex) - { - LOG.debug("CGI: interrupted!"); - } - finally - { - IO.close(os); - p.destroy(); - // LOG.debug("CGI: terminated!"); - async.complete(); - } - } - - private static void writeProcessInput(final Process p, final String input) - { - new Thread(new Runnable() - { - @Override - public void run() - { - try - { - try (Writer outToCgi = new OutputStreamWriter(p.getOutputStream())) - { - outToCgi.write(input); - } - } - catch (IOException e) - { - LOG.debug("Unable to write out to CGI", e); - } - } - }).start(); - } - - private static void writeProcessInput(final Process p, final InputStream input, final int len) - { - if (len <= 0) - return; - - new Thread(new Runnable() - { - @Override - public void run() - { - try - { - try (OutputStream outToCgi = p.getOutputStream()) - { - IO.copy(input, outToCgi, len); - } - } - catch (IOException e) - { - LOG.debug("Unable to write out to CGI", e); - } - } - }).start(); - } - - /** - * Utility method to get a line of text from the input stream. - * - * @param is the input stream - * @return the line of text - * @throws IOException if reading from the input stream throws - */ - private static String getTextLineFromStream(InputStream is) throws IOException - { - StringBuilder buffer = new StringBuilder(); - int b; - - while ((b = is.read()) != -1 && b != '\n') - { - buffer.append((char)b); - } - return buffer.toString().trim(); - } - - /** - * private utility class that manages the Environment passed to exec. - */ - private static class EnvList - { - private Map envMap; - - EnvList() - { - envMap = new HashMap<>(); - } - - EnvList(EnvList l) - { - envMap = new HashMap<>(l.envMap); - } - - /** - * Set a name/value pair, null values will be treated as an empty String - * - * @param name the name - * @param value the value - */ - public void set(String name, String value) - { - envMap.put(name, name + "=" + StringUtil.nonNull(value)); - } - - /** - * Get representation suitable for passing to exec. - * - * @return the env map as an array - */ - public String[] getEnvArray() - { - return envMap.values().toArray(new String[envMap.size()]); - } - - public String getExportString() - { - StringBuilder sb = new StringBuilder(); - for (String variable : getEnvArray()) - { - sb.append("export \""); - sb.append(variable); - sb.append("\"; "); - } - return sb.toString(); - } - - @Override - public String toString() - { - return envMap.toString(); - } - } -} diff --git a/test-proxy/src/main/java/servlets/CloseableDoSFilter.java b/test-proxy/src/main/java/servlets/CloseableDoSFilter.java deleted file mode 100644 index dd4264b..0000000 --- a/test-proxy/src/main/java/servlets/CloseableDoSFilter.java +++ /dev/null @@ -1,39 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.server.Request; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * This is an extension to {@link DoSFilter} that uses Jetty APIs to - * abruptly close the connection when the request times out. - */ - -public class CloseableDoSFilter extends DoSFilter -{ - @Override - protected void onRequestTimeout(HttpServletRequest request, HttpServletResponse response, Thread handlingThread) - { - Request baseRequest = Request.getBaseRequest(request); - baseRequest.getHttpChannel().getEndPoint().close(); - } -} diff --git a/test-proxy/src/main/java/servlets/ConcatServlet.java b/test-proxy/src/main/java/servlets/ConcatServlet.java deleted file mode 100644 index 6af12af..0000000 --- a/test-proxy/src/main/java/servlets/ConcatServlet.java +++ /dev/null @@ -1,147 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.util.URIUtil; - -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - *

This servlet may be used to concatenate multiple resources into - * a single response.

- *

It is intended to be used to load multiple - * javascript or css files, but may be used for any content of the - * same mime type that can be meaningfully concatenated.

- *

The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} - * to combine the requested content, so dynamically generated content - * may be combined (Eg engine.js for DWR).

- *

The servlet uses parameter names of the query string as resource names - * relative to the context root. So these script tags:

- *
- * <script type="text/javascript" src="../js/behaviour.js"></script>
- * <script type="text/javascript" src="../js/ajax.js"></script>
- * <script type="text/javascript" src="../chat/chat.js"></script>
- * 
- *

can be replaced with the single tag (with the {@code ConcatServlet} - * mapped to {@code /concat}):

- *
- * <script type="text/javascript" src="../concat?/js/behaviour.js&/js/ajax.js&/chat/chat.js"></script>
- * 
- *

The {@link ServletContext#getMimeType(String)} method is used to determine the - * mime type of each resource. If the types of all resources do not match, then a 415 - * UNSUPPORTED_MEDIA_TYPE error is returned.

- *

If the init parameter {@code development} is set to {@code true} then the servlet - * will run in development mode and the content will be concatenated on every request.

- *

Otherwise the init time of the servlet is used as the lastModifiedTime of the combined content - * and If-Modified-Since requests are handled with 304 NOT Modified responses if - * appropriate. This means that when not in development mode, the servlet must be - * restarted before changed content will be served.

- */ -public class ConcatServlet extends HttpServlet -{ - private boolean _development; - private long _lastModified; - - @Override - public void init() throws ServletException - { - _lastModified = System.currentTimeMillis(); - _development = Boolean.parseBoolean(getInitParameter("development")); - } - - /* - * @return The start time of the servlet unless in development mode, in which case -1 is returned. - */ - @Override - protected long getLastModified(HttpServletRequest req) - { - return _development ? -1 : _lastModified; - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - String query = request.getQueryString(); - if (query == null) - { - response.sendError(HttpServletResponse.SC_NO_CONTENT); - return; - } - - List dispatchers = new ArrayList<>(); - String[] parts = query.split("\\&"); - String type = null; - for (String part : parts) - { - String path = URIUtil.canonicalPath(URIUtil.decodePath(part)); - if (path == null) - { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - - // Verify that the path is not protected. - if (startsWith(path, "/WEB-INF/") || startsWith(path, "/META-INF/")) - { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - - String t = getServletContext().getMimeType(path); - if (t != null) - { - if (type == null) - { - type = t; - } - else if (!type.equals(t)) - { - response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); - return; - } - } - - RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path); - if (dispatcher != null) - dispatchers.add(dispatcher); - } - - if (type != null) - response.setContentType(type); - - for (RequestDispatcher dispatcher : dispatchers) - { - dispatcher.include(request, response); - } - } - - private boolean startsWith(String path, String prefix) - { - // Case insensitive match. - return prefix.regionMatches(true, 0, path, 0, prefix.length()); - } -} diff --git a/test-proxy/src/main/java/servlets/CrossOriginFilter.java b/test-proxy/src/main/java/servlets/CrossOriginFilter.java deleted file mode 100644 index 80167cc..0000000 --- a/test-proxy/src/main/java/servlets/CrossOriginFilter.java +++ /dev/null @@ -1,505 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.util.StringUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.*; -import java.util.regex.Pattern; - -/** - * Implementation of the - * cross-origin resource sharing. - *

- * A typical example is to use this filter to allow cross-domain - * cometd communication using the standard - * long polling transport instead of the JSONP transport (that is less - * efficient and less reactive to failures). - *

- * This filter allows the following configuration parameters: - *

- *
allowedOrigins
- *
a comma separated list of origins that are - * allowed to access the resources. Default value is *, meaning all - * origins. Note that using wild cards can result in security problems - * for requests identifying hosts that do not exist. - *

- * If an allowed origin contains one or more * characters (for example - * http://*.domain.com), then "*" characters are converted to ".*", "." - * characters are escaped to "\." and the resulting allowed origin - * interpreted as a regular expression. - *

- * Allowed origins can therefore be more complex expressions such as - * https?://*.domain.[a-z]{3} that matches http or https, multiple subdomains - * and any 3 letter top-level domain (.com, .net, .org, etc.).

- * - *
allowedTimingOrigins
- *
a comma separated list of origins that are - * allowed to time the resource. Default value is the empty string, meaning - * no origins. - *

- * The check whether the timing header is set, will be performed only if - * the user gets general access to the resource using the allowedOrigins. - * - *

allowedMethods
- *
a comma separated list of HTTP methods that - * are allowed to be used when accessing the resources. Default value is - * GET,POST,HEAD
- * - * - *
allowedHeaders
- *
a comma separated list of HTTP headers that - * are allowed to be specified when accessing the resources. Default value - * is X-Requested-With,Content-Type,Accept,Origin. If the value is a single "*", - * this means that any headers will be accepted.
- * - *
preflightMaxAge
- *
the number of seconds that preflight requests - * can be cached by the client. Default value is 1800 seconds, or 30 - * minutes
- * - *
allowCredentials
- *
a boolean indicating if the resource allows - * requests with credentials. Default value is true
- * - *
exposedHeaders
- *
a comma separated list of HTTP headers that - * are allowed to be exposed on the client. Default value is the - * empty list
- * - *
chainPreflight
- *
if true preflight requests are chained to their - * target resource for normal handling (as an OPTION request). Otherwise the - * filter will response to the preflight. Default is true.
- * - *
- * A typical configuration could be: - *
- * <web-app ...>
- *     ...
- *     <filter>
- *         <filter-name>cross-origin</filter-name>
- *         <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
- *     </filter>
- *     <filter-mapping>
- *         <filter-name>cross-origin</filter-name>
- *         <url-pattern>/cometd/*</url-pattern>
- *     </filter-mapping>
- *     ...
- * </web-app>
- * 
- */ -public class CrossOriginFilter implements Filter -{ - private static final Logger LOG = LoggerFactory.getLogger(CrossOriginFilter.class); - - // Request headers - private static final String ORIGIN_HEADER = "Origin"; - public static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method"; - public static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers"; - // Response headers - public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin"; - public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods"; - public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers"; - public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age"; - public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials"; - public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers"; - public static final String TIMING_ALLOW_ORIGIN_HEADER = "Timing-Allow-Origin"; - // Implementation constants - public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins"; - public static final String ALLOWED_TIMING_ORIGINS_PARAM = "allowedTimingOrigins"; - public static final String ALLOWED_METHODS_PARAM = "allowedMethods"; - public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders"; - public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge"; - public static final String ALLOW_CREDENTIALS_PARAM = "allowCredentials"; - public static final String EXPOSED_HEADERS_PARAM = "exposedHeaders"; - public static final String OLD_CHAIN_PREFLIGHT_PARAM = "forwardPreflight"; - public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight"; - private static final String ANY_ORIGIN = "*"; - private static final String DEFAULT_ALLOWED_ORIGINS = "*"; - private static final String DEFAULT_ALLOWED_TIMING_ORIGINS = ""; - private static final List SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD"); - private static final List DEFAULT_ALLOWED_METHODS = Arrays.asList("GET", "POST", "HEAD"); - private static final List DEFAULT_ALLOWED_HEADERS = Arrays.asList("X-Requested-With", "Content-Type", "Accept", "Origin"); - - private boolean anyOriginAllowed; - private boolean anyTimingOriginAllowed; - private boolean anyHeadersAllowed; - private Set allowedOrigins = new HashSet(); - private List allowedOriginPatterns = new ArrayList(); - private Set allowedTimingOrigins = new HashSet(); - private List allowedTimingOriginPatterns = new ArrayList(); - private List allowedMethods = new ArrayList(); - private List allowedHeaders = new ArrayList(); - private List exposedHeaders = new ArrayList(); - private int preflightMaxAge; - private boolean allowCredentials; - private boolean chainPreflight; - - @Override - public void init(FilterConfig config) throws ServletException - { - String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM); - String allowedTimingOriginsConfig = config.getInitParameter(ALLOWED_TIMING_ORIGINS_PARAM); - - anyOriginAllowed = generateAllowedOrigins(allowedOrigins, allowedOriginPatterns, allowedOriginsConfig, DEFAULT_ALLOWED_ORIGINS); - anyTimingOriginAllowed = generateAllowedOrigins(allowedTimingOrigins, allowedTimingOriginPatterns, allowedTimingOriginsConfig, DEFAULT_ALLOWED_TIMING_ORIGINS); - - String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM); - if (allowedMethodsConfig == null) - allowedMethods.addAll(DEFAULT_ALLOWED_METHODS); - else - allowedMethods.addAll(Arrays.asList(StringUtil.csvSplit(allowedMethodsConfig))); - - String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM); - if (allowedHeadersConfig == null) - allowedHeaders.addAll(DEFAULT_ALLOWED_HEADERS); - else if ("*".equals(allowedHeadersConfig)) - anyHeadersAllowed = true; - else - allowedHeaders.addAll(Arrays.asList(StringUtil.csvSplit(allowedHeadersConfig))); - - String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM); - if (preflightMaxAgeConfig == null) - preflightMaxAgeConfig = "1800"; // Default is 30 minutes - try - { - preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig); - } - catch (NumberFormatException x) - { - LOG.info("Cross-origin filter, could not parse '{}' parameter as integer: {}", PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig); - } - - String allowedCredentialsConfig = config.getInitParameter(ALLOW_CREDENTIALS_PARAM); - if (allowedCredentialsConfig == null) - allowedCredentialsConfig = "true"; - allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig); - - String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM); - if (exposedHeadersConfig == null) - exposedHeadersConfig = ""; - exposedHeaders.addAll(Arrays.asList(StringUtil.csvSplit(exposedHeadersConfig))); - - String chainPreflightConfig = config.getInitParameter(OLD_CHAIN_PREFLIGHT_PARAM); - if (chainPreflightConfig != null) - LOG.warn("DEPRECATED CONFIGURATION: Use " + CHAIN_PREFLIGHT_PARAM + " instead of " + OLD_CHAIN_PREFLIGHT_PARAM); - else - chainPreflightConfig = config.getInitParameter(CHAIN_PREFLIGHT_PARAM); - if (chainPreflightConfig == null) - chainPreflightConfig = "true"; - chainPreflight = Boolean.parseBoolean(chainPreflightConfig); - - if (LOG.isDebugEnabled()) - { - LOG.debug("Cross-origin filter configuration: " + - ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " + - ALLOWED_TIMING_ORIGINS_PARAM + " = " + allowedTimingOriginsConfig + ", " + - ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " + - ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " + - PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " + - ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig + "," + - EXPOSED_HEADERS_PARAM + " = " + exposedHeadersConfig + "," + - CHAIN_PREFLIGHT_PARAM + " = " + chainPreflightConfig - ); - } - } - - private boolean generateAllowedOrigins(Set allowedOriginStore, List allowedOriginPatternStore, String allowedOriginsConfig, String defaultOrigin) - { - if (allowedOriginsConfig == null) - allowedOriginsConfig = defaultOrigin; - String[] allowedOrigins = StringUtil.csvSplit(allowedOriginsConfig); - for (String allowedOrigin : allowedOrigins) - { - if (allowedOrigin.length() > 0) - { - if (ANY_ORIGIN.equals(allowedOrigin)) - { - allowedOriginStore.clear(); - allowedOriginPatternStore.clear(); - return true; - } - else if (allowedOrigin.contains("*")) - { - allowedOriginPatternStore.add(Pattern.compile(parseAllowedWildcardOriginToRegex(allowedOrigin))); - } - else - { - allowedOriginStore.add(allowedOrigin); - } - } - } - return false; - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException - { - handle((HttpServletRequest)request, (HttpServletResponse)response, chain); - } - - private void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException - { - String origin = request.getHeader(ORIGIN_HEADER); - // Is it a cross origin request ? - if (origin != null && isEnabled(request)) - { - if (anyOriginAllowed || originMatches(allowedOrigins, allowedOriginPatterns, origin)) - { - if (isSimpleRequest(request)) - { - LOG.debug("Cross-origin request to {} is a simple cross-origin request", request.getRequestURI()); - handleSimpleResponse(request, response, origin); - } - else if (isPreflightRequest(request)) - { - LOG.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI()); - handlePreflightResponse(request, response, origin); - if (chainPreflight) - LOG.debug("Preflight cross-origin request to {} forwarded to application", request.getRequestURI()); - else - return; - } - else - { - LOG.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI()); - handleSimpleResponse(request, response, origin); - } - - if (anyTimingOriginAllowed || originMatches(allowedTimingOrigins, allowedTimingOriginPatterns, origin)) - { - response.setHeader(TIMING_ALLOW_ORIGIN_HEADER, origin); - } - else - { - LOG.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed timing origins " + allowedTimingOrigins); - } - } - else - { - LOG.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed origins " + allowedOrigins); - } - } - - chain.doFilter(request, response); - } - - protected boolean isEnabled(HttpServletRequest request) - { - // WebSocket clients such as Chrome 5 implement a version of the WebSocket - // protocol that does not accept extra response headers on the upgrade response - for (Enumeration connections = request.getHeaders("Connection"); connections.hasMoreElements(); ) - { - String connection = (String)connections.nextElement(); - if ("Upgrade".equalsIgnoreCase(connection)) - { - for (Enumeration upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements(); ) - { - String upgrade = (String)upgrades.nextElement(); - if ("WebSocket".equalsIgnoreCase(upgrade)) - return false; - } - } - } - return true; - } - - private boolean originMatches(Set allowedOrigins, List allowedOriginPatterns, String originList) - { - if (originList.trim().length() == 0) - return false; - - String[] origins = originList.split(" "); - for (String origin : origins) - { - if (origin.trim().length() == 0) - continue; - - if (allowedOrigins.contains(origin)) - return true; - - for (Pattern allowedOrigin : allowedOriginPatterns) - { - if (allowedOrigin.matcher(origin).matches()) - return true; - } - } - return false; - } - - private String parseAllowedWildcardOriginToRegex(String allowedOrigin) - { - String regex = StringUtil.replace(allowedOrigin, ".", "\\."); - return StringUtil.replace(regex, "*", ".*"); // we want to be greedy here to match multiple subdomains, thus we use .* - } - - private boolean isSimpleRequest(HttpServletRequest request) - { - String method = request.getMethod(); - if (SIMPLE_HTTP_METHODS.contains(method)) - { - // TODO: implement better detection of simple headers - // The specification says that for a request to be simple, custom request headers must be simple. - // Here for simplicity I just check if there is a Access-Control-Request-Method header, - // which is required for preflight requests - return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null; - } - return false; - } - - private boolean isPreflightRequest(HttpServletRequest request) - { - String method = request.getMethod(); - if (!"OPTIONS".equalsIgnoreCase(method)) - return false; - if (request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null) - return false; - return true; - } - - private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin) - { - response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); - //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation - response.addHeader("Vary", ORIGIN_HEADER); - if (allowCredentials) - response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); - if (!exposedHeaders.isEmpty()) - response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS_HEADER, commify(exposedHeaders)); - } - - private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin) - { - boolean methodAllowed = isMethodAllowed(request); - - if (!methodAllowed) - return; - List headersRequested = getAccessControlRequestHeaders(request); - boolean headersAllowed = areHeadersAllowed(headersRequested); - if (!headersAllowed) - return; - response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); - //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation - if (!anyOriginAllowed) - response.addHeader("Vary", ORIGIN_HEADER); - if (allowCredentials) - response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); - if (preflightMaxAge > 0) - response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge)); - response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods)); - if (anyHeadersAllowed) - response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(headersRequested)); - else - response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders)); - } - - private boolean isMethodAllowed(HttpServletRequest request) - { - String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER); - LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod); - boolean result = false; - if (accessControlRequestMethod != null) - result = allowedMethods.contains(accessControlRequestMethod); - LOG.debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}", accessControlRequestMethod, allowedMethods); - return result; - } - - private List getAccessControlRequestHeaders(HttpServletRequest request) - { - String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER); - LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders); - if (accessControlRequestHeaders == null) - return Collections.emptyList(); - - List requestedHeaders = new ArrayList(); - String[] headers = StringUtil.csvSplit(accessControlRequestHeaders); - for (String header : headers) - { - String h = header.trim(); - if (h.length() > 0) - requestedHeaders.add(h); - } - return requestedHeaders; - } - - private boolean areHeadersAllowed(List requestedHeaders) - { - if (anyHeadersAllowed) - { - LOG.debug("Any header is allowed"); - return true; - } - - boolean result = true; - for (String requestedHeader : requestedHeaders) - { - boolean headerAllowed = false; - for (String allowedHeader : allowedHeaders) - { - if (requestedHeader.equalsIgnoreCase(allowedHeader.trim())) - { - headerAllowed = true; - break; - } - } - if (!headerAllowed) - { - result = false; - break; - } - } - LOG.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", requestedHeaders, allowedHeaders); - return result; - } - - private String commify(List strings) - { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < strings.size(); ++i) - { - if (i > 0) - builder.append(","); - String string = strings.get(i); - builder.append(string); - } - return builder.toString(); - } - - @Override - public void destroy() - { - anyOriginAllowed = false; - anyTimingOriginAllowed = false; - allowedOrigins.clear(); - allowedOriginPatterns.clear(); - allowedTimingOrigins.clear(); - allowedTimingOriginPatterns.clear(); - allowedMethods.clear(); - allowedHeaders.clear(); - preflightMaxAge = 0; - allowCredentials = false; - } -} diff --git a/test-proxy/src/main/java/servlets/DataRateLimitedServlet.java b/test-proxy/src/main/java/servlets/DataRateLimitedServlet.java deleted file mode 100644 index dace47b..0000000 --- a/test-proxy/src/main/java/servlets/DataRateLimitedServlet.java +++ /dev/null @@ -1,313 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.server.HttpOutput; -import org.eclipse.jetty.util.ProcessorUtils; - -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel.MapMode; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * A servlet that uses the Servlet 3.1 asynchronous IO API to server - * static content at a limited data rate. - *

- * Two implementations are supported:

    - *
  • The StandardDataStream impl uses only standard - * APIs, but produces more garbage due to the byte[] nature of the API. - *
  • the JettyDataStream impl uses a Jetty API to write a ByteBuffer - * and thus allow the efficient use of file mapped buffers without any - * temporary buffer copies (I did tell the JSR that this was a good idea to - * have in the standard!). - *
- *

- * The data rate is controlled by setting init parameters: - *

- *
buffersize
The amount of data in bytes written per write
- *
pause
The period in ms to wait after a write before attempting another
- *
pool
The size of the thread pool used to service the writes (defaults to available processors)
- *
- * Thus if buffersize = 1024 and pause = 100, the data rate will be limited to 10KB per second. - */ -public class DataRateLimitedServlet extends HttpServlet -{ - private static final long serialVersionUID = -4771757707068097025L; - private int buffersize = 8192; - private long pauseNS = TimeUnit.MILLISECONDS.toNanos(100); - ScheduledThreadPoolExecutor scheduler; - private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); - - @Override - public void init() throws ServletException - { - // read the init params - String tmp = getInitParameter("buffersize"); - if (tmp != null) - buffersize = Integer.parseInt(tmp); - tmp = getInitParameter("pause"); - if (tmp != null) - pauseNS = TimeUnit.MILLISECONDS.toNanos(Integer.parseInt(tmp)); - tmp = getInitParameter("pool"); - int pool = tmp == null ? ProcessorUtils.availableProcessors() : Integer.parseInt(tmp); - - // Create and start a shared scheduler. - scheduler = new ScheduledThreadPoolExecutor(pool); - } - - @Override - public void destroy() - { - scheduler.shutdown(); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - // Get the path of the static resource to serve. - String info = request.getPathInfo(); - - // We don't handle directories - if (info.endsWith("/")) - { - response.sendError(503, "directories not supported"); - return; - } - - // Set the mime type of the response - String contentType = getServletContext().getMimeType(info); - response.setContentType(contentType == null ? "application/x-data" : contentType); - - // Look for a matching file path - String path = request.getPathTranslated(); - - // If we have a file path and this is a jetty response, we can use the JettyStream impl - ServletOutputStream out = response.getOutputStream(); - if (path != null && out instanceof HttpOutput) - { - // If the file exists - File file = new File(path); - if (file.exists() && file.canRead()) - { - // Set the content length - response.setContentLengthLong(file.length()); - - // Look for a file mapped buffer in the cache - ByteBuffer mapped = cache.get(path); - - // Handle cache miss - if (mapped == null) - { - // TODO implement LRU cache flush - try (RandomAccessFile raf = new RandomAccessFile(file, "r")) - { - ByteBuffer buf = raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()); - mapped = cache.putIfAbsent(path, buf); - if (mapped == null) - mapped = buf; - } - } - - // start async request handling - AsyncContext async = request.startAsync(); - - // Set a JettyStream as the write listener to write the content asynchronously. - out.setWriteListener(new JettyDataStream(mapped, async, out)); - return; - } - } - - // Jetty API was not used, so lets try the standards approach - - // Can we find the content as an input stream - InputStream content = getServletContext().getResourceAsStream(info); - if (content == null) - { - response.sendError(404); - return; - } - - // Set a StandardStream as he write listener to write the content asynchronously - out.setWriteListener(new StandardDataStream(content, request.startAsync(), out)); - } - - /** - * A standard API Stream writer - */ - private final class StandardDataStream implements WriteListener, Runnable - { - private final InputStream content; - private final AsyncContext async; - private final ServletOutputStream out; - - private StandardDataStream(InputStream content, AsyncContext async, ServletOutputStream out) - { - this.content = content; - this.async = async; - this.out = out; - } - - @Override - public void onWritePossible() throws IOException - { - // If we are able to write - if (out.isReady()) - { - // Allocated a copy buffer for each write, so as to not hold while paused - // TODO put these buffers into a pool - byte[] buffer = new byte[buffersize]; - - // read some content into the copy buffer - int len = content.read(buffer); - - // If we are at EOF - if (len < 0) - { - // complete the async lifecycle - async.complete(); - return; - } - - // write out the copy buffer. This will be an asynchronous write - // and will always return immediately without blocking. If a subsequent - // call to out.isReady() returns false, then this onWritePossible method - // will be called back when a write is possible. - out.write(buffer, 0, len); - - // Schedule a timer callback to pause writing. Because isReady() is not called, - // a onWritePossible callback is no scheduled. - scheduler.schedule(this, pauseNS, TimeUnit.NANOSECONDS); - } - } - - @Override - public void run() - { - try - { - // When the pause timer wakes up, call onWritePossible. Either isReady() will return - // true and another chunk of content will be written, or it will return false and the - // onWritePossible() callback will be scheduled when a write is next possible. - onWritePossible(); - } - catch (Exception e) - { - onError(e); - } - } - - @Override - public void onError(Throwable t) - { - getServletContext().log("Async Error", t); - async.complete(); - } - } - - /** - * A Jetty API DataStream - */ - private final class JettyDataStream implements WriteListener, Runnable - { - private final ByteBuffer content; - private final int limit; - private final AsyncContext async; - private final HttpOutput out; - - private JettyDataStream(ByteBuffer content, AsyncContext async, ServletOutputStream out) - { - // Make a readonly copy of the passed buffer. This uses the same underlying content - // without a copy, but gives this instance its own position and limit. - this.content = content.asReadOnlyBuffer(); - // remember the ultimate limit. - this.limit = this.content.limit(); - this.async = async; - this.out = (HttpOutput)out; - } - - @Override - public void onWritePossible() throws IOException - { - // If we are able to write - if (out.isReady()) - { - // Position our buffers limit to allow only buffersize bytes to be written - int l = content.position() + buffersize; - // respect the ultimate limit - if (l > limit) - l = limit; - content.limit(l); - - // if all content has been written - if (!content.hasRemaining()) - { - // complete the async lifecycle - async.complete(); - return; - } - - // write our limited buffer. This will be an asynchronous write - // and will always return immediately without blocking. If a subsequent - // call to out.isReady() returns false, then this onWritePossible method - // will be called back when a write is possible. - out.write(content); - - // Schedule a timer callback to pause writing. Because isReady() is not called, - // a onWritePossible callback is not scheduled. - scheduler.schedule(this, pauseNS, TimeUnit.NANOSECONDS); - } - } - - @Override - public void run() - { - try - { - // When the pause timer wakes up, call onWritePossible. Either isReady() will return - // true and another chunk of content will be written, or it will return false and the - // onWritePossible() callback will be scheduled when a write is next possible. - onWritePossible(); - } - catch (Exception e) - { - onError(e); - } - } - - @Override - public void onError(Throwable t) - { - getServletContext().log("Async Error", t); - async.complete(); - } - } -} diff --git a/test-proxy/src/main/java/servlets/DoSFilter.java b/test-proxy/src/main/java/servlets/DoSFilter.java deleted file mode 100644 index bad473f..0000000 --- a/test-proxy/src/main/java/servlets/DoSFilter.java +++ /dev/null @@ -1,1329 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.annotation.ManagedAttribute; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.annotation.ManagedOperation; -import org.eclipse.jetty.util.annotation.Name; -import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; -import org.eclipse.jetty.util.thread.Scheduler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.*; -import javax.servlet.http.*; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Denial of Service filter - *

- * This filter is useful for limiting - * exposure to abuse from request flooding, whether malicious, or as a result of - * a misconfigured client. - *

- * The filter keeps track of the number of requests from a connection per - * second. If a limit is exceeded, the request is either rejected, delayed, or - * throttled. - *

- * When a request is throttled, it is placed in a priority queue. Priority is - * given first to authenticated users and users with an HttpSession, then - * connections which can be identified by their IP addresses. Connections with - * no way to identify them are given lowest priority. - *

- * The {@link #extractUserId(ServletRequest request)} function should be - * implemented, in order to uniquely identify authenticated users. - *

- * The following init parameters control the behavior of the filter: - *

- *
maxRequestsPerSec
- *
the maximum number of requests from a connection per - * second. Requests in excess of this are first delayed, - * then throttled.
- *
delayMs
- *
is the delay given to all requests over the rate limit, - * before they are considered at all. -1 means just reject request, - * 0 means no delay, otherwise it is the delay.
- *
maxWaitMs
- *
how long to blocking wait for the throttle semaphore.
- *
throttledRequests
- *
is the number of requests over the rate limit able to be - * considered at once.
- *
throttleMs
- *
how long to async wait for semaphore.
- *
maxRequestMs
- *
how long to allow this request to run.
- *
maxIdleTrackerMs
- *
how long to keep track of request rates for a connection, - * before deciding that the user has gone away, and discarding it
- *
insertHeaders
- *
if true , insert the DoSFilter headers into the response. Defaults to true.
- *
trackSessions
- *
if true, usage rate is tracked by session if a session exists. Defaults to true.
- *
remotePort
- *
if true and session tracking is not used, then rate is tracked by IP+port (effectively connection). Defaults to false.
- *
ipWhitelist
- *
a comma-separated list of IP addresses that will not be rate limited
- *
managedAttr
- *
if set to true, then this servlet is set as a {@link ServletContext} attribute with the - * filter name as the attribute name. This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to - * manage the configuration of the filter.
- *
tooManyCode
- *
The status code to send if there are too many requests. By default is 429 (too many requests), but 503 (Unavailable) is - * another option
- *
- *

- * This filter should be configured for {@link DispatcherType#REQUEST} and {@link DispatcherType#ASYNC} and with - * {@code true}. - *

- */ -@ManagedObject("limits exposure to abuse from request flooding, whether malicious, or as a result of a misconfigured client") -public class DoSFilter implements Filter -{ - private static final Logger LOG = LoggerFactory.getLogger(DoSFilter.class); - - private static final String IPv4_GROUP = "(\\d{1,3})"; - private static final Pattern IPv4_PATTERN = Pattern.compile(IPv4_GROUP + "\\." + IPv4_GROUP + "\\." + IPv4_GROUP + "\\." + IPv4_GROUP); - private static final String IPv6_GROUP = "(\\p{XDigit}{1,4})"; - private static final Pattern IPv6_PATTERN = Pattern.compile(IPv6_GROUP + ":" + IPv6_GROUP + ":" + IPv6_GROUP + ":" + IPv6_GROUP + ":" + IPv6_GROUP + ":" + IPv6_GROUP + ":" + IPv6_GROUP + ":" + IPv6_GROUP); - private static final Pattern CIDR_PATTERN = Pattern.compile("([^/]+)/(\\d+)"); - - private static final String __TRACKER = "DoSFilter.Tracker"; - private static final String __THROTTLED = "DoSFilter.Throttled"; - - private static final int __DEFAULT_MAX_REQUESTS_PER_SEC = 25; - private static final int __DEFAULT_DELAY_MS = 100; - private static final int __DEFAULT_THROTTLE = 5; - private static final int __DEFAULT_MAX_WAIT_MS = 50; - private static final long __DEFAULT_THROTTLE_MS = 30000L; - private static final long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM = 30000L; - private static final long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM = 30000L; - - static final String MANAGED_ATTR_INIT_PARAM = "managedAttr"; - static final String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec"; - static final String DELAY_MS_INIT_PARAM = "delayMs"; - static final String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests"; - static final String MAX_WAIT_INIT_PARAM = "maxWaitMs"; - static final String THROTTLE_MS_INIT_PARAM = "throttleMs"; - static final String MAX_REQUEST_MS_INIT_PARAM = "maxRequestMs"; - static final String MAX_IDLE_TRACKER_MS_INIT_PARAM = "maxIdleTrackerMs"; - static final String INSERT_HEADERS_INIT_PARAM = "insertHeaders"; - static final String TRACK_SESSIONS_INIT_PARAM = "trackSessions"; - static final String REMOTE_PORT_INIT_PARAM = "remotePort"; - static final String IP_WHITELIST_INIT_PARAM = "ipWhitelist"; - static final String ENABLED_INIT_PARAM = "enabled"; - static final String TOO_MANY_CODE = "tooManyCode"; - - private static final int USER_AUTH = 2; - private static final int USER_SESSION = 2; - private static final int USER_IP = 1; - private static final int USER_UNKNOWN = 0; - - private final String _suspended = "DoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED"; - private final String _resumed = "DoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED"; - private final ConcurrentHashMap _rateTrackers = new ConcurrentHashMap<>(); - private final List _whitelist = new CopyOnWriteArrayList<>(); - private int _tooManyCode; - private volatile long _delayMs; - private volatile long _throttleMs; - private volatile long _maxWaitMs; - private volatile long _maxRequestMs; - private volatile long _maxIdleTrackerMs; - private volatile boolean _insertHeaders; - private volatile boolean _trackSessions; - private volatile boolean _remotePort; - private volatile boolean _enabled; - private volatile String _name; - private Semaphore _passes; - private volatile int _throttledRequests; - private volatile int _maxRequestsPerSec; - private Queue[] _queues; - private AsyncListener[] _listeners; - private Scheduler _scheduler; - private ServletContext _context; - - @Override - public void init(FilterConfig filterConfig) throws ServletException - { - _queues = new Queue[getMaxPriority() + 1]; - _listeners = new AsyncListener[_queues.length]; - for (int p = 0; p < _queues.length; p++) - { - _queues[p] = new ConcurrentLinkedQueue<>(); - _listeners[p] = new DoSAsyncListener(p); - } - - _rateTrackers.clear(); - - int maxRequests = __DEFAULT_MAX_REQUESTS_PER_SEC; - String parameter = filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM); - if (parameter != null) - maxRequests = Integer.parseInt(parameter); - setMaxRequestsPerSec(maxRequests); - - long delay = __DEFAULT_DELAY_MS; - parameter = filterConfig.getInitParameter(DELAY_MS_INIT_PARAM); - if (parameter != null) - delay = Long.parseLong(parameter); - setDelayMs(delay); - - int throttledRequests = __DEFAULT_THROTTLE; - parameter = filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM); - if (parameter != null) - throttledRequests = Integer.parseInt(parameter); - setThrottledRequests(throttledRequests); - - long maxWait = __DEFAULT_MAX_WAIT_MS; - parameter = filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM); - if (parameter != null) - maxWait = Long.parseLong(parameter); - setMaxWaitMs(maxWait); - - long throttle = __DEFAULT_THROTTLE_MS; - parameter = filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM); - if (parameter != null) - throttle = Long.parseLong(parameter); - setThrottleMs(throttle); - - long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM; - parameter = filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM); - if (parameter != null) - maxRequestMs = Long.parseLong(parameter); - setMaxRequestMs(maxRequestMs); - - long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM; - parameter = filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM); - if (parameter != null) - maxIdleTrackerMs = Long.parseLong(parameter); - setMaxIdleTrackerMs(maxIdleTrackerMs); - - String whiteList = ""; - parameter = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM); - if (parameter != null) - whiteList = parameter; - setWhitelist(whiteList); - - parameter = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM); - setInsertHeaders(parameter == null || Boolean.parseBoolean(parameter)); - - parameter = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM); - setTrackSessions(parameter == null || Boolean.parseBoolean(parameter)); - - parameter = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM); - setRemotePort(parameter != null && Boolean.parseBoolean(parameter)); - - parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM); - setEnabled(parameter == null || Boolean.parseBoolean(parameter)); - - parameter = filterConfig.getInitParameter(TOO_MANY_CODE); - setTooManyCode(parameter == null ? 429 : Integer.parseInt(parameter)); - - setName(filterConfig.getFilterName()); - _context = filterConfig.getServletContext(); - if (_context != null) - { - _context.setAttribute(filterConfig.getFilterName(), this); - } - - _scheduler = startScheduler(); - } - - protected Scheduler startScheduler() throws ServletException - { - try - { - Scheduler result = new ScheduledExecutorScheduler(String.format("DoS-Scheduler-%x", hashCode()), false); - result.start(); - return result; - } - catch (Exception x) - { - throw new ServletException(x); - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException - { - doFilter((HttpServletRequest)request, (HttpServletResponse)response, filterChain); - } - - protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException - { - if (!isEnabled()) - { - filterChain.doFilter(request, response); - return; - } - - // Look for the rate tracker for this request. - RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER); - if (tracker == null) - { - // This is the first time we have seen this request. - if (LOG.isDebugEnabled()) - LOG.debug("Filtering {}", request); - - // Get a rate tracker associated with this request, and record one hit. - tracker = getRateTracker(request); - - // Calculate the rate and check if it is over the allowed limit - final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis()); - - // Pass it through if we are not currently over the rate limit. - if (!overRateLimit) - { - if (LOG.isDebugEnabled()) - LOG.debug("Allowing {}", request); - doFilterChain(filterChain, request, response); - return; - } - - // We are over the limit. - - // So either reject it, delay it or throttle it. - long delayMs = getDelayMs(); - boolean insertHeaders = isInsertHeaders(); - switch ((int)delayMs) - { - case -1: - { - // Reject this request. - LOG.warn("DOS ALERT: Request rejected ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal()); - if (insertHeaders) - response.addHeader("DoSFilter", "unavailable"); - response.sendError(getTooManyCode()); - return; - } - case 0: - { - // Fall through to throttle the request. - LOG.warn("DOS ALERT: Request throttled ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal()); - request.setAttribute(__TRACKER, tracker); - break; - } - default: - { - // Insert a delay before throttling the request, - // using the suspend+timeout mechanism of AsyncContext. - LOG.warn("DOS ALERT: Request delayed={}ms, ip={}, session={}, user={}", delayMs, request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal()); - if (insertHeaders) - response.addHeader("DoSFilter", "delayed"); - request.setAttribute(__TRACKER, tracker); - AsyncContext asyncContext = request.startAsync(); - if (delayMs > 0) - asyncContext.setTimeout(delayMs); - asyncContext.addListener(new DoSTimeoutAsyncListener()); - return; - } - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("Throttling {}", request); - - // Throttle the request. - boolean accepted = false; - try - { - // Check if we can afford to accept another request at this time. - accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS); - if (!accepted) - { - // We were not accepted, so either we suspend to wait, - // or if we were woken up we insist or we fail. - Boolean throttled = (Boolean)request.getAttribute(__THROTTLED); - long throttleMs = getThrottleMs(); - if (!Boolean.TRUE.equals(throttled) && throttleMs > 0) - { - int priority = getPriority(request, tracker); - request.setAttribute(__THROTTLED, Boolean.TRUE); - if (isInsertHeaders()) - response.addHeader("DoSFilter", "throttled"); - AsyncContext asyncContext = request.startAsync(); - request.setAttribute(_suspended, Boolean.TRUE); - asyncContext.setTimeout(throttleMs); - asyncContext.addListener(_listeners[priority]); - _queues[priority].add(asyncContext); - if (LOG.isDebugEnabled()) - LOG.debug("Throttled {}, {}ms", request, throttleMs); - return; - } - - Boolean resumed = (Boolean)request.getAttribute(_resumed); - if (Boolean.TRUE.equals(resumed)) - { - // We were resumed, we wait for the next pass. - _passes.acquire(); - accepted = true; - } - } - - // If we were accepted (either immediately or after throttle)... - if (accepted) - { - // ...call the chain. - if (LOG.isDebugEnabled()) - LOG.debug("Allowing {}", request); - doFilterChain(filterChain, request, response); - } - else - { - // ...otherwise fail the request. - if (LOG.isDebugEnabled()) - LOG.debug("Rejecting {}", request); - if (isInsertHeaders()) - response.addHeader("DoSFilter", "unavailable"); - response.sendError(getTooManyCode()); - } - } - catch (InterruptedException e) - { - LOG.trace("IGNORED", e); - response.sendError(getTooManyCode()); - } - finally - { - if (accepted) - { - try - { - // Wake up the next highest priority request. - for (int p = _queues.length - 1; p >= 0; --p) - { - AsyncContext asyncContext = _queues[p].poll(); - if (asyncContext != null) - { - ServletRequest candidate = asyncContext.getRequest(); - Boolean suspended = (Boolean)candidate.getAttribute(_suspended); - if (Boolean.TRUE.equals(suspended)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Resuming {}", request); - candidate.setAttribute(_resumed, Boolean.TRUE); - asyncContext.dispatch(); - break; - } - } - } - } - finally - { - _passes.release(); - } - } - } - } - - protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException - { - final Thread thread = Thread.currentThread(); - Runnable requestTimeout = () -> onRequestTimeout(request, response, thread); - Scheduler.Task task = _scheduler.schedule(requestTimeout, getMaxRequestMs(), TimeUnit.MILLISECONDS); - try - { - chain.doFilter(request, response); - } - finally - { - task.cancel(); - } - } - - /** - * Invoked when the request handling exceeds {@link #getMaxRequestMs()}. - *

- * By default, an HTTP 503 response is returned and the handling thread is interrupted. - * - * @param request the current request - * @param response the current response - * @param handlingThread the handling thread - */ - protected void onRequestTimeout(HttpServletRequest request, HttpServletResponse response, Thread handlingThread) - { - try - { - if (LOG.isDebugEnabled()) - LOG.debug("Timing out {}", request); - try - { - response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503); - } - catch (IllegalStateException ise) - { - LOG.trace("IGNORED", ise); - // abort instead - response.sendError(-1); - } - } - catch (Throwable x) - { - LOG.info("Failed to sendError", x); - } - - handlingThread.interrupt(); - } - - /** - * Get priority for this request, based on user type - * - * @param request the current request - * @param tracker the rate tracker for this request - * @return the priority for this request - */ - private int getPriority(HttpServletRequest request, RateTracker tracker) - { - if (extractUserId(request) != null) - return USER_AUTH; - if (tracker != null) - return tracker.getType(); - return USER_UNKNOWN; - } - - /** - * @return the maximum priority that we can assign to a request - */ - protected int getMaxPriority() - { - return USER_AUTH; - } - - private void schedule(RateTracker tracker) - { - _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS); - } - - /** - * Return a request rate tracker associated with this connection; keeps - * track of this connection's request rate. If this is not the first request - * from this connection, return the existing object with the stored stats. - * If it is the first request, then create a new request tracker. - *

- * Assumes that each connection has an identifying characteristic, and goes - * through them in order, taking the first that matches: user id (logged - * in), session id, client IP address. Unidentifiable connections are lumped - * into one. - *

- * When a session expires, its rate tracker is automatically deleted. - * - * @param request the current request - * @return the request rate tracker for the current connection - */ - RateTracker getRateTracker(ServletRequest request) - { - HttpSession session = ((HttpServletRequest)request).getSession(false); - - String loadId = extractUserId(request); - final int type; - if (loadId != null) - { - type = USER_AUTH; - } - else - { - if (isTrackSessions() && session != null && !session.isNew()) - { - loadId = session.getId(); - type = USER_SESSION; - } - else - { - loadId = isRemotePort() ? createRemotePortId(request) : request.getRemoteAddr(); - type = USER_IP; - } - } - - RateTracker tracker = _rateTrackers.get(loadId); - - if (tracker == null) - { - boolean allowed = checkWhitelist(request.getRemoteAddr()); - int maxRequestsPerSec = getMaxRequestsPerSec(); - tracker = allowed ? new FixedRateTracker(_context, _name, loadId, type, maxRequestsPerSec) - : new RateTracker(_context, _name, loadId, type, maxRequestsPerSec); - tracker.setContext(_context); - RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker); - if (existing != null) - tracker = existing; - - if (type == USER_IP) - { - // USER_IP expiration from _rateTrackers is handled by the _scheduler - _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS); - } - else if (session != null) - { - // USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener - session.setAttribute(__TRACKER, tracker); - } - } - - return tracker; - } - - private void addToRateTracker(RateTracker tracker) - { - _rateTrackers.put(tracker.getId(), tracker); - } - - public void removeFromRateTracker(String id) - { - _rateTrackers.remove(id); - } - - protected boolean checkWhitelist(String candidate) - { - for (String address : _whitelist) - { - if (address.contains("/")) - { - if (subnetMatch(address, candidate)) - return true; - } - else - { - if (address.equals(candidate)) - return true; - } - } - return false; - } - - protected boolean subnetMatch(String subnetAddress, String address) - { - Matcher cidrMatcher = CIDR_PATTERN.matcher(subnetAddress); - if (!cidrMatcher.matches()) - return false; - - String subnet = cidrMatcher.group(1); - int prefix; - try - { - prefix = Integer.parseInt(cidrMatcher.group(2)); - } - catch (NumberFormatException x) - { - LOG.info("Ignoring malformed CIDR address {}", subnetAddress); - return false; - } - - byte[] subnetBytes = addressToBytes(subnet); - if (subnetBytes == null) - { - LOG.info("Ignoring malformed CIDR address {}", subnetAddress); - return false; - } - byte[] addressBytes = addressToBytes(address); - if (addressBytes == null) - { - LOG.info("Ignoring malformed remote address {}", address); - return false; - } - - // Comparing IPv4 with IPv6 ? - int length = subnetBytes.length; - if (length != addressBytes.length) - return false; - - byte[] mask = prefixToBytes(prefix, length); - - for (int i = 0; i < length; ++i) - { - if ((subnetBytes[i] & mask[i]) != (addressBytes[i] & mask[i])) - return false; - } - - return true; - } - - private byte[] addressToBytes(String address) - { - Matcher ipv4Matcher = IPv4_PATTERN.matcher(address); - if (ipv4Matcher.matches()) - { - byte[] result = new byte[4]; - for (int i = 0; i < result.length; ++i) - { - result[i] = Integer.valueOf(ipv4Matcher.group(i + 1)).byteValue(); - } - return result; - } - else - { - Matcher ipv6Matcher = IPv6_PATTERN.matcher(address); - if (ipv6Matcher.matches()) - { - byte[] result = new byte[16]; - for (int i = 0; i < result.length; i += 2) - { - int word = Integer.parseInt(ipv6Matcher.group(i / 2 + 1), 16); - result[i] = (byte)((word & 0xFF00) >>> 8); - result[i + 1] = (byte)(word & 0xFF); - } - return result; - } - } - return null; - } - - private byte[] prefixToBytes(int prefix, int length) - { - byte[] result = new byte[length]; - int index = 0; - while (prefix / 8 > 0) - { - result[index] = -1; - prefix -= 8; - ++index; - } - - if (index == result.length) - return result; - - // Sets the _prefix_ most significant bits to 1 - result[index] = (byte)~((1 << (8 - prefix)) - 1); - return result; - } - - @Override - public void destroy() - { - LOG.debug("Destroy {}", this); - stopScheduler(); - _rateTrackers.clear(); - _whitelist.clear(); - } - - protected void stopScheduler() - { - try - { - _scheduler.stop(); - } - catch (Exception x) - { - LOG.trace("IGNORED", x); - } - } - - /** - * Returns the user id, used to track this connection. - * This SHOULD be overridden by subclasses. - * - * @param request the current request - * @return a unique user id, if logged in; otherwise null. - */ - protected String extractUserId(ServletRequest request) - { - return null; - } - - /** - * Get maximum number of requests from a connection per - * second. Requests in excess of this are first delayed, - * then throttled. - * - * @return maximum number of requests - */ - @ManagedAttribute("maximum number of requests allowed from a connection per second") - public int getMaxRequestsPerSec() - { - return _maxRequestsPerSec; - } - - /** - * Get maximum number of requests from a connection per - * second. Requests in excess of this are first delayed, - * then throttled. - * - * @param value maximum number of requests - */ - public void setMaxRequestsPerSec(int value) - { - _maxRequestsPerSec = value; - } - - /** - * Get delay (in milliseconds) that is applied to all requests - * over the rate limit, before they are considered at all. - * - * @return the delay in milliseconds - */ - @ManagedAttribute("delay applied to all requests over the rate limit (in ms)") - public long getDelayMs() - { - return _delayMs; - } - - /** - * Set delay (in milliseconds) that is applied to all requests - * over the rate limit, before they are considered at all. - * - * @param value delay (in milliseconds), 0 - no delay, -1 - reject request - */ - public void setDelayMs(long value) - { - _delayMs = value; - } - - /** - * Get maximum amount of time (in milliseconds) the filter will - * blocking wait for the throttle semaphore. - * - * @return maximum wait time - */ - @ManagedAttribute("maximum time the filter will block waiting throttled connections, (0 for no delay, -1 to reject requests)") - public long getMaxWaitMs() - { - return _maxWaitMs; - } - - /** - * Set maximum amount of time (in milliseconds) the filter will - * blocking wait for the throttle semaphore. - * - * @param value maximum wait time - */ - public void setMaxWaitMs(long value) - { - _maxWaitMs = value; - } - - /** - * Get number of requests over the rate limit able to be - * considered at once. - * - * @return number of requests - */ - @ManagedAttribute("number of requests over rate limit") - public int getThrottledRequests() - { - return _throttledRequests; - } - - /** - * Set number of requests over the rate limit able to be - * considered at once. - * - * @param value number of requests - */ - public void setThrottledRequests(int value) - { - int permits = _passes == null ? 0 : _passes.availablePermits(); - _passes = new Semaphore((value - _throttledRequests + permits), true); - _throttledRequests = value; - } - - /** - * Get amount of time (in milliseconds) to async wait for semaphore. - * - * @return wait time - */ - @ManagedAttribute("amount of time to async wait for semaphore") - public long getThrottleMs() - { - return _throttleMs; - } - - /** - * Set amount of time (in milliseconds) to async wait for semaphore. - * - * @param value wait time - */ - public void setThrottleMs(long value) - { - _throttleMs = value; - } - - /** - * Get maximum amount of time (in milliseconds) to allow - * the request to process. - * - * @return maximum processing time - */ - @ManagedAttribute("maximum time to allow requests to process (in ms)") - public long getMaxRequestMs() - { - return _maxRequestMs; - } - - /** - * Set maximum amount of time (in milliseconds) to allow - * the request to process. - * - * @param value maximum processing time - */ - public void setMaxRequestMs(long value) - { - _maxRequestMs = value; - } - - /** - * Get maximum amount of time (in milliseconds) to keep track - * of request rates for a connection, before deciding that - * the user has gone away, and discarding it. - * - * @return maximum tracking time - */ - @ManagedAttribute("maximum time to track of request rates for connection before discarding") - public long getMaxIdleTrackerMs() - { - return _maxIdleTrackerMs; - } - - /** - * Set maximum amount of time (in milliseconds) to keep track - * of request rates for a connection, before deciding that - * the user has gone away, and discarding it. - * - * @param value maximum tracking time - */ - public void setMaxIdleTrackerMs(long value) - { - _maxIdleTrackerMs = value; - } - - /** - * The unique name of the filter when there is more than - * one DosFilter instance. - * - * @return the name - */ - public String getName() - { - return _name; - } - - /** - * @param name the name to set - */ - public void setName(String name) - { - _name = name; - } - - /** - * Check flag to insert the DoSFilter headers into the response. - * - * @return value of the flag - */ - @ManagedAttribute("inser DoSFilter headers in response") - public boolean isInsertHeaders() - { - return _insertHeaders; - } - - /** - * Set flag to insert the DoSFilter headers into the response. - * - * @param value value of the flag - */ - public void setInsertHeaders(boolean value) - { - _insertHeaders = value; - } - - /** - * Get flag to have usage rate tracked by session if a session exists. - * - * @return value of the flag - */ - @ManagedAttribute("usage rate is tracked by session if one exists") - public boolean isTrackSessions() - { - return _trackSessions; - } - - /** - * Set flag to have usage rate tracked by session if a session exists. - * - * @param value value of the flag - */ - public void setTrackSessions(boolean value) - { - _trackSessions = value; - } - - /** - * Get flag to have usage rate tracked by IP+port (effectively connection) - * if session tracking is not used. - * - * @return value of the flag - */ - @ManagedAttribute("usage rate is tracked by IP+port is session tracking not used") - public boolean isRemotePort() - { - return _remotePort; - } - - /** - * Set flag to have usage rate tracked by IP+port (effectively connection) - * if session tracking is not used. - * - * @param value value of the flag - */ - public void setRemotePort(boolean value) - { - _remotePort = value; - } - - /** - * @return whether this filter is enabled - */ - @ManagedAttribute("whether this filter is enabled") - public boolean isEnabled() - { - return _enabled; - } - - /** - * @param enabled whether this filter is enabled - */ - public void setEnabled(boolean enabled) - { - _enabled = enabled; - } - - public int getTooManyCode() - { - return _tooManyCode; - } - - public void setTooManyCode(int tooManyCode) - { - _tooManyCode = tooManyCode; - } - - /** - * Get a list of IP addresses that will not be rate limited. - * - * @return comma-separated whitelist - */ - @ManagedAttribute("list of IPs that will not be rate limited") - public String getWhitelist() - { - StringBuilder result = new StringBuilder(); - for (Iterator iterator = _whitelist.iterator(); iterator.hasNext(); ) - { - String address = iterator.next(); - result.append(address); - if (iterator.hasNext()) - result.append(","); - } - return result.toString(); - } - - /** - * Set a list of IP addresses that will not be rate limited. - * - * @param commaSeparatedList comma-separated whitelist - */ - public void setWhitelist(String commaSeparatedList) - { - List result = new ArrayList<>(); - for (String address : StringUtil.csvSplit(commaSeparatedList)) - { - addWhitelistAddress(result, address); - } - clearWhitelist(); - _whitelist.addAll(result); - LOG.debug("Whitelisted IP addresses: {}", result); - } - - /** - * Clears the list of whitelisted IP addresses - */ - @ManagedOperation("clears the list of IP addresses that will not be rate limited") - public void clearWhitelist() - { - _whitelist.clear(); - } - - /** - * Adds the given IP address, either in the form of a dotted decimal notation A.B.C.D - * or in the CIDR notation A.B.C.D/M, to the list of whitelisted IP addresses. - * - * @param address the address to add - * @return whether the address was added to the list - * @see #removeWhitelistAddress(String) - */ - @ManagedOperation("adds an IP address that will not be rate limited") - public boolean addWhitelistAddress(@Name("address") String address) - { - return addWhitelistAddress(_whitelist, address); - } - - private boolean addWhitelistAddress(List list, String address) - { - address = address.trim(); - return address.length() > 0 && list.add(address); - } - - /** - * Removes the given address from the list of whitelisted IP addresses. - * - * @param address the address to remove - * @return whether the address was removed from the list - * @see #addWhitelistAddress(String) - */ - @ManagedOperation("removes an IP address that will not be rate limited") - public boolean removeWhitelistAddress(@Name("address") String address) - { - return _whitelist.remove(address); - } - - /** - * A RateTracker is associated with a connection, and stores request rate - * data. - */ - static class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable - { - private static final long serialVersionUID = 3534663738034577872L; - - protected final String _filterName; - protected transient ServletContext _context; - protected final String _id; - protected final int _type; - protected final long[] _timestamps; - - protected int _next; - - public RateTracker(ServletContext context, String filterName, String id, int type, int maxRequestsPerSecond) - { - _context = context; - _filterName = filterName; - _id = id; - _type = type; - _timestamps = new long[maxRequestsPerSecond]; - _next = 0; - } - - /** - * @param now the time now (in milliseconds) - * @return the current calculated request rate over the last second - */ - public boolean isRateExceeded(long now) - { - final long last; - synchronized (this) - { - last = _timestamps[_next]; - _timestamps[_next] = now; - _next = (_next + 1) % _timestamps.length; - } - - return last != 0 && (now - last) < 1000L; - } - - public String getId() - { - return _id; - } - - public int getType() - { - return _type; - } - - @Override - public void valueBound(HttpSessionBindingEvent event) - { - if (LOG.isDebugEnabled()) - LOG.debug("Value bound: {}", getId()); - _context = event.getSession().getServletContext(); - } - - @Override - public void valueUnbound(HttpSessionBindingEvent event) - { - //take the tracker out of the list of trackers - DoSFilter filter = (DoSFilter)event.getSession().getServletContext().getAttribute(_filterName); - removeFromRateTrackers(filter, _id); - _context = null; - } - - @Override - public void sessionWillPassivate(HttpSessionEvent se) - { - //take the tracker of the list of trackers (if its still there) - DoSFilter filter = (DoSFilter)se.getSession().getServletContext().getAttribute(_filterName); - removeFromRateTrackers(filter, _id); - _context = null; - } - - @Override - public void sessionDidActivate(HttpSessionEvent se) - { - RateTracker tracker = (RateTracker)se.getSession().getAttribute(__TRACKER); - ServletContext context = se.getSession().getServletContext(); - tracker.setContext(context); - DoSFilter filter = (DoSFilter)context.getAttribute(_filterName); - if (filter == null) - { - LOG.info("No filter {} for rate tracker {}", _filterName, tracker); - return; - } - addToRateTrackers(filter, tracker); - } - - public void setContext(ServletContext context) - { - _context = context; - } - - protected void removeFromRateTrackers(DoSFilter filter, String id) - { - if (filter == null) - return; - - filter.removeFromRateTracker(id); - if (LOG.isDebugEnabled()) - LOG.debug("Tracker removed: {}", getId()); - } - - private void addToRateTrackers(DoSFilter filter, RateTracker tracker) - { - if (filter == null) - return; - filter.addToRateTracker(tracker); - } - - @Override - public void run() - { - if (_context == null) - { - LOG.warn("Unknkown context for rate tracker {}", this); - return; - } - - int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1); - long last = _timestamps[latestIndex]; - boolean hasRecentRequest = last != 0 && (System.currentTimeMillis() - last) < 1000L; - - DoSFilter filter = (DoSFilter)_context.getAttribute(_filterName); - - if (hasRecentRequest) - { - if (filter != null) - filter.schedule(this); - else - LOG.warn("No filter {}", _filterName); - } - else - removeFromRateTrackers(filter, _id); - } - - @Override - public String toString() - { - return "RateTracker/" + _id + "/" + _type; - } - } - - private static class FixedRateTracker extends RateTracker - { - public FixedRateTracker(ServletContext context, String filterName, String id, int type, int numRecentRequestsTracked) - { - super(context, filterName, id, type, numRecentRequestsTracked); - } - - @Override - public boolean isRateExceeded(long now) - { - // rate limit is never exceeded, but we keep track of the request timestamps - // so that we know whether there was recent activity on this tracker - // and whether it should be expired - synchronized (this) - { - _timestamps[_next] = now; - _next = (_next + 1) % _timestamps.length; - } - - return false; - } - - @Override - public String toString() - { - return "Fixed" + super.toString(); - } - } - - private static class DoSTimeoutAsyncListener implements AsyncListener - { - @Override - public void onStartAsync(AsyncEvent event) - { - } - - @Override - public void onComplete(AsyncEvent event) - { - } - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - event.getAsyncContext().dispatch(); - } - - @Override - public void onError(AsyncEvent event) - { - } - } - - private class DoSAsyncListener extends DoSTimeoutAsyncListener - { - private final int priority; - - public DoSAsyncListener(int priority) - { - this.priority = priority; - } - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - _queues[priority].remove(event.getAsyncContext()); - super.onTimeout(event); - } - } - - private String createRemotePortId(final ServletRequest request) - { - final String addr = request.getRemoteAddr(); - final int port = request.getRemotePort(); - if (addr.contains(":")) - return "[" + addr + "]:" + port; - return addr + ":" + port; - } -} diff --git a/test-proxy/src/main/java/servlets/EventSource.java b/test-proxy/src/main/java/servlets/EventSource.java deleted file mode 100644 index 5bf2390..0000000 --- a/test-proxy/src/main/java/servlets/EventSource.java +++ /dev/null @@ -1,108 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import java.io.IOException; - -/** - *

{@link EventSource} is the passive half of an event source connection, as defined by the - * EventSource Specification.

- *

{@link EventSource.Emitter} is the active half of the connection and allows to operate on the connection.

- *

{@link EventSource} allows applications to be notified of events happening on the connection; - * two events are being notified: the opening of the event source connection, where method - * {@link EventSource#onOpen(Emitter)} is invoked, and the closing of the event source connection, - * where method {@link EventSource#onClose()} is invoked.

- * - * @see EventSourceServlet - */ -public interface EventSource -{ - /** - *

Callback method invoked when an event source connection is opened.

- * - * @param emitter the {@link Emitter} instance that allows to operate on the connection - * @throws IOException if the implementation of the method throws such exception - */ - public void onOpen(Emitter emitter) throws IOException; - - /** - *

Callback method invoked when an event source connection is closed.

- */ - public void onClose(); - - /** - *

{@link Emitter} is the active half of an event source connection, and allows applications - * to operate on the connection by sending events, data or comments, or by closing the connection.

- *

An {@link Emitter} instance will be created for each new event source connection.

- *

{@link Emitter} instances are fully thread safe and can be used from multiple threads.

- */ - public interface Emitter - { - /** - *

Sends a named event with data to the client.

- *

When invoked as: event("foo", "bar"), the client will receive the lines:

- *
-         * event: foo
-         * data: bar
-         * 
- * - * @param name the event name - * @param data the data to be sent - * @throws IOException if an I/O failure occurred - * @see #data(String) - */ - public void event(String name, String data) throws IOException; - - /** - *

Sends a default event with data to the client.

- *

When invoked as: data("baz"), the client will receive the line:

- *
-         * data: baz
-         * 
- *

When invoked as: data("foo\r\nbar\rbaz\nbax"), the client will receive the lines:

- *
-         * data: foo
-         * data: bar
-         * data: baz
-         * data: bax
-         * 
- * - * @param data the data to be sent - * @throws IOException if an I/O failure occurred - */ - public void data(String data) throws IOException; - - /** - *

Sends a comment to the client.

- *

When invoked as: comment("foo"), the client will receive the line:

- *
-         * : foo
-         * 
- * - * @param comment the comment to send - * @throws IOException if an I/O failure occurred - */ - public void comment(String comment) throws IOException; - - /** - *

Closes this event source connection.

- */ - public void close(); - } -} diff --git a/test-proxy/src/main/java/servlets/EventSourceServlet.java b/test-proxy/src/main/java/servlets/EventSourceServlet.java deleted file mode 100644 index 9344fa7..0000000 --- a/test-proxy/src/main/java/servlets/EventSourceServlet.java +++ /dev/null @@ -1,236 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - *

A servlet that implements the event source protocol, - * also known as "server sent events".

- *

This servlet must be subclassed to implement abstract method {@link #newEventSource(HttpServletRequest)} - * to return an instance of {@link EventSource} that allows application to listen for event source events - * and to emit event source events.

- *

This servlet supports the following configuration parameters:

- *
    - *
  • heartBeatPeriod, that specifies the heartbeat period, in seconds, used to check - * whether the connection has been closed by the client; defaults to 10 seconds.
  • - *
- * - *

NOTE: there is currently no support for last-event-id.

- */ -public abstract class EventSourceServlet extends HttpServlet -{ - private static final byte[] CRLF = new byte[]{'\r', '\n'}; - private static final byte[] EVENT_FIELD = "event: ".getBytes(StandardCharsets.UTF_8); - private static final byte[] DATA_FIELD = "data: ".getBytes(StandardCharsets.UTF_8); - private static final byte[] COMMENT_FIELD = ": ".getBytes(StandardCharsets.UTF_8); - - private ScheduledExecutorService scheduler; - private int heartBeatPeriod = 10; - - @Override - public void init() throws ServletException - { - String heartBeatPeriodParam = getServletConfig().getInitParameter("heartBeatPeriod"); - if (heartBeatPeriodParam != null) - heartBeatPeriod = Integer.parseInt(heartBeatPeriodParam); - scheduler = Executors.newSingleThreadScheduledExecutor(); - } - - @Override - public void destroy() - { - if (scheduler != null) - scheduler.shutdown(); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - @SuppressWarnings("unchecked") Enumeration acceptValues = request.getHeaders("Accept"); - while (acceptValues.hasMoreElements()) - { - String accept = acceptValues.nextElement(); - if (accept.equals("text/event-stream")) - { - EventSource eventSource = newEventSource(request); - if (eventSource == null) - { - response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - } - else - { - respond(request, response); - AsyncContext async = request.startAsync(); - // Infinite timeout because the continuation is never resumed, - // but only completed on close - async.setTimeout(0); - EventSourceEmitter emitter = new EventSourceEmitter(eventSource, async); - emitter.scheduleHeartBeat(); - open(eventSource, emitter); - } - return; - } - } - super.doGet(request, response); - } - - protected abstract EventSource newEventSource(HttpServletRequest request); - - protected void respond(HttpServletRequest request, HttpServletResponse response) throws IOException - { - response.setStatus(HttpServletResponse.SC_OK); - response.setCharacterEncoding(StandardCharsets.UTF_8.name()); - response.setContentType("text/event-stream"); - // By adding this header, and not closing the connection, - // we disable HTTP chunking, and we can use write()+flush() - // to send data in the text/event-stream protocol - response.addHeader("Connection", "close"); - response.flushBuffer(); - } - - protected void open(EventSource eventSource, EventSource.Emitter emitter) throws IOException - { - eventSource.onOpen(emitter); - } - - protected class EventSourceEmitter implements EventSource.Emitter, Runnable - { - private final EventSource eventSource; - private final AsyncContext async; - private final ServletOutputStream output; - private Future heartBeat; - private boolean closed; - - public EventSourceEmitter(EventSource eventSource, AsyncContext async) throws IOException - { - this.eventSource = eventSource; - this.async = async; - this.output = async.getResponse().getOutputStream(); - } - - @Override - public void event(String name, String data) throws IOException - { - synchronized (this) - { - output.write(EVENT_FIELD); - output.write(name.getBytes(StandardCharsets.UTF_8)); - output.write(CRLF); - data(data); - } - } - - @Override - public void data(String data) throws IOException - { - synchronized (this) - { - BufferedReader reader = new BufferedReader(new StringReader(data)); - String line; - while ((line = reader.readLine()) != null) - { - output.write(DATA_FIELD); - output.write(line.getBytes(StandardCharsets.UTF_8)); - output.write(CRLF); - } - output.write(CRLF); - flush(); - } - } - - @Override - public void comment(String comment) throws IOException - { - synchronized (this) - { - output.write(COMMENT_FIELD); - output.write(comment.getBytes(StandardCharsets.UTF_8)); - output.write(CRLF); - output.write(CRLF); - flush(); - } - } - - @Override - public void run() - { - // If the other peer closes the connection, the first - // flush() should generate a TCP reset that is detected - // on the second flush() - try - { - synchronized (this) - { - output.write('\r'); - flush(); - output.write('\n'); - flush(); - } - // We could write, reschedule heartbeat - scheduleHeartBeat(); - } - catch (IOException x) - { - // The other peer closed the connection - close(); - eventSource.onClose(); - } - } - - protected void flush() throws IOException - { - async.getResponse().flushBuffer(); - } - - @Override - public void close() - { - synchronized (this) - { - closed = true; - heartBeat.cancel(false); - } - async.complete(); - } - - private void scheduleHeartBeat() - { - synchronized (this) - { - if (!closed) - heartBeat = scheduler.schedule(this, heartBeatPeriod, TimeUnit.SECONDS); - } - } - } -} diff --git a/test-proxy/src/main/java/servlets/HeaderFilter.java b/test-proxy/src/main/java/servlets/HeaderFilter.java deleted file mode 100644 index 461aac8..0000000 --- a/test-proxy/src/main/java/servlets/HeaderFilter.java +++ /dev/null @@ -1,194 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.util.StringUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Header Filter - *

- * This filter sets or adds a header to the response. - *

- * The {@code headerConfig} init param is a CSV of actions to perform on headers, with the following syntax:
- * [action] [header name]: [header value]
- * [action] can be one of set, add, setDate, or addDate
- * The date actions will add the header value in milliseconds to the current system time before setting a date header. - *

- * Below is an example value for headerConfig:
- * - *

- * set X-Frame-Options: DENY,
- * "add Cache-Control: no-cache, no-store, must-revalidate",
- * setDate Expires: 31540000000,
- * addDate Date: 0
- * 
- * - * @see IncludeExcludeBasedFilter - */ -public class HeaderFilter extends IncludeExcludeBasedFilter -{ - private List _configuredHeaders = new ArrayList<>(); - private static final Logger LOG = LoggerFactory.getLogger(HeaderFilter.class); - - @Override - public void init(FilterConfig filterConfig) throws ServletException - { - super.init(filterConfig); - String headerConfig = filterConfig.getInitParameter("headerConfig"); - - if (headerConfig != null) - { - String[] configs = StringUtil.csvSplit(headerConfig); - for (String config : configs) - { - _configuredHeaders.add(parseHeaderConfiguration(config)); - } - } - - if (LOG.isDebugEnabled()) - LOG.debug(this.toString()); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException - { - HttpServletRequest httpRequest = (HttpServletRequest)request; - HttpServletResponse httpResponse = (HttpServletResponse)response; - - if (super.shouldFilter(httpRequest, httpResponse)) - { - for (ConfiguredHeader header : _configuredHeaders) - { - if (header.isDate()) - { - long headerValue = System.currentTimeMillis() + header.getMsOffset(); - if (header.isAdd()) - { - httpResponse.addDateHeader(header.getName(), headerValue); - } - else - { - httpResponse.setDateHeader(header.getName(), headerValue); - } - } - else // constant header value - { - if (header.isAdd()) - { - httpResponse.addHeader(header.getName(), header.getValue()); - } - else - { - httpResponse.setHeader(header.getName(), header.getValue()); - } - } - } - } - - chain.doFilter(request, response); - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString()).append("\n"); - sb.append("configured headers:\n"); - for (ConfiguredHeader c : _configuredHeaders) - { - sb.append(c).append("\n"); - } - - return sb.toString(); - } - - private ConfiguredHeader parseHeaderConfiguration(String config) - { - String[] configTokens = config.trim().split(" ", 2); - String method = configTokens[0].trim(); - String header = configTokens[1]; - String[] headerTokens = header.trim().split(":", 2); - String headerName = headerTokens[0].trim(); - String headerValue = headerTokens[1].trim(); - ConfiguredHeader configuredHeader = new ConfiguredHeader(headerName, headerValue, method.startsWith("add"), method.endsWith("Date")); - return configuredHeader; - } - - private static class ConfiguredHeader - { - private String _name; - private String _value; - private long _msOffset; - private boolean _add; - private boolean _date; - - public ConfiguredHeader(String name, String value, boolean add, boolean date) - { - _name = name; - _value = value; - _add = add; - _date = date; - - if (_date) - { - _msOffset = Long.parseLong(_value); - } - } - - public String getName() - { - return _name; - } - - public String getValue() - { - return _value; - } - - public boolean isAdd() - { - return _add; - } - - public boolean isDate() - { - return _date; - } - - public long getMsOffset() - { - return _msOffset; - } - - @Override - public String toString() - { - return (_add ? "add" : "set") + (_date ? "Date" : "") + " " + _name + ": " + _value; - } - } -} diff --git a/test-proxy/src/main/java/servlets/IncludeExcludeBasedFilter.java b/test-proxy/src/main/java/servlets/IncludeExcludeBasedFilter.java deleted file mode 100644 index 77405f6..0000000 --- a/test-proxy/src/main/java/servlets/IncludeExcludeBasedFilter.java +++ /dev/null @@ -1,185 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.http.pathmap.PathSpecSet; -import org.eclipse.jetty.util.IncludeExclude; -import org.eclipse.jetty.util.IncludeExcludeSet; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.URIUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.Filter; -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Include Exclude Based Filter - *

- * This is an abstract filter which helps with filtering based on include/exclude of paths, mime types, and/or http methods. - *

- * Use the {@link #shouldFilter(HttpServletRequest, HttpServletResponse)} method to determine if a request/response should be filtered. If mime types are used, - * it should be called after {@link javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} since the mime type may not - * be written until then. - * - * Supported init params: - *

    - *
  • includedPaths - CSV of path specs to include
  • - *
  • excludedPaths - CSV of path specs to exclude
  • - *
  • includedMimeTypes - CSV of mime types to include
  • - *
  • excludedMimeTypes - CSV of mime types to exclude
  • - *
  • includedHttpMethods - CSV of http methods to include
  • - *
  • excludedHttpMethods - CSV of http methods to exclude
  • - *
- *

- * Path spec rules: - *

    - *
  • If the spec starts with '^' the spec is assumed to be a regex based path spec and will match with normal Java regex rules.
  • - *
  • If the spec starts with '/' the spec is assumed to be a Servlet url-pattern rules path spec for either an exact match or prefix based - * match.
  • - *
  • If the spec starts with '*.' the spec is assumed to be a Servlet url-pattern rules path spec for a suffix based match.
  • - *
  • All other syntaxes are unsupported.
  • - *
- *

- * CSVs are parsed with {@link StringUtil#csvSplit(String)} - * - * @see PathSpecSet - * @see IncludeExcludeSet - */ -public abstract class IncludeExcludeBasedFilter implements Filter -{ - private final IncludeExclude _mimeTypes = new IncludeExclude<>(); - private final IncludeExclude _httpMethods = new IncludeExclude<>(); - private final IncludeExclude _paths = new IncludeExclude<>(PathSpecSet.class); - private static final Logger LOG = LoggerFactory.getLogger(IncludeExcludeBasedFilter.class); - - @Override - public void init(FilterConfig filterConfig) throws ServletException - { - final String includedPaths = filterConfig.getInitParameter("includedPaths"); - final String excludedPaths = filterConfig.getInitParameter("excludedPaths"); - final String includedMimeTypes = filterConfig.getInitParameter("includedMimeTypes"); - final String excludedMimeTypes = filterConfig.getInitParameter("excludedMimeTypes"); - final String includedHttpMethods = filterConfig.getInitParameter("includedHttpMethods"); - final String excludedHttpMethods = filterConfig.getInitParameter("excludedHttpMethods"); - - if (includedPaths != null) - { - _paths.include(StringUtil.csvSplit(includedPaths)); - } - if (excludedPaths != null) - { - _paths.exclude(StringUtil.csvSplit(excludedPaths)); - } - if (includedMimeTypes != null) - { - _mimeTypes.include(StringUtil.csvSplit(includedMimeTypes)); - } - if (excludedMimeTypes != null) - { - _mimeTypes.exclude(StringUtil.csvSplit(excludedMimeTypes)); - } - if (includedHttpMethods != null) - { - _httpMethods.include(StringUtil.csvSplit(includedHttpMethods)); - } - if (excludedHttpMethods != null) - { - _httpMethods.exclude(StringUtil.csvSplit(excludedHttpMethods)); - } - } - - protected String guessMimeType(HttpServletRequest httpRequest, HttpServletResponse httpResponse) - { - String contentType = httpResponse.getContentType(); - LOG.debug("Content Type is: {}", contentType); - - String mimeType = ""; - if (contentType != null) - { - mimeType = MimeTypes.getContentTypeWithoutCharset(contentType); - LOG.debug("Mime Type is: {}", mimeType); - } - else - { - String requestUrl = httpRequest.getPathInfo(); - mimeType = MimeTypes.getDefaultMimeByExtension(requestUrl); - - if (mimeType == null) - { - mimeType = ""; - } - - LOG.debug("Guessed mime type is {}", mimeType); - } - - return mimeType; - } - - protected boolean shouldFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse) - { - String httpMethod = httpRequest.getMethod(); - LOG.debug("HTTP method is: {}", httpMethod); - if (!_httpMethods.test(httpMethod)) - { - LOG.debug("should not apply filter because HTTP method does not match"); - return false; - } - - String mimeType = guessMimeType(httpRequest, httpResponse); - - if (!_mimeTypes.test(mimeType)) - { - LOG.debug("should not apply filter because mime type does not match"); - return false; - } - - ServletContext context = httpRequest.getServletContext(); - String path = context == null ? httpRequest.getRequestURI() : URIUtil.addPaths(httpRequest.getServletPath(), httpRequest.getPathInfo()); - LOG.debug("Path is: {}", path); - if (!_paths.test(path)) - { - LOG.debug("should not apply filter because path does not match"); - return false; - } - - return true; - } - - @Override - public void destroy() - { - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("filter configuration:\n"); - sb.append("paths:\n").append(_paths).append("\n"); - sb.append("mime types:\n").append(_mimeTypes).append("\n"); - sb.append("http methods:\n").append(_httpMethods); - return sb.toString(); - } -} diff --git a/test-proxy/src/main/java/servlets/PushCacheFilter.java b/test-proxy/src/main/java/servlets/PushCacheFilter.java deleted file mode 100644 index d78a544..0000000 --- a/test-proxy/src/main/java/servlets/PushCacheFilter.java +++ /dev/null @@ -1,306 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.http.*; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.annotation.ManagedAttribute; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.annotation.ManagedOperation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.PushBuilder; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -/** - *

A filter that builds a cache of secondary resources associated - * to primary resources.

- *

A typical request for a primary resource such as {@code index.html} - * is immediately followed by a number of requests for secondary resources. - * Secondary resource requests will have a {@code Referer} HTTP header - * that points to {@code index.html}, which is used to associate the secondary - * resource to the primary resource.

- *

Only secondary resources that are requested within a (small) time period - * from the request of the primary resource are associated with the primary - * resource.

- *

This allows to build a cache of secondary resources associated with - * primary resources. When a request for a primary resource arrives, associated - * secondary resources are pushed to the client, unless the request carries - * {@code If-xxx} header that hint that the client has the resources in its - * cache.

- *

If the init param useQueryInKey is set, then the query string is used as - * as part of the key to identify a resource

- */ -@ManagedObject("Push cache based on the HTTP 'Referer' header") -public class PushCacheFilter implements Filter -{ - private static final Logger LOG = LoggerFactory.getLogger(PushCacheFilter.class); - - private final Set _ports = new HashSet<>(); - private final Set _hosts = new HashSet<>(); - private final ConcurrentMap _cache = new ConcurrentHashMap<>(); - private long _associatePeriod = 4000L; - private int _maxAssociations = 16; - private long _renew = System.nanoTime(); - private boolean _useQueryInKey; - - @Override - public void init(FilterConfig config) throws ServletException - { - String associatePeriod = config.getInitParameter("associatePeriod"); - if (associatePeriod != null) - _associatePeriod = Long.parseLong(associatePeriod); - - String maxAssociations = config.getInitParameter("maxAssociations"); - if (maxAssociations != null) - _maxAssociations = Integer.parseInt(maxAssociations); - - String hosts = config.getInitParameter("hosts"); - if (hosts != null) - Collections.addAll(_hosts, StringUtil.csvSplit(hosts)); - - String ports = config.getInitParameter("ports"); - if (ports != null) - for (String p : StringUtil.csvSplit(ports)) - { - _ports.add(Integer.parseInt(p)); - } - - _useQueryInKey = Boolean.parseBoolean(config.getInitParameter("useQueryInKey")); - - // Expose for JMX. - config.getServletContext().setAttribute(config.getFilterName(), this); - - if (LOG.isDebugEnabled()) - LOG.debug("period={} max={} hosts={} ports={}", _associatePeriod, _maxAssociations, _hosts, _ports); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException - { - HttpServletRequest request = (HttpServletRequest)req; - - PushBuilder pushBuilder = request.newPushBuilder(); - if (HttpVersion.fromString(request.getProtocol()).getVersion() < 20 || - !HttpMethod.GET.is(request.getMethod()) || - pushBuilder == null) - { - chain.doFilter(req, resp); - return; - } - - long now = System.nanoTime(); - - boolean conditional = false; - String referrer = null; - List headerNames = Collections.list(request.getHeaderNames()); - for (String headerName : headerNames) - { - if (HttpHeader.IF_MATCH.is(headerName) || - HttpHeader.IF_MODIFIED_SINCE.is(headerName) || - HttpHeader.IF_NONE_MATCH.is(headerName) || - HttpHeader.IF_UNMODIFIED_SINCE.is(headerName)) - { - conditional = true; - break; - } - else if (HttpHeader.REFERER.is(headerName)) - { - referrer = request.getHeader(headerName); - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("{} {} referrer={} conditional={}", request.getMethod(), request.getRequestURI(), referrer, conditional); - - String path = request.getRequestURI(); - String query = request.getQueryString(); - if (_useQueryInKey && query != null) - path += "?" + query; - if (referrer != null) - { - HttpURI referrerURI = new HttpURI(referrer); - String host = referrerURI.getHost(); - int port = referrerURI.getPort(); - if (port <= 0) - { - String scheme = referrerURI.getScheme(); - if (scheme != null) - port = HttpScheme.HTTPS.is(scheme) ? 443 : 80; - else - port = request.isSecure() ? 443 : 80; - } - - boolean referredFromHere = !_hosts.isEmpty() ? _hosts.contains(host) : host.equals(request.getServerName()); - referredFromHere &= !_ports.isEmpty() ? _ports.contains(port) : port == request.getServerPort(); - - if (referredFromHere) - { - if (HttpMethod.GET.is(request.getMethod())) - { - String referrerPath = _useQueryInKey ? referrerURI.getPathQuery() : referrerURI.getPath(); - if (referrerPath == null) - referrerPath = "/"; - if (referrerPath.startsWith(request.getContextPath() + "/")) - { - if (!referrerPath.equals(path)) - { - PrimaryResource primaryResource = _cache.get(referrerPath); - if (primaryResource != null) - { - long primaryTimestamp = primaryResource._timestamp.get(); - if (primaryTimestamp != 0) - { - if (now - primaryTimestamp < TimeUnit.MILLISECONDS.toNanos(_associatePeriod)) - { - Set associated = primaryResource._associated; - // Not strictly concurrent-safe, just best effort to limit associations. - if (associated.size() <= _maxAssociations) - { - if (associated.add(path)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Associated {} to {}", path, referrerPath); - } - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not associated {} to {}, exceeded max associations of {}", path, referrerPath, _maxAssociations); - } - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not associated {} to {}, outside associate period of {}ms", path, referrerPath, _associatePeriod); - } - } - } - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not associated {} to {}, referring to self", path, referrerPath); - } - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not associated {} to {}, different context", path, referrerPath); - } - } - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("External referrer {}", referrer); - } - } - - PrimaryResource primaryResource = _cache.get(path); - if (primaryResource == null) - { - PrimaryResource r = new PrimaryResource(); - primaryResource = _cache.putIfAbsent(path, r); - primaryResource = primaryResource == null ? r : primaryResource; - primaryResource._timestamp.compareAndSet(0, now); - if (LOG.isDebugEnabled()) - LOG.debug("Cached primary resource {}", path); - } - else - { - long last = primaryResource._timestamp.get(); - if (last < _renew && primaryResource._timestamp.compareAndSet(last, now)) - { - primaryResource._associated.clear(); - if (LOG.isDebugEnabled()) - LOG.debug("Clear associated resources for {}", path); - } - } - - // Push associated resources. - if (!conditional && !primaryResource._associated.isEmpty()) - { - // Breadth-first push of associated resources. - Queue queue = new ArrayDeque<>(); - queue.offer(primaryResource); - while (!queue.isEmpty()) - { - PrimaryResource parent = queue.poll(); - for (String childPath : parent._associated) - { - PrimaryResource child = _cache.get(childPath); - if (child != null) - queue.offer(child); - - if (LOG.isDebugEnabled()) - LOG.debug("Pushing {} for {}", childPath, path); - pushBuilder.path(childPath).push(); - } - } - } - - chain.doFilter(request, resp); - } - - @Override - public void destroy() - { - clearPushCache(); - } - - @ManagedAttribute("The push cache contents") - public Map getPushCache() - { - Map result = new HashMap<>(); - for (Map.Entry entry : _cache.entrySet()) - { - PrimaryResource resource = entry.getValue(); - String value = String.format("size=%d: %s", resource._associated.size(), new TreeSet<>(resource._associated)); - result.put(entry.getKey(), value); - } - return result; - } - - @ManagedOperation(value = "Renews the push cache contents", impact = "ACTION") - public void renewPushCache() - { - _renew = System.nanoTime(); - } - - @ManagedOperation(value = "Clears the push cache contents", impact = "ACTION") - public void clearPushCache() - { - _cache.clear(); - } - - private static class PrimaryResource - { - private final Set _associated = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final AtomicLong _timestamp = new AtomicLong(); - } -} diff --git a/test-proxy/src/main/java/servlets/PushSessionCacheFilter.java b/test-proxy/src/main/java/servlets/PushSessionCacheFilter.java deleted file mode 100644 index 9d71bb9..0000000 --- a/test-proxy/src/main/java/servlets/PushSessionCacheFilter.java +++ /dev/null @@ -1,194 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpURI; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.servlet.http.PushBuilder; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; - -public class PushSessionCacheFilter implements Filter -{ - private static final String RESPONSE_ATTR = "PushSessionCacheFilter.response"; - private static final String TARGET_ATTR = "PushSessionCacheFilter.target"; - private static final String TIMESTAMP_ATTR = "PushSessionCacheFilter.timestamp"; - private static final Logger LOG = LoggerFactory.getLogger(PushSessionCacheFilter.class); - private final ConcurrentMap _cache = new ConcurrentHashMap<>(); - private long _associateDelay = 5000L; - - @Override - public void init(FilterConfig config) throws ServletException - { - if (config.getInitParameter("associateDelay") != null) - _associateDelay = Long.parseLong(config.getInitParameter("associateDelay")); - - // Add a listener that is used to collect information - // about associated resource, etags and modified dates. - config.getServletContext().addListener(new ServletRequestListener() - { - // Collect information when request is destroyed. - @Override - public void requestDestroyed(ServletRequestEvent sre) - { - HttpServletRequest request = (HttpServletRequest)sre.getServletRequest(); - Target target = (Target)request.getAttribute(TARGET_ATTR); - if (target == null) - return; - - // Update conditional data. - HttpServletResponse response = (HttpServletResponse)request.getAttribute(RESPONSE_ATTR); - target._etag = response.getHeader(HttpHeader.ETAG.asString()); - target._lastModified = response.getHeader(HttpHeader.LAST_MODIFIED.asString()); - - if (LOG.isDebugEnabled()) - LOG.debug("Served {} for {}", response.getStatus(), request.getRequestURI()); - - // Does this request have a referer? - String referer = request.getHeader(HttpHeader.REFERER.asString()); - if (referer != null) - { - // Is the referer from this contexts? - HttpURI refererUri = new HttpURI(referer); - if (request.getServerName().equals(refererUri.getHost())) - { - Target refererTarget = _cache.get(refererUri.getPath()); - if (refererTarget != null) - { - HttpSession session = request.getSession(); - @SuppressWarnings("unchecked") ConcurrentHashMap timestamps = (ConcurrentHashMap)session.getAttribute(TIMESTAMP_ATTR); - Long last = timestamps.get(refererTarget._path); - if (last != null && TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - last) < _associateDelay) - { - if (refererTarget._associated.putIfAbsent(target._path, target) == null) - { - if (LOG.isDebugEnabled()) - LOG.debug("ASSOCIATE {}->{}", refererTarget._path, target._path); - } - } - } - } - } - } - - @Override - public void requestInitialized(ServletRequestEvent sre) - { - } - }); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException - { - req.setAttribute(RESPONSE_ATTR, resp); - HttpServletRequest request = (HttpServletRequest)req; - String uri = request.getRequestURI(); - - if (LOG.isDebugEnabled()) - LOG.debug("{} {}", request.getMethod(), uri); - - HttpSession session = request.getSession(true); - - // find the target for this resource - Target target = _cache.get(uri); - if (target == null) - { - Target t = new Target(uri); - target = _cache.putIfAbsent(uri, t); - target = target == null ? t : target; - } - request.setAttribute(TARGET_ATTR, target); - - // Set the timestamp for this resource in this session - @SuppressWarnings("unchecked") ConcurrentHashMap timestamps = (ConcurrentHashMap)session.getAttribute(TIMESTAMP_ATTR); - if (timestamps == null) - { - timestamps = new ConcurrentHashMap<>(); - session.setAttribute(TIMESTAMP_ATTR, timestamps); - } - timestamps.put(uri, System.nanoTime()); - - // Push any associated resources. - PushBuilder builder = request.newPushBuilder(); - if (builder != null && !target._associated.isEmpty()) - { - boolean conditional = request.getHeader(HttpHeader.IF_NONE_MATCH.asString()) != null || - request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString()) != null; - // Breadth-first push of associated resources. - Queue queue = new ArrayDeque<>(); - queue.offer(target); - while (!queue.isEmpty()) - { - Target parent = queue.poll(); - builder.addHeader("X-Pusher", PushSessionCacheFilter.class.toString()); - for (Target child : parent._associated.values()) - { - queue.offer(child); - - String path = child._path; - if (LOG.isDebugEnabled()) - LOG.debug("PUSH {} <- {}", path, uri); - - builder.path(path) - .setHeader(HttpHeader.IF_NONE_MATCH.asString(), conditional ? child._etag : null) - .setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(), conditional ? child._lastModified : null); - } - } - } - - chain.doFilter(req, resp); - } - - @Override - public void destroy() - { - _cache.clear(); - } - - private static class Target - { - private final String _path; - private final ConcurrentMap _associated = new ConcurrentHashMap<>(); - private volatile String _etag; - private volatile String _lastModified; - - private Target(String path) - { - _path = path; - } - - @Override - public String toString() - { - return String.format("Target{p=%s,e=%s,m=%s,a=%d}", _path, _etag, _lastModified, _associated.size()); - } - } -} diff --git a/test-proxy/src/main/java/servlets/PutFilter.java b/test-proxy/src/main/java/servlets/PutFilter.java deleted file mode 100644 index 85137f3..0000000 --- a/test-proxy/src/main/java/servlets/PutFilter.java +++ /dev/null @@ -1,356 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.URIUtil; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * PutFilter - * - * A Filter that handles PUT, DELETE and MOVE methods. - * Files are hidden during PUT operations, so that 404's result. - * - * The following init parameters pay be used:
    - *
  • baseURI - The file URI of the document root for put content. - *
  • delAllowed - boolean, if true DELETE and MOVE methods are supported. - *
  • putAtomic - boolean, if true PUT files are written to a temp location and moved into place. - *
- */ -public class PutFilter implements Filter -{ - public static final String __PUT = "PUT"; - public static final String __DELETE = "DELETE"; - public static final String __MOVE = "MOVE"; - public static final String __OPTIONS = "OPTIONS"; - - Set _operations = new HashSet(); - private ConcurrentMap _hidden = new ConcurrentHashMap(); - - private ServletContext _context; - private String _baseURI; - private boolean _delAllowed; - private boolean _putAtomic; - private File _tmpdir; - - @Override - public void init(FilterConfig config) throws ServletException - { - _context = config.getServletContext(); - - _tmpdir = (File)_context.getAttribute("javax.servlet.context.tempdir"); - - if (_context.getRealPath("/") == null) - throw new UnavailableException("Packed war"); - - String b = config.getInitParameter("baseURI"); - if (b != null) - { - _baseURI = b; - } - else - { - File base = new File(_context.getRealPath("/")); - _baseURI = base.toURI().toString(); - } - - _delAllowed = getInitBoolean(config, "delAllowed"); - _putAtomic = getInitBoolean(config, "putAtomic"); - - _operations.add(__OPTIONS); - _operations.add(__PUT); - if (_delAllowed) - { - _operations.add(__DELETE); - _operations.add(__MOVE); - } - } - - private boolean getInitBoolean(FilterConfig config, String name) - { - String value = config.getInitParameter(name); - return value != null && value.length() > 0 && (value.startsWith("t") || value.startsWith("T") || value.startsWith("y") || value.startsWith("Y") || value.startsWith("1")); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException - { - HttpServletRequest request = (HttpServletRequest)req; - HttpServletResponse response = (HttpServletResponse)res; - - String servletPath = request.getServletPath(); - String pathInfo = request.getPathInfo(); - String pathInContext = URIUtil.addPaths(servletPath, pathInfo); - - String resource = URIUtil.addPaths(_baseURI, pathInContext); - - String method = request.getMethod(); - boolean op = _operations.contains(method); - - if (op) - { - File file = null; - try - { - if (method.equals(__OPTIONS)) - handleOptions(chain, request, response); - else - { - file = new File(new URI(resource)); - boolean exists = file.exists(); - if (exists && !passConditionalHeaders(request, response, file)) - return; - - if (method.equals(__PUT)) - handlePut(request, response, pathInContext, file); - else if (method.equals(__DELETE)) - handleDelete(request, response, pathInContext, file); - else if (method.equals(__MOVE)) - handleMove(request, response, pathInContext, file); - else - throw new IllegalStateException(); - } - } - catch (Exception e) - { - _context.log(e.toString(), e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } - else - { - if (isHidden(pathInContext)) - response.sendError(HttpServletResponse.SC_NOT_FOUND); - else - chain.doFilter(request, response); - return; - } - } - - private boolean isHidden(String pathInContext) - { - return _hidden.containsKey(pathInContext); - } - - @Override - public void destroy() - { - } - - public void handlePut(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException - { - boolean exists = file.exists(); - if (pathInContext.endsWith("/")) - { - if (!exists) - { - if (!file.mkdirs()) - response.sendError(HttpServletResponse.SC_FORBIDDEN); - else - { - response.setStatus(HttpServletResponse.SC_CREATED); - response.flushBuffer(); - } - } - else - { - response.setStatus(HttpServletResponse.SC_OK); - response.flushBuffer(); - } - } - else - { - boolean ok = false; - try - { - _hidden.put(pathInContext, pathInContext); - File parent = file.getParentFile(); - parent.mkdirs(); - int toRead = request.getContentLength(); - InputStream in = request.getInputStream(); - - if (_putAtomic) - { - File tmp = File.createTempFile(file.getName(), null, _tmpdir); - try (OutputStream out = new FileOutputStream(tmp, false)) - { - if (toRead >= 0) - IO.copy(in, out, toRead); - else - IO.copy(in, out); - } - - if (!tmp.renameTo(file)) - throw new IOException("rename from " + tmp + " to " + file + " failed"); - } - else - { - try (OutputStream out = new FileOutputStream(file, false)) - { - if (toRead >= 0) - IO.copy(in, out, toRead); - else - IO.copy(in, out); - } - } - - response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED); - response.flushBuffer(); - ok = true; - } - catch (Exception ex) - { - _context.log(ex.toString(), ex); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - finally - { - if (!ok) - { - try - { - if (file.exists()) - file.delete(); - } - catch (Exception e) - { - _context.log(e.toString(), e); - } - } - _hidden.remove(pathInContext); - } - } - } - - public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException - { - try - { - // delete the file - if (file.delete()) - { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - response.flushBuffer(); - } - else - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - catch (SecurityException sex) - { - _context.log(sex.toString(), sex); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - } - - public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) - throws ServletException, IOException, URISyntaxException - { - String newPath = URIUtil.canonicalEncodedPath(request.getHeader("new-uri")); - if (newPath == null) - { - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - return; - } - - String contextPath = request.getContextPath(); - if (contextPath != null && !newPath.startsWith(contextPath)) - { - response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - return; - } - String newInfo = newPath; - if (contextPath != null) - newInfo = newInfo.substring(contextPath.length()); - - String newResource = URIUtil.addEncodedPaths(_baseURI, newInfo); - File newFile = new File(new URI(newResource)); - - file.renameTo(newFile); - - response.setStatus(HttpServletResponse.SC_NO_CONTENT); - response.flushBuffer(); - } - - public void handleOptions(FilterChain chain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - chain.doFilter(request, new HttpServletResponseWrapper(response) - { - @Override - public void setHeader(String name, String value) - { - if ("Allow".equalsIgnoreCase(name)) - { - Set options = new HashSet(); - options.addAll(Arrays.asList(StringUtil.csvSplit(value))); - options.addAll(_operations); - value = null; - for (String o : options) - { - value = value == null ? o : (value + ", " + o); - } - } - - super.setHeader(name, value); - } - }); - } - - /* - * Check modification date headers. - */ - protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException - { - long date = 0; - - if ((date = request.getDateHeader("if-unmodified-since")) > 0) - { - if (file.lastModified() / 1000 > date / 1000) - { - response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); - return false; - } - } - - if ((date = request.getDateHeader("if-modified-since")) > 0) - { - if (file.lastModified() / 1000 <= date / 1000) - { - response.reset(); - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - response.flushBuffer(); - return false; - } - } - return true; - } -} diff --git a/test-proxy/src/main/java/servlets/QoSFilter.java b/test-proxy/src/main/java/servlets/QoSFilter.java deleted file mode 100644 index 79fad50..0000000 --- a/test-proxy/src/main/java/servlets/QoSFilter.java +++ /dev/null @@ -1,380 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.annotation.ManagedAttribute; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -/** - * Quality of Service Filter. - *

- * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10). - * If more requests are received, they are suspended and placed on priority queues. Priorities are determined by - * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" - * init parameter (default 10), with higher values having higher priority. - *

- * This filter is ideal to prevent wasting threads waiting for slow/limited - * resources such as a JDBC connection pool. It avoids the situation where all of a - * containers thread pool may be consumed blocking on such a slow resource. - * By limiting the number of active threads, a smaller thread pool may be used as - * the threads are not wasted waiting. Thus more memory may be available for use by - * the active threads. - *

- * Furthermore, this filter uses a priority when resuming waiting requests. So that if - * a container is under load, and there are many requests waiting for resources, - * the {@link #getPriority(ServletRequest)} method is used, so that more important - * requests are serviced first. For example, this filter could be deployed with a - * maxRequest limit slightly smaller than the containers thread pool and a high priority - * allocated to admin users. Thus regardless of load, admin users would always be - * able to access the web application. - *

- * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire - * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be - * avoided if the semaphore is shortly available. If the semaphore cannot be obtained, the request will be suspended - * for the default suspend period of the container or the valued set as the "suspendMs" init parameter. - *

- * If the "managedAttr" init parameter is set to true, then this servlet is set as a {@link ServletContext} attribute with the - * filter name as the attribute name. This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to - * manage the configuration of the filter. - */ -@ManagedObject("Quality of Service Filter") -public class QoSFilter implements Filter -{ - private static final Logger LOG = LoggerFactory.getLogger(QoSFilter.class); - - static final int __DEFAULT_MAX_PRIORITY = 10; - static final int __DEFAULT_PASSES = 10; - static final int __DEFAULT_WAIT_MS = 50; - static final long __DEFAULT_TIMEOUT_MS = -1; - - static final String MANAGED_ATTR_INIT_PARAM = "managedAttr"; - static final String MAX_REQUESTS_INIT_PARAM = "maxRequests"; - static final String MAX_PRIORITY_INIT_PARAM = "maxPriority"; - static final String MAX_WAIT_INIT_PARAM = "waitMs"; - static final String SUSPEND_INIT_PARAM = "suspendMs"; - - private final String _suspended = "QoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED"; - private final String _resumed = "QoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED"; - private long _waitMs; - private long _suspendMs; - private int _maxRequests; - private Semaphore _passes; - private Queue[] _queues; - private AsyncListener[] _listeners; - - @Override - public void init(FilterConfig filterConfig) - { - int maxPriority = __DEFAULT_MAX_PRIORITY; - if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM) != null) - maxPriority = Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)); - _queues = new Queue[maxPriority + 1]; - _listeners = new AsyncListener[_queues.length]; - for (int p = 0; p < _queues.length; ++p) - { - _queues[p] = new ConcurrentLinkedQueue<>(); - _listeners[p] = new QoSAsyncListener(p); - } - - int maxRequests = __DEFAULT_PASSES; - if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM) != null) - maxRequests = Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)); - _passes = new Semaphore(maxRequests, true); - _maxRequests = maxRequests; - - long wait = __DEFAULT_WAIT_MS; - if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM) != null) - wait = Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)); - _waitMs = wait; - - long suspend = __DEFAULT_TIMEOUT_MS; - if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM) != null) - suspend = Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM)); - _suspendMs = suspend; - - ServletContext context = filterConfig.getServletContext(); - if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM))) - context.setAttribute(filterConfig.getFilterName(), this); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException - { - boolean accepted = false; - try - { - Boolean suspended = (Boolean)request.getAttribute(_suspended); - if (suspended == null) - { - accepted = _passes.tryAcquire(getWaitMs(), TimeUnit.MILLISECONDS); - if (accepted) - { - request.setAttribute(_suspended, Boolean.FALSE); - if (LOG.isDebugEnabled()) - LOG.debug("Accepted {}", request); - } - else - { - request.setAttribute(_suspended, Boolean.TRUE); - int priority = getPriority(request); - AsyncContext asyncContext = request.startAsync(); - long suspendMs = getSuspendMs(); - if (suspendMs > 0) - asyncContext.setTimeout(suspendMs); - asyncContext.addListener(_listeners[priority]); - _queues[priority].add(asyncContext); - if (LOG.isDebugEnabled()) - LOG.debug("Suspended {}", request); - return; - } - } - else - { - if (suspended) - { - request.setAttribute(_suspended, Boolean.FALSE); - Boolean resumed = (Boolean)request.getAttribute(_resumed); - if (Boolean.TRUE.equals(resumed)) - { - _passes.acquire(); - accepted = true; - if (LOG.isDebugEnabled()) - LOG.debug("Resumed {}", request); - } - else - { - // Timeout! try 1 more time. - accepted = _passes.tryAcquire(getWaitMs(), TimeUnit.MILLISECONDS); - if (LOG.isDebugEnabled()) - LOG.debug("Timeout {}", request); - } - } - else - { - // Pass through resume of previously accepted request. - _passes.acquire(); - accepted = true; - if (LOG.isDebugEnabled()) - LOG.debug("Passthrough {}", request); - } - } - - if (accepted) - { - chain.doFilter(request, response); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Rejected {}", request); - ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - } - } - catch (InterruptedException e) - { - ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - } - finally - { - if (accepted) - { - _passes.release(); - - for (int p = _queues.length - 1; p >= 0; --p) - { - AsyncContext asyncContext = _queues[p].poll(); - if (asyncContext != null) - { - ServletRequest candidate = asyncContext.getRequest(); - Boolean suspended = (Boolean)candidate.getAttribute(_suspended); - if (Boolean.TRUE.equals(suspended)) - { - try - { - candidate.setAttribute(_resumed, Boolean.TRUE); - asyncContext.dispatch(); - break; - } - catch (IllegalStateException x) - { - LOG.warn("Unable to resume suspended dispatch", x); - continue; - } - } - } - } - } - } - } - - /** - * Computes the request priority. - *

- * The default implementation assigns the following priorities: - *

    - *
  • 2 - for an authenticated request - *
  • 1 - for a request with valid / non new session - *
  • 0 - for all other requests. - *
- * This method may be overridden to provide application specific priorities. - * - * @param request the incoming request - * @return the computed request priority - */ - protected int getPriority(ServletRequest request) - { - HttpServletRequest baseRequest = (HttpServletRequest)request; - if (baseRequest.getUserPrincipal() != null) - { - return 2; - } - else - { - HttpSession session = baseRequest.getSession(false); - if (session != null && !session.isNew()) - return 1; - else - return 0; - } - } - - @Override - public void destroy() - { - } - - /** - * Get the (short) amount of time (in milliseconds) that the filter would wait - * for the semaphore to become available before suspending a request. - * - * @return wait time (in milliseconds) - */ - @ManagedAttribute("(short) amount of time filter will wait before suspending request (in ms)") - public long getWaitMs() - { - return _waitMs; - } - - /** - * Set the (short) amount of time (in milliseconds) that the filter would wait - * for the semaphore to become available before suspending a request. - * - * @param value wait time (in milliseconds) - */ - public void setWaitMs(long value) - { - _waitMs = value; - } - - /** - * Get the amount of time (in milliseconds) that the filter would suspend - * a request for while waiting for the semaphore to become available. - * - * @return suspend time (in milliseconds) - */ - @ManagedAttribute("amount of time filter will suspend a request for while waiting for the semaphore to become available (in ms)") - public long getSuspendMs() - { - return _suspendMs; - } - - /** - * Set the amount of time (in milliseconds) that the filter would suspend - * a request for while waiting for the semaphore to become available. - * - * @param value suspend time (in milliseconds) - */ - public void setSuspendMs(long value) - { - _suspendMs = value; - } - - /** - * Get the maximum number of requests allowed to be processed - * at the same time. - * - * @return maximum number of requests - */ - @ManagedAttribute("maximum number of requests to allow processing of at the same time") - public int getMaxRequests() - { - return _maxRequests; - } - - /** - * Set the maximum number of requests allowed to be processed - * at the same time. - * - * @param value the number of requests - */ - public void setMaxRequests(int value) - { - _passes = new Semaphore((value - getMaxRequests() + _passes.availablePermits()), true); - _maxRequests = value; - } - - private class QoSAsyncListener implements AsyncListener - { - private final int priority; - - public QoSAsyncListener(int priority) - { - this.priority = priority; - } - - @Override - public void onStartAsync(AsyncEvent event) throws IOException - { - } - - @Override - public void onComplete(AsyncEvent event) throws IOException - { - } - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - // Remove before it's redispatched, so it won't be - // redispatched again at the end of the filtering. - AsyncContext asyncContext = event.getAsyncContext(); - _queues[priority].remove(asyncContext); - ((HttpServletResponse)event.getSuppliedResponse()).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - asyncContext.complete(); - } - - @Override - public void onError(AsyncEvent event) throws IOException - { - } - } -} diff --git a/test-proxy/src/main/java/servlets/WelcomeFilter.java b/test-proxy/src/main/java/servlets/WelcomeFilter.java deleted file mode 100644 index 03f325b..0000000 --- a/test-proxy/src/main/java/servlets/WelcomeFilter.java +++ /dev/null @@ -1,69 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package servlets; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; - -/** - * Welcome Filter - * This filter can be used to server an index file for a directory - * when no index file actually exists (thus the web.xml mechanism does - * not work). - * - * This filter will dispatch requests to a directory (URLs ending with /) - * to the welcome URL determined by the "welcome" init parameter. So if - * the filter "welcome" init parameter is set to "index.do" then a request - * to "/some/directory/" will be dispatched to "/some/directory/index.do" and - * will be handled by any servlets mapped to that URL. - * - * Requests to "/some/directory" will be redirected to "/some/directory/". - */ -public class WelcomeFilter implements Filter -{ - private String welcome; - - @Override - public void init(FilterConfig filterConfig) - { - welcome = filterConfig.getInitParameter("welcome"); - if (welcome == null) - welcome = "index.html"; - } - - @Override - public void doFilter(ServletRequest request, - ServletResponse response, - FilterChain chain) - throws IOException, ServletException - { - String path = ((HttpServletRequest)request).getServletPath(); - if (welcome != null && path.endsWith("/")) - request.getRequestDispatcher(path + welcome).forward(request, response); - else - chain.doFilter(request, response); - } - - @Override - public void destroy() - { - } -} - diff --git a/test-proxy/src/main/java/servlets/package-info.java b/test-proxy/src/main/java/servlets/package-info.java deleted file mode 100644 index e9151f2..0000000 --- a/test-proxy/src/main/java/servlets/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -/** - * Jetty Servlets : Generally Useful Servlets, Handlers and Filters - */ -package servlets; - diff --git a/test-proxy/src/main/webapp/WEB-INF/web.xml b/test-proxy/src/main/webapp/WEB-INF/web.xml index 9f88c1f..dcc0494 100644 --- a/test-proxy/src/main/webapp/WEB-INF/web.xml +++ b/test-proxy/src/main/webapp/WEB-INF/web.xml @@ -1,7 +1,22 @@ + "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" + "http://java.sun.com/dtd/web-app_2_3.dtd" > + + + + misp-proxy + + + test-proxy + com.olexyn.test.proxy.ProxyServlet + + + + test-proxy + /core + - - Archetype Created Web Application diff --git a/test-proxy/src/main/webapp/index.jsp b/test-proxy/src/main/webapp/index.jsp index c38169b..1ed1b19 100644 --- a/test-proxy/src/main/webapp/index.jsp +++ b/test-proxy/src/main/webapp/index.jsp @@ -1,5 +1,20 @@ - -

Hello World!

+ +test-proxy + + + + + + + + + + +
+ +
+

test-proxy

+