Skip to content

Commit

Permalink
use lib for parsing uri
Browse files Browse the repository at this point in the history
  • Loading branch information
loshz committed Aug 28, 2023
1 parent b50ed30 commit d274b0d
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 30 deletions.
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "metrics_server"
version = "0.12.0"
version = "0.13.0"
authors = ["Dan Bond <danbond@protonmail.com>"]
edition = "2021"
rust-version = "1.58"
Expand All @@ -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]
Expand Down
6 changes: 3 additions & 3 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.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
Expand Down Expand Up @@ -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());
Expand Down
1 change: 0 additions & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ unlicensed = "deny"
allow = [
"Apache-2.0",
"BSD-3-Clause",
"ISC",
"MIT",

]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
52 changes: 32 additions & 20 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -119,23 +121,23 @@ 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() {
return;
}
}

// 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.
Expand All @@ -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);
Expand Down Expand Up @@ -210,33 +212,43 @@ 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)]
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);
}
}
4 changes: 2 additions & 2 deletions tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit d274b0d

Please sign in to comment.