231 lines
7.6 KiB
Java
231 lines
7.6 KiB
Java
/*
|
|
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package org.xbmc.kore.testutils.tcpserver;
|
|
|
|
import com.squareup.okhttp.internal.Util;
|
|
|
|
import org.xbmc.kore.utils.LogUtils;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStreamReader;
|
|
import java.io.PrintWriter;
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import javax.net.ServerSocketFactory;
|
|
|
|
public class MockTcpServer {
|
|
public static final String TAG = LogUtils.makeLogTag(MockTcpServer.class);
|
|
|
|
private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
|
|
private ServerSocket serverSocket;
|
|
private boolean running;
|
|
private ExecutorService executor;
|
|
private int port = -1;
|
|
private InetSocketAddress inetSocketAddress;
|
|
|
|
private final Set<Socket> openClientSockets =
|
|
Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
|
|
|
|
private final TcpServerConnectionHandler connectionHandler;
|
|
|
|
// TODO
|
|
// Enhance handler to handle multiple connections simultaneously. It can now handle one
|
|
// connection at a time, which makes the current setup of the MockTcpServer (with threading)
|
|
// overkill.
|
|
public interface TcpServerConnectionHandler {
|
|
/**
|
|
* Processes received input
|
|
* @param socket
|
|
* @return id of associated response if any, -1 if more input is needed.
|
|
*/
|
|
void processInput(Socket socket);
|
|
|
|
/**
|
|
* Gets the answer for this handler that should be returned to the server after input has been
|
|
* processed successfully
|
|
* @return answer or null if no answer is available
|
|
*/
|
|
String getResponse();
|
|
}
|
|
|
|
public MockTcpServer(TcpServerConnectionHandler handler) {
|
|
connectionHandler = handler;
|
|
}
|
|
|
|
/**
|
|
* Starts the server on localhost on a random free port
|
|
* @throws IOException
|
|
*/
|
|
public void start() throws IOException {
|
|
start(new InetSocketAddress(InetAddress.getByName("localhost"), 0));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param inetSocketAddress set portnumber to 0 to select a random free port
|
|
* @throws IOException
|
|
*/
|
|
public void start(InetSocketAddress inetSocketAddress) throws IOException {
|
|
if (running) throw new IllegalStateException("start() already called");
|
|
running = true;
|
|
this.inetSocketAddress = inetSocketAddress;
|
|
|
|
serverSocket = serverSocketFactory.createServerSocket();
|
|
// Reuse port if not using a random port
|
|
serverSocket.setReuseAddress(inetSocketAddress.getPort() != 0);
|
|
serverSocket.bind(inetSocketAddress, 50);
|
|
|
|
executor = Executors.newCachedThreadPool(Util.threadFactory("MockTcpServer", false));
|
|
|
|
port = serverSocket.getLocalPort();
|
|
|
|
LogUtils.LOGD(TAG, "start: server started on " + serverSocket.getInetAddress() + ":" + port);
|
|
|
|
executor.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
while (running) {
|
|
try {
|
|
Socket socket = acceptConnection();
|
|
serveConnection(socket);
|
|
} catch(IOException e){
|
|
//Socket closed
|
|
LogUtils.LOGD(TAG, "acceptConnection: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private Socket acceptConnection() throws IOException {
|
|
Socket socket = serverSocket.accept();
|
|
|
|
synchronized (openClientSockets) {
|
|
openClientSockets.add(socket);
|
|
}
|
|
|
|
return socket;
|
|
}
|
|
});
|
|
}
|
|
|
|
public synchronized void shutdown() throws IOException {
|
|
if (!running) return;
|
|
|
|
if (serverSocket == null) throw new IllegalStateException("shutdown() before start()");
|
|
|
|
running = false;
|
|
|
|
// Release all sockets and all threads, even if any close fails.
|
|
for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext(); ) {
|
|
Socket socket = s.next();
|
|
Util.closeQuietly(socket);
|
|
s.remove();
|
|
}
|
|
Util.closeQuietly(serverSocket);
|
|
|
|
executor.shutdown();
|
|
|
|
// Await shutdown.
|
|
try {
|
|
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
|
throw new IOException("Gave up waiting for executor to shut down");
|
|
}
|
|
} catch (InterruptedException e) {
|
|
LogUtils.LOGD(TAG, "shutdown: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the local port of this server socket or -1 if it is not bound
|
|
* @return the local port this server is listening on.
|
|
*/
|
|
public int getPort() {
|
|
return port;
|
|
}
|
|
|
|
public String getHostName() {
|
|
if ( inetSocketAddress == null )
|
|
throw new RuntimeException("Must start server before getting hostname");
|
|
|
|
return inetSocketAddress.getHostName();
|
|
}
|
|
|
|
private void serveConnection(final Socket socket) {
|
|
executor.execute(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
LogUtils.LOGD(TAG, "serveConnection: handling client " + socket.getInetAddress()
|
|
+ ":" + socket.getLocalPort());
|
|
|
|
connectionHandler.processInput(socket);
|
|
socket.close();
|
|
|
|
synchronized (openClientSockets) {
|
|
openClientSockets.remove(socket);
|
|
}
|
|
} catch (IOException e) {
|
|
LogUtils.LOGW(TAG, "processing input from " + socket.getInetAddress() + " failed: " + e);
|
|
}
|
|
}
|
|
});
|
|
|
|
executor.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
while ( ! (serverSocket.isClosed() || socket.isClosed()) ) {
|
|
sendResponse();
|
|
Thread.sleep(100);
|
|
}
|
|
} catch (IOException e) {
|
|
LogUtils.LOGW(TAG, " sending response from " + socket.getInetAddress() + " failed: " + e);
|
|
} catch (InterruptedException e) {
|
|
LogUtils.LOGW(TAG, " wait interrupted" + e);
|
|
}
|
|
}
|
|
|
|
private void sendResponse() throws IOException {
|
|
PrintWriter out =
|
|
new PrintWriter(socket.getOutputStream(), false);
|
|
String answer = connectionHandler.getResponse();
|
|
if (answer != null) {
|
|
LogUtils.LOGD(TAG, "serveConnection: sendResponse: " +answer);
|
|
out.print(answer);
|
|
out.flush();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "MockTcpServer[" + port + "]";
|
|
}
|
|
}
|