├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs ├── org.eclipse.m2e.core.prefs ├── org.eclipse.wst.common.component └── org.eclipse.wst.common.project.facet.core.xml ├── .springBeans ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── net │ └── sample │ └── app │ ├── AppConfig.java │ ├── AppController.java │ ├── Application.java │ ├── ServletConfig.java │ └── WebsocketConfig.java └── resources ├── META-INF ├── resources │ └── views │ │ └── main.jsp └── web-fragment.xml ├── sample.properties ├── static ├── css │ └── some.css ├── js │ ├── sockjs-0.3.4.js │ ├── some.js │ └── stomp.js └── resource │ └── index.html └── views ├── jade4j.jade └── thymeleaf.html /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | /target 3 | ======= 4 | *.class 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | >>>>>>> refs/remotes/choose_remote_name/master 11 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spring4MVC 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.springframework.ide.eclipse.core.springbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.wst.validation.validationbuilder 25 | 26 | 27 | 28 | 29 | org.eclipse.m2e.core.maven2Builder 30 | 31 | 32 | 33 | 34 | 35 | org.eclipse.jem.workbench.JavaEMFNature 36 | org.eclipse.wst.common.modulecore.ModuleCoreNature 37 | org.springframework.ide.eclipse.core.springnature 38 | org.eclipse.jdt.core.javanature 39 | org.eclipse.m2e.core.maven2Nature 40 | org.eclipse.wst.common.project.facet.core.nature 41 | 42 | 43 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding//src/test/java=UTF-8 5 | encoding/=UTF-8 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.7 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 12 | org.eclipse.jdt.core.compiler.source=1.7 13 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.springBeans: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Ukjin Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring 4.0.0.RELEASE Template for MVC with Embedded Tomcat 7.0.47 2 | 3 | This project is minimal setting of Spring 4 MVC. maybe. 4 | 5 | ## Before use this project 6 | 7 | - Java 1.6 and later 8 | - favorite text editor or Eclipse or STS. 9 | - Experience of Spring MVC. 10 | 11 | ## How to? 12 | 13 | - Download or `git clone` 14 | - `mvn install` 15 | - `java -jar "PROJECT.jar"` 16 | - `http://localhost:8080` 17 | 18 | ## Goals 19 | 20 | - **Fully Websocket!** 21 | - **I wanna make Production use!!!** 22 | - JSP forward (but less PRIORITY. because I'll not use JSP View at my production.) 23 | 24 | ## Changelog 25 | 26 | - View page solved. When get context, you must call `Tomcat.initWebappDefaults(context);` 27 | - Static resources solved. `registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/")` 28 | - jade4j and Thymeleaf support (but with Thymeleaf, JSP not work. why?) 29 | - JSP resolver removed dut to unable forward JSP file in JAR. use Thymeleaf or jade4j instead until solve this problem. 30 | - parameterized port change and tomcat workdir changeable. `java -jar "PROJECT.jar" port=8081 basedir=/path/to/tomcat/workdir` 31 | - Websocket support by [puryfury](https://github.com/puryfury). thanks. 32 | - embedded Tomcat will run with Non-blocking HTTP IO (Http11NioProtocol). 33 | 34 | ## Websocket notes 35 | 36 | - This example will running with SockJS websocket emulation. If you want native websocket, convert this to war project and run with server below, 37 | + Tomcat 8 38 | + Jetty 9 39 | + Glassfish 4.0 40 | - looking for similar project? google or check out project below. 41 | + [rstoyanchev / spring-websocket-test](https://github.com/rstoyanchev/spring-websocket-test) 42 | - If you want technical infomation, see below, 43 | + [JSR-356 Java EE 7 Specification](https://www.jcp.org/en/jsr/detail?id=356) 44 | + [SockJS](http://sockjs.org) 45 | + [STOMP](http://stomp.github.io/) 46 | 47 | ## Known Issues 48 | 49 | - Why can't forward JSP file in JAR? It's possible in Servlet 3.0 but not affect in embedded tomcat. 50 | - **NOT STABLE. PLZ DO NOT USE THIS AT PRODUCTION YET.** -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.springframework.samples 5 | Spring4MVC 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | 10 | 11 | 1.7 12 | UTF-8 13 | UTF-8 14 | 15 | 16 | 4.0.0.RELEASE 17 | 1.3.3.RELEASE 18 | 19 | 20 | 1.4 21 | 1.6 22 | 23 | 24 | 2.3.0 25 | 26 | 27 | 3.0.1 28 | 4.0 29 | 7.0.47 30 | 31 | 32 | 1.7.4 33 | 2.2 34 | 35 | 36 | 1.6 37 | 5.7.0 38 | 39 | 40 | 4.3.1.Final 41 | 4.2.8.Final 42 | 1.0.1.Final 43 | 44 | 45 | 1.0.13 46 | 1.7.5 47 | 48 | 49 | 0.4.0 50 | 2.1.2.RELEASE 51 | 2.3 52 | 53 | 54 | 4.11 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework 62 | spring-core 63 | ${spring.version} 64 | 65 | 66 | commons-logging 67 | commons-logging 68 | 69 | 70 | 71 | 72 | org.springframework 73 | spring-oxm 74 | ${spring.version} 75 | 76 | 77 | commons-lang 78 | commons-lang 79 | 80 | 81 | 82 | 83 | org.springframework 84 | spring-webmvc 85 | ${spring.version} 86 | 87 | 88 | commons-logging 89 | commons-logging 90 | 91 | 92 | 93 | 94 | org.springframework 95 | spring-websocket 96 | ${spring.version} 97 | 98 | 99 | 100 | org.springframework 101 | spring-tx 102 | ${spring.version} 103 | 104 | 105 | org.springframework 106 | spring-messaging 107 | ${spring.version} 108 | 109 | 110 | 111 | org.springframework.data 112 | spring-data-mongodb 113 | ${spring-data-mongodb.version} 114 | 115 | 116 | 117 | 118 | 119 | org.aspectj 120 | aspectjrt 121 | ${aspectj.version} 122 | 123 | 124 | org.aspectj 125 | aspectjweaver 126 | ${aspectj.version} 127 | 128 | 129 | 130 | cglib 131 | cglib-nodep 132 | ${cglib.version} 133 | 134 | 135 | 136 | 137 | org.hibernate 138 | hibernate-entitymanager 139 | ${hibernate-entitymanager.version} 140 | 141 | 142 | org.hibernate.javax.persistence 143 | hibernate-jpa-2.0-api 144 | ${hibernate-jpa-api.version} 145 | 146 | 147 | org.hibernate 148 | hibernate-validator 149 | ${hibernate.version} 150 | 151 | 152 | 153 | 154 | com.fasterxml.jackson.core 155 | jackson-databind 156 | ${jackson.version} 157 | 158 | 159 | commons-dbcp 160 | commons-dbcp 161 | ${commons-dbcp.version} 162 | 163 | 164 | commons-pool 165 | commons-pool 166 | ${commons-pool.version} 167 | 168 | 169 | 170 | 171 | javax.servlet 172 | javax.servlet-api 173 | ${servlet.version} 174 | 175 | 176 | javax.servlet 177 | jstl 178 | 1.2 179 | 180 | 181 | 182 | org.apache.activemq 183 | activemq-core 184 | ${activemq.version} 185 | 186 | 187 | org.apache.activemq 188 | activemq-pool 189 | ${activemq.version} 190 | 191 | 192 | 193 | 194 | org.apache.httpcomponents 195 | httpasyncclient 196 | ${httpasyncclient.version} 197 | 198 | 199 | org.apache.tomcat.embed 200 | tomcat-embed-core 201 | ${tomcat.version} 202 | 203 | 204 | org.apache.tomcat.embed 205 | tomcat-embed-jasper 206 | ${tomcat.version} 207 | 208 | 209 | org.apache.tomcat 210 | tomcat-jdbc 211 | ${tomcat.version} 212 | 213 | 214 | org.apache.tomcat 215 | tomcat-jsp-api 216 | ${tomcat.version} 217 | 218 | 219 | 220 | 221 | org.slf4j 222 | slf4j-api 223 | ${slf4j.version} 224 | 225 | 226 | ch.qos.logback 227 | logback-classic 228 | ${logback.version} 229 | runtime 230 | 231 | 232 | 233 | 234 | de.neuland-bfi 235 | spring-jade4j 236 | ${spring-jade4j.version} 237 | 238 | 239 | org.thymeleaf 240 | thymeleaf-spring3 241 | ${thymeleaf.version} 242 | 243 | 244 | 245 | 246 | joda-time 247 | joda-time 248 | ${joda-time.version} 249 | 250 | 251 | 252 | 253 | 254 | org.springframework 255 | spring-test 256 | ${spring.version} 257 | test 258 | 259 | 260 | junit 261 | junit 262 | ${junit.version} 263 | test 264 | 265 | 266 | 267 | 268 | 269 | 270 | org.apache.maven.plugins 271 | maven-compiler-plugin 272 | 2.3.2 273 | 274 | ${java.version} 275 | ${java.version} 276 | ${project.build.sourceEncoding} 277 | 278 | 279 | 280 | maven-jar-plugin 281 | 2.4 282 | 283 | 284 | 285 | net.sample.app.Application 286 | 287 | 288 | 289 | 290 | 291 | maven-war-plugin 292 | 2.3 293 | 294 | false 295 | 296 | 297 | hello.Application 298 | 299 | 300 | 301 | 302 | 303 | org.apache.maven.plugins 304 | maven-shade-plugin 305 | 1.6 306 | 307 | 308 | package 309 | 310 | shade 311 | 312 | 313 | 314 | 316 | net.sample.app.Application 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /src/main/java/net/sample/app/AppConfig.java: -------------------------------------------------------------------------------- 1 | package net.sample.app; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 6 | 7 | @Configuration @ComponentScan(basePackages = {"net.sample.app"}) 8 | public class AppConfig extends WebMvcConfigurerAdapter { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/net/sample/app/AppController.java: -------------------------------------------------------------------------------- 1 | package net.sample.app; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.text.DateFormat; 6 | import java.util.Date; 7 | import java.util.Map; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.messaging.handler.annotation.MessageMapping; 15 | import org.springframework.messaging.handler.annotation.SendTo; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.ui.Model; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.ResponseBody; 21 | 22 | @Controller 23 | public class AppController { 24 | 25 | private final static Logger log = LoggerFactory.getLogger(AppController.class); 26 | 27 | @RequestMapping("/sample{ext:\\.\\w+}") 28 | public String index(@PathVariable("ext") String ext, HttpServletRequest req, Model model){ 29 | 30 | model.addAttribute("date", DateFormat.getDateInstance(DateFormat.MEDIUM, req.getLocale()).format(new Date())); 31 | 32 | log.info("sample with ext : " + ext); 33 | 34 | if(".jade".equals(ext)){ 35 | return "jade4j"; 36 | }else if(".tl".equals(ext)){ 37 | return "thymeleaf"; 38 | }else return "main"; 39 | } 40 | 41 | @RequestMapping("/") 42 | public @ResponseBody void htmlString(HttpServletResponse res) throws IOException{ 43 | res.setContentType("text/html;charset=utf-8"); 44 | 45 | PrintWriter out = res.getWriter(); 46 | 47 | out.write("

Hello World!

"); 48 | out.write(""); 54 | 55 | } 56 | 57 | @MessageMapping("/hello") 58 | @SendTo("/topic/greetings") 59 | public String greeting(Map message) throws Exception { 60 | Thread.sleep(3000); // simulated delay 61 | return "Hello, " + message.get("msg") + "!"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/sample/app/Application.java: -------------------------------------------------------------------------------- 1 | package net.sample.app; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.InetAddress; 6 | import java.net.ServerSocket; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import org.apache.catalina.Context; 11 | import org.apache.catalina.Host; 12 | import org.apache.catalina.Wrapper; 13 | import org.apache.catalina.connector.Connector; 14 | import org.apache.catalina.deploy.ApplicationListener; 15 | import org.apache.catalina.loader.WebappLoader; 16 | import org.apache.catalina.startup.Tomcat; 17 | import org.apache.commons.logging.Log; 18 | import org.apache.commons.logging.LogFactory; 19 | import org.apache.coyote.http11.Http11NioProtocol; 20 | import org.springframework.web.context.ContextLoaderListener; 21 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; 22 | import org.springframework.web.servlet.DispatcherServlet; 23 | 24 | public class Application { 25 | 26 | private final static String ROOT = "/"; 27 | private final static String SERVLET = "appServlet"; 28 | 29 | private final static Log log = LogFactory.getLog(Application.class); 30 | 31 | public static void main(String[] args) throws Exception { 32 | 33 | final File tmp = createBaseDirectory(); 34 | final Tomcat tomcat = new Tomcat(); 35 | File appbase = null; 36 | int port = 8080; 37 | 38 | //인자 파싱 39 | List argkeys = Arrays.asList("appbase","port"); 40 | for(String arg : args){ 41 | if(arg.contains("=")){ 42 | String[] kv = arg.split("="); 43 | if(kv.length == 2){ 44 | switch(argkeys.indexOf(kv[0])){ 45 | case 0://appbase 46 | appbase = new File(kv[1]); 47 | if(!appbase.isDirectory() || !appbase.exists()){ 48 | log.warn("path '"+kv[1]+"' is not vaild directory or not exists."); 49 | appbase = tmp; 50 | } 51 | break; 52 | case 1://port 53 | try{ 54 | port = Integer.parseInt(kv[1]); 55 | }catch(NumberFormatException e){ 56 | log.warn("port '"+kv[1]+"' is not vaild port."); 57 | } 58 | break; 59 | } 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | ServerSocket socket = null; 67 | try{ 68 | socket = new ServerSocket(port); 69 | }catch(IOException e){ 70 | log.error("port " + port + " is already in use. please try another port."); 71 | System.exit(1); 72 | return; 73 | }finally{ 74 | socket.close(); 75 | } 76 | 77 | log.info("Tomcat port: " + port); 78 | log.info("Tomcat workdir: " + tmp.getAbsolutePath()); 79 | 80 | tomcat.setPort(port); 81 | tomcat.setBaseDir(tmp.getAbsolutePath()); 82 | 83 | Host host = tomcat.getHost(); 84 | host.setAppBase(tmp.getAbsolutePath()); 85 | host.setAutoDeploy(true); 86 | host.setDeployOnStartup(true); 87 | 88 | //web.xml 구현 89 | Context context = tomcat.addContext(ROOT, ""); 90 | Tomcat.initWebappDefaults(context);//기본형 web.xml 구현화 (필수) 91 | 92 | context.setLoader(new WebappLoader(Thread.currentThread().getContextClassLoader())); 93 | 94 | //Spring 리스너 및 AppServlet 구성 95 | context.addApplicationListener(new ApplicationListener(ContextLoaderListener.class.getName(), false)); 96 | Wrapper appServlet = tomcat.addServlet(ROOT, SERVLET, new DispatcherServlet(new AnnotationConfigWebApplicationContext(){{ 97 | register(ServletConfig.class); 98 | }})); 99 | appServlet.setAsyncSupported(true); 100 | appServlet.setLoadOnStartup(1); 101 | context.addServletMapping(ROOT, SERVLET); 102 | 103 | //??? 104 | context.addParameter("contextClass", AnnotationConfigWebApplicationContext.class.getName()); 105 | context.addParameter("contextConfigLocation", AppConfig.class.getName()); 106 | 107 | // Non-blocking IO 프로토콜로 작동 108 | Connector connector = new Connector(Http11NioProtocol.class.getName()); 109 | connector.setPort(port); 110 | connector.setProperty("address", InetAddress.getByName("localhost").getHostAddress()); 111 | connector.setXpoweredBy(false); 112 | connector.setURIEncoding("UTF-8"); 113 | connector.setAttribute("connectionTimeout", "3000"); 114 | connector.setAttribute("useComet", "false"); 115 | connector.setAttribute("socket.directBuffer", "true"); 116 | connector.setAttribute("pollerThreadCount", "2"); 117 | connector.setAttribute("maxThreads", "800"); 118 | connector.setAttribute("processorCache", "800"); 119 | tomcat.getService().addConnector(connector); 120 | tomcat.setConnector(connector); 121 | 122 | 123 | //톰캣 시작 124 | tomcat.start(); 125 | tomcat.getServer().await(); 126 | } 127 | 128 | private static File createBaseDirectory() throws IOException { 129 | final File base = File.createTempFile("tmp-", ""); 130 | 131 | if (!base.delete()) { 132 | throw new IOException("Cannot (re)create base folder: " 133 | + base.getAbsolutePath()); 134 | } 135 | 136 | if (!base.mkdir()) { 137 | throw new IOException("Cannot create base folder: " 138 | + base.getAbsolutePath()); 139 | } 140 | 141 | return base; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/net/sample/app/ServletConfig.java: -------------------------------------------------------------------------------- 1 | package net.sample.app; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | import org.springframework.context.annotation.PropertySource; 11 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 12 | import org.springframework.context.support.ReloadableResourceBundleMessageSource; 13 | import org.springframework.core.io.ClassPathResource; 14 | import org.springframework.core.io.Resource; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.web.servlet.ViewResolver; 17 | import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; 18 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 19 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 20 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 21 | import org.springframework.web.servlet.view.BeanNameViewResolver; 22 | import org.thymeleaf.spring3.SpringTemplateEngine; 23 | import org.thymeleaf.spring3.view.ThymeleafViewResolver; 24 | import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; 25 | import org.thymeleaf.templateresolver.TemplateResolver; 26 | 27 | import de.neuland.jade4j.JadeConfiguration; 28 | import de.neuland.jade4j.spring.template.SpringTemplateLoader; 29 | import de.neuland.jade4j.spring.view.JadeViewResolver; 30 | 31 | @Configuration @Import({WebsocketConfig.class}) 32 | @EnableWebMvc 33 | @ComponentScan(basePackages = {"net.sample.app"}) 34 | @PropertySource("classpath:sample.properties") 35 | public class ServletConfig extends WebMvcConfigurerAdapter { 36 | @Override 37 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 38 | registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/").setCachePeriod(31556926); 39 | registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/").setCachePeriod(31556926); 40 | registry.addResourceHandler("/resource/**").addResourceLocations("classpath:/static/resource/").setCachePeriod(31556926); 41 | } 42 | 43 | @Override 44 | public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 45 | configurer.ignoreAcceptHeader(true); 46 | Map mediaTypes = new HashMap(); 47 | mediaTypes.put("json", MediaType.APPLICATION_JSON); 48 | mediaTypes.put("xml", MediaType.APPLICATION_XML); 49 | mediaTypes.put("xml2", MediaType.TEXT_XML); 50 | mediaTypes.put("xhtml", MediaType.APPLICATION_XHTML_XML); 51 | mediaTypes.put("html", MediaType.TEXT_HTML); 52 | configurer.mediaTypes(mediaTypes); 53 | configurer.defaultContentType(MediaType.TEXT_HTML); 54 | } 55 | 56 | // Only needed if we are using @Value and ${...} when referencing properties 57 | @Bean 58 | public static PropertySourcesPlaceholderConfigurer properties() { 59 | PropertySourcesPlaceholderConfigurer propertySources = new PropertySourcesPlaceholderConfigurer(); 60 | Resource[] resources = new ClassPathResource[] { new ClassPathResource("sample.properties") }; 61 | propertySources.setLocations(resources); 62 | propertySources.setIgnoreUnresolvablePlaceholders(true); 63 | return propertySources; 64 | } 65 | 66 | // Provides internationalization of messages 67 | @Bean 68 | public ReloadableResourceBundleMessageSource messageSource() { 69 | ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource(); 70 | source.setBasename("messages"); 71 | source.setDefaultEncoding("UTF-8"); 72 | return source; 73 | } 74 | 75 | /////////////////////////// 76 | // BeanNameViewResolver 77 | /////////////////////////// 78 | @Bean 79 | public BeanNameViewResolver beanNameViewResolver(){ 80 | BeanNameViewResolver viewResolver = new BeanNameViewResolver(); 81 | viewResolver.setOrder(1); 82 | return viewResolver; 83 | } 84 | 85 | /////////////////////////// 86 | // View Engine : jade 87 | /////////////////////////// 88 | @Bean 89 | public SpringTemplateLoader templateLoader() { 90 | SpringTemplateLoader templateLoader = new SpringTemplateLoader(); 91 | templateLoader.setBasePath("classpath:/views/"); 92 | templateLoader.setEncoding("UTF-8"); 93 | templateLoader.setSuffix(".jade"); 94 | return templateLoader; 95 | } 96 | 97 | @Bean 98 | public JadeConfiguration jadeConfiguration() { 99 | JadeConfiguration configuration = new JadeConfiguration(); 100 | configuration.setCaching(false); 101 | configuration.setTemplateLoader(templateLoader()); 102 | return configuration; 103 | } 104 | 105 | @Bean 106 | public ViewResolver jadeViewResolver() { 107 | JadeViewResolver viewResolver = new JadeViewResolver(); 108 | viewResolver.setConfiguration(jadeConfiguration()); 109 | viewResolver.setOrder(2); 110 | return viewResolver; 111 | } 112 | 113 | /////////////////////////// 114 | // View Engine : Thymeleaf 115 | /////////////////////////// 116 | @Bean 117 | public TemplateResolver templateResolver(){ 118 | ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); 119 | templateResolver.setPrefix("views/"); 120 | templateResolver.setSuffix(".html"); 121 | templateResolver.setTemplateMode("HTML5"); 122 | templateResolver.setCharacterEncoding("UTF-8"); 123 | templateResolver.setCacheable(false); 124 | return templateResolver; 125 | } 126 | @Bean 127 | public SpringTemplateEngine springTemplateEngine(){ 128 | SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine(); 129 | springTemplateEngine.setTemplateResolver(templateResolver()); 130 | return springTemplateEngine; 131 | } 132 | @Bean 133 | public ThymeleafViewResolver thymeleafViewResolver(){ 134 | ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); 135 | viewResolver.setTemplateEngine(springTemplateEngine()); 136 | viewResolver.setOrder(3); 137 | return viewResolver; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/net/sample/app/WebsocketConfig.java: -------------------------------------------------------------------------------- 1 | package net.sample.app; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.ChannelRegistration; 5 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 6 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 7 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 8 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 9 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 10 | 11 | @Configuration 12 | @EnableWebSocket 13 | @EnableWebSocketMessageBroker 14 | public class WebsocketConfig implements WebSocketMessageBrokerConfigurer { 15 | 16 | @Override 17 | public void configureMessageBroker(MessageBrokerRegistry config) { 18 | config.enableSimpleBroker("/topic"); 19 | config.setApplicationDestinationPrefixes("/app"); 20 | } 21 | 22 | @Override 23 | public void registerStompEndpoints(StompEndpointRegistry registry) { 24 | registry.addEndpoint("/hello").withSockJS(); 25 | } 26 | 27 | @Override 28 | public void configureClientInboundChannel(ChannelRegistration channelRegistration) { 29 | } 30 | 31 | @Override 32 | public void configureClientOutboundChannel(ChannelRegistration channelRegistration) { 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/views/main.jsp: -------------------------------------------------------------------------------- 1 | <%@page contentType="text/html" pageEncoding="UTF-8" %> 2 | 3 | 4 | 5 | Spring View 6 | 7 | 8 |

Hello world!

9 |

Using JSTL Date : ${date }

10 |

Using plain JSP Date : <%=request.getAttribute("date") %>

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/sample.properties: -------------------------------------------------------------------------------- 1 | this.is.sample = just sample. -------------------------------------------------------------------------------- /src/main/resources/static/css/some.css: -------------------------------------------------------------------------------- 1 | html,body{width:100%;height:100%;} 2 | -------------------------------------------------------------------------------- /src/main/resources/static/js/sockjs-0.3.4.js: -------------------------------------------------------------------------------- 1 | /* SockJS client, version 0.3.4, http://sockjs.org, MIT License 2 | 3 | Copyright (c) 2011-2012 VMware, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | // JSON2 by Douglas Crockford (minified). 25 | 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;c 1) { 71 | this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) ); 72 | } else { 73 | delete this._listeners[eventType]; 74 | } 75 | return; 76 | } 77 | return; 78 | }; 79 | 80 | REventTarget.prototype.dispatchEvent = function (event) { 81 | var t = event.type; 82 | var args = Array.prototype.slice.call(arguments, 0); 83 | if (this['on'+t]) { 84 | this['on'+t].apply(this, args); 85 | } 86 | if (this._listeners && t in this._listeners) { 87 | for(var i=0; i < this._listeners[t].length; i++) { 88 | this._listeners[t][i].apply(this, args); 89 | } 90 | } 91 | }; 92 | // [*] End of lib/reventtarget.js 93 | 94 | 95 | // [*] Including lib/simpleevent.js 96 | /* 97 | * ***** BEGIN LICENSE BLOCK ***** 98 | * Copyright (c) 2011-2012 VMware, Inc. 99 | * 100 | * For the license see COPYING. 101 | * ***** END LICENSE BLOCK ***** 102 | */ 103 | 104 | var SimpleEvent = function(type, obj) { 105 | this.type = type; 106 | if (typeof obj !== 'undefined') { 107 | for(var k in obj) { 108 | if (!obj.hasOwnProperty(k)) continue; 109 | this[k] = obj[k]; 110 | } 111 | } 112 | }; 113 | 114 | SimpleEvent.prototype.toString = function() { 115 | var r = []; 116 | for(var k in this) { 117 | if (!this.hasOwnProperty(k)) continue; 118 | var v = this[k]; 119 | if (typeof v === 'function') v = '[function]'; 120 | r.push(k + '=' + v); 121 | } 122 | return 'SimpleEvent(' + r.join(', ') + ')'; 123 | }; 124 | // [*] End of lib/simpleevent.js 125 | 126 | 127 | // [*] Including lib/eventemitter.js 128 | /* 129 | * ***** BEGIN LICENSE BLOCK ***** 130 | * Copyright (c) 2011-2012 VMware, Inc. 131 | * 132 | * For the license see COPYING. 133 | * ***** END LICENSE BLOCK ***** 134 | */ 135 | 136 | var EventEmitter = function(events) { 137 | var that = this; 138 | that._events = events || []; 139 | that._listeners = {}; 140 | }; 141 | EventEmitter.prototype.emit = function(type) { 142 | var that = this; 143 | that._verifyType(type); 144 | if (that._nuked) return; 145 | 146 | var args = Array.prototype.slice.call(arguments, 1); 147 | if (that['on'+type]) { 148 | that['on'+type].apply(that, args); 149 | } 150 | if (type in that._listeners) { 151 | for(var i = 0; i < that._listeners[type].length; i++) { 152 | that._listeners[type][i].apply(that, args); 153 | } 154 | } 155 | }; 156 | 157 | EventEmitter.prototype.on = function(type, callback) { 158 | var that = this; 159 | that._verifyType(type); 160 | if (that._nuked) return; 161 | 162 | if (!(type in that._listeners)) { 163 | that._listeners[type] = []; 164 | } 165 | that._listeners[type].push(callback); 166 | }; 167 | 168 | EventEmitter.prototype._verifyType = function(type) { 169 | var that = this; 170 | if (utils.arrIndexOf(that._events, type) === -1) { 171 | utils.log('Event ' + JSON.stringify(type) + 172 | ' not listed ' + JSON.stringify(that._events) + 173 | ' in ' + that); 174 | } 175 | }; 176 | 177 | EventEmitter.prototype.nuke = function() { 178 | var that = this; 179 | that._nuked = true; 180 | for(var i=0; i= 3000 && code <= 4999); 266 | }; 267 | 268 | // See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/ 269 | // and RFC 2988. 270 | utils.countRTO = function (rtt) { 271 | var rto; 272 | if (rtt > 100) { 273 | rto = 3 * rtt; // rto > 300msec 274 | } else { 275 | rto = rtt + 200; // 200msec < rto <= 300msec 276 | } 277 | return rto; 278 | } 279 | 280 | utils.log = function() { 281 | if (_window.console && console.log && console.log.apply) { 282 | console.log.apply(console, arguments); 283 | } 284 | }; 285 | 286 | utils.bind = function(fun, that) { 287 | if (fun.bind) { 288 | return fun.bind(that); 289 | } else { 290 | return function() { 291 | return fun.apply(that, arguments); 292 | }; 293 | } 294 | }; 295 | 296 | utils.flatUrl = function(url) { 297 | return url.indexOf('?') === -1 && url.indexOf('#') === -1; 298 | }; 299 | 300 | utils.amendUrl = function(url) { 301 | var dl = _document.location; 302 | if (!url) { 303 | throw new Error('Wrong url for SockJS'); 304 | } 305 | if (!utils.flatUrl(url)) { 306 | throw new Error('Only basic urls are supported in SockJS'); 307 | } 308 | 309 | // '//abc' --> 'http://abc' 310 | if (url.indexOf('//') === 0) { 311 | url = dl.protocol + url; 312 | } 313 | // '/abc' --> 'http://localhost:80/abc' 314 | if (url.indexOf('/') === 0) { 315 | url = dl.protocol + '//' + dl.host + url; 316 | } 317 | // strip trailing slashes 318 | url = url.replace(/[/]+$/,''); 319 | return url; 320 | }; 321 | 322 | // IE doesn't support [].indexOf. 323 | utils.arrIndexOf = function(arr, obj){ 324 | for(var i=0; i < arr.length; i++){ 325 | if(arr[i] === obj){ 326 | return i; 327 | } 328 | } 329 | return -1; 330 | }; 331 | 332 | utils.arrSkip = function(arr, obj) { 333 | var idx = utils.arrIndexOf(arr, obj); 334 | if (idx === -1) { 335 | return arr.slice(); 336 | } else { 337 | var dst = arr.slice(0, idx); 338 | return dst.concat(arr.slice(idx+1)); 339 | } 340 | }; 341 | 342 | // Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df 343 | utils.isArray = Array.isArray || function(value) { 344 | return {}.toString.call(value).indexOf('Array') >= 0 345 | }; 346 | 347 | utils.delay = function(t, fun) { 348 | if(typeof t === 'function') { 349 | fun = t; 350 | t = 0; 351 | } 352 | return setTimeout(fun, t); 353 | }; 354 | 355 | 356 | // Chars worth escaping, as defined by Douglas Crockford: 357 | // https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196 358 | var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 359 | json_lookup = { 360 | "\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003", 361 | "\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007", 362 | "\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r", 363 | "\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011", 364 | "\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015", 365 | "\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019", 366 | "\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d", 367 | "\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\", 368 | "\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082", 369 | "\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086", 370 | "\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a", 371 | "\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e", 372 | "\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092", 373 | "\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096", 374 | "\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a", 375 | "\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e", 376 | "\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601", 377 | "\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f", 378 | "\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d", 379 | "\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029", 380 | "\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d", 381 | "\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061", 382 | "\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065", 383 | "\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069", 384 | "\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d", 385 | "\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0", 386 | "\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4", 387 | "\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8", 388 | "\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc", 389 | "\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"}; 390 | 391 | // Some extra characters that Chrome gets wrong, and substitutes with 392 | // something else on the wire. 393 | var extra_escapable = /[\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, 394 | extra_lookup; 395 | 396 | // JSON Quote string. Use native implementation when possible. 397 | var JSONQuote = (JSON && JSON.stringify) || function(string) { 398 | json_escapable.lastIndex = 0; 399 | if (json_escapable.test(string)) { 400 | string = string.replace(json_escapable, function(a) { 401 | return json_lookup[a]; 402 | }); 403 | } 404 | return '"' + string + '"'; 405 | }; 406 | 407 | // This may be quite slow, so let's delay until user actually uses bad 408 | // characters. 409 | var unroll_lookup = function(escapable) { 410 | var i; 411 | var unrolled = {} 412 | var c = [] 413 | for(i=0; i<65536; i++) { 414 | c.push( String.fromCharCode(i) ); 415 | } 416 | escapable.lastIndex = 0; 417 | c.join('').replace(escapable, function (a) { 418 | unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 419 | return ''; 420 | }); 421 | escapable.lastIndex = 0; 422 | return unrolled; 423 | }; 424 | 425 | // Quote string, also taking care of unicode characters that browsers 426 | // often break. Especially, take care of unicode surrogates: 427 | // http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates 428 | utils.quote = function(string) { 429 | var quoted = JSONQuote(string); 430 | 431 | // In most cases this should be very fast and good enough. 432 | extra_escapable.lastIndex = 0; 433 | if(!extra_escapable.test(quoted)) { 434 | return quoted; 435 | } 436 | 437 | if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable); 438 | 439 | return quoted.replace(extra_escapable, function(a) { 440 | return extra_lookup[a]; 441 | }); 442 | } 443 | 444 | var _all_protocols = ['websocket', 445 | 'xdr-streaming', 446 | 'xhr-streaming', 447 | 'iframe-eventsource', 448 | 'iframe-htmlfile', 449 | 'xdr-polling', 450 | 'xhr-polling', 451 | 'iframe-xhr-polling', 452 | 'jsonp-polling']; 453 | 454 | utils.probeProtocols = function() { 455 | var probed = {}; 456 | for(var i=0; i<_all_protocols.length; i++) { 457 | var protocol = _all_protocols[i]; 458 | // User can have a typo in protocol name. 459 | probed[protocol] = SockJS[protocol] && 460 | SockJS[protocol].enabled(); 461 | } 462 | return probed; 463 | }; 464 | 465 | utils.detectProtocols = function(probed, protocols_whitelist, info) { 466 | var pe = {}, 467 | protocols = []; 468 | if (!protocols_whitelist) protocols_whitelist = _all_protocols; 469 | for(var i=0; i 0) { 479 | maybe_push(protos); 480 | } 481 | } 482 | } 483 | 484 | // 1. Websocket 485 | if (info.websocket !== false) { 486 | maybe_push(['websocket']); 487 | } 488 | 489 | // 2. Streaming 490 | if (pe['xhr-streaming'] && !info.null_origin) { 491 | protocols.push('xhr-streaming'); 492 | } else { 493 | if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) { 494 | protocols.push('xdr-streaming'); 495 | } else { 496 | maybe_push(['iframe-eventsource', 497 | 'iframe-htmlfile']); 498 | } 499 | } 500 | 501 | // 3. Polling 502 | if (pe['xhr-polling'] && !info.null_origin) { 503 | protocols.push('xhr-polling'); 504 | } else { 505 | if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) { 506 | protocols.push('xdr-polling'); 507 | } else { 508 | maybe_push(['iframe-xhr-polling', 509 | 'jsonp-polling']); 510 | } 511 | } 512 | return protocols; 513 | } 514 | // [*] End of lib/utils.js 515 | 516 | 517 | // [*] Including lib/dom.js 518 | /* 519 | * ***** BEGIN LICENSE BLOCK ***** 520 | * Copyright (c) 2011-2012 VMware, Inc. 521 | * 522 | * For the license see COPYING. 523 | * ***** END LICENSE BLOCK ***** 524 | */ 525 | 526 | // May be used by htmlfile jsonp and transports. 527 | var MPrefix = '_sockjs_global'; 528 | utils.createHook = function() { 529 | var window_id = 'a' + utils.random_string(8); 530 | if (!(MPrefix in _window)) { 531 | var map = {}; 532 | _window[MPrefix] = function(window_id) { 533 | if (!(window_id in map)) { 534 | map[window_id] = { 535 | id: window_id, 536 | del: function() {delete map[window_id];} 537 | }; 538 | } 539 | return map[window_id]; 540 | } 541 | } 542 | return _window[MPrefix](window_id); 543 | }; 544 | 545 | 546 | 547 | utils.attachMessage = function(listener) { 548 | utils.attachEvent('message', listener); 549 | }; 550 | utils.attachEvent = function(event, listener) { 551 | if (typeof _window.addEventListener !== 'undefined') { 552 | _window.addEventListener(event, listener, false); 553 | } else { 554 | // IE quirks. 555 | // According to: http://stevesouders.com/misc/test-postmessage.php 556 | // the message gets delivered only to 'document', not 'window'. 557 | _document.attachEvent("on" + event, listener); 558 | // I get 'window' for ie8. 559 | _window.attachEvent("on" + event, listener); 560 | } 561 | }; 562 | 563 | utils.detachMessage = function(listener) { 564 | utils.detachEvent('message', listener); 565 | }; 566 | utils.detachEvent = function(event, listener) { 567 | if (typeof _window.addEventListener !== 'undefined') { 568 | _window.removeEventListener(event, listener, false); 569 | } else { 570 | _document.detachEvent("on" + event, listener); 571 | _window.detachEvent("on" + event, listener); 572 | } 573 | }; 574 | 575 | 576 | var on_unload = {}; 577 | // Things registered after beforeunload are to be called immediately. 578 | var after_unload = false; 579 | 580 | var trigger_unload_callbacks = function() { 581 | for(var ref in on_unload) { 582 | on_unload[ref](); 583 | delete on_unload[ref]; 584 | }; 585 | }; 586 | 587 | var unload_triggered = function() { 588 | if(after_unload) return; 589 | after_unload = true; 590 | trigger_unload_callbacks(); 591 | }; 592 | 593 | // 'unload' alone is not reliable in opera within an iframe, but we 594 | // can't use `beforeunload` as IE fires it on javascript: links. 595 | utils.attachEvent('unload', unload_triggered); 596 | 597 | utils.unload_add = function(listener) { 598 | var ref = utils.random_string(8); 599 | on_unload[ref] = listener; 600 | if (after_unload) { 601 | utils.delay(trigger_unload_callbacks); 602 | } 603 | return ref; 604 | }; 605 | utils.unload_del = function(ref) { 606 | if (ref in on_unload) 607 | delete on_unload[ref]; 608 | }; 609 | 610 | 611 | utils.createIframe = function (iframe_url, error_callback) { 612 | var iframe = _document.createElement('iframe'); 613 | var tref, unload_ref; 614 | var unattach = function() { 615 | clearTimeout(tref); 616 | // Explorer had problems with that. 617 | try {iframe.onload = null;} catch (x) {} 618 | iframe.onerror = null; 619 | }; 620 | var cleanup = function() { 621 | if (iframe) { 622 | unattach(); 623 | // This timeout makes chrome fire onbeforeunload event 624 | // within iframe. Without the timeout it goes straight to 625 | // onunload. 626 | setTimeout(function() { 627 | if(iframe) { 628 | iframe.parentNode.removeChild(iframe); 629 | } 630 | iframe = null; 631 | }, 0); 632 | utils.unload_del(unload_ref); 633 | } 634 | }; 635 | var onerror = function(r) { 636 | if (iframe) { 637 | cleanup(); 638 | error_callback(r); 639 | } 640 | }; 641 | var post = function(msg, origin) { 642 | try { 643 | // When the iframe is not loaded, IE raises an exception 644 | // on 'contentWindow'. 645 | if (iframe && iframe.contentWindow) { 646 | iframe.contentWindow.postMessage(msg, origin); 647 | } 648 | } catch (x) {}; 649 | }; 650 | 651 | iframe.src = iframe_url; 652 | iframe.style.display = 'none'; 653 | iframe.style.position = 'absolute'; 654 | iframe.onerror = function(){onerror('onerror');}; 655 | iframe.onload = function() { 656 | // `onload` is triggered before scripts on the iframe are 657 | // executed. Give it few seconds to actually load stuff. 658 | clearTimeout(tref); 659 | tref = setTimeout(function(){onerror('onload timeout');}, 2000); 660 | }; 661 | _document.body.appendChild(iframe); 662 | tref = setTimeout(function(){onerror('timeout');}, 15000); 663 | unload_ref = utils.unload_add(cleanup); 664 | return { 665 | post: post, 666 | cleanup: cleanup, 667 | loaded: unattach 668 | }; 669 | }; 670 | 671 | utils.createHtmlfile = function (iframe_url, error_callback) { 672 | var doc = new ActiveXObject('htmlfile'); 673 | var tref, unload_ref; 674 | var iframe; 675 | var unattach = function() { 676 | clearTimeout(tref); 677 | }; 678 | var cleanup = function() { 679 | if (doc) { 680 | unattach(); 681 | utils.unload_del(unload_ref); 682 | iframe.parentNode.removeChild(iframe); 683 | iframe = doc = null; 684 | CollectGarbage(); 685 | } 686 | }; 687 | var onerror = function(r) { 688 | if (doc) { 689 | cleanup(); 690 | error_callback(r); 691 | } 692 | }; 693 | var post = function(msg, origin) { 694 | try { 695 | // When the iframe is not loaded, IE raises an exception 696 | // on 'contentWindow'. 697 | if (iframe && iframe.contentWindow) { 698 | iframe.contentWindow.postMessage(msg, origin); 699 | } 700 | } catch (x) {}; 701 | }; 702 | 703 | doc.open(); 704 | doc.write('' + 705 | 'document.domain="' + document.domain + '";' + 706 | ''); 707 | doc.close(); 708 | doc.parentWindow[WPrefix] = _window[WPrefix]; 709 | var c = doc.createElement('div'); 710 | doc.body.appendChild(c); 711 | iframe = doc.createElement('iframe'); 712 | c.appendChild(iframe); 713 | iframe.src = iframe_url; 714 | tref = setTimeout(function(){onerror('timeout');}, 15000); 715 | unload_ref = utils.unload_add(cleanup); 716 | return { 717 | post: post, 718 | cleanup: cleanup, 719 | loaded: unattach 720 | }; 721 | }; 722 | // [*] End of lib/dom.js 723 | 724 | 725 | // [*] Including lib/dom2.js 726 | /* 727 | * ***** BEGIN LICENSE BLOCK ***** 728 | * Copyright (c) 2011-2012 VMware, Inc. 729 | * 730 | * For the license see COPYING. 731 | * ***** END LICENSE BLOCK ***** 732 | */ 733 | 734 | var AbstractXHRObject = function(){}; 735 | AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']); 736 | 737 | AbstractXHRObject.prototype._start = function(method, url, payload, opts) { 738 | var that = this; 739 | 740 | try { 741 | that.xhr = new XMLHttpRequest(); 742 | } catch(x) {}; 743 | 744 | if (!that.xhr) { 745 | try { 746 | that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP'); 747 | } catch(x) {}; 748 | } 749 | if (_window.ActiveXObject || _window.XDomainRequest) { 750 | // IE8 caches even POSTs 751 | url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); 752 | } 753 | 754 | // Explorer tends to keep connection open, even after the 755 | // tab gets closed: http://bugs.jquery.com/ticket/5280 756 | that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); 757 | try { 758 | that.xhr.open(method, url, true); 759 | } catch(e) { 760 | // IE raises an exception on wrong port. 761 | that.emit('finish', 0, ''); 762 | that._cleanup(); 763 | return; 764 | }; 765 | 766 | if (!opts || !opts.no_credentials) { 767 | // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest : 768 | // "This never affects same-site requests." 769 | that.xhr.withCredentials = 'true'; 770 | } 771 | if (opts && opts.headers) { 772 | for(var key in opts.headers) { 773 | that.xhr.setRequestHeader(key, opts.headers[key]); 774 | } 775 | } 776 | 777 | that.xhr.onreadystatechange = function() { 778 | if (that.xhr) { 779 | var x = that.xhr; 780 | switch (x.readyState) { 781 | case 3: 782 | // IE doesn't like peeking into responseText or status 783 | // on Microsoft.XMLHTTP and readystate=3 784 | try { 785 | var status = x.status; 786 | var text = x.responseText; 787 | } catch (x) {}; 788 | // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 789 | if (status === 1223) status = 204; 790 | 791 | // IE does return readystate == 3 for 404 answers. 792 | if (text && text.length > 0) { 793 | that.emit('chunk', status, text); 794 | } 795 | break; 796 | case 4: 797 | var status = x.status; 798 | // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 799 | if (status === 1223) status = 204; 800 | 801 | that.emit('finish', status, x.responseText); 802 | that._cleanup(false); 803 | break; 804 | } 805 | } 806 | }; 807 | that.xhr.send(payload); 808 | }; 809 | 810 | AbstractXHRObject.prototype._cleanup = function(abort) { 811 | var that = this; 812 | if (!that.xhr) return; 813 | utils.unload_del(that.unload_ref); 814 | 815 | // IE needs this field to be a function 816 | that.xhr.onreadystatechange = function(){}; 817 | 818 | if (abort) { 819 | try { 820 | that.xhr.abort(); 821 | } catch(x) {}; 822 | } 823 | that.unload_ref = that.xhr = null; 824 | }; 825 | 826 | AbstractXHRObject.prototype.close = function() { 827 | var that = this; 828 | that.nuke(); 829 | that._cleanup(true); 830 | }; 831 | 832 | var XHRCorsObject = utils.XHRCorsObject = function() { 833 | var that = this, args = arguments; 834 | utils.delay(function(){that._start.apply(that, args);}); 835 | }; 836 | XHRCorsObject.prototype = new AbstractXHRObject(); 837 | 838 | var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) { 839 | var that = this; 840 | utils.delay(function(){ 841 | that._start(method, url, payload, { 842 | no_credentials: true 843 | }); 844 | }); 845 | }; 846 | XHRLocalObject.prototype = new AbstractXHRObject(); 847 | 848 | 849 | 850 | // References: 851 | // http://ajaxian.com/archives/100-line-ajax-wrapper 852 | // http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx 853 | var XDRObject = utils.XDRObject = function(method, url, payload) { 854 | var that = this; 855 | utils.delay(function(){that._start(method, url, payload);}); 856 | }; 857 | XDRObject.prototype = new EventEmitter(['chunk', 'finish']); 858 | XDRObject.prototype._start = function(method, url, payload) { 859 | var that = this; 860 | var xdr = new XDomainRequest(); 861 | // IE caches even POSTs 862 | url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); 863 | 864 | var onerror = xdr.ontimeout = xdr.onerror = function() { 865 | that.emit('finish', 0, ''); 866 | that._cleanup(false); 867 | }; 868 | xdr.onprogress = function() { 869 | that.emit('chunk', 200, xdr.responseText); 870 | }; 871 | xdr.onload = function() { 872 | that.emit('finish', 200, xdr.responseText); 873 | that._cleanup(false); 874 | }; 875 | that.xdr = xdr; 876 | that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); 877 | try { 878 | // Fails with AccessDenied if port number is bogus 879 | that.xdr.open(method, url); 880 | that.xdr.send(payload); 881 | } catch(x) { 882 | onerror(); 883 | } 884 | }; 885 | 886 | XDRObject.prototype._cleanup = function(abort) { 887 | var that = this; 888 | if (!that.xdr) return; 889 | utils.unload_del(that.unload_ref); 890 | 891 | that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress = 892 | that.xdr.onload = null; 893 | if (abort) { 894 | try { 895 | that.xdr.abort(); 896 | } catch(x) {}; 897 | } 898 | that.unload_ref = that.xdr = null; 899 | }; 900 | 901 | XDRObject.prototype.close = function() { 902 | var that = this; 903 | that.nuke(); 904 | that._cleanup(true); 905 | }; 906 | 907 | // 1. Is natively via XHR 908 | // 2. Is natively via XDR 909 | // 3. Nope, but postMessage is there so it should work via the Iframe. 910 | // 4. Nope, sorry. 911 | utils.isXHRCorsCapable = function() { 912 | if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) { 913 | return 1; 914 | } 915 | // XDomainRequest doesn't work if page is served from file:// 916 | if (_window.XDomainRequest && _document.domain) { 917 | return 2; 918 | } 919 | if (IframeTransport.enabled()) { 920 | return 3; 921 | } 922 | return 4; 923 | }; 924 | // [*] End of lib/dom2.js 925 | 926 | 927 | // [*] Including lib/sockjs.js 928 | /* 929 | * ***** BEGIN LICENSE BLOCK ***** 930 | * Copyright (c) 2011-2012 VMware, Inc. 931 | * 932 | * For the license see COPYING. 933 | * ***** END LICENSE BLOCK ***** 934 | */ 935 | 936 | var SockJS = function(url, dep_protocols_whitelist, options) { 937 | if (this === _window) { 938 | // makes `new` optional 939 | return new SockJS(url, dep_protocols_whitelist, options); 940 | } 941 | 942 | var that = this, protocols_whitelist; 943 | that._options = {devel: false, debug: false, protocols_whitelist: [], 944 | info: undefined, rtt: undefined}; 945 | if (options) { 946 | utils.objectExtend(that._options, options); 947 | } 948 | that._base_url = utils.amendUrl(url); 949 | that._server = that._options.server || utils.random_number_string(1000); 950 | if (that._options.protocols_whitelist && 951 | that._options.protocols_whitelist.length) { 952 | protocols_whitelist = that._options.protocols_whitelist; 953 | } else { 954 | // Deprecated API 955 | if (typeof dep_protocols_whitelist === 'string' && 956 | dep_protocols_whitelist.length > 0) { 957 | protocols_whitelist = [dep_protocols_whitelist]; 958 | } else if (utils.isArray(dep_protocols_whitelist)) { 959 | protocols_whitelist = dep_protocols_whitelist 960 | } else { 961 | protocols_whitelist = null; 962 | } 963 | if (protocols_whitelist) { 964 | that._debug('Deprecated API: Use "protocols_whitelist" option ' + 965 | 'instead of supplying protocol list as a second ' + 966 | 'parameter to SockJS constructor.'); 967 | } 968 | } 969 | that._protocols = []; 970 | that.protocol = null; 971 | that.readyState = SockJS.CONNECTING; 972 | that._ir = createInfoReceiver(that._base_url); 973 | that._ir.onfinish = function(info, rtt) { 974 | that._ir = null; 975 | if (info) { 976 | if (that._options.info) { 977 | // Override if user supplies the option 978 | info = utils.objectExtend(info, that._options.info); 979 | } 980 | if (that._options.rtt) { 981 | rtt = that._options.rtt; 982 | } 983 | that._applyInfo(info, rtt, protocols_whitelist); 984 | that._didClose(); 985 | } else { 986 | that._didClose(1002, 'Can\'t connect to server', true); 987 | } 988 | }; 989 | }; 990 | // Inheritance 991 | SockJS.prototype = new REventTarget(); 992 | 993 | SockJS.version = "0.3.4"; 994 | 995 | SockJS.CONNECTING = 0; 996 | SockJS.OPEN = 1; 997 | SockJS.CLOSING = 2; 998 | SockJS.CLOSED = 3; 999 | 1000 | SockJS.prototype._debug = function() { 1001 | if (this._options.debug) 1002 | utils.log.apply(utils, arguments); 1003 | }; 1004 | 1005 | SockJS.prototype._dispatchOpen = function() { 1006 | var that = this; 1007 | if (that.readyState === SockJS.CONNECTING) { 1008 | if (that._transport_tref) { 1009 | clearTimeout(that._transport_tref); 1010 | that._transport_tref = null; 1011 | } 1012 | that.readyState = SockJS.OPEN; 1013 | that.dispatchEvent(new SimpleEvent("open")); 1014 | } else { 1015 | // The server might have been restarted, and lost track of our 1016 | // connection. 1017 | that._didClose(1006, "Server lost session"); 1018 | } 1019 | }; 1020 | 1021 | SockJS.prototype._dispatchMessage = function(data) { 1022 | var that = this; 1023 | if (that.readyState !== SockJS.OPEN) 1024 | return; 1025 | that.dispatchEvent(new SimpleEvent("message", {data: data})); 1026 | }; 1027 | 1028 | SockJS.prototype._dispatchHeartbeat = function(data) { 1029 | var that = this; 1030 | if (that.readyState !== SockJS.OPEN) 1031 | return; 1032 | that.dispatchEvent(new SimpleEvent('heartbeat', {})); 1033 | }; 1034 | 1035 | SockJS.prototype._didClose = function(code, reason, force) { 1036 | var that = this; 1037 | if (that.readyState !== SockJS.CONNECTING && 1038 | that.readyState !== SockJS.OPEN && 1039 | that.readyState !== SockJS.CLOSING) 1040 | throw new Error('INVALID_STATE_ERR'); 1041 | if (that._ir) { 1042 | that._ir.nuke(); 1043 | that._ir = null; 1044 | } 1045 | 1046 | if (that._transport) { 1047 | that._transport.doCleanup(); 1048 | that._transport = null; 1049 | } 1050 | 1051 | var close_event = new SimpleEvent("close", { 1052 | code: code, 1053 | reason: reason, 1054 | wasClean: utils.userSetCode(code)}); 1055 | 1056 | if (!utils.userSetCode(code) && 1057 | that.readyState === SockJS.CONNECTING && !force) { 1058 | if (that._try_next_protocol(close_event)) { 1059 | return; 1060 | } 1061 | close_event = new SimpleEvent("close", {code: 2000, 1062 | reason: "All transports failed", 1063 | wasClean: false, 1064 | last_event: close_event}); 1065 | } 1066 | that.readyState = SockJS.CLOSED; 1067 | 1068 | utils.delay(function() { 1069 | that.dispatchEvent(close_event); 1070 | }); 1071 | }; 1072 | 1073 | SockJS.prototype._didMessage = function(data) { 1074 | var that = this; 1075 | var type = data.slice(0, 1); 1076 | switch(type) { 1077 | case 'o': 1078 | that._dispatchOpen(); 1079 | break; 1080 | case 'a': 1081 | var payload = JSON.parse(data.slice(1) || '[]'); 1082 | for(var i=0; i < payload.length; i++){ 1083 | that._dispatchMessage(payload[i]); 1084 | } 1085 | break; 1086 | case 'm': 1087 | var payload = JSON.parse(data.slice(1) || 'null'); 1088 | that._dispatchMessage(payload); 1089 | break; 1090 | case 'c': 1091 | var payload = JSON.parse(data.slice(1) || '[]'); 1092 | that._didClose(payload[0], payload[1]); 1093 | break; 1094 | case 'h': 1095 | that._dispatchHeartbeat(); 1096 | break; 1097 | } 1098 | }; 1099 | 1100 | SockJS.prototype._try_next_protocol = function(close_event) { 1101 | var that = this; 1102 | if (that.protocol) { 1103 | that._debug('Closed transport:', that.protocol, ''+close_event); 1104 | that.protocol = null; 1105 | } 1106 | if (that._transport_tref) { 1107 | clearTimeout(that._transport_tref); 1108 | that._transport_tref = null; 1109 | } 1110 | 1111 | while(1) { 1112 | var protocol = that.protocol = that._protocols.shift(); 1113 | if (!protocol) { 1114 | return false; 1115 | } 1116 | // Some protocols require access to `body`, what if were in 1117 | // the `head`? 1118 | if (SockJS[protocol] && 1119 | SockJS[protocol].need_body === true && 1120 | (!_document.body || 1121 | (typeof _document.readyState !== 'undefined' 1122 | && _document.readyState !== 'complete'))) { 1123 | that._protocols.unshift(protocol); 1124 | that.protocol = 'waiting-for-load'; 1125 | utils.attachEvent('load', function(){ 1126 | that._try_next_protocol(); 1127 | }); 1128 | return true; 1129 | } 1130 | 1131 | if (!SockJS[protocol] || 1132 | !SockJS[protocol].enabled(that._options)) { 1133 | that._debug('Skipping transport:', protocol); 1134 | } else { 1135 | var roundTrips = SockJS[protocol].roundTrips || 1; 1136 | var to = ((that._options.rto || 0) * roundTrips) || 5000; 1137 | that._transport_tref = utils.delay(to, function() { 1138 | if (that.readyState === SockJS.CONNECTING) { 1139 | // I can't understand how it is possible to run 1140 | // this timer, when the state is CLOSED, but 1141 | // apparently in IE everythin is possible. 1142 | that._didClose(2007, "Transport timeouted"); 1143 | } 1144 | }); 1145 | 1146 | var connid = utils.random_string(8); 1147 | var trans_url = that._base_url + '/' + that._server + '/' + connid; 1148 | that._debug('Opening transport:', protocol, ' url:'+trans_url, 1149 | ' RTO:'+that._options.rto); 1150 | that._transport = new SockJS[protocol](that, trans_url, 1151 | that._base_url); 1152 | return true; 1153 | } 1154 | } 1155 | }; 1156 | 1157 | SockJS.prototype.close = function(code, reason) { 1158 | var that = this; 1159 | if (code && !utils.userSetCode(code)) 1160 | throw new Error("INVALID_ACCESS_ERR"); 1161 | if(that.readyState !== SockJS.CONNECTING && 1162 | that.readyState !== SockJS.OPEN) { 1163 | return false; 1164 | } 1165 | that.readyState = SockJS.CLOSING; 1166 | that._didClose(code || 1000, reason || "Normal closure"); 1167 | return true; 1168 | }; 1169 | 1170 | SockJS.prototype.send = function(data) { 1171 | var that = this; 1172 | if (that.readyState === SockJS.CONNECTING) 1173 | throw new Error('INVALID_STATE_ERR'); 1174 | if (that.readyState === SockJS.OPEN) { 1175 | that._transport.doSend(utils.quote('' + data)); 1176 | } 1177 | return true; 1178 | }; 1179 | 1180 | SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) { 1181 | var that = this; 1182 | that._options.info = info; 1183 | that._options.rtt = rtt; 1184 | that._options.rto = utils.countRTO(rtt); 1185 | that._options.info.null_origin = !_document.domain; 1186 | var probed = utils.probeProtocols(); 1187 | that._protocols = utils.detectProtocols(probed, protocols_whitelist, info); 1188 | }; 1189 | // [*] End of lib/sockjs.js 1190 | 1191 | 1192 | // [*] Including lib/trans-websocket.js 1193 | /* 1194 | * ***** BEGIN LICENSE BLOCK ***** 1195 | * Copyright (c) 2011-2012 VMware, Inc. 1196 | * 1197 | * For the license see COPYING. 1198 | * ***** END LICENSE BLOCK ***** 1199 | */ 1200 | 1201 | var WebSocketTransport = SockJS.websocket = function(ri, trans_url) { 1202 | var that = this; 1203 | var url = trans_url + '/websocket'; 1204 | if (url.slice(0, 5) === 'https') { 1205 | url = 'wss' + url.slice(5); 1206 | } else { 1207 | url = 'ws' + url.slice(4); 1208 | } 1209 | that.ri = ri; 1210 | that.url = url; 1211 | var Constructor = _window.WebSocket || _window.MozWebSocket; 1212 | 1213 | that.ws = new Constructor(that.url); 1214 | that.ws.onmessage = function(e) { 1215 | that.ri._didMessage(e.data); 1216 | }; 1217 | // Firefox has an interesting bug. If a websocket connection is 1218 | // created after onunload, it stays alive even when user 1219 | // navigates away from the page. In such situation let's lie - 1220 | // let's not open the ws connection at all. See: 1221 | // https://github.com/sockjs/sockjs-client/issues/28 1222 | // https://bugzilla.mozilla.org/show_bug.cgi?id=696085 1223 | that.unload_ref = utils.unload_add(function(){that.ws.close()}); 1224 | that.ws.onclose = function() { 1225 | that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken")); 1226 | }; 1227 | }; 1228 | 1229 | WebSocketTransport.prototype.doSend = function(data) { 1230 | this.ws.send('[' + data + ']'); 1231 | }; 1232 | 1233 | WebSocketTransport.prototype.doCleanup = function() { 1234 | var that = this; 1235 | var ws = that.ws; 1236 | if (ws) { 1237 | ws.onmessage = ws.onclose = null; 1238 | ws.close(); 1239 | utils.unload_del(that.unload_ref); 1240 | that.unload_ref = that.ri = that.ws = null; 1241 | } 1242 | }; 1243 | 1244 | WebSocketTransport.enabled = function() { 1245 | return !!(_window.WebSocket || _window.MozWebSocket); 1246 | }; 1247 | 1248 | // In theory, ws should require 1 round trip. But in chrome, this is 1249 | // not very stable over SSL. Most likely a ws connection requires a 1250 | // separate SSL connection, in which case 2 round trips are an 1251 | // absolute minumum. 1252 | WebSocketTransport.roundTrips = 2; 1253 | // [*] End of lib/trans-websocket.js 1254 | 1255 | 1256 | // [*] Including lib/trans-sender.js 1257 | /* 1258 | * ***** BEGIN LICENSE BLOCK ***** 1259 | * Copyright (c) 2011-2012 VMware, Inc. 1260 | * 1261 | * For the license see COPYING. 1262 | * ***** END LICENSE BLOCK ***** 1263 | */ 1264 | 1265 | var BufferedSender = function() {}; 1266 | BufferedSender.prototype.send_constructor = function(sender) { 1267 | var that = this; 1268 | that.send_buffer = []; 1269 | that.sender = sender; 1270 | }; 1271 | BufferedSender.prototype.doSend = function(message) { 1272 | var that = this; 1273 | that.send_buffer.push(message); 1274 | if (!that.send_stop) { 1275 | that.send_schedule(); 1276 | } 1277 | }; 1278 | 1279 | // For polling transports in a situation when in the message callback, 1280 | // new message is being send. If the sending connection was started 1281 | // before receiving one, it is possible to saturate the network and 1282 | // timeout due to the lack of receiving socket. To avoid that we delay 1283 | // sending messages by some small time, in order to let receiving 1284 | // connection be started beforehand. This is only a halfmeasure and 1285 | // does not fix the big problem, but it does make the tests go more 1286 | // stable on slow networks. 1287 | BufferedSender.prototype.send_schedule_wait = function() { 1288 | var that = this; 1289 | var tref; 1290 | that.send_stop = function() { 1291 | that.send_stop = null; 1292 | clearTimeout(tref); 1293 | }; 1294 | tref = utils.delay(25, function() { 1295 | that.send_stop = null; 1296 | that.send_schedule(); 1297 | }); 1298 | }; 1299 | 1300 | BufferedSender.prototype.send_schedule = function() { 1301 | var that = this; 1302 | if (that.send_buffer.length > 0) { 1303 | var payload = '[' + that.send_buffer.join(',') + ']'; 1304 | that.send_stop = that.sender(that.trans_url, payload, function(success, abort_reason) { 1305 | that.send_stop = null; 1306 | if (success === false) { 1307 | that.ri._didClose(1006, 'Sending error ' + abort_reason); 1308 | } else { 1309 | that.send_schedule_wait(); 1310 | } 1311 | }); 1312 | that.send_buffer = []; 1313 | } 1314 | }; 1315 | 1316 | BufferedSender.prototype.send_destructor = function() { 1317 | var that = this; 1318 | if (that._send_stop) { 1319 | that._send_stop(); 1320 | } 1321 | that._send_stop = null; 1322 | }; 1323 | 1324 | var jsonPGenericSender = function(url, payload, callback) { 1325 | var that = this; 1326 | 1327 | if (!('_send_form' in that)) { 1328 | var form = that._send_form = _document.createElement('form'); 1329 | var area = that._send_area = _document.createElement('textarea'); 1330 | area.name = 'd'; 1331 | form.style.display = 'none'; 1332 | form.style.position = 'absolute'; 1333 | form.method = 'POST'; 1334 | form.enctype = 'application/x-www-form-urlencoded'; 1335 | form.acceptCharset = "UTF-8"; 1336 | form.appendChild(area); 1337 | _document.body.appendChild(form); 1338 | } 1339 | var form = that._send_form; 1340 | var area = that._send_area; 1341 | var id = 'a' + utils.random_string(8); 1342 | form.target = id; 1343 | form.action = url + '/jsonp_send?i=' + id; 1344 | 1345 | var iframe; 1346 | try { 1347 | // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) 1348 | iframe = _document.createElement('