Commit d66df4a1 authored by Sefa Eyeoglu's avatar Sefa Eyeoglu

Little refactoring, documented code (some parts), Added annotations (NotNull and Nullable)

parent ffe7b4ea
......@@ -9,6 +9,11 @@
<version>0.0.1</version>
<dependencies>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>15.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
......
package net.scrumplex.implify;
import net.scrumplex.implify.core.ImplifyServer;
import net.scrumplex.implify.core.exchange.handler.FileSystemHTTPHandler;
import net.scrumplex.implify.exceptions.ImplifyException;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) {
ImplifyServer implifyServer = new ImplifyServer(8080, "default");
try {
implifyServer.start();
implifyServer.setHttpHandler(new FileSystemHTTPHandler(Paths.get("").toFile(), "index.html"));
} catch (ImplifyException e) {
e.printStackTrace();
}
......
package net.scrumplex.implify.concurrent;
import net.scrumplex.implify.core.ImplifyServer;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadFactory;
......@@ -12,11 +13,11 @@ public class ImplifyThreadFactory implements ThreadFactory {
this.serverInstance = serverInstance;
}
public Thread newThread(Runnable r) {
public Thread newThread(@NotNull Runnable r) {
return newThread(r, null);
}
public Thread newThread(Runnable r, String context) {
public Thread newThread(@NotNull Runnable r, String context) {
Thread t = new Thread(r);
t.setName(context == null ? "implify_" + t.getId() : context);
t.setPriority(8);
......
......@@ -2,6 +2,7 @@ package net.scrumplex.implify.core;
import net.scrumplex.implify.core.exchange.HTTPRequest;
import net.scrumplex.implify.core.exchange.HTTPResponse;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
......@@ -14,7 +15,7 @@ import java.util.Map;
public class HTTPUtils {
public static String getContentTypeFromFile(File f) {
public static String getContentTypeFromFile(@NotNull File f) {
String name = f.getName();
if (name.endsWith(".js")) {
......@@ -30,7 +31,7 @@ public class HTTPUtils {
return "application/octet-stream";
}
public static String encodeString(String string) {
public static String encodeString(@NotNull String string) {
try {
return URLEncoder.encode(string, "UTF-8");
} catch (UnsupportedEncodingException ignored) {
......@@ -38,7 +39,7 @@ public class HTTPUtils {
return string;
}
public static String decodeString(String string) {
public static String decodeString(@NotNull String string) {
try {
return URLDecoder.decode(string, "UTF-8");
} catch (UnsupportedEncodingException ignored) {
......@@ -46,28 +47,37 @@ public class HTTPUtils {
return string;
}
public static Map<String, String> parseParameterString(String parameterString) {
public static Map<String, String> parseParameterString(@NotNull String parameterString) {
Map<String, String> parameters = new HashMap<>();
if (parameterString.contains("&")) {
//Multiple parameters
String[] params = parameterString.split("&");
for (String param : params) {
String[] parts = param.split("=", 2);
parameters.put(parts[0], HTTPUtils.decodeString(parts[1]));
if(param.contains("=")) {
String[] parts = param.split("=", 2);
parameters.put(parts[0], HTTPUtils.decodeString(parts[1]));
} else {
parameters.put(param, null);
}
}
} else {
//Just one parameter
String[] parts = parameterString.split("=", 2);
parameters.put(parts[0], HTTPUtils.decodeString(parts[1]));
if(parameterString.contains("=")) {
String[] parts = parameterString.split("=", 2);
parameters.put(parts[0], HTTPUtils.decodeString(parts[1]));
} else {
parameters.put(parameterString, null);
}
}
return parameters;
}
public static HTTPResponse getInternalServerErrorResponse(ImplifyServer serverInstance, HTTPRequest request) {
public static HTTPResponse getInternalServerErrorResponse(@NotNull ImplifyServer serverInstance, @NotNull HTTPRequest request) {
HTTPResponse response = new HTTPResponse(serverInstance, request);
response.setStatusCode(HTTPResponse.Code.INTERNAL_SERVER_ERROR);
response.setContentType("text/plain");
response.setResponseData("Internal Server Error");
response.save();
return response;
}
......
......@@ -2,14 +2,19 @@ package net.scrumplex.implify.core;
import net.scrumplex.implify.concurrent.ImplifyThreadFactory;
import net.scrumplex.implify.core.exchange.*;
import net.scrumplex.implify.core.exchange.handler.DefaultHTTPHandler;
import net.scrumplex.implify.core.exchange.preprocess.DefaultHTTPPreprocessor;
import net.scrumplex.implify.core.exchange.socket.DefaultSocketHandler;
import net.scrumplex.implify.exceptions.ImplifyException;
import net.scrumplex.implify.exceptions.ImplifyExceptionHandler;
import net.scrumplex.implify.lang.HTTPHandler;
import net.scrumplex.implify.lang.HTTPPreprocessor;
import net.scrumplex.implify.lang.RawHandler;
import net.scrumplex.implify.exceptions.ExceptionHandler;
import net.scrumplex.implify.core.exchange.handler.HTTPHandler;
import net.scrumplex.implify.core.exchange.preprocess.HTTPPreprocessor;
import net.scrumplex.implify.core.exchange.socket.SocketHandler;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.io.DataOutputStream;
import java.io.IOException;
......@@ -28,9 +33,9 @@ public class ImplifyServer {
private boolean running;
private ImplifyExceptionHandler exceptionHandler;
private ImplifyThreadFactory threadFactory;
private RawHandler rawSocketHandler;
private ExceptionHandler exceptionHandler;
private SocketHandler socketHandler;
private HTTPPreprocessor httpPreprocessor;
private HTTPHandler httpHandler;
private Logger logger;
......@@ -38,15 +43,15 @@ public class ImplifyServer {
private ServerSocket serverSocket;
private Thread mainThread;
public ImplifyServer(int port, String identifier) {
public ImplifyServer(int port, @NotNull String identifier) {
this("0.0.0.0", port, identifier);
}
public ImplifyServer(String ip, int port, String identifier) {
public ImplifyServer(@NotNull String ip, int port, @NotNull String identifier) {
this(ip, port, 1024, identifier);
}
public ImplifyServer(String ip, int port, int backlog, String identifier) {
public ImplifyServer(@NotNull String ip, int port, int backlog, @NotNull String identifier) {
this.ip = ip;
this.port = port;
this.backlog = backlog;
......@@ -54,14 +59,20 @@ public class ImplifyServer {
exceptionHandler = new ImplifyExceptionHandler(this);
threadFactory = new ImplifyThreadFactory(this);
//May be changed by user if needed
rawSocketHandler = new RawSocketHandler();
httpPreprocessor = new HTTPDefaultPreprocessor();
socketHandler = new DefaultSocketHandler();
httpPreprocessor = new DefaultHTTPPreprocessor();
//Should be changed by user
httpHandler = new HTTPExampleHandler();
httpHandler = new DefaultHTTPHandler();
this.logger = LogManager.getLogger("implify_" + identifier);
}
/**
* Starts the current Implify instance. It will start a new thread, which will start other threads if needed.
* Exceptions will be handled by an {@link ExceptionHandler}, which can be changed with {@link ImplifyServer#setExceptionHandler(ExceptionHandler)}
*
* @throws ImplifyException if instance already running or an I/O error occurs.
*/
public void start() throws ImplifyException {
if (running)
throw new ImplifyException("Instance " + identifier + " already running!");
......@@ -70,7 +81,7 @@ public class ImplifyServer {
serverSocket = new ServerSocket(port, backlog, InetAddress.getByName(ip));
running = true;
} catch (IOException e) {
getExceptionHandler().caughtException(e, getInstanceIdentifier());
throw new ImplifyException(e);
}
mainThread = getThreadFactory().newThread(() -> {
......@@ -79,25 +90,31 @@ public class ImplifyServer {
Socket socket = serverSocket.accept();
getThreadFactory().newThread(() -> {
try {
HTTPRequest request = getRawSocketHandler().handle(this, socket);
if (request == null)
HTTPRequest request = getSocketHandler().handle(this, socket);
if (request == null) {
if (!socket.isClosed())
socket.close();
HTTPResponse response = getHttpPreprocessor().process(this, request);
if (response == null) {
response = HTTPUtils.getInternalServerErrorResponse(this, request);
return;
}
response = getHttpHandler().handle(this, request, response);
if (response == null) {
HTTPResponse response = getHttpPreprocessor().process(this, request);
if (response == null)
response = HTTPUtils.getInternalServerErrorResponse(this, request);
if (!response.isSaved()) {
response = getHttpHandler().handle(this, request, response);
if (response == null)
response = HTTPUtils.getInternalServerErrorResponse(this, request);
if(!response.isSaved())
response.save();
}
//Send to client
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeBytes("HTTP/1.1 " + response.getStatusCode().getCode() + " " + response.getStatusCode().getCodeName() + "\n");
for (String headerKey : response.getHeaders().keySet()) {
//TODO: URL ENCODING?
String headerValue = response.getHeaders().get(headerKey);
out.writeBytes(headerKey + ": " + headerValue + "\n");
}
......@@ -125,6 +142,11 @@ public class ImplifyServer {
mainThread.start();
}
/**
* Stops current implify instance.
*
* @throws ImplifyException if instance not running or an I/O error occurs.
*/
public void stop() throws ImplifyException {
if (!running)
throw new ImplifyException("Instance " + identifier + " not running!");
......@@ -134,14 +156,18 @@ public class ImplifyServer {
serverSocket.close();
running = false;
} catch (IOException e) {
getExceptionHandler().caughtException(e, "stop_instance_" + identifier);
throw new ImplifyException(e);
}
}
public ImplifyExceptionHandler getExceptionHandler() {
public ExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
private void setExceptionHandler(@NotNull ExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
public String getIdentifier() {
return identifier;
}
......@@ -158,23 +184,23 @@ public class ImplifyServer {
return httpPreprocessor;
}
public void setHttpPreprocessor(HTTPPreprocessor httpPreprocessor) {
public void setHttpPreprocessor(@NotNull HTTPPreprocessor httpPreprocessor) {
this.httpPreprocessor = httpPreprocessor;
}
public RawHandler getRawSocketHandler() {
return rawSocketHandler;
public SocketHandler getSocketHandler() {
return socketHandler;
}
public void setRawSocketHandler(RawHandler rawSocketHandler) {
this.rawSocketHandler = rawSocketHandler;
public void setSocketHandler(@NotNull SocketHandler socketHandler) {
this.socketHandler = socketHandler;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
public void setLogger(@NotNull Logger logger) {
this.logger = logger;
}
......@@ -186,7 +212,7 @@ public class ImplifyServer {
return httpHandler;
}
public void setHttpHandler(HTTPHandler httpHandler) {
public void setHttpHandler(@NotNull HTTPHandler httpHandler) {
this.httpHandler = httpHandler;
}
}
......@@ -2,6 +2,8 @@ package net.scrumplex.implify.core.exchange;
import net.scrumplex.implify.core.HTTPUtils;
import net.scrumplex.implify.core.ImplifyServer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.net.Socket;
......@@ -12,8 +14,8 @@ public class HTTPRequest {
private ImplifyServer serverInstance;
private Socket socket;
private String requestPath;
private String requestMethod;
private String path;
private Method method;
private String httpVersion;
private Map<String, String> headers;
private Map<String, String> getParameters;
......@@ -22,45 +24,101 @@ public class HTTPRequest {
public HTTPRequest(ImplifyServer serverInstance, Socket socket) {
this.serverInstance = serverInstance;
this.socket = socket;
this.requestPath = "/";
this.requestMethod = "GET";
this.path = "/";
this.method = Method.GET;
this.httpVersion = "1.1";
this.headers = new HashMap<>();
}
public String getRequestPath() {
return requestPath;
}
public void setRequestPath(String requestPath) {
this.requestPath = requestPath;
}
public String getRequestMethod() {
return requestMethod;
}
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
public String getPath() {
return path;
}
/**
* Setter method for field path. It will HTTP decode the given path.
*
* @param path requested path
*/
public void setPath(@NotNull String path) {
this.path = HTTPUtils.decodeString(path);
}
/**
* Getter method for method.
*
* @return method as {@link java.lang.String}
*/
public Method getMethod() {
return method;
}
/**
* Setter method for field method. This wil wrap the String with the {@link HTTPRequest.Method} class.
*
* @param method method used to request
* @see HTTPRequest#setMethod(Method)
*/
public void setMethod(@NotNull String method) {
Method m = Method.fromName(method);
if (m != null)
setMethod(m);
else
throw new NullPointerException("Method " + method + " not recognized.");
}
/**
* Setter method for field method.
*
* @param method HTTP Method used for the request
*/
public void setMethod(@NotNull Method method) {
this.method = method;
}
/**
* Getter method for socket.
*
* @return socket as {@link java.net,Socket}
*/
public Socket getSocket() {
return socket;
}
public String getHttpVersion() {
/**
* Getter method for httpVersion.
*
* @return httpVersion as {@link java.lang.String}
*/
public String getHTTPVersion() {
return httpVersion;
}
public void setHttpVersion(String httpVersion) {
/**
* Setter method for field httpVersion.
*
* @param httpVersion HTTP Version used by client
*/
public void setHTTPVersion(@NotNull String httpVersion) {
this.httpVersion = httpVersion;
}
/**
* Getter method for headers.
*
* @return headers as {@link java.util.Map}
*/
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
/**
* Setter method for field headers.
*
* @param headers Headers sent by client
*/
public void setHeaders(@Nullable Map<String, String> headers) {
if (headers == null)
this.headers = new HashMap<>();
this.headers = headers;
}
......@@ -72,21 +130,31 @@ public class HTTPRequest {
return response;
}
public void setResponse(HTTPResponse response) {
public void setResponse(@NotNull HTTPResponse response) {
this.response = response;
}
/**
* Closes the HTTPRequest and if not already closed the socket.
*
* @throws IOException if an I/O error occurs when closing this socket.
*/
public void close() throws IOException {
if (isClosed())
if (!isClosed())
socket.close();
this.serverInstance = null;
this.socket = null;
this.requestPath = null;
this.requestMethod = null;
this.path = null;
this.method = null;
this.httpVersion = null;
this.headers = null;
}
/**
* Checks if the request is closed.
*
* @return if HTTPRequest was closed by {@link HTTPRequest#close()}.
*/
public boolean isClosed() {
return socket == null || socket.isClosed();
}
......@@ -95,7 +163,45 @@ public class HTTPRequest {
return getParameters;
}
public void setGETParameterString(String parameterString) {
public void setGETParameterString(@NotNull String parameterString) {
this.getParameters = HTTPUtils.parseParameterString(parameterString);
}
public enum Method {
GET("GET"),
POST("POST");
private String method;
Method(String method) {
this.method = method;
}
/**
* Returns the method's name in uppercase.
* <p>
* Example:
* Method.GET.method = "GET";
*
* @return the method's name in uppercase.
*/
public String getMethodName() {
return method;
}
/**
* Returns the Method from given methodName.
*
* @param name method's name
* @return Method
* @see Method#getMethodName()
*/
public static Method fromName(String name) {
for (Method m : Method.values()) {
if (m.getMethodName().equalsIgnoreCase(name))
return m;
}
return null;
}
}
}
......@@ -2,6 +2,7 @@ package net.scrumplex.implify.core.exchange;
import net.scrumplex.implify.core.ImplifyServer;
import net.scrumplex.implify.exceptions.ImplifyException;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
......@@ -17,8 +18,9 @@ public class HTTPResponse {
private Code statusCode;
private boolean compressed;
private InputStream responseData;
private boolean saved;
public HTTPResponse(ImplifyServer serverInstance, HTTPRequest request) {
public HTTPResponse(@NotNull ImplifyServer serverInstance, @NotNull HTTPRequest request) {
this.serverInstance = serverInstance;
this.request = request;
if (request.getServerInstance() != serverInstance)
......@@ -33,7 +35,7 @@ public class HTTPResponse {
return headers;
}
public void setHeaders(Map<String, String> headers) {
public void setHeaders(@NotNull Map<String, String> headers) {
this.headers = headers;
}
......@@ -42,6 +44,8 @@ public class HTTPResponse {
}
public void setContentLength(long length) {
if (isSaved())
return;
if (isCompressed())
return;
getHeaders().put("Content-Length", String.valueOf(length));
......@@ -51,7 +55,9 @@ public class HTTPResponse {
return Long.parseLong(getHeaders().get("Content-Type"));
}
public void setContentType(String type) {
public void setContentType(@NotNull String type) {
if (isSaved())
return;
getHeaders().put("Content-Type", type);
}
......@@ -60,10 +66,16 @@ public class HTTPResponse {
}
public void setStatusCode(int statusCode) {
this.statusCode = Code.fromStatusCodeNumber(statusCode);
Code c = Code.fromStatusCodeNumber(statusCode);
if (c != null)
setStatusCode(c);
else
throw new NullPointerException("Status code " + statusCode + " not recognized.");
}
public void setStatusCode(Code statusCode) {
public void setStatusCode(@NotNull Code statusCode) {
if (isSaved() || isClosed())
return;
this.statusCode = statusCode;
}
......@@ -72,6 +84,8 @@ public class HTTPResponse {
}
public void setCompressed(boolean compressed) {
if (isSaved() || isClosed())
return;
this.compressed = compressed;
}
......@@ -87,15 +101,21 @@ public class HTTPResponse {
return responseData;
}
public void setResponseData(String responseData) {
public void setResponseData(@NotNull String responseData) {
if (isSaved() || isClosed())
return;
setResponseData(responseData.getBytes());
}
public void setResponseData(InputStream responseData) {
public void setResponseData(@NotNull InputStream responseData) {
if (isSaved() || isClosed())
return;
this.responseData = responseData;
}
public void setResponseData(byte[] responseData) {
public void setResponseData(@NotNull byte[] responseData) {
if (isSaved() || isClosed())
return;
setContentLength(responseData.length);
setResponseData(new ByteArrayInputStream(responseData));
}
......@@ -114,6 +134,14 @@ public class HTTPResponse {
return request == null || request.isClosed();
}
public boolean isSaved() {
return saved;
}
public void save() {
this.saved = true;
}
public enum Code {
CONTINUE(100, "Continue"),
SWITCHING_PROTOCOLS(101, "Switching Protocols"),
......@@ -159,7 +187,7 @@ public class HTTPResponse {
private final int code;
private final String codeName;
private Code(int code, String codeName) {
Code(int code, String codeName) {
this.code = code;
this.codeName = codeName;
}
......
package net.scrumplex.implify.core.exchange;
package net.scrumplex.implify.core.exchange.handler;
import net.scrumplex.implify.core.ImplifyServer;
import net.scrumplex.implify.core.exchange.HTTPRequest;
import net.scrumplex.implify.core.exchange.HTTPResponse;
import net.scrumplex.implify.exceptions.ImplifyException;
import net.scrumplex.implify.lang.HTTPHandler;
public class HTTPExampleHandler implements HTTPHandler {
public class DefaultHTTPHandler implements HTTPHandler {
@Override
public HTTPResponse handle(ImplifyServer serverInstance, HTTPRequest request, HTTPResponse response) throws ImplifyException {
response.setResponseData(getClass().getResourceAsStream("/demo.html"));
response.save();
return response;
}
}