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
+
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..b09511948f
--- /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", 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