Repository: cxf Updated Branches: refs/heads/master 9a19f69aa -> 1f430d4c7
http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/BookStoreWebSocket.java ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/BookStoreWebSocket.java b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/BookStoreWebSocket.java new file mode 100644 index 0000000..78f032c --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/BookStoreWebSocket.java @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.StreamingOutput; + +import org.apache.cxf.jaxrs.ext.StreamingResponse; +import org.apache.cxf.transport.websocket.WebSocketConstants; + +@Path("/web/bookstore") +public class BookStoreWebSocket { + private static ExecutorService executor = Executors.newSingleThreadExecutor(); + private Map<String, OutputStream> eventsStreams = new HashMap<String, OutputStream>(); + + @GET + @Path("/booknames") + @Produces("text/plain") + public byte[] getBookName() { + return "CXF in Action".getBytes(); + } + + @GET + @Path("/booknames/servletstream") + @Produces("text/plain") + public void getBookNameStream(@Context HttpServletResponse response) throws Exception { + OutputStream os = response.getOutputStream(); + response.setContentType("text/plain"); + os.write("CXF in Action".getBytes()); + os.flush(); + } + + @GET + @Path("/books/{id}") + @Produces("application/xml") + public Book getBook(@PathParam("id") long id) { + return new Book("CXF in Action", id); + } + + @POST + @Path("/booksplain") + @Consumes("text/plain") + @Produces("text/plain") + public Long echoBookId(long theBookId) { + return new Long(theBookId); + } + + @GET + @Path("/bookbought") + @Produces("application/*") + public StreamingOutput getBookBought() { + return new StreamingOutput() { + public void write(final OutputStream out) throws IOException, WebApplicationException { + out.write(("Today: " + new java.util.Date()).getBytes()); + // just for testing, using a thread + executor.execute(new Runnable() { + public void run() { + try { + for (int r = 2, i = 1; i <= 5; r *= 2, i++) { + Thread.sleep(500); + out.write(Integer.toString(r).getBytes()); + out.flush(); + } + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + }; + } + + @GET + @Path("/bookstream") + @Produces("application/json") + public StreamingResponse<Book> getBookStream() { + return new StreamingResponse<Book>() { + public void writeTo(final StreamingResponse.Writer<Book> out) throws IOException { + out.write(new Book("WebSocket1", 1)); + executor.execute(new Runnable() { + public void run() { + try { + for (int i = 2; i <= 5; i++) { + Thread.sleep(500); + out.write(new Book("WebSocket" + i, i)); + out.getEntityStream().flush(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + }; + } + + @GET + @Path("/hold/{t}") + @Produces("text/plain") + public String hold(@PathParam("t") long t) { + Date from = new Date(); + try { + Thread.sleep(t); + } catch (InterruptedException e) { + // ignore + } + return "Held from " + from + " for " + t + " ms"; + } + + @GET + @Path("/events/register") + @Produces("text/plain") + public StreamingOutput registerEventsStream(@HeaderParam(WebSocketConstants.DEFAULT_REQUEST_ID_KEY) String reqid) { + final String key = reqid == null ? "*" : reqid; + return new StreamingOutput() { + public void write(final OutputStream out) throws IOException, WebApplicationException { + eventsStreams.put(key, out); + out.write(("Registered " + key + " at " + new java.util.Date()).getBytes()); + } + }; + + } + + @GET + @Path("/events/create/{name}") + @Produces("text/plain") + public String createEvent(@PathParam("name") String name) { + for (Iterator<OutputStream> it = eventsStreams.values().iterator(); it.hasNext();) { + OutputStream out = it.next(); + try { + out.write(("News: event " + name + " created").getBytes()); + out.flush(); + } catch (IOException e) { + it.remove(); + e.printStackTrace(); + } + } + return name + " created"; + } + + @GET + @Path("/events/unregister/{key}") + @Produces("text/plain") + public String unregisterEventsStream(@PathParam("key") String key) { + return (eventsStreams.remove(key) != null ? "Unregistered: " : "Already Unregistered: ") + key; + } +} + + http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/Chapter.java ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/Chapter.java b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/Chapter.java new file mode 100644 index 0000000..dc41db9 --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/Chapter.java @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import javax.xml.bind.annotation.XmlRootElement; + + +@XmlRootElement(name = "Chapter") +public class Chapter { + private String title; + private long id; + + public Chapter() { + } + + public void setTitle(String n) { + title = n; + } + + public String getTitle() { + return title; + } + + public void setId(long i) { + id = i; + } + public long getId() { + return id; + } + + @GET + @Path("/recurse") + @Produces("application/xml") + public Chapter getItself() { + return this; + } + + @Path("/recurse2") + public Chapter getItself2() { + return this; + } + + @GET + @Produces("application/xml;charset=ISO-8859-1") + public Chapter get() { + return this; + } + + @GET + @Path("/ids") + @Produces("application/xml;charset=ISO-8859-1") + public Chapter getWithBookId(@PathParam("bookId") int bookId, + @PathParam("chapterid") int chapterId) { + if (bookId != 123 || chapterId != 1) { + throw new RuntimeException(); + } + return this; + } + + + @GET + @Path("/matched-resources") + @Produces("text/plain") + public String getMatchedResources(@Context UriInfo ui) { + List<String> list = new ArrayList<>(); + for (Object obj : ui.getMatchedResources()) { + list.add(obj.toString()); + } + return list.toString(); + } + + @GET + @Path("/matched%21uris") + @Produces("text/plain") + public String getMatchedUris(@Context UriInfo ui, + @QueryParam("decode") String decode) { + return ui.getMatchedURIs(Boolean.parseBoolean(decode)).toString(); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java new file mode 100644 index 0000000..bc790a8 --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java @@ -0,0 +1,482 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + +import java.util.List; +import java.util.UUID; + +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.cxf.jaxrs.model.AbstractResourceInfo; +import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase; +import org.apache.cxf.transport.websocket.WebSocketConstants; + +import org.junit.BeforeClass; +import org.junit.Test; + +public class JAXRSClientServerWebSocketTest extends AbstractBusClientServerTestBase { + private static final String PORT = BookServerWebSocket.PORT; + + @BeforeClass + public static void startServers() throws Exception { + AbstractResourceInfo.clearAllMaps(); + assertTrue("server did not launch correctly", launchServer(new BookServerWebSocket())); + createStaticBus(); + } + + @Test + public void testBookWithWebSocket() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/booknames").getBytes()); + assertTrue("one book must be returned", wsclient.await(30000)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + + // call the same GET service in the text mode + wsclient.reset(1); + wsclient.sendTextMessage("GET " + getContext() + "/websocket/web/bookstore/booknames"); + assertTrue("one book must be returned", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + + // call another GET service + wsclient.reset(1); + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/books/123").getBytes()); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("application/xml", resp.getContentType()); + value = resp.getTextEntity(); + assertTrue(value.startsWith("<?xml ") && value.endsWith("</Book>")); + + // call the POST service + wsclient.reset(1); + wsclient.sendMessage( + ("POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n\r\n123") + .getBytes()); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + value = resp.getTextEntity(); + assertEquals("123", value); + + // call the same POST service in the text mode + wsclient.reset(1); + wsclient.sendTextMessage( + "POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n\r\n123"); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + value = resp.getTextEntity(); + assertEquals("123", value); + + // call the GET service returning a continous stream output + wsclient.reset(6); + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/bookbought").getBytes()); + assertTrue("response expected", wsclient.await(5)); + received = wsclient.getReceivedResponses(); + assertEquals(6, received.size()); + resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("application/octet-stream", resp.getContentType()); + value = resp.getTextEntity(); + assertTrue(value.startsWith("Today:")); + for (int r = 2, i = 1; i < 6; r *= 2, i++) { + // subsequent data should not carry the headers nor the status. + resp = received.get(i); + assertEquals(0, resp.getStatusCode()); + assertEquals(r, Integer.parseInt(resp.getTextEntity())); + } + } finally { + wsclient.close(); + } + } + + @Test + public void testGetBookStream() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + wsclient.reset(5); + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/bookstream\r\nAccept: application/json\r\n\r\n") + .getBytes()); + assertTrue("response expected", wsclient.await(5)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(5, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("application/json", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals(value, getBookJson(1)); + for (int i = 2; i <= 5; i++) { + // subsequent data should not carry the headers nor the status. + resp = received.get(i - 1); + assertEquals(0, resp.getStatusCode()); + assertEquals(resp.getTextEntity(), getBookJson(i)); + } + } finally { + wsclient.close(); + } + } + + @Test + public void testGetBookStreamWithIDReferences() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + wsclient.reset(5); + String reqid = UUID.randomUUID().toString(); + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/bookstream\r\nAccept: application/json\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + reqid + "\r\n\r\n") + .getBytes()); + assertTrue("response expected", wsclient.await(5)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(5, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("application/json", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals(value, getBookJson(1)); + for (int i = 2; i <= 5; i++) { + // subsequent data should not carry the status but the id header + resp = received.get(i - 1); + assertEquals(0, resp.getStatusCode()); + assertEquals(reqid, resp.getId()); + assertEquals(resp.getTextEntity(), getBookJson(i)); + } + } finally { + wsclient.close(); + } + } + + private String getBookJson(int index) { + return "{\"Book\":{\"id\":" + index + ",\"name\":\"WebSocket" + index + "\"}}"; + } + + @Test + public void testBookWithWebSocketAndHTTP() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/booknames").getBytes()); + assertTrue("one book must be returned", wsclient.await(3)); + List<Object> received = wsclient.getReceived(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = new WebSocketTestClient.Response(received.get(0)); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + + testGetBookHTTPFromWebSocketEndpoint(); + + } finally { + wsclient.close(); + } + } + + @Test + public void testGetBookHTTPFromWebSocketEndpoint() throws Exception { + String address = "http://localhost:" + getPort() + getContext() + "/websocket/web/bookstore/books/1"; + WebClient wc = WebClient.create(address); + wc.accept("application/xml"); + Book book = wc.get(Book.class); + assertEquals(1L, book.getId()); + } + + @Test + public void testBookWithWebSocketServletStream() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/booknames/servletstream") + .getBytes()); + assertTrue("one book must be returned", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + } finally { + wsclient.close(); + } + } + + @Test + public void testWrongMethod() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service using POST + wsclient.reset(1); + wsclient.sendMessage(("POST " + getContext() + "/websocket/web/bookstore/booknames").getBytes()); + assertTrue("error response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(405, resp.getStatusCode()); + } finally { + wsclient.close(); + } + } + + @Test + public void testPathRestriction() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service over the different path + wsclient.sendMessage(("GET " + getContext() + "/websocket/bookstore2").getBytes()); + assertTrue("error response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(404, resp.getStatusCode()); + } finally { + wsclient.close(); + } + } + + @Test + public void testCallsWithIDReferences() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the POST service without requestId + wsclient.sendTextMessage( + "POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n\r\n459"); + assertTrue("response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("459", value); + String id = resp.getId(); + assertNull("response id is incorrect", id); + + // call the POST service twice with a unique requestId + wsclient.reset(2); + String reqid1 = UUID.randomUUID().toString(); + String reqid2 = UUID.randomUUID().toString(); + wsclient.sendTextMessage( + "POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + reqid1 + "\r\n\r\n549"); + wsclient.sendTextMessage( + "POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + reqid2 + "\r\n\r\n495"); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + for (WebSocketTestClient.Response r : received) { + assertEquals(200, r.getStatusCode()); + assertEquals("text/plain", r.getContentType()); + value = r.getTextEntity(); + id = r.getId(); + if (reqid1.equals(id)) { + assertEquals("549", value); + } else if (reqid2.equals(id)) { + assertEquals("495", value); + } else { + fail("unexpected responseId: " + id); + } + } + } finally { + wsclient.close(); + } + } + + @Test + public void testCallsInParallel() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service that takes a long time to response + wsclient.reset(2); + wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/hold/3000"); + wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/hold/3000"); + // each call takes 3 seconds but executed in parallel, so waiting 4 secs is sufficient + assertTrue("response expected", wsclient.await(4)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(2, received.size()); + } finally { + wsclient.close(); + } + } + + @Test + public void testStreamRegisterAndUnregister() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient1 = new WebSocketTestClient(address); + WebSocketTestClient wsclient2 = new WebSocketTestClient(address); + wsclient1.connect(); + wsclient2.connect(); + try { + String regkey = UUID.randomUUID().toString(); + + EventCreatorRunner runner = new EventCreatorRunner(wsclient2, regkey, 1000, 1000); + new Thread(runner).start(); + + // register for the event stream with requestId ane expect to get 2 messages + wsclient1.reset(3); + wsclient1.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/events/register\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + regkey + "\r\n\r\n"); + assertFalse("only 2 responses expected", wsclient1.await(5)); + List<WebSocketTestClient.Response> received = wsclient1.getReceivedResponses(); + assertEquals(2, received.size()); + + // the first response is the registration confirmation + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertTrue(value.startsWith("Registered " + regkey)); + String id = resp.getId(); + assertEquals("unexpected responseId", regkey, id); + + // the second response is the event news + resp = received.get(1); + assertEquals(0, resp.getStatusCode()); + value = resp.getTextEntity(); + assertEquals("News: event Hello created", value); + id = resp.getId(); + assertEquals("unexpected responseId", regkey, id); + + String[] values = runner.getValues(); + assertTrue(runner.isCompleted()); + assertEquals("Hello created", values[0]); + assertTrue(values[1].startsWith("Unregistered: " + regkey)); + assertEquals("Hola created", values[2]); + } finally { + wsclient1.close(); + wsclient2.close(); + } + } + + private class EventCreatorRunner implements Runnable { + private WebSocketTestClient wsclient; + private String key; + private long delay1; + private long delay2; + private String[] values = new String[3]; + private boolean completed; + + EventCreatorRunner(WebSocketTestClient wsclient, String key, long delay1, long delay2) { + this.wsclient = wsclient; + this.key = key; + this.delay1 = delay1; + this.delay2 = delay2; + } + + public void run() { + try { + Thread.sleep(delay1); + // creating an event and the event stream will see this event + wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/events/create/Hello\r\n\r\n"); + assertTrue("response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + WebSocketTestClient.Response resp = received.get(0); + values[0] = resp.getTextEntity(); + + Thread.sleep(delay2); + wsclient.reset(1); + // unregistering the event stream + wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/events/unregister/" + key + "\r\n\r\n"); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + values[1] = resp.getTextEntity(); + + wsclient.reset(1); + // creating another event and the event stream will not see this event + wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/events/create/Hola\r\n\r\n"); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + values[2] = resp.getTextEntity(); + } catch (InterruptedException e) { + // ignore + } finally { + completed = true; + } + } + + public String[] getValues() { + return values; + } + + public boolean isCompleted() { + return completed; + } + } + + protected String getPort() { + return PORT; + } + protected String getContext() { + return ""; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java.bak ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java.bak b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java.bak new file mode 100644 index 0000000..4747965 --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/JAXRSClientServerWebSocketTest.java.bak @@ -0,0 +1,438 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + +import java.util.List; +import java.util.UUID; + +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.cxf.jaxrs.model.AbstractResourceInfo; +import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase; +import org.apache.cxf.transport.websocket.WebSocketConstants; + +import org.junit.BeforeClass; +import org.junit.Test; + +public class JAXRSClientServerWebSocketTest extends AbstractBusClientServerTestBase { + private static final String PORT = BookServerWebSocket.PORT; + + @BeforeClass + public static void startServers() throws Exception { + AbstractResourceInfo.clearAllMaps(); + assertTrue("server did not launch correctly", launchServer(new BookServerWebSocket())); + createStaticBus(); + } + + @Test + public void testBookWithWebSocket() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + + try { + // call the GET service + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/booknames").getBytes()); + assertTrue("one book must be returned", wsclient.await(500)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + + + } finally { + wsclient.close(); + } + } + + @Test + public void testGetBookStream() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + wsclient.reset(5); + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/bookstream\r\nAccept: application/json\r\n\r\n") + .getBytes()); + assertTrue("response expected", wsclient.await(15)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(5, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("application/json", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals(value, getBookJson(1)); + for (int i = 2; i <= 5; i++) { + // subsequent data should not carry the headers nor the status. + resp = received.get(i - 1); + assertEquals(0, resp.getStatusCode()); + assertEquals(resp.getTextEntity(), getBookJson(i)); + } + } finally { + wsclient.close(); + } + } + + @Test + public void testGetBookStreamWithIDReferences() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + wsclient.reset(5); + String reqid = UUID.randomUUID().toString(); + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/bookstream\r\nAccept: application/json\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + reqid + "\r\n\r\n") + .getBytes()); + assertTrue("response expected", wsclient.await(50)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(5, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("application/json", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals(value, getBookJson(1)); + for (int i = 2; i <= 5; i++) { + // subsequent data should not carry the status but the id header + resp = received.get(i - 1); + assertEquals(0, resp.getStatusCode()); + assertEquals(reqid, resp.getId()); + assertEquals(resp.getTextEntity(), getBookJson(i)); + } + } finally { + wsclient.close(); + } + } + + private String getBookJson(int index) { + return "{\"Book\":{\"id\":" + index + ",\"name\":\"WebSocket" + index + "\"}}"; + } + + @Test + public void testBookWithWebSocketAndHTTP() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/booknames").getBytes()); + assertTrue("one book must be returned", wsclient.await(3)); + List<Object> received = wsclient.getReceived(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = new WebSocketTestClient.Response(received.get(0)); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + + testGetBookHTTPFromWebSocketEndpoint(); + + } finally { + wsclient.close(); + } + } + + @Test + public void testGetBookHTTPFromWebSocketEndpoint() throws Exception { + String address = "http://localhost:" + getPort() + getContext() + "/websocket/web/bookstore/books/1"; + WebClient wc = WebClient.create(address); + wc.accept("application/xml"); + Book book = wc.get(Book.class); + assertEquals(1L, book.getId()); + } + + @Test + public void testBookWithWebSocketServletStream() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/booknames/servletstream") + .getBytes()); + assertTrue("one book must be returned", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("CXF in Action", value); + } finally { + wsclient.close(); + } + } + + @Test + public void testWrongMethod() throws Exception { + String address = "ws://127.0.0.1:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service using POST + wsclient.reset(1); + wsclient.sendMessage(("POST " + getContext() + "/websocket/web/bookstore/booknames").getBytes()); + assertTrue("error response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(405, resp.getStatusCode()); + } finally { + wsclient.close(); + } + } + + @Test + public void testPathRestriction() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service over the different path + wsclient.sendMessage(("GET " + getContext() + "/websocket/bookstore2").getBytes()); + assertTrue("error response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(1, received.size()); + WebSocketTestClient.Response resp = received.get(0); + //assertEquals(400, resp.getStatusCode()); TODO later + assertEquals(404, resp.getStatusCode()); + } finally { + wsclient.close(); + } + } + + @Test + public void testCallsWithIDReferences() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the POST service without requestId + wsclient.sendMessage( + ("POST " + getContext() + + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n\r\n459").getBytes()); + assertTrue("response expected", wsclient.await(300)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertEquals("459", value); + String id = resp.getId(); + assertNull("response id is incorrect", id); + + // call the POST service twice with a unique requestId + wsclient.reset(2); + String reqid1 = UUID.randomUUID().toString(); + String reqid2 = UUID.randomUUID().toString(); + /*wsclient + .sendMessage(("POST " + getContext() + + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + + reqid1 + "\r\n\r\n549").getBytes()); + wsclient + .sendMessage(("POST " + getContext() + + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + + reqid2 + "\r\n\r\n495").getBytes());*/ + wsclient.sendTextMessage( + "POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + reqid1 + "\r\n\r\n549"); + wsclient.sendTextMessage( + "POST " + getContext() + "/websocket/web/bookstore/booksplain\r\nContent-Type: text/plain\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + reqid2 + "\r\n\r\n495"); + assertTrue("response expected", wsclient.await(300)); + received = wsclient.getReceivedResponses(); + for (WebSocketTestClient.Response r : received) { + assertEquals(200, r.getStatusCode()); + assertEquals("text/plain", r.getContentType()); + value = r.getTextEntity(); + id = r.getId(); + if (reqid1.equals(id)) { + assertEquals("549", value); + } else if (reqid2.equals(id)) { + assertEquals("495", value); + } else { + fail("unexpected responseId: " + id); + } + } + } finally { + wsclient.close(); + } + } + + @Test + public void testCallsInParallel() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient = new WebSocketTestClient(address); + wsclient.connect(); + try { + // call the GET service that takes a long time to response + wsclient.reset(2); + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/hold/3000").getBytes()); + wsclient.sendMessage(("GET " + getContext() + "/websocket/web/bookstore/hold/3000").getBytes()); + /*wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/hold/3000"); + wsclient.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/hold/3000");*/ + // each call takes 3 seconds but executed in parallel, so waiting 4 secs is sufficient + assertTrue("response expected", wsclient.await(20)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + assertEquals(2, received.size()); + } finally { + wsclient.close(); + } + } + + @Test + public void testStreamRegisterAndUnregister() throws Exception { + String address = "ws://localhost:" + getPort() + getContext() + "/websocket/web/bookstore"; + + WebSocketTestClient wsclient1 = new WebSocketTestClient(address); + WebSocketTestClient wsclient2 = new WebSocketTestClient(address); + wsclient1.connect(); + wsclient2.connect(); + try { + String regkey = UUID.randomUUID().toString(); + + EventCreatorRunner runner = new EventCreatorRunner(wsclient2, regkey, 1000, 1000); + new Thread(runner).start(); + + // register for the event stream with requestId ane expect to get 2 messages + wsclient1.reset(3); + wsclient1.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/events/register\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + + regkey + "\r\n\r\n").getBytes()); + /*wsclient1.sendTextMessage( + "GET " + getContext() + "/websocket/web/bookstore/events/register\r\n" + + WebSocketConstants.DEFAULT_REQUEST_ID_KEY + ": " + regkey + "\r\n\r\n");*/ + assertFalse("only 2 responses expected", wsclient1.await(25)); + List<WebSocketTestClient.Response> received = wsclient1.getReceivedResponses(); + assertEquals(2, received.size()); + + // the first response is the registration confirmation + WebSocketTestClient.Response resp = received.get(0); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain", resp.getContentType()); + String value = resp.getTextEntity(); + assertTrue(value.startsWith("Registered " + regkey)); + String id = resp.getId(); + assertEquals("unexpected responseId", regkey, id); + + // the second response is the event news + resp = received.get(1); + assertEquals(0, resp.getStatusCode()); + value = resp.getTextEntity(); + assertEquals("News: event Hello created", value); + id = resp.getId(); + assertEquals("unexpected responseId", regkey, id); + + String[] values = runner.getValues(); + assertTrue(runner.isCompleted()); + assertEquals("Hello created", values[0]); + assertTrue(values[1].startsWith("Unregistered: " + regkey)); + assertEquals("Hola created", values[2]); + } finally { + wsclient1.close(); + wsclient2.close(); + } + } + + private class EventCreatorRunner implements Runnable { + private WebSocketTestClient wsclient; + private String key; + private long delay1; + private long delay2; + private String[] values = new String[3]; + private boolean completed; + + EventCreatorRunner(WebSocketTestClient wsclient, String key, long delay1, long delay2) { + this.wsclient = wsclient; + this.key = key; + this.delay1 = delay1; + this.delay2 = delay2; + } + + public void run() { + try { + Thread.sleep(delay1); + // creating an event and the event stream will see this event + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/events/create/Hello\r\n\r\n").getBytes()); + assertTrue("response expected", wsclient.await(3)); + List<WebSocketTestClient.Response> received = wsclient.getReceivedResponses(); + WebSocketTestClient.Response resp = received.get(0); + values[0] = resp.getTextEntity(); + + Thread.sleep(delay2); + wsclient.reset(1); + // unregistering the event stream + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/events/unregister/" + + key + "\r\n\r\n").getBytes()); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + values[1] = resp.getTextEntity(); + + wsclient.reset(1); + // creating another event and the event stream will not see this event + wsclient.sendMessage( + ("GET " + getContext() + "/websocket/web/bookstore/events/create/Hola\r\n\r\n").getBytes()); + assertTrue("response expected", wsclient.await(3)); + received = wsclient.getReceivedResponses(); + resp = received.get(0); + values[2] = resp.getTextEntity(); + } catch (InterruptedException e) { + // ignore + } finally { + completed = true; + } + } + + public String[] getValues() { + return values; + } + + public boolean isCompleted() { + return completed; + } + } + + protected String getPort() { + return PORT; + } + protected String getContext() { + return ""; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBook.java ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBook.java b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBook.java new file mode 100644 index 0000000..5ffac9c --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBook.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + +import javax.xml.bind.annotation.XmlRootElement; + + +@XmlRootElement(name = "SuperBook") +public class SuperBook extends Book implements SuperBookInterface { + private boolean superBook; + + public SuperBook() { + + } + + public SuperBook(String name, long id, boolean superStatus) { + super(name, id); + this.superBook = superStatus; + } + + public boolean isSuperBook() { + return superBook; + } + + public void setSuperBook(boolean superBook) { + this.superBook = superBook; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBookInterface.java ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBookInterface.java b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBookInterface.java new file mode 100644 index 0000000..ff8b628 --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/SuperBookInterface.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + +public interface SuperBookInterface { + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6755b06c/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/WebSocketTestClient.java ---------------------------------------------------------------------- diff --git a/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/WebSocketTestClient.java b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/WebSocketTestClient.java new file mode 100644 index 0000000..ffb46bf --- /dev/null +++ b/systests/transport-undertow/src/test/java/org/apache/cxf/systest/http_undertow/websocket/WebSocketTestClient.java @@ -0,0 +1,329 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.http_undertow.websocket; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.ws.WebSocket; +import com.ning.http.client.ws.WebSocketByteListener; +import com.ning.http.client.ws.WebSocketTextListener; +import com.ning.http.client.ws.WebSocketUpgradeHandler; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.transport.websocket.WebSocketConstants; + + + +/** + * Test client to do websocket calls. + * @see JAXRSClientServerWebSocketTest + * + * we may put this in test-tools so that other systests can use this code. + * for now keep it here to experiment jaxrs websocket scenarios. + */ +class WebSocketTestClient { + private static final Logger LOG = LogUtils.getL7dLogger(WebSocketTestClient.class); + + private List<Object> received; + private List<Object> fragments; + private CountDownLatch latch; + private AsyncHttpClient client; + private WebSocket websocket; + private String url; + + WebSocketTestClient(String url) { + this.received = Collections.synchronizedList(new ArrayList<>()); + this.fragments = Collections.synchronizedList(new ArrayList<>()); + this.latch = new CountDownLatch(1); + this.client = new AsyncHttpClient(); + this.url = url; + } + + public void connect() throws InterruptedException, ExecutionException, IOException { + websocket = client.prepareGet(url).execute( + new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WsSocketListener()).build()).get(); + if (websocket == null) { + throw new NullPointerException("websocket is null"); + } + } + + public void sendTextMessage(String message) { + websocket.sendMessage(message); + } + + public void sendMessage(byte[] message) { + websocket.sendMessage(message); + } + + public boolean await(int secs) throws InterruptedException { + return latch.await(secs, TimeUnit.SECONDS); + } + + public void reset(int count) { + latch = new CountDownLatch(count); + received.clear(); + } + + public List<Object> getReceived() { + return received; + } + + public List<Response> getReceivedResponses() { + Object[] objs = received.toArray(); + List<Response> responses = new ArrayList<>(objs.length); + for (Object o : objs) { + responses.add(new Response(o)); + } + return responses; + } + + public void close() { + if (websocket != null) { + websocket.close(); + } + if (client != null) { + client.close(); + } + } + + class WsSocketListener implements WebSocketTextListener, WebSocketByteListener { + + public void onOpen(WebSocket ws) { + LOG.info("[ws] opened"); + } + + public void onClose(WebSocket ws) { + LOG.info("[ws] closed"); + } + + public void onError(Throwable t) { + LOG.info("[ws] error: " + t); + } + + public void onMessage(byte[] message) { + received.add(message); + LOG.info("[ws] received bytes --> " + makeString(message)); + latch.countDown(); + } + + public void onFragment(byte[] fragment, boolean last) { + LOG.info("[ws] received fragment bytes (last?" + last + ") --> " + fragment); + processFragments(fragment, last); + } + + public void onMessage(String message) { + received.add(message); + LOG.info("[ws] received --> " + message); + latch.countDown(); + } + + public void onFragment(String fragment, boolean last) { + LOG.info("[ws] received fragment (last?" + last + ") --> " + fragment); + processFragments(fragment, last); + } + + private void processFragments(Object f, boolean last) { + synchronized (fragments) { + fragments.add(f); + if (last) { + if (f instanceof String) { + // string + StringBuilder sb = new StringBuilder(); + for (Iterator<Object> it = fragments.iterator(); it.hasNext();) { + Object o = it.next(); + if (o instanceof String) { + sb.append((String)o); + it.remove(); + } + } + received.add(sb.toString()); + } else { + // byte[] + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + for (Iterator<Object> it = fragments.iterator(); it.hasNext();) { + Object o = it.next(); + if (o instanceof byte[]) { + bao.write((byte[])o, 0, ((byte[])o).length); + it.remove(); + } + } + received.add(bao.toByteArray()); + } + } + } + } + } + + private static String makeString(byte[] data) { + return data == null ? null : makeString(data, 0, data.length).toString(); + } + + private static StringBuilder makeString(byte[] data, int offset, int length) { + if (data .length > 256) { + return makeString(data, offset, 256).append("..."); + } + StringBuilder xbuf = new StringBuilder().append("\nHEX: "); + StringBuilder cbuf = new StringBuilder().append("\nASC: "); + for (byte b : data) { + writeHex(xbuf, 0xff & b); + writePrintable(cbuf, 0xff & b); + } + return xbuf.append(cbuf); + } + + private static void writeHex(StringBuilder buf, int b) { + buf.append(Integer.toHexString(0x100 | (0xff & b)).substring(1)).append(' '); + } + + private static void writePrintable(StringBuilder buf, int b) { + if (b == 0x0d) { + buf.append("\\r"); + } else if (b == 0x0a) { + buf.append("\\n"); + } else if (b == 0x09) { + buf.append("\\t"); + } else if ((0x80 & b) != 0) { + buf.append('.').append(' '); + } else { + buf.append((char)b).append(' '); + } + buf.append(' '); + } + + //TODO this is a temporary way to verify the response; we should come up with something better. + public static class Response { + private Object data; + private int pos; + private int statusCode; + private String contentType; + private String id; + private Object entity; + + Response(Object data) { + this.data = data; + String line; + boolean first = true; + while ((line = readLine()) != null) { + if (first && isStatusCode(line)) { + statusCode = Integer.parseInt(line); + continue; + } else { + first = false; + } + + int del = line.indexOf(':'); + String h = line.substring(0, del).trim(); + String v = line.substring(del + 1).trim(); + if ("Content-Type".equalsIgnoreCase(h)) { + contentType = v; + } else if (WebSocketConstants.DEFAULT_RESPONSE_ID_KEY.equals(h)) { + id = v; + } + } + if (data instanceof String) { + entity = ((String)data).substring(pos); + } else if (data instanceof byte[]) { + entity = new byte[((byte[])data).length - pos]; + System.arraycopy((byte[])data, pos, (byte[])entity, 0, ((byte[])entity).length); + } + } + + private static boolean isStatusCode(String line) { + char c = line.charAt(0); + return '0' <= c && c <= '9'; + } + + public int getStatusCode() { + return statusCode; + } + + public String getContentType() { + return contentType; + } + + public Object getEntity() { + return entity; + } + + public String getTextEntity() { + return gettext(entity); + } + + public String getId() { + return id; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Status: ").append(statusCode).append("\r\n"); + sb.append("Type: ").append(contentType).append("\r\n"); + sb.append("Entity: ").append(gettext(entity)).append("\r\n"); + return sb.toString(); + } + + private String readLine() { + StringBuilder sb = new StringBuilder(); + while (pos < length(data)) { + int c = getchar(data, pos++); + if (c == '\n') { + break; + } else if (c == '\r') { + continue; + } else { + sb.append((char)c); + } + } + if (sb.length() == 0) { + return null; + } + return sb.toString(); + } + + private int length(Object o) { + if (o instanceof String) { + return ((String)o).length(); + } else if (o instanceof char[]) { + return ((char[])o).length; + } else if (o instanceof byte[]) { + return ((byte[])o).length; + } else { + return 0; + } + } + + private int getchar(Object o, int p) { + return 0xff & (o instanceof String ? ((String)o).charAt(p) : (o instanceof byte[] ? ((byte[])o)[p] : -1)); + } + + private String gettext(Object o) { + return o instanceof String ? (String)o : (o instanceof byte[] ? new String((byte[])o) : null); + } + } +}