所以我必须说所有的websocket教程/示例看起来都很简单,但是你似乎真的需要挖掘才能找到简单例子中遗漏的非常重要的信息.我的webapp使用前端有SockJS的Spring 4 Stomp消息代理,我有很多问题.
目前,如果我在没有启用SockJS()的情况下向StompEndpointRegistry添加端点,然后使用dojo的dojox / socket在前端声明我的套接字,Firefox 28将打开websocket就好了.但是,我需要在IE8和IE9中支持,所以我切换到了SockJS.使用AbstractAnnotationConfigDispatcherServletInitializer,我花了很多时间来弄清楚如何确保所有过滤器和servlet都设置为使用异步(在Web上非常稀疏的文档).一旦我解决了这个问题,我现在可以在Firefox中使用它,但只能使用xhr_streaming.将sessionCookieNeeded设置为true,IE9默认尝试使用iframe进行连接,但是,它失败:
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,wasClean=false)
LOG: Whoops! Lost connection to undefined
如果我将所需的cookie设置为false,IE将使用xdr-streaming并正常工作,它会丢失请求中的jsessionid cookie,反过来我失去了在控制器中获取Principal的能力,这对我来说很重要.我在spring security中启用了相同的origin x frame header,我已经验证了请求中是否存在标题,但它没有帮助.所以我想知道如何A)让Spring和SockJS在Firefox中使用WebSocket传输正确协商,并且B)让IE8和9正确使用iframe传输,这样我就可以保留cookie.
这是我的配置/代码:
网络应用配置:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
Mapsql");
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
return new javax.servlet.Filter[]{openEntityManagerInViewFilter,encodingFilter};
}
}
Spring MVC配置:
@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
Spring root上下文配置:
@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();
Listlogout().permitAll();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**","/img/**");
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(List
WebSocket消息代理配置:
@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控制器:
@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
最后,我的html中的javascript用于测试:
当我在Firefox中连接时,这是我在控制台中看到的:
>>> 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连接时,请注意奇怪的字符串错误.我猜这是我的问题的根源,但我没有做任何有趣的事情,我不知道是什么导致它.
在IE中,这是网络流量. iframe.html文件似乎构建正确,但它无法建立到后端的连接.
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
信息响应如下所示:
{"entropy":-475136625,"websocket":true}
如果有人想查看请求或响应标头,请告诉我.
更新1:
罗森,谢谢你的回应.关于Spring 4我所知道的一切我都是从你那里学到的:)
Firefox实际上并没有(完全)工作,我无法获得websocket会话,它降级为xhr-streaming.使用xhr-streaming,没有任何问题,但我希望有一个真正的websocket会话.
使用IE浏览器,我不确定删除标题会确认什么?我认为x帧标题只影响了iframe会话,但根本不起作用.当我禁用require cookie时,IE使用xdr-streaming(并且可以工作,虽然无法获取Principal).一旦我启用了cookie,IE就可以正确地使用iframe来尝试ATTEMPTS.但即使标头到位,所有尝试都会失败:
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,wasClean=false)
LOG: Whoops! Lost connection to undefined
iframe-htmlfile和iframe-xhr-polling都失败了.我确实清除IE中每次刷新的缓存,我确实在SockJS中启用了调试模式.我会在IE中使用xdr-streaming很好,但我真的需要jsessionid cookie.
有什么想法吗?
另外,如果客户端库代码支持相对路径(它实际上构建了具有相对路径的html文件并且应该工作,但仍然在日志中产生错误),那将是非常好的,即:
SockJsServiceRegistration registration = registry.addEndpoint("/hello").withSockJS().setClientLibraryUrl("js/sockjs-0.3.4.min.js");
这将使部署到生产的痛苦减少.
更新2:
这是我尝试在我的安全配置中使用.headers().和()连接IE9:
LOG: opening Web Socket...
LOG: opening transport: iframe-htmlfile url:rest/hello/924/1ztfjm7z RTO:330
LOG: Closed transport: iframe-htmlfile SimpleEvent(type=close,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,wasClean=false)
LOG: Whoops! Lost connection to undefined
/ 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,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:
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,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,10000
您的配置似乎正确.特别适用于Spring Security:
.headers().addHeaderWriter(
new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)).and()
对于SockJS也是如此:
setClientLibraryUrl("http://localhost:8084/swtc/js/sockjs-0.3.4.min.js");
我建议尝试禁用标题只是为了确认它是否是问题,即:
.headers().and()
还要确保没有涉及发送相同响应的浏览器缓存问题.因此,请检查X-Frame-Options值的实际响应标头.
为此,我强烈建议通过SockJS构造函数的options参数启用SockJS客户端调试模式.