diff --git a/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializer.java b/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializer.java index ddd40cc..776c2e1 100644 --- a/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializer.java +++ b/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializer.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,6 +45,8 @@ public abstract class AbstractHttpSwitchingDeserializer implements StatefulDeser protected final ByteArrayCrLfSerializer crlfDeserializer = new ByteArrayCrLfSerializer(); + private static final Pattern requestLinePattern = Pattern.compile("GET *([^ ]+) *HTTP/"); + public void setMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; } @@ -69,14 +73,31 @@ public abstract class AbstractHttpSwitchingDeserializer implements StatefulDeser if (isStreaming == null) { //Consume the headers - TODO - check status StringBuilder headersBuilder = new StringBuilder(); byte[] headers = new byte[this.maxMessageSize]; + String path = null; + String queryString = null; int headersLength; do { headersLength = this.crlfDeserializer.fillToCrLf(inputStream, headers); String header = new String(headers, 0, headersLength, "UTF-8"); + if (path == null) { + if (header.startsWith("GET")) { + Matcher requestLineMatcher = requestLinePattern.matcher(header); + if (requestLineMatcher.find()) { + path = requestLineMatcher.group(1); + if (path.contains("?")) { + int queryStarts = path.indexOf("?"); + queryString = path.substring(queryStarts + 1); + path = path.substring(0, queryStarts); + } + } + } + } headersBuilder.append(header).append("\r\n"); } while (headersLength > 0); BasicState basicState = createState(); + basicState.setPath(path); + basicState.setQueryString(queryString); List dataList = new ArrayList(); List decodedHeaders = decodeHeaders(headersBuilder.toString(), basicState, dataList); this.streamState.put(inputStream, basicState); @@ -119,6 +140,10 @@ public abstract class AbstractHttpSwitchingDeserializer implements StatefulDeser public static class BasicState { + private volatile String path; + + private volatile String queryString; + private volatile DataFrame pendingFrame; private final List fragments = new ArrayList(); @@ -135,11 +160,31 @@ public abstract class AbstractHttpSwitchingDeserializer implements StatefulDeser return fragments; } - @Override - public String toString() { - return "BasicState [pendingFrame=" + pendingFrame + ", fragments.size()=" + fragments.size() + "]"; + public String getPath() { + return path; } + private void setPath(String path) { + this.path = path; + } + + public String getQueryString() { + return queryString; + } + + private void setQueryString(String queryString) { + this.queryString = queryString; + } + + @Override + public String toString() { + return "BasicState [" + + (this.path != null ? ("path=" + this.path) : "") + + (this.queryString != null ? (", queryString=" + this.queryString) : "") + + (this.pendingFrame != null ? (", pendingFrame=" + this.pendingFrame) : "") + + (this.fragments.size() > 0 ? (", fragments.size()=" + this.fragments.size()) : "") + + "]"; + } } diff --git a/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketHeaders.java b/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketHeaders.java new file mode 100644 index 0000000..306d21b --- /dev/null +++ b/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketHeaders.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.x.ip.websocket; + +/** + * @author Gary Russell + * @since 3.0 + * + */ +public class WebSocketHeaders { + + private WebSocketHeaders() {} + + private final static String WS = "websocket_"; + + public final static String PATH = WS + "path"; + + public final static String QUERY_STRING = WS + "queryString"; + +} diff --git a/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketTcpConnectionInterceptorFactory.java b/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketTcpConnectionInterceptorFactory.java index 1d4f1ff..7378f59 100644 --- a/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketTcpConnectionInterceptorFactory.java +++ b/spring-integration-ip-extensions/src/main/java/org/springframework/integration/x/ip/websocket/WebSocketTcpConnectionInterceptorFactory.java @@ -115,8 +115,8 @@ public class WebSocketTcpConnectionInterceptorFactory implements TcpConnectionIn WebSocketState state = (WebSocketState) this.getRequiredDeserializer().getState(inputStream); Assert.notNull(state, "State must not be null:" + message); - if (logger.isDebugEnabled()) { - logger.debug(state); + if (logger.isTraceEnabled()) { + logger.trace(state); } if (payload.getRsv() > 0) { if (logger.isDebugEnabled()) { @@ -184,7 +184,16 @@ public class WebSocketTcpConnectionInterceptorFactory implements TcpConnectionIn } } else if (this.shook) { - return super.onMessage(message); + MessageBuilder messageBuilder = MessageBuilder.fromMessage(message); + // TODO: Move to subclass of TcpMessageMapper when INT-2877 is merged + if (state.getPath() != null) { + messageBuilder.setHeader(WebSocketHeaders.PATH, state.getPath()); + } + if (state.getQueryString() != null) { + messageBuilder.setHeader(WebSocketHeaders.QUERY_STRING, state.getQueryString()); + } + return super.onMessage( + messageBuilder.build()); } else { try { diff --git a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializerTests.java b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializerTests.java new file mode 100644 index 0000000..2d2bd7d --- /dev/null +++ b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/serializer/AbstractHttpSwitchingDeserializerTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.x.ip.serializer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.junit.Test; +import org.springframework.integration.x.ip.serializer.AbstractHttpSwitchingDeserializer.BasicState; + +/** + * @author Gary Russell + * @since 3.0 + * + */ +public class AbstractHttpSwitchingDeserializerTests { + + private final AbstractHttpSwitchingDeserializer deserializer = new AbstractHttpSwitchingDeserializer() { + @Override + public DataFrame deserialize(InputStream inputStream) throws IOException { + return checkStreaming(inputStream).get(0); + } + }; + + @Test + public void testPathNoQuery() throws Exception { + + String simplePath = "GET /foo HTTP/1.1\r\n\r\n"; + InputStream stream = new ByteArrayInputStream(simplePath.getBytes()); + + DataFrame frame = deserializer.deserialize(stream); + assertEquals(DataFrame.TYPE_HEADERS, frame.getType()); + + BasicState state = deserializer.getState(stream); + assertNotNull(state); + assertEquals("/foo", state.getPath()); + assertNull(state.getQueryString()); + } + + @Test + public void testPathAndQuery() throws Exception { + + String simplePath = "GET /foo?bar HTTP/1.1\r\n\r\n"; + InputStream stream = new ByteArrayInputStream(simplePath.getBytes()); + + DataFrame frame = deserializer.deserialize(stream); + assertEquals(DataFrame.TYPE_HEADERS, frame.getType()); + + BasicState state = deserializer.getState(stream); + assertNotNull(state); + assertEquals("/foo", state.getPath()); + assertEquals("bar", state.getQueryString()); + } + + @Test + public void testPathEmptyQuery() throws Exception { + + String simplePath = "GET /foo? HTTP/1.1\r\n\r\n"; + InputStream stream = new ByteArrayInputStream(simplePath.getBytes()); + + DataFrame frame = deserializer.deserialize(stream); + assertEquals(DataFrame.TYPE_HEADERS, frame.getType()); + + BasicState state = deserializer.getState(stream); + assertNotNull(state); + assertEquals("/foo", state.getPath()); + assertEquals("", state.getQueryString()); + } + +} diff --git a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/Autobahn-context.xml b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/Autobahn-context.xml similarity index 98% rename from spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/Autobahn-context.xml rename to spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/Autobahn-context.xml index b3b8f06..847549a 100644 --- a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/Autobahn-context.xml +++ b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/Autobahn-context.xml @@ -60,7 +60,7 @@ - + diff --git a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/AutobahnTests.java b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/AutobahnTests.java similarity index 94% rename from spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/AutobahnTests.java rename to spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/AutobahnTests.java index 51f3aaa..d91bc47 100644 --- a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/AutobahnTests.java +++ b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/AutobahnTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.integration.x.ip.sockjs; +package org.springframework.integration.x.ip.websocket; import org.springframework.context.support.ClassPathXmlApplicationContext; diff --git a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/WebSocketServerTests-context.xml b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/WebSocketServerTests-context.xml similarity index 98% rename from spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/WebSocketServerTests-context.xml rename to spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/WebSocketServerTests-context.xml index 70155bc..efa6f27 100644 --- a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/WebSocketServerTests-context.xml +++ b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/WebSocketServerTests-context.xml @@ -29,7 +29,7 @@ - + diff --git a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/WebSocketServerTests.java b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/WebSocketServerTests.java similarity index 98% rename from spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/WebSocketServerTests.java rename to spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/WebSocketServerTests.java index ab1a2ef..9d1f553 100644 --- a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/WebSocketServerTests.java +++ b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/WebSocketServerTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.integration.x.ip.sockjs; +package org.springframework.integration.x.ip.websocket; import java.util.ArrayList; import java.util.HashMap; diff --git a/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/ws.html b/spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/ws.html similarity index 100% rename from spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/sockjs/ws.html rename to spring-integration-ip-extensions/src/test/java/org/springframework/integration/x/ip/websocket/ws.html