TeenyHttpd is an extremely basic HTTP server, as well as an extremely basic application stack (similar to Spring). It is implemented in plain old Java with no runtime dependencies, making it lightweight and applicable in situations where a basic, small HTTP server and application stack is all that is required.
To make use of TeenyHttpd in your project, just add the following Maven dependency to your build:
<dependency>
<groupId>net.jonathangiles.tools</groupId>
<artifactId>teenyhttpd</artifactId>
<version>1.0.5</version>
</dependency>
The TeenyHttpd JavaDoc for the current code in this repo is available here. This may be different than the JavaDoc for the most recent release.
The following examples demonstrate how to use TeenyHttpd to serve static content, as well as how to programmatically define routes. Further down this page are examples of how to use the TeenyApplication application stack.
If you already have content you want to serve, you can simply place the latest teenyhttpd-x.y.z.jar
file in the same
directory as your content, and then run the following command:
java -jar teenyhttpd-x.y.z.jar
This will start a server on port 80, and will serve all content from the directory that the JAR file is located in.
There is a --help
parameter that will give some useful guidance on how to customize the server, which is shown below:
Usage: java -jar teenyhttpd.jar [options]
Options:
--port=80
The port to run the server on
--dir=.
The root directory to serve files from
--path=/
The path to serve files from (e.g. '/blah' for http://localhost/blah
-h, --help
Print this help message and exit
By default, with the configuration below, TeenyHttpd will serve files from within the /src/main/resources/webroot
directory, and they will be accessible from the root path (for example, a GET request for http://localhost/index.html
will look in the root of the webroot
directory for an index.html
file):
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addFileRoute("/");
server.start();
You can change the path to the webroot directory by passing in a File
object to the constructor:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addFileRoute("/", new File("/path/to/webroot"));
server.start();
You can also change the request path that is used to access the webroot directory:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addFileRoute("/files", new File("/path/to/webroot"));
server.start();
You can also programmatically define routes to serve. For example, the following code will serve a Hello world!
when a GET request is received for the /hello
path:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addStringRoute("/hello", request -> "Hello world!");
server.start();
The TeenyHttpd server supports all HTTP methods. There is an addGetRoute
method for the commonly-used GET method,
as well as a generic addRoute
method that can be used to specify any HTTP method. For example, the following code
will serve a Hello world!
when a GET request is received for the /hello
path, but will return a 404 for any other
HTTP method:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addGetRoute("/hello", request -> "Hello world!");
server.start();
For all other HTTP methods, you can use the addRoute
method. For example, the following code will serve a Hello world!
when a POST request is received for the /hello
path, but will return a 404 for any other HTTP method:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addRoute(HttpMethod.POST, "/hello", request -> "Hello world!");
server.start();
It is possible to specify path parameters in the path that is registered by using the :
character. For example,
the following code will serve a response of User ID: 123
when a GET request is received for the /user/123/details
server.addGetRoute("/user/:id/details", request -> {
String id = request.getPathParams().get("id");
return Response.create(StatusCode.OK, "User ID: " + id);
});
It is possible to access query parameters in the request. For example, the following code will print out all query
parameters that are received in a GET request on the route /QueryParams
:
server.addGetRoute("/QueryParams", request -> {
request.getQueryParams().forEach((key, value) -> System.out.println(key + " = " + value));
return StatusCode.OK.asResponse();
});
For example, calling the route above with the http://localhost/queryParams?test=true&foo=bar
URL will print out the following:
test = true
foo = bar
TeenyHttpd supports Server-Sent Events (SSE). To use this feature, you need to use the addServerSentEventRoute
method,
with a ServerSentEventHandler
. For example, the following code will send a message to the client every second:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.addServerSentEventRoute("/events", ServerSentEventHandler.create(sse -> new Thread(() -> {
int i = 0;
while (sse.hasActiveConnections()) {
sse.sendMessage(new ServerSentEventMessage("Message " + i++, "counter"));
threadSleep(1000);
}
}).start()));
server.start();
If more than one user connects to the same /events topic, they will share the same state, each getting the same value
for i
at the same time. If you want to customise the response per user (for example, based on a path parameter or
query parameter), you can use the message generator feature:
ServerSentEventHandler sse = ServerSentEventHandler.create((ServerSentEventHandler _sse) -> {
// start a thread and send messages to the client(s)
new Thread(() -> {
// all clients share the same integer value, but they get a custom message based
// on the path parameter for :username
AtomicInteger i = new AtomicInteger(0);
while (_sse.hasActiveConnections()) {
_sse.sendMessage(client -> {
String username = client.getPathParams().get("username");
return new ServerSentEventMessage("Hello " + username + " - " + i, "counter");
});
i.incrementAndGet();
threadSleep(1000);
}
}).start();
});
server.addServerSentEventRoute("/sse/:username", sse);
The above samples assume that you start a thread when there are active connections, to send messages to connected
clients at a regular interval. Another approach is to just have a ServerSentEventHandler that sends messages to
connected clients when a message is received. For example, the following code will send a message to all clients
that are connected to the /messages
topic, when a message is posted to /message
:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
ServerSentEventHandler chatMessagesEventHandler = ServerSentEventHandler.create();
server.addServerSentEventRoute("/messages", chatMessagesEventHandler);
server.addRoute(Method.POST, "/message", request -> {
String message = request.getQueryParams().get("message");
if (message != null && !message.isEmpty()) {
chatMessagesEventHandler.sendMessage(message);
}
return StatusCode.OK.asResponse();
});
For a complete example, check out the ChatServer demo application, that demonstrates how to use Server-Sent Events to create a simple chat server.
You stop a running instance as follows:
final int PORT = 80;
TeenyHttpd server = new TeenyHttpd(PORT);
server.start();
// some time later...
server.stop();
What was shown above is the 'low-level' APIs of TeenyHttpd. However, TeenyHttpd also includes a simple application stack called TeenyApplication. This application stack is similar to Spring, but is much simpler and lighter weight.
A basic application starts as follows:
public class RestApp {
public static void main(String[] args) {
System.setProperty("server.port", "80");
TeenyApplication.start(RestApp.class);
}
}
Of course, this isn't all that useful, as we have not defined any routes. Let's update the code above to do that now:
public class RestApp {
public static void main(String[] args) {
System.setProperty("server.port", "80");
TeenyApplication.start(RestApp.class);
}
@Post("/message")
public void message(@QueryParam("message") String message) {
// do something with the message that was posted to the /message route
}
@Get("/products")
public Collection<Product> get() {
// return a collection of products
}
@Get("/product/:id")
public TypedResponse<Product> getProduct(@PathParam("id") int id) {
// Do something like this, by taking the 'id' path parameter and looking up a product
Product product = productMap.get(id);
if (product != null) return TypedResponse.ok(product);
return TypedResponse.notFound();
}
}
As you might expect, there are annotations for many of the common use cases:
- HTTP Methods:
@Get
,@Post
,@Put
,@Delete
,@Patch
- Path Parameters:
@PathParam
- Query Parameters:
@QueryParam
- Headers:
@RequestHeader
- Request Body:
@RequestBody
- Server-Sent Events:
@ServerEvent
Simply define the handler and give it a name, if no name is specified then the name of the method will be used instead.
@ServerEvent(value = "/messages", name = "messages")
public ServerSentEventHandler chatMessages() {
return ServerSentEventHandler.create();
}
Use it directly anywhere:
@Post("/message")
public void message(@QueryParam("message") String message,
@EventHandler("messages") ServerSentEventHandler chatMessagesEventHandler) {
chatMessagesEventHandler.sendMessage(message);
}
Similar to the ChatServer demo application linked above (which simply uses TeenyHttpd), there is also one built using annotations and TeenyApplication.
TeenyApplication provides support for custom message converters. These converters are used to handle specific content types as specified by the user. For example the following code handles requests of content type application/json
public class GsonMessageConverter implements MessageConverter {
final Gson gson = new Gson();
@Override
public String getContentType() {
return "application/json";
}
@Override
public void write(Object value, BufferedOutputStream dataOut) throws IOException {
if (value instanceof String) {
dataOut.write(((String) value).getBytes());
return;
}
dataOut.write(gson.toJson(value).getBytes());
}
@Override
public Object read(String value, Type type) {
if (String.class.isAssignableFrom((Class<?>) type)) {
return value;
}
return gson.fromJson(value, type);
}
}
Similar to Spring, @Configuration
is used to provide configurations. To specify a custom MessageConverter, you can define your configuration as shown below, within your application:
@Configuration
public GsonMessageConverter getGsonConverter() {
return new GsonMessageConverter();
}
TeenyHttpd includes a simple JSON library called TeenyJson. It is a simple, lightweight JSON library that is used to convert JSON strings to Java objects, and vice versa. It is not as feature-rich as other JSON libraries, but it is lightweight and easy to use.
Person person = new TeenyJson().readValue(json, Person.class);
Similar to jackson to parse a ambiguous property, you can use the @JsonDeserialize
annotation:
@JsonDeserialize(contentAs = ObjectC.class)
public void setList(List<? extends ObjectC> list) {
this.list = list;
}
@JsonDeserialize(as = ObjectC.class)
public void setC(ObjectC c) {
this.c = c;
}
Person person = new Person("John", 30, null);
String json = new TeenyJson().writeValueAsString(person);
To give a property an alias in the JSON, you can use the @JsonProperty
annotation:
@JsonAlias("bestSongs")
public Set<String> getFavoriteSongs() {
return favoriteSongs;
}
To ignore a property in the JSON, you can use the @JsonIgnore
annotation:
@JsonIgnore
public String getSecret() {
return secret;
}
To ignore all properties that are null, you can use the @JsonIncludeNonNull
annotation:
@JsonIncludeNonNull
public class Person {
// ...
}
You can also customize the deserialization and serialization process by specifying a custom serializer or deserializer:
new TeenyJson()
.registerSerializer(String.class, String::toUpperCase)
.registerParser(String.class, (value) -> value.toString().toLowerCase());
Releases are performed using mvn clean deploy -Prelease
.