Skip to content

Commit

Permalink
Getting the code into better shape / maven ready
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanGiles committed May 28, 2020
1 parent 4373da8 commit 55941c4
Show file tree
Hide file tree
Showing 21 changed files with 283 additions and 51 deletions.
21 changes: 21 additions & 0 deletions 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.
93 changes: 93 additions & 0 deletions pom.xml
@@ -0,0 +1,93 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.jonathangiles.tools</groupId>
<artifactId>teenyhttpd</artifactId>
<version>1.0.0-SNAPSHOT</version>

<name>TeenyHttpd</name>
<description>TeenyHttpd is an extremely basic HTTP server.</description>
<url>https://github.com/JonathanGiles/TeenyHttpd</url>

<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<scm>
<connection>scm:git:git://github.com/JonathanGiles/TeenyHttpd.git</connection>
<developerConnection>scm:git:git@github.com:JonathanGiles/TeenyHttpd.git</developerConnection>
<url>https://github.com/JonathanGiles/TeenyHttpd</url>
<tag>HEAD</tag>
</scm>

<issueManagement>
<system>GitHub</system>
<url>https://github.com/JonathanGiles/TeenyHttpd/issues</url>
</issueManagement>

<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<id>jonathangiles</id>
<name>Jonathan Giles</name>
<url>http://jonathangiles.net</url>
</developer>
</developers>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
65 changes: 65 additions & 0 deletions 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`.
@@ -1,4 +1,4 @@
package net.jonathangiles.teenyhttpd;
package net.jonathangiles.tools.teenyhttpd;

public class Header {
private final String keyValue;
Expand Down
@@ -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;
Expand All @@ -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;
Expand All @@ -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<? extends ExecutorService> 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();

Expand All @@ -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) {
Expand Down
@@ -1,4 +1,4 @@
package net.jonathangiles.teenyhttpd.request;
package net.jonathangiles.tools.teenyhttpd.request;

public enum Method {
OPTIONS,
Expand Down
@@ -1,4 +1,4 @@
package net.jonathangiles.teenyhttpd.request;
package net.jonathangiles.tools.teenyhttpd.request;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
Expand Down
@@ -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;
Expand Down
@@ -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;
Expand Down
@@ -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<String> headers;
private File fileToReturn;
Expand Down Expand Up @@ -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);
}
}
@@ -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;
Expand Down
@@ -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
Expand Down
@@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/webroot/404.html
@@ -0,0 +1 @@
404 - File or Directory Not Found
1 change: 1 addition & 0 deletions src/main/resources/webroot/index.html
@@ -0,0 +1 @@
Hello from TeenyHttpd

0 comments on commit 55941c4

Please sign in to comment.