index : static-web-server.git

ascending towards madness

author Gaƫtan Lehmann <glehmann@users.noreply.github.com> 2023-07-11 12:20:58.0 +00:00:00
committer GitHub <noreply@github.com> 2023-07-11 12:20:58.0 +00:00:00
commit
b42214b95be266a135d78ce2e4d7481925e75f6a [patch]
tree
c2fbb8afb4a33f8ad64671de519f5b990eb64ca3
parent
3a47ef6aed372e81fc6defc3b02d19879fe8a0fc
download
b42214b95be266a135d78ce2e4d7481925e75f6a.tar.gz

feat: optional `/health` endpoint (#238)

* feat: add an optional and quiet /health
* HEAD support for /health
* health endpoint doc
* Update docs/content/features/health-endpoint.md

---------

Co-authored-by: Jose Quintana <1700322+joseluisq@users.noreply.github.com>

Diff

 docs/content/configuration/environment-variables.md |  3 ++-
 docs/content/features/health-endpoint.md            | 35 +++++++++++++++++++-
 docs/mkdocs.yml                                     |  1 +-
 src/handler.rs                                      | 41 ++++++++++++++++++----
 src/server.rs                                       |  5 +++-
 src/settings/cli.rs                                 | 13 +++++++-
 src/settings/file.rs                                |  3 ++-
 src/settings/mod.rs                                 |  5 +++-
 8 files changed, 99 insertions(+), 7 deletions(-)

diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md
index a8dce67..0eb3b47 100644
--- a/docs/content/configuration/environment-variables.md
+++ b/docs/content/configuration/environment-variables.md
@@ -105,6 +105,9 @@ Check for a trailing slash in the requested directory URI and redirect permanent
### SERVER_IGNORE_HIDDEN_FILES
Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing).

### SERVER_HEALTH
Activate the health endpoint.

## Windows
The following options and commands are Windows platform-specific.

diff --git a/docs/content/features/health-endpoint.md b/docs/content/features/health-endpoint.md
new file mode 100644
index 0000000..7588836
--- /dev/null
+++ b/docs/content/features/health-endpoint.md
@@ -0,0 +1,35 @@
# Health endpoint

SWS provides an optional `/health` endpoint that can be used to check if it is running properly.
When the  `/health` is requested, SWS will generate a log only at the `debug` level instead of the usual `info` level for a regular file.

The HTTP methods supported are `GET` and `HEAD`.

This feature is disabled by default and can be controlled by the boolean `--health` option or the equivalent [SERVER_HEALTH]./../configuration/environment-variables.md#health env.

## Usage with kubernetes liveness probe

The health endpoint is well suited for the kubernetes liveness probe:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
    - name: sws
      image: frontend:1.0.0
      command:
        - static-web-server
        - --root=/public
        - --log-level=info
        - --health
      ports:
      - containerPort: 80
        name: http
      livenessProbe:
        httpGet:
          path: /health
          port: http
```
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 2c2f785..4d24609 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -161,6 +161,7 @@ nav:
    - 'Windows Service': 'features/windows-service.md'
    - 'Trailing Slash Redirect': 'features/trailing-slash-redirect.md'
    - 'Ignore Files': 'features/ignore-files.md'
    - 'Health endpoint': 'features/health-endpoint.md'
  - 'Platforms & Architectures': 'platforms-architectures.md'
  - 'Migrating from v1 to v2': 'migration.md'
  - 'Changelog v2 (stable)': 'https://github.com/static-web-server/static-web-server/blob/master/CHANGELOG.md'
diff --git a/src/handler.rs b/src/handler.rs
index 066e1b2..0e8fea3 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -6,7 +6,7 @@
//! Request handler module intended to manage incoming HTTP requests.
//!

use headers::HeaderValue;
use headers::{ContentType, HeaderMapExt, HeaderValue};
use hyper::{Body, Request, Response, StatusCode};
use std::{future::Future, net::IpAddr, net::SocketAddr, path::PathBuf, sync::Arc};

@@ -76,6 +76,8 @@ pub struct RequestHandlerOpts {
    pub redirect_trailing_slash: bool,
    /// Ignore hidden files feature.
    pub ignore_hidden_files: bool,
    /// Health endpoint feature.
    pub health: bool,

    /// Advanced options from the config file.
    pub advanced_opts: Option<Advanced>,
@@ -111,9 +113,13 @@ impl RequestHandler {
        let redirect_trailing_slash = self.opts.redirect_trailing_slash;
        let compression_static = self.opts.compression_static;
        let ignore_hidden_files = self.opts.ignore_hidden_files;
        let health = self.opts.health;

        let mut cors_headers: Option<http::HeaderMap> = None;

        let health_request =
            health && uri_path == "/health" && (method.is_get() || method.is_head());

        // Log request information with its remote address if available
        let mut remote_addr_str = String::new();
        if log_remote_addr {
@@ -130,14 +136,35 @@ impl RequestHandler {
                remote_addr_str.push_str(&client_ip_address.to_string())
            }
        }
        tracing::info!(
            "incoming request: method={} uri={}{}",
            method,
            uri,
            remote_addr_str,
        );

        if health_request {
            tracing::debug!(
                "incoming request: method={} uri={}{}",
                method,
                uri,
                remote_addr_str,
            );
        } else {
            tracing::info!(
                "incoming request: method={} uri={}{}",
                method,
                uri,
                remote_addr_str,
            );
        }

        async move {
            if health_request {
                let body = if method.is_get() {
                    Body::from("OK")
                } else {
                    Body::empty()
                };
                let mut resp = Response::new(body);
                resp.headers_mut().typed_insert(ContentType::html());
                return Ok(resp);
            }

            // Reject in case of incoming HTTP request method is not allowed
            if !method.is_allowed() {
                return error_page::error_response(
diff --git a/src/server.rs b/src/server.rs
index d90bd5b..bcc8cbd 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -257,6 +257,10 @@ impl Server {
        let grace_period = general.grace_period;
        tracing::info!("grace period before graceful shutdown: {}s", grace_period);

        // Health endpoint option
        let health = general.health;
        tracing::info!("health endpoint: enabled={}", health);

        // Create a service router for Hyper
        let router_service = RouterService::new(RequestHandler {
            opts: Arc::from(RequestHandlerOpts {
@@ -281,6 +285,7 @@ impl Server {
                log_remote_address,
                redirect_trailing_slash,
                ignore_hidden_files,
                health,
                advanced_opts,
            }),
        });
diff --git a/src/settings/cli.rs b/src/settings/cli.rs
index 0c560a4..470f58b 100644
--- a/src/settings/cli.rs
+++ b/src/settings/cli.rs
@@ -378,6 +378,19 @@ pub struct General {
    /// Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing).
    pub ignore_hidden_files: bool,

    #[arg(
        long,
        default_value = "false",
        default_missing_value("true"),
        num_args(0..=1),
        require_equals(true),
        action = clap::ArgAction::Set,
        env = "SERVER_HEALTH",
    )]
    /// Add a /health endpoint that doesn't generate any log entry and returns a 200 status code.
    /// This is especially useful with Kubernetes liveness and readiness probes.
    pub health: bool,

    //
    // Windows specific arguments and commands
    //
diff --git a/src/settings/file.rs b/src/settings/file.rs
index 3feee8d..a199f2c 100644
--- a/src/settings/file.rs
+++ b/src/settings/file.rs
@@ -219,6 +219,9 @@ pub struct General {
    /// Ignore hidden files feature.
    pub ignore_hidden_files: Option<bool>,

    /// Health endpoint feature.
    pub health: Option<bool>,

    #[cfg(windows)]
    /// windows service feature.
    pub windows_service: Option<bool>,
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index 03d786a..d0f3757 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -132,6 +132,7 @@ impl Settings {
        let mut log_remote_address = opts.log_remote_address;
        let mut redirect_trailing_slash = opts.redirect_trailing_slash;
        let mut ignore_hidden_files = opts.ignore_hidden_files;
        let mut health = opts.health;

        // Windows-only options
        #[cfg(windows)]
@@ -277,6 +278,9 @@ impl Settings {
                    if let Some(v) = general.ignore_hidden_files {
                        ignore_hidden_files = v
                    }
                    if let Some(v) = general.health {
                        health = v
                    }

                    // Windows-only options
                    #[cfg(windows)]
@@ -446,6 +450,7 @@ impl Settings {
                log_remote_address,
                redirect_trailing_slash,
                ignore_hidden_files,
                health,

                // Windows-only options and commands
                #[cfg(windows)]