/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.reactive.socket; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.apache.tomcat.websocket.server.WsContextListener; import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import org.xnio.OptionMap; import org.xnio.Xnio; import reactor.core.publisher.Flux; import reactor.util.function.Tuple3; import org.springframework.context.Lifecycle; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.bootstrap.HttpServer; import org.springframework.http.server.reactive.bootstrap.JettyHttpServer; import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer; import org.springframework.http.server.reactive.bootstrap.RxNettyHttpServer; import org.springframework.http.server.reactive.bootstrap.TomcatHttpServer; import org.springframework.http.server.reactive.bootstrap.UndertowHttpServer; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.socket.client.JettyWebSocketClient; import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; import org.springframework.web.reactive.socket.client.RxNettyWebSocketClient; import org.springframework.web.reactive.socket.client.StandardWebSocketClient; import org.springframework.web.reactive.socket.client.UndertowWebSocketClient; import org.springframework.web.reactive.socket.client.WebSocketClient; import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.WebSocketService; import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; import org.springframework.web.reactive.socket.server.upgrade.JettyRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.RxNettyRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.TomcatRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.UndertowRequestUpgradeStrategy; import static org.junit.Assume.*; /** * Base class for WebSocket integration tests. Sub-classes must implement * {@link #getWebConfigClass()} to return Spring config class with (server-side) * handler mappings to {@code WebSocketHandler}'s. * * @author Rossen Stoyanchev */ @RunWith(Parameterized.class) @SuppressWarnings({"unused", "WeakerAccess"}) public abstract class AbstractWebSocketIntegrationTests { private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir")); protected int port; @Parameter(0) public WebSocketClient client; @Parameter(1) public HttpServer server; @Parameter(2) public Class<?> serverConfigClass; @Parameters(name = "client[{0}] - server [{1}]") public static Object[][] arguments() throws IOException { Flux<? extends WebSocketClient> clients = Flux.concat( Flux.just(new StandardWebSocketClient()).repeat(5), Flux.just(new JettyWebSocketClient()).repeat(5), Flux.just(new ReactorNettyWebSocketClient()).repeat(5), Flux.just(new RxNettyWebSocketClient()).repeat(5), Flux.just(new UndertowWebSocketClient(Xnio.getInstance().createWorker(OptionMap.EMPTY))).repeat(5)); Flux<? extends HttpServer> servers = Flux.just( new TomcatHttpServer(TMP_DIR.getAbsolutePath(), WsContextListener.class), new JettyHttpServer(), new ReactorHttpServer(), new RxNettyHttpServer(), new UndertowHttpServer()).repeat(5); Flux<? extends Class<?>> configs = Flux.just( TomcatConfig.class, JettyConfig.class, ReactorNettyConfig.class, RxNettyConfig.class, UndertowConfig.class).repeat(5); return Flux.zip(clients, servers, configs) .map(Tuple3::toArray) .collectList() .block() .toArray(new Object[25][2]); } @Before public void setup() throws Exception { // TODO // Caused by: java.io.IOException: Upgrade responses cannot have a transfer coding // at org.xnio.http.HttpUpgrade$HttpUpgradeState.handleUpgrade(HttpUpgrade.java:490) // at org.xnio.http.HttpUpgrade$HttpUpgradeState.access$1200(HttpUpgrade.java:165) // at org.xnio.http.HttpUpgrade$HttpUpgradeState$UpgradeResultListener.handleEvent(HttpUpgrade.java:461) // at org.xnio.http.HttpUpgrade$HttpUpgradeState$UpgradeResultListener.handleEvent(HttpUpgrade.java:400) // at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) assumeFalse(this.client instanceof UndertowWebSocketClient && this.server instanceof RxNettyHttpServer); this.server.setHandler(createHttpHandler()); this.server.afterPropertiesSet(); this.server.start(); // Set dynamically chosen port this.port = this.server.getPort(); if (this.client instanceof Lifecycle) { ((Lifecycle) this.client).start(); } } @After public void stop() throws Exception { if (this.client instanceof Lifecycle) { ((Lifecycle) this.client).stop(); } this.server.stop(); } private HttpHandler createHttpHandler() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(DispatcherConfig.class, this.serverConfigClass); context.register(getWebConfigClass()); context.refresh(); return DispatcherHandler.toHttpHandler(context); } protected URI getUrl(String path) throws URISyntaxException { return new URI("ws://localhost:" + this.port + path); } protected abstract Class<?> getWebConfigClass(); @Configuration static class DispatcherConfig { @Bean public DispatcherHandler webHandler() { return new DispatcherHandler(); } } static abstract class AbstractHandlerAdapterConfig { @Bean public WebSocketHandlerAdapter handlerAdapter() { return new WebSocketHandlerAdapter(webSocketService()); } @Bean public WebSocketService webSocketService() { return new HandshakeWebSocketService(getUpgradeStrategy()); } protected abstract RequestUpgradeStrategy getUpgradeStrategy(); } @Configuration static class ReactorNettyConfig extends AbstractHandlerAdapterConfig { @Override protected RequestUpgradeStrategy getUpgradeStrategy() { return new ReactorNettyRequestUpgradeStrategy(); } } @Configuration static class RxNettyConfig extends AbstractHandlerAdapterConfig { @Override protected RequestUpgradeStrategy getUpgradeStrategy() { return new RxNettyRequestUpgradeStrategy(); } } @Configuration static class TomcatConfig extends AbstractHandlerAdapterConfig { @Override protected RequestUpgradeStrategy getUpgradeStrategy() { return new TomcatRequestUpgradeStrategy(); } } @Configuration static class UndertowConfig extends AbstractHandlerAdapterConfig { @Override protected RequestUpgradeStrategy getUpgradeStrategy() { return new UndertowRequestUpgradeStrategy(); } } @Configuration static class JettyConfig extends AbstractHandlerAdapterConfig { @Override protected RequestUpgradeStrategy getUpgradeStrategy() { return new JettyRequestUpgradeStrategy(); } } }