Spring 4 AbstractWebSocketMessageBrokerConfigurer与SockJS无法正确协商传输 [英] Spring 4 AbstractWebSocketMessageBrokerConfigurer With SockJS Not Negotiating Transport Properly

查看:713
本文介绍了Spring 4 AbstractWebSocketMessageBrokerConfigurer与SockJS无法正确协商传输的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我必须说,所有的websocket教程/示例似乎都非常简单,但是似乎您真的需要挖掘才能找到简单示例中所没有的非常重要的信息.我在使用Spring 4 Stomp消息代理和前端SockJS的web应用程序上出现了很多问题.

So I must say that all of the websocket tutorials/examples appear to be so easy, but it seems you really have to dig to find really important pieces of information that are left out of the simple examples. I'm stil having quite a few issues with my webapp using the Spring 4 Stomp message broker with SockJS on the front end.

当前,如果我在不启用SockJS()的情况下将端点添加到StompEndpointRegistry,然后使用dojo的dojox/socket在前端声明我的套接字,则Firefox 28会很好地打开一个websocket.但是,我需要IE8和IE9的支持,因此我改用了SockJS.使用AbstractAnnotationConfigDispatcherServletInitializer,我花了很多时间弄清楚如何确保所有过滤器和servlet都设置为使用异步(为此,网络上的文档非常稀疏).解决此问题后,现在就可以在Firefox中使用它,但只能使用xhr_streaming.在sessionCookieNeeded设置为true的情况下,IE9默认尝试使用iframe进行连接,但是失败:

Currently, if I add an endpoint to the StompEndpointRegistry without enabling SockJS(), then declare my socket on the front end using dojo's dojox/socket, Firefox 28 will open a websocket just fine. However, I need support in IE8 and IE9, so I switched to SockJS. Using AbstractAnnotationConfigDispatcherServletInitializer, it took me quite a bit of time to figure out how to ensure all filters and servlets were set to use async (very sparse documentation on the web for this). Once I solved this, I can now get it to work in Firefox, but only using xhr_streaming. With sessionCookieNeeded set to true, IE9 defaults to trying to use iframes for the connection, however, it fails:

LOG: Opening Web Socket... 
LOG: Opening transport: iframe-htmlfile  url:rest/hello/904/ft3apk1g  RTO:1008 
LOG: Closed transport: iframe-htmlfile SimpleEvent(type=close, code=1006, reason=Unable to load an iframe (onload timeout), wasClean=false) 
LOG: Opening transport: iframe-xhr-polling  url:rest/hello/904/bf63eisu  RTO:1008 
LOG: Closed transport: iframe-xhr-polling SimpleEvent(type=close, code=1006, reason=Unable to load an iframe (onload timeout), wasClean=false) 
LOG: Whoops! Lost connection to undefined 

如果将所需的cookie设置为false,IE将使用xdr-streaming并可以正常工作,但是,它将丢失请求中的jsessionid cookie,从而使我失去在控制器中获取Principal的能力,这对于我.我在Spring Security中启用了相同的Origin X框架标头,并且我已经验证了标头存在于请求中,但这没有帮助.因此,我希望能够弄清楚如何:A)使用Firefox中的WebSocket传输使Spring和SockJS正确协商,以及B)使IE8和9正确使用iframe传输,以便我可以保留cookie.

if I set the cookie needed to false, IE will use xdr-streaming and work fine, however, it loses the jsessionid cookie in the requests and in turn I lose the ability to acquire the Principal in the controller which is important for me. I enabled the same origin x frame headers in spring security and I've verified the headers are present in the requests, but it didn't help. So I'd like to be able to figure out how to A) make Spring and SockJS properly negotiate using WebSocket transport in Firefox, and B) get IE8 and 9 to properly use iframe transport so I can keep cookies.

这是我的配置/代码:

Web应用程序配置:

Web app config:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        Map<String, ? extends FilterRegistration> registrations = servletContext.getFilterRegistrations();
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        // this is needed for async support for websockets/sockjs
        registration.setInitParameter("dispatchOptionsRequest", "true");
        registration.setAsyncSupported(true);
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SecurityConfig.class, Log4jConfig.class, PersistenceConfig.class, ServiceConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // loading the Initializer class from the dispatcher servlet context ensures it only executes once,
        // as the ContextRefreshedEvent fires once from the root context and once from the dispatcher servlet context   
        return new Class[]{SpringMvcConfig.class, WebSocketConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{
            "/rest/*",
            "/index.html",
            "/login.html",
            "/admin.html",
            "/index/*",
            "/login/*",
            "/admin/*"
        };
    }

    @Override
    protected Filter[] getServletFilters() {
        OpenEntityManagerInViewFilter openEntityManagerInViewFilter = new OpenEntityManagerInViewFilter();
        openEntityManagerInViewFilter.setBeanName("openEntityManagerInViewFilter");
        openEntityManagerInViewFilter.setPersistenceUnitName("HSQL");

        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceEncoding(true);

        return new javax.servlet.Filter[]{openEntityManagerInViewFilter, encodingFilter};
    }

}

Spring MVC配置:

Spring MVC config:

@Configuration
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@ComponentScan(basePackages = "x.controllers")  // Only scan for controllers.  Other classes are scanned in the parent's root context
public class SpringMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJacksonHttpMessageConverter());
        converters.add(marshallingMessageConverter());
        super.configureMessageConverters(converters);
    }

    @Bean
    public InternalResourceViewResolver setupViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Bean
    public JacksonAnnotationIntrospector jacksonAnnotationIntrospector() {
        return new JacksonAnnotationIntrospector();
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setAnnotationIntrospector(jacksonAnnotationIntrospector());
        mapper.registerModule(new JodaModule());
        mapper.registerModule(new Hibernate4Module());
        return mapper;
    }

    @Bean
    public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        messageConverter.setObjectMapper(objectMapper());
        return messageConverter;
    }

    @Bean(name = "marshaller")
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("com.x);
        return marshaller;
    }

    @Bean
    public MarshallingHttpMessageConverter marshallingMessageConverter() {
        return new MarshallingHttpMessageConverter(
                jaxb2Marshaller(),
                jaxb2Marshaller()
        );
    }
}

Spring根上下文配置:

Spring root context config:

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.x.services"},   // scan for all annotated classes for the root context OTHER than controllers -- those are in the child web context. also don't rescan these config files
        excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class)
        }
)
public class ServiceConfig {

    @Bean
    public DefaultAnnotationHandlerMapping defaultAnnotationHandlerMapping() {
        DefaultAnnotationHandlerMapping handlerMapping = new DefaultAnnotationHandlerMapping();
        handlerMapping.setAlwaysUseFullPath(true);
        handlerMapping.setDetectHandlersInAncestorContexts(true);
        return handlerMapping;
    }

    @Bean
    public DefaultConversionService defaultConversionService() {
        return new DefaultConversionService();
    }

    @Bean(name = "kmlContext")
    public JAXBContext kmlContext() throws JAXBException {
        return JAXBContext.newInstance("net.opengis.kml");
    }

    @Bean(name = "ogcContext")
    public JAXBContext ogcContext() throws JAXBException {
        return JAXBContext.newInstance("net.x");
    }
}

春季安全性:

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AuthenticationProvider rememberMeAuthenticationProvider = rememberMeAuthenticationProvider();
        TokenBasedRememberMeServices tokenBasedRememberMeServices = tokenBasedRememberMeServices();

        List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>(2);
        authenticationProviders.add(rememberMeAuthenticationProvider);
        authenticationProviders.add(customAuthenticationProvider);
        AuthenticationManager authenticationManager = authenticationManager(authenticationProviders);

        http
                .csrf().disable()
                //.headers().disable()
                .headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
                .and()
                .authenticationProvider(customAuthenticationProvider)
                .addFilter(new RememberMeAuthenticationFilter(authenticationManager, tokenBasedRememberMeServices))
                .rememberMe().rememberMeServices(tokenBasedRememberMeServices)
                .and()
                .authorizeRequests()
                .antMatchers("/js/**", "/css/**", "/img/**", "/login", "/processLogin").permitAll()
                .antMatchers("/index.jsp", "/index.html", "/index").hasRole("USER")
                .antMatchers("/admin", "/admin.html", "/admin.jsp", "/js/saic/jswe/admin/**").hasRole("ADMIN")
                .and()
                .formLogin().loginProcessingUrl("/processLogin").loginPage("/login").usernameParameter("username").passwordParameter("password").permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/login")
                .and()
                .logout().permitAll();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/img/**");
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviders) {
        return new ProviderManager(authenticationProviders);
    }

    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        return new TokenBasedRememberMeServices("testKey", userDetailsService);
    }

    @Bean
    public AuthenticationProvider rememberMeAuthenticationProvider() {
        return new org.springframework.security.authentication.RememberMeAuthenticationProvider("testKey");
    }

    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

WebSocket消息代理配置:

WebSocket message broker config:

@Configuration
@EnableWebSocketMessageBroker
@EnableScheduling
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
            SockJsServiceRegistration registration = registry.addEndpoint("/hello").withSockJS().setClientLibraryUrl("http://localhost:8084/swtc/js/sockjs-0.3.4.min.js");
            registration.setWebSocketEnabled(true);
            //registration.setSessionCookieNeeded(false);

    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.taskExecutor().corePoolSize(4).maxPoolSize(8);
    }

    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        registration.taskExecutor().corePoolSize(4).maxPoolSize(8);
    }

}

WebSocket控制器:

WebSocket controller:

@Controller
public class WebSocketController {
    @MessageMapping({"/hello", "/hello/**"})
    @SendTo("/topic/greetings")
    // in order to get principal, you must set cookiesNeeded in WebSocketConfig, which forces IE to use iframes, which doesn't seem to work
    public AjaxResponse<String> greeting(@Payload PointRadiusRequest prr, Principal principal) throws Exception {
        Thread.sleep(3000); // simulated delay
        AjaxResponse<String> ajaxResponse = new AjaxResponse<String>();
        ajaxResponse.setValue(principal.getName());
        ajaxResponse.setSuccess(true);
        return ajaxResponse;
    } 
}

最后,我正在测试的html中的javascript:

And finally, the javascript in my html I'm using to test:

<script>
            // test/prototype websocket code
            stompClient = null;

            window.connect = function() {

                var options = {protocols_whitelist: ["websocket", "xhr-streaming", "xdr-streaming", "xhr-polling", "xdr-polling", "iframe-htmlfile", "iframe-eventsource", "iframe-xhr-polling"], debug: true}; 
                wsSocket = new SockJS('rest/hello', undefined, options);

                stompClient = Stomp.over(wsSocket);
                stompClient.connect({}, function(frame) {
                    console.log('Connected: ' + frame);
                    stompClient.subscribe('/topic/greetings', function(message) {
                        console.info("response: ", JSON.parse(message.body));
                    });
                });
            };

            window.disconnect = function() {
                stompClient.disconnect();
                console.log("Disconnected");
            };

            window.sendName = function() {
                stompClient.send("/app/hello", {}, JSON.stringify({'latitude': 12, 'longitude': 123.2, radius: 3.14}));
            };
        </script>

在Firefox中连接时,这是我在控制台中看到的内容:

When I connect in Firefox, this is what I see in the console:

>>> connect()
connecting
/swtc/ (line 109)
Opening Web Socket...
stomp.js (line 130)
undefined
GET http://localhost:8084/swtc/rest/hello/info

200 OK
        202ms   
sockjs....min.js (line 27)
Opening transport: websocket url:rest/hello/007/xkc17fkt RTO:912
sockjs....min.js (line 27)
SyntaxError: An invalid or illegal string was specified


...3,reason:"All transports failed",wasClean:!1,last_event:g})}f.readyState=y.CLOSE...

sockjs....min.js (line 27)
Closed transport: websocket SimpleEvent(type=close, code=2007, reason=Transport timeouted, wasClean=false)
sockjs....min.js (line 27)
Opening transport: xhr-streaming url:rest/hello/007/8xz79yip RTO:912
sockjs....min.js (line 27)
POST http://localhost:8084/swtc/rest/hello/007/8xz79yip/xhr_streaming

200 OK
        353ms   
sockjs....min.js (line 27)
Web Socket Opened...

>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

�

stomp.js (line 130)
POST http://localhost:8084/swtc/rest/hello/007/8xz79yip/xhr_send

204 No Content
        63ms    

<<< CONNECTED
user-name:first.mi.last
heart-beat:0,0
version:1.1

�

stomp.js (line 130)
connected to server undefined
stomp.js (line 130)

Connected: CONNECTED
version:1.1
heart-beat:0,0
user-name:xxx

>>> SUBSCRIBE
id:sub-0
destination:/topic/greetings

�

stomp.js (line 130)
POST http://localhost:8084/swtc/rest/hello/007/8xz79yip/xhr_send

204 No Content
        57ms

/info响应为:

{"entropy":441118013,"origins":["*:*"],"cookie_needed":true,"websocket":true}

请注意在尝试建立websocket连接时出现奇怪的字符串错误.我猜这是问题的根源,但我没有做任何有趣的事,也不知道是什么原因造成的.

Note the weird string error when it tries to make the websocket connection. I'm guessing that's the source of my problems, but I'm not doing anything funny and I have no idea what's causing it.

在IE中,这是网络流量. iframe.html文件似乎已正确构建,但无法与后端建立连接.

In IE, here is the network traffic. The iframe.html files seem to be built properly, but it just can't make the connection to the back-end.

URL Method  Result  Type    Received    Taken   Initiator   Wait‎‎  Start‎‎ Request‎‎   Response‎‎  Cache read‎‎    Gap‎‎
/swtc/rest/hello/info?t=1399328502157   GET 200 application/json    411 B   328 ms      0   47  281 0   0   2199
/swtc/rest/hello/iframe.html    GET 200 text/html   0.97 KB 156 ms  frame navigate  328 0   156 0   0   2043
/swtc/js/sockjs-0.3.4.min.js    GET 304 application/javascript  157 B   < 1 ms  <script>    484 0   0   0   0   2043
/swtc/rest/hello/iframe.html    GET 304 text/html   191 B   < 1 ms  frame navigate  2527    0   0   0   0   0
/swtc/js/sockjs-0.3.4.min.js    GET 304 application/javascript  157 B   < 1 ms  <script>    2527    0   0   0   0   0

信息响应如下:

{"entropy":-475136625,"origins":["*:*"],"cookie_needed":true,"websocket":true}

如果有人想查看请求或响应头,请告诉我.

If anybody wants to see the request or response headers, just let me know.

更新1:

罗森,感谢您的回复.我从您那里学到的关于Spring 4的所有知识:)

Rossen, thanks for the response. Everything I know about Spring 4 I learned from you :)

Firefox不能真正(完全)运行,我无法进行websocket会话,它降级为xhr-streaming.使用xhr-streaming,没有任何问题,但是我希望有一个真正的websocket会话.

Firefox isn't actually working (completely), I can't get a websocket session, it downgrades to xhr-streaming. With xhr-streaming, there are no issues, but I'd like to have a true websocket session.

对于IE,我不确定删除标题会确认什么?我认为x框架标头仅影响iframe会话,这根本不起作用.当我禁用要求cookie时,IE使用xdr流(尽管没有获取Principal的能力,但仍然有效).启用Cookie后,IE会正确尝试使用iframe.但是,即使在头文件到位的情况下,所有尝试也会失败:

With IE, I'm not sure what removing the headers will confirm? I thought the x frame header only affected the iframe session, which doesn't work at all. IE uses xdr-streaming (and works, albeit without the ability to fetch the Principal) when I disable require cookies. Once I enable cookies, IE properly ATTEMPTS to use iframes. But even with the headers in place, all attempts fail:

    http://localhost:8084/swtc/rest/hello/info?t=1399328502157

        Key Value
        Response    HTTP/1.1 200 OK
        Server  Apache-Coyote/1.1
        X-Frame-Options SAMEORIGIN
        Access-Control-Allow-Origin http://localhost:8084
        Access-Control-Allow-Credentials    true
        Cache-Control   no-store, no-cache, must-revalidate, max-age=0
        Content-Type    application/json;charset=UTF-8
        Content-Length  78
        Date    Mon, 05 May 2014 22:21:42 GMT

LOG: Opening Web Socket... 
LOG: Opening transport: iframe-htmlfile  url:rest/hello/904/ft3apk1g  RTO:1008 
LOG: Closed transport: iframe-htmlfile SimpleEvent(type=close, code=1006, reason=Unable to load an iframe (onload timeout), wasClean=false) 
LOG: Opening transport: iframe-xhr-polling  url:rest/hello/904/bf63eisu  RTO:1008 
LOG: Closed transport: iframe-xhr-polling SimpleEvent(type=close, code=1006, reason=Unable to load an iframe (onload timeout), wasClean=false) 
LOG: Whoops! Lost connection to undefined 

iframe-htmlfile和iframe-xhr-polling均失败.我确实确实在IE中的每次刷新时都清除了缓存,并且在SockJS中启用了调试模式.我可以在IE中使用xdr-streaming很好,但我确实需要jsessionid cookie.

Both iframe-htmlfile and iframe-xhr-polling fail. I do indeed clear cache with each refresh in IE and I do have debug mode enabled in SockJS. I would be fine living with xdr-streaming in IE, but I really need the jsessionid cookie.

有什么想法吗?

另一方面,如果客户端库代码支持相对路径(它确实会使用相对路径构建html文件,并且应该可以工作,但仍然会在日志中产生错误),那就太好了,

On a side note, it would be really nice if the client library code supported relative paths (it actually does build the html file with the relative path and should work, but still produces errors in the log), ie:

SockJsServiceRegistration registration = registry.addEndpoint("/hello").withSockJS().setClientLibraryUrl("js/sockjs-0.3.4.min.js");

这将减少部署到生产环境中的痛苦.

That would make deploying to production less painful.

更新2:

快速摘要:没有变化.

这是我尝试在IE9中用我的安全配置中的.headers().and()连接:

Here is my attempt to connect in IE9 with .headers().and() in my security config:

LOG: Opening Web Socket... 
LOG: Opening transport: iframe-htmlfile  url:rest/hello/924/1ztfjm7z  RTO:330 
LOG: Closed transport: iframe-htmlfile SimpleEvent(type=close, code=2007, reason=Transport timeouted, wasClean=false) 
LOG: Opening transport: iframe-xhr-polling  url:rest/hello/924/cgq8_s5j  RTO:330 
LOG: Closed transport: iframe-xhr-polling SimpleEvent(type=close, code=2007, reason=Transport timeouted, wasClean=false) 
LOG: Whoops! Lost connection to undefined 

/info的请求标头:

The request headers for /info:

Key Value
Request GET /swtc/rest/hello/info?t=1399404419358 HTTP/1.1
Accept  */*
Origin  http://localhost:8084
Accept-Language en-US
UA-CPU  AMD64
Accept-Encoding gzip, deflate
User-Agent  Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Host    localhost:8084
Connection  Keep-Alive
Cache-Control   no-cache

和响应头:

Key Value
Response    HTTP/1.1 200 OK
Server  Apache-Coyote/1.1
X-Content-Type-Options  nosniff
X-XSS-Protection    1; mode=block
Cache-Control   no-cache, no-store, max-age=0, must-revalidate
Pragma  no-cache
Expires 0
X-Frame-Options DENY
Access-Control-Allow-Origin http://localhost:8084
Access-Control-Allow-Credentials    true
Cache-Control   no-store, no-cache, must-revalidate, max-age=0
Content-Type    application/json;charset=UTF-8
Content-Length  78
Date    Tue, 06 May 2014 19:26:59 GMT

Firefox没有区别.当我尝试打开websocket时,我得到了同样奇怪的字符串错误,然后又回到了xhr-streaming:

There was no difference in Firefox. I get the same weird string error when it tries to open the websocket, then falls back to xhr-streaming:

Opening transport: websocket url:rest/hello/849/fy_06t1v RTO:342
SyntaxError: An invalid or illegal string was specified
Closed transport: websocket SimpleEvent(type=close, code=2007, reason=Transport timeouted, wasClean=false)
Opening transport: xhr-streaming url:rest/hello/849/2r0raiz8 RTO:342
http://localhost:8084/swtc/rest/hello/849/2r0raiz8/xhr_streaming
Web Socket Opened...
>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

推荐答案

由于SockJS在尝试进行WebSocket连接时产生了一个奇怪的字符串错误,然后又回到了xhr_streaming,因此我决定加载非缩小版本的. js文件并在Firebug中对其进行调试,以查看发生了什么情况.事实证明,SockJS不喜欢相对URL,这是一种臭味.

Since SockJS was producing a weird string error when attempting the WebSocket connection, then fell back to xhr_streaming, I decided to load up the non-minified version of the .js file and debug it in Firebug to see what was going on. Turns out, SockJS does not like relative URLs, which kind of stinks.

对于我的大多数REST/AJAX服务,我将/rest/*映射到我的调度程序servlet,通常在每个控制器上都有一个@RequestMapping,并且在每个控制器方法上都有一个@RequestMapping.使用Dojo,我通过指定URL "rest/<controller>/<method>"进行AJAX调用.

For most of my REST/AJAX services, I have /rest/* mapped to my dispatcher servlet, typically have an @RequestMapping on each controller, and another @RequestMapping on each controller method. Using Dojo, I make AJAX calls by specifying the url "rest/<controller>/<method>".

我正在用SockJS尝试同样的事情.我只是指休息/你好".我将其更改为完全限定的URL"http://localhost:8084/swtc/rest/hello",突然,Firefox可以很好地构建websocket传输层.我跳到IE进行了快速测试,果然,它建立了iframe会话,并且运行良好.

I was attempting the same thing with SockJS. I was just pointing to "rest/hello". I changed this to the fully qualified URL "http://localhost:8084/swtc/rest/hello" and suddenly firefox could build the websocket transport layer just fine. I hopped over to IE for a quick test and sure enough, it built the iframe session and also worked just fine.

这样一个愚蠢的小问题.我讨厌必须在任何地方指定非相对URL,因为此代码库在多个开发人员之间共享,所有开发人员都部署到不同的服务器进行测试,并部署到生产环境.我想在前端我可以使用window.doc.URL动态构建URL,但是在指定setClientLibraryUrl时让AbstractWebSocketMessageBrokerConfigurer自动跨部署工作会有些棘手.

Such a silly little problem. I hate having to specify non-relative urls anywhere, as this code base is shared between multiple developers, all who deploy to different servers for testing, and deploy to production. I suppose on the front-end I can dynamically build the URL using window.doc.URL, but it'll be a bit more tricky to get the AbstractWebSocketMessageBrokerConfigurer to automatically work across deployments when specifying setClientLibraryUrl.

无论哪种方式,孩子们都不要在SockJS中使用相对路径.

Either way kids, don't use relative paths with SockJS.

这篇关于Spring 4 AbstractWebSocketMessageBrokerConfigurer与SockJS无法正确协商传输的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆