package com.github.davidmoten.rx.internal.operators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.BindException;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Test;
import com.github.davidmoten.junit.Asserts;
import com.github.davidmoten.rx.Actions;
import com.github.davidmoten.rx.Bytes;
import com.github.davidmoten.rx.Functions;
import com.github.davidmoten.rx.IO;
import com.github.davidmoten.rx.RetryWhen;
import rx.Observable;
import rx.Scheduler;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.observers.TestSubscriber;
import rx.plugins.RxJavaHooks;
import rx.schedulers.Schedulers;
public final class ObservableServerSocketTest {
private static final Charset UTF_8 = Charset.forName("UTF-8");
// private static final int PORT = 12345;
private static final String TEXT = "hello there";
private static final int POOL_SIZE = 10;
private static final Scheduler scheduler = Schedulers
.from(Executors.newFixedThreadPool(POOL_SIZE));
private static final Scheduler clientScheduler = Schedulers
.from(Executors.newFixedThreadPool(POOL_SIZE));
@Test
public void serverSocketReadsTcpPushWhenBufferIsSmallerThanInput()
throws UnknownHostException, IOException, InterruptedException {
checkServerSocketReadsTcpPushWhenBufferSizeIs(TEXT, 4);
}
@Test
public void serverSocketReadsTcpPushWhenBufferIsBiggerThanInput()
throws UnknownHostException, IOException, InterruptedException {
checkServerSocketReadsTcpPushWhenBufferSizeIs(TEXT, 8192);
}
@Test
public void serverSocketReadsTcpPushWhenBufferIsSameSizeAsInput()
throws UnknownHostException, IOException, InterruptedException {
checkServerSocketReadsTcpPushWhenBufferSizeIs(TEXT, TEXT.length());
}
@Test
public void serverSocketReadsTcpPushWhenInputIsEmpty()
throws UnknownHostException, IOException, InterruptedException {
checkServerSocketReadsTcpPushWhenBufferSizeIs("", 4);
}
@Test
public void serverSocketReadsTcpPushWhenInputIsOneCharacter()
throws UnknownHostException, IOException, InterruptedException {
checkServerSocketReadsTcpPushWhenBufferSizeIs("a", 4);
}
@Test
public void errorEmittedIfServerSocketBusy() throws IOException {
reset();
TestSubscriber<Object> ts = TestSubscriber.create();
ServerSocket socket = null;
int port = 12345;
try {
socket = new ServerSocket(port);
IO.serverSocket(port).readTimeoutMs(10000).bufferSize(5).create().subscribe(ts);
ts.assertNoValues();
ts.assertNotCompleted();
ts.assertTerminalEvent();
assertTrue(ts.getOnErrorEvents().get(0).getCause().getCause() instanceof BindException);
} finally {
socket.close();
}
}
@Test
public void isUtilityClass() {
Asserts.assertIsUtilityClass(ObservableServerSocket.class);
}
@Test
public void isUtilityClassIO() {
Asserts.assertIsUtilityClass(IO.class);
}
@Test
public void testCloserWhenDoesNotThrow() {
final AtomicBoolean called = new AtomicBoolean();
Closeable c = new Closeable() {
@Override
public void close() throws IOException {
called.set(true);
}
};
Actions.close().call(c);
assertTrue(called.get());
}
@Test
public void testCloserWhenThrows() {
final IOException ex = new IOException();
Closeable c = new Closeable() {
@Override
public void close() throws IOException {
throw ex;
}
};
try {
Actions.close().call(c);
Assert.fail();
} catch (RuntimeException e) {
assertTrue(ex == e.getCause());
}
}
private static void reset() {
com.github.davidmoten.rx.Schedulers.blockUntilWorkFinished(scheduler, POOL_SIZE);
com.github.davidmoten.rx.Schedulers.blockUntilWorkFinished(clientScheduler, POOL_SIZE);
}
@Test
public void testEarlyUnsubscribe()
throws UnknownHostException, IOException, InterruptedException {
reset();
TestSubscriber<Object> ts = TestSubscriber.create();
final AtomicReference<byte[]> result = new AtomicReference<byte[]>();
try {
int bufferSize = 4;
AtomicInteger port = new AtomicInteger();
IO.serverSocketAutoAllocatePort(Actions.setAtomic(port)) //
.readTimeoutMs(10000) //
.bufferSize(bufferSize) //
.create() //
.flatMap(new Func1<Observable<byte[]>, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(Observable<byte[]> g) {
return g //
.first() //
.compose(Bytes.collect()) //
.doOnNext(Actions.setAtomic(result)) //
.doOnNext(new Action1<byte[]>() {
@Override
public void call(byte[] bytes) {
System.out.println(Thread.currentThread().getName() + ": "
+ new String(bytes));
}
}) //
.onErrorResumeNext(Observable.<byte[]> empty()) //
.subscribeOn(scheduler);
}
}) //
.subscribeOn(scheduler) //
.subscribe(ts);
Thread.sleep(300);
Socket socket = new Socket("localhost", port.get());
OutputStream out = socket.getOutputStream();
out.write("12345678901234567890".getBytes());
out.close();
socket.close();
Thread.sleep(1000);
assertEquals("1234", new String(result.get(), UTF_8));
} finally {
// will close server socket
ts.unsubscribe();
}
}
@Test
public void testCancelDoesNotHaveToWaitForTimeout()
throws UnknownHostException, IOException, InterruptedException {
reset();
RxJavaHooks.setOnError(Actions.printStackTrace1());
TestSubscriber<Object> ts = TestSubscriber.create();
final AtomicReference<byte[]> result = new AtomicReference<byte[]>();
AtomicInteger port = new AtomicInteger();
try {
int bufferSize = 4;
IO.serverSocketAutoAllocatePort(Actions.setAtomic(port)) //
.readTimeoutMs(Integer.MAX_VALUE).bufferSize(bufferSize).create()
.flatMap(new Func1<Observable<byte[]>, Observable<String>>() {
@Override
public Observable<String> call(Observable<byte[]> g) {
return g //
.first() //
.compose(Bytes.collect()) //
.doOnNext(Actions.setAtomic(result)) //
.map(new Func1<byte[], String>() {
@Override
public String call(byte[] bytes) {
return new String(bytes, UTF_8);
}
}) //
.doOnNext(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(Thread.currentThread().getName() + ": " + s);
}
}) //
.onErrorResumeNext(Observable.<String> empty()) //
.subscribeOn(scheduler);
}
}).subscribeOn(scheduler) //
.subscribe(ts);
Thread.sleep(300);
@SuppressWarnings("resource")
Socket socket = new Socket("localhost", port.get());
OutputStream out = socket.getOutputStream();
out.write("hell".getBytes(UTF_8));
out.flush();
Thread.sleep(500);
assertEquals(Arrays.asList("hell"), ts.getOnNextEvents());
ts.assertNoTerminalEvent();
out.write("will-fail".getBytes(UTF_8));
out.flush();
} finally {
// will close server socket
try {
ts.unsubscribe();
Thread.sleep(300);
} finally {
RxJavaHooks.reset();
}
}
}
@Test
public void testLoad() throws InterruptedException {
reset();
AtomicBoolean errored = new AtomicBoolean(false);
for (int k = 0; k < 1; k++) {
System.out.println("loop " + k);
TestSubscriber<String> ts = TestSubscriber.create();
final AtomicInteger connections = new AtomicInteger();
final AtomicInteger port = new AtomicInteger();
try {
int bufferSize = 4;
IO.serverSocketAutoAllocatePort(Actions.setAtomic(port)) //
.readTimeoutMs(30000) //
.bufferSize(bufferSize) //
.create().flatMap(new Func1<Observable<byte[]>, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(Observable<byte[]> g) {
return g //
.doOnSubscribe(Actions.increment0(connections)) //
.compose(Bytes.collect()) //
.doOnError(Actions.printStackTrace1()) //
.subscribeOn(scheduler) //
.retryWhen(RetryWhen.delay(1, TimeUnit.SECONDS).build());
}
}, 1) //
.map(new Func1<byte[], String>() {
@Override
public String call(byte[] bytes) {
return new String(bytes, UTF_8);
}
}) //
.doOnNext(Actions.decrement1(connections)) //
.doOnError(Actions.printStackTrace1()) //
.doOnError(Actions.<Throwable> setToTrue1(errored)) //
.subscribeOn(scheduler) //
.subscribe(ts);
TestSubscriber<Object> ts2 = TestSubscriber.create();
final Set<String> messages = new ConcurrentSkipListSet<String>();
final int messageBlocks = 10;
int numMessages = 1000;
final AtomicInteger openSockets = new AtomicInteger(0);
// sender
Observable.range(1, numMessages).flatMap(new Func1<Integer, Observable<Object>>() {
@Override
public Observable<Object> call(Integer n) {
return Observable.defer(new Func0<Observable<Object>>() {
@Override
public Observable<Object> call() {
// System.out.println(Thread.currentThread().getName()
// +
// " - writing message");
String id = UUID.randomUUID().toString();
StringBuilder s = new StringBuilder();
for (int i = 0; i < messageBlocks; i++) {
s.append(id);
}
messages.add(s.toString());
Socket socket = null;
try {
socket = new Socket("localhost", port.get());
// allow reuse so we don't run out of
// sockets
socket.setReuseAddress(true);
socket.setSoTimeout(5000);
openSockets.incrementAndGet();
OutputStream out = socket.getOutputStream();
for (int i = 0; i < messageBlocks; i++) {
out.write(id.getBytes(UTF_8));
}
out.close();
openSockets.decrementAndGet();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return Observable.<Object> just(1);
}
}) //
.timeout(30, TimeUnit.SECONDS) //
.subscribeOn(clientScheduler);
}
}) //
.doOnError(Actions.printStackTrace1()) //
.subscribe(ts2);
ts2.awaitTerminalEvent();
ts2.assertCompleted();
// allow server to complete processing
Thread.sleep(1000);
assertEquals(messages, new HashSet<String>(ts.getOnNextEvents()));
assertFalse(errored.get());
} finally {
ts.unsubscribe();
Thread.sleep(1000);
reset();
}
}
}
private void checkServerSocketReadsTcpPushWhenBufferSizeIs(String text, int bufferSize)
throws UnknownHostException, IOException, InterruptedException {
reset();
TestSubscriber<Object> ts = TestSubscriber.create();
final AtomicReference<byte[]> result = new AtomicReference<byte[]>();
AtomicInteger port = new AtomicInteger();
try {
IO.serverSocketAutoAllocatePort(Actions.setAtomic(port)) //
.readTimeoutMs(10000) //
.bufferSize(bufferSize) //
.create() //
.flatMap(new Func1<Observable<byte[]>, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(Observable<byte[]> g) {
return g //
.compose(Bytes.collect()) //
.doOnNext(Actions.setAtomic(result)) //
.doOnNext(new Action1<byte[]>() {
@Override
public void call(byte[] bytes) {
System.out.println(Thread.currentThread().getName() + ": "
+ new String(bytes));
}
}) //
.onErrorResumeNext(Observable.<byte[]> empty()) //
.subscribeOn(scheduler);
}
}).subscribeOn(scheduler) //
.subscribe(ts);
Socket socket = null;
for (int i = 0; i < 15; i++) {
Thread.sleep(1000);
try {
socket = new Socket("127.0.0.1", port.get());
break;
} catch (ConnectException e) {
System.out.println(e.getMessage());
}
}
assertNotNull("could not connect to port " + port.get(), socket);
OutputStream out = socket.getOutputStream();
out.write(text.getBytes());
out.close();
socket.close();
Thread.sleep(1000);
assertEquals(text, new String(result.get(), UTF_8));
} finally {
// will close server socket
ts.unsubscribe();
}
}
@Test
public void testAcceptSocketRejectsAlways()
throws UnknownHostException, IOException, InterruptedException {
reset();
TestSubscriber<Object> ts = TestSubscriber.create();
try {
int bufferSize = 4;
AtomicInteger port = new AtomicInteger();
IO.serverSocketAutoAllocatePort(Actions.setAtomic(port)) //
.readTimeoutMs(10000) //
.acceptTimeoutMs(200) //
.bufferSize(bufferSize) //
.acceptSocketIf(Functions.alwaysFalse()) //
.create() //
.subscribeOn(scheduler) //
.subscribe(ts);
Thread.sleep(300);
Socket socket = new Socket("localhost", port.get());
OutputStream out = socket.getOutputStream();
out.write("12345678901234567890".getBytes());
out.close();
socket.close();
Thread.sleep(1000);
ts.assertNoValues();
} finally {
// will close server socket
ts.unsubscribe();
}
}
public static void main(String[] args) throws InterruptedException {
reset();
TestSubscriber<Object> ts = TestSubscriber.create();
IO.serverSocket(12345).readTimeoutMs(10000).bufferSize(8).create()
.flatMap(new Func1<Observable<byte[]>, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(Observable<byte[]> g) {
return g //
.compose(Bytes.collect()) //
.doOnNext(new Action1<byte[]>() {
@Override
public void call(byte[] bytes) {
System.out.println(Thread.currentThread().getName() + ": "
+ new String(bytes).trim());
}
}) //
.onErrorResumeNext(Observable.<byte[]> empty()) //
.subscribeOn(scheduler);
}
}).subscribeOn(scheduler) //
.subscribe(ts);
Thread.sleep(10000000);
}
}