-
Notifications
You must be signed in to change notification settings - Fork 64
Add HTTPS example with TLS support #409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: Nugine <[email protected]>
Co-authored-by: Nugine <[email protected]>
|
Done! Changes made in commit 5c0d29d:
The example now requires users to provide certificate files, with documentation showing how to generate test certificates using OpenSSL. The example supports PKCS8, PKCS1, and SEC1 private key formats. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a comprehensive HTTPS server example to demonstrate TLS integration with s3s. The example shows how to configure and run an S3-compatible service over HTTPS using tokio-rustls for TLS support.
Key Changes:
- New HTTPS example file with full TLS certificate handling and graceful shutdown
- Added development dependencies for TLS (
tokio-rustls,rustls-pemfile), CLI parsing (clap), HTTP utilities (hyper-util), and logging (tracing-subscriber)
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
crates/s3s/examples/https.rs |
Complete HTTPS server implementation with TLS certificate loading, multiple private key format support (PKCS8/PKCS1/SEC1), CLI argument parsing, and graceful shutdown handling |
crates/s3s/Cargo.toml |
Added dev-dependencies for TLS support, CLI parsing, HTTP utilities, and logging |
crates/s3s/examples/https.rs
Outdated
| } | ||
|
|
||
| /// Load certificates from a PEM file | ||
| fn load_certs(path: &PathBuf) -> io::Result<Vec<CertificateDer<'static>>> { |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path parameter should be &Path instead of &PathBuf to follow Rust API design conventions. PathBuf is an owned type and passing a reference to it is unnecessary when &Path would be more idiomatic and flexible.
crates/s3s/examples/https.rs
Outdated
| } | ||
|
|
||
| /// Load private key from a PEM file | ||
| fn load_private_key(path: &PathBuf) -> io::Result<PrivateKeyDer<'static>> { |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path parameter should be &Path instead of &PathBuf to follow Rust API design conventions. PathBuf is an owned type and passing a reference to it is unnecessary when &Path would be more idiomatic and flexible.
crates/s3s/examples/https.rs
Outdated
| } | ||
|
|
||
| /// Create TLS server configuration from certificate and key files | ||
| fn create_tls_config(cert_path: &PathBuf, key_path: &PathBuf) -> io::Result<ServerConfig> { |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cert_path and key_path parameters should be &Path instead of &PathBuf to follow Rust API design conventions. PathBuf is an owned type and passing references to it is unnecessary when &Path would be more idiomatic and flexible.
crates/s3s/examples/https.rs
Outdated
| // Try reading as PKCS8 first, then RSA, then EC | ||
| let mut keys = rustls_pemfile::pkcs8_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | ||
|
|
||
| if !keys.is_empty() { | ||
| return Ok(PrivateKeyDer::Pkcs8(keys.remove(0))); | ||
| } | ||
|
|
||
| // Reset reader | ||
| let file = File::open(path)?; | ||
| let mut reader = BufReader::new(file); | ||
| let mut keys = rustls_pemfile::rsa_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | ||
|
|
||
| if !keys.is_empty() { | ||
| return Ok(PrivateKeyDer::Pkcs1(keys.remove(0))); | ||
| } | ||
|
|
||
| // Reset reader | ||
| let file = File::open(path)?; | ||
| let mut reader = BufReader::new(file); | ||
| let mut keys = rustls_pemfile::ec_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | ||
|
|
||
| if !keys.is_empty() { | ||
| return Ok(PrivateKeyDer::Sec1(keys.remove(0))); | ||
| } | ||
|
|
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file is being opened and read three times sequentially to try different key formats. Consider using rustls_pemfile::read_one() in a loop to parse all PEM items in a single pass, then filter by type. This would be more efficient and avoid multiple file operations.
| // Try reading as PKCS8 first, then RSA, then EC | |
| let mut keys = rustls_pemfile::pkcs8_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | |
| if !keys.is_empty() { | |
| return Ok(PrivateKeyDer::Pkcs8(keys.remove(0))); | |
| } | |
| // Reset reader | |
| let file = File::open(path)?; | |
| let mut reader = BufReader::new(file); | |
| let mut keys = rustls_pemfile::rsa_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | |
| if !keys.is_empty() { | |
| return Ok(PrivateKeyDer::Pkcs1(keys.remove(0))); | |
| } | |
| // Reset reader | |
| let file = File::open(path)?; | |
| let mut reader = BufReader::new(file); | |
| let mut keys = rustls_pemfile::ec_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | |
| if !keys.is_empty() { | |
| return Ok(PrivateKeyDer::Sec1(keys.remove(0))); | |
| } | |
| use rustls_pemfile::Item; | |
| while let Some(item) = rustls_pemfile::read_one(&mut reader)? { | |
| match item { | |
| Item::Pkcs8Key(key) => { | |
| return Ok(PrivateKeyDer::Pkcs8(key)); | |
| } | |
| Item::RsaKey(key) => { | |
| return Ok(PrivateKeyDer::Pkcs1(key)); | |
| } | |
| Item::EcKey(key) => { | |
| return Ok(PrivateKeyDer::Sec1(key)); | |
| } | |
| _ => {} | |
| } | |
| } |
crates/s3s/examples/https.rs
Outdated
| let file = File::open(path)?; | ||
| let mut reader = BufReader::new(file); | ||
| let mut keys = rustls_pemfile::rsa_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file is being opened and read three times sequentially to try different key formats. Consider using rustls_pemfile::read_one() in a loop to parse all PEM items in a single pass, then filter by type. This would be more efficient and avoid multiple file operations.
crates/s3s/examples/https.rs
Outdated
| // Try reading as PKCS8 first, then RSA, then EC | ||
| let mut keys = rustls_pemfile::pkcs8_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | ||
|
|
||
| if !keys.is_empty() { | ||
| return Ok(PrivateKeyDer::Pkcs8(keys.remove(0))); | ||
| } | ||
|
|
||
| // Reset reader | ||
| let file = File::open(path)?; | ||
| let mut reader = BufReader::new(file); | ||
| let mut keys = rustls_pemfile::rsa_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | ||
|
|
||
| if !keys.is_empty() { | ||
| return Ok(PrivateKeyDer::Pkcs1(keys.remove(0))); | ||
| } | ||
|
|
||
| // Reset reader | ||
| let file = File::open(path)?; | ||
| let mut reader = BufReader::new(file); | ||
| let mut keys = rustls_pemfile::ec_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | ||
|
|
||
| if !keys.is_empty() { | ||
| return Ok(PrivateKeyDer::Sec1(keys.remove(0))); | ||
| } | ||
|
|
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file is being opened and read three times sequentially to try different key formats. Consider using rustls_pemfile::read_one() in a loop to parse all PEM items in a single pass, then filter by type. This would be more efficient and avoid multiple file operations.
| // Try reading as PKCS8 first, then RSA, then EC | |
| let mut keys = rustls_pemfile::pkcs8_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | |
| if !keys.is_empty() { | |
| return Ok(PrivateKeyDer::Pkcs8(keys.remove(0))); | |
| } | |
| // Reset reader | |
| let file = File::open(path)?; | |
| let mut reader = BufReader::new(file); | |
| let mut keys = rustls_pemfile::rsa_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | |
| if !keys.is_empty() { | |
| return Ok(PrivateKeyDer::Pkcs1(keys.remove(0))); | |
| } | |
| // Reset reader | |
| let file = File::open(path)?; | |
| let mut reader = BufReader::new(file); | |
| let mut keys = rustls_pemfile::ec_private_keys(&mut reader).collect::<Result<Vec<_>, _>>()?; | |
| if !keys.is_empty() { | |
| return Ok(PrivateKeyDer::Sec1(keys.remove(0))); | |
| } | |
| use rustls_pemfile::{Item, read_one}; | |
| loop { | |
| match read_one(&mut reader)? { | |
| Some(Item::Pkcs8Key(key)) => { | |
| return Ok(PrivateKeyDer::Pkcs8(key)); | |
| } | |
| Some(Item::RsaKey(key)) => { | |
| return Ok(PrivateKeyDer::Pkcs1(key)); | |
| } | |
| Some(Item::EcKey(key)) => { | |
| return Ok(PrivateKeyDer::Sec1(key)); | |
| } | |
| Some(_) => continue, | |
| None => break, | |
| } | |
| } |
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: Nugine <[email protected]>
Applied all review feedback in commit 593f8b0:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
| //! cargo run --example https -- --cert cert.pem --key key.pem | ||
| //! ``` | ||
| //! | ||
| //! Then you can access the server at <https://localhost:8014>. You'll need to accept |
Copilot
AI
Dec 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation refers to "localhost:8014" but the actual default host is "127.0.0.1" (line 124). While these typically resolve to the same address, for accuracy and consistency with the actual default values in the code, consider using "127.0.0.1:8014" instead.
| //! Then you can access the server at <https://localhost:8014>. You'll need to accept | |
| //! Then you can access the server at <https://127.0.0.1:8014>. You'll need to accept |
Adds a complete HTTPS server example demonstrating TLS integration with s3s.
Changes
New example
crates/s3s/examples/https.rs: Shows how to run an S3 service over HTTPS usingtokio-rustls&Pathparametersrustls_pemfile::read_one()Dependencies: Added
tokio-rustls,rustls-pemfile,clap,hyper-util, andtracing-subscriberto dev-dependenciesUsage
The example demonstrates loading existing TLS certificates from files, which is the recommended approach for production deployments. Documentation includes commands for generating test certificates using OpenSSL.
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.