diff --git a/.gitignore b/.gitignore index 50281a4..af26ef8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +/target +**/*.rs.bk diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6ac6c9b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,62 @@ +language: rust +cache: cargo + +rust: + - stable + - beta + - nightly + +addons: + apt: + packages: + - mingw-w64 + - upx + +matrix: + include: + - env: + - TARGET=x86_64-unknown-linux-musl + - BIN_NAME=miniserve + - PROPER_NAME=miniserve-linux + os: linux + - env: + - TARGET=x86_64-pc-windows-gnu + - BIN_NAME=miniserve.exe + - PROPER_NAME=miniserve-win.exe + - RUSTFLAGS="-C linker=x86_64-w64-mingw32-gcc" + os: linux + - env: + - TARGET=x86_64-apple-darwin + - BIN_NAME=miniserve + - PROPER_NAME=miniserve-osx + os: osx + +before_install: + - rustup self update + +install: + # On Apple, the default target is already the right one. + - if [[ -n $TARGET && $TARGET != "x86_64-apple-darwin" ]]; then rustup target add $TARGET; fi + +script: + # If this is a normal, non-deployment build... + - if [[ -z $TARGET ]]; then cargo build --verbose; fi + - if [[ -n $TARGET ]]; then cargo build --verbose --release --target $TARGET; fi + +before_deploy: + # If this is a binary deployment... + - if [[ -n $TARGET ]]; then cp -a target/$TARGET/release/$BIN_NAME $PROPER_NAME && strip $PROPER_NAME; fi + - # Run upx on the binary if this is a deployment for Linux or Windows + - if [[ $TARGET = "x86_64-pc-windows-gnu" ]]; then upx $PROPER_NAME; fi + - if [[ $TARGET = "x86_64-unknown-linux-musl" ]]; then upx $PROPER_NAME; fi + +deploy: + - provider: releases + api_key: + secure: "n1OCiCjupkVieqsepGBOk6oxGcU89n9z0lQWWMAtBKREIEL1fnqt7A4z9FC1LV9UiAwvcvMHI8As5TA/7sV6jiWFuNOqu5HurX/n7cXlAuVWx00gCtWZ7Fh14ipVIDr2W52Mj/5qw1LSW19g8+9nLF1xO2YLrNpiaQX+V6Ypf04=" + file: $PROPER_NAME + skip_cleanup: true + on: + branch: master + tags: true + condition: $TRAVIS_RUST_VERSION = stable && -n $TARGET diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d1ed422 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "miniserve" +version = "0.1.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" +license = "MIT" +readme = "README.md" +keywords = ["serve", "http-server", "static-files"] +categories = ["command-line-utilities", "command-line-interface"] + +[badges] +travis-ci = { repository = "svenstaro/miniserve", branch = "master" } +maintenance = { status = "actively-developed" } + +[dependencies] +pbr = "1.0" +yansi = "0.4" +rand = "0.4" +chrono = "0.4" +url = "1.6" +lazy_static = "1.0" +humantime = "1.1" +regex = "0.2.5" +clap = "2.29" +ctrlc = "3.1" +actix = "0.5" +actix-web = { git = "https://github.com/actix/actix-web.git" } +simplelog = "0.5" diff --git a/README.md b/README.md index f180b55..7595257 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# servethis -:star2: For when you really just want to serve some files over HTTP right now! +# miniserve - For when you really just want to serve some files over HTTP right now! + +[![Build Status](https://travis-ci.org/svenstaro/miniserve.svg?branch=master)](https://travis-ci.org/svenstaro/miniserve) +[![AUR](https://img.shields.io/aur/version/miniserve.svg)](https://aur.archlinux.org/packages/miniserve/) +[![Crates.io](https://img.shields.io/crates/v/miniserve.svg)](https://crates.io/crates/miniserve) +[![dependency status](https://deps.rs/repo/github/svenstaro/miniserve/status.svg)](https://deps.rs/repo/github/svenstaro/miniserve) +[![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/svenstaro/miniserve/blob/master/LICENSE) + diff --git a/index.html b/index.html new file mode 100644 index 0000000..ced54c9 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +rofl diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2e4a69f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,131 @@ +extern crate actix; +extern crate actix_web; +extern crate simplelog; + +#[macro_use] +extern crate clap; + +use actix_web::{server, App, fs, middleware, HttpRequest, HttpResponse, Result}; +use simplelog::{TermLogger, LevelFilter, Config}; +use std::path::PathBuf; +use std::net::IpAddr; + +#[derive(Clone)] +pub struct MiniserveConfig { + verbose: bool, + path: std::path::PathBuf, + port: u16, + interface: IpAddr, +} + +fn is_valid_path(path: String) -> Result<(), String> { + let path_to_check = PathBuf::from(path); + if path_to_check.is_file() || path_to_check.is_dir() { + return Ok(()); + } + Err(String::from("Path either doesn't exist or is not a regular file or a directory")) +} + +fn is_valid_port(port: String) -> Result<(), String> { + port.parse::().and(Ok(())).or_else(|e| Err(e.to_string())) +} + +fn is_valid_interface(interface: String) -> Result<(), String> { + interface.parse::().and(Ok(())).or_else(|e| Err(e.to_string())) +} + +pub fn parse_args() -> MiniserveConfig { + use clap::{App, Arg}; + + let matches = App::new(crate_name!()) + .version(crate_version!()) + .author(crate_authors!()) + .about(crate_description!()) + .arg( + Arg::with_name("verbose") + .short("v") + .long("verbose") + .help("Be verbose"), + ) + .arg( + Arg::with_name("PATH") + .required(true) + .validator(is_valid_path) + .help("Which path to serve"), + ) + .arg( + Arg::with_name("port") + .short("p") + .long("port") + .help("Port to use") + .validator(is_valid_port) + .required(false) + .default_value("8080") + .takes_value(true), + ) + .arg( + Arg::with_name("interface") + .short("i") + .long("if") + .help("Interface to listen on") + .validator(is_valid_interface) + .required(false) + .default_value("0.0.0.0") + .takes_value(true), + ) + .get_matches(); + + let verbose = matches.is_present("verbose"); + let path = matches.value_of("PATH").unwrap(); + let port = matches.value_of("port").unwrap().parse().unwrap(); + let interface = matches.value_of("interface").unwrap().parse().unwrap(); + + MiniserveConfig { + verbose, + path: PathBuf::from(path), + port, + interface, + } +} + +fn file_handler(req: HttpRequest) -> Result { + let path = &req.state().path; + Ok(fs::NamedFile::open(path)?) +} + +fn configure_app(app: App) -> App { + let s = { + let path = &app.state().path; + if path.is_file() { + None + } else { + Some(fs::StaticFiles::new(path).show_files_listing()) + } + }; + + if let Some(s) = s { + app.handler("/", s) + } else { + app.resource("/", |r| r.f(file_handler)) + } +} + +fn main() { + let miniserve_config = parse_args(); + + if miniserve_config.verbose { + let _ = TermLogger::init(LevelFilter::Info, Config::default()); + } + let sys = actix::System::new("miniserve"); + + let inside_config = miniserve_config.clone(); + server::new( + move || App::with_state(inside_config.clone()) + .middleware(middleware::Logger::default()) + .configure(configure_app)) + .bind(format!("{}:{}", &miniserve_config.interface, miniserve_config.port)).expect("Couldn't bind server") + .shutdown_timeout(1) + .start(); + + let _ = sys.run(); +}