diff --git a/docs/README.md b/docs/README.md index da0edba..fbdc89f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,10 +6,10 @@ The documentation for Handy-Httpd is built using [Vuepress](https://vuepress.vue To help improve the consistency of the documentation, we roll our own D-documentation via the `build-ddoc.d` script. It builds all documentation for Handy-Httpd, and places it into `docs/src/.vuepress/public/ddoc/`, so it can be served as static content along with the rest of the documentation site. -To link to a D symbol in a documentation markdown file, use the `ddoc-link` plugin by writing a specially-formatted link that starts with `ddoc-`, followed by the fully-qualified symbol name. In the example below, we create a link to the `PathDelegatingHandler` class. +To link to a D symbol in a documentation markdown file, use the `ddoc-link` plugin by writing a specially-formatted link that starts with `ddoc-`, followed by the fully-qualified symbol name. In the example below, we create a link to the `PathHandler` class. ```markdown -Click on the [PathDelegatingHandler](ddoc-handy_httpd.handlers.path_delegating_handler.PathDelegatingHandler) link to learn more! +Click on the [PathHandler](ddoc-handy_httpd.handlers.path_handler.PathHandler) link to learn more! ``` ## Deployment diff --git a/docs/src/.vuepress/config.js b/docs/src/.vuepress/config.js index eb96c9b..ea11b93 100755 --- a/docs/src/.vuepress/config.js +++ b/docs/src/.vuepress/config.js @@ -68,7 +68,9 @@ module.exports = { 'handling-exceptions', 'logging', 'configuration', - 'testing' + 'testing', + 'deployment', + 'architecture' ] }, { diff --git a/docs/src/guide/README.md b/docs/src/guide/README.md index 1609100..5f6ce86 100755 --- a/docs/src/guide/README.md +++ b/docs/src/guide/README.md @@ -15,12 +15,12 @@ import handy_httpd; void main() { auto server = new HttpServer((ref ctx) { - ctx.response.setStatus(HttpStatus.OK); + ctx.response.writeBodyString("Hello world!"); }); server.start(); } ``` -The above code creates and starts an HTTP server that simple responds with a `200 OK` response to any request. The full example is available [on GitHub](https://github.com/andrewlalis/handy-httpd/tree/main/examples/single-file-server). +TIf you open your browser to [http://localhost:8080](http://localhost:8080), you should see the text, "Hello world!". The full example is available [on GitHub](https://github.com/andrewlalis/handy-httpd/tree/main/examples/single-file-server). diff --git a/docs/src/guide/architecture.md b/docs/src/guide/architecture.md new file mode 100644 index 0000000..a0a2824 --- /dev/null +++ b/docs/src/guide/architecture.md @@ -0,0 +1,23 @@ +# Server Architecture + +**⚠️ This page is a work-in-progress.** + +This page will go into detail about the design of Handy-Httpd, both in terms of abstract concepts, and implementation details. It's usually not necessary to understand the contents of this page in order to effectively use Handy-Httpd, but it can help get the most out of your server, and will probably help if you'd like to contribute to this framework in any way. + +## Project Structure + +The Handy-Httpd project is organized into a main directories: + +- `/source` contains all the source code for the project. +- `/examples` contains all the examples that are referenced anywhere throughout this documentation, and any others. +- `/integration-tests` contains all integration tests that involve their own unique testing setup. +- `/docs` contains this documentation site. +- `/design` contains any design documents. + +The majority of this architecture document will cover the project's source code, as the other directories are fairly straightforward and self-explanatory. + +Within `/source`, Handy-Httpd is divided into modules according to the principle of single-responsibility. Generally, every module in Handy-Httpd has one responsibility; it offers one key ingredient to the project. + +### handy_httpd.server + +The server module defines the main `HttpServer` class that acts as the foundation for the entire framework. The server runs a main loop that accepts new clients, and passes them off to a request handler. diff --git a/docs/src/guide/configuration.md b/docs/src/guide/configuration.md index 198e947..82472df 100644 --- a/docs/src/guide/configuration.md +++ b/docs/src/guide/configuration.md @@ -2,7 +2,7 @@ Handy-Httpd servers are highly-configurable thanks to a simple [ServerConfig](ddoc-handy_httpd.components.config.ServerConfig) struct that's passed to the server on initialization. On this page, we'll cover all of the available configuration options, what they mean, and how changing them can affect your server. -> Note: Configuration options **cannot** be changed during runtime. +> ⚠️ Configuration options **cannot** be changed at runtime. ## Socket Options @@ -70,7 +70,7 @@ Note that internally, the queue is implemented with a fixed-size array. | Type | Default Value | |--- |--- | | `size_t` | `25` | -The number of worker threads to use to process incoming requests. Increasing this number can improve performance for servers where the bottleneck is in the number of concurrent requests. +The number of worker threads to use to process incoming requests. Increasing this number can improve performance for servers where the bottleneck is in the number of concurrent requests. Each worker thread pre-allocates its own receive buffer (whose size is defined by [receiveBufferSize](#receivebuffersize)) as well as its own thread stack and other variables, so keep in mind that while more workers may increase performance in certain situations, it will play a large role in your app's memory consumption. ### `workerPoolManagerIntervalMs` | Type | Default Value | diff --git a/docs/src/guide/deployment.md b/docs/src/guide/deployment.md new file mode 100644 index 0000000..e8352ec --- /dev/null +++ b/docs/src/guide/deployment.md @@ -0,0 +1,12 @@ +# Deployment Tips + +Now that you've gone and done the hard work of making your very own HTTP server, it's time to go and share it with the world. + +This page contains a list of tips that should help you get the most out of your server when it's deployed somewhere. + +1. Use [LDC](https://github.com/ldc-developers/ldc) to compile the release version of your application. Compile times with LDC are a little longer than with DMD, but you get much better performance, and better compatibility with various OS/arch combinations because the project is based on LLVM. +2. Tune the number of worker threads according to your needs. For small services, you can usually get by with just a few workers, and reducing the number of worker threads greatly cuts down on the memory usage of your application. See the [configuration page](configuration.md#workerpoolsize) for more details. +3. Use a [reverse-proxy](https://en.wikipedia.org/wiki/Reverse_proxy) like [nginx](https://www.nginx.com/) to send traffic to Handy-Httpd, instead of having Handy-Httpd handle traffic directly. This is because something like nginx is highly-optimized for maximum performance, and already handles encryption (HTTPS via SSL/TLS). This way, you (and me, the developer of Handy-Httpd) can focus on the features that improve the quality of the application, instead of worrying about problems others have solved. Also, consider serving static content via reverse-proxy for best performance. +4. Set [enableWebSockets](configuration.md#enablewebsockets) to `false` to save some memory. If websockets are enabled, an extra thread is started to manage the websocket messages, separate from the main worker pool. If you don't need websockets and you want to optimize your memory usage, disable them. +5. Avoid long-duration operations while handling requests. This locks up a worker thread for the duration of the request, so it's best to respond right away (also for the user's experience) and then do additonal processing asynchronously. +6. Try to avoid throwing exceptions from your request handlers, where possible. Handle exceptions as soon as they crop up, and set the appropriate HTTP response code instead of relying on Handy-Httpd's exception handling. Exceptions are quite costly in terms of performance. diff --git a/docs/src/guide/handlers/path-delegating-handler.md b/docs/src/guide/handlers/path-delegating-handler.md index 6d1dfd8..e1a4f82 100644 --- a/docs/src/guide/handlers/path-delegating-handler.md +++ b/docs/src/guide/handlers/path-delegating-handler.md @@ -1,6 +1,6 @@ # Path Delegating Handler -**This handler is deprecated in favor of the [PathHandler](./path-handler.md).** +**⚠️ This handler is deprecated in favor of the [PathHandler](./path-handler.md). It will no longer receive any updates, and you should consider switching over to the PathHandler for improved performance and support.** As you may have read in [Handling Requests](./handling-requests.md), Handy-Httpd offers a pre-made [PathDelegatingHandler](ddoc-handy_httpd.handlers.path_delegating_handler.PathDelegatingHandler) that can match HTTP methods and URLs to specific handlers; a common use case for web servers. diff --git a/docs/src/guide/handling-exceptions.md b/docs/src/guide/handling-exceptions.md index f350193..e28435c 100644 --- a/docs/src/guide/handling-exceptions.md +++ b/docs/src/guide/handling-exceptions.md @@ -29,4 +29,4 @@ It's rare, but you may encounter fatal errors while handling a request, to the t Handy-Httpd will try and resurrect dead workers periodically, and it'll warn you each time it does, but it's recommended that you try and address the underlying cause instead of relying on brute-force thread resurrection. -> Note that D programmers are discouraged from catching `Error` or any subclass of it. Most errors are a symptom of unsafe code. +> ⚠️ D programmers are discouraged from catching `Error` or any subclass of it. Most errors are a symptom of unsafe code. diff --git a/docs/src/guide/handling-requests.md b/docs/src/guide/handling-requests.md index 859c81e..dadb9bf 100644 --- a/docs/src/guide/handling-requests.md +++ b/docs/src/guide/handling-requests.md @@ -53,7 +53,7 @@ void handle(ref HttpRequestContext ctx) { #### Path Parameters -If a request is handled by a [PathHandler](ddoc-handy_httpd.handlers.path_handler.PathHandler), 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) *(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. 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: @@ -86,9 +86,9 @@ 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. | -> Note: 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. +> ⚠️ 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. -Sometimes, the body content of a request may be encoded if the `Transfer-Encoding=chunked` header is provided. In that case, Handy-Httpd will automatically wrap the underlying input stream with one that reads the chunked encoding and provides you with the raw data. In short, you Handy-Httpd will manage chunked encoded requests for you. However, it *will not* automatically apply chunked encoding to your server's responses. +Sometimes, the body content of a request will be encoded if the `Transfer-Encoding=chunked` header is provided. In that case, Handy-Httpd will automatically wrap the underlying input stream with one that reads the chunked encoding and provides you with the raw data. In short, Handy-Httpd will manage chunked-encoded requests for you. However, it *will not* automatically apply chunked encoding to your server's responses. If you'd like to send chunked-encoded responses, consider using the [ChunkedEncodingOutputStream](https://github.com/andrewlalis/streams/blob/main/source/streams/types/chunked.d#L127) from the [streams](https://github.com/andrewlalis/streams) library to do so, since Handy-Httpd already includes it as a dependency. ### Response @@ -113,7 +113,7 @@ The first thing you should do when responding to a request is to send a status, You can add headers via [addHeader(string name, string value)](ddoc-handy_httpd.components.response.HttpResponse.addHeader). -> Note that setting the status and headers is only possible before they've been flushed, i.e. before any body content is sent. +> ⚠️ Setting the status and headers is only possible before they've been flushed, i.e. before any body content is sent. #### Writing the Response Body @@ -121,7 +121,7 @@ After setting a status and headers, you can write the response body. This can be |
Method
| Description | |--- |--- | -| [writeBody](ddoc-handy_httpd.components.response.HttpResponse.writeBodyRange) | Writes the response body using data taken from an input stream of bytes. The size and content type must be explicitly specified before anything is written. | +| [writeBody](ddoc-handy_httpd.components.response.HttpResponse.writeBody) | Writes the response body using data taken from an input stream of bytes. The size and content type must be explicitly specified before anything is written. | | [writeBodyBytes](ddoc-handy_httpd.components.response.HttpResponse.writeBodyBytes) | Writes the given bytes to the response body. You can optionally specify a content type, or it'll default to `application/octet-stream`. | | [writeBodyString](ddoc-handy_httpd.components.response.HttpResponse.writeBodyString) | Writes the given text to the response body. You can optionally specify a content type, or it'll default to `text/plain; charset=utf-8`. | diff --git a/examples/single-file-server/hello.d b/examples/single-file-server/hello.d index bf4132d..dafed69 100755 --- a/examples/single-file-server/hello.d +++ b/examples/single-file-server/hello.d @@ -6,18 +6,25 @@ import handy_httpd; import slf4d; void main() { + // First we set up our server's configuration, using mostly default values, + // but we'll tweak a few settings, and to be extra clear, we explicitly set + // the port to 8080 (even though that's the default). ServerConfig cfg = ServerConfig.defaultValues(); cfg.workerPoolSize = 5; cfg.port = 8080; + + // Now we construct a new instance of the HttpServer class, and provide it + // a lambda function to use when handling requests. new HttpServer((ref ctx) { + // We can inspect the request's URL directly like so: if (ctx.request.url == "/stop") { ctx.response.writeBodyString("Shutting down the server."); - ctx.server.stop(); + ctx.server.stop(); // Calling stop will gracefully shutdown the server. } else if (ctx.request.url == "/hello") { infoF!"Responding to request: %s"(ctx.request.url); ctx.response.writeBodyString("Hello world!"); } else { ctx.response.setStatus(HttpStatus.NOT_FOUND); } - }, cfg).start(); + }, cfg).start(); // Calling start actually start's the server's main loop. }