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