From b58624818372c63a74028c1e0830b8186a3abcbf Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Fri, 10 Aug 2018 07:05:11 +0200 Subject: [PATCH] Allow for multiple interfaces to be specified at the same time --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 12 +++++++ src/main.rs | 94 ++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4af52edc8..1a7ede6e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,7 +657,7 @@ dependencies = [ [[package]] name = "miniserve" -version = "0.1.5" +version = "0.2.0" dependencies = [ "actix 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "actix-web 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 3d96aa679..1f2c80ec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miniserve" -version = "0.1.5" +version = "0.2.0" description = "For when you really just want to serve some files over HTTP right now!" authors = ["Sven-Hendrik Haase "] repository = "https://github.com/svenstaro/miniserve" diff --git a/README.md b/README.md index 77b105e69..7588d59a1 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ Sometimes this is just a more practical and quick way than doing things properly miniserve --auth joe:123 unreleased-linux-distros/ +### Bind to multiple interfaces: + + miniserve -i 192.168.0.1 -i 10.13.37.10 -i ::1 -- /tmp/myshare + ## Features - Easy to use @@ -54,6 +58,14 @@ Sometimes this is just a more practical and quick way than doing things properly cargo install miniserve miniserve +## Binding behavior + +For convenience reasons, miniserve will try to bind on all interfaces by default (if no `-i` is provided). +It will also do that if explicitly provided with `-i 0.0.0.0` or `-i ::`. +In all of the aforementioned cases, it will bind on both IPv4 and IPv6. +If provided with an explicit non-default interface, it will ONLY bind to that interface. +You can provide `-i` multiple times to bind to multiple interfaces at the same time. + ## Why use this over alternatives? - darkhttpd: Not easily available on Windows and it's not as easy as download and go. diff --git a/src/main.rs b/src/main.rs index 316de6713..af9f244dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use actix_web::middleware::{Middleware, Response}; use actix_web::{fs, middleware, server, App, HttpMessage, HttpRequest, HttpResponse, Result}; use simplelog::{Config, LevelFilter, TermLogger}; use std::io::{self, Write}; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}; use std::path::PathBuf; use std::thread; use std::time::Duration; @@ -33,7 +33,7 @@ pub struct MiniserveConfig { verbose: bool, path: std::path::PathBuf, port: u16, - interface: IpAddr, + interfaces: Vec, auth: Option, path_explicitly_chosen: bool, } @@ -80,7 +80,7 @@ fn is_valid_interface(interface: String) -> Result<(), String> { fn is_valid_auth(auth: String) -> Result<(), String> { auth.find(':') - .ok_or("Correct format is username:password".to_owned()) + .ok_or_else(|| "Correct format is username:password".to_owned()) .map(|_| ()) } @@ -111,14 +111,14 @@ pub fn parse_args() -> MiniserveConfig { .default_value("8080") .takes_value(true), ).arg( - Arg::with_name("interface") + Arg::with_name("interfaces") .short("i") .long("if") .help("Interface to listen on") .validator(is_valid_interface) .required(false) - .default_value("0.0.0.0") - .takes_value(true), + .takes_value(true) + .multiple(true), ).arg( Arg::with_name("auth") .short("a") @@ -131,7 +131,14 @@ pub fn parse_args() -> MiniserveConfig { let verbose = matches.is_present("verbose"); let path = matches.value_of("PATH"); let port = matches.value_of("port").unwrap().parse().unwrap(); - let interface = matches.value_of("interface").unwrap().parse().unwrap(); + let interfaces = if let Some(interfaces) = matches.values_of("interfaces") { + interfaces.map(|x| x.parse().unwrap()).collect() + } else { + vec![ + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), + ] + }; let auth = if let Some(auth_split) = matches.value_of("auth").map(|x| x.splitn(2, ':')) { let auth_vec = auth_split.collect::>(); if auth_vec.len() == 2 { @@ -150,7 +157,7 @@ pub fn parse_args() -> MiniserveConfig { verbose, path: PathBuf::from(path.unwrap_or(".")), port, - interface, + interfaces, auth, path_explicitly_chosen: path.is_some(), } @@ -239,27 +246,46 @@ fn main() { .middleware(Auth) .middleware(middleware::Logger::default()) .configure(configure_app) - }).bind(format!( - "{}:{}", - &miniserve_config.interface, miniserve_config.port - )).expect("Couldn't bind server") + }).bind( + miniserve_config + .interfaces + .iter() + .map(|interface| { + format!( + "{interface}:{port}", + interface = &interface, + port = miniserve_config.port, + ).to_socket_addrs() + .unwrap() + .next() + .unwrap() + }).collect::>() + .as_slice(), + ).expect("Couldn't bind server") .shutdown_timeout(0) .start(); - // If the interface is 0.0.0.0, we'll change it to localhost so that clicking the link will - // also work on Windows. Why can't Windows interpret 0.0.0.0? - let interface = if miniserve_config.interface == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { - String::from("localhost") - } else { - format!("{}", miniserve_config.interface) - }; + let interfaces = miniserve_config.interfaces.iter().map(|&interface| { + if interface == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { + // If the interface is 0.0.0.0, we'll change it to localhost so that clicking the link will + // also work on Windows. Why can't Windows interpret 0.0.0.0? + String::from("localhost") + } else if interface.is_ipv6() { + // If the interface is IPv6 then we'll print it with brackets so that it is clickable. + format!("[{}]", interface) + } else { + format!("{}", interface) + } + }); let canon_path = miniserve_config.path.canonicalize().unwrap(); let path_string = canon_path.to_string_lossy(); - println!("{name} v{version}", - name=Paint::new("miniserve").bold(), - version=crate_version!()); + println!( + "{name} v{version}", + name = Paint::new("miniserve").bold(), + version = crate_version!() + ); if !miniserve_config.path_explicitly_chosen { println!("{info} miniserve has been invoked without an explicit path so it will serve the current directory.", info=Color::Blue.paint("Info:").bold()); println!( @@ -273,15 +299,25 @@ fn main() { thread::sleep(Duration::from_millis(500)); } } + let mut addresses = String::new(); + for interface in interfaces { + if !addresses.is_empty() { + addresses.push_str(", "); + } + addresses.push_str(&format!( + "{}", + Color::Green + .paint(format!( + "http://{interface}:{port}", + interface = interface, + port = miniserve_config.port + )).bold() + )); + } println!( - "Serving path {path} at {address}", + "Serving path {path} at {addresses}", path = Color::Yellow.paint(path_string).bold(), - address = Color::Green - .paint(format!( - "http://{interface}:{port}", - interface = interface, - port = miniserve_config.port - )).bold() + addresses = addresses, ); println!("Quit by pressing CTRL-C");