Skip to content
This repository has been archived by the owner on Apr 6, 2021. It is now read-only.

Commit

Permalink
Added websocket support to testeefi-rest
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Stockinger committed Sep 13, 2017
1 parent 069ee8d commit 4cce6a6
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 13 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ ext {
libJavassist = 'org.javassist:javassist:3.21.0-GA'
libByteBuddy = 'net.bytebuddy:byte-buddy:1.7.1'
libJettyServlet = 'org.eclipse.jetty:jetty-servlet:9.4.6.v20170531'
libJettyWebsocketServer = 'org.eclipse.jetty.websocket:javax-websocket-server-impl:9.4.6.v20170531'
libJettyWebsocketClient = 'org.eclipse.jetty.websocket:javax-websocket-client-impl:9.4.6.v20170531'
libJerseyServlet = 'com.sun.jersey:jersey-servlet:1.19.4'
libOkhttp3 = 'com.squareup.okhttp3:okhttp:3.8.1'
libJavaxActivation = 'javax.activation:activation:1.1.1'
Expand Down
3 changes: 3 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Version 0.7.0
- Added websocket support to testeefi-rest

Version 0.6.0
- Hibernate support
- Custom params for @TestData methods can be contributed via TestDataSetupAccessorFactories now
Expand Down
3 changes: 3 additions & 0 deletions rest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ dependencies {
compile libJerseyMediaMoxy
compile libJettyServlet
compile libJavaxActivation
compile libJettyWebsocketServer

testCompile project(":junit4")
testCompile project(":mockito")
testCompile libCommonsIO
testCompile libLogback
testCompile libOkhttp3
testCompile libJettyWebsocketClient
}
55 changes: 55 additions & 0 deletions rest/src/main/java/fi/testee/rest/DelegatingConfigurator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2017 Alex Stockinger
*
* 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 fi.testee.rest;

import javax.websocket.Extension;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.List;

public class DelegatingConfigurator extends ServerEndpointConfig.Configurator {
private ServerEndpointConfig.Configurator delegate;

public DelegatingConfigurator(final ServerEndpointConfig.Configurator delegate) {
this.delegate = delegate;
}

@Override
public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
return delegate.getNegotiatedSubprotocol(supported, requested);
}

@Override
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
return delegate.getNegotiatedExtensions(installed, requested);
}

@Override
public boolean checkOrigin(String originHeaderValue) {
return delegate.checkOrigin(originHeaderValue);
}

@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
delegate.modifyHandshake(sec, request, response);
}

@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
return delegate.getEndpointInstance(endpointClass);
}
}
81 changes: 69 additions & 12 deletions rest/src/main/java/fi/testee/rest/RestServerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,25 @@
import fi.testee.exceptions.TestEEfiException;
import fi.testee.spi.AnnotationScanner;
import fi.testee.spi.DependencyInjection;
import fi.testee.spi.Releaser;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope;
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
import org.eclipse.jetty.websocket.jsr356.server.AnnotatedServerEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerEndpoint;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Path;
import javax.ws.rs.ext.Provider;
Expand All @@ -45,12 +55,14 @@
class RestServerImpl implements RestServer {
private static final Logger LOG = LoggerFactory.getLogger(RestServerImpl.class);
@SuppressWarnings("unchecked")
private static final Class<? extends Annotation>[] MANAGED_CLASSES = new Class[]{Path.class, Provider.class};
private static final Class<? extends Annotation>[] JAX_RS_MANAGED_CLASSES = new Class[]{Path.class, Provider.class};
private static final Class<? extends Annotation>[] WEBSOCKET_MANAGED_CLASSES = new Class[]{ServerEndpoint.class};

private static final String PREFIX = RestServerImpl.class.getName();
static final String ATTR_CLASSES = PREFIX + ".classes";
static final String ATTR_DI = PREFIX + ".di";

private final Releaser releaser = new Releaser();
private final AnnotationScanner annotationScanner;
private final DependencyInjection dependencyInjection;
private final Collection<StaticResourceResolver> staticResourceResolvers;
Expand Down Expand Up @@ -83,27 +95,71 @@ private void createServer() {

try {
server = new Server(0);
server.setHandler(new HandlerCollection(
createJerseyHandler(),
createStaticHandler()
));
final HandlerCollection handlerCollection = new HandlerCollection();
server.setHandler(handlerCollection);
createJerseyHandler(handlerCollection);
createWebsocketHandler(handlerCollection);
createStaticHandler(handlerCollection);
server.start();
} catch (final Exception e) {
throw new TestEEfiException("Failed to start Jetty", e);
}
}

private ServletContextHandler createStaticHandler() {
private void createStaticHandler(final HandlerCollection handlerCollection) {
final ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/static");
context.addServlet(new ServletHolder(new StaticServlet(staticResourceResolvers)), "/*");
return context;
handlerCollection.addHandler(context);
}


private void createWebsocketHandler(final HandlerCollection handlerCollection) {
// https://github.com/jetty-project/embedded-jetty-websocket-examples/blob/master/javax.websocket-example/src/main/java/org/eclipse/jetty/demo/EventServer.java
try {
final Set<Class<?>> managedClasses = collectAnnotated(WEBSOCKET_MANAGED_CLASSES);
final ServletContextHandler context = initContext("/websockets", managedClasses);
handlerCollection.addHandler(context);
final ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
managedClasses.forEach(c -> registerEndpoint(wscontainer, c));
} catch (final ServletException e) {
throw new TestEEfiException("Failed to initialize websockets", e);
}
}

private ServletContextHandler createJerseyHandler() {
private void registerEndpoint(final ServerContainer wscontainer, final Class<?> endpointClass) {
try {
final ServerEndpoint annotation = endpointClass.getAnnotation(ServerEndpoint.class);
final WebSocketContainerScope scope = new SimpleContainerScope(WebSocketPolicy.newServerPolicy());
final AnnotatedServerEndpointConfig endpointConfig = new AnnotatedServerEndpointConfig(
scope,
endpointClass,
annotation,
null
) {
@Override
public Configurator getConfigurator() {
return new DelegatingConfigurator(super.getConfigurator()) {
@Override
public <T> T getEndpointInstance(
final Class<T> endpointClass
) throws InstantiationException {
return dependencyInjection.getInstanceOf(endpointClass, releaser);
}
};
}
};
wscontainer.addEndpoint(endpointConfig);
} catch (final DeploymentException e) {
throw new TestEEfiException("Failed to register websocket enpoint", e);
}
}

private ServletContextHandler createJerseyHandler(final HandlerCollection handlerCollection) {
// http://nikgrozev.com/2014/10/16/rest-with-embedded-jetty-and-jersey-in-a-single-jar-step-by-step/
final Set<Class<?>> managedClasses = getManagedClasses();
final Set<Class<?>> managedClasses = collectAnnotated(JAX_RS_MANAGED_CLASSES);
final ServletContextHandler context = initContext("/rest", managedClasses);
handlerCollection.addHandler(context);
final ResourceConfig config = initConfig(managedClasses);
annotationScanner.scanFor(ApplicationPath.class).forEach(appClass -> {
final String path = appClass.getAnnotation(ApplicationPath.class).value();
Expand All @@ -121,21 +177,22 @@ private ResourceConfig initConfig(final Set<Class<?>> managedClasses) {
}

private ServletContextHandler initContext(final String contextPath, final Set<Class<?>> classes) {
final ServletContextHandler context = new ServletContextHandler();
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath(contextPath);
context.setAttribute(ATTR_CLASSES, classes);
context.setAttribute(ATTR_DI, dependencyInjection);
return context;
}

private Set<Class<?>> getManagedClasses() {
return Stream.of(MANAGED_CLASSES)
private Set<Class<?>> collectAnnotated(Class<? extends Annotation>[] annotations) {
return Stream.of(annotations)
.map(annotationScanner::scanFor)
.flatMap(Collection::stream)
.collect(toSet());
}

public void shutdown() {
releaser.release();
if (server != null) {
try {
server.stop();
Expand Down
4 changes: 4 additions & 0 deletions rest/src/test/java/fi/testee/rest/AbstractBaseRestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
package fi.testee.rest;

import fi.testee.junit4.TestEEfi;
import fi.testee.rest.app.ManagedBean;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.junit.runner.RunWith;
import org.mockito.Mock;

import javax.annotation.Resource;
import java.io.IOException;
Expand All @@ -34,6 +36,8 @@ public abstract class AbstractBaseRestTest {

@Resource
private RestServer restServer;
@Mock
protected ManagedBean managedBean;

protected void assertGet(
final String path,
Expand Down
4 changes: 3 additions & 1 deletion rest/src/test/java/fi/testee/rest/RestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import java.util.function.Consumer;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

public class RestTest extends AbstractBaseRestTest {
@Test
public void serves_rest_resources() throws IOException {
assertGet("/rest/api/ping", 200, body -> assertEquals("managed:session", body));
when(managedBean.getValue()).thenReturn("mocked");
assertGet("/rest/api/ping", 200, body -> assertEquals("mocked:session", body));
}
}
80 changes: 80 additions & 0 deletions rest/src/test/java/fi/testee/rest/WebsocketTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2017 Alex Stockinger
*
* 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 fi.testee.rest;

import fi.testee.junit4.TestEEfi;
import fi.testee.rest.app.ManagedBean;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;

import javax.annotation.Resource;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;

import static java.util.concurrent.TimeUnit.SECONDS;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

@RunWith(TestEEfi.class)
public class WebsocketTest {
@Resource
private RestServer restServer;
@Mock
private ManagedBean managedBean;

@Test
public void roundtrip_works() throws Exception {
when(managedBean.getValue()).thenReturn("mocked");

final URI uri = URI.create("ws://localhost:" + restServer.getPort() + "/websockets/echo");
final CountDownLatch latch = new CountDownLatch(1);
final WebSocketContainer container = ContainerProvider.getWebSocketContainer();
final Client client = new Client(() -> latch.countDown());
try (final Session session = container.connectToServer(client, uri)) {
session.getBasicRemote().sendText("Hello");
assertTrue(latch.await(5, SECONDS));
} finally {
((LifeCycle) container).stop();
}
assertEquals("mocked:Hello", client.message);
}

@ClientEndpoint
public static class Client {
private final Runnable callback;
public String message;

public Client(final Runnable callback) {
this.callback = callback;
}

@OnMessage
public void onWebSocketText(final String message) throws IOException {
this.message = message;
callback.run();
}

}
}
Loading

0 comments on commit 4cce6a6

Please sign in to comment.