diff --git a/Cargo.toml b/Cargo.toml index 5d1dfd5..dca0c12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "metrics_server" -version = "0.12.0" +version = "0.13.0" authors = ["Dan Bond "] edition = "2021" rust-version = "1.58" @@ -20,10 +20,11 @@ doctest = false [dependencies] log = "0.4" tiny_http = "0.12" +http = "0.2" [dev-dependencies] -ctrlc = { version = "3.2", features = ["termination"] } -prometheus-client = "0.19" +ctrlc = { version = "3.4", features = ["termination"] } +prometheus-client = "0.21" reqwest = { version = "0.11", features = ["blocking"] } [features] diff --git a/README.md b/README.md index 3243880..690c4b6 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ This crate provides a thread safe, minimalstic HTTP/S server used to buffer metr Include the lib in your `Cargo.toml` dependencies: ```toml [dependencies] -metrics_server = "0.12" +metrics_server = "0.13" ``` To enable TLS support, pass the optional feature flag: ```toml [dependencies] -metrics_server = { version = "0.12", features = ["tls"] } +metrics_server = { version = "0.13", features = ["tls"] } ``` ### HTTP @@ -66,7 +66,7 @@ use metrics_server::MetricsServer; // Create a new server and specify the URL path to serve. let mut server = MetricsServer::new("localhost:8001", None, None); -server.serve_url("/path/to/metrics"); +server.serve_uri("/path/to/metrics"); // Publish your application metrics. let bytes = server.update("my_awesome_metric = 10".into()); diff --git a/deny.toml b/deny.toml index 348ae8a..c4752cd 100644 --- a/deny.toml +++ b/deny.toml @@ -23,7 +23,6 @@ unlicensed = "deny" allow = [ "Apache-2.0", "BSD-3-Clause", - "ISC", "MIT", ] diff --git a/src/lib.rs b/src/lib.rs index b2591e0..681c037 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,7 @@ //! //! // Create a new server and specify the URL path to serve. //! let mut server = MetricsServer::new("localhost:8001", None, None); -//! server.serve_url("/path/to/metrics"); +//! server.serve_uri("/path/to/metrics"); //! //! // Publish your application metrics. //! let bytes = server.update("my_awesome_metric = 10".into()); diff --git a/src/server.rs b/src/server.rs index c94513f..2fb519e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,10 @@ use std::net::ToSocketAddrs; +use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; +use http::Uri; use log::{debug, error}; use tiny_http::{ConfigListenAddr, Method, Response, Server}; @@ -119,14 +121,14 @@ impl MetricsServer { /// The server will only respond synchronously as it blocks until receiving new requests. /// Suqsequent calls to this method will return a no-op and not affect the underlying server. pub fn serve(&mut self) { - self.serve_url(DEFAULT_METRICS_PATH.to_string()) + self.serve_uri(DEFAULT_METRICS_PATH.to_string()) } /// Start serving requests to a specific URL path on the underlying server. /// /// The server will only respond synchronously as it blocks until receiving new requests. /// Suqsequent calls to this method will return a no-op and not affect the underlying server. - pub fn serve_url(&mut self, mut url: String) { + pub fn serve_uri(&mut self, uri: String) { // Check if we already have a thread running. if let Some(thread) = &self.thread { if !thread.is_finished() { @@ -134,8 +136,8 @@ impl MetricsServer { } } - // Ensure URL is valid. - url = parse_url(url); + // Ensure URI is valid. + let u = parse_uri(uri); // Invoking clone on Arc produces a new Arc instance, which points to the // same allocation on the heap as the source Arc, while increasing a reference count. @@ -158,8 +160,8 @@ impl MetricsServer { req.http_version(), ); - // Only serve the /metrics path. - if req.url().to_lowercase() != url { + // Only serve the specified uri path. + if req.url().to_lowercase() != u { let res = Response::empty(404); if let Err(e) = req.respond(res) { error!("metrics_server error: {}", e); @@ -210,16 +212,21 @@ impl MetricsServer { } } -/// Naive URL parse that simply removes whitespace and prepends a "/" if not already present. -/// TODO: use a url parsing lib? -fn parse_url(mut url: String) -> String { - url.retain(|c| !c.is_whitespace()); - - if !url.starts_with('/') { - url = format!("/{}", url); +/// Validate the provided URI or return the default /metrics on error. +fn parse_uri(mut uri: String) -> String { + if !uri.starts_with('/') { + uri = format!("/{}", uri); } - url.to_lowercase() + let u = match Uri::from_str(&uri) { + Ok(u) => u.path().to_string(), + Err(_) => { + error!("invalid uri, defaulting to {}", DEFAULT_METRICS_PATH); + return DEFAULT_METRICS_PATH.to_string(); + } + }; + + u.to_lowercase() } #[cfg(test)] @@ -227,16 +234,21 @@ mod tests { use super::*; #[test] - fn test_parse_url() { - let expected = DEFAULT_METRICS_PATH.to_string(); + fn test_parse_uri() { + let expected_default = DEFAULT_METRICS_PATH.to_string(); + let expected_valid = "/v1/metrics".to_string(); + // Invalid. + assert_eq!(parse_uri("Hello, World!".to_string()), expected_default); // No slash prefix. - assert_eq!(parse_url("metrics".to_string()), expected); + assert_eq!(parse_uri("metrics".to_string()), expected_default); // Leading slash prefix. - assert_eq!(parse_url("/metrics".to_string()), expected); + assert_eq!(parse_uri("/metrics".to_string()), expected_default); // Whitespace. - assert_eq!(parse_url(" metr ics ".to_string()), expected); + assert_eq!(parse_uri(" metr ics ".to_string()), expected_default); // Uppercase. - assert_eq!(parse_url("METRICS".to_string()), expected); + assert_eq!(parse_uri("METRICS".to_string()), expected_default); + // Valid. + assert_eq!(parse_uri("/v1/metrics".to_string()), expected_valid); } } diff --git a/tests/server.rs b/tests/server.rs index ea82483..74dea33 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -117,9 +117,9 @@ fn test_http_server_serve() { } #[test] -fn test_http_server_serve_url() { +fn test_http_server_serve_uri() { let mut server = MetricsServer::new("localhost:8004", None, None).unwrap(); - server.serve_url("/test".to_string()); + server.serve_uri("/test".to_string()); // Assert calls to non /metrics endpoint returns 404. let res = reqwest::blocking::get("http://localhost:8004/metrics").unwrap();