├── LICENSE ├── README.md ├── _config.yml ├── lightweight-webserver ├── pom.xml └── src │ ├── main │ └── java │ │ └── xdvrx1_serverProject │ │ ├── ClientRequest.java │ │ ├── FileNotFoundMessage.java │ │ ├── FileWebServer.java │ │ ├── GETMethod.java │ │ ├── MainMethod.java │ │ ├── NotSupportedMessage.java │ │ ├── POSTMethod.java │ │ ├── ReadInputStream.java │ │ ├── ServerApp.java │ │ └── ServerHeader.java │ └── test │ └── java │ └── xdvrx1_serverProject │ ├── ClientRequestTest.java │ ├── FileWebServerTest.java │ ├── GETMethodTest.java │ ├── POSTMethodTest.java │ └── ReadInputStreamTest.java └── screenshots └── screenshot1.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 xdvrx1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightweight Web Server 2 | 3 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fxdvrx1%2Flightweight-web-server&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=PAGE+VIEWS&edge_flat=false)](https://hits.seeyoufarm.com) 4 | 5 | ![picture](screenshots/screenshot1.png) 6 | 7 | I'm happy to share with you my custom server written in Java ! 8 | 9 | ## Disclaimer 10 | Please note that this project is presented as a showcase of my work during a 11 | specific period. It represents a snapshot of my skills and accomplishments 12 | at that time. As such, this project is no longer actively maintained or updated. 13 | It is kept public for demonstration purposes and may not reflect my current 14 | abilities or the latest best practices in the field. 15 | 16 | However, feel free to learn from this archived project, 17 | preserved as it was during that specific period ! 18 | 19 | ## Project Intro 20 | Network programming is very important. Remember that the Internet 21 | was created for that very reason: that is, computers 22 | must communicate to one another around the world and 23 | data must arrive as soon as possible. 24 | 25 | Unlike C, in Java you don't need to do much complexity, Java 26 | does that exactly. But remember, data will always end as bytes. 27 | So, everything can be processed by a computer as long as the 28 | programmer can represent the data as bytes. 29 | 30 | Remember that both servers and 31 | browsers can pass data to one another, but typically a browser will always 32 | initiate the connection while the server is just always waiting for a connection. 33 | The same is true for other servers like Telnet or FTP servers. 34 | 35 | Yet, HTTP is also good to pass any data as long 36 | as it is expressed in bytes. HTTP is so famous now as it is the protocol of 37 | web servers and browsers, so more often, we always link HTTP for web sites. 38 | Also, updated browsers nowadays can display more than text documents like PDF and 39 | images and even markdown files. 40 | 41 | Also, bytes are not even numbers, they are just representations for us humans because 42 | a computer can only understand the presence or absence of an electrical pulse: that 43 | is, again, represented as 0 and 1. For today, of course, typical users will hate seeing 44 | 0s and 1s so programmers do the abstraction. 45 | 46 | This server is enabled for GET and POST methods. Please see the instructions. 47 | 48 | For this app, this is capable of getting contents from a directory 49 | and displaying that through a browser and capable of accepting forms using 50 | the POST method. 51 | 52 | Once you get the runnable `jar` file through 53 | `mvn install`, say, you put the `.jar` file in Desktop 54 | directory, all the files there can simply be accessed 55 | through a web browser, preferably Google Chrome, as 56 | this browser can display images & videos by default. 57 | 58 | For posting, you need to have a html form, 59 | so that data can be entered. 60 | 61 | ## Compiling & Using The Server 62 | 63 | This is namespaced as package `xdvrx1_serverProject`. It is up to your IDE 64 | how it actually manages Java projects. 65 | 66 | But now, I decided to pick the Maven build tool 67 | using the Command Prompt only. I use Java on the 68 | Command Prompt also. 69 | 70 | If you want to follow my setup, these are the steps: 71 | 72 | 1. Install Java SDK 8 if there is none. 73 | 2. Install Maven if there is none. 74 | 3. Add both in the System Environment Variables 75 | so that you can use it through the Command Prompt. 76 | 4. Using the Command Prompt, 77 | go to the project folder `lightweight-webserver`. 78 | 5. Compile or build the project. 79 | 80 | ### Command Prompt Using Maven And Java 81 | `mvn clean` to clean the directory 82 | 83 | `mvn install` to install including 84 | the runnable `.jar` 85 | 86 | `mvn compile` to simply compile 87 | 88 | To run the runnable `.jar`, go 89 | to the `target` directory and type 90 | on the Command prompt `java -jar [filename]` 91 | 92 | and it is standalone, you can simply 93 | copy the `.jar` and put that to any directory 94 | you want and it will start hosting 95 | there once you run that. 96 | 97 | ### How To Run The Server 98 | 1. Compile the project. 99 | 2. Run the project. 100 | 3. Put `index.html` to your working directory 101 | and other sample files. 102 | 3. Open a browser. 103 | 4. Type in the address bar `localhost`. 104 | 5. See the default web page! 105 | 6. Access every file in that root directory 106 | by typing the filename relative to that directory 107 | or just create a list on the default web page. 108 | 7. If you are quite confused, you can download my release. 109 | 110 | Remember also that the `index.html` 111 | is the default web page but of course 112 | you can change that. 113 | 114 | Once you have created the executable jar file, 115 | all files within the directory of this executable jar file 116 | can be accessed through this server. 117 | 118 | As my example, in my release the executable jar file must 119 | have its own folder, then inside that folder is the default page 120 | `index.html`, then you can create a subfolder, in my case, 121 | `data` and you can put files there to be served by this webserver. 122 | 123 | And there is the form sample to post. When you click `add record`, 124 | the data will be sent as POST. 125 | 126 | ## License 127 | MIT- the permissive license 128 | 129 | ## More Java Projects 130 | for more Java discussion and other details, 131 | check the Main Page -> [Java](https://github.com/jdevfullstack/java) 132 | 133 | ## More Of My Content 134 | - [jdevfullstack Profile](https://github.com/jdevfullstack) 135 | - [jdevfullstack Repos](https://github.com/jdevfullstack?tab=repositories) 136 | - [jdevfullstack Projects](https://github.com/jdevfullstack-projects) 137 | - [jdevfullstack Tutorials](https://github.com/jdevfullstack-tutorials) 138 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /lightweight-webserver/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | xdvrx1_serverProject 5 | lightweight-webserver 6 | 1.0-SNAPSHOT 7 | my-app 8 | 9 | http://www.example.com 10 | 11 | UTF-8 12 | 1.7 13 | 1.7 14 | 15 | 16 | 17 | junit 18 | junit 19 | 4.13.1 20 | test 21 | 22 | 23 | org.junit.vintage 24 | junit-vintage-engine 25 | 5.4.0 26 | test 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | maven-clean-plugin 36 | 3.1.0 37 | 38 | 39 | 40 | maven-resources-plugin 41 | 3.0.2 42 | 43 | 44 | maven-compiler-plugin 45 | 3.8.0 46 | 47 | 48 | maven-surefire-plugin 49 | 2.22.1 50 | 51 | 52 | maven-install-plugin 53 | 2.5.2 54 | 55 | 56 | maven-deploy-plugin 57 | 2.8.2 58 | 59 | 60 | 61 | maven-site-plugin 62 | 3.7.1 63 | 64 | 65 | maven-project-info-reports-plugin 66 | 3.0.0 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-dependency-plugin 71 | 72 | 73 | copy-dependencies 74 | prepare-package 75 | 76 | copy-dependencies 77 | 78 | 79 | xdvrx1_serverProject/libs 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-jar-plugin 87 | 88 | 89 | 90 | true 91 | libs/ 92 | xdvrx1_serverProject.MainMethod 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/ClientRequest.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | /** 4 | * This is where the request of a client 5 | * is being processed. When we say `client`, 6 | * it can be Google Chrome or any other browser. 7 | * This implements `Runnable` to be called in 8 | * `FileWebServer` class. 9 | */ 10 | 11 | import java.io.*; 12 | import java.net.*; 13 | import java.util.logging.*; 14 | 15 | public class ClientRequest implements Runnable { 16 | 17 | private String defaultPage = "index.html"; 18 | private File rootDirectory; 19 | private Socket connection; 20 | 21 | private final static Logger requestLogger = 22 | Logger.getLogger(ClientRequest.class.getCanonicalName()); 23 | 24 | //the constructor 25 | public ClientRequest(File rootDirectory, 26 | String defaultPage, 27 | Socket connection) { 28 | 29 | //check if the given rootDirectory is a file 30 | //and will explicitly throw an exception 31 | if (rootDirectory.isFile()) { 32 | throw new 33 | IllegalArgumentException("rootDirectory must be" 34 | + "a directory, not a file"); 35 | } 36 | 37 | //will try to get the canonical pathname 38 | //from the supplied `rootDirectory` argument 39 | try { 40 | rootDirectory = rootDirectory.getCanonicalFile(); 41 | } catch (IOException ex) { 42 | requestLogger.log(Level.WARNING, "IOException", ex); 43 | } 44 | 45 | //constructors `rootDirectory` is assigned to 46 | //the successful `rootDirectory` 47 | this.rootDirectory = rootDirectory; 48 | 49 | if (defaultPage != null) { 50 | this.defaultPage = defaultPage; 51 | } 52 | 53 | this.connection = connection; 54 | 55 | } 56 | 57 | @Override 58 | public void run() { 59 | 60 | try { 61 | //remember, once the connection is established 62 | //the server will use the Socket to pass 63 | //data back and forth 64 | 65 | //raw output stream, 66 | //in case it is not a text document 67 | //this will be used purely to pass 68 | //the data as bytes 69 | OutputStream raw = 70 | new BufferedOutputStream(connection.getOutputStream()); 71 | 72 | //for text files that uses the 73 | //underlying output stream 74 | Writer out = 75 | new OutputStreamWriter(raw); 76 | 77 | //needed to add new line correctly 78 | //for different platforms 79 | BufferedWriter bufferedOut = 80 | new BufferedWriter(out); 81 | 82 | BufferedInputStream bis = 83 | new BufferedInputStream(connection.getInputStream()); 84 | 85 | //for reading the GET header from a browser 86 | Reader in = 87 | new 88 | InputStreamReader(bis, "US-ASCII"); 89 | 90 | //the request send by a client 91 | //take note, this can be loaded into a data structure 92 | //instead of using StringBuffer to generate string 93 | //but, it's up to you, for demonstration 94 | //I will just use the StringBuffer to build 95 | //the string object 96 | ReadInputStream readInputStream = new ReadInputStream(); 97 | StringBuffer userRequest = 98 | readInputStream.readUserRequest(bis, in, connection); 99 | 100 | //build a string object from StringBuffer 101 | String userRequestToString = userRequest.toString(); 102 | 103 | //get the first line through this index 104 | int indexOfFirst = userRequestToString.indexOf("\r\n"); 105 | String firstLine = userRequestToString 106 | .substring(0,indexOfFirst); 107 | 108 | //express it in the logger, mostly for debugging purposes 109 | requestLogger 110 | .info(connection 111 | .getRemoteSocketAddress() + " " + firstLine); 112 | 113 | //`token` are the words separated from the request line, 114 | //for example, `GET /data/ HTTP/1.1` 115 | String[] token = firstLine.split("\\s+"); 116 | //0 index tells the method 117 | String method = token[0]; 118 | //null at first 119 | String http_version = ""; 120 | 121 | if (method.equals("GET")) { 122 | 123 | GETMethod getMethod = new GETMethod(); 124 | byte[] _data = getMethod.processGET(rootDirectory, 125 | token, 126 | defaultPage, 127 | http_version, 128 | out); 129 | // still send the file; 130 | //and use the underlying stream 131 | //instead of the writer 132 | raw.write(_data); 133 | raw.flush(); 134 | 135 | } else if(method.equals("POST")) { 136 | 137 | POSTMethod postMethod = new POSTMethod(); 138 | 139 | String requestBody = 140 | postMethod.returnPOSTData(userRequestToString); 141 | 142 | //we use also the buffered out writer 143 | //to make sure that the new line will be correct 144 | //for all platforms 145 | bufferedOut.write("data recorded:"); 146 | bufferedOut.newLine(); 147 | bufferedOut.write(requestBody); 148 | bufferedOut.flush(); 149 | 150 | } else { 151 | 152 | //not yet implemented methods 153 | if (http_version.startsWith("HTTP/")) { 154 | // send a MIME header 155 | ServerHeader.serverHeader(out, 156 | "HTTP/1.0 501 Not Implemented", 157 | "text/html; charset=utf-8", 158 | NotSupportedMessage.content.length()); 159 | } 160 | 161 | out.write(NotSupportedMessage.content); 162 | out.flush(); 163 | } 164 | } catch (IOException ex) { 165 | requestLogger 166 | .log(Level.WARNING, "Can't talk to: " 167 | + connection.getRemoteSocketAddress(), ex); 168 | } finally { 169 | try { 170 | connection.close(); 171 | } catch (IOException ex) { 172 | requestLogger.log(Level.WARNING, "IO exception", ex); 173 | } 174 | } 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/FileNotFoundMessage.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | class FileNotFoundMessage { 4 | 5 | static final String content = 6 | new StringBuilder("\r\n") 7 | .append("File Not Found\r\n") 8 | .append("\r\n") 9 | .append("") 10 | .append("

HTTP Error 404: File Not Found [Try again later]

\r\n") 11 | .append("\r\n") 12 | .toString(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/FileWebServer.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | /** 4 | * This is the actual server class. 5 | * This will call the overridden 6 | * method `run()` of `Runnable`. 7 | */ 8 | 9 | import java.util.concurrent.*; 10 | import java.io.*; 11 | import java.net.*; 12 | 13 | import java.util.logging.*; 14 | 15 | public class FileWebServer { 16 | 17 | private final File rootDirectory; 18 | private final int port; 19 | private static final int pool_count = 1000; 20 | private static final String defaultPage = "index.html"; 21 | 22 | private static final Logger 23 | serverLogger = Logger.getLogger(FileWebServer 24 | .class.getCanonicalName()); 25 | 26 | //the constructor 27 | public FileWebServer(File rootDirectory, int port) 28 | throws IOException { 29 | 30 | if (!rootDirectory.isDirectory()) { 31 | throw new IOException(rootDirectory 32 | + " is not a directory"); 33 | } 34 | 35 | this.rootDirectory = rootDirectory; 36 | this.port = port; 37 | 38 | } 39 | 40 | //void start 41 | public void start() 42 | throws IOException { 43 | 44 | ExecutorService pool = 45 | Executors.newFixedThreadPool(pool_count); 46 | 47 | try (ServerSocket server = new ServerSocket(port)) { 48 | 49 | serverLogger.info("Listening on port " + server.getLocalPort()); 50 | serverLogger.info("@DocumentRoot"); 51 | 52 | while (true) { 53 | try { 54 | Socket request = server.accept(); 55 | Runnable r = 56 | new ClientRequest(rootDirectory, defaultPage, request); 57 | pool.submit(r); 58 | } catch (IOException ex) { 59 | serverLogger.log(Level.WARNING, "Error accepting connection", ex); 60 | } 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/GETMethod.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.nio.file.Files; 4 | import java.io.*; 5 | import java.net.*; 6 | 7 | class GETMethod { 8 | 9 | byte[] processGET(File rootDirectory, 10 | String[] token, 11 | String defaultPage, 12 | String http_version, 13 | Writer out) { 14 | 15 | try { 16 | String fileName = token[1]; 17 | 18 | //add manually the default page 19 | if (fileName.endsWith("/")) { 20 | fileName = fileName + defaultPage; 21 | } 22 | 23 | //get the content type for proper encoding of data 24 | String contentType = 25 | URLConnection.getFileNameMap().getContentTypeFor(fileName); 26 | 27 | if (token.length > 2) { 28 | http_version = token[2]; 29 | } 30 | 31 | File actualFile = 32 | new File(rootDirectory, 33 | fileName.substring(1, fileName.length())); 34 | 35 | String root = rootDirectory.getPath(); 36 | 37 | // restrict clients inside the document root 38 | if (actualFile.canRead() 39 | && actualFile.getCanonicalPath().startsWith(root)) { 40 | 41 | byte[] _data = Files.readAllBytes(actualFile.toPath()); 42 | 43 | if (http_version.startsWith("HTTP/")) { 44 | // send a MIME header 45 | ServerHeader 46 | .serverHeader(out, 47 | "HTTP/1.0 200 OK", 48 | contentType, 49 | _data.length); 50 | } 51 | 52 | return _data; 53 | 54 | } else { 55 | 56 | // file not found 57 | if (http_version.startsWith("HTTP/")) { 58 | 59 | // send a MIME header 60 | ServerHeader 61 | .serverHeader(out, 62 | "HTTP/1.0 404 File Not Found", 63 | "text/html; charset=utf-8", 64 | FileNotFoundMessage.content.length()); 65 | } 66 | 67 | out.write(FileNotFoundMessage.content); 68 | out.flush(); 69 | return null; 70 | } 71 | 72 | } catch (IOException ioe) { 73 | System.out.println(ioe.getMessage()); 74 | return null; 75 | } 76 | 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/MainMethod.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.io.*; 4 | 5 | class MainMethod { 6 | public static void main(String[] args) { 7 | ServerApp serverApp = new ServerApp(); 8 | serverApp.build(); 9 | } 10 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/NotSupportedMessage.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | class NotSupportedMessage { 4 | 5 | static final String content = new StringBuilder("\r\n") 6 | .append("Not Implemented\r\n") 7 | .append("\r\n") 8 | .append("") 9 | .append("

HTTP Error 501: Not Yet Supported Method

\r\n") 10 | .append("\r\n") 11 | .toString(); 12 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/POSTMethod.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | class POSTMethod { 4 | 5 | String returnPOSTData(String userRequestToString) { 6 | 7 | //get the request body for POST method 8 | //add 4, because the index that 9 | //will be returned is relative to the 10 | //very first occurence of the string 11 | String requestBody = 12 | userRequestToString 13 | .substring(userRequestToString.lastIndexOf("\r\n\r\n") + 4); 14 | 15 | //just showing the input data back to the client 16 | //a lot of things can be done for the request body 17 | //it can go directly to a file or a database, 18 | //or be loaded into an XML file for further processing 19 | return requestBody; 20 | } 21 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/ReadInputStream.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.io.*; 4 | import java.net.*; 5 | 6 | class ReadInputStream { 7 | 8 | StringBuffer readUserRequest(BufferedInputStream bis, 9 | Reader in, 10 | Socket connection) { 11 | 12 | StringBuffer userRequest = new StringBuffer(); 13 | 14 | try { 15 | //this will be the basis to get 16 | //all the bytes from the stream 17 | int bufferSize = bis.available(); 18 | 19 | while (true) { 20 | if (userRequest.length() > bufferSize-1) { 21 | //for performance, always shutdown 22 | //after breaking from this loop 23 | if (connection.isConnected()) { 24 | connection.shutdownInput(); 25 | } 26 | break; 27 | } 28 | 29 | //read() of Reader is actually a blocking 30 | //method, and without proper algorithm 31 | //this will hang the entire program 32 | int c = in.read(); 33 | userRequest.append((char) c); 34 | 35 | //ignore the line endings, 36 | //the Reader will interpret this as end of buffer 37 | //we need to read the entire content of the buffer 38 | if (c == '\n' || c == '\r' || c == -1) continue; 39 | } 40 | return userRequest; 41 | } catch (IOException ioe) { 42 | System.out.println(ioe.getMessage()); 43 | return null; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/ServerApp.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.io.*; 4 | 5 | class ServerApp { 6 | public void build() { 7 | 8 | try { 9 | //the relative root directory 10 | //it's up to you if you want to change this 11 | File currentDir = new File("."); 12 | 13 | //create an instance of `FileWebServer` 14 | //at the current directory and using port 80 15 | //again, it is up to you when you want to change 16 | //the port 17 | FileWebServer filewebserver = new FileWebServer(currentDir, 80); 18 | 19 | //call `start` method that 20 | //contains the call for the Runnable `run` 21 | //of `ClientRequest` class 22 | filewebserver.start(); 23 | 24 | } catch (IOException ex) { 25 | //throws an exception if `currentDir` 26 | //is not recognized as such 27 | System.out.println(ex.getMessage()); 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/main/java/xdvrx1_serverProject/ServerHeader.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.util.*; 4 | import java.io.*; 5 | 6 | class ServerHeader { 7 | 8 | static void serverHeader(Writer out, 9 | String responseCode, 10 | String contentType, 11 | int length) 12 | throws IOException { 13 | 14 | Date current = new Date(); 15 | 16 | out.write(responseCode + "\r\n"); 17 | out.write("Date: " + current + "\r\n"); 18 | out.write("Server: `xdvrx1_Server` 3.0\r\n"); 19 | out.write("Content-length: " + length + "\r\n"); 20 | out.write("Content-type: " + contentType + "\r\n\r\n"); 21 | out.flush(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lightweight-webserver/src/test/java/xdvrx1_serverProject/ClientRequestTest.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.io.*; 4 | import java.net.*; 5 | 6 | import org.junit.*; 7 | 8 | //streams are expressed referring to files 9 | //but since we want to test the functionality, 10 | //and since streams are treated almost the same, 11 | //we use streams using files 12 | public class ClientRequestTest { 13 | 14 | File rootDirectory; 15 | String defaultPage; 16 | Socket connection; 17 | File tempFile; 18 | 19 | ClientRequest clientRequest; 20 | 21 | @Before 22 | public void setUp() throws Exception { 23 | rootDirectory = new File("."); 24 | defaultPage = "index.html"; 25 | //a blank socket, 26 | connection = new Socket(); 27 | 28 | tempFile = File.createTempFile("sample", null); 29 | 30 | clientRequest = new ClientRequest(rootDirectory, 31 | defaultPage, 32 | connection); 33 | } 34 | 35 | @Test 36 | public void testObjectShouldNotBeNull() throws IOException { 37 | //just a basic test whether this object 38 | //is successfully created 39 | Assert.assertNotNull(clientRequest); 40 | } 41 | 42 | @Test(expected = IllegalArgumentException.class) 43 | public void testConstructorShouldThrowException() { 44 | //`tempFile` is not a directory, it is a file 45 | //so, this constructor, as expected, should 46 | //throw IllegalArgumentException 47 | ClientRequest clientRequest2 = 48 | new ClientRequest(tempFile, defaultPage, connection); 49 | } 50 | 51 | @Test 52 | public void testConstructorShouldNotThrowException() { 53 | //now, rootDirectory is a real directory 54 | //it will not throw an exception 55 | ClientRequest clientRequest3 = 56 | new ClientRequest(rootDirectory, defaultPage, connection); 57 | 58 | } 59 | 60 | @After 61 | public void tearDown() throws Exception { 62 | tempFile.deleteOnExit(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /lightweight-webserver/src/test/java/xdvrx1_serverProject/FileWebServerTest.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.io.*; 4 | 5 | import org.junit.*; 6 | 7 | public class FileWebServerTest { 8 | 9 | File rootDirectory; 10 | FileWebServer fileWebServer; 11 | File tempFile; 12 | 13 | @Before 14 | public void setUp() throws Exception { 15 | 16 | rootDirectory = new File("."); 17 | fileWebServer = new FileWebServer(rootDirectory, 80); 18 | tempFile = File.createTempFile("sample", null); 19 | 20 | } 21 | 22 | @Test 23 | public void testObjectShouldNotBeNull() { 24 | //a simple test whether the object 25 | //is successfully created 26 | Assert.assertNotNull(fileWebServer); 27 | } 28 | 29 | @Test(expected = IOException.class) 30 | public void testConstructorShouldThrowException() throws IOException { 31 | //`tempFile` is not a directory, it is a file 32 | //so this should throw the exception 33 | FileWebServer fileWebServer2 = new FileWebServer(tempFile, 80); 34 | 35 | } 36 | 37 | @Test 38 | public void testConstructorShouldNotThrowException() throws IOException { 39 | //rootDirectory is a real directory, so 40 | //this will not throw the exception 41 | FileWebServer fileWebServer3 = new FileWebServer(rootDirectory, 80); 42 | 43 | } 44 | 45 | @After 46 | public void tearDown() throws Exception { 47 | tempFile.deleteOnExit(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lightweight-webserver/src/test/java/xdvrx1_serverProject/GETMethodTest.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.nio.file.Files; 4 | import java.io.*; 5 | import java.net.*; 6 | 7 | import org.junit.*; 8 | 9 | //streams are expressed referring to files 10 | //but since we want to test the functionality, 11 | //and since streams are treated almost the same, 12 | //we use streams using files 13 | public class GETMethodTest { 14 | 15 | File rootDirectory; 16 | String defaultPage; 17 | String http_version; 18 | 19 | GETMethod getMethod; 20 | 21 | String firstLine; 22 | String[] token; 23 | 24 | File tempFile; 25 | OutputStream out; 26 | Writer out2; 27 | File rootDirectoryX; 28 | 29 | @Before 30 | public void setUp() throws Exception { 31 | rootDirectoryX = new File("."); 32 | 33 | //get the absolute path 34 | //you should not do this when this program 35 | //is run, or else a client will have an access to 36 | //other directories you don't want to expose 37 | //but for testing purposes, we need to get the 38 | //canonical path 39 | String absolutePath = rootDirectoryX.getCanonicalPath(); 40 | 41 | rootDirectory = new File(absolutePath); 42 | 43 | firstLine = "GET /README.md HTTP/1.1"; 44 | token = firstLine.split("\\s+"); 45 | 46 | defaultPage = "index.html"; 47 | http_version = null; 48 | 49 | //the `tempFile` and `out` are just here 50 | //for testing, but it is not the focus of testing 51 | tempFile = File.createTempFile("tempFileXX", ".txt"); 52 | out = new FileOutputStream(tempFile); 53 | out = new BufferedOutputStream(out); 54 | out2 = new OutputStreamWriter(out, "US-ASCII"); 55 | 56 | getMethod = new GETMethod(); 57 | } 58 | 59 | @Test 60 | public void testObjectShouldNotBeNull() { 61 | 62 | //test both the object and the method 63 | Assert.assertNotNull(getMethod); 64 | Assert.assertNotNull(getMethod 65 | .processGET(rootDirectory, token, 66 | defaultPage, http_version, 67 | out2)); 68 | } 69 | 70 | @Test 71 | public void testMethodShouldReturnNull() { 72 | //since rootDirectoryX did not get the 73 | //absolute path, 74 | //inside the real method, the 75 | //`if (actualFile.getCanonicalPath().startsWith(root))` will fail 76 | //thereby returning null as indicated in its `else` block 77 | Assert.assertNull(getMethod. 78 | processGET(rootDirectoryX, token, 79 | defaultPage, http_version, 80 | out2)); 81 | } 82 | 83 | @After 84 | public void tearDown() throws Exception { 85 | tempFile.deleteOnExit(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /lightweight-webserver/src/test/java/xdvrx1_serverProject/POSTMethodTest.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import org.junit.*; 4 | 5 | public class POSTMethodTest { 6 | 7 | POSTMethod postMethod = new POSTMethod(); 8 | //after the third line, that is one blank line 9 | //in HTTP request, following that is the body of the 10 | //request 11 | String userRequestToString = 12 | "first line" + "\r\n" + 13 | "second line" + "\r\n" + 14 | "third line" + "\r\n\r\n" + 15 | "Hello World"; 16 | 17 | @Test 18 | public void testTheLineEndingsOfClientRequest() { 19 | Assert.assertEquals("Hello World", 20 | postMethod.returnPOSTData(userRequestToString)); 21 | } 22 | } -------------------------------------------------------------------------------- /lightweight-webserver/src/test/java/xdvrx1_serverProject/ReadInputStreamTest.java: -------------------------------------------------------------------------------- 1 | package xdvrx1_serverProject; 2 | 3 | import java.io.*; 4 | import java.net.*; 5 | 6 | import org.junit.*; 7 | 8 | //streams are expressed referring to files 9 | //but since we want to test the functionality, 10 | //and since streams are treated almost the same, 11 | //we use streams using files 12 | public class ReadInputStreamTest { 13 | 14 | File rootDirectory = new File("."); 15 | File tempFile; 16 | String defaultPage; 17 | Socket connection; 18 | 19 | InputStream is; 20 | OutputStream out; 21 | 22 | FileInputStream fin; 23 | BufferedInputStream bis; 24 | 25 | Reader in; 26 | Writer out2; 27 | 28 | ReadInputStream readInputStream; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | tempFile = File.createTempFile("tempFileX", ".txt"); 33 | 34 | out = new FileOutputStream(tempFile); 35 | out = new BufferedOutputStream(out); 36 | 37 | out2 = new OutputStreamWriter(out, "US-ASCII"); 38 | 39 | out2.append('a'); 40 | out2.append('b'); 41 | out2.append('c'); 42 | out2.append('\r'); 43 | out2.append('\n'); 44 | out2.append(' '); 45 | 46 | out2.flush(); 47 | 48 | is = new FileInputStream(tempFile); 49 | bis = new BufferedInputStream(is); 50 | 51 | in = new InputStreamReader(bis, "US-ASCII"); 52 | 53 | readInputStream = new ReadInputStream(); 54 | 55 | defaultPage = "index.html"; 56 | connection = new Socket(); 57 | 58 | } 59 | 60 | @Test 61 | public void testMethodShouldReturnNotNull() { 62 | //test the method whether its returning 63 | //an object 64 | Assert.assertNotNull(readInputStream. 65 | readUserRequest(bis, in, connection)); 66 | } 67 | 68 | @Test 69 | public void testMethodShouldReturnAnObject() { 70 | 71 | //a simple expectation from the encoded data 72 | StringBuffer expectedResult = new StringBuffer(); 73 | expectedResult.append('a'); 74 | expectedResult.append('b'); 75 | expectedResult.append('c'); 76 | expectedResult.append('\r'); 77 | expectedResult.append('\n'); 78 | expectedResult.append(' '); 79 | 80 | Assert.assertEquals(expectedResult.toString(), 81 | readInputStream. 82 | readUserRequest(bis, in, connection).toString()); 83 | 84 | } 85 | 86 | @Test(expected = IOException.class) 87 | public void testMethodShouldThrowException() throws IOException { 88 | 89 | //when we close the input stream, it should 90 | //throw an exception 91 | in.close(); 92 | StringBuffer bufferResult = readInputStream.readUserRequest(bis, in, connection); 93 | //from the method, it will go directly to else 94 | //and will return null, thereby indicating that there 95 | //is an exception 96 | 97 | if (bufferResult == null) 98 | //in order for this test to pass 99 | //it should deliberately throw the exception 100 | throw new IOException("IOException"); 101 | 102 | } 103 | 104 | @After 105 | public void tearDown() throws Exception { 106 | tempFile.deleteOnExit(); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfullstackdev/lightweight-web-server/ceb619ac53a53cb60d4747c95e280ba202e01d54/screenshots/screenshot1.png --------------------------------------------------------------------------------