From 4b10cd8e97e3bcdc62334e9f07de1e9359a89794 Mon Sep 17 00:00:00 2001 From: Martijn Swaagman Date: Sun, 6 Jun 2021 14:15:30 +0200 Subject: [PATCH] [initial] setup echo server --- .github/workflows/build.yml | 20 +++++++ .github/workflows/ci.yml | 25 ++++++++ .gitignore | 1 + Cargo.lock | 5 ++ Cargo.toml | 10 ++++ Dockerfile | 30 ++++++++++ README.md | 34 +++++++++++ src/main.rs | 111 ++++++++++++++++++++++++++++++++++++ 8 files changed, 236 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ac97923 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Stable with rustfmt and clippy + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + - name: Release build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..655410f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Stable with rustfmt and clippy + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: rustfmt, clippy + - name: Release build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features + - name: test + uses: actions-rs/cargo@v1 + with: + command: test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7e51c5b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "echo-server" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..915001a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "echo-server" +version = "0.1.0" +authors = ["Martijn Swaagman "] +description = "Minimalistic HTTP echo server" +edition = "2018" +license = "MIT" +keywords = ["echo", "server", "simple", "minimalistic", "zero-dependencies"] + +[dependencies] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5c9d50c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Build image +FROM rust:alpine as builder + +RUN mkdir /server +WORKDIR /server +COPY . . + +RUN cargo build --release + +# Final image +FROM alpine:latest + +ENV USER="app" + +# Define user that executes the echo-server +RUN addgroup -S $USER +RUN adduser -S -g $USER $USER + +RUN mkdir /server +WORKDIR /server + +COPY --from=builder /server/target/release/echo-server /server/echo-server +RUN chown -R $USER:$USER /server + +USER $USER + +# Expose default port of echo-server +EXPOSE 8080 + +ENTRYPOINT ["/server/echo-server"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fcf52f2 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Echo server + +Zero dependency minimalist echo server written in Rust. + +## Installation + +```console +cargo install echo-server +``` + +## Usage + +```console +echo-server [--port=1337] [--body="your preferred response message"] +``` + +The server has defaults: + +```yaml +- port: 8080 +- body: "hello world" +``` + +> You can also us the build docker image from docker hub. + +## Contributing + +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. + +## License + +[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..be4ff95 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,111 @@ +use std::{env, thread}; +use std::io::prelude::*; +use std::net::TcpListener; +use std::net::TcpStream; + +fn main() -> Result<(), std::io::Error> { + let args: Vec = env::args().collect(); + let (port, _) = parse_parameters(&args); + + let address = format!("127.0.0.1:{}", port); + let listener = TcpListener::bind(address)?; + println!("Echo server listening on port {}", port); + + for stream in listener.incoming() { + let stream = stream?; + let address = stream.peer_addr()?; + + // TODO: Implement max concurrency/threads, potential fork bomb + thread::spawn(move || -> Result<(), std::io::Error> { + // TODO: pass body value from main thread. + let args: Vec = env::args().collect(); + let (_, body) = parse_parameters(&args); + + match handle_connection(stream, body) { + Ok(_) => println!("Handled request from {}", address), + Err(err) => println!("Unable to handle request {}", err) + }; + + Ok(()) + }); + } + + println!("{:?}",args); + Ok(()) +} + +fn parse_parameters(args: &[String]) -> (&str, &str) { + let mut port = "8080"; + let mut body = "hello world"; + + args.iter().for_each(|arg| { + let arg: Vec<&str> = arg.split("=").collect(); + + match arg[0] { + "--port" => port = &arg[1], + "--body" => body = &arg[1], + _ => () + } + }); + + (port, body) +} + +fn handle_connection(mut stream: TcpStream, body: &str) -> Result<(), std::io::Error> { + let mut buffer = [0; 1024]; + + stream.read(&mut buffer)?; + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", + body.len(), + body + ); + + stream.write(response.as_bytes())?; + stream.flush() +} + +#[test] +fn test_parse_parameters() -> Result<(), std::string::FromUtf8Error> { + let args = vec![ + String::from("exec"), + String::from("--port=80"), + String::from("--body=hello"), + ]; + let (port, body) = parse_parameters(&args); + + assert_eq!(port, "80"); + assert_eq!(body, "hello"); + + // Single parameter + let args = vec![String::from("exec"), String::from("--port=8081")]; + let (port, body) = parse_parameters(&args); + + assert_eq!(port, "8081"); + assert_eq!(body, "hello world"); + + // Invert order + let args = vec![ + String::from("exec"), + String::from("--body=first"), + String::from("--port=8082"), + ]; + let (port, body) = parse_parameters(&args); + + assert_eq!(port, "8082"); + assert_eq!(body, "first"); + + Ok(()) +} + +#[test] +fn test_parse_parameters_with_defaults() -> Result<(), std::string::FromUtf8Error> { + let args = vec![String::from("exec"), String::from(""), String::from("")]; + let (port, body) = parse_parameters(&args); + + assert_eq!(port, "8080"); + assert_eq!(body, "hello world"); + + Ok(()) +}