Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking Change: Version 8.0.0 #27

Merged
merged 23 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bb9d27a
Refactored code to use multivaluemap for query params and headers.
andrewlalis Jan 19, 2024
60532c8
Added comment
andrewlalis Jan 22, 2024
14c40c5
Merged Steven's url reserved characters fix.
andrewlalis Jan 22, 2024
3f2b05a
Fixed stuff.
andrewlalis Jan 22, 2024
ac9ba96
Fixed stuff.
andrewlalis Jan 22, 2024
fce9882
Added logging test harness to readBody() unit test, to hide warning f…
andrewlalis Jan 22, 2024
70d3197
Fixed constness of multivalue map.
andrewlalis Jan 22, 2024
4ef2c76
Added better description for remove()
andrewlalis Jan 22, 2024
a20c57b
Added public imports to multipart module in request module, used deco…
andrewlalis Jan 22, 2024
81ae215
Refactored examples.
andrewlalis Jan 23, 2024
880b162
Fix unittest for multivalue map
andrewlalis Jan 24, 2024
ed944be
Updated examples workflow.
andrewlalis Jan 24, 2024
aedf966
Added path-handler example.
andrewlalis Jan 24, 2024
744c6c9
Simplified testing with runner-based test.
andrewlalis Jan 24, 2024
e01e0b9
Fixed runner clean command, and added more docs to path-handler example.
andrewlalis Jan 24, 2024
a5839ab
Removed path_delegating_handler module.
andrewlalis Jan 24, 2024
c29b5a8
Update documentation.
andrewlalis Jan 24, 2024
d95fde9
Added secondary worker pool implementation, extracted request process…
andrewlalis Jan 24, 2024
fc6a1c4
Refactored worker pool implementation.
andrewlalis Jan 24, 2024
3deb690
Added blocking worker pool implementation just for fun.
andrewlalis Jan 25, 2024
4bce0b5
Fix long text line.
andrewlalis Jan 25, 2024
ef3537a
Simplified the readme.
andrewlalis Jan 25, 2024
7ef9338
Added shields to readme.
andrewlalis Jan 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,10 @@ jobs:
with:
compiler: ${{ matrix.compiler }}

- name: "Example: file-upload"
working-directory: examples/file-upload
run: dub -q build --single server.d

- name: "Example: handler-testing"
working-directory: examples/handler-testing
run: dub test --single handler.d

- name: "Example: multiple-handlers"
working-directory: examples/multiple-handlers
run: dub -q build

- name: "Example: single-file-server"
working-directory: examples/single-file-server
run: dub -q build --single hello.d

- name: "Example: static-content-server"
working-directory: examples/static-content-server
run: dub -q build --single content_server.d
- name: Test Examples
working-directory: examples
run: rdmd runner.d test

- name: Clean Examples
working-directory: examples
run: rdmd runner.d clean
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# handy-httpd

![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/andrewlalis/handy-httpd/testing.yml?branch=main&style=flat-square&logo=github&label=tests)
![GitHub issues](https://img.shields.io/github/issues/andrewlalis/handy-httpd?style=flat-square)
![DUB Downloads](https://img.shields.io/dub/dt/handy-httpd?style=flat-square&logo=d&logoColor=%23B03931)
![GitHub Tag](https://img.shields.io/github/v/tag/andrewlalis/handy-httpd?style=flat-square&label=version&color=%23B03931)

An extremely lightweight HTTP server for the [D programming language](https://dlang.org/).

## Features
- HTTP/1.1
- [Web Sockets](https://andrewlalis.github.io/handy-httpd/guide/handlers/websocket-handler.html)
- Worker pool for request handling
- [Simple configuration](https://andrewlalis.github.io/handy-httpd/guide/configuration.html)
- High performance
- High performance with interchangeable request processors
- Beginner friendly
- Extensible with custom handlers, exception handlers, and filters
- [Well-documented](https://andrewlalis.github.io/handy-httpd/)
Expand All @@ -17,7 +21,7 @@ An extremely lightweight HTTP server for the [D programming language](https://dl
- Apply filters before and after handling requests with the [FilteredHandler](https://andrewlalis.github.io/handy-httpd/guide/handlers/filtered-handler.html)
- Handle complex URL paths, including path parameters and wildcards, with the [PathHandler](https://andrewlalis.github.io/handy-httpd/guide/handlers/path-handler.html)

## Important Links
## Links
- [Documentation](https://andrewlalis.github.io/handy-httpd/)
- [Examples](https://github.com/andrewlalis/handy-httpd/tree/main/examples)
- [Dub Package Page](https://code.dlang.org/packages/handy-httpd)
Expand All @@ -30,11 +34,7 @@ import handy_httpd;

void main() {
new HttpServer((ref ctx) {
if (ctx.request.url == "/hello") {
ctx.response.writeBodyString("Hello world!");
} else {
ctx.response.setStatus(HttpStatus.NOT_FOUND);
}
ctx.response.writeBodyString("Hello world!");
}).start();
}
```
2 changes: 1 addition & 1 deletion docs/src/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,5 @@ This interval shouldn't need to be very small, unless a high percentage of your
### `enableWebSockets`
| Type | Default Value |
|--- |--- |
| `bool` | `true` |
| `bool` | `false` |
Whether to enable websocket functionality for the server. If set to true, starting the server will also start an additional thread that handles websocket traffic in a nonblocking fashion.
78 changes: 0 additions & 78 deletions docs/src/guide/handlers/path-delegating-handler.md

This file was deleted.

10 changes: 4 additions & 6 deletions docs/src/guide/handling-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ Each request also contains a `remoteAddress`, which contains the remote socket a

#### Headers and Parameters

The request's headers are available via the `headers` associative array, where each header name is mapped to a single string value. There are no guarantees about which headers may be present, nor their type. It's generally up to the handler to figure out how to deal with them.
The request's headers are available via the [headers](ddoc-handy_httpd.components.request.HttpRequest.headers) *[multivalued map](ddoc-handy_httpd.components.multivalue_map.MultiValueMap)*, where each header name is mapped to one or more string values. There are no guarantees about which headers may be present, and it's generally up to the handler to do this.

Similarly, the request's `queryParams` is a list of [QueryParam](ddoc-handy_httpd.components.form_urlencoded.QueryParam)s that were parsed from the URL. For example, in `http://example.com/?x=5`, Handy-Httpd would provide a request whose params are `[QueryParam("x", "5")]`. Like the headers, no guarantee is made about what params are present, or what type they are. However, you can use the [getParamAs](ddoc-handy_httpd.components.request.HttpRequest.getParamAs) function as a safe way to get a parameter as a specified type, or fallback to a default.
Similarly, the request's [queryParams](ddoc-handy_httpd.components.request.HttpRequest.queryParams) is a *[multivalued map](ddoc-handy_httpd.components.multivalue_map.MultiValueMap)* containing all query parameters that were parsed from the URL. For example, in `http://example.com/?x=5`, Handy-Httpd would provide a request whose params are `["x": ["5"]]`. Like the headers, no guarantee is made about what params are present, or what type they are. However, you can use the [getParamAs](ddoc-handy_httpd.components.request.HttpRequest.getParamAs) function as a safe way to get a parameter as a specified type, or fallback to a default.

```d
void handle(ref HttpRequestContext ctx) {
Expand All @@ -53,11 +53,9 @@ void handle(ref HttpRequestContext ctx) {

In the above snippet, if the user requests `https://your-site.com/search?page=24`, then `page` will be set to 24. However, if a user requests `https://your-site.com/search?page=blah`, or doesn't provide a page at all, `page` will be set to `1`.

> ⚠️ Previously, query parameters were accessed through [HttpRequest.params](ddoc-handy_httpd.components.request.HttpRequest.params), but this is deprecated in favor of the query params. This is because the old params implementation didn't accurately follow the official specification; specifically, there can be multiple query parameters with the same name, but different values. An associative string array can't represent that type of data.

#### Path Parameters

If a request is handled by a [PathHandler](handlers/path-handler.md) *(or the now-deprecated [PathDelegatingHandler](handlers/path-delegating-handler.md))*, then its `pathParams` associative array will be populated with any path parameters that were parsed from the URL.
If a request is handled by a [PathHandler](handlers/path-handler.md), then its [pathParams](ddoc-handy_httpd.components.request.HttpRequest.pathParams) associative array will be populated with any path parameters that were parsed from the URL.

The easiest way to understand this behavior is through an example. Suppose we define our top-level PathHandler with the following mapping, so that a `userSettingsHandler` will handle requests to that endpoint:

Expand Down Expand Up @@ -91,7 +89,7 @@ Some requests that your server receives may include a body, which is any content
| [readBodyAsJson](ddoc-handy_httpd.components.request.HttpRequest.readBodyAsJson) | Reads the entire request body as a [JSONValue](https://dlang.org/phobos/std_json.html#.JSONValue). |
| [readBodyToFile](ddoc-handy_httpd.components.request.HttpRequest.readBodyToFile) | Reads the entire request body and writes it to a given file. |

Additionally, you can import the [multipart](ddoc-handy_httpd.components.multipart) module to expose the [readBodyAsMultipartFormData](ddoc-handy_httpd.components.multipart.readBodyAsMultipartFormData) function, which is commonly used for handling file uploads.
Additionally, you can use the [multipart](ddoc-handy_httpd.components.multipart) module's [readBodyAsMultipartFormData](ddoc-handy_httpd.components.multipart.readBodyAsMultipartFormData) function, which is commonly used for handling file uploads.

> ⚠️ While Handy-Httpd doesn't force you to limit the amount of data you read, please be careful when reading an entire request body at once, like with `readBodyAsString`. This will load the entire request body into memory, and **will** crash your program if the body is too large.

Expand Down
11 changes: 8 additions & 3 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
single-file-server/hello
static-content-server/content_server
*.log
# Ignore all artifacts that might be generated.
*

# Except D source files and other stuff that's not auto-generated.
!*.d
!*.md
!*.sh
!*.json
22 changes: 21 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,24 @@ Inside this directory, you'll find a series of examples which show how Handy-htt

Single-file scripts annotated with a shebang can be run in a unix command-line with `./<script.d>`. On other systems, you should be able to do `dub run --single <script.d>`.

Otherwise, you'll find a `dub.json` file, indicating a dub project, so you can just do `dub run` for those cases.
| Example | Description |
|---|---|
| hello-world | Basic example which shows how to configure and start a server. |
| using-headers | Shows you how to inspect, list, and get the headers from a request. |
| path-handler | Shows you how to use the `PathHandler` to route requests to handlers based on their path, and consume path variables from the request's URL. |
| file-upload | Demonstrates file uploads using multipart/form-data encoding. |
| handler-testing | Shows how you can write unit tests for your request handler functions or classes. |
| static-content-server | Shows how you can use the `FileResolvingHandler` to serve static files from a directory. |
| websocket | Shows how you can enable websocket support and use the `WebSocketHandler` to send and receive websocket messages. |


## Runner Script

A runner script is provided for your convenience. Compile it with `dmd runner.d`, or run directly with `./runner.d`. You can:
- List all examples: `./runner list`
- Clean the examples directory and remove compiled binaries: `./runner clean`
- Select an example to run from a list: `./runner run`
- Run a specific example: `./runner run <example>`
- Run all examples at the same time: `./runner run all`

> Note: When running all examples at the same time, servers will each be given a different port number, starting at 8080.
68 changes: 68 additions & 0 deletions examples/file-upload.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env dub
/+ dub.sdl:
dependency "handy-httpd" path="../"
+/

/**
* This example shows how you can manage basic file-upload mechanics using
* an HTML form and multipart/form-data encoding. In this example, we show a
* simple form, and when the user uploads some files, a summary of the files
* is shown.
*/
module examples.file_upload;

import handy_httpd;
import slf4d;
import handy_httpd.handlers.path_handler;

const indexContent = `
<html>
<body>
<h4>Upload a file!</h4>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="file" name="file2">
<input type="file" multiple name="other files">
<input type="submit" value="Submit"/>
</form>
</body>
</html>
`;

void main(string[] args) {
ServerConfig cfg = ServerConfig.defaultValues;
if (args.length > 1) {
import std.conv;
cfg.port = args[1].to!ushort;
}
new HttpServer(new PathHandler()
.addMapping(Method.GET, "/**", &serveIndex) // Show the index content to any request.
.addMapping(Method.POST, "/upload", &handleUpload), // And handle uploads only on POST requests to /upload.
cfg
).start();
}

void serveIndex(ref HttpRequestContext ctx) {
ctx.response.writeBodyString(indexContent, "text/html; charset=utf-8");
}

void handleUpload(ref HttpRequestContext ctx) {
MultipartFormData data = readBodyAsMultipartFormData(ctx.request);
string response = "File Upload Summary:\n\n";
foreach (i, MultipartElement element; data.elements) {
import std.format;
string filename = element.filename.isNull ? "NULL" : element.filename.get();
response ~= format!
"Multipart Element %d of %d:\n\tFilename: %s\n\tSize: %d\n"
(
i + 1,
data.elements.length,
filename,
element.content.length
);
foreach (string header, string value; element.headers) {
response ~= format!"\t\tHeader \"%s\": \"%s\"\n"(header, value);
}
}
ctx.response.writeBodyString(response);
}
2 changes: 0 additions & 2 deletions examples/file-upload/.gitignore

This file was deleted.

5 changes: 0 additions & 5 deletions examples/file-upload/README.md

This file was deleted.

54 changes: 0 additions & 54 deletions examples/file-upload/server.d

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/+ dub.sdl:
dependency "handy-httpd" path="../../"
dependency "handy-httpd" path="../"
+/
module handler;

/**
* An example that shows how you can unit-test a request handler. You can run
* this example by calling "dub test --single handler-testing.d".
*/
module examples.handler_testing;

import handy_httpd;
import std.conv : to, ConvException;
Expand Down
8 changes: 0 additions & 8 deletions examples/handler-testing/README.md

This file was deleted.

3 changes: 0 additions & 3 deletions examples/handler-testing/run-tests.sh

This file was deleted.

Loading
Loading