diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efff418 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +httpserver/target \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 7530104..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# simple-java-http-server -Create a Simple HTTP Server in Java Tutorial Series - https://www.youtube.com/playlist?list=PLAuGQNR28pW56GigraPdiI0oKwcs8gglW diff --git a/Request.txt b/Request.txt deleted file mode 100644 index bdd8dbc..0000000 --- a/Request.txt +++ /dev/null @@ -1,11 +0,0 @@ -GET / HTTP/1.1 -Host: localhost:8080 -Connection: keep-alive -Upgrade-Insecure-Requests: 1 -User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36 -Sec-Fetch-User: ?1 -Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 -Sec-Fetch-Site: none -Sec-Fetch-Mode: navigate -Accept-Encoding: gzip, deflate, br -Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4 diff --git a/WebRoot/favicon.ico b/WebRoot/favicon.ico deleted file mode 100644 index dad8daf..0000000 Binary files a/WebRoot/favicon.ico and /dev/null differ diff --git a/WebRoot/index.html b/WebRoot/index.html deleted file mode 100644 index 13265de..0000000 --- a/WebRoot/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - Simple Java HTTP Server - - -
-

Welcome to our first rendered page!

-
-

This page was delivered using our own made Https Server made in Java.

-
-
- - \ No newline at end of file diff --git a/WebRoot/logo.png b/WebRoot/logo.png deleted file mode 100644 index 430c02b..0000000 Binary files a/WebRoot/logo.png and /dev/null differ diff --git a/httpserver/WebRoot/Index.html b/httpserver/WebRoot/Index.html new file mode 100644 index 0000000..eb7d87b --- /dev/null +++ b/httpserver/WebRoot/Index.html @@ -0,0 +1,17 @@ + + + Simple Java HTTP Server + + +
+

Welcome to my rendered page!

+
+

This page was delivered using my custom-built HTTP Server made in Java.

+
+
+broken + \ No newline at end of file diff --git a/httpserver/WebRoot/favicon.ico b/httpserver/WebRoot/favicon.ico new file mode 100644 index 0000000..a243e62 Binary files /dev/null and b/httpserver/WebRoot/favicon.ico differ diff --git a/httpserver/WebRoot/randomlogo.png b/httpserver/WebRoot/randomlogo.png new file mode 100644 index 0000000..9bfcf64 Binary files /dev/null and b/httpserver/WebRoot/randomlogo.png differ diff --git a/httpserver/pom.xml b/httpserver/pom.xml new file mode 100644 index 0000000..8faf82f --- /dev/null +++ b/httpserver/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + com.johan.httpserver + httpserver + 1.0-SNAPSHOT + + + 17 + 17 + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + com.johan.httpserver.httpserver + + + + + + + + + com.fasterxml.jackson.core + jackson-core + 2.21.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.21.1 + + + org.slf4j + slf4j-api + 2.0.13 + + + ch.qos.logback + logback-classic + 1.5.13 + + + org.jetbrains + annotations + 24.0.1 + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + + \ No newline at end of file diff --git a/httpserver/src/main/java/com/johan/http/BadHttpVersionException.java b/httpserver/src/main/java/com/johan/http/BadHttpVersionException.java new file mode 100644 index 0000000..f127ce0 --- /dev/null +++ b/httpserver/src/main/java/com/johan/http/BadHttpVersionException.java @@ -0,0 +1,11 @@ +package com.johan.http; + +public class BadHttpVersionException extends Exception { + public BadHttpVersionException() { + super("Unsupported or malformed HTTP version"); + } + + public BadHttpVersionException(String message) { + super(message); + } +} diff --git a/httpserver/src/main/java/com/johan/http/HttpMethod.java b/httpserver/src/main/java/com/johan/http/HttpMethod.java new file mode 100644 index 0000000..1101c71 --- /dev/null +++ b/httpserver/src/main/java/com/johan/http/HttpMethod.java @@ -0,0 +1,17 @@ +package com.johan.http; + +public enum HttpMethod { + GET, HEAD; + public static final int MAX_LENGTH; + + //this checks all the valid METHODS declared in the server and sets max_length as the biggest header + static{ + int tempMaxLength = -1; + for (HttpMethod method : HttpMethod.values()){ + if(method.name().length()>tempMaxLength){ + tempMaxLength = method.name().length(); + } + } + MAX_LENGTH = tempMaxLength; + } +} diff --git a/httpserver/src/main/java/com/johan/http/HttpParser.java b/httpserver/src/main/java/com/johan/http/HttpParser.java new file mode 100644 index 0000000..f7b0616 --- /dev/null +++ b/httpserver/src/main/java/com/johan/http/HttpParser.java @@ -0,0 +1,135 @@ +package com.johan.http; + +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public class HttpParser { + + private final static Logger LOGGER = LoggerFactory.getLogger(HttpParser.class); + + private static final int SP = 0x20; //32 + private static final int CR = 0x0D; //13 + private static final int LF = 0x0A; //10 + + public static HttpRequest parseHttpReq(InputStream iptStream) throws HttpParsingException { + InputStreamReader isr = new InputStreamReader(iptStream, StandardCharsets.US_ASCII); + + HttpRequest request = new HttpRequest(); + try { + parseRequestLine(isr, request); + parseHeader(isr, request); + }catch(IOException e){ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + parseBody(isr, request); + + return request; + } + private static void parseRequestLine(InputStreamReader isr, HttpRequest req) throws IOException, HttpParsingException { + boolean methodParsed = false; + boolean reqTargetParsed = false; + StringBuilder processDataBuffer = new StringBuilder(); + int _byte; + while ((_byte=isr.read()) >=0){ + if(_byte==CR){ + _byte=isr.read(); + if(_byte==LF){ + LOGGER.debug("Request Line VERSION to process : {}", processDataBuffer.toString()); + if (!methodParsed || !reqTargetParsed) { + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + try { + req.setHttpVersion(processDataBuffer.toString()); + }catch (BadHttpVersionException e) { + LOGGER.error("Bad HTTP Version received : {}", e.getMessage()); + throw new HttpParsingException(HttpStatusCode.SERVER_ERROR_505_HTTP_VERSION_NOT_SUPPORTED); + } + return; + }else{ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + } + if(_byte==SP){ + if (!methodParsed) { + LOGGER.debug("Request Line METHOD to process : {}", processDataBuffer.toString()); + try { + req.setMethod(processDataBuffer.toString()); + methodParsed = true; + }catch (HttpParsingException e){ + LOGGER.error("Invalid HTTP Method received : {}", e.getMessage()); + throw new HttpParsingException(HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); + } + }else if (!reqTargetParsed) { + LOGGER.debug("Request Line REQ TARGET to process : {}", processDataBuffer.toString()); + req.setRequestTarget(processDataBuffer.toString()); + reqTargetParsed = true; + } + else{ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + processDataBuffer.delete(0, processDataBuffer.length()); + }else{ + processDataBuffer.append((char)_byte); + if(!methodParsed){ + if(processDataBuffer.length()>HttpMethod.MAX_LENGTH){ + LOGGER.error("Terminating connection, Bad Method received : {}", processDataBuffer.toString()); + throw new HttpParsingException(HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); + } + } + } + } + } + + private static void parseHeader(InputStreamReader isr, HttpRequest req) throws HttpParsingException, IOException { + StringBuilder processDataBuffer = new StringBuilder(); + boolean headerParsed = false; + int _byte; + while ((_byte=isr.read()) >=0){ + if(_byte==CR){ + _byte=isr.read(); + if(_byte==LF) { + String line = processDataBuffer.toString().trim(); + if (line.isEmpty()) { + if(!headerParsed){ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + return; + } + processingHeaderField(processDataBuffer, req); + headerParsed = true; + processDataBuffer.delete(0, processDataBuffer.length()); + } else{ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + } else{ + processDataBuffer.append((char)_byte); + } + } + } + + private static void processingHeaderField(StringBuilder processDataBuffer, HttpRequest req) throws HttpParsingException { + String rawHeaderField = processDataBuffer.toString(); + if (rawHeaderField.length() > 8192) { + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_414_URI_TOO_LONG); + } + int colonIndex = rawHeaderField.indexOf(':'); + if(colonIndex == -1){ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + String fieldName = rawHeaderField.substring(0, colonIndex).trim(); + String fieldValue = rawHeaderField.substring(colonIndex+1).trim(); + if(fieldName.isEmpty()){ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + req.addHeader(fieldName, fieldValue); + } + + private static void parseBody(InputStreamReader isr, HttpRequest req) { + + } +} diff --git a/src/main/java/com/coderfromscratch/http/HttpParsingException.java b/httpserver/src/main/java/com/johan/http/HttpParsingException.java similarity index 90% rename from src/main/java/com/coderfromscratch/http/HttpParsingException.java rename to httpserver/src/main/java/com/johan/http/HttpParsingException.java index 1e372eb..d4474d5 100644 --- a/src/main/java/com/coderfromscratch/http/HttpParsingException.java +++ b/httpserver/src/main/java/com/johan/http/HttpParsingException.java @@ -1,4 +1,4 @@ -package com.coderfromscratch.http; +package com.johan.http; public class HttpParsingException extends Exception { diff --git a/httpserver/src/main/java/com/johan/http/HttpRequest.java b/httpserver/src/main/java/com/johan/http/HttpRequest.java new file mode 100644 index 0000000..32470c5 --- /dev/null +++ b/httpserver/src/main/java/com/johan/http/HttpRequest.java @@ -0,0 +1,65 @@ +package com.johan.http; + +import java.util.HashMap; +import java.util.Set; + +public class HttpRequest { + + private HttpMethod method; + private String requestTarget; + private String originalHttpVersion; + private HttpVersion getBestCompatibleVersion; + private HashMap headers = new HashMap<>(); + + HttpRequest() { + } + + public HttpMethod getMethod() { + return method; + } + + public String getRequestTarget() { + return requestTarget; + } + + public HttpVersion getBestCompatibleVersion() { return getBestCompatibleVersion; } + + public String getOriginalHttpVersion() { return originalHttpVersion; } + + public Set getHeaderNames() { + return headers.keySet(); + } + + public String getHeader(String headerName) { + return headers.get(headerName.toLowerCase()); + } + + void setMethod(String methodName) throws HttpParsingException { + for (HttpMethod method: HttpMethod.values()) { + if(methodName.equals(method.name())){ + this.method = method; + return; + } + } + throw new HttpParsingException(HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); + } + + public void setRequestTarget(String requestTarget) throws HttpParsingException { + if (requestTarget == null || requestTarget.isEmpty()){ + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + this.requestTarget = requestTarget; + } + + public void setHttpVersion(String originalHttpVersion) throws BadHttpVersionException, HttpParsingException { + this.originalHttpVersion = originalHttpVersion; + this.getBestCompatibleVersion = HttpVersion.getBestCompatibleVersion(originalHttpVersion); + if(this.getBestCompatibleVersion == null){ + throw new BadHttpVersionException(); + } + } + + void addHeader(String headerName, String HeaderField){ + headers.put(headerName.toLowerCase(), HeaderField); + } +} diff --git a/httpserver/src/main/java/com/johan/http/HttpStatusCode.java b/httpserver/src/main/java/com/johan/http/HttpStatusCode.java new file mode 100644 index 0000000..e13d252 --- /dev/null +++ b/httpserver/src/main/java/com/johan/http/HttpStatusCode.java @@ -0,0 +1,21 @@ +package com.johan.http; + +public enum HttpStatusCode { + /* ---Client Errors --- */ + CLIENT_ERROR_400_BAD_REQ(400, "Bad Request"), + CLIENT_ERROR_401_METHOD_NOT_ALLOWED(401, "Method not allowed"), + CLIENT_ERROR_404_NOT_FOUND(404, "Request Target not found"), + CLIENT_ERROR_414_URI_TOO_LONG(414, "URI too long"), + /* ---Server Errors --- */ + SERVER_ERROR_500_INTERNAL_SERVER_ERROR(500, "Internal server error"), + SERVER_ERROR_501_NOT_IMPLEMENTED(501, "Method Not implemented"), + SERVER_ERROR_505_HTTP_VERSION_NOT_SUPPORTED(505, "Http Version Not Supported"); + + public final int STATUS_CODE; + public final String MESSAGE; + + HttpStatusCode(int STATUS_CODE, String MESSAGE) { + this.STATUS_CODE=STATUS_CODE; + this.MESSAGE = MESSAGE; + } +} diff --git a/httpserver/src/main/java/com/johan/http/HttpVersion.java b/httpserver/src/main/java/com/johan/http/HttpVersion.java new file mode 100644 index 0000000..0ab9e6f --- /dev/null +++ b/httpserver/src/main/java/com/johan/http/HttpVersion.java @@ -0,0 +1,29 @@ +package com.johan.http; + +public enum HttpVersion { + + HTTP_1_1("HTTP/1.1", 1, 1), + HTTP_1_0("HTTP/1.0",1,0); + + public final String literal; + public final int major; + public final int minor; + + HttpVersion(String literal, int major, int minor){ + this.literal = literal; + this.major = major; + this.minor = minor; + } + + public static HttpVersion getBestCompatibleVersion(String version) throws BadHttpVersionException { + if(version == null){ + throw new BadHttpVersionException("Version NULL"); + } + for(HttpVersion v : HttpVersion.values()){ + if(v.literal.equals(version)){ + return v; + } + } + return null; + } +} \ No newline at end of file diff --git a/httpserver/src/main/java/com/johan/httpserver/config/HttpConfigException.java b/httpserver/src/main/java/com/johan/httpserver/config/HttpConfigException.java new file mode 100644 index 0000000..47c8f52 --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/config/HttpConfigException.java @@ -0,0 +1,18 @@ +package com.johan.httpserver.config; + +public class HttpConfigException extends RuntimeException{ + public HttpConfigException() { + } + + public HttpConfigException(String message) { + super(message); + } + + public HttpConfigException(String message, Throwable cause) { + super(message, cause); + } + + public HttpConfigException(Throwable cause) { + super(cause); + } +} diff --git a/httpserver/src/main/java/com/johan/httpserver/config/configmanager.java b/httpserver/src/main/java/com/johan/httpserver/config/configmanager.java new file mode 100644 index 0000000..a703fe4 --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/config/configmanager.java @@ -0,0 +1,61 @@ +package com.johan.httpserver.config; + +import com.johan.httpserver.util.json; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +public class configmanager { + private static configmanager myconfigmanager; + private static configuration mycurrentconfig; + + private configmanager(){ + + } + + public static configmanager getInstance(){ + if(myconfigmanager==null) + myconfigmanager = new configmanager(); + return myconfigmanager; + } + + //method for loading a config file by the path provided + public void loadConfigFile(String filePath) { + FileReader fr = null; + try { + fr = new FileReader(filePath); + } catch (FileNotFoundException e) { + throw new HttpConfigException(e); + } + StringBuffer sb = new StringBuffer(); + int i; + try { + while ((i = fr.read())!=-1){ + sb.append((char)i); + } + } catch (IOException e) { + throw new HttpConfigException(e); + } + JsonNode conf = null; + try { + conf = json.parse(sb.toString()); + } catch (IOException e) { + throw new HttpConfigException("Error parsing configuration file", e); + } + try { + mycurrentconfig = json.fromJson(conf, configuration.class); + } catch (IOException e) { + throw new HttpConfigException("Error parsing configuration file, internal", e); + } + } + + //method for returning currently loaded config + public configuration getCurrentConfig(){ + if (mycurrentconfig == null){ + throw new HttpConfigException("No Current Configuration Set"); + } + return mycurrentconfig; + } +} diff --git a/src/main/java/com/coderfromscratch/httpserver/config/Configuration.java b/httpserver/src/main/java/com/johan/httpserver/config/configuration.java similarity index 79% rename from src/main/java/com/coderfromscratch/httpserver/config/Configuration.java rename to httpserver/src/main/java/com/johan/httpserver/config/configuration.java index 91447e7..020ab2e 100644 --- a/src/main/java/com/coderfromscratch/httpserver/config/Configuration.java +++ b/httpserver/src/main/java/com/johan/httpserver/config/configuration.java @@ -1,22 +1,18 @@ -package com.coderfromscratch.httpserver.config; - -public class Configuration { +package com.johan.httpserver.config; +public class configuration { private int port; private String webroot; - + public int getPort() { return port; } - public void setPort(int port) { this.port = port; } - public String getWebroot() { return webroot; } - public void setWebroot(String webroot) { this.webroot = webroot; } diff --git a/httpserver/src/main/java/com/johan/httpserver/core/HttpConnectionWorkerThread.java b/httpserver/src/main/java/com/johan/httpserver/core/HttpConnectionWorkerThread.java new file mode 100644 index 0000000..740ffd4 --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/core/HttpConnectionWorkerThread.java @@ -0,0 +1,105 @@ +package com.johan.httpserver.core; + +import com.johan.http.HttpParser; +import com.johan.http.HttpParsingException; +import com.johan.http.HttpRequest; +import com.johan.http.HttpStatusCode; +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.file.Files; + +public class HttpConnectionWorkerThread extends Thread{ + + final String CRLF = "\r\n"; //13, 10 + private final static Logger LOGGER = LoggerFactory.getLogger(HttpConnectionWorkerThread.class); + private Socket socket; + public HttpConnectionWorkerThread(Socket socket){ + this.socket=socket; + } + + //For sending the Error code and Message to Client + private void sendErrorResponse(HttpStatusCode code, OutputStream op) throws IOException { + String body = code.MESSAGE; + + String response = + "HTTP/1.1 " + code.STATUS_CODE + " " + code.MESSAGE + CRLF + + "Content-Length: " + body.length() + CRLF + + CRLF + + body; + + op.write(response.getBytes()); + } + + @Override + public void run(){ + InputStream ipStream = null; + OutputStream opStream = null; + try { + ipStream = socket.getInputStream(); + opStream = socket.getOutputStream(); + HttpRequest req = null; + try { + req = HttpParser.parseHttpReq(ipStream); + }catch (HttpParsingException e){ + sendErrorResponse(e.getErrorCode(), opStream); + return; + } + + //Get the file that the user wants + //finish working on this later + String path = req.getRequestTarget(); + if ("/".equals(path)){ + path="/Index.html"; + }else{ + LOGGER.error("Invalid Request Target received : {}", path); + throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_404_NOT_FOUND); + } + + File file = new File(System.getProperty("user.dir") + "/httpserver/WebRoot" + path); + + //Response + byte[] fileBytes = Files.readAllBytes(file.toPath()); + String response = + "HTTP/1.1 200 OK" + CRLF + // Status Line : HTTP Version, Response_code, Response_msg + "Content-Length: " + fileBytes.length + CRLF + // Header + "Content-Type: text/html"+ CRLF + //Add MIME Files later + CRLF; + + opStream.write(response.getBytes()); + opStream.write(fileBytes); + + LOGGER.info("Connection Processing Finished"); + }catch(IOException e){ + LOGGER.error("Problem with communication", e); + }catch(HttpParsingException e){ + try { + sendErrorResponse(e.getErrorCode(), opStream); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + finally { + if(ipStream!=null){ + try { + ipStream.close(); + }catch (IOException ignored) {} + } + if(opStream!=null){ + try{ + opStream.close(); + }catch (IOException ignored){} + } + if(socket!=null){ + try{ + socket.close(); + }catch (IOException ignored){} + } + } + } +} diff --git a/httpserver/src/main/java/com/johan/httpserver/core/ServerListenerThread.java b/httpserver/src/main/java/com/johan/httpserver/core/ServerListenerThread.java new file mode 100644 index 0000000..be088fa --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/core/ServerListenerThread.java @@ -0,0 +1,47 @@ +package com.johan.httpserver.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class ServerListenerThread extends Thread{ + private final static Logger LOGGER = LoggerFactory.getLogger(ServerListenerThread.class); + + private int port; + private String webRoot; + private ServerSocket serverSocket; + + public ServerListenerThread(int port, String webRoot) throws IOException { + this.port=port; + this.webRoot=webRoot; + this.serverSocket = new ServerSocket(this.port); + } + + @Override + public void run(){ + + try { + while(serverSocket.isBound() && !serverSocket.isClosed()) { + Socket socket = serverSocket.accept(); + + LOGGER.info(" Connection accepted: " + socket.getInetAddress()); + + HttpConnectionWorkerThread workerThread = new HttpConnectionWorkerThread(socket); + workerThread.start(); + } + + } + catch (IOException e) { + LOGGER.error("Problem with setting socket", e); + } + finally { + if(serverSocket!=null){ + try{ + serverSocket.close(); + }catch (IOException ignore){} + } + } + } +} diff --git a/httpserver/src/main/java/com/johan/httpserver/core/io/WebRootHandler.java b/httpserver/src/main/java/com/johan/httpserver/core/io/WebRootHandler.java new file mode 100644 index 0000000..01fed10 --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/core/io/WebRootHandler.java @@ -0,0 +1,71 @@ +package com.johan.httpserver.core.io; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URLConnection; + +public class WebRootHandler { + private File webRoot; + + public WebRootHandler(String webRootPath) throws WebRootNotFoundException{ + webRoot = new File(webRootPath); + if(!webRoot.exists() || !webRoot.isDirectory()){ + throw new WebRootNotFoundException(); + } + } + + private boolean CheckIfEndsWithSlash(String relativePath){ + return relativePath.endsWith("/"); + } + + private boolean CheckIfRelativePathExists(String relativePath){ + File file = new File(webRoot, relativePath); + if(!file.exists()) { + return false; + } + try { + if (!file.getCanonicalPath().startsWith(webRoot.getCanonicalPath())) { + return true; + } + }catch (Exception e){ + return false; + } + return false; + } + + public String GetFileType(String relativePath) throws FileNotFoundException{ + if (CheckIfEndsWithSlash(relativePath)) { + relativePath += "Index.html"; + } + if(CheckIfRelativePathExists(relativePath)) { + throw new FileNotFoundException("File not found: "+relativePath); + } + File file = new File(webRoot, relativePath);; + String mimeType = URLConnection.getFileNameMap().getContentTypeFor(file.getName()); + if(mimeType == null){ + return "application/octet-stream"; + } + return mimeType; + } + + public byte[] GetFileByteArrayData(String relativePath) throws IOException { + if (CheckIfEndsWithSlash(relativePath)) { + relativePath += "Index.html"; + } + if(CheckIfRelativePathExists(relativePath)) { + throw new FileNotFoundException("File not found: "+relativePath); + } + File file = new File(webRoot, relativePath); + FileInputStream fis = new FileInputStream(file); + byte[] fileBytes = new byte[(int)file.length()]; + try{ + fis.read(fileBytes); + fis.close(); + }catch (IOException e){ + throw new IOException(); + } + return fileBytes; + } +} diff --git a/httpserver/src/main/java/com/johan/httpserver/core/io/WebRootNotFoundException.java b/httpserver/src/main/java/com/johan/httpserver/core/io/WebRootNotFoundException.java new file mode 100644 index 0000000..d1c726f --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/core/io/WebRootNotFoundException.java @@ -0,0 +1,5 @@ +package com.johan.httpserver.core.io; + +public class WebRootNotFoundException extends Exception{ + +} diff --git a/httpserver/src/main/java/com/johan/httpserver/httpserver.java b/httpserver/src/main/java/com/johan/httpserver/httpserver.java new file mode 100644 index 0000000..f009280 --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/httpserver.java @@ -0,0 +1,32 @@ +package com.johan.httpserver; + +import com.johan.httpserver.config.configuration; +import com.johan.httpserver.config.configmanager; +import com.johan.httpserver.core.ServerListenerThread; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; + +public class httpserver { + + private final static Logger LOGGER = LoggerFactory.getLogger(httpserver.class); + public static void main(String[] args) + { + LOGGER.info("Server Starting"); + + System.out.println("Server starting..."); + + configmanager.getInstance().loadConfigFile(("httpserver/src/main/resources/http.json")); + configuration conf = configmanager.getInstance().getCurrentConfig(); + + LOGGER.info("Using Port: "+conf.getPort()); + LOGGER.info("Using WebRoot: "+ conf.getWebroot()); + + try { + ServerListenerThread serverListenerThread = new ServerListenerThread(conf.getPort(), conf.getWebroot()); + serverListenerThread.start(); + }catch (IOException e){ + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/httpserver/src/main/java/com/johan/httpserver/util/json.java b/httpserver/src/main/java/com/johan/httpserver/util/json.java new file mode 100644 index 0000000..ea2e8ee --- /dev/null +++ b/httpserver/src/main/java/com/johan/httpserver/util/json.java @@ -0,0 +1,45 @@ +package com.johan.httpserver.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.*; + +import java.io.IOException; + +public class json { + + private static ObjectMapper myobjectmapper = new ObjectMapper(); + + private static ObjectMapper defaultObjectMapper(){ + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return om; + } + + public static JsonNode parse(String jsonSrc) throws IOException { + return myobjectmapper.readTree(jsonSrc); + } + + public static A fromJson(JsonNode node, Class c) throws IOException{ + return myobjectmapper.treeToValue(node, c); + } + + public static JsonNode toJson(Object obj){ + return myobjectmapper.valueToTree(obj); + } + + public static String stringify (JsonNode node) throws JsonProcessingException{ + return generateJson(node, false); + } + + public static String stringifyPretty (JsonNode node) throws JsonProcessingException{ + return generateJson(node, true); + } + + private static String generateJson (Object o, boolean pretty) throws JsonProcessingException { + ObjectWriter objectwriter = myobjectmapper.writer(); + if(pretty){ + objectwriter = objectwriter.with(SerializationFeature.INDENT_OUTPUT); + } + return objectwriter.writeValueAsString(o); + } +} diff --git a/httpserver/src/main/resources/http.json b/httpserver/src/main/resources/http.json new file mode 100644 index 0000000..e530b6f --- /dev/null +++ b/httpserver/src/main/resources/http.json @@ -0,0 +1,4 @@ +{ + "port": 8080, + "webroot":"httpserver/WebRoot/Index.html" +} \ No newline at end of file diff --git a/httpserver/src/test/java/com/johan/http/HttpParserTest.java b/httpserver/src/test/java/com/johan/http/HttpParserTest.java new file mode 100644 index 0000000..fbe5185 --- /dev/null +++ b/httpserver/src/test/java/com/johan/http/HttpParserTest.java @@ -0,0 +1,131 @@ +package com.johan.http; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class HttpParserTest { + + private HttpParser httpParser; + + @BeforeAll + public void beforeClass(){ + httpParser = new HttpParser(); + } + + @Test + void parseHttpReq() { + HttpRequest request = null; + try { + request = HttpParser.parseHttpReq(generateValidTestCase()); + } catch (HttpParsingException e){ + fail(e); + } + assertNotNull(request); + } + + @Test + void parseHttpReqBadMethod() { + try { + HttpRequest request = HttpParser.parseHttpReq(generateValidTestCaseBadMethod()); + fail(); + } catch (HttpParsingException e) { + assertEquals(e.getErrorCode(), HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); + } + } + + @Test + void parseHttpReqBadMethod2() { + try { + HttpRequest request = HttpParser.parseHttpReq(generateValidTestCaseBadMethod2()); + fail(); + } catch (HttpParsingException e) { + assertEquals(e.getErrorCode(), HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); + } + } + + @Test + void parseHttpReqReqLineInvalidItems() { + try { + HttpRequest request = HttpParser.parseHttpReq(generateValidTestCaseReqLineInvalidItems()); + fail(); + } catch (HttpParsingException e) { + assertEquals(e.getErrorCode(), HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + } + + @Test + void parseHttpReqEmptyReq() { + try { + HttpRequest request = HttpParser.parseHttpReq(generateValidTestCaseEmptyReqLine()); + fail(); + } catch (HttpParsingException e) { + assertEquals(e.getErrorCode(), HttpStatusCode.CLIENT_ERROR_400_BAD_REQ); + } + } + + + private InputStream generateValidTestCase(){ + String rawData="GET / HTTP/1.1\r\n" + + "Host: localhost:8080\r\n" + + "Connection: keep-alive\r\n" + + "sec-ch-ua: \"Not:A-Brand\";v=\"99\", \"Google Chrome\";v=\"145\", \"Chromium\";v=\"145\"\r\n" + + "sec-ch-ua-mobile: ?0\r\n" + + "sec-ch-ua-platform: \"Windows\"\r\n" + + "Upgrade-Insecure-Requests: 1\r\n" + + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36\r\n" + + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n" + + "Sec-Fetch-Site: none\r\n" + + "Sec-Fetch-Mode: navigate\r\n" + + "Sec-Fetch-User: ?1\r\n" + + "Sec-Fetch-Dest: document\r\n" + + "Accept-Encoding: gzip, deflate, br, zstd\r\n" + + "Accept-Language: en-US,en;q=0.9,fr;q=0.8\r\n"+ + "\r\n"; + InputStream ipStream = new ByteArrayInputStream(rawData.getBytes(StandardCharsets.US_ASCII)); + return ipStream; + } + + private InputStream generateValidTestCaseBadMethod(){ + String rawData="TF / HTTP/1.1\r\n" + + "Host: localhost:8080\r\n" + + "Accept-Language: en-US,en;q=0.9,fr;q=0.8\r\n"+ + "\r\n"; + InputStream ipStream = new ByteArrayInputStream(rawData.getBytes(StandardCharsets.US_ASCII)); + return ipStream; + } + + private InputStream generateValidTestCaseBadMethod2(){ + String rawData="GETTTT / HTTP/1.1\r\n" + + "Host: localhost:8080\r\n" + + "Accept-Language: en-US,en;q=0.9,fr;q=0.8\r\n"+ + "\r\n"; + InputStream ipStream = new ByteArrayInputStream(rawData.getBytes(StandardCharsets.US_ASCII)); + return ipStream; + } + + private InputStream generateValidTestCaseReqLineInvalidItems(){ + String rawData="GET / AAAAA HTTP/1.1\r\n" + + "Host: localhost:8080\r\n" + + "Accept-Language: en-US,en;q=0.9,fr;q=0.8\r\n"+ + "\r\n"; + InputStream ipStream = new ByteArrayInputStream(rawData.getBytes(StandardCharsets.US_ASCII)); + return ipStream; + } + + private InputStream generateValidTestCaseEmptyReqLine(){ + String rawData="\r\n" + + "Host: localhost:8080\r\n" + + "Accept-Language: en-US,en;q=0.9,fr;q=0.8\r\n"+ + "\r\n"; + InputStream ipStream = new ByteArrayInputStream(rawData.getBytes(StandardCharsets.US_ASCII)); + return ipStream; + } +} \ No newline at end of file diff --git a/src/test/java/com/coderfromscratch/http/HttpVersionTest.java b/httpserver/src/test/java/com/johan/http/HttpVersionTest.java similarity index 76% rename from src/test/java/com/coderfromscratch/http/HttpVersionTest.java rename to httpserver/src/test/java/com/johan/http/HttpVersionTest.java index eec0460..38ec7ea 100644 --- a/src/test/java/com/coderfromscratch/http/HttpVersionTest.java +++ b/httpserver/src/test/java/com/johan/http/HttpVersionTest.java @@ -1,4 +1,4 @@ -package com.coderfromscratch.http; +package com.johan.http; import org.junit.jupiter.api.Test; @@ -7,11 +7,11 @@ public class HttpVersionTest { @Test - void getBestCompatibleVersionExactMatch() { - HttpVersion version = null; + void getBestCompatibleVersionExactMatch(){ + HttpVersion version=null; try { version = HttpVersion.getBestCompatibleVersion("HTTP/1.1"); - } catch (BadHttpVersionException e) { + }catch(BadHttpVersionException e){ fail(); } assertNotNull(version); @@ -19,7 +19,7 @@ void getBestCompatibleVersionExactMatch() { } @Test - void getBestCompatibleVersionBadFormat() { + void getBestCompatibleVersionBadMatch(){ HttpVersion version = null; try { version = HttpVersion.getBestCompatibleVersion("http/1.1"); @@ -30,7 +30,7 @@ void getBestCompatibleVersionBadFormat() { } @Test - void getBestCompatibleVersionHigherVersion() { + void getBestCompatibleVersionHigherVersion(){ HttpVersion version = null; try { version = HttpVersion.getBestCompatibleVersion("HTTP/1.2"); diff --git a/pom.xml b/pom.xml deleted file mode 100644 index b589e83..0000000 --- a/pom.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - 4.0.0 - - com.coderfromscratch - simplehttpserver - 1.0-SNAPSHOT - - - 1.8 - 1.8 - - - - - - com.fasterxml.jackson.core - jackson-core - 2.9.9 - - - com.fasterxml.jackson.core - jackson-databind - 2.9.10.3 - - - - org.slf4j - slf4j-api - 1.7.29 - - - ch.qos.logback - logback-classic - 1.2.3 - - - - - org.junit.jupiter - junit-jupiter - RELEASE - test - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - com.coderfromscratch.httpserver.HttpServer - - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.4 - - - package - - shade - - - - - com.coderfromscratch.httpserver.HttpServer - - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/coderfromscratch/http/BadHttpVersionException.java b/src/main/java/com/coderfromscratch/http/BadHttpVersionException.java deleted file mode 100644 index 6037c4c..0000000 --- a/src/main/java/com/coderfromscratch/http/BadHttpVersionException.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.coderfromscratch.http; - -public class BadHttpVersionException extends Exception{ - -} diff --git a/src/main/java/com/coderfromscratch/http/HttpHeaderName.java b/src/main/java/com/coderfromscratch/http/HttpHeaderName.java deleted file mode 100644 index 1ada9e1..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpHeaderName.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.coderfromscratch.http; - -public enum HttpHeaderName { - CONTENT_TYPE("Content-Type"), - CONTENT_LENGTH("Content-Length"); - - public final String headerName; - - HttpHeaderName(String headerName) { - this.headerName = headerName; - } -} diff --git a/src/main/java/com/coderfromscratch/http/HttpMessage.java b/src/main/java/com/coderfromscratch/http/HttpMessage.java deleted file mode 100644 index 1c2e665..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpMessage.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.coderfromscratch.http; - -import java.util.HashMap; -import java.util.Set; - -public abstract class HttpMessage { - - private HashMap headers = new HashMap<>(); - - private byte[] messageBody = new byte[0]; - - public Set getHeaderNames() { - return headers.keySet(); - } - - public String getHeader(String headerName) { - return headers.get(headerName.toLowerCase()); - } - - void addHeader(String headerName, String headerField) { - headers.put(headerName.toLowerCase(), headerField); - } - - public byte[] getMessageBody() { - return messageBody; - } - - public void setMessageBody(byte[] messageBody) { - this.messageBody = messageBody; - } -} diff --git a/src/main/java/com/coderfromscratch/http/HttpMethod.java b/src/main/java/com/coderfromscratch/http/HttpMethod.java deleted file mode 100644 index 0f91338..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpMethod.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.coderfromscratch.http; - -public enum HttpMethod { - GET, HEAD; - - public static final int MAX_LENGTH; - - static { - int tempMaxLength = -1; - for (HttpMethod method : values()) { - if (method.name().length() > tempMaxLength) { - tempMaxLength = method.name().length(); - } - } - MAX_LENGTH = tempMaxLength; - } -} diff --git a/src/main/java/com/coderfromscratch/http/HttpParser.java b/src/main/java/com/coderfromscratch/http/HttpParser.java deleted file mode 100644 index d7e0341..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpParser.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.coderfromscratch.http; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class HttpParser { - - private final static Logger LOGGER = LoggerFactory.getLogger(HttpParser.class); - - private static final int SP = 0x20; // 32 - private static final int CR = 0x0D; // 13 - private static final int LF = 0x0A; // 10 - - public HttpRequest parseHttpRequest(InputStream inputStream) throws HttpParsingException { - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII); - - HttpRequest request = new HttpRequest(); - - try { - parseRequestLine(reader, request); - } catch (IOException e) { - e.printStackTrace(); - } - try { - parseHeaders(reader, request); - } catch (IOException e) { - e.printStackTrace(); - } - parseBody(reader, request); - - return request; - } - - private void parseRequestLine(InputStreamReader reader, HttpRequest request) throws IOException, HttpParsingException { - StringBuilder processingDataBuffer = new StringBuilder(); - - boolean methodParsed = false; - boolean requestTargetParsed = false; - - // TODO validate URI size! - - int _byte; - while ((_byte = reader.read()) >=0) { - if (_byte == CR) { - _byte = reader.read(); - if (_byte == LF) { - LOGGER.debug("Request Line VERSION to Process : {}" , processingDataBuffer.toString()); - if (!methodParsed || !requestTargetParsed) { - throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - - try { - request.setHttpVersion(processingDataBuffer.toString()); - } catch (BadHttpVersionException e) { - throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - - return; - } else { - throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } - - if (_byte == SP) { - if (!methodParsed) { - LOGGER.debug("Request Line METHOD to Process : {}" , processingDataBuffer.toString()); - request.setMethod(processingDataBuffer.toString()); - methodParsed = true; - } else if (!requestTargetParsed) { - LOGGER.debug("Request Line REQ TARGET to Process : {}" , processingDataBuffer.toString()); - request.setRequestTarget(processingDataBuffer.toString()); - requestTargetParsed = true; - } else { - throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - processingDataBuffer.delete(0, processingDataBuffer.length()); - } else { - processingDataBuffer.append((char)_byte); - if (!methodParsed) { - if (processingDataBuffer.length() > HttpMethod.MAX_LENGTH) { - throw new HttpParsingException(HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); - } - } - } - } - - } - - private void parseHeaders(InputStreamReader reader, HttpRequest request) throws IOException, HttpParsingException { - StringBuilder processingDataBuffer = new StringBuilder(); - boolean crlfFound = false; - - int _byte; - while ((_byte = reader.read()) >=0) { - if (_byte == CR) { - _byte = reader.read(); - if (_byte == LF) { - if (!crlfFound) { - crlfFound = true; - - // Do Things like processing - processSingleHeaderField(processingDataBuffer, request); - // Clear the buffer - processingDataBuffer.delete(0, processingDataBuffer.length()); - } else { - // Two CRLF received, end of Headers section - return; - } - } else { - throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } else { - crlfFound = false; - // Append to Buffer - processingDataBuffer.append((char)_byte); - } - } - } - - private void processSingleHeaderField(StringBuilder processingDataBuffer, HttpRequest request) throws HttpParsingException { - String rawHeaderField = processingDataBuffer.toString(); - Pattern pattern = Pattern.compile("^(?[!#$%&’*+\\-./^_‘|˜\\dA-Za-z]+):\\s?(?[!#$%&’*+\\-./^_‘|˜(),:;<=>?@[\\\\]{}\" \\dA-Za-z]+)\\s?$"); - - Matcher matcher = pattern.matcher(rawHeaderField); - if (matcher.matches()) { - // We found a proper header - String fieldName = matcher.group("fieldName"); - String fieldValue = matcher.group("fieldValue"); - request.addHeader(fieldName, fieldValue); - } else{ - throw new HttpParsingException(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } - - private void parseBody(InputStreamReader reader, HttpRequest request) { - - } - -} diff --git a/src/main/java/com/coderfromscratch/http/HttpRequest.java b/src/main/java/com/coderfromscratch/http/HttpRequest.java deleted file mode 100644 index 3c99bb0..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.coderfromscratch.http; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Set; - -public class HttpRequest extends HttpMessage{ - - private HttpMethod method; - private String requestTarget; - private String originalHttpVersion; // literal from the request - private HttpVersion bestCompatibleHttpVersion; - - HttpRequest() { - } - - public HttpMethod getMethod() { - return method; - } - - public String getRequestTarget() { - return requestTarget; - } - - public HttpVersion getBestCompatibleHttpVersion() { - return bestCompatibleHttpVersion; - } - - public String getOriginalHttpVersion() { - return originalHttpVersion; - } - - void setMethod(String methodName) throws HttpParsingException { - for (HttpMethod method : HttpMethod.values()) { - if (methodName.equals(method.name())) { - this.method = method; - return; - } - } - throw new HttpParsingException( - HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED - ); - } - - void setRequestTarget(String requestTarget) throws HttpParsingException { - if (requestTarget == null || requestTarget.length() == 0) { - throw new HttpParsingException(HttpStatusCode.SERVER_ERROR_500_INTERNAL_SERVER_ERROR); - } - this.requestTarget = requestTarget; - } - - void setHttpVersion(String originalHttpVersion) throws BadHttpVersionException, HttpParsingException { - this.originalHttpVersion = originalHttpVersion; - this.bestCompatibleHttpVersion = HttpVersion.getBestCompatibleVersion(originalHttpVersion); - if (this.bestCompatibleHttpVersion == null) { - throw new HttpParsingException( - HttpStatusCode.SERVER_ERROR_505_HTTP_VERSION_NOT_SUPPORTED - ); - } - } - -} diff --git a/src/main/java/com/coderfromscratch/http/HttpResponse.java b/src/main/java/com/coderfromscratch/http/HttpResponse.java deleted file mode 100644 index 6336da0..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpResponse.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.coderfromscratch.http; - -public class HttpResponse extends HttpMessage { - - private final String CRLF = "\r\n"; - - // status-line = HTTP-version SP status-code SP reason-phrase CRLF - private String httpVersion; - - private HttpStatusCode statusCode; - - private String reasonPhrase = null; - - private HttpResponse() { - } - - public String getHttpVersion() { - return httpVersion; - } - - public void setHttpVersion(String httpVersion) { - this.httpVersion = httpVersion; - } - - public HttpStatusCode getStatusCode() { - return statusCode; - } - - public void setStatusCode(HttpStatusCode statusCode) { - this.statusCode = statusCode; - } - - public String getReasonPhrase() { - if (reasonPhrase == null && statusCode!=null) { - return statusCode.MESSAGE; - } - return reasonPhrase; - } - - public void setReasonPhrase(String reasonPhrase) { - this.reasonPhrase = reasonPhrase; - } - - public byte[] getResponseBytes() { - StringBuilder responseBuilder = new StringBuilder(); - responseBuilder.append(httpVersion) - .append(" ") - .append(statusCode.STATUS_CODE) - .append(" ") - .append(getReasonPhrase()) - .append(CRLF); - - for (String headerName: getHeaderNames()) { - responseBuilder.append(headerName) - .append(": ") - .append(getHeader(headerName)) - .append(CRLF); - } - - responseBuilder.append(CRLF); - - byte[] responseBytes = responseBuilder.toString().getBytes(); - - if (getMessageBody().length == 0) - return responseBytes; - - byte[] responseWithBody = new byte[responseBytes.length + getMessageBody().length]; - System.arraycopy(responseBytes, 0, responseWithBody, 0, responseBytes.length); - System.arraycopy(getMessageBody(), 0, responseWithBody, responseBytes.length, getMessageBody().length); - - return responseWithBody; - } - - public static class Builder { - - private HttpResponse response = new HttpResponse(); - - public Builder httpVersion ( String httpVersion) { - response.setHttpVersion(httpVersion); - return this; - } - - public Builder statusCode(HttpStatusCode statusCode) { - response.setStatusCode(statusCode); - return this; - } - - public Builder reasonPhrase(String reasonPhrase) { - response.setReasonPhrase(reasonPhrase); - return this; - } - - public Builder addHeader(String headerName, String headerField) { - response.addHeader(headerName, headerField); - return this; - } - - public Builder messageBody(byte[] messageBody) { - response.setMessageBody(messageBody); - return this; - } - - public HttpResponse build() { - return response; - } - - } -} diff --git a/src/main/java/com/coderfromscratch/http/HttpStatusCode.java b/src/main/java/com/coderfromscratch/http/HttpStatusCode.java deleted file mode 100644 index 4eaf380..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpStatusCode.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.coderfromscratch.http; - -public enum HttpStatusCode { - - /* --- CLIENT ERRORS --- */ - CLIENT_ERROR_400_BAD_REQUEST(400, "Bad Request"), - CLIENT_ERROR_401_METHOD_NOT_ALLOWED(401, "Method Not Allowed"), - CLIENT_ERROR_414_BAD_REQUEST(414, "URI Too Long"), - CLIENT_ERROR_404_NOT_FOUND(404, "Not Found" ), - - /* --- SERVER ERRORS --- */ - SERVER_ERROR_500_INTERNAL_SERVER_ERROR(500, "Internal Server Error"), - SERVER_ERROR_501_NOT_IMPLEMENTED(501, "Not Implemented"), - SERVER_ERROR_505_HTTP_VERSION_NOT_SUPPORTED(505, "Http Version Not Supported"), - OK(200,"OK" ); - - - public final int STATUS_CODE; - public final String MESSAGE; - - HttpStatusCode(int STATUS_CODE, String MESSAGE) { - this.STATUS_CODE = STATUS_CODE; - this.MESSAGE = MESSAGE; - } - -} diff --git a/src/main/java/com/coderfromscratch/http/HttpVersion.java b/src/main/java/com/coderfromscratch/http/HttpVersion.java deleted file mode 100644 index 74f6070..0000000 --- a/src/main/java/com/coderfromscratch/http/HttpVersion.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.coderfromscratch.http; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public enum HttpVersion { - HTTP_1_1("HTTP/1.1", 1 , 1); - - public final String LITERAL; - public final int MAJOR; - public final int MINOR; - - HttpVersion(String LITERAL, int MAJOR, int MINOR) { - this.LITERAL = LITERAL; - this.MAJOR = MAJOR; - this.MINOR = MINOR; - } - - private static final Pattern httpVersionRegexPattern = Pattern.compile("^HTTP/(?\\d+).(?\\d+)"); - - public static HttpVersion getBestCompatibleVersion(String literalVersion) throws BadHttpVersionException { - Matcher matcher = httpVersionRegexPattern.matcher(literalVersion); - if (!matcher.find() || matcher.groupCount() != 2) { - throw new BadHttpVersionException(); - } - int major = Integer.parseInt(matcher.group("major")); - int minor = Integer.parseInt(matcher.group("minor")); - - HttpVersion tempBestCompatible = null; - for (HttpVersion version : HttpVersion.values()) { - if (version.LITERAL.equals(literalVersion)) { - return version; - } else { - if (version.MAJOR == major) { - if (version.MINOR < minor) { - tempBestCompatible = version; - } - } - } - } - return tempBestCompatible; - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/HttpServer.java b/src/main/java/com/coderfromscratch/httpserver/HttpServer.java deleted file mode 100644 index 894c650..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/HttpServer.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.coderfromscratch.httpserver; - -import com.coderfromscratch.httpserver.config.Configuration; -import com.coderfromscratch.httpserver.config.ConfigurationManager; -import com.coderfromscratch.httpserver.core.ServerListenerThread; -import com.coderfromscratch.httpserver.core.io.WebRootNotFoundException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -/** - * - * Driver Class for the Http Server - * - */ -public class HttpServer { - - private final static Logger LOGGER = LoggerFactory.getLogger(HttpServer.class); - - public static void main(String[] args) { - - if (args.length != 1) { - LOGGER.error("No configuration file provided."); - LOGGER.error("Syntax: java -jar simplehttpserver-1.0-SNAPSHOT.jar "); - return; - } - - LOGGER.info("Server starting..."); - - ConfigurationManager.getInstance().loadConfigurationFile(args[0]); - Configuration conf = ConfigurationManager.getInstance().getCurrentConfiguration(); - - LOGGER.info("Using Port: " + conf.getPort()); - LOGGER.info("Using WebRoot: " + conf.getWebroot()); - - try { - ServerListenerThread serverListenerThread = new ServerListenerThread(conf.getPort(), conf.getWebroot()); - serverListenerThread.start(); - } catch (IOException e) { - e.printStackTrace(); - // TODO handle later. - } catch (WebRootNotFoundException e) { - LOGGER.error("Webroot folder not found",e); - } - - - } - -} diff --git a/src/main/java/com/coderfromscratch/httpserver/config/ConfigurationManager.java b/src/main/java/com/coderfromscratch/httpserver/config/ConfigurationManager.java deleted file mode 100644 index 8857d58..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/config/ConfigurationManager.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.coderfromscratch.httpserver.config; - -import com.coderfromscratch.httpserver.util.Json; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; - -public class ConfigurationManager { - - private static ConfigurationManager myConfigurationManager; - private static Configuration myCurrentConfiguration; - - private ConfigurationManager() { - } - - public static ConfigurationManager getInstance() { - if (myConfigurationManager==null) - myConfigurationManager = new ConfigurationManager(); - return myConfigurationManager; - } - - /** - * Used to load a configuration file by the path provided - */ - public void loadConfigurationFile(String filePath) { - FileReader fileReader = null; - try { - fileReader = new FileReader(filePath); - } catch (FileNotFoundException e) { - throw new HttpConfigurationException(e); - } - StringBuffer sb = new StringBuffer(); - int i ; - try { - while ( ( i = fileReader.read()) != -1) { - sb.append((char)i); - } - } catch (IOException e) { - throw new HttpConfigurationException(e); - } - JsonNode conf = null; - try { - conf = Json.parse(sb.toString()); - } catch (IOException e) { - throw new HttpConfigurationException("Error parsing the Configuration File", e); - } - try { - myCurrentConfiguration = Json.fromJson(conf, Configuration.class); - } catch (JsonProcessingException e) { - throw new HttpConfigurationException("Error parsing the Configuration file, internal",e); - } - } - - /** - * Returns the Current loaded Configuration - */ - public Configuration getCurrentConfiguration() { - if ( myCurrentConfiguration == null) { - throw new HttpConfigurationException("No Current Configuration Set."); - } - return myCurrentConfiguration; - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/config/HttpConfigurationException.java b/src/main/java/com/coderfromscratch/httpserver/config/HttpConfigurationException.java deleted file mode 100644 index 516ddef..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/config/HttpConfigurationException.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.coderfromscratch.httpserver.config; - -public class HttpConfigurationException extends RuntimeException { - - public HttpConfigurationException() { - } - - public HttpConfigurationException(String message) { - super(message); - } - - public HttpConfigurationException(String message, Throwable cause) { - super(message, cause); - } - - public HttpConfigurationException(Throwable cause) { - super(cause); - } - -} diff --git a/src/main/java/com/coderfromscratch/httpserver/core/HttpConnectionWorkerThread.java b/src/main/java/com/coderfromscratch/httpserver/core/HttpConnectionWorkerThread.java deleted file mode 100644 index d55311d..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/core/HttpConnectionWorkerThread.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.coderfromscratch.httpserver.core; - -import com.coderfromscratch.http.*; -import com.coderfromscratch.httpserver.core.io.ReadFileException; -import com.coderfromscratch.httpserver.core.io.WebRootHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -public class HttpConnectionWorkerThread extends Thread { - private final static Logger LOGGER = LoggerFactory.getLogger(HttpConnectionWorkerThread.class); - private Socket socket; - private WebRootHandler webRootHandler; - private HttpParser httpParser = new HttpParser(); - - public HttpConnectionWorkerThread(Socket socket, WebRootHandler webRootHandler) { - this.socket = socket; - this.webRootHandler = webRootHandler; - } - - @Override - public void run() { - InputStream inputStream = null; - OutputStream outputStream = null; - - try { - inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); - - HttpRequest request = httpParser.parseHttpRequest(inputStream); - HttpResponse response = handleRequest(request); - - outputStream.write(response.getResponseBytes()); - - LOGGER.info(" * Connection Processing Finished."); - } catch (IOException e) { - LOGGER.error("Problem with communication", e); - } catch (HttpParsingException e) { - LOGGER.info("Bag Request", e); - - HttpResponse response = new HttpResponse.Builder() - .httpVersion(HttpVersion.HTTP_1_1.LITERAL) - .statusCode(e.getErrorCode()) - .build(); - try { - outputStream.write(response.getResponseBytes()); - } catch (IOException ex) { - LOGGER.error("Problem with communication", e); - } - - } finally { - if (inputStream!= null) { - try { - inputStream.close(); - } catch (IOException e) {} - } - if (outputStream!=null) { - try { - outputStream.close(); - } catch (IOException e) {} - } - if (socket!= null) { - try { - socket.close(); - } catch (IOException e) {} - } - } - } - - private HttpResponse handleRequest(HttpRequest request) { - - switch (request.getMethod()) { - case GET: - LOGGER.info(" * GET Request"); - return handleGetRequest(request, true); - case HEAD: - LOGGER.info(" * HEAD Request"); - return handleGetRequest(request, false); - default: - return new HttpResponse.Builder() - .httpVersion(request.getBestCompatibleHttpVersion().LITERAL) - .statusCode(HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED) - .build(); - } - - } - - private HttpResponse handleGetRequest(HttpRequest request, boolean setMessageBody) { - try { - - HttpResponse.Builder builder = new HttpResponse.Builder() - .httpVersion(request.getBestCompatibleHttpVersion().LITERAL) - .statusCode(HttpStatusCode.OK) - .addHeader(HttpHeaderName.CONTENT_TYPE.headerName, webRootHandler.getFileMimeType(request.getRequestTarget())); - - if (setMessageBody) { - byte[] messageBody = webRootHandler.getFileByteArrayData(request.getRequestTarget()); - builder.addHeader(HttpHeaderName.CONTENT_LENGTH.headerName, String.valueOf(messageBody.length)) - .messageBody(messageBody); - } - - return builder.build(); - - } catch (FileNotFoundException e) { - - return new HttpResponse.Builder() - .httpVersion(request.getBestCompatibleHttpVersion().LITERAL) - .statusCode(HttpStatusCode.CLIENT_ERROR_404_NOT_FOUND) - .build(); - - } catch (ReadFileException e) { - - return new HttpResponse.Builder() - .httpVersion(request.getBestCompatibleHttpVersion().LITERAL) - .statusCode(HttpStatusCode.SERVER_ERROR_500_INTERNAL_SERVER_ERROR) - .build(); - } - - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/core/ServerListenerThread.java b/src/main/java/com/coderfromscratch/httpserver/core/ServerListenerThread.java deleted file mode 100644 index c00766d..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/core/ServerListenerThread.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.coderfromscratch.httpserver.core; - -import com.coderfromscratch.httpserver.core.io.WebRootHandler; -import com.coderfromscratch.httpserver.core.io.WebRootNotFoundException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; - -public class ServerListenerThread extends Thread { - - private final static Logger LOGGER = LoggerFactory.getLogger(ServerListenerThread.class); - - private int port; - private String webroot; - private ServerSocket serverSocket; - - private WebRootHandler webRootHandler; - - public ServerListenerThread(int port, String webroot) throws IOException, WebRootNotFoundException { - this.port = port; - this.webroot = webroot; - this.webRootHandler = new WebRootHandler(webroot); - this.serverSocket = new ServerSocket(this.port); - } - - @Override - public void run() { - - try { - - while ( serverSocket.isBound() && !serverSocket.isClosed()) { - Socket socket = serverSocket.accept(); - - LOGGER.info(" * Connection accepted: " + socket.getInetAddress()); - - HttpConnectionWorkerThread workerThread = new HttpConnectionWorkerThread(socket, webRootHandler); - workerThread.start(); - - } - - } catch (IOException e) { - LOGGER.error("Problem with setting socket", e); - } finally { - if (serverSocket!=null) { - try { - serverSocket.close(); - } catch (IOException e) {} - } - } - - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/core/io/ReadFileException.java b/src/main/java/com/coderfromscratch/httpserver/core/io/ReadFileException.java deleted file mode 100644 index b27608f..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/core/io/ReadFileException.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.coderfromscratch.httpserver.core.io; - -public class ReadFileException extends Throwable { - public ReadFileException() { - } - - public ReadFileException(String message) { - super(message); - } - - public ReadFileException(String message, Throwable cause) { - super(message, cause); - } - - public ReadFileException(Throwable cause) { - super(cause); - } - - public ReadFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/core/io/WebRootHandler.java b/src/main/java/com/coderfromscratch/httpserver/core/io/WebRootHandler.java deleted file mode 100644 index efcfc88..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/core/io/WebRootHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.coderfromscratch.httpserver.core.io; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.URLConnection; - -public class WebRootHandler { - - private File webRoot; - - public WebRootHandler(String webRootPath) throws WebRootNotFoundException { - webRoot = new File(webRootPath); - if (!webRoot.exists() || !webRoot.isDirectory()) { - throw new WebRootNotFoundException("Webroot provided does not exist or is not a folder"); - } - } - - private boolean checkIfEndsWithSlash(String relativePath) { - return relativePath.endsWith("/"); - } - - /** - * This method checks to see if the relative path provided exists inside WebRoot - * - * @param relativePath - * @return true if the path exists inside WebRoot, false if not. - */ - private boolean checkIfProvidedRelativePathExists(String relativePath) { - File file = new File(webRoot, relativePath); - - if (!file.exists()) - return false; - - try { - if (file.getCanonicalPath().startsWith(webRoot.getCanonicalPath())) { - return true; - } - } catch (IOException e) { - return false; - } - return false; - } - - public String getFileMimeType(String relativePath) throws FileNotFoundException { - if (checkIfEndsWithSlash(relativePath)) { - relativePath += "index.html"; // By default serve the index.html, if it exists. - } - - if (!checkIfProvidedRelativePathExists(relativePath)) { - throw new FileNotFoundException("File not found: " + relativePath); - } - - File file = new File(webRoot, relativePath); - - String mimeType = URLConnection.getFileNameMap().getContentTypeFor(file.getName()); - - if (mimeType == null) { - return "application/octet-stream"; - } - - return mimeType; - } - - /** - * Returns a byte array of the content of a file. - * - * Todo - For large files a new strategy might be necessary. - * - * @param relativePath the path to the file inside the webroot folder. - * @return a byte array of the data. - * @throws FileNotFoundException if the file can not be found - * @throws ReadFileException if there was a problem reading the file. - */ - public byte[] getFileByteArrayData(String relativePath) throws FileNotFoundException, ReadFileException { - if (checkIfEndsWithSlash(relativePath)) { - relativePath += "index.html"; // By default serve the index.html, if it exists. - } - - if (!checkIfProvidedRelativePathExists(relativePath)) { - throw new FileNotFoundException("File not found: " + relativePath); - } - - File file = new File(webRoot, relativePath); - FileInputStream fileInputStream = new FileInputStream(file); - byte[] fileBytes = new byte[(int)file.length()]; - try { - fileInputStream.read(fileBytes); - fileInputStream.close(); - } catch (IOException e) { - throw new ReadFileException(e); - } - return fileBytes; - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/core/io/WebRootNotFoundException.java b/src/main/java/com/coderfromscratch/httpserver/core/io/WebRootNotFoundException.java deleted file mode 100644 index d597787..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/core/io/WebRootNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.coderfromscratch.httpserver.core.io; - -public class WebRootNotFoundException extends Throwable { - public WebRootNotFoundException(String message) { - super(message); - } -} diff --git a/src/main/java/com/coderfromscratch/httpserver/util/Json.java b/src/main/java/com/coderfromscratch/httpserver/util/Json.java deleted file mode 100644 index 04d3656..0000000 --- a/src/main/java/com/coderfromscratch/httpserver/util/Json.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.coderfromscratch.httpserver.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.*; - -import java.io.IOException; - -public class Json { - - private static ObjectMapper myObjectMapper = defaultObjectMapper(); - - private static ObjectMapper defaultObjectMapper() { - ObjectMapper om = new ObjectMapper(); - om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return om; - } - - public static JsonNode parse(String jsonSrc) throws IOException { - return myObjectMapper.readTree(jsonSrc); - } - - public static A fromJson(JsonNode node , Class clazz) throws JsonProcessingException { - return myObjectMapper.treeToValue(node, clazz); - } - - public static JsonNode toJson(Object obj) { - return myObjectMapper.valueToTree(obj); - } - - public static String stringify(JsonNode node) throws JsonProcessingException { - return generateJson(node, false); - } - - public static String stringifyPretty(JsonNode node) throws JsonProcessingException { - return generateJson(node, true); - } - - private static String generateJson(Object o, boolean pretty) throws JsonProcessingException { - ObjectWriter objectWriter = myObjectMapper.writer(); - if (pretty) { - objectWriter = objectWriter.with(SerializationFeature.INDENT_OUTPUT); - } - return objectWriter.writeValueAsString(o); - } -} diff --git a/src/main/resources/http.json b/src/main/resources/http.json deleted file mode 100644 index e1e8221..0000000 --- a/src/main/resources/http.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "port": 8080, - "webroot": "WebRoot" -} \ No newline at end of file diff --git a/src/test/java/com/coderfromscratch/http/HttpHeadersParserTest.java b/src/test/java/com/coderfromscratch/http/HttpHeadersParserTest.java deleted file mode 100644 index 21a960d..0000000 --- a/src/test/java/com/coderfromscratch/http/HttpHeadersParserTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.coderfromscratch.http; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class HttpHeadersParserTest { - - private HttpParser httpParser; - private Method parseHeadersMethod; - - @BeforeAll - public void beforeClass() throws NoSuchMethodException { - httpParser = new HttpParser(); - Class cls = HttpParser.class; - parseHeadersMethod = cls.getDeclaredMethod("parseHeaders", InputStreamReader.class, HttpRequest.class); - parseHeadersMethod.setAccessible(true); - } - - @Test - public void testSimpleSingleHeader() throws InvocationTargetException, IllegalAccessException { - HttpRequest request = new HttpRequest(); - parseHeadersMethod.invoke( - httpParser, - generateSimpleSingleHeaderMessage(), - request); - assertEquals(1, request.getHeaderNames().size()); - assertEquals("localhost:8080", request.getHeader("host")); - } - - @Test - public void testMultipleHeaders() throws InvocationTargetException, IllegalAccessException { - HttpRequest request = new HttpRequest(); - parseHeadersMethod.invoke( - httpParser, - generateMultipleHeadersMessage(), - request); - assertEquals(10, request.getHeaderNames().size()); - assertEquals("localhost:8080", request.getHeader("host")); - } - - @Test - public void testErrorSpaceBeforeColonHeader() throws InvocationTargetException, IllegalAccessException { - HttpRequest request = new HttpRequest(); - - try { - parseHeadersMethod.invoke( - httpParser, - generateSpaceBeforeColonErrorHeaderMessage(), - request); - } catch (InvocationTargetException e) { - if (e.getCause() instanceof HttpParsingException) { - assertEquals(HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST, ((HttpParsingException)e.getCause()).getErrorCode()); - } - } - - } - - private InputStreamReader generateSimpleSingleHeaderMessage() { - String rawData = "Host: localhost:8080\r\n" ; -// "Connection: keep-alive\r\n" + -// "Upgrade-Insecure-Requests: 1\r\n" + -// "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36\r\n" + -// "Sec-Fetch-User: ?1\r\n" + -// "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" + -// "Sec-Fetch-Site: none\r\n" + -// "Sec-Fetch-Mode: navigate\r\n" + -// "Accept-Encoding: gzip, deflate, br\r\n" + -// "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + -// "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII); - return reader; - } - - private InputStreamReader generateMultipleHeadersMessage() { - String rawData = "Host: localhost:8080\r\n" + - "Connection: keep-alive\r\n" + - "Upgrade-Insecure-Requests: 1\r\n" + - "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36\r\n" + - "Sec-Fetch-User: ?1\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" + - "Sec-Fetch-Site: none\r\n" + - "Sec-Fetch-Mode: navigate\r\n" + - "Accept-Encoding: gzip, deflate, br\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII); - return reader; - } - - private InputStreamReader generateSpaceBeforeColonErrorHeaderMessage() { - String rawData = "Host : localhost:8080\r\n\r\n" ; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII); - return reader; - } - -} diff --git a/src/test/java/com/coderfromscratch/http/HttpParserTest.java b/src/test/java/com/coderfromscratch/http/HttpParserTest.java deleted file mode 100644 index 1a5a867..0000000 --- a/src/test/java/com/coderfromscratch/http/HttpParserTest.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.coderfromscratch.http; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.*; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class HttpParserTest { - - private HttpParser httpParser; - - @BeforeAll - public void beforeClass() { - httpParser = new HttpParser(); - } - - @Test - void parseHttpRequest() { - HttpRequest request = null; - try { - request = httpParser.parseHttpRequest( - generateValidGETTestCase() - ); - } catch (HttpParsingException e) { - fail(e); - } - - assertNotNull(request); - assertEquals(request.getMethod(), HttpMethod.GET); - assertEquals(request.getRequestTarget(), "/"); - assertEquals(request.getOriginalHttpVersion(), "HTTP/1.1"); - assertEquals(request.getBestCompatibleHttpVersion(), HttpVersion.HTTP_1_1); - } - - @Test - void parseHttpRequestBadMethod1() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateBadTestCaseMethodName1() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); - } - } - - @Test - void parseHttpRequestBadMethod2() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateBadTestCaseMethodName2() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.SERVER_ERROR_501_NOT_IMPLEMENTED); - } - } - - @Test - void parseHttpRequestInvNumItems1() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateBadTestCaseRequestLineInvNumItems1() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } - - @Test - void parseHttpEmptyRequestLine() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateBadTestCaseEmptyRequestLine() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } - - @Test - void parseHttpRequestLineCRnoLF() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateBadTestCaseRequestLineOnlyCRnoLF() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } - - @Test - void parseHttpRequestBadHttpVersion() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateBadHttpVersionTestCase() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.CLIENT_ERROR_400_BAD_REQUEST); - } - } - - @Test - void parseHttpRequestUnsupportedHttpVersion() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateUnsuportedHttpVersionTestCase() - ); - fail(); - } catch (HttpParsingException e) { - assertEquals(e.getErrorCode(), HttpStatusCode.SERVER_ERROR_505_HTTP_VERSION_NOT_SUPPORTED); - } - } - - @Test - void parseHttpRequestSupportedHttpVersion1() { - try { - HttpRequest request = httpParser.parseHttpRequest( - generateSupportedHttpVersion1() - ); - assertNotNull(request); - assertEquals(request.getBestCompatibleHttpVersion(), HttpVersion.HTTP_1_1); - assertEquals(request.getOriginalHttpVersion(), "HTTP/1.2"); - } catch (HttpParsingException e) { - fail(); - } - } - - private InputStream generateValidGETTestCase() { - String rawData = "GET / HTTP/1.1\r\n" + - "Host: localhost:8080\r\n" + - "Connection: keep-alive\r\n" + - "Upgrade-Insecure-Requests: 1\r\n" + - "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36\r\n" + - "Sec-Fetch-User: ?1\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" + - "Sec-Fetch-Site: none\r\n" + - "Sec-Fetch-Mode: navigate\r\n" + - "Accept-Encoding: gzip, deflate, br\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateBadTestCaseMethodName1() { - String rawData = "GeT / HTTP/1.1\r\n" + - "Host: localhost:8080\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateBadTestCaseMethodName2() { - String rawData = "GETTTT / HTTP/1.1\r\n" + - "Host: localhost:8080\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateBadTestCaseRequestLineInvNumItems1() { - String rawData = "GET / AAAAAA HTTP/1.1\r\n" + - "Host: localhost:8080\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateBadTestCaseEmptyRequestLine() { - String rawData = "\r\n" + - "Host: localhost:8080\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateBadTestCaseRequestLineOnlyCRnoLF() { - String rawData = "GET / HTTP/1.1\r" + // <----- no LF - "Host: localhost:8080\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateBadHttpVersionTestCase() { - String rawData = "GET / HTP/1.1\r\n" + - "Host: localhost:8080\r\n" + - "Connection: keep-alive\r\n" + - "Upgrade-Insecure-Requests: 1\r\n" + - "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36\r\n" + - "Sec-Fetch-User: ?1\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" + - "Sec-Fetch-Site: none\r\n" + - "Sec-Fetch-Mode: navigate\r\n" + - "Accept-Encoding: gzip, deflate, br\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateUnsuportedHttpVersionTestCase() { - String rawData = "GET / HTTP/2.1\r\n" + - "Host: localhost:8080\r\n" + - "Connection: keep-alive\r\n" + - "Upgrade-Insecure-Requests: 1\r\n" + - "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36\r\n" + - "Sec-Fetch-User: ?1\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" + - "Sec-Fetch-Site: none\r\n" + - "Sec-Fetch-Mode: navigate\r\n" + - "Accept-Encoding: gzip, deflate, br\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } - - private InputStream generateSupportedHttpVersion1() { - String rawData = "GET / HTTP/1.2\r\n" + - "Host: localhost:8080\r\n" + - "Connection: keep-alive\r\n" + - "Upgrade-Insecure-Requests: 1\r\n" + - "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36\r\n" + - "Sec-Fetch-User: ?1\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" + - "Sec-Fetch-Site: none\r\n" + - "Sec-Fetch-Mode: navigate\r\n" + - "Accept-Encoding: gzip, deflate, br\r\n" + - "Accept-Language: en-US,en;q=0.9,es;q=0.8,pt;q=0.7,de-DE;q=0.6,de;q=0.5,la;q=0.4\r\n" + - "\r\n"; - - InputStream inputStream = new ByteArrayInputStream( - rawData.getBytes( - StandardCharsets.US_ASCII - ) - ); - - return inputStream; - } -} \ No newline at end of file diff --git a/src/test/java/com/coderfromscratch/httpserver/core/io/WebRootHandlerTest.java b/src/test/java/com/coderfromscratch/httpserver/core/io/WebRootHandlerTest.java deleted file mode 100644 index c8d4a58..0000000 --- a/src/test/java/com/coderfromscratch/httpserver/core/io/WebRootHandlerTest.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.coderfromscratch.httpserver.core.io; - -import com.coderfromscratch.http.HttpParser; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.FileNotFoundException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import static org.junit.jupiter.api.Assertions.*; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class WebRootHandlerTest { - - private WebRootHandler webRootHandler; - - private Method checkIfEndsWithSlashMethod; - - private Method checkIfProvidedRelativePathExistsMethod; - @BeforeAll - public void beforeClass() throws WebRootNotFoundException, NoSuchMethodException { - webRootHandler = new WebRootHandler("WebRoot"); - Class cls = WebRootHandler.class; - checkIfEndsWithSlashMethod = cls.getDeclaredMethod("checkIfEndsWithSlash", String.class); - checkIfEndsWithSlashMethod.setAccessible(true); - - checkIfProvidedRelativePathExistsMethod = cls.getDeclaredMethod("checkIfProvidedRelativePathExists", String.class); - checkIfProvidedRelativePathExistsMethod.setAccessible(true); - } - - @Test - void constructorGoodPath() { - try { - WebRootHandler webRootHandler = new WebRootHandler("E:\\Projects\\CoderFromScratch\\simple-java-http-server\\WebRoot"); - } catch (WebRootNotFoundException e) { - fail(e); - } - } - - @Test - void constructorBadPath() { - try { - WebRootHandler webRootHandler = new WebRootHandler("E:\\Projects\\CoderFromScratch\\simple-java-http-server\\WebRoot2"); - fail(); - } catch (WebRootNotFoundException e) { - } - } - - @Test - void constructorGoodPath2() { - try { - WebRootHandler webRootHandler = new WebRootHandler("WebRoot"); - } catch (WebRootNotFoundException e) { - fail(e); - } - } - - @Test - void constructorBadPath2() { - try { - WebRootHandler webRootHandler = new WebRootHandler("WebRoot2"); - fail(); - } catch (WebRootNotFoundException e) { - } - } - - @Test - void checkIfEndsWithSlashMethodFalse() { - try { - boolean result = (Boolean) checkIfEndsWithSlashMethod.invoke(webRootHandler,"index.html"); - assertFalse(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void checkIfEndsWithSlashMethodFalse2() { - try { - boolean result = (Boolean) checkIfEndsWithSlashMethod.invoke(webRootHandler,"/index.html"); - assertFalse(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void checkIfEndsWithSlashMethodFalse3() { - try { - boolean result = (Boolean) checkIfEndsWithSlashMethod.invoke(webRootHandler,"/private/index.html"); - assertFalse(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void checkIfEndsWithSlashMethodTrue() { - try { - boolean result = (Boolean) checkIfEndsWithSlashMethod.invoke(webRootHandler,"/"); - assertTrue(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void checkIfEndsWithSlashMethodTrue2() { - try { - boolean result = (Boolean) checkIfEndsWithSlashMethod.invoke(webRootHandler,"/private/"); - assertTrue(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void testWebRootFilePathExists() { - try { - boolean result = (boolean) checkIfProvidedRelativePathExistsMethod.invoke(webRootHandler, "/index.html"); - assertTrue(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void testWebRootFilePathExistsGoodRelative() { - try { - boolean result = (boolean) checkIfProvidedRelativePathExistsMethod.invoke(webRootHandler, "/./././index.html"); - assertTrue(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void testWebRootFilePathExistsDoesNotExist() { - try { - boolean result = (boolean) checkIfProvidedRelativePathExistsMethod.invoke(webRootHandler, "/indexNotHere.html"); - assertFalse(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void testWebRootFilePathExistsInvalid() { - try { - boolean result = (boolean) checkIfProvidedRelativePathExistsMethod.invoke(webRootHandler, "/../LICENSE"); - assertFalse(result); - } catch (IllegalAccessException e) { - fail(e); - } catch (InvocationTargetException e) { - fail(e); - } - } - - @Test - void testGetFileMimeTypeText() { - try { - String mimeType = webRootHandler.getFileMimeType("/"); - assertEquals("text/html", mimeType); - } catch (FileNotFoundException e) { - fail(e); - } - } - - @Test - void testGetFileMimeTypePng() { - try { - String mimeType = webRootHandler.getFileMimeType("/logo.png"); - assertEquals("image/png", mimeType); - } catch (FileNotFoundException e) { - fail(e); - } - } - - @Test - void testGetFileMimeTypeDefault() { - try { - String mimeType = webRootHandler.getFileMimeType("/favicon.ico"); - assertEquals("application/octet-stream", mimeType); - } catch (FileNotFoundException e) { - fail(e); - } - } - - @Test - void testGetFileByteArrayData() { - try { - assertTrue(webRootHandler.getFileByteArrayData("/").length > 0); - } catch (FileNotFoundException e) { - fail(e); - } catch (ReadFileException e) { - fail(e); - } - } - - @Test - void testGetFileByteArrayDataFileNotThere() { - try { - webRootHandler.getFileByteArrayData("/test.html"); - fail(); - } catch (FileNotFoundException e) { - // pass - } catch (ReadFileException e) { - fail(e); - } - } -}