/**
* 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 "";
}
}