Let's start coding

Guillaume / 2021-01-17 / Santiago, Chile


Coding

I wrote the README, summoned a web site, so let's code now.

I plan to start with the mocking part, which will work as an enhanced static file server.

The mock definitions will be stored and retrieved from the file system. Let's say in a mock folder, something like this:

.
├── oauth
│   └── token
│       ├── data
│       └── meta
└── order
    ├── 10001
    │   ├── data
    │   └── meta
    └── 10002
        ├── data
        └── meta

Which will provide mocks for those endpoints:

The data file would provide the body of the response, while the meta file would provide the remaining information, like method, headers, status code to return, time to wait, etc.. Something like that:

method: GET
status: 200
headers:
- name: Content-Type
  value: application/json

YAML seems like a good fit here, but it may be something simpler, time will tell, but you get the idea.

So I've heard about some web frameworks like Rocket, Actix or Warp but my first impression is that I shouldn't need one, at least at this point, because I need basic http and file reading, not a full blown API.

Also, it would had been complicated to pick one of those frameworks. On the other hand Hyper seems like a safe choice somehow (despite being 0.14) maybe because various frameworks are built on top of it.

The first guide on the Hyper website shows how to build an http server returning "Hello, world", so let's start from here.

First thing first

Ok, I refactored that Hello World so it now responds Hello Roxyp, easy.. I installed the Rust extension for VSCode, it seems to be somewhat oficial and recommended, it brings autocompletion and "jump to code" feature, which is cool.

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use std::convert::Infallible;
use std::net::SocketAddr;

async fn hello_roxyp(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new("Hello, Roxyp".into()))
}

#[tokio::main]
async fn main() {
    // We'll bind to 127.0.0.1:3000
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // A `Service` is needed for every connection, so this
    // creates one from our `hello_roxyp` function.
    let make_svc = make_service_fn(|_conn| async {
        // service_fn converts our function into a `Service`
        Ok::<_, Infallible>(service_fn(hello_roxyp))
    });

    let server = Server::bind(&addr).serve(make_svc);

    // Run this server for... forever!
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

Some familiar stuff here, async/await like in NodeJS, type annotations are similar to Typescript, a main function like in Java, and there is funcional programming too, but with a weird two pipes syntax.

It says infallible, that bit definitely sounds good.

Now I want my server to returns his name, like does Nginx, so let's add a server header:

use hyper::header::{HeaderValue, SERVER};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use std::convert::Infallible;
use std::net::SocketAddr;

const SERVER_NAME: &str = concat!("roxyp/", env!("CARGO_PKG_VERSION"));

fn add_server_header(mut res: Response<Body>) -> Response<Body> {
    res.headers_mut()
        .insert(SERVER, HeaderValue::from_static(SERVER_NAME));
    res
}

async fn hello_roxyp(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(add_server_header(Response::new("Hello, Roxyp".into())))
}

#[tokio::main]
async fn main() {
    // Initialize
    println!("Starting {}", SERVER_NAME);

    // We'll bind to 127.0.0.1:3000
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // A `Service` is needed for every connection, so this
    // creates one from our `hello_roxyp` function.
    let make_svc = make_service_fn(|_conn| async {
        // service_fn converts our function into a `Service`
        Ok::<_, Infallible>(service_fn(hello_roxyp))
    });

    let server = Server::bind(&addr).serve(make_svc);

    // Run this server for... forever!
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

That seems pretty straightforward, doesn'it? Not so much..

Where I come from

Some might have system programming background, not me.

When I was a student, Java was the hype, the Rust of today, but stronger. I learn programming C++ one year, then Java one year, and was done with studies.

I got a job in a french software editor, and worked for 14 years on Windows desktop client/server softwares, for business backoffice, real estate and banking stuff mainly.

So I became an expert of PowerBuilder, a RAD (Rapid Application Development) similar to Visual Basic or Delphi, a nice tool but less relevant than Cobol nowadays.

The company was bought by a bigger one and I discovered loyalty does not pay in this game.

I moved to Chile, learnt some Javascript and Python to update my skills, and found a job, with a Mac!

From there I do web. I learnt Docker and some linux on the way, have been introduced to the Cloud, Kubernetes and, of course, the microservices. I did a bunch of them, some with NodeJS, but the majority with Spring Boot, which is ubiquitous, here at least.

How do I concatenate strings?

So here I am, after two minutes of coding, googling for help. With all the languages I'm used to, concatening strings is a no-brainer, at most you will instantiate a StringBuilder in Java, but Rust is not the same beast.

There are (at least) two kinds of string in Rust: &str and String, functions will accept one or another. And there are multiple slightly diferent ways to concatenate them (format!, concat!, to_owned(), clone(), +).

See the StackOverflow response that got me out of trouble: https://stackoverflow.com/questions/30154541/how-do-i-concatenate-strings

That's it for now, right at the bottom of the steep learning curve.

Time to read the manual.




Guillaume / 2021-01-17 / Santiago, Chile