Skip to content

Commit

Permalink
simplify uri path parsing
Browse files Browse the repository at this point in the history
Signed-off-by: Dan Bond <[email protected]>
  • Loading branch information
loshz committed May 29, 2024
1 parent ea4056d commit 6a493b8
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "metrics_server"
version = "0.14.0"
version = "0.15.0"
authors = ["Dan Bond <[email protected]>"]
edition = "2021"
rust-version = "1.63"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.14"
metrics_server = "0.15"
```

To enable TLS support, pass the optional feature flag:
```toml
[dependencies]
metrics_server = { version = "0.14", features = ["tls"] }
metrics_server = { version = "0.15", features = ["tls"] }
```

### HTTP
Expand Down
56 changes: 29 additions & 27 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;

use http::Uri;
use http::uri::PathAndQuery;
use log::{debug, error};
use time::{format_description, OffsetDateTime};
use tiny_http::{ConfigListenAddr, Method, Response, Server};
Expand Down Expand Up @@ -127,16 +127,17 @@ 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_uri(&mut self, uri: String) {
pub fn serve_uri(&mut self, path: String) {
// Check if we already have a thread running.
if let Some(thread) = &self.thread {
if !thread.is_finished() {
debug!("metrics server already running, continuing");
return;
}
}

// Ensure URI is valid.
let u = parse_uri(uri);
// Ensure path is valid.
let path = parse_path(&path);

// 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.
Expand All @@ -149,11 +150,12 @@ impl MetricsServer {
for req in s.server.incoming_requests() {
// Check to see if we should stop handling requests.
if s.stop.load(Ordering::Relaxed) {
break;
debug!("metrics server stopping");
return;
}

// Only serve the specified uri path.
if req.url().to_lowercase() != u {
// Only serve the specified URI path.
if req.url() != path {
let res = Response::empty(404);
respond(req, res);
continue;
Expand Down Expand Up @@ -198,16 +200,18 @@ impl MetricsServer {
}
}

// Validate the provided URI or return the default /metrics on error.
fn parse_uri(mut uri: String) -> String {
if !uri.starts_with('/') {
uri.insert(0, '/');
}

match Uri::from_str(&uri) {
Ok(u) => u.path().to_lowercase(),
// Validate the provided URL path, or return the default path on error.
fn parse_path(uri: &str) -> String {
match PathAndQuery::from_str(uri) {
Ok(pq) => {
let mut path = pq.path().to_lowercase();
if !path.starts_with('/') {
path.insert(0, '/');
}
path
}
Err(_) => {
error!("invalid uri, defaulting to {}", DEFAULT_METRICS_PATH);
error!("invalid uri, defaulting to {DEFAULT_METRICS_PATH}");
DEFAULT_METRICS_PATH.to_string()
}
}
Expand Down Expand Up @@ -242,21 +246,19 @@ mod tests {
use super::*;

#[test]
fn test_parse_uri() {
fn test_parse_path() {
let expected_default = DEFAULT_METRICS_PATH.to_string();
let expected_valid = "/v1/metrics".to_string();
let expected_valid = "/debug/metrics".to_string();

// Invalid.
assert_eq!(parse_uri("Hello, World!".to_string()), expected_default);
// No slash prefix.
assert_eq!(parse_uri("metrics".to_string()), expected_default);
// Leading slash prefix.
assert_eq!(parse_uri("/metrics".to_string()), expected_default);
assert_eq!(parse_path("Hello, World!"), expected_default);
// Whitespace.
assert_eq!(parse_uri(" metr ics ".to_string()), expected_default);
// Uppercase.
assert_eq!(parse_uri("METRICS".to_string()), expected_default);
assert_eq!(parse_path(" metr ics "), expected_default);
// Non-ASCII.
assert_eq!(parse_path("mëtrîcs"), expected_default);
// Valid.
assert_eq!(parse_uri("/v1/metrics".to_string()), expected_valid);
assert_eq!(parse_path("/debug/metrics"), expected_valid);
assert_eq!(parse_path("debug/metrics"), expected_valid);
assert_eq!(parse_path("DEBUG/METRICS"), expected_valid);
}
}
4 changes: 2 additions & 2 deletions tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ fn test_http_server_serve() {
let res = reqwest::blocking::get("http://localhost:8001/metricsssss").unwrap();
assert_eq!(404, res.status());

// Assert calls to uppercase URLs returns 200.
// Assert calls to uppercase URLs returns 404.
let res = reqwest::blocking::get("http://localhost:8001/METRICS").unwrap();
assert_eq!(200, res.status());
assert_eq!(404, res.status());

// Assert non GET requests to /metrics endpoint returns 405.
let client = reqwest::blocking::Client::new();
Expand Down

0 comments on commit 6a493b8

Please sign in to comment.