Skip to content

Commit

Permalink
[initial] setup echo server
Browse files Browse the repository at this point in the history
  • Loading branch information
Swaagie committed Jun 6, 2021
0 parents commit 4b10cd8
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "echo-server"
version = "0.1.0"
authors = ["Martijn Swaagman <martijn@swaagman.online>"]
description = "Minimalistic HTTP echo server"
edition = "2018"
license = "MIT"
keywords = ["echo", "server", "simple", "minimalistic", "zero-dependencies"]

[dependencies]
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/)
111 changes: 111 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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<String> = 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<String> = 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(())
}

0 comments on commit 4b10cd8

Please sign in to comment.