<file_sep>package weblab.http.header;

import weblab.http.statuscode.StatusCode;

/**
 * Header to define the Http Version in use
 * 
 * @author <NAME> <<EMAIL>>
 */
public class HttpHeader implements Header {
	private String mHttpVersion;
	private StatusCode mStatusCode;

	public HttpHeader(String httpVersion, StatusCode statusCode) {
		mHttpVersion = httpVersion;
		mStatusCode = statusCode;
	}

	@Override
	public String toString() {
		return mHttpVersion + " " + mStatusCode.getStatusCode() + " " + mStatusCode.getMessage() + EOL;
	}
}
<file_sep># Simple Http Server Express.js Style #

This is a simple implementation of a Http Server in Java. Currently it only serves static files like HTML, Css, Javascript and Images and enables some simple routing mechanisms.

## Execution ##

The server.jar in the dist folder can be used to execute the server. It serves all files from the public_html.

## Routing ##

The server is written similar to express.js. The only things needed to instantiate the server is a config and a base server.


```
#!java

Config config = ConfigFactory.parseFile(...);
Server server = new MultithreadedServer(config.getInt("server.port"), config);

```

After that we are good to go and can start attaching middlewares and routes to the server. Like in express.js the server itself is also a router.


```
#!java

// express.js style routing, pretty neat right?
server
// this middleware attaches the Server: NDS Http Server header to each request, just for fun
.use((request, response, next) -> {
	response.addHeader(new ServerHeader("NDS Http Server"));
	next.apply();
})

// serve static files
.use(new FileMiddleware(config.getString("server.static")))

// no file found? let's try these routes then
.use("/", (request, response) -> {
        // listens to /
	// let's return only hello world
	response.setStatusCode(StatusCode.OK);
	response.addHeader(new ContentTypeHeader("text/html"));
	response.setContent("Hello World");
});

/*
 * ok that is neat, but what about nested routers?
 * oh thats possible too? cool! let's try that.
 */

Router router = new Router();

router.use("/test", (request, response) -> {
	response.setStatusCode(StatusCode.OK);
	response.addHeader(new ContentTypeHeader("text/html"));
	response.setContent("I am the nested route /test");
});

// let's add the sub router
// makes the /test route available under /workspace/text
server.use("/workspace", router);

// no route matched till now apparently, so let's return 404
server.use((request, response, next) -> {
	if(response.getStatusCode() == null) {
		response.setStatusCode(StatusCode.NOT_FOUND);
	}
});

```

Ok after setting up the server all that is left, is to start it


```
#!java

server.start();

```

The Configuration File looks like this

```
#!json

"server": {
	"port": 9090,
	"static": "./public_html"
},

"mime": {
	"extension": {
		"js": "application/javascript",
		"css": "text/css",
		"html": "text/html",
		"jpg": "image/jpeg",
		"jpeg": "image/jpeg",
		"png": "image/png",
		"ico": "image/x-icon"
	}
}

```
<file_sep>package weblab.server;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.typesafe.config.Config;

/**
 * Abstract Server implementation, which handles incoming connections, logging,
 * cleanup and middlewares.
 * 
 * @author <NAME> <<EMAIL>>
 */
public abstract class Server extends Router {
	/**
	 * logging file
	 */
	private FileWriter mLog;

	/**
	 * logging format
	 */
	private DateFormat mFormat;

	/**
	 * defines if logging is enabled or not
	 */
	private boolean _DEBUG_ = false;

	/**
	 * socket the server is listening to
	 */
	private ServerSocket mServerSocket;

	/**
	 * the connection handler for new connections
	 */
	private ConnectionHandler mConnectionHandler;

	/**
	 * server configuration (e.g. allowed mime types)
	 */
	private Config mConfig;

	/**
	 * custom constructor, needs to be called by subclasses
	 * 
	 * @param port
	 *            the port on which the server will listen
	 * @throws IOException
	 *             when the ServerSocket can not be opened
	 */
	public Server(final int port, final Config config) throws IOException {
		mServerSocket = new ServerSocket(port);

		mConnectionHandler = new ConnectionHandler(this);
		mFormat = new SimpleDateFormat("HH:mm:ss:SSS");

		mConfig = config;

		try {
			mLog = new FileWriter(new File("log.txt"));
		} catch (IOException e) {
			_DEBUG_ = false;
			System.err.println("[" + mFormat.format(new Date()) + "] could not open logfile");
		}
		
		System.out.println("Server now running on port " + port);
	}

	/**
	 * get the server's configuration
	 * 
	 * @return the server configuration
	 */
	public Config getConfig() {
		return mConfig;
	}

	/**
	 * starts the server and initializes the ConnectionHandler waiting for new
	 * Clients trying to connect calling {@link #clientConnected(Socket)} on the
	 * server
	 */
	public void start() {
		System.out.println("starting server ...");

		if (running()) {
			mConnectionHandler.start();
		}
	}

	/**
	 * shuts down the server by closing the {@link ServerSocket} and calls
	 * {@link #cleanUp()} on the server
	 */
	public synchronized void shutdown() {
		System.out.println("shutting down server ...");

		if (running()) {
			log("initializing server-shutdown ...");

			try {
				mServerSocket.close();
			} catch (IOException e) {
				log("error while closing the server-socket");
			}

			cleanUp();
		}
	}

	/**
	 * get the {@link ServerSocket} of the server
	 * 
	 * @return the {@link ServerSocket} on which the server is accepting
	 *         connections
	 */
	public ServerSocket getSocket() {
		return mServerSocket;
	}

	/**
	 * check if the server is running or not
	 * 
	 * @return true if the server is running, false otherwise
	 */
	public synchronized boolean running() {
		if (mServerSocket == null) {
			return false;
		}
		return !mServerSocket.isClosed();
	}

	/**
	 * called by the {@link ConnectionHandler} when a client connected
	 * 
	 * @param socket
	 *            which has been opened by ServerSocket
	 */
	public abstract void clientConnected(final Socket socket);

	/**
	 * called by {@link Connection} when the connection is being closed
	 * 
	 * @param connection
	 *            the connection which was closed
	 */
	public abstract void clientDisconnected(Connection connection);

	/**
	 * called after closing the ServerSocket to clean up anyhting necessary
	 */
	public abstract void cleanUp();

	/**
	 * write a given message to the log-file
	 * 
	 * @param message
	 *            the message to be written
	 */
	public synchronized void log(final String message) {
		if (_DEBUG_ && mLog != null) {
			try {
				String log = "[" + mFormat.format(new Date()) + "] " + message + "\n\n";
				System.out.println(log);
				mLog.append(log);
				mLog.flush();
			} catch (IOException e) {
				System.err
						.println("[" + mFormat.format(new Date()) + "] something went wrong while writing to log-file");
			}
		}
	}
}
<file_sep>package weblab.server;

import java.util.UUID;

/**
 * Interface connections and sessions implement.
 * 
 * @author <NAME> <<EMAIL>>
 */
public interface Session {
	/**
	 * get the server on which the session is running
	 * 
	 * @return the actual server
	 */
	public Server getServer();

	/**
	 * get the session's id
	 * 
	 * @return the session id
	 */
	public UUID getSessionId();
}
<file_sep>package weblab.request;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import com.typesafe.config.Config;

import weblab.http.method.Method;
import weblab.http.request.Query;

public class RequestFactory {
	public static Request build(InputStream in, Config config) throws IOException {
		// let's read what the client sent
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));

		String http;

		try {
			// first line gives information about the Http protocol
			http = reader.readLine();

			if (http != null) {
				// the request looks something like this:
				// GET|POST /theUrl HTTP_VERSION

				// let's get the needed information (token == " ")
				StringTokenizer tokenizer = new StringTokenizer(http);

				// get the method
				String method = tokenizer.nextToken();

				// get the query url (also extract parameters)
				Query query = new Query(tokenizer.nextToken());

				String httpVersion = tokenizer.nextToken();

				// TODO maybe include headers later for caching etc.
				List<String> headers = new LinkedList<>();
				String header;

				while ((header = reader.readLine()) != null && !header.equals("")) {
					headers.add(header);
				}

				Request request = new Request(config, in, Method.fromSlug(method), query, httpVersion, headers);

				return request;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}
}
<file_sep>package weblab.http.request;

import java.util.HashMap;

/**
 * Represents a Query for a request, query string and parameters
 * 
 * @author <NAME> <<EMAIL>>
 */
public class Query {
	/**
	 * the query string
	 */
	private String mQuery;

	/**
	 * the passed parameters
	 */
	private HashMap<String, String> mParameters;

	/**
	 * create an empty query
	 */
	public Query() {
		this("", new HashMap<String, String>());
	}

	/**
	 * create a query from a query string and extract the parameters
	 * 
	 * @param queryString
	 *            the query string
	 */
	public Query(String queryString) {
		String[] queryParts = queryString.split("\\?");

		try {
			mQuery = queryParts[0];
			mParameters = Query.buildParameters(queryParts[1]);
		} catch (Exception e) {
			// no query parameters
		}
	}

	/**
	 * create a query by passing the query string and the parameters
	 * 
	 * @param query
	 * @param parameters
	 */
	public Query(String query, HashMap<String, String> parameters) {
		mQuery = query;
		mParameters = parameters;
	}

	/**
	 * get the query string
	 * 
	 * @return the query string
	 */
	public String getQuery() {
		return mQuery;
	}

	/**
	 * set the query string
	 * 
	 * @param query
	 *            the query string
	 */
	public void setQuery(String query) {
		mQuery = query;
	}

	/**
	 * get the parameters
	 * 
	 * @return the parameters
	 */
	public HashMap<String, String> getParameters() {
		return mParameters;
	}

	/**
	 * set the parameters
	 * 
	 * @param parameters
	 *            the parameters
	 */
	public void setParameters(HashMap<String, String> parameters) {
		mParameters = parameters;
	}

	/**
	 * add a paramter to the query
	 * 
	 * @param key
	 *            the key
	 * @param value
	 *            the value
	 */
	public void addParameter(String key, String value) {
		mParameters.put(key, value);
	}

	/**
	 * build parameters from a query string
	 * 
	 * @param parameterString
	 *            the query string
	 *            
	 * @return the extracted parameters
	 */
	public static HashMap<String, String> buildParameters(String parameterString) {
		HashMap<String, String> parameters = new HashMap<>();

		String[] parameterParts = parameterString.split("\\&");
		for (String parameter : parameterParts) {
			try {
				String[] parameterPair = parameter.split("\\=");
				parameters.put(parameterPair[0], parameterPair[1]);
			} catch (Exception e) {
				// ignore invalid parameter Strings
			}
		}

		return parameters;
	}
}
<file_sep>package weblab.http.header;

/**
 * Header to define the type of the content being sent
 * 
 * @author <NAME> <<EMAIL>>
 */
public class ContentTypeHeader implements Header {
	public static final String IDENTIFIER = "Content-Type";

	private String mContentType;

	public ContentTypeHeader(String contentType) {
		mContentType = contentType;
	}

	@Override
	public String toString() {
		return IDENTIFIER + ": " + mContentType + EOL;
	}
}
<file_sep>package weblab.request;

public class MiddlewareUsable implements Usable {
	private Middleware mMiddleware;
	
	public MiddlewareUsable(Middleware middleware) {
		mMiddleware = middleware;
	}

	@Override
	public boolean applicable(Request request) {
		return true;
	}

	@Override
	public void use(Request request, Response response, Nextable next) {
		mMiddleware.execute(request, response, next);
	}
}
<file_sep>package weblab.http.header;

/**
 * Not supported in current version
 * 
 * @author <NAME> <<EMAIL>>
 */
public class ReferrerHeader implements Header {
	public static final String IDENTIFIER = "Referer";
	
	private String mReferer;
	
	public ReferrerHeader(String referer) {
		mReferer = referer;
	}
	
	@Override
	public String toString() {
		return IDENTIFIER + ": " + mReferer + EOL;
	}
}
<file_sep>(function() {
	console.log('hello world');
	
	var p = document.createElement('p');
	p.innerHTML = 'I was appended through Javascript';
	
	var img = document.getElementsByTagName('img')[0];
	
	img.parentNode.insertBefore(p, img);
})();<file_sep>package weblab.server;

import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import com.typesafe.config.Config;

/**
 * Multithreaded server implementation for resolving requests.
 * 
 * @author <NAME> <<EMAIL>>
 */
public class MultithreadedServer extends Server {
	private List<Connection> mConnections;

	/**
	 * Create a new instance of MultithreadedServer
	 * 
	 * @param port
	 *            the port the server will listen to
	 * 
	 * @throws IOException
	 *             if the port is already in use
	 */
	public MultithreadedServer(int port, Config config) throws IOException {
		super(port, config);

		if (running()) {
			mConnections = new ArrayList<Connection>();
		}
	}

	/**
	 * called if the ConnectionHandler opened a new connection
	 * 
	 * @param socket
	 *            the socket of the new connection
	 */
	@Override
	public void clientConnected(Socket socket) {
		// create a new connection
		Connection connection = new Connection(UUID.randomUUID(), this, socket);

		// add the connection so it can be terminated if the server is shut down
		mConnections.add(connection);

		// resolve the connection
		connection.start();
	}

	@Override
	public synchronized void clientDisconnected(Connection connection) {
		mConnections.remove(connection);
	}

	/**
	 * close all open connections
	 */
	@Override
	public void cleanUp() {
		for (Connection connection : mConnections) {
			try {
				connection.close();
			} catch (IOException e) {
				// nothing to do here
			}
		}

		mConnections.clear();
	}
}<file_sep>package weblab.request;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import weblab.http.file.FileStreamer;
import weblab.http.header.Header;
import weblab.http.header.HttpHeader;
import weblab.http.statuscode.StatusCode;

public class Response extends HeaderHolder {
	private OutputStream mOut;
	private StatusCode mStatusCode;
	private String mContent;
	private FileStreamer mFileStreamer;

	public Response(OutputStream out) {
		mOut = out;
	}

	public OutputStream getOutputStream() {
		return mOut;
	}

	public void setStatusCode(StatusCode statusCode) {
		mStatusCode = statusCode;
	}

	public StatusCode getStatusCode() {
		return mStatusCode;
	}

	public void setFileStreamer(FileStreamer fileStreamer) {
		mFileStreamer = fileStreamer;
	}

	public void setContent(String content) {
		mContent = content;
	}

	public void sendBack(Request request) throws IOException {
		// create a data stream
		DataOutputStream out = new DataOutputStream(getOutputStream());

		out.writeBytes(new HttpHeader(request.getHttpVersion(), getStatusCode()).toString());

		// send the headers back to the client
		for (Header header : getHeaders()) {
			out.writeBytes(header.toString());
		}

		// empty line as delimiter of headers and content
		out.writeBytes(Header.EOL);

		// write the content back to the client
		if (mContent != null) {
			out.writeBytes(mContent);
		} else if(mFileStreamer != null){
			mFileStreamer.stream(out);
		}
	}
}
<file_sep>package weblab.request;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import weblab.http.header.Header;

public class HeaderHolder {
	private List<Header> mHeaders = new LinkedList<>();
	
	public List<Header> getHeaders() {
		return mHeaders;
	}

	public void addHeader(Header header) {
		Iterator<Header> iterator = mHeaders.iterator();
		while(iterator.hasNext()) {
			if(iterator.next().getClass().equals(header.getClass())) {
				iterator.remove();
				break;
			}
		}
		
		mHeaders.add(header);
	}
	
	public Header getHeader(Class<? extends Header> header) {
		Iterator<Header> iterator = mHeaders.iterator();
		Header current;
		
		while(iterator.hasNext()) {
			current = iterator.next();
			
			if(header.isAssignableFrom(current.getClass())) {
				iterator.remove();
				return current;
			}
		}
		
		return null;
	}
}
<file_sep>package weblab.http.header;

/**
 * Header to define the server name
 * 
 * @author <NAME> <<EMAIL>>
 */
public class ServerHeader implements Header {
	private String mServerName;

	public ServerHeader(String serverName) {
		mServerName = serverName;
	}

	@Override
	public String toString() {
		return "Server: " + mServerName + EOL;
	}
}
