From 031608d230458dfdfe221bb4a5d896835392dd7e Mon Sep 17 00:00:00 2001 From: ctpahhik Date: Mon, 29 Jul 2013 09:43:43 -0700 Subject: [PATCH 1/4] Client library embedded into iframe --- pom.xml | 13 ++++++++- resources/sockjs-0.3.4.min.js | 27 ++++++++++++++++++ .../java/com/cgbystrom/sockjs/IframePage.java | 28 ++++++++++++++++--- .../java/com/cgbystrom/sockjs/TestServer.java | 5 ++-- 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 resources/sockjs-0.3.4.min.js diff --git a/pom.xml b/pom.xml index f4f87f9..506d64f 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ 4.0.0 com.cgbystrom sockjs-netty - 0.1.0-SNAPSHOT + 0.1.1-SNAPSHOT SockJS for JBoss Netty jar @@ -96,7 +96,18 @@ + + org.apache.maven.plugins + maven-resources-plugin + 2.2 + + + + resources + resources + + diff --git a/resources/sockjs-0.3.4.min.js b/resources/sockjs-0.3.4.min.js new file mode 100644 index 0000000..80ce980 --- /dev/null +++ b/resources/sockjs-0.3.4.min.js @@ -0,0 +1,27 @@ +/* SockJS client, version 0.3.4, http://sockjs.org, MIT License + +Copyright (c) 2011-2012 VMware, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// JSON2 by Douglas Crockford (minified). +var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c1?this._listeners[a]=d.slice(0,e).concat(d.slice(e+1)):delete this._listeners[a];return}return},d.prototype.dispatchEvent=function(a){var b=a.type,c=Array.prototype.slice.call(arguments,0);this["on"+b]&&this["on"+b].apply(this,c);if(this._listeners&&b in this._listeners)for(var d=0;d=3e3&&a<=4999},c.countRTO=function(a){var b;return a>100?b=3*a:b=a+200,b},c.log=function(){b.console&&console.log&&console.log.apply&&console.log.apply(console,arguments)},c.bind=function(a,b){return a.bind?a.bind(b):function(){return a.apply(b,arguments)}},c.flatUrl=function(a){return a.indexOf("?")===-1&&a.indexOf("#")===-1},c.amendUrl=function(b){var d=a.location;if(!b)throw new Error("Wrong url for SockJS");if(!c.flatUrl(b))throw new Error("Only basic urls are supported in SockJS");return b.indexOf("//")===0&&(b=d.protocol+b),b.indexOf("/")===0&&(b=d.protocol+"//"+d.host+b),b=b.replace(/[/]+$/,""),b},c.arrIndexOf=function(a,b){for(var c=0;c=0},c.delay=function(a,b){return typeof a=="function"&&(b=a,a=0),setTimeout(b,a)};var i=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,j={"\0":"\\u0000","\x01":"\\u0001","\x02":"\\u0002","\x03":"\\u0003","\x04":"\\u0004","\x05":"\\u0005","\x06":"\\u0006","\x07":"\\u0007","\b":"\\b","\t":"\\t","\n":"\\n","\x0b":"\\u000b","\f":"\\f","\r":"\\r","\x0e":"\\u000e","\x0f":"\\u000f","\x10":"\\u0010","\x11":"\\u0011","\x12":"\\u0012","\x13":"\\u0013","\x14":"\\u0014","\x15":"\\u0015","\x16":"\\u0016","\x17":"\\u0017","\x18":"\\u0018","\x19":"\\u0019","\x1a":"\\u001a","\x1b":"\\u001b","\x1c":"\\u001c","\x1d":"\\u001d","\x1e":"\\u001e","\x1f":"\\u001f",'"':'\\"',"\\":"\\\\","\x7f":"\\u007f","\x80":"\\u0080","\x81":"\\u0081","\x82":"\\u0082","\x83":"\\u0083","\x84":"\\u0084","\x85":"\\u0085","\x86":"\\u0086","\x87":"\\u0087","\x88":"\\u0088","\x89":"\\u0089","\x8a":"\\u008a","\x8b":"\\u008b","\x8c":"\\u008c","\x8d":"\\u008d","\x8e":"\\u008e","\x8f":"\\u008f","\x90":"\\u0090","\x91":"\\u0091","\x92":"\\u0092","\x93":"\\u0093","\x94":"\\u0094","\x95":"\\u0095","\x96":"\\u0096","\x97":"\\u0097","\x98":"\\u0098","\x99":"\\u0099","\x9a":"\\u009a","\x9b":"\\u009b","\x9c":"\\u009c","\x9d":"\\u009d","\x9e":"\\u009e","\x9f":"\\u009f","\xad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601","\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f","\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d","\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029","\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d","\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061","\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065","\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069","\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d","\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0","\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4","\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8","\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc","\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"},k=/[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g,l,m=JSON&&JSON.stringify||function(a){return i.lastIndex=0,i.test(a)&&(a=a.replace(i,function(a){return j[a]})),'"'+a+'"'},n=function(a){var b,c={},d=[];for(b=0;b<65536;b++)d.push(String.fromCharCode(b));return a.lastIndex=0,d.join("").replace(a,function(a){return c[a]="\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4),""}),a.lastIndex=0,c};c.quote=function(a){var b=m(a);return k.lastIndex=0,k.test(b)?(l||(l=n(k)),b.replace(k,function(a){return l[a]})):b};var o=["websocket","xdr-streaming","xhr-streaming","iframe-eventsource","iframe-htmlfile","xdr-polling","xhr-polling","iframe-xhr-polling","jsonp-polling"];c.probeProtocols=function(){var a={};for(var b=0;b0&&h(a)};return c.websocket!==!1&&h(["websocket"]),d["xhr-streaming"]&&!c.null_origin?e.push("xhr-streaming"):d["xdr-streaming"]&&!c.cookie_needed&&!c.null_origin?e.push("xdr-streaming"):h(["iframe-eventsource","iframe-htmlfile"]),d["xhr-polling"]&&!c.null_origin?e.push("xhr-polling"):d["xdr-polling"]&&!c.cookie_needed&&!c.null_origin?e.push("xdr-polling"):h(["iframe-xhr-polling","jsonp-polling"]),e};var p="_sockjs_global";c.createHook=function(){var a="a"+c.random_string(8);if(!(p in b)){var d={};b[p]=function(a){return a in d||(d[a]={id:a,del:function(){delete d[a]}}),d[a]}}return b[p](a)},c.attachMessage=function(a){c.attachEvent("message",a)},c.attachEvent=function(c,d){typeof b.addEventListener!="undefined"?b.addEventListener(c,d,!1):(a.attachEvent("on"+c,d),b.attachEvent("on"+c,d))},c.detachMessage=function(a){c.detachEvent("message",a)},c.detachEvent=function(c,d){typeof b.addEventListener!="undefined"?b.removeEventListener(c,d,!1):(a.detachEvent("on"+c,d),b.detachEvent("on"+c,d))};var q={},r=!1,s=function(){for(var a in q)q[a](),delete q[a]},t=function(){if(r)return;r=!0,s()};c.attachEvent("unload",t),c.unload_add=function(a){var b=c.random_string(8);return q[b]=a,r&&c.delay(s),b},c.unload_del=function(a){a in q&&delete q[a]},c.createIframe=function(b,d){var e=a.createElement("iframe"),f,g,h=function(){clearTimeout(f);try{e.onload=null}catch(a){}e.onerror=null},i=function(){e&&(h(),setTimeout(function(){e&&e.parentNode.removeChild(e),e=null},0),c.unload_del(g))},j=function(a){e&&(i(),d(a))},k=function(a,b){try{e&&e.contentWindow&&e.contentWindow.postMessage(a,b)}catch(c){}};return e.src=b,e.style.display="none",e.style.position="absolute",e.onerror=function(){j("onerror")},e.onload=function(){clearTimeout(f),f=setTimeout(function(){j("onload timeout")},2e3)},a.body.appendChild(e),f=setTimeout(function(){j("timeout")},15e3),g=c.unload_add(i),{post:k,cleanup:i,loaded:h}},c.createHtmlfile=function(a,d){var e=new ActiveXObject("htmlfile"),f,g,i,j=function(){clearTimeout(f)},k=function(){e&&(j(),c.unload_del(g),i.parentNode.removeChild(i),i=e=null,CollectGarbage())},l=function(a){e&&(k(),d(a))},m=function(a,b){try{i&&i.contentWindow&&i.contentWindow.postMessage(a,b)}catch(c){}};e.open(),e.write('\n" + - " \n" + + jsClient + "\n" + "\n" + "

Don't panic!

\n" + @@ -68,6 +72,22 @@ private ChannelBuffer createContent(String url) { return ChannelBuffers.copiedBuffer(content, CharsetUtil.UTF_8); } + private String getJsClient(String url) { + return "\n"; + } + + private String getJsClient(InputStream clientResource) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(clientResource)); + StringBuilder builder = new StringBuilder(); + builder.append("\n"); + return builder.toString(); + } + private static String generateMd5(String value) { String encryptedString = null; byte[] bytesToBeEncrypted; diff --git a/src/test/java/com/cgbystrom/sockjs/TestServer.java b/src/test/java/com/cgbystrom/sockjs/TestServer.java index b8f8bc4..73d0a0b 100644 --- a/src/test/java/com/cgbystrom/sockjs/TestServer.java +++ b/src/test/java/com/cgbystrom/sockjs/TestServer.java @@ -24,7 +24,7 @@ import static org.jboss.netty.channel.Channels.pipeline; public class TestServer { - public static void main(String[] args) { + public static void main(String[] args) throws Exception { Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); LoggerContext loggerContext = rootLogger.getLoggerContext(); loggerContext.reset(); @@ -46,7 +46,8 @@ public static void main(String[] args) { Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); - final ServiceRouter router = new ServiceRouter("http://cdn.sockjs.org/sockjs-0.3.4.min.js"); + //final ServiceRouter router = new ServiceRouter("http://cdn.sockjs.org/sockjs-0.3.4.min.js"); + final ServiceRouter router = new ServiceRouter(TestServer.class.getClassLoader().getResourceAsStream("resources/sockjs-0.3.4.min.js")); router.registerService("/echo", new SessionCallbackFactory() { @Override public EchoSession getSession(String id) throws Exception { From 73558209a5dbc8f8a37778ac57b0e4d95ea41b5b Mon Sep 17 00:00:00 2001 From: ctpahhik Date: Mon, 29 Jul 2013 09:50:33 -0700 Subject: [PATCH 2/4] Added ability to stop accepting new connections but continue serving already existed. Required for graceful shutdown. --- .../com/cgbystrom/sockjs/ServiceRouter.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/main/java/com/cgbystrom/sockjs/ServiceRouter.java b/src/main/java/com/cgbystrom/sockjs/ServiceRouter.java index 93560d6..cd17d8b 100644 --- a/src/main/java/com/cgbystrom/sockjs/ServiceRouter.java +++ b/src/main/java/com/cgbystrom/sockjs/ServiceRouter.java @@ -12,6 +12,7 @@ import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.KEEP_ALIVE; import java.io.IOException; +import java.io.InputStream; import java.util.LinkedHashMap; import java.util.Map; import java.util.Random; @@ -27,6 +28,7 @@ private enum SessionCreation { CREATE_OR_REUSE, FORCE_REUSE, FORCE_CREATE } private final Map services = new LinkedHashMap(); private IframePage iframe; + private volatile boolean noNewConnections; /** * @@ -37,6 +39,18 @@ public ServiceRouter(String clientUrl) { this.iframe = new IframePage(clientUrl); } + public ServiceRouter(InputStream clientResource) throws Exception { + try { + this.iframe = new IframePage(clientResource); + } finally { + if (clientResource != null) { + try { + clientResource.close(); + } catch (Exception ignore) {} + } + } + } + public synchronized ServiceMetadata registerService(String baseUrl, final SessionCallback service, boolean isWebSocketEnabled, int maxResponseSize) { return registerService(baseUrl, new SessionCallbackFactory() { @@ -103,9 +117,15 @@ private void handleService(ChannelHandlerContext ctx, MessageEvent e, ServiceMet iframe.handle(request, response); writeResponse(e.getChannel(), request, response); } else if (path.startsWith("/info")) { + if (noNewConnections) { + response.setStatus(HttpResponseStatus.SERVICE_UNAVAILABLE); + response.setHeader(CACHE_CONTROL, "no-store, no-cache, must-revalidate, max-age=0"); + response.setContent(ChannelBuffers.copiedBuffer("Temporary unavailable", CharsetUtil.UTF_8)); + } else { response.setHeader(CONTENT_TYPE, "application/json; charset=UTF-8"); response.setHeader(CACHE_CONTROL, "no-store, no-cache, must-revalidate, max-age=0"); response.setContent(getInfo(serviceMetadata)); + } writeResponse(e.getChannel(), request, response); } else if (path.startsWith("/websocket")) { // Raw web socket @@ -236,6 +256,30 @@ private ChannelBuffer getInfo(ServiceMetadata metadata) { return ChannelBuffers.copiedBuffer(sb.toString(), CharsetUtil.UTF_8); } + /** + * Check if server is accepting new connections + * + * @return true if new connections are not allowed + */ + public boolean isNoNewConnections() { + return noNewConnections; + } + + /** + * Stop accepting new connections. + * Existed sessions still to be handled + */ + public void stopAcceptingNewConnections() { + noNewConnections = true; + } + + /** + * Continue accepting new connections. + */ + public void startAcceptingNewConnections() { + noNewConnections = false; + } + public static class ServiceMetadata { private ServiceMetadata(String url, SessionCallbackFactory factory, ConcurrentHashMap sessions, boolean isWebSocketEnabled, int maxResponseSize) { From b047e41cd63a42772c914b6e6de7742154e03d3d Mon Sep 17 00:00:00 2001 From: ctpahhik Date: Mon, 29 Jul 2013 09:54:47 -0700 Subject: [PATCH 3/4] Added ability to get RemoteAddress via Session --- src/main/java/com/cgbystrom/sockjs/Session.java | 9 +++++++++ src/main/java/com/cgbystrom/sockjs/SessionHandler.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/com/cgbystrom/sockjs/Session.java b/src/main/java/com/cgbystrom/sockjs/Session.java index d2096c0..483eacf 100644 --- a/src/main/java/com/cgbystrom/sockjs/Session.java +++ b/src/main/java/com/cgbystrom/sockjs/Session.java @@ -1,6 +1,15 @@ package com.cgbystrom.sockjs; +import java.net.SocketAddress; + public interface Session { public void send(String message); public void close(); + + /** + * Allow to get client's Address + * + * @return SocketAddress (ip, port etc.) of the client. + */ + public SocketAddress getRemoteAddress(); } diff --git a/src/main/java/com/cgbystrom/sockjs/SessionHandler.java b/src/main/java/com/cgbystrom/sockjs/SessionHandler.java index 0000592..b070448 100644 --- a/src/main/java/com/cgbystrom/sockjs/SessionHandler.java +++ b/src/main/java/com/cgbystrom/sockjs/SessionHandler.java @@ -5,6 +5,7 @@ import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.util.CharsetUtil; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicBoolean; @@ -197,4 +198,12 @@ public LockException(Channel channel) { super("Session is locked by channel " + channel + ". Please disconnect other channel first before trying to register it with a session."); } } + + @Override + public SocketAddress getRemoteAddress() { + if (channel != null) { + return channel.getRemoteAddress(); + } + return null; + } } From d60ccf4270806d1985b28e07e9b2b5ac547b05d8 Mon Sep 17 00:00:00 2001 From: ctpahhik Date: Tue, 30 Jul 2013 11:04:17 -0700 Subject: [PATCH 4/4] Default constructor for ServiceRouter added. Changed closing logic. --- pom.xml | 2 +- src/main/java/com/cgbystrom/sockjs/IframePage.java | 4 ++-- src/main/java/com/cgbystrom/sockjs/ServiceRouter.java | 6 +++++- src/main/java/com/cgbystrom/sockjs/SessionHandler.java | 9 +++++++-- src/test/java/com/cgbystrom/sockjs/TestServer.java | 4 ++-- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 506d64f..1a16158 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ 4.0.0 com.cgbystrom sockjs-netty - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT SockJS for JBoss Netty jar diff --git a/src/main/java/com/cgbystrom/sockjs/IframePage.java b/src/main/java/com/cgbystrom/sockjs/IframePage.java index 6af073a..54aefda 100644 --- a/src/main/java/com/cgbystrom/sockjs/IframePage.java +++ b/src/main/java/com/cgbystrom/sockjs/IframePage.java @@ -18,7 +18,7 @@ public IframePage(String url) { content = createContent(getJsClient(url)); } - public IframePage(InputStream clientResource) throws Exception { + public IframePage(InputStream clientResource) throws IOException { content = createContent(getJsClient(clientResource)); } @@ -76,7 +76,7 @@ private String getJsClient(String url) { return "\n"; } - private String getJsClient(InputStream clientResource) throws Exception { + private String getJsClient(InputStream clientResource) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(clientResource)); StringBuilder builder = new StringBuilder(); builder.append("