SockJS Support for Netty4 #1615

Closed
wants to merge 71 commits into
from

Projects

None yet
@danbev
Member
danbev commented Jul 19, 2013

Please see sockjs/README.md for instructions about running tests general information regarding the SockJS support.

@ghost
ghost commented Jul 19, 2013

Build result for #1615 at 8388bc4703902a32222b3ed4fe885ae2eec4df75: Success

@normanmaurer normanmaurer was assigned Jul 19, 2013
@normanmaurer
Member

@danbev YAY ... Let me review it... Thanks also for signing the CLA :)

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...c/main/java/io/netty/handler/codec/sockjs/Config.java
+ /**
+ * The prefix/name, of the SockJS service.
+ * For example, in the url "http://localhost/echo/111/12345/xhr", 'echo' is the prefix.
+ *
+ * @return {@code String} the prefix/name of the SockJS service.
+ */
+ public String prefix() {
+ return prefix;
+ }
+
+ /**
+ * Determines whether WebSocket support will not be enabled.
+ *
+ * @return {@code true} if WebSocket support is enabled.
+ */
+ public boolean websocketEnabled() {
@normanmaurer
normanmaurer Jul 19, 2013 Member

isWebsocketEnabled()

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...c/main/java/io/netty/handler/codec/sockjs/Config.java
+ if (iterator.hasNext()) {
+ sb.append(iterator.next());
+ while (iterator.hasNext()) {
+ sb.append(",").append(iterator.next());
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Determines if a {@code JSESSIONID} cookie will be set. This is used by some
+ * load balancers to enable session stickyness.
+ *
+ * @return {@code true} if a {@code JSESSIONID} cookie should be set.
+ */
+ public boolean cookiesNeeded() {
@normanmaurer
normanmaurer Jul 19, 2013 Member

areCookiesNeeded()

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...c/main/java/io/netty/handler/codec/sockjs/Config.java
+ * responseText in the XHR Object will not grow and be come an issue for the client. Instead,
+ * by forcing a reconnect the client will create a new XHR object and this can be see as a
+ * form of garbage collection.
+ *
+ * @return {@code int} the max number of bytes that can be written. Default is 131072.
+ */
+ public int maxStreamingBytesSize() {
+ return maxStreamingBytesSize;
+ }
+
+ /**
+ * Determines whether transport layer security (TLS) should be used.
+ *
+ * @return {@code true} if transport layer security should be used.
+ */
+ public boolean tls() {
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...c/main/java/io/netty/handler/codec/sockjs/Config.java
+ */
+ public Builder maxStreamingBytesSize(final int max) {
+ this.maxStreamingBytesSize = max;
+ return this;
+ }
+
+ /**
+ * Determines whether transport layer security (TLS) should be used.
+ * @param tsl if transport layer security should be used.
+ */
+ public Builder tls(final boolean tls) {
+ this.tls = tls;
+ return this;
+ }
+
+ public Config build() {
@normanmaurer
normanmaurer Jul 19, 2013 Member

javadocs please :)

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...handler/codec/sockjs/handlers/CorsInboundHandler.java
+ headers.set(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type");
+ headers.set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ headers.set(EXPIRES, "dummy");
+ headers.set(SET_COOKIE, Transports.DEFAULT_COOKIE);
+ ctx.writeAndFlush(response);
+ }
+
+ private boolean isPollingTransport(final String uri) {
+ return uri.contains(Transports.Types.XHR.path());
+ }
+
+ private boolean isPreflightRequest(final HttpRequest request) {
+ return request.getMethod().equals(HttpMethod.OPTIONS);
+ }
+
+ private CorsMetadata extractCorsMetadata(final HttpRequest request) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

could be static

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...handler/codec/sockjs/handlers/CorsInboundHandler.java
+ headers.set(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, POST");
+ } else {
+ headers.set(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, GET");
+ }
+ headers.set(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type");
+ headers.set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ headers.set(EXPIRES, "dummy");
+ headers.set(SET_COOKIE, Transports.DEFAULT_COOKIE);
+ ctx.writeAndFlush(response);
+ }
+
+ private boolean isPollingTransport(final String uri) {
+ return uri.contains(Transports.Types.XHR.path());
+ }
+
+ private boolean isPreflightRequest(final HttpRequest request) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

could be static

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...handler/codec/sockjs/handlers/CorsInboundHandler.java
+ headers.set(ACCESS_CONTROL_ALLOW_ORIGIN, md.origin());
+ headers.set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ headers.set(ACCESS_CONTROL_MAX_AGE, "31536000");
+ if (isPollingTransport(request.getUri())) {
+ headers.set(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, POST");
+ } else {
+ headers.set(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, GET");
+ }
+ headers.set(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type");
+ headers.set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ headers.set(EXPIRES, "dummy");
+ headers.set(SET_COOKIE, Transports.DEFAULT_COOKIE);
+ ctx.writeAndFlush(response);
+ }
+
+ private boolean isPollingTransport(final String uri) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

could be static

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...netty/handler/codec/sockjs/handlers/CorsMetadata.java
+ *
+ * The Netty Project licenses this file to you 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 io.netty.handler.codec.sockjs.handlers;
+
+public class CorsMetadata {
@normanmaurer
normanmaurer Jul 19, 2013 Member

Please ad javadocs and may be final ?

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/handlers/PollingSessionState.java
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(PollingSessionState.class);
+ private final SockJSSession session;
+ private final ConcurrentMap<String, SockJSSession> sessions;
+ private ScheduledFuture<?> sessionTimer;
+ private ScheduledFuture<?> heartbeatFuture;
+
+ public PollingSessionState(final SockJSSession session, final ConcurrentMap<String, SockJSSession> sessions) {
+ ArgumentUtil.checkNotNull(session, "session");
+ ArgumentUtil.checkNotNull(sessions, "sessions");
+ this.session = session;
+ this.sessions = sessions;
+ }
+
+ @Override
+ public void onConnect(final SessionContext s, final ChannelHandlerContext ctx) {
+ synchronized (session) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

synchronized blocks always make me cry ;) Can't we at least reduce the scope of it a bit if not remove it completely?

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/handlers/PollingSessionState.java
+ @Override
+ public void onConnect(final SessionContext s, final ChannelHandlerContext ctx) {
+ synchronized (session) {
+ session.setContext(ctx);
+ session.setState(States.OPEN);
+ session.onOpen(s);
+ startSessionTimer(ctx);
+ startHeartbeatTimer(ctx);
+ }
+ }
+
+ @Override
+ public void onOpen(final ChannelHandlerContext ctx) {
+ if (isInuse()) {
+ logger.debug("Another connection still in open for [" + session.sessionId() + "]");
+ final CloseFrame closeFrame = new CloseFrame(2010, "Another connection still open");
@normanmaurer
normanmaurer Jul 19, 2013 Member

declare it as static final and so reduce GC pressure..

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/handlers/PollingSessionState.java
+ if (isInuse()) {
+ return;
+ }
+ if (session.timestamp() + session.config().sessionTimeout() < now) {
+ final SockJSSession removed = sessions.remove(session.sessionId());
+ session.context().close();
+ sessionTimer.cancel(true);
+ heartbeatFuture.cancel(true);
+ logger.debug("Removed " + removed.sessionId() + " from map[" + sessions.size() + "]");
+ }
+ }
+ }, session.config().sessionTimeout(), session.config().sessionTimeout(), TimeUnit.MILLISECONDS);
+ }
+ } catch (final UnsupportedOperationException e) {
+ logger.debug("Caught: " + e);
+ // ignoring this exception which needs to be fixed.
@normanmaurer
normanmaurer Jul 19, 2013 Member

We should either fix this or you should put some workaround in your test-code..

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/handlers/PollingSessionState.java
+
+ private void startHeartbeatTimer(final ChannelHandlerContext ctx) {
+ try {
+ heartbeatFuture = ctx.executor().scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
+ logger.debug("Sending heartbeat for " + session);
+ ctx.channel().writeAndFlush(new HeartbeatFrame());
+ }
+ }
+ },
+ session.config().heartbeatInterval(),
+ session.config().heartbeatInterval(),
+ TimeUnit.MILLISECONDS);
+ } catch (final UnsupportedOperationException e) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

Same as above

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/handlers/SendingSessionState.java
+ }
+
+ @Override
+ public void onConnect(final SessionContext s, final ChannelHandlerContext ctx) {
+ synchronized (session) {
+ session.setContext(ctx);
+ session.setState(States.OPEN);
+ session.onOpen(s);
+ }
+ }
+
+ @Override
+ public void onOpen(final ChannelHandlerContext ctx) {
+ if (isInuse()) {
+ logger.debug("Another connection still in open for [" + session.sessionId() + "]");
+ final CloseFrame closeFrame = new CloseFrame(2010, "Another connection still open");
@normanmaurer
normanmaurer Jul 19, 2013 Member

declare as static final field and so reduce GC pressure.

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...tty/handler/codec/sockjs/handlers/SessionHandler.java
+
+ private final SessionState sessionState;
+ private ChannelHandlerContext currentContext;
+
+ public SessionHandler(final SessionState sessionState) {
+ ArgumentUtil.checkNotNull(sessionState, "sessionState");
+ this.sessionState = sessionState;
+ }
+
+ @Override
+ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+ if (msg instanceof HttpRequest) {
+ handleSession(ctx);
+ } else if (msg instanceof String) {
+ handleMessage((String) msg);
+ }
@normanmaurer
normanmaurer Jul 19, 2013 Member

throw some exception if an other type is read or forward it to the next handler

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...tty/handler/codec/sockjs/handlers/SessionHandler.java
+ sessionState.addMessage(message);
+ }
+ }
+
+ private Channel getActiveChannel() {
+ final Channel sessionChannel = sessionState.getSessionChannelHandlerContext().channel();
+ return sessionChannel.isActive() && sessionChannel.isRegistered() ? sessionChannel : currentContext.channel();
+ }
+
+ @Override
+ public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
+ sessionState.onRequestCompleted(ctx);
+ ctx.fireChannelInactive();
+ }
+
+ private boolean isWritable(final Channel channel) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

can be static

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...netty/handler/codec/sockjs/handlers/SessionState.java
+ /**
+ * Called when a message is to be processed by the underlying SockJS service
+ */
+ void onMessage(final String message) throws Exception;
+
+ /**
+ * Called when a message is sent to the session.
+ */
+ void addMessage(final String message);
+
+ /**
+ * Indicates if this session is in use.
+ *
+ * @return {@code true} if the session is in use.
+ */
+ boolean isInuse();
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...etty/handler/codec/sockjs/handlers/SockJSHandler.java
+ SockJSSession session = sessions.get(sessionId);
+ if (session == null) {
+ final SockJSSession newSession = new SockJSSession(sessionId, factory.create());
+ session = sessions.putIfAbsent(sessionId, newSession);
+ if (session == null) {
+ session = newSession;
+ }
+ logger.debug("Created new session [" + sessionId + "]");
+ } else {
+ logger.debug("Using existing session {" + sessionId + "]");
+ }
+ return session;
+ }
+
+ private void writeNotFoundResponse(final HttpRequest request, final ChannelHandlerContext ctx) {
+ final FullHttpResponse response = httpResponse(request, HttpResponseStatus.NOT_FOUND);
@normanmaurer
normanmaurer Jul 19, 2013 Member

Directly pass Unpooled.copiedBuffer(...) in the constructor to reduce byte copies

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...etty/handler/codec/sockjs/handlers/SockJSHandler.java
+import io.netty.handler.codec.sockjs.transports.WebSocketTransport;
+import io.netty.handler.codec.sockjs.transports.XhrPollingTransport;
+import io.netty.handler.codec.sockjs.transports.XhrSendTransport;
+import io.netty.handler.codec.sockjs.transports.XhrStreamingTransport;
+import io.netty.util.CharsetUtil;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SockJSHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@normanmaurer
normanmaurer Jul 19, 2013 Member

javadocs please :)

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...etty/handler/codec/sockjs/handlers/SockJSHandler.java
+
+ public SessionNotFoundException(final String sessionId, final HttpRequest request) {
+ this.sessionId = sessionId;
+ this.request = request;
+ }
+
+ public String sessionId() {
+ return sessionId;
+ }
+
+ public HttpRequest httpRequest() {
+ return request;
+ }
+ }
+
+ public interface PathParams {
@normanmaurer
normanmaurer Jul 19, 2013 Member

Javadocs please

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...etty/handler/codec/sockjs/handlers/SockJSSession.java
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+class SockJSSession {
+
+ private SessionState.States state = SessionState.States.CONNECTING;
+ private final String sessionId;
+ private final SockJSService service;
+ private final LinkedList<String> messages = new LinkedList<String>();
+ private final AtomicLong timestamp = new AtomicLong();
+ private final AtomicBoolean inuse = new AtomicBoolean();
+ private ChannelHandlerContext ctx;
+
@normanmaurer
normanmaurer Jul 19, 2013 Member

Can't you use AtomicReferenceFieldUpdater to remove synchronized blocks... This is way faster

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...dler/codec/sockjs/handlers/StreamingSessionState.java
+ final long now = System.currentTimeMillis();
+ if (isInuse()) {
+ return;
+ }
+ if (session.timestamp() + session.config().sessionTimeout() < now) {
+ final SockJSSession removed = sessions.remove(session.sessionId());
+ session.context().close();
+ sessionTimer.cancel(true);
+ heartbeatFuture.cancel(true);
+ logger.debug("Removed " + removed.sessionId() + " from map[" + sessions.size() + "]");
+ }
+ }
+ }, session.config().sessionTimeout(), session.config().sessionTimeout(), TimeUnit.MILLISECONDS);
+ }
+ } catch (final UnsupportedOperationException e) {
+ logger.debug("Caught: " + e);
@normanmaurer
normanmaurer Jul 19, 2013 Member

Needs to get fixed

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...dler/codec/sockjs/handlers/StreamingSessionState.java
+ private void startHeartbeatTimer(final ChannelHandlerContext ctx) {
+ try {
+ heartbeatFuture = ctx.executor().scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
+ logger.debug("Sending heartbeat for " + session);
+ ctx.channel().writeAndFlush(new HeartbeatFrame());
+ }
+ }
+ },
+ session.config().heartbeatInterval(),
+ session.config().heartbeatInterval(),
+ TimeUnit.MILLISECONDS);
+ } catch (final UnsupportedOperationException e) {
+ logger.debug("Caught: " + e);
@normanmaurer
normanmaurer Jul 19, 2013 Member

Needs to get fixed

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...o/netty/handler/codec/sockjs/protocol/CloseFrame.java
+
+/**
+ * This frame is sent from the server if the client requests data from a closed connection.
+ */
+public class CloseFrame implements Frame {
+
+ private final int statusCode;
+ private final String statusMsg;
+
+ public CloseFrame(final int statusCode, final String statusMsg) {
+ ArgumentUtil.checkNotNull(statusMsg, "statusMsg");
+ this.statusCode = statusCode;
+ this.statusMsg = statusMsg;
+ }
+
+ public int statusCode() {
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...o/netty/handler/codec/sockjs/protocol/CloseFrame.java
+ * 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 io.netty.handler.codec.sockjs.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.sockjs.util.ArgumentUtil;
+import io.netty.util.CharsetUtil;
+
+/**
+ * This frame is sent from the server if the client requests data from a closed connection.
+ */
+public class CloseFrame implements Frame {
@normanmaurer
normanmaurer Jul 19, 2013 Member

Should extend DefaultByteBufHolder

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...ava/io/netty/handler/codec/sockjs/protocol/Frame.java
+ * 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 io.netty.handler.codec.sockjs.protocol;
+
+import io.netty.buffer.ByteBuf;
+
+public interface Frame {
@normanmaurer
normanmaurer Jul 19, 2013 Member

Should extend ByteBufHolder

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...o/netty/handler/codec/sockjs/protocol/CloseFrame.java
+ ArgumentUtil.checkNotNull(statusMsg, "statusMsg");
+ this.statusCode = statusCode;
+ this.statusMsg = statusMsg;
+ }
+
+ public int statusCode() {
+ return statusCode;
+ }
+
+ public String statusMsg() {
+ return statusMsg;
+ }
+
+ @Override
+ public ByteBuf content() {
+ return Unpooled.copiedBuffer("c[" + statusCode + ",\"" + statusMsg + "\"]", CharsetUtil.UTF_8);
@normanmaurer
normanmaurer Jul 19, 2013 Member

don't copy on every access... create ByteBuf on the start, this is to expensive

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
.../io/netty/handler/codec/sockjs/protocol/Greeting.java
+
+public final class Greeting {
+
+ private static final String GREETING = "Welcome to SockJS!";
+
+ private Greeting() {
+ }
+
+ public static boolean matches(final String path) {
+ return path.equals("") || path.equals("/");
+ }
+
+ public static FullHttpResponse response(final HttpRequest request) throws Exception {
+ final FullHttpResponse response = new DefaultFullHttpResponse(request.getProtocolVersion(), OK);
+ response.headers().set(CONTENT_TYPE, Transports.CONTENT_TYPE_PLAIN);
+ response.content().writeBytes(Unpooled.copiedBuffer(GREETING + "\n", CharsetUtil.UTF_8));
@normanmaurer
normanmaurer Jul 19, 2013 Member

Specify the content directly in the constructor of DefaultFullHttpResponse to save byte copies

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...tty/handler/codec/sockjs/protocol/HeartbeatFrame.java
+package io.netty.handler.codec.sockjs.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.CharsetUtil;
+
+/**
+ * A heartbeat frame is sent by the server to keep the connection from breaking.
+ */
+public class HeartbeatFrame implements Frame {
+
+ public static final String HEARTBEAT = "h";
+
+ @Override
+ public ByteBuf content() {
+ return Unpooled.copiedBuffer(HEARTBEAT, CharsetUtil.UTF_8);
@normanmaurer
normanmaurer Jul 19, 2013 Member

Again not create on every call..

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...netty/handler/codec/sockjs/protocol/MessageFrame.java
+
+ private final List<String> messages;
+ private final ByteBuf content;
+
+ public MessageFrame(final String message) {
+ this(new String[] {message});
+ }
+
+ public MessageFrame(final String... messages) {
+ ArgumentUtil.checkNotNull(messages, "messages");
+ this.messages = new ArrayList<String>(Arrays.asList(messages));
+ this.content = generateContent(this.messages);
+ }
+
+ public List<String> messages() {
+ return Collections.unmodifiableList(messages);
@normanmaurer
normanmaurer Jul 19, 2013 Member

wrap on construction to safe instance of class and so GC-pressure

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...netty/handler/codec/sockjs/protocol/MessageFrame.java
+ final JsonStringEncoder jsonEndocder = new JsonStringEncoder();
+ final ByteBuf content = Unpooled.buffer();
+ content.writeByte('a').writeByte('[');
+ for (int i = 0; i < size; i++) {
+ content.writeByte('"');
+ final String escaped = escapeCharacters(jsonEndocder.quoteAsString(messages.get(i)));
+ content.writeBytes(Unpooled.copiedBuffer(escaped, CharsetUtil.UTF_8)).writeByte('"');
+ if (i < size - 1) {
+ content.writeByte(',');
+ }
+ }
+ return content.writeByte(']');
+ }
+
+ private static String escapeCharacters(final char[] value) {
+ final StringBuilder sb = new StringBuilder();
@normanmaurer
normanmaurer Jul 19, 2013 Member

use new StringBuilder(value.length()) ?

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...io/netty/handler/codec/sockjs/protocol/OpenFrame.java
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.CharsetUtil;
+
+/**
+ * Everytime a new session is estabilshed with the server the server must sent an OpenFrame in accordance with
+ * <a href="http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-42">Protocol and Framing</a>.
+ */
+public class OpenFrame implements Frame {
+
+ public static final String OPEN = "o";
+
+ @Override
+ public ByteBuf content() {
+ return Unpooled.copiedBuffer(OPEN, CharsetUtil.UTF_8);
@normanmaurer
normanmaurer Jul 19, 2013 Member

Same as before.. not copy every time

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...netty/handler/codec/sockjs/protocol/PreludeFrame.java
+package io.netty.handler.codec.sockjs.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+/**
+ * A PreludeFrame the first message sent by the
+ * <a href="http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-85">xhr-streaming</a> protocol.
+ */
+public class PreludeFrame implements Frame {
+
+ public static final int CONTENT_SIZE = 2048;
+
+ @Override
+ public ByteBuf content() {
+ final ByteBuf buf = Unpooled.buffer(CONTENT_SIZE);
@normanmaurer
normanmaurer Jul 19, 2013 Member

same as above

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/AbstractSendTransport.java
+ ctx.writeAndFlush(internalServerErrorResponse(request.getProtocolVersion(), "Payload expected."))
+ .addListener(ChannelFutureListener.CLOSE);
+ } else {
+ try {
+ final String[] messages = JsonUtil.decode(content);
+ for (String message : messages) {
+ ctx.fireChannelRead(message);
+ }
+ respond(ctx, request);
+ } catch (final JsonParseException e) {
+ ctx.writeAndFlush(internalServerErrorResponse(request.getProtocolVersion(), "Broken JSON encoding."));
+ }
+ }
+ }
+
+ public abstract void respond(final ChannelHandlerContext ctx, final FullHttpRequest request) throws Exception;
@normanmaurer
normanmaurer Jul 19, 2013 Member

javadocs please :)

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/AbstractSendTransport.java
+ } else {
+ return "";
+ }
+ }
+ return request.content().toString(CharsetUtil.UTF_8);
+ }
+
+ private String getContentType(final FullHttpRequest request) {
+ final String contentType = request.headers().get(CONTENT_TYPE);
+ if (contentType == null) {
+ return Transports.CONTENT_TYPE_PLAIN;
+ }
+ return contentType;
+ }
+
+ private List<String> getDataFormParameter(final FullHttpRequest request) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

can be static

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/AbstractSendTransport.java
+ }
+
+ private String getContent(final FullHttpRequest request) {
+ final String contentType = getContentType(request);
+ if (Transports.CONTENT_TYPE_FORM.equals(contentType)) {
+ final List<String> data = getDataFormParameter(request);
+ if (data != null) {
+ return data.get(0);
+ } else {
+ return "";
+ }
+ }
+ return request.content().toString(CharsetUtil.UTF_8);
+ }
+
+ private String getContentType(final FullHttpRequest request) {
@normanmaurer
normanmaurer Jul 19, 2013 Member

can be static

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...ler/codec/sockjs/transports/EventSourceTransport.java
+import io.netty.handler.codec.http.DefaultHttpResponse;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.handler.codec.sockjs.Config;
+import io.netty.handler.codec.sockjs.protocol.Frame;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class EventSourceTransport extends ChannelOutboundHandlerAdapter {
@normanmaurer
normanmaurer Jul 19, 2013 Member

javadocs please

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...ler/codec/sockjs/transports/EventSourceTransport.java
+ public EventSourceTransport(final Config config, final HttpRequest request) {
+ this.config = config;
+ this.request = request;
+ }
+
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ if (headerSent.compareAndSet(false, true)) {
+ ctx.writeAndFlush(createResponse(CONTENT_TYPE_EVENT_STREAM), promise);
+ ctx.writeAndFlush(new DefaultHttpContent(CRLF.duplicate()));
+ }
+
+ final ByteBuf data = Unpooled.buffer();
@normanmaurer
normanmaurer Jul 19, 2013 Member

use ctx.alloc().buffer()

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+ @Override
+ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+ if (msg instanceof HttpRequest) {
+ final String c = getCallbackFromRequest((HttpRequest) msg);
+ if (c.length() == 0) {
+ respondCallbackRequired(ctx);
+ ctx.fireUserEventTriggered(Events.CLOSE_SESSION);
+ return;
+ } else {
+ callback = c;
+ }
+ }
+ ctx.fireChannelRead(msg);
+ }
+
+ public String getCallbackFromRequest(final HttpRequest request) {
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+ }
+
+ public String getCallbackFromRequest(final HttpRequest request) {
+ final QueryStringDecoder qsd = new QueryStringDecoder(request.getUri());
+ final List<String> c = qsd.parameters().get("c");
+ return c == null || c.isEmpty() ? "" : c.get(0);
+ }
+
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ if (headerSent.compareAndSet(false, true)) {
+ final HttpResponse response = createResponse(Transports.CONTENT_TYPE_HTML);
+ final ByteBuf header = Unpooled.buffer();
@normanmaurer
normanmaurer Jul 19, 2013 Member

Use ctx.alloc().buffer()

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ if (headerSent.compareAndSet(false, true)) {
+ final HttpResponse response = createResponse(Transports.CONTENT_TYPE_HTML);
+ final ByteBuf header = Unpooled.buffer();
+ header.writeBytes(HEADER_PART1.duplicate());
+ header.writeBytes(copiedBuffer(callback, UTF_8));
+ header.writeBytes(HEADER_PART2.duplicate());
+ final int spaces = 1024 * header.readableBytes();
+ final ByteBuf paddedBuffer = Unpooled.buffer(1024 + 50);
+ paddedBuffer.writeBytes(header);
+ for (int s = 0; s < spaces + 20; s++) {
+ paddedBuffer.writeByte(' ');
+ }
+ paddedBuffer.writeBytes(END_HEADER.duplicate());
+ ctx.writeAndFlush(response, promise);
@normanmaurer
normanmaurer Jul 19, 2013 Member

use only write(..) will get flushed in the next line anyway

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+ return c == null || c.isEmpty() ? "" : c.get(0);
+ }
+
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ if (headerSent.compareAndSet(false, true)) {
+ final HttpResponse response = createResponse(Transports.CONTENT_TYPE_HTML);
+ final ByteBuf header = Unpooled.buffer();
+ header.writeBytes(HEADER_PART1.duplicate());
+ header.writeBytes(copiedBuffer(callback, UTF_8));
+ header.writeBytes(HEADER_PART2.duplicate());
+ final int spaces = 1024 * header.readableBytes();
+ final ByteBuf paddedBuffer = Unpooled.buffer(1024 + 50);
@normanmaurer
normanmaurer Jul 19, 2013 Member

use ctx.alloc().buffer(...)

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+ final ByteBuf paddedBuffer = Unpooled.buffer(1024 + 50);
+ paddedBuffer.writeBytes(header);
+ for (int s = 0; s < spaces + 20; s++) {
+ paddedBuffer.writeByte(' ');
+ }
+ paddedBuffer.writeBytes(END_HEADER.duplicate());
+ ctx.writeAndFlush(response, promise);
+ ctx.writeAndFlush(new DefaultHttpContent(paddedBuffer));
+ }
+
+ final ByteBuf data = Unpooled.buffer();
+ data.writeBytes(PREFIX.duplicate());
+ data.writeBytes(Transports.escapeJson(frame.content(), data));
+ data.writeBytes(POSTFIX.duplicate());
+ final int dataSize = data.readableBytes();
+ ctx.writeAndFlush(new DefaultHttpContent(data));
@normanmaurer
normanmaurer Jul 19, 2013 Member

use write(..) and then only flush if the next if block not hit.

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+ final ByteBuf header = Unpooled.buffer();
+ header.writeBytes(HEADER_PART1.duplicate());
+ header.writeBytes(copiedBuffer(callback, UTF_8));
+ header.writeBytes(HEADER_PART2.duplicate());
+ final int spaces = 1024 * header.readableBytes();
+ final ByteBuf paddedBuffer = Unpooled.buffer(1024 + 50);
+ paddedBuffer.writeBytes(header);
+ for (int s = 0; s < spaces + 20; s++) {
+ paddedBuffer.writeByte(' ');
+ }
+ paddedBuffer.writeBytes(END_HEADER.duplicate());
+ ctx.writeAndFlush(response, promise);
+ ctx.writeAndFlush(new DefaultHttpContent(paddedBuffer));
+ }
+
+ final ByteBuf data = Unpooled.buffer();
@normanmaurer
normanmaurer Jul 19, 2013 Member

ctx.alloc().buffer()

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...andler/codec/sockjs/transports/HtmlFileTransport.java
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A streaming transport for SockJS.
+ *
+ * This transport is intended to be used in an iframe, where the src of
+ * the iframe will have the an url looking something like this:
+ *
+ * http://server/echo/serverId/sessionId/htmlfile?c=callback
+ * The server will respond with a html snipped containing a html header
+ * and a script element. When data is available on the server this classes
+ * write method will write a script to the connection that will invoke the
+ * callback.
+ */
+public class HtmlFileTransport extends ChannelOutboundHandlerAdapter implements ChannelInboundHandler {
@normanmaurer
normanmaurer Jul 19, 2013 Member

extend ChannelDuplexHandler

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
.../io/netty/handler/codec/sockjs/transports/Iframe.java
+ return response;
+ }
+
+ if (request.headers().contains(HttpHeaders.Names.IF_NONE_MATCH)) {
+ final FullHttpResponse response = createResponse(request, HttpResponseStatus.NOT_MODIFIED);
+ response.headers().set(HttpHeaders.Names.SET_COOKIE, "JSESSIONID=dummy; path=/");
+ return response;
+ } else {
+ final FullHttpResponse response = createResponse(request, HttpResponseStatus.OK);
+ response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
+ response.headers().set(HttpHeaders.Names.CACHE_CONTROL, "max-age=31536000, public");
+ response.headers().set(HttpHeaders.Names.EXPIRES, generateExpires());
+ final String content = createContent(config.sockjsUrl());
+ final String etag = "\"" + generateMd5(content) + "\"";
+ response.headers().set(HttpHeaders.Names.ETAG, etag);
+ response.content().writeBytes(Unpooled.copiedBuffer(content, CharsetUtil.UTF_8));
@normanmaurer
normanmaurer Jul 19, 2013 Member

maybe you could let the createResponse(..) method let take the buffer directly and save byte copies

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
.../io/netty/handler/codec/sockjs/transports/Iframe.java
+ } else {
+ final FullHttpResponse response = createResponse(request, HttpResponseStatus.OK);
+ response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
+ response.headers().set(HttpHeaders.Names.CACHE_CONTROL, "max-age=31536000, public");
+ response.headers().set(HttpHeaders.Names.EXPIRES, generateExpires());
+ final String content = createContent(config.sockjsUrl());
+ final String etag = "\"" + generateMd5(content) + "\"";
+ response.headers().set(HttpHeaders.Names.ETAG, etag);
+ response.content().writeBytes(Unpooled.copiedBuffer(content, CharsetUtil.UTF_8));
+ return response;
+ }
+ }
+
+ private static String generateExpires() {
+ final long oneYear = 365 * 24 * 60 * 60 * 1000;
+ final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
@normanmaurer
normanmaurer Jul 19, 2013 Member

Store SimpleDateFormat in a ThreadLocal, no need to create a new instance all the time

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
.../io/netty/handler/codec/sockjs/transports/Iframe.java
+ " <script>\n" +
+ " document.domain = document.domain;\n" +
+ " _sockjs_onload = function(){SockJS.bootstrap_iframe();};\n" +
+ " </script>\n" +
+ " <script src=\"" + url + "\"></script>\n" +
+ "</head>\n" +
+ "<body>\n" +
+ " <h2>Don't panic!</h2>\n" +
+ " <p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>\n" +
+ "</body>\n" +
+ "</html>";
+ }
+
+ private static String generateMd5(final String value) throws Exception {
+ final byte[] bytesToBeEncrypted = value.getBytes(CharsetUtil.UTF_8);
+ final MessageDigest md = MessageDigest.getInstance("MD5");
@normanmaurer
normanmaurer Jul 19, 2013 Member

Store MessageDigest in a ThreadLocal ... no need for a new one everytime

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/JsonpPollingTransport.java
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import io.netty.handler.codec.sockjs.Config;
+import io.netty.handler.codec.sockjs.handlers.SessionHandler.Events;
+import io.netty.handler.codec.sockjs.protocol.Frame;
+import io.netty.util.CharsetUtil;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+import java.util.List;
+
+public class JsonpPollingTransport extends ChannelOutboundHandlerAdapter implements ChannelInboundHandler {
@normanmaurer
normanmaurer Jul 19, 2013 Member

extend ChannelDuplexHandler

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/JsonpPollingTransport.java
+ } else {
+ callback = c.get(0);
+ }
+ }
+ ctx.fireChannelRead(msg);
+ }
+
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ final FullHttpResponse response = new DefaultFullHttpResponse(request.getProtocolVersion(), OK);
+ final ByteBuf content = frame.content();
+
+ final ByteBuf buffer = Unpooled.buffer();
@normanmaurer
normanmaurer Jul 19, 2013 Member

ctx.alloc().buffer()

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/JsonpPollingTransport.java
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ final FullHttpResponse response = new DefaultFullHttpResponse(request.getProtocolVersion(), OK);
+ final ByteBuf content = frame.content();
+
+ final ByteBuf buffer = Unpooled.buffer();
+ Transports.escapeJson(content, buffer);
+ final String function = callback + "(\"" + buffer.toString(CharsetUtil.UTF_8) + "\");\r\n";
+
+ final ByteBuf responseContent = Unpooled.copiedBuffer(function, CharsetUtil.UTF_8);
+ response.headers().set(CONTENT_TYPE, Transports.CONTENT_TYPE_JAVASCRIPT);
+ response.headers().set(CONTENT_LENGTH, responseContent.readableBytes());
+ response.content().writeBytes(responseContent);
@normanmaurer
normanmaurer Jul 19, 2013 Member

pass the responseContent directly in the constructor of DefaultFullHttpResponse

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/RawWebSocketTransport.java
+ }
+ });
+ }
+ }
+
+ private static String getWebSocketLocation(final boolean tls, final FullHttpRequest req, final String path) {
+ final String protocol = tls ? "wss://" : "ws://";
+ return protocol + req.headers().get(HttpHeaders.Names.HOST) + path;
+ }
+
+ private void handleWebSocketFrame(final ChannelHandlerContext ctx, final WebSocketFrame wsFrame) throws Exception {
+ if (wsFrame instanceof CloseWebSocketFrame) {
+ wsFrame.retain();
+ service.onClose();
+ handshaker.close(ctx.channel(), (CloseWebSocketFrame) wsFrame);
+ ctx.close();
@normanmaurer
normanmaurer Jul 19, 2013 Member

shouldn't the handshaker close the channel anyway ?

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...netty/handler/codec/sockjs/transports/Transports.java
+import io.netty.handler.codec.http.Cookie;
+import io.netty.handler.codec.http.CookieDecoder;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.ServerCookieEncoder;
+import io.netty.handler.codec.sockjs.Config;
+import io.netty.util.CharsetUtil;
+
+import java.util.Set;
+
+public final class Transports {
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...dec/sockjs/transports/WebSocketHAProxyHandshaker.java
+ public void operationComplete(ChannelFuture future) throws Exception {
+ if (future.isSuccess()) {
+ ChannelPipeline p = future.channel().pipeline();
+ p.addFirst(newWebsocketDecoder());
+ p.addFirst(newWebSocketEncoder());
+ } else {
+ logger.info("Write failed: ", future.cause());
+ }
+ }
+ });
+ }
+
+ private static byte[] md5(final byte[] data) {
+ try {
+ // Try to get a MessageDigest that uses MD5
+ final MessageDigest md = MessageDigest.getInstance("MD5");
@normanmaurer
normanmaurer Jul 19, 2013 Member

again maybe put it in a ThreadLocal

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...ler/codec/sockjs/transports/WebSocketSendHandler.java
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSendHandler.class);
+
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame sockJSFrame = (Frame) msg;
+ ctx.writeAndFlush(new TextWebSocketFrame(sockJSFrame.content())).addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(final ChannelFuture future) throws Exception {
+ if (future.isSuccess()) {
+ promise.setSuccess();
+ }
+ }
+ });
+ }
@normanmaurer
normanmaurer Jul 19, 2013 Member

do something if its not a Frame

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...ndler/codec/sockjs/transports/WebSocketTransport.java
+ if (wsFrame instanceof PingWebSocketFrame) {
+ wsFrame.content().retain();
+ ctx.channel().writeAndFlush(new PongWebSocketFrame(wsFrame.content()));
+ return;
+ }
+ if (!(wsFrame instanceof TextWebSocketFrame)) {
+ throw new UnsupportedOperationException(String.format("%s frame types not supported",
+ wsFrame.getClass().getName()));
+ }
+ final String[] messages = JsonUtil.decode((TextWebSocketFrame) wsFrame);
+ for (String message : messages) {
+ ctx.fireChannelRead(message);
+ }
+ }
+
+ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
@normanmaurer
normanmaurer Jul 19, 2013 Member

Missing @Override annotation

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...dler/codec/sockjs/transports/XhrPollingTransport.java
+import static io.netty.handler.codec.http.HttpResponseStatus.OK;
+import static io.netty.handler.codec.sockjs.transports.Transports.CONTENT_TYPE_JAVASCRIPT;
+import static io.netty.handler.codec.sockjs.transports.Transports.wrapWithLN;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.sockjs.Config;
+import io.netty.handler.codec.sockjs.protocol.Frame;
+import io.netty.handler.codec.sockjs.util.ArgumentUtil;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+public class XhrPollingTransport extends ChannelOutboundHandlerAdapter {
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...dler/codec/sockjs/transports/XhrPollingTransport.java
+ this.config = config;
+ this.request = request;
+ this.request.retain();
+ }
+
+ @Override
+ public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
+ throws Exception {
+ if (msg instanceof Frame) {
+ final Frame frame = (Frame) msg;
+ final FullHttpResponse response = new DefaultFullHttpResponse(request.getProtocolVersion(), OK);
+ response.headers().set(CONNECTION, CLOSE);
+ Transports.setDefaultHeaders(response, config, request);
+ Transports.writeContent(response, wrapWithLN(frame.content()), CONTENT_TYPE_JAVASCRIPT);
+ Transports.writeResponse(ctx, promise, response);
+ }
@normanmaurer
normanmaurer Jul 19, 2013 Member

do something if its not a Frame

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/XhrStreamingTransport.java
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.handler.codec.sockjs.Config;
+import io.netty.handler.codec.sockjs.protocol.CloseFrame;
+import io.netty.handler.codec.sockjs.protocol.Frame;
+import io.netty.handler.codec.sockjs.protocol.PreludeFrame;
+import io.netty.util.CharsetUtil;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class XhrStreamingTransport extends ChannelOutboundHandlerAdapter {
@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...er/codec/sockjs/transports/XhrStreamingTransport.java
+
+ final ByteBuf content = Transports.wrapWithLN(new PreludeFrame().content());
+ final DefaultHttpContent preludeChunk = new DefaultHttpContent(content);
+ ctx.writeAndFlush(preludeChunk);
+ }
+
+ ctx.writeAndFlush(new DefaultHttpContent(wrapWithLN(frame.content())), promise);
+ if (frame instanceof CloseFrame) {
+ ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE);
+ }
+
+ if (maxBytesLimit(frame.content().readableBytes())) {
+ logger.debug("max bytesSize limit reached [" + config.maxStreamingBytesSize() + "]. Closing");
+ ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE);
+ }
+ }
@normanmaurer
normanmaurer Jul 19, 2013 Member

do something if its not a frame

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
.../java/io/netty/handler/codec/sockjs/CloseService.java
+ * The Netty Project licenses this file to you 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 io.netty.handler.codec.sockjs;
+
+
+public class CloseService implements SockJSService {
@normanmaurer
normanmaurer Jul 19, 2013 Member

javadocs and maybe final ?

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...t/java/io/netty/handler/codec/sockjs/EchoService.java
+ * The Netty Project licenses this file to you 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 io.netty.handler.codec.sockjs;
+
+
+public class EchoService implements SockJSService {
@normanmaurer
normanmaurer Jul 19, 2013 Member

javadocs :)

@normanmaurer normanmaurer commented on an outdated diff Jul 19, 2013
...t/java/io/netty/handler/codec/sockjs/EchoService.java
+ public Config config() {
+ return config;
+ }
+
+ @Override
+ public void onMessage(final String message) throws Exception {
+ session.send(message);
+ }
+
+ @Override
+ public void onOpen(final SessionContext session) {
+ this.session = session;
+ }
+
+ @Override
+ public void onClose() {
@normanmaurer
normanmaurer Jul 19, 2013 Member

is this correct ?

@normanmaurer
Member

@danbev all in all amazing work ! Would be nice if we could remove synchronized stuff

@normanmaurer
Member

@danbev can you verify what license this work is: https://github.com/cgbystrom/sockjs-netty ?

You said your work is based on it so we need to make sure it is ASL2 compatible and also add it to the NOTICE file

@danbev
Member
danbev commented Jul 19, 2013

@normanmaurer Thanks for the review. I'll start fixing these issue.
Let me take care of the smaller tasks and then revisit the synchronised stuff.

Regarding the licence there, is it enough to just include the licence in the NOTICE file?

@normanmaurer
Member

@danbev just ping me once you have fixed those... I may do another review round then.

@ghost
ghost commented Jul 19, 2013

Build result for #1615 at cc186c730b35938fbaaa618c20f17c5158aee495: Success

@ghost
ghost commented Jul 21, 2013

Build result for #1615 at 4aad09889b88f4a337952d5d3d86d4696c2bf98d: Success

@ghost
ghost commented Jul 21, 2013

Build result for #1615 at ef55ae4b79354ee006a970413e4768ac887209b7: Success

@ghost
ghost commented Jul 21, 2013

Build result for #1615 at e581ce515a56cfa56f968fe73e66d20fb87c12fa: Success

@ghost
ghost commented Jul 21, 2013

Build result for #1615 at 5655edeca637683e53b2782212a8e064b0683ed5: Success

@normanmaurer
Member

@trustin please also review

@normanmaurer normanmaurer commented on an outdated diff Jul 21, 2013
...andler/codec/sockjs/handlers/PollingSessionState.java
+ }
+
+ @Override
+ public boolean isInUse() {
+ synchronized (session) {
+ return session.context().channel().isActive() || session.inuse();
+ }
+ }
+
+ private void startSessionTimer(final ChannelHandlerContext ctx) {
+ try {
+ if (sessionTimer == null) {
+ sessionTimer = ctx.executor().scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ final long now = System.currentTimeMillis();
@normanmaurer
normanmaurer Jul 21, 2013 Member

Better use System.nanoTime()

@normanmaurer normanmaurer commented on an outdated diff Jul 21, 2013
...dler/codec/sockjs/handlers/StreamingSessionState.java
+ });
+ }
+ }
+
+ @Override
+ public boolean isInUse() {
+ return session.context().channel().isActive();
+ }
+
+ private void startSessionTimer(final ChannelHandlerContext ctx) {
+ try {
+ if (sessionTimer == null) {
+ sessionTimer = ctx.executor().scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ final long now = System.currentTimeMillis();
@normanmaurer
normanmaurer Jul 21, 2013 Member

Better use System.nanoTime()

@ghost
ghost commented Jul 22, 2013

Build result for #1615 at 8d585530f90de64182cedc0c991496939d3d8aa8: Success

@bk1te
Contributor
bk1te commented Jul 22, 2013

Should sockjs be renamed in favour of codec-sockjs ? Like it was with codec-dns ?

@danbev
Member
danbev commented Jul 22, 2013

Sounds reasonable to me. Let me know and I'll make the change.

@normanmaurer
Member

+1

Am 22.07.2013 um 17:36 schrieb Daniel Bevenius notifications@github.com:

Sounds reasonable to me. Let me know and I'll make the change.


Reply to this email directly or view it on GitHub.

@ghost
ghost commented Jul 22, 2013

Build result for #1615 at 801f983ba3aebfac34c4ec1eaa1ccee5500ce4be: Success

@normanmaurer
Member

target for 4.1.0.Final

@ghost
ghost commented Jul 23, 2013

Build result for #1615 at e38db3e59334e35fd94108b358871218f06e5da0: Success

@ghost
ghost commented Jul 23, 2013

Build result for #1615 at 94aec64e18fb19f6b687afe8722206a87cf32e16: Success

@danbev
Member
danbev commented Jul 25, 2013

@normanmaurer I'd l like to rebase this with master to keep things up to date. Any objections about doing that?

@normanmaurer
Member

go ahead!

@ghost
ghost commented Jul 25, 2013

Build result for #1615 at bb9ef0a: Success

@ghost
ghost commented Jul 26, 2013

Build result for #1615 at b8dd958: Success

@ghost
ghost commented Aug 5, 2013

Build result for #1615 at 1fe20ad: Success

@trustin trustin commented on an outdated diff Aug 7, 2013
codec-sockjs/README.md
@@ -0,0 +1,361 @@
+## SockJS Netty Support
@trustin
trustin Aug 7, 2013 Member

Could we move this to our wiki?

@trustin trustin commented on an outdated diff Aug 7, 2013
codec-sockjs/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2012 The Netty Project
@trustin
trustin Aug 7, 2013 Member

Year? I'd recommend you to check all license headers just in case.

@trustin trustin commented on an outdated diff Aug 7, 2013
.../io/netty/handler/codec/sockjs/NettySockJSServer.java
@@ -0,0 +1,105 @@
+/*
@trustin
trustin Aug 7, 2013 Member

Could we move this file to the example package?

@trustin trustin commented on an outdated diff Aug 7, 2013
...etty/handler/codec/sockjs/AbstractServiceFactory.java
+ *
+ * The Netty Project licenses this file to you 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 io.netty.handler.codec.sockjs;
+
+public abstract class AbstractServiceFactory implements SockJSServiceFactory {
@trustin
trustin Aug 7, 2013 Member

AbstractSocketJsServiceFactory?

@trustin trustin commented on an outdated diff Aug 7, 2013
codec-sockjs/pom.xml
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netty-codec-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netty-handler</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ <version>1.9.12</version>
+ </dependency>
@trustin
trustin Aug 7, 2013 Member

Could we move the versions and exclusions of jackson-mapper-asl and mockito-core to the parent pom's dependencyManagement section?

@trustin trustin commented on an outdated diff Aug 7, 2013
codec-sockjs/pom.xml
+ <version>1.9.5</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2</version>
@trustin
trustin Aug 7, 2013 Member

Plugin version should be specifed in the parent pom's pluginManagement section for easier management.

@trustin trustin commented on an outdated diff Aug 7, 2013
...c/main/java/io/netty/handler/codec/sockjs/Config.java
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package io.netty.handler.codec.sockjs;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import io.netty.handler.codec.sockjs.util.ArgumentUtil;
+
+/**
+ * Configuration for a SockJS Session.
+ */
+public final class Config {
@trustin
trustin Aug 7, 2013 Member

SocksJsConfig?

@trustin trustin commented on an outdated diff Aug 7, 2013
...c/main/java/io/netty/handler/codec/sockjs/Config.java
+ .append(", cookiesNeeded=").append(cookiesNeeded)
+ .append(", sockjsUrl=").append(sockjsUrl)
+ .append(", sessionTimeout=").append(sessionTimeout)
+ .append(", heartbeatInterval=").append(heartbeatInterval)
+ .append(", maxStreamingBytesSize=").append(maxStreamingBytesSize)
+ .append(", tls=").append(tls)
+ .append("]").toString();
+ }
+
+ /**
+ * The prefix, or name, of the service.
+ * For example, in the url "http://localhost/echo/111/12345/xhr", 'echo' is the prefix.
+ *
+ * @param prefix the prefix/name of the SockJS service.
+ */
+ public static Builder prefix(final String prefix) {
@trustin
trustin Aug 7, 2013 Member

I wonder if withPrefix() sounds better. What do you think?

SocksJsConfig.withPrefix("echo").disableWebsocket();
@trustin trustin commented on an outdated diff Aug 7, 2013
...ava/io/netty/handler/codec/sockjs/SessionContext.java
+ *
+ * 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 io.netty.handler.codec.sockjs;
+
+import io.netty.channel.ChannelHandlerContext;
+
+/**
+ * Allows a {@link SockJSService} to interact with its session by providing
+ * methods to send data, and to close the session.
+ */
+public interface SessionContext {
@trustin
trustin Aug 7, 2013 Member

SocksJsSessionContext?

danbev added some commits Jul 11, 2013
@danbev danbev Updated to reflect recent changes in Netty API. b7b47ad
@danbev danbev Updating to 4.0.4.Final-SNAPSHOT. 1fcd342
@danbev danbev Releasing the discarded buffer and not simply ignoring them. bff1500
@danbev danbev Adding instructions about how to run the sockjs-protocol tests. 2af2717
@danbev danbev Making sure that writeAndFlush is used and not simple write where
approriate.
296b95c
@danbev danbev Code clean up and document updates 8fddc18
@danbev danbev Fixing the ignored jsonEncodingTestXhrServerDecodes test. e673dcd
@danbev danbev Docs and minor updates from PR feedback. 736ada8
@danbev danbev Making Frame extend ByteBufHolder and subclasses extend
DefaultByteBufHolder.
51e144a
@danbev danbev Making HtmlFileTransport extend ChannelDuplexHandler and use
ByteBufAllocator, and reduce writeAndFlush calls.
ada54a8
@danbev danbev Reducing buffer copies and adding ThreadLocals for SimpleDateFormat and
MessageDigest.
d5830a8
@danbev danbev Making JsonPollingTransport extends ChannelDuplexHandler and reducing
buffer copies.
ea186d8
@danbev danbev Removing duplicate close. f982878
@danbev danbev Adding javadocs and minor code clean up. 151afa2
@danbev danbev Handle none Frames. db232af
@danbev danbev Adding javadocs and minor clean ups after PR feedback. 2e12618
@danbev danbev Workaround for UnsupportedOperationException when calling
scheduleAtFixedRate.
0bea496
@danbev danbev Renaming sockjs to codec-sockjs. 5217765
@danbev danbev Refactoring session handling. 4642681
@danbev danbev Adding ThreadLocal for MessageDigest. 898c21f
@danbev danbev Using SystemnanoTime instead of System.currentTimeMillis. 0a1a5d6
@danbev danbev Adding licence and credit to NOTICE file. ef0d770
@danbev danbev Making AbstractTimerSessionState package protected and converting nano
to millis.
1f64bd4
@danbev danbev Fixing SockJSSessionTest plus minor refactoring. f6b0095
@danbev danbev Updated version to 4.0.5.Final-SNAPSHOT. 7dfcf03
@danbev danbev Adding heartbeat support for SockJS WebSocket transport. d7acae1
@danbev danbev Updating WebSocket documentation fixing incorrect protocol for raw
websockets.
1eab499
@danbev danbev Updated version to 4.0.7.Final-SNAPSHOT. 0f45e87
@danbev danbev Updated license headers to correct year (2013). 70c7981
@danbev danbev Removing README.md and will move content to wiki. e76c896
@danbev danbev Changing SockJS named classes to SockJs. 7da9d72
@danbev danbev Moving dependencies into parent pom.xml 311fa2c
@danbev danbev Renaming prefix to withPrefix. 9d97567
@danbev danbev Adhering to Netty naming conventions. af86e2f
@danbev danbev Adding keyStore and keyStorePassword as configuration options. e4e03e7
@danbev danbev Rebase with upstream master. Now at 5.0.0.Alpha1-SNAPSHOT. db74e76
@danbev danbev Adding tests for Hybi-17 (Sec-WebSocket-Version=13). 4bf87fd
@danbev danbev Fixing code inspection issues. 8fc201b
@danbev danbev Fixing indentation. cc18130
@danbev danbev Fixing logging statements to use text substitutions. 8cc242b
@danbev danbev Overriding copy, duplicate, and retain methods for SockJS frames. e76d2b0
@danbev danbev Fixing inconsistent method parameter ordering. fac20a0
@danbev danbev Removed unused class PathUtil. d007509
@danbev danbev Making PathParams public.
- Also renamed the enum Transports.Types to Transports.Type
f370ee1
@danbev danbev Limiting the visibilty Frame implementation fields. a50d276
@danbev danbev Renaming package handlers to handler. 1b30628
@danbev danbev Renaming package transports to transport. 9e8c346
@danbev danbev Moving Greeting, Iframe, and Info classes to handler package.
- also made the classes package-private.
f5a6b4a
@danbev danbev Using StringUtil.simpleClassName for toString methods. c5f6f69
@danbev danbev Adding support for null terminated arrays in MessageFrame. 9e4fcf3
@danbev danbev Moving SockJsChannelInitializer to test directory where it is used. 30ac1c9
@danbev danbev Removing unused method. cf3ba1d
@danbev danbev Releasing SockJS Frame. 2a0402e
@danbev danbev Removing the passMessage variable to simplify the code. 517b960
@danbev danbev Adding additional test for xhr polling session reuse. c08f5df
@danbev danbev Making unreleasable frames delegate release to their unreleasable ins…
…tance.
c41d93a
@danbev danbev Releasing unpooled buffers. 8ff83e6
@danbev danbev Changing write to writeAndFlush for XhrPolling and XhrStreaming. 1499eef
@danbev danbev Switching to com.fasterxml.jackson 2.3.0. 823c528
@danbev danbev Updating to Netty-5.0.0.Alpha1. d439a26
@danbev danbev Adding session.onClose call for userEventTriggered events. 82d6dee
@danbev danbev Code cleanup. 5b3ba8c
@danbev danbev Adding duplicate call to unlrealisable buffer. e04c811
@danbev danbev Fixing session handling.
When a polling request, for example xhr_polling, is
made the wrong ChannelHandlerContext was being used to
f14d2fa
@danbev danbev SockJS Session refactoring. 3508ee1
@danbev danbev Fixing buffer leak in HtmlFileTransport and JsonpPolling transports 926089a
@danbev danbev Removing synchronization from SockJsSession. a4ed84e
@ghost
ghost commented Mar 18, 2014

Build result for #1615 at a4ed84e: Success

@normanmaurer
Member

@danbev please let you know once you think it is "complete"

@danbev
Member
danbev commented Mar 23, 2014

@normanmaurer will do. I'm currently taking another stab at the refactoring to avoid the API on top of Netty. Made some progress on Friday and hope to continue tomorrow.

@ghost
ghost commented May 11, 2014

Build result for #1615 at a4ed84e: Success

@Scottmitch
Member

@normanmaurer @danbev - I'm going to move this to 5.0.0.Alpha3 (outstanding for a while, needs to absorb latest changes from master, not squashed). Let me know if you have any objections.

@Scottmitch Scottmitch added the feature label Sep 23, 2014
@danbev
Member
danbev commented Sep 24, 2014

@Scottmitch No problems. I've got this on my list to update with the latest changes from master and then go through it once more with @normanmaurer.

@chemist777

@danbev HeartbeatTimer for jsonp-polling transport does not work. Because condition ctx.channel().isActive() && ctx.channel().isRegistered() in the file io/netty/handler/codec/sockjs/handler/AbstractTimersSessionState.java is always false for JSONP. Browser sockjs client with simple reconnection logic to echo server quickly reaches his HTTP connections limit.

@danbev
Member
danbev commented Sep 25, 2014

@chatovod Thanks for reporting this! I'm going to revisit this PR after next week (on holidays) and I'll make sure I cover this case.

@chemist777

@danbev One more bug. SockJsSession will never be removed from server sessions in case of session timeout because you use TimeUnit.NANOSECONDS.toMillis(System.nanoTime()). It is not always the same as System.currentTimeMillis() in Oracle JDK 8 64 bit. See the AbstractTimersSessionState.java.

And third bug.
io.netty.handler.codec.sockjs.SockJsService#onClose will never be executed in case of session timeout.

@danbev
Member
danbev commented Sep 25, 2014

@chatovod Great, (well, not great that there are bugs but...) I'll take a look at this one too. Thanks!

@danbev
Member
danbev commented Sep 26, 2014

When I get back I'll also update the default SockJS CDN as this is being changed. More information can be found in this issue.

@danbev danbev added a commit to danbev/aerogear-simplepush-server that referenced this pull request Oct 6, 2014
@danbev danbev Removing non-exising Netty SockJS dependency.
This dependency will be used later when the SockJS PR is merged into
upstream Netty [1]

[1] netty/netty#1615
f1070fa
@danbev danbev referenced this pull request in aerogear/aerogear-simplepush-server Oct 6, 2014
Merged

Removing non-exising Netty SockJS dependency. #85

@danbev danbev added a commit to danbev/aerogear-simplepush-server that referenced this pull request Oct 6, 2014
@danbev danbev Removing non-exising Netty SockJS dependency.
This dependency will be used later when the SockJS PR is merged into
upstream Netty [1]

[1] netty/netty#1615
658b016
@ghost
ghost commented Oct 13, 2014

Build result for #1615 at a4ed84e: Unstable

@trustin
Member
trustin commented Oct 13, 2014

@danbev, first of all, thank you so much for your patience on this pull request.

Initially, I thought it is better implementing this as a meta-transport, but I ended up thinking that it might lead to just another abstraction leak.

Therefore, I'd like to ask you to:

  • forget about my idea about meta-transport,
  • create a new pull request which is same with this one, so that we don't suffer with the loading time of this page, and
  • close this pull request and add a link to the new pull request to continue your work.

Once you're done, I'll do an extensive review so that this can be merged into upstream.

@trustin
Member
trustin commented Oct 13, 2014

Oh, and you might want to rebase against 4.1, because we can make this part of 4.1 for sure.

@danbev
Member
danbev commented Oct 13, 2014

Initially, I thought it is better implementing this as a meta-transport, but I ended up thinking that it might >lead to just another abstraction leak.

I have attempted to create such a meta-transport which I've managed to get working but I'm not really sure about it. Would be great if you can take a look and see what you think of it. If you still think it's a bad idea, I'll take care of the 3 bugs reported here by @chemist777 and create a new rebased PR.

@trustin
Member
trustin commented Oct 13, 2014

@danbev You are an expert on SockJS and I'm not, so I'd say you should tell me which sounds better to you and why. If there's no good reason to keep the meta-transport and it has more shortcomings than this pull request, we should keep this pull request. Let me know what you think.

@danbev
Member
danbev commented Oct 14, 2014

@trustin What I liked with your suggestion was that is uses Netty constructs, you just have a normal ChannelHandler to implement and you simply use the normal API to configure, and write messages etc.

But while implementing I came across issues and it felt like I'm "shoe horning" this into Netty. My concern is that it SockJS might not be a good choice for such a meta-transport as it is really a protocol on top of HTTP in a simliar way to WebSocket. I some ways it is similar to WebSocketServerProtocolHandler.
I'll rebase and create a new PR and have it ready for review. As there have been so many commits would you prefer that I squash them all?

@normanmaurer
Member

Yes please squash

Am 14.10.2014 um 08:33 schrieb Daniel Bevenius notifications@github.com:

@trustin What I liked with your suggestion was that is uses Netty constructs, you just have a normal ChannelHandler to implement and you simply use the normal API to configure, and write messages etc.

But while implementing I came across issues and it felt like I'm "shoe horning" this into Netty. My concern is that it SockJS might not be a good choice for such a meta-transport as it is really a protocol on top of HTTP in a simliar way to WebSocket. I some ways it is similar to WebSocketServerProtocolHandler.
I'll rebase and create a new PR and have it ready for review. As there have been so many commits would you prefer that I squash them all?


Reply to this email directly or view it on GitHub.

@danbev
Member
danbev commented Oct 23, 2014

Closing this pull request in favour of #3045

@danbev danbev closed this Oct 23, 2014
@danbev danbev deleted the danbev:sockjs branch Jan 20, 2015
@mortan
mortan commented Jan 25, 2015

Any update on this? Would love sock.js support in netty! 👍

@danbev
Member
danbev commented Jan 26, 2015

@mortan I'm currently working on getting #3271 merged and after that I'll rebase and hopefully a final round of reviews before merging. Can't give an exact date but there should be some updates to this PR shortly, hopefully later this week or next.

@danbev
Member
danbev commented Mar 5, 2015

This needs to be rebased against the latest 4.1 version and verify that
things are working correctly. The last rebase passed the
sockjs-protocol-0.3.3 but only after custom updates due to how header
values are compared in the test, so nothing major. I've not got much time
at the moment due to parental leave but will try to rebase and run the
sockjs-protocol tests this weekend and also update the PR against
sockjs-protocol (the header issue mentioned above)

onsdag 4 mars 2015 skrev Tiger Toes notifications@github.com:

Hey @danbev https://github.com/danbev, is there any update to this? Is
there anything I can do to help?


Reply to this email directly or view it on GitHub
#1615 (comment).

@mortan
mortan commented Apr 10, 2015

Still sitting on the edge of my seat and waiting for it! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment