├── 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 | [](https://hits.seeyoufarm.com)
4 |
5 | 
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
--------------------------------------------------------------------------------