package org.apereo.cas.adaptors.x509.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
/**
* Provides a simple HTTP Web server that can serve out a single resource for
* all requests. SSL/TLS is not supported.
*
* @author Marvin S. Addison
* @since 3.4.6
*
*/
public class MockWebServer {
private static final Logger LOGGER = LoggerFactory.getLogger(MockWebServer.class);
/** Request handler. */
private Worker worker;
/** Controls the worker thread. */
private Thread workerThread;
/**
* Creates a new server that listens for requests on the given port and
* serves the given resource for all requests.
*
* @param port Server listening port.
* @param resource Resource to serve.
* @param contentType MIME content type of resource to serve.
*/
public MockWebServer(final int port, final Resource resource, final String contentType) {
try {
this.worker = new Worker(new ServerSocket(port), resource, contentType);
} catch (final IOException e) {
throw new RuntimeException("Cannot create Web server", e);
}
}
/** Starts the Web server so it can accept requests on the listening port. */
public void start() {
this.workerThread = new Thread(this.worker, "MockWebServer.Worker");
this.workerThread.start();
}
/** Stops the Web server after processing any pending requests. */
public void stop() {
if (!isRunning()) {
return;
}
this.worker.stop();
try {
this.workerThread.join();
} catch (final InterruptedException e) {
LOGGER.error(e.getMessage(), e);
}
}
/**
* Determines whether the server is running or not.
*
* @return True if server is running, false otherwise.
*/
public boolean isRunning() {
return this.workerThread.isAlive();
}
/**
* Worker class handles request processing.
*/
private static class Worker implements Runnable {
/** Server always returns HTTP 200 response. */
private static final String STATUS_LINE = "HTTP/1.1 200 Success\r\n";
/** Separates HTTP header from body. */
private static final String SEPARATOR = "\r\n";
/** Response buffer size. */
private static final int BUFFER_SIZE = 2048;
private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class);
/** Run flag. */
private boolean running;
/** Server socket. */
private ServerSocket serverSocket;
/** Resource to serve. */
private Resource resource;
/** MIME content type of resource to serve. */
private String contentType;
/**
* Creates a request-handling worker that listens for requests on the
* given socket and serves the given resource for all requests.
*
* @param sock Server socket.
* @param resource Single resource to serve.
* @param contentType MIME content type of resource to serve.
*/
Worker(final ServerSocket sock, final Resource resource, final String contentType) {
this.serverSocket = sock;
this.resource = resource;
this.contentType = contentType;
this.running = true;
}
@Override
public void run() {
while (this.running) {
try {
writeResponse(this.serverSocket.accept());
Thread.sleep(500);
} catch (final SocketException e) {
LOGGER.debug("Stopping on socket close.");
this.running = false;
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
public void stop() {
try {
this.serverSocket.close();
} catch (final IOException e) {
LOGGER.trace("Exception when closing the server socket: [{}]", e.getMessage());
}
}
private void writeResponse(final Socket socket) throws IOException {
LOGGER.debug("Socket response for resource [{}]", resource.getFilename());
final OutputStream out = socket.getOutputStream();
out.write(STATUS_LINE.getBytes());
out.write(header("Content-Length", this.resource.contentLength()));
out.write(header("Content-Type", this.contentType));
out.write(SEPARATOR.getBytes());
final byte[] buffer = new byte[BUFFER_SIZE];
try(InputStream in = this.resource.getInputStream()) {
int count = 0;
while ((count = in.read(buffer)) > -1) {
out.write(buffer, 0, count);
}
}
LOGGER.debug("Wrote response for resource [{}] for [{}]",
resource.getFilename(),
resource.contentLength());
socket.shutdownOutput();
}
private static byte[] header(final String name, final Object value) {
return String.format("%s: %s\r\n", name, value).getBytes();
}
}
}