From 55941c4fdfd54f1d91492985a3e72058e53f0160 Mon Sep 17 00:00:00 2001 From: Jonathan Giles Date: Fri, 29 May 2020 09:42:12 +1200 Subject: [PATCH] Getting the code into better shape / maven ready --- LICENSE | 21 +++++ pom.xml | 93 +++++++++++++++++++ readme.md | 65 +++++++++++++ .../tools}/teenyhttpd/Header.java | 2 +- .../tools}/teenyhttpd/TeenyHttpd.java | 61 ++++++++++-- .../tools}/teenyhttpd/request/Method.java | 2 +- .../teenyhttpd/request/QueryParams.java | 2 +- .../tools}/teenyhttpd/request/Request.java | 4 +- .../teenyhttpd/response/ByteResponse.java | 4 +- .../teenyhttpd/response/FileResponse.java | 22 +++-- .../tools}/teenyhttpd/response/Response.java | 4 +- .../teenyhttpd/response/StatusCode.java | 2 +- .../teenyhttpd/response/StringResponse.java | 4 +- src/main/resources/webroot/404.html | 1 + src/main/resources/webroot/index.html | 1 + src/main/resources/webroot/not_supported.html | 1 + src/net/jonathangiles/teenyhttpd/Main.java | 23 ----- .../tools/teenyhttpd/TestServer.java | 19 ++++ wwwroot/404.html | 1 - wwwroot/index.html | 1 - wwwroot/not_supported.html | 1 - 21 files changed, 283 insertions(+), 51 deletions(-) create mode 100644 LICENSE create mode 100644 pom.xml create mode 100644 readme.md rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/Header.java (93%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/TeenyHttpd.java (66%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/request/Method.java (68%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/request/QueryParams.java (96%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/request/Request.java (93%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/response/ByteResponse.java (92%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/response/FileResponse.java (72%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/response/Response.java (81%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/response/StatusCode.java (97%) rename src/{net/jonathangiles => main/java/net/jonathangiles/tools}/teenyhttpd/response/StringResponse.java (88%) create mode 100644 src/main/resources/webroot/404.html create mode 100644 src/main/resources/webroot/index.html create mode 100644 src/main/resources/webroot/not_supported.html delete mode 100644 src/net/jonathangiles/teenyhttpd/Main.java create mode 100644 src/test/java/net/jonathangiles/tools/teenyhttpd/TestServer.java delete mode 100644 wwwroot/404.html delete mode 100644 wwwroot/index.html delete mode 100644 wwwroot/not_supported.html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7dc3b21 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Jonathan Giles + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4b48177 --- /dev/null +++ b/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + net.jonathangiles.tools + teenyhttpd + 1.0.0-SNAPSHOT + + TeenyHttpd + TeenyHttpd is an extremely basic HTTP server. + https://github.com/JonathanGiles/TeenyHttpd + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + scm:git:git://github.com/JonathanGiles/TeenyHttpd.git + scm:git:git@github.com:JonathanGiles/TeenyHttpd.git + https://github.com/JonathanGiles/TeenyHttpd + HEAD + + + + GitHub + https://github.com/JonathanGiles/TeenyHttpd/issues + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + + + + + jonathangiles + Jonathan Giles + http://jonathangiles.net + + + + + 1.8 + 1.8 + + + + + release + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..fdf343c --- /dev/null +++ b/readme.md @@ -0,0 +1,65 @@ +# TeenyHttpd + +TeenyHttpd is an extremely basic HTTP server. It is implemented in plain Java 8 with no external dependencies, making it lightweight and applicable in situations where a basic, small HTTP server is all that is required. + +## Examples + +### Starting TeenyHttpd + +You can start a new instance of TeenyHttpd up simply using the following code: + +```java +final int PORT = 80; +TeenyHttpd server = new TeenyHttpd(PORT); +server.start(); +``` + +By default, TeenyHttpd will serve files from within its own library, so you will see placeholder files. To configure the webroot from where files should be served, do the following: + +```java +final int PORT = 80; +TeenyHttpd server = new TeenyHttpd(PORT); +server.setWebroot(new File("/Users/jonathan/Code/jonathangiles.net")); +server.start(); +``` + +With this configured, all file requests will served from this base directory. + +### Stopping TeenyHttpd + +You stop a running instance as follows: + +```java +final int PORT = 80; +TeenyHttpd server = new TeenyHttpd(PORT); +server.start(); + +// some time later... +server.stop(); +``` + +### Overriding Response Processing + +In some cases, we don't want to just serve up files from a web root directory - we want to do something more custom when a request is received in the server. To do this, we override the `serve` method, as demonstrated below: + +```java +final int PORT = 80; +TeenyHttpd server = new TeenyHttpd(PORT) { + @Override + public Response serve(final Request request) { + String path = request.getPath(); + + // do work... + + // return response to user + return new StringResponse(request, StatusCode.OK, "Hello!"); + } +}; +server.start(); +``` + +In the above code sample, we return a `StringResponse`. TeenyHttpd also has `ByteResponse` and `FileResponse` types. `ByteResponse` simply returns a `byte[]` to the caller, and `FileResponse` parses the path information from the request to return a file (which is what the default behavior of TeenyHttpd uses to do file hosting). + +## Project Management + +Releases are performed using `mvn clean deploy -Prelease`. diff --git a/src/net/jonathangiles/teenyhttpd/Header.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/Header.java similarity index 93% rename from src/net/jonathangiles/teenyhttpd/Header.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/Header.java index 1f8029c..bee7ed3 100644 --- a/src/net/jonathangiles/teenyhttpd/Header.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/Header.java @@ -1,4 +1,4 @@ -package net.jonathangiles.teenyhttpd; +package net.jonathangiles.tools.teenyhttpd; public class Header { private final String keyValue; diff --git a/src/net/jonathangiles/teenyhttpd/TeenyHttpd.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/TeenyHttpd.java similarity index 66% rename from src/net/jonathangiles/teenyhttpd/TeenyHttpd.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/TeenyHttpd.java index 1d1c08a..ec0312f 100644 --- a/src/net/jonathangiles/teenyhttpd/TeenyHttpd.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/TeenyHttpd.java @@ -1,13 +1,14 @@ -package net.jonathangiles.teenyhttpd; +package net.jonathangiles.tools.teenyhttpd; -import net.jonathangiles.teenyhttpd.request.Method; -import net.jonathangiles.teenyhttpd.request.QueryParams; -import net.jonathangiles.teenyhttpd.request.Request; -import net.jonathangiles.teenyhttpd.response.FileResponse; -import net.jonathangiles.teenyhttpd.response.Response; +import net.jonathangiles.tools.teenyhttpd.request.Method; +import net.jonathangiles.tools.teenyhttpd.request.QueryParams; +import net.jonathangiles.tools.teenyhttpd.request.Request; +import net.jonathangiles.tools.teenyhttpd.response.FileResponse; +import net.jonathangiles.tools.teenyhttpd.response.Response; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; @@ -19,6 +20,10 @@ import java.util.concurrent.Executors; import java.util.function.Supplier; +/** + * The TeenyHttpd server itself - instantiating an instance of this class and calling 'start()' is all that is required + * to begin serving requests. + */ public class TeenyHttpd { private final int port; @@ -27,16 +32,45 @@ public class TeenyHttpd { private ExecutorService executorService; private boolean isRunning = false; + private File webroot; + + /** + * Creates a single-threaded server that will work on the given port, although the server does not start until + * 'stort()' is called. + * + * @param port The port for the server to listen to. + */ public TeenyHttpd(final int port) { this(port, Executors::newSingleThreadExecutor); } + /** + * Creates a server that will work on the given port, although the server does not start until 'stort()' is called. + * The executor supplier enables creating {@link ExecutorService} instances that can handle requests with a range + * of different threading models. + * + * @param port The port for the server to listen to. + * @param executorSupplier A {@link ExecutorService} instances that can handle requests with a range + * of different threading models. + */ public TeenyHttpd(final int port, final Supplier executorSupplier) { this.port = port; this.executorSupplier = executorSupplier; } + /** + * Sets the root directory to look for requested files. + * @param webroot A path on the local file system for serving requested files from. + */ + public void setWebroot(final File webroot) { + this.webroot = webroot; + } + + /** + * Starts the server instance. + */ public void start() { + System.out.println("TeenyHttp server started.\nListening for connections on port : " + port + " ...\n"); isRunning = true; executorService = executorSupplier.get(); @@ -50,13 +84,26 @@ public void start() { } } + /** + * Requests that the server instance stop serving requests. + */ public void stop() { isRunning = false; executorService.shutdown(); } + /** + * This method is called on every request, and allows for responses to be generated as appropriate. + * + * @param request The incoming request that must be responded to. + * @return The response that will be given to the requestor. + */ public Response serve(final Request request) { - return new FileResponse(request); + return new FileResponse(request) { + @Override protected File getFile(final String filename) { + return new File(webroot, filename); + } + }; } private void run(final Socket connect) { diff --git a/src/net/jonathangiles/teenyhttpd/request/Method.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/request/Method.java similarity index 68% rename from src/net/jonathangiles/teenyhttpd/request/Method.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/request/Method.java index dec3420..b3948d0 100644 --- a/src/net/jonathangiles/teenyhttpd/request/Method.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/request/Method.java @@ -1,4 +1,4 @@ -package net.jonathangiles.teenyhttpd.request; +package net.jonathangiles.tools.teenyhttpd.request; public enum Method { OPTIONS, diff --git a/src/net/jonathangiles/teenyhttpd/request/QueryParams.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/request/QueryParams.java similarity index 96% rename from src/net/jonathangiles/teenyhttpd/request/QueryParams.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/request/QueryParams.java index 538e133..f237183 100644 --- a/src/net/jonathangiles/teenyhttpd/request/QueryParams.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/request/QueryParams.java @@ -1,4 +1,4 @@ -package net.jonathangiles.teenyhttpd.request; +package net.jonathangiles.tools.teenyhttpd.request; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; diff --git a/src/net/jonathangiles/teenyhttpd/request/Request.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/request/Request.java similarity index 93% rename from src/net/jonathangiles/teenyhttpd/request/Request.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/request/Request.java index add8763..162b0b9 100644 --- a/src/net/jonathangiles/teenyhttpd/request/Request.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/request/Request.java @@ -1,6 +1,6 @@ -package net.jonathangiles.teenyhttpd.request; +package net.jonathangiles.tools.teenyhttpd.request; -import net.jonathangiles.teenyhttpd.Header; +import net.jonathangiles.tools.teenyhttpd.Header; import java.util.ArrayList; import java.util.LinkedHashMap; diff --git a/src/net/jonathangiles/teenyhttpd/response/ByteResponse.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/ByteResponse.java similarity index 92% rename from src/net/jonathangiles/teenyhttpd/response/ByteResponse.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/response/ByteResponse.java index 68661a3..febcfe3 100644 --- a/src/net/jonathangiles/teenyhttpd/response/ByteResponse.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/ByteResponse.java @@ -1,6 +1,6 @@ -package net.jonathangiles.teenyhttpd.response; +package net.jonathangiles.tools.teenyhttpd.response; -import net.jonathangiles.teenyhttpd.request.Request; +import net.jonathangiles.tools.teenyhttpd.request.Request; import java.io.BufferedOutputStream; import java.io.IOException; diff --git a/src/net/jonathangiles/teenyhttpd/response/FileResponse.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/FileResponse.java similarity index 72% rename from src/net/jonathangiles/teenyhttpd/response/FileResponse.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/response/FileResponse.java index 9aca3ae..fccea74 100644 --- a/src/net/jonathangiles/teenyhttpd/response/FileResponse.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/FileResponse.java @@ -1,21 +1,24 @@ -package net.jonathangiles.teenyhttpd.response; +package net.jonathangiles.tools.teenyhttpd.response; -import net.jonathangiles.teenyhttpd.request.Method; -import net.jonathangiles.teenyhttpd.request.Request; +import net.jonathangiles.tools.teenyhttpd.request.Method; +import net.jonathangiles.tools.teenyhttpd.request.Request; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; public class FileResponse extends Response { - static final File WEB_ROOT = new File("./wwwroot"); static final String DEFAULT_FILE = "index.html"; static final String FILE_NOT_FOUND = "404.html"; static final String METHOD_NOT_SUPPORTED = "not_supported.html"; + private static final ClassLoader loader = Thread.currentThread().getContextClassLoader(); + private static final File DEFAULT_WEB_ROOT = Paths.get(loader.getResource("webroot").getPath()).toFile(); + private final StatusCode statusCode; private final List headers; private File fileToReturn; @@ -83,7 +86,14 @@ private String getContentType(final File file) { } } - private File getFile(final String filename) { - return new File(WEB_ROOT, filename); + /** + * This method is called when the file is about to be loaded from the file system. Overriding it offers the + * opportunity of modifying where the file is retrieved from. + * + * @param filename The name of the file to be loaded, relative to the webroot (or otherwise). + * @return A File reference of the file being requested. + */ + protected File getFile(final String filename) { + return new File(DEFAULT_WEB_ROOT, filename); } } diff --git a/src/net/jonathangiles/teenyhttpd/response/Response.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/Response.java similarity index 81% rename from src/net/jonathangiles/teenyhttpd/response/Response.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/response/Response.java index e9b8402..9a5ecaf 100644 --- a/src/net/jonathangiles/teenyhttpd/response/Response.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/Response.java @@ -1,6 +1,6 @@ -package net.jonathangiles.teenyhttpd.response; +package net.jonathangiles.tools.teenyhttpd.response; -import net.jonathangiles.teenyhttpd.request.Request; +import net.jonathangiles.tools.teenyhttpd.request.Request; import java.io.BufferedOutputStream; import java.io.IOException; diff --git a/src/net/jonathangiles/teenyhttpd/response/StatusCode.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/StatusCode.java similarity index 97% rename from src/net/jonathangiles/teenyhttpd/response/StatusCode.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/response/StatusCode.java index f01fd4a..d513c5f 100644 --- a/src/net/jonathangiles/teenyhttpd/response/StatusCode.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/StatusCode.java @@ -1,4 +1,4 @@ -package net.jonathangiles.teenyhttpd.response; +package net.jonathangiles.tools.teenyhttpd.response; public enum StatusCode { // https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html diff --git a/src/net/jonathangiles/teenyhttpd/response/StringResponse.java b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/StringResponse.java similarity index 88% rename from src/net/jonathangiles/teenyhttpd/response/StringResponse.java rename to src/main/java/net/jonathangiles/tools/teenyhttpd/response/StringResponse.java index 2d1e8f0..c7b63de 100644 --- a/src/net/jonathangiles/teenyhttpd/response/StringResponse.java +++ b/src/main/java/net/jonathangiles/tools/teenyhttpd/response/StringResponse.java @@ -1,6 +1,6 @@ -package net.jonathangiles.teenyhttpd.response; +package net.jonathangiles.tools.teenyhttpd.response; -import net.jonathangiles.teenyhttpd.request.Request; +import net.jonathangiles.tools.teenyhttpd.request.Request; import java.util.List; diff --git a/src/main/resources/webroot/404.html b/src/main/resources/webroot/404.html new file mode 100644 index 0000000..7555ec1 --- /dev/null +++ b/src/main/resources/webroot/404.html @@ -0,0 +1 @@ +404 - File or Directory Not Found \ No newline at end of file diff --git a/src/main/resources/webroot/index.html b/src/main/resources/webroot/index.html new file mode 100644 index 0000000..f216474 --- /dev/null +++ b/src/main/resources/webroot/index.html @@ -0,0 +1 @@ +Hello from TeenyHttpd \ No newline at end of file diff --git a/src/main/resources/webroot/not_supported.html b/src/main/resources/webroot/not_supported.html new file mode 100644 index 0000000..1ce121c --- /dev/null +++ b/src/main/resources/webroot/not_supported.html @@ -0,0 +1 @@ +The requested operation is not supported. \ No newline at end of file diff --git a/src/net/jonathangiles/teenyhttpd/Main.java b/src/net/jonathangiles/teenyhttpd/Main.java deleted file mode 100644 index 5f6117a..0000000 --- a/src/net/jonathangiles/teenyhttpd/Main.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.jonathangiles.teenyhttpd; - -import net.jonathangiles.teenyhttpd.request.Request; -import net.jonathangiles.teenyhttpd.response.Response; -import net.jonathangiles.teenyhttpd.response.StatusCode; -import net.jonathangiles.teenyhttpd.response.StringResponse; - -public class Main { - - public static void main(String[] args) { - final int PORT = 8182; - - System.out.println("Server started.\nListening for connections on port : " + PORT + " ...\n"); - TeenyHttpd server = new TeenyHttpd(PORT) { - @Override - public Response serve(final Request request) { -// return new ByteResponse(request, StatusCode.OK, "Hello world!".getBytes()); - return new StringResponse(request, StatusCode.OK, "Hello!"); - } - }; - server.start(); - } -} diff --git a/src/test/java/net/jonathangiles/tools/teenyhttpd/TestServer.java b/src/test/java/net/jonathangiles/tools/teenyhttpd/TestServer.java new file mode 100644 index 0000000..b06cfd9 --- /dev/null +++ b/src/test/java/net/jonathangiles/tools/teenyhttpd/TestServer.java @@ -0,0 +1,19 @@ +package net.jonathangiles.tools.teenyhttpd; + +import net.jonathangiles.tools.teenyhttpd.request.Request; +import net.jonathangiles.tools.teenyhttpd.response.Response; +import net.jonathangiles.tools.teenyhttpd.response.StatusCode; +import net.jonathangiles.tools.teenyhttpd.response.StringResponse; + +import java.io.File; + +public class TestServer { + + public static void main(String[] args) { + final int PORT = 80; + + TeenyHttpd server = new TeenyHttpd(PORT); + server.setWebroot(new File("/Users/jonathan/Code/jonathangiles.net/output")); + server.start(); + } +} diff --git a/wwwroot/404.html b/wwwroot/404.html deleted file mode 100644 index d9b9af6..0000000 --- a/wwwroot/404.html +++ /dev/null @@ -1 +0,0 @@ -You've got a 404! :-( \ No newline at end of file diff --git a/wwwroot/index.html b/wwwroot/index.html deleted file mode 100644 index e141d3b..0000000 --- a/wwwroot/index.html +++ /dev/null @@ -1 +0,0 @@ -Hello from index.html \ No newline at end of file diff --git a/wwwroot/not_supported.html b/wwwroot/not_supported.html deleted file mode 100644 index bddc748..0000000 --- a/wwwroot/not_supported.html +++ /dev/null @@ -1 +0,0 @@ -This isn't supported friendo. \ No newline at end of file