From d9a4a61a381c4ec8189eb104b6f80b51600e5317 Mon Sep 17 00:00:00 2001 From: nicetryzz <11810115@mail.sustech.edu.cn> Date: Sun, 25 Apr 2021 20:55:44 +0800 Subject: [PATCH 1/4] fix issue #986 --- pom.xml | 10 ++ .../jetty/EmbeddedJettyServer.java | 4 +- .../embeddedserver/jetty/JettyHandler.java | 25 ++- .../spark/http/matching/MatcherFilter.java | 7 - src/test/java/spark/WebSocketRequestTest.java | 161 ++++++++++++++++++ .../resources/public/chatPages/index.html | 17 ++ src/test/resources/public/chatPages/style.css | 78 +++++++++ .../public/chatPages/websocketDemo.js | 42 +++++ 8 files changed, 329 insertions(+), 15 deletions(-) create mode 100644 src/test/java/spark/WebSocketRequestTest.java create mode 100644 src/test/resources/public/chatPages/index.html create mode 100644 src/test/resources/public/chatPages/style.css create mode 100644 src/test/resources/public/chatPages/websocketDemo.js diff --git a/pom.xml b/pom.xml index 70728ade51..c24a01077b 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,16 @@ ${jetty.version} test + + com.j2html + j2html + 1.2.0 + + + org.json + json + 20160810 + diff --git a/src/main/java/spark/embeddedserver/jetty/EmbeddedJettyServer.java b/src/main/java/spark/embeddedserver/jetty/EmbeddedJettyServer.java index f143efda43..96833722d1 100644 --- a/src/main/java/spark/embeddedserver/jetty/EmbeddedJettyServer.java +++ b/src/main/java/spark/embeddedserver/jetty/EmbeddedJettyServer.java @@ -132,7 +132,9 @@ public int ignite(String host, server.setHandler(handler); } else { List handlersInList = new ArrayList<>(); - handlersInList.add(handler); + JettyHandler jettyHandler = (JettyHandler) handler; + jettyHandler.consume(webSocketHandlers.keySet()); + handlersInList.add(jettyHandler); // WebSocket handler must be the last one if (webSocketServletContextHandler != null) { diff --git a/src/main/java/spark/embeddedserver/jetty/JettyHandler.java b/src/main/java/spark/embeddedserver/jetty/JettyHandler.java index ef0c432c9a..aa72b628dd 100644 --- a/src/main/java/spark/embeddedserver/jetty/JettyHandler.java +++ b/src/main/java/spark/embeddedserver/jetty/JettyHandler.java @@ -17,6 +17,7 @@ package spark.embeddedserver.jetty; import java.io.IOException; +import java.util.Set; import javax.servlet.Filter; import javax.servlet.ServletException; @@ -35,6 +36,8 @@ public class JettyHandler extends SessionHandler { private Filter filter; + private Set consume; + public JettyHandler(Filter filter) { this.filter = filter; } @@ -47,14 +50,22 @@ public void doHandle( HttpServletResponse response) throws IOException, ServletException { HttpRequestWrapper wrapper = new HttpRequestWrapper(request); - filter.doFilter(wrapper, response, null); - - if (wrapper.notConsumed()) { - baseRequest.setHandled(false); - } else { - baseRequest.setHandled(true); + if(consume!=null && consume.contains(baseRequest.getRequestURI())){ + if (wrapper instanceof HttpRequestWrapper) { + ((HttpRequestWrapper) wrapper).notConsumed(true); + } + } + else { + filter.doFilter(wrapper, response, null); } + baseRequest.setHandled(!wrapper.notConsumed()); + } + public void consume(Set consume){ + this.consume=consume; } -} \ No newline at end of file + public Set consume(){ + return this.consume; + } +} diff --git a/src/main/java/spark/http/matching/MatcherFilter.java b/src/main/java/spark/http/matching/MatcherFilter.java index 3fa05bea0a..80679e2f6c 100644 --- a/src/main/java/spark/http/matching/MatcherFilter.java +++ b/src/main/java/spark/http/matching/MatcherFilter.java @@ -156,13 +156,6 @@ public void doFilter(ServletRequest servletRequest, body.set(""); } - if (body.notSet() && hasOtherHandlers) { - if (servletRequest instanceof HttpRequestWrapper) { - ((HttpRequestWrapper) servletRequest).notConsumed(true); - return; - } - } - if (body.notSet()) { LOG.info("The requested route [{}] has not been mapped in Spark for {}: [{}]", uri, ACCEPT_TYPE_REQUEST_MIME_HEADER, acceptType); diff --git a/src/test/java/spark/WebSocketRequestTest.java b/src/test/java/spark/WebSocketRequestTest.java new file mode 100644 index 0000000000..74e3416a20 --- /dev/null +++ b/src/test/java/spark/WebSocketRequestTest.java @@ -0,0 +1,161 @@ +package spark; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.*; +import org.json.JSONObject; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static j2html.TagCreator.*; +import static org.junit.Assert.assertEquals; +import static spark.Spark.*; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import spark.util.SparkTestUtil; + +public class WebSocketRequestTest { + + public static final int PORT = 4567; + + public static final String CHAT = "/hat"; + + public static final String HELLO = "/hello"; + + public static final String OTHER = "/:param"; + + private static final SparkTestUtil http = new SparkTestUtil(4567); + + @AfterClass + public static void tearDown() { + Spark.stop(); + } + + @BeforeClass + public static void setup() throws IOException { + staticFiles.location("/public/chatPages"); //index.html is served at localhost:4567 (default port) + staticFiles.expireTime(600); + webSocket("/chat", spark.test.ChatWebSocketHandler.class); + get(HELLO, (req, res) -> "Hello!"); + get(OTHER, (req, res) -> "other"); + init(); + } + + @Test + public void testUrl1() throws Exception { + try { + Map requestHeader = new HashMap<>(); + requestHeader.put("Host", "localhost:" + PORT); + requestHeader.put("User-Agent", "curl/7.55.1"); + SparkTestUtil.UrlResponse response = http.doMethod("GET",HELLO, "", false, "*/*", requestHeader); + assertEquals(200, response.status); + assertEquals("Hello!",response.body); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testUrl2() throws Exception { + try { + Map requestHeader = new HashMap<>(); + requestHeader.put("Host", "localhost:" + PORT); + requestHeader.put("User-Agent", "curl/7.55.1"); + SparkTestUtil.UrlResponse response = http.doMethod("GET","/aya", "", false, "*/*", requestHeader); + assertEquals(200, response.status); + assertEquals("other",response.body); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testUrl3() throws Exception { + try { + Map requestHeader = new HashMap<>(); + requestHeader.put("Host", "localhost:" + PORT); + requestHeader.put("User-Agent", "curl/7.55.1"); + SparkTestUtil.UrlResponse response = http.doMethod("GET","/chat", "", false, "*/*", requestHeader); + assertEquals(404, response.status); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testUrl4() throws Exception { + try { + Map requestHeader = new HashMap<>(); + requestHeader.put("Host", "localhost:" + PORT); + requestHeader.put("User-Agent", "curl/7.55.1"); + SparkTestUtil.UrlResponse response = http.doMethod("GET","/", "", false, "*/*", requestHeader); + assertEquals(200, response.status); + } + catch (Exception e) { + e.printStackTrace(); + } + } +} + +class Chat{ + // this map is shared between sessions and threads, so it needs to be thread-safe (http://stackoverflow.com/a/2688817) + static Map userUsernameMap = new ConcurrentHashMap<>(); + static int nextUserNumber = 1; //Assign to username for next connecting user + + //Sends a message from one user to all users, along with a list of current usernames + public static void broadcastMessage(String sender, String message) { + userUsernameMap.keySet().stream().filter(Session::isOpen).forEach(session -> { + try { + session.getRemote().sendString(String.valueOf(new JSONObject() + .put("userMessage", createHtmlMessageFromSender(sender, message)) + .put("userlist", userUsernameMap.values()) + )); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + //Builds a HTML element with a sender-name, a message, and a timestamp, + private static String createHtmlMessageFromSender(String sender, String message) { + return article( + b(sender + " says:"), + span(attrs(".timestamp"), new SimpleDateFormat("HH:mm:ss").format(new Date())), + p(message) + ).render(); + } +} + +@WebSocket +class ChatWebSocketHandler { + + private String sender, msg; + + @OnWebSocketConnect + public void onConnect(Session user) throws Exception { + String username = "User" + Chat.nextUserNumber++; + Chat.userUsernameMap.put(user, username); + Chat.broadcastMessage(sender = "Server", msg = (username + " joined the chat")); + } + + @OnWebSocketClose + public void onClose(Session user, int statusCode, String reason) { + String username = Chat.userUsernameMap.get(user); + Chat.userUsernameMap.remove(user); + Chat.broadcastMessage(sender = "Server", msg = (username + " left the chat")); + } + + @OnWebSocketMessage + public void onMessage(Session user, String message) { + Chat.broadcastMessage(sender = Chat.userUsernameMap.get(user), msg = message); + } +} diff --git a/src/test/resources/public/chatPages/index.html b/src/test/resources/public/chatPages/index.html new file mode 100644 index 0000000000..fd6a432b6a --- /dev/null +++ b/src/test/resources/public/chatPages/index.html @@ -0,0 +1,17 @@ + + + + + WebsSockets + + + +
+ + +
+
+
+ + + diff --git a/src/test/resources/public/chatPages/style.css b/src/test/resources/public/chatPages/style.css new file mode 100644 index 0000000000..a28a34ac8f --- /dev/null +++ b/src/test/resources/public/chatPages/style.css @@ -0,0 +1,78 @@ +* { + box-sizing: border-box; +} + +html { + overflow-y: scroll; +} + +body { + font-family: monospace; + font-size: 14px; + max-width: 480px; + margin: 0 auto; + padding: 20px +} + +input { + width: 100%; + padding: 5px; + margin: 5px 0; +} + +button { + float: right; +} + +li { + margin: 5px 0; +} + +#chatControls { + overflow: auto; + margin: 0 0 5px 0 +} + +#userlist { + position: fixed; + left: 50%; + list-style: none; + margin-left: 250px; + background: #f0f0f9; + padding: 5px 10px; + width: 150px; + top: 11px; +} + +#chat p { + margin: 5px 0; + font-weight: 300 +} + +#chat .timestamp { + position: absolute; + top: 10px; + right: 10px; + font-size: 12px; +} + +#chat article { + background: #f1f1f1; + padding: 10px; + margin: 10px 0; + border-left: 5px solid #aaa; + position: relative; + word-wrap: break-word; +} + +#chat article:first-of-type { + background: #c9edc3; + border-left-color: #74a377; + animation: enter .2s 1; +} + +@keyframes enter { + from { transform: none; } + 50% { transform: scale(1.05); } + to { transform: none; } +} \ No newline at end of file diff --git a/src/test/resources/public/chatPages/websocketDemo.js b/src/test/resources/public/chatPages/websocketDemo.js new file mode 100644 index 0000000000..c59383c2ea --- /dev/null +++ b/src/test/resources/public/chatPages/websocketDemo.js @@ -0,0 +1,42 @@ +//Establish the WebSocket connection and set up event handlers +var webSocket = new WebSocket("ws://" + location.hostname + ":" + location.port + "/chat"); +webSocket.onmessage = function (msg) { updateChat(msg); }; +webSocket.onclose = function () { alert("WebSocket connection closed") }; + +//Send message if "Send" is clicked +id("send").addEventListener("click", function () { + sendMessage(id("message").value); +}); + +//Send message if enter is pressed in the input field +id("message").addEventListener("keypress", function (e) { + if (e.keyCode === 13) { sendMessage(e.target.value); } +}); + +//Send a message if it's not empty, then clear the input field +function sendMessage(message) { + if (message !== "") { + webSocket.send(message); + id("message").value = ""; + } +} + +//Update the chat-panel, and the list of connected users +function updateChat(msg) { + var data = JSON.parse(msg.data); + insert("chat", data.userMessage); + id("userlist").innerHTML = ""; + data.userlist.forEach(function (user) { + insert("userlist", "
  • " + user + "
  • "); + }); +} + +//Helper function for inserting HTML as the first child of an element +function insert(targetId, message) { + id(targetId).insertAdjacentHTML("afterbegin", message); +} + +//Helper function for selecting element by id +function id(id) { + return document.getElementById(id); +} \ No newline at end of file From 2b613a31722aee3901b76bb926a2ea6d7cef894e Mon Sep 17 00:00:00 2001 From: nicetryzz <11810115@mail.sustech.edu.cn> Date: Sun, 25 Apr 2021 21:07:29 +0800 Subject: [PATCH 2/4] fix issue #986 --- pom.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pom.xml b/pom.xml index c24a01077b..70728ade51 100644 --- a/pom.xml +++ b/pom.xml @@ -124,16 +124,6 @@ ${jetty.version} test - - com.j2html - j2html - 1.2.0 - - - org.json - json - 20160810 - From 5b52b2e6969931275d051ba72692f008267f5b12 Mon Sep 17 00:00:00 2001 From: nicetryzz <11810115@mail.sustech.edu.cn> Date: Sun, 25 Apr 2021 21:15:27 +0800 Subject: [PATCH 3/4] fix issue #986 --- src/test/java/spark/WebSocketRequestTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/spark/WebSocketRequestTest.java b/src/test/java/spark/WebSocketRequestTest.java index 74e3416a20..b09511948f 100644 --- a/src/test/java/spark/WebSocketRequestTest.java +++ b/src/test/java/spark/WebSocketRequestTest.java @@ -41,7 +41,7 @@ public static void tearDown() { public static void setup() throws IOException { staticFiles.location("/public/chatPages"); //index.html is served at localhost:4567 (default port) staticFiles.expireTime(600); - webSocket("/chat", spark.test.ChatWebSocketHandler.class); + webSocket("/chat", ChatWebSocketHandler.class); get(HELLO, (req, res) -> "Hello!"); get(OTHER, (req, res) -> "other"); init(); From 0328f67bccef457d9f31f5a899caf2c107c84167 Mon Sep 17 00:00:00 2001 From: nicetryzz <11810115@mail.sustech.edu.cn> Date: Tue, 18 May 2021 18:31:45 +0800 Subject: [PATCH 4/4] change pom --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 70728ade51..777fa0f472 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,16 @@ ${jetty.version} test + + org.json + json + 20160810 + + + com.j2html + j2html + 1.2.0 +