Skip to content

Commit

Permalink
Add method for creating a HTTPS server (#2)
Browse files Browse the repository at this point in the history
* create https server

Signed-off-by: Dan Bond <[email protected]>

* add TLS tests

Signed-off-by: Dan Bond <[email protected]>
  • Loading branch information
loshz authored Apr 6, 2022
1 parent d62fb37 commit 308027d
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 46 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "metrics_server"
version = "0.3.0"
version = "0.4.0"
authors = ["Dan Bond <[email protected]>"]
edition = "2021"
rust-version = "1.58"
Expand All @@ -15,7 +15,7 @@ categories = ["web-programming::http-server"]
include = ["src/**/*", "tests", "examples", "LICENSE", "README.md"]

[dependencies]
tiny_http = "0.11"
tiny_http = { version = "0.11", features = ["ssl-rustls"] }

[dev-dependencies]
prometheus-client = "0.15"
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ publish:
# https://doc.rust-lang.org/cargo/reference/publishing.html
cargo package --list
cargo publish ${PUBLISH_FLAGS}

.PHONY: gen-certs
gen-certs:
mkdir -p ./tests/certs
openssl genpkey -algorithm Ed25519 -out ./tests/certs/private_key.pem
openssl req -x509 -key ./tests/certs/private_key.pem -out ./tests/certs/certificate.pem -sha256 -nodes -subj '/CN=localhost'
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,36 @@ This crate provides a thread safe, minimalstic HTTP server used to buffer metric
Include the lib in your `Cargo.toml` dependencies:
```toml
[dependencies]
metrics_server = "0.3"
metrics_server = "0.4"
```

In your application:
### HTTP
```rust
use metrics_server::MetricsServer;

// Create a new server and start listening for requests in the background.
// Create a new HTTP server and start listening for requests in the background.
let server = MetricsServer::new("localhost:8001");

// Publish you application metrics.
let bytes = server.update(Vec::from([1, 2, 3, 4]));
assert_eq!(4, bytes);
```

### HTTPS
Note: there is currently no option to skip TLS cert verification.
```rust
use metrics_server::MetricsServer;

// Load TLS config.
let cert = include_bytes!("/path/to/cert.pem").to_vec();
let key = include_bytes!("/path/to/key.pem").to_vec();

// Create a new HTTPS server and start listening for requests in the background.
let server = MetricsServer::https("localhost:8443", cert, key);

// Publish you application metrics.
let bytes = server.update(Vec::from([1, 2, 3, 4]));
assert_eq!(4, bytes);
```

For more comprehensive usage, see the included [examples](./examples).
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#![warn(missing_docs, rustdoc::missing_doc_code_examples)]

//! A hassle-free, single-responsibility HTTP server used to easily expose metrics in an application.
//! A hassle-free, single-responsibility HTTP/S server used to easily expose metrics in an application.
//!
//! This crate provides a thread safe, minimalstic HTTP server used to buffer metrics and serve
//! them via a standard /metrics endpoint. It's aim is to remove the boilerplate needed to
//! This crate provides a thread safe, minimalstic HTTP/S server used to buffer metrics and serve
//! them via a standard `/metrics` endpoint. It's aim is to remove the boilerplate needed to
//! create such simple mechanisms. It is currently somewhat oppinionated and naive in order to
//! maintain little complexity.
//!
//! # Examples
//!
//! ```
//! ```rust
//! use metrics_server::MetricsServer;
//!
//! // Create a new server and start listening for requests in the background.
//! // Create a new HTTP server and start listening for requests in the background.
//! let server = MetricsServer::new("localhost:8001");
//!
//! // Publish your application metrics.
Expand Down
85 changes: 51 additions & 34 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::net::ToSocketAddrs;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;

use tiny_http::{Method, Response, Server};

/// A thread-safe datastore for serving metrics via a HTTP server.
/// A thread-safe datastore for serving metrics via a HTTP/S server.
pub struct MetricsServer {
shared: Arc<MetricsServerShared>,
thread: Option<thread::JoinHandle<()>>,
Expand All @@ -17,27 +18,64 @@ struct MetricsServerShared {
}

impl MetricsServer {
/// Creates a new empty `MetricsServer`.
/// Creates an empty `MetricsServer` and starts a HTTP server on a new thread at the given address.
///
/// Starts a simple HTTP server on a new thread at the given address and expose the stored metrics.
/// This server is intended to only be queried synchronously as it blocks upon receiving
/// each request.
/// This server will only respond synchronously as it blocks until receiving new requests.
///
/// # Examples
/// # Panics
///
/// Panics if given an invalid address.
pub fn new<A>(addr: A) -> Self
where
A: ToSocketAddrs,
{
let config = tiny_http::ServerConfig { addr, ssl: None };

MetricsServer::serve(config)
}

/// Creates an empty `MetricsServer` and starts a HTTPS server on a new thread at the given address.
///
/// ```
/// use metrics_server::MetricsServer;
/// This server will only respond synchronously as it blocks until receiving new requests.
///
/// let server = MetricsServer::new("localhost:8001");
/// ```
/// Note: there is currently no option to skip TLS cert verification.
///
/// # Panics
///
/// Panics if given an invalid address.
pub fn new(addr: &str) -> Self {
/// Panics if given an invalid address or incorrect TLS credentials.
pub fn https<A>(addr: A, certificate: Vec<u8>, private_key: Vec<u8>) -> Self
where
A: ToSocketAddrs,
{
let config = tiny_http::ServerConfig {
addr,
ssl: Some(tiny_http::SslConfig {
certificate,
private_key,
}),
};

MetricsServer::serve(config)
}

/// Safely updates the data in a `MetricsServer` and returns the number of bytes written.
///
/// This method is protected by a mutex making it safe
/// to call concurrently from multiple threads.
pub fn update(&self, data: Vec<u8>) -> usize {
let mut buf = self.shared.data.lock().unwrap();
*buf = data;
buf.as_slice().len()
}

fn serve<A>(config: tiny_http::ServerConfig<A>) -> Self
where
A: ToSocketAddrs,
{
// Create an Arc of the shared data.
let shared = Arc::new(MetricsServerShared {
data: Mutex::new(Vec::new()),
server: Server::http(addr).unwrap(),
server: Server::new(config).unwrap(),
stop: AtomicBool::new(false),
});

Expand Down Expand Up @@ -81,27 +119,6 @@ impl MetricsServer {

MetricsServer { shared, thread }
}

/// Safely updates the data in a `MetricsServer` and returns the number of
/// bytes written.
///
/// This method is protected by a mutex making it safe
/// to call concurrently from multiple threads.
///
/// # Examples
///
/// ```
/// use metrics_server::MetricsServer;
///
/// let server = MetricsServer::new("localhost:8001");
/// let bytes = server.update(Vec::from([1, 2, 3, 4]));
/// assert_eq!(4, bytes);
/// ```
pub fn update(&self, data: Vec<u8>) -> usize {
let mut buf = self.shared.data.lock().unwrap();
*buf = data;
buf.as_slice().len()
}
}

impl Drop for MetricsServer {
Expand Down
9 changes: 9 additions & 0 deletions tests/certs/certificate.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBPDCB76ADAgECAhRxHim70w5qhJ/oUT/22jAulbCNATAFBgMrZXAwFDESMBAG
A1UEAwwJbG9jYWxob3N0MB4XDTIyMDQwNjE2MTc1MVoXDTIyMDUwNjE2MTc1MVow
FDESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAsqEDZLWNs1fXdMoAlHH/
QMfSvRGM6fDE9MFnLwQgjLWjUzBRMB0GA1UdDgQWBBTH+IEpa29un9FHOfpAdqke
ahkXHTAfBgNVHSMEGDAWgBTH+IEpa29un9FHOfpAdqkeahkXHTAPBgNVHRMBAf8E
BTADAQH/MAUGAytlcANBAPn7SJqNHhWKuEpM2ekQZChi8DzOWigCejvufGpRSbUB
dm+RnjZ88KvlEXlqUqknvBTY9lbL3v/9ylsZiSKogAQ=
-----END CERTIFICATE-----
3 changes: 3 additions & 0 deletions tests/certs/private_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIEmQJozhKPDztKioPj+Q8NhItwelrY2LBRJYoOIl4p2c
-----END PRIVATE KEY-----
43 changes: 41 additions & 2 deletions tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use metrics_server::MetricsServer;

#[test]
#[should_panic]
fn test_server_invalid_address() {
fn test_http_server_invalid_address() {
let _ = MetricsServer::new("invalid:99999999");
}

#[test]
fn test_server_serve() {
fn test_http_server_serve() {
let server = MetricsServer::new("localhost:8001");

for i in 0..3 {
Expand All @@ -26,3 +26,42 @@ fn test_server_serve() {
assert_eq!(v, buf);
}
}

#[test]
#[should_panic]
fn test_https_server_invalid_address() {
// Load TLS config.
let cert = include_bytes!("./certs/certificate.pem").to_vec();
let key = include_bytes!("./certs/private_key.pem").to_vec();

let _ = MetricsServer::https("invalid:99999999", cert, key);
}

#[test]
#[should_panic]
fn test_https_server_invalid_cert() {
// Load TLS config.
let cert = Vec::new();
let key = include_bytes!("./certs/private_key.pem").to_vec();

let _ = MetricsServer::https("localhost:8441", cert, key);
}

#[test]
#[should_panic]
fn test_https_server_invalid_key() {
// Load TLS config.
let cert = include_bytes!("./certs/certificate.pem").to_vec();
let key = Vec::new();

let _ = MetricsServer::https("localhost:8442", cert, key);
}

#[test]
fn test_https_server_serve() {
// Load TLS config.
let cert = include_bytes!("./certs/certificate.pem").to_vec();
let key = include_bytes!("./certs/private_key.pem").to_vec();

let _ = MetricsServer::https("localhost:8443", cert, key);
}

0 comments on commit 308027d

Please sign in to comment.