triston-notes/Cards/dev/Rust http server.md
2023-10-21 18:52:54 -05:00

309 lines
7.5 KiB
Markdown

---
banner: "https://images.unsplash.com/photo-1451187580459-43490279c0fa?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2372&q=80"
---
up:: [[Rust]]
X::[[Boilerplate Code]]
tags:: #boilerplate
# Rust HTTP Server
### http/1.1
- [L7 Protocol](https://www.cloudflare.com/learning/ddos/what-is-layer-7/)
- Sent Over [TCP](https://www.geeksforgeeks.org/what-is-transmission-control-protocol-tcp/)
- Message based (client REQUEST / server RESPONSE)
REQUEST
GET /search?name=abc
HTTP/1.1
Accept: text/html
RESPONSE
HTTP/1.1 200 OK
Content-Type: text/html
`<html></html>`
---
### Architecture
[[http_server.excalidraw]]
---
### Implimentation
in the main function we want to create a server and run it
```rust
fn main() {
let server = Server::new("localhost:8080");
server.run();
}
```
the only problem is, we dont yet have a Server struct, so lets create one
```rust
struct Server{
addr: String
}
```
we add addr property - but now we need to add some functionality
```rust
impl Server {
fn new(addr: String) -> Server {
Server { // this is implicit return Server
addr: addr // or just put addr since names are same
}
}
}
// can also use word Self for Server
fn new(addr: String) -> Self{
Self { }
}
// does the same thing
```
cool, now we have our constructor function - but now we need to impliment the run method
```rust
impl Server {
fn run(self) { // lower case self is just this instance
println!("Server listening on: {}", &self.addr[10..]); // &self is because we just want to borrow the intance
loop {
println!("Looping");
std::thread::sleep(std::time::Duration::from_secs(1));
// we want to create an infinite loop and sleep the iterations so as to not hammer the cpu
}
}
}
```
So now we need to create the Request concept. We will use another struct for this
```rust
struct Request {
path: String,
query_string: String,
method: Method // <-- whats this?
}
// method is enum, which looks like this
enum Method {
GET, PUT, POST, DELETE
}
```
But how do we use the ENUM?
```rust
let get = Method::GET;
```
Cool, but what if we dont have a query_string passed in the request? Theres a solution for that:
```rust
query_string = Option<String>;
// Option will make it possible to have a NONE value because its an optional generic
// This is what option looks like
pub enum Option<T> {
None,
Some(T),
}
// this is great for typing a none wihout the fear of Null pointer exceptions
```
---
### Modules
Our code is now getting pretty long, so we will split our code into 'modules'. Modules control the visibility of code. Modules can be public or private.
We can create an inline module with the `mod` keyword
Modules look similar to Namespaces in other languages
```rust
mod server {
stuct Server {...}
impl Server {...}
}
```
But now, server is a private module, therefore the Server struct isn't usable from the outside i.e. our Main function.. We can fix that by making the parts we want public to be public by adding the `pub` keyword to them, like so:
```rust
mod server {
pub stuct Server {...}
impl Server {
pub fn new() {...}
pub fn run() {...}
fn destroy() {...}
}
}
```
>Keep in mind, if you have a module within another module, and need to access it publically, you can prepend `pub` to that submodule as well
> Modules act just like files as far as imports go. You can use the `use` keyword to import other modules into a module even within the same file
---
### Break up into seperate files
create a new `server.rs` file and copy the contents of your server module into the server.rs file
WAIT! So we dont need to copy the `mod` stuff?
NOPE - every file is treated like a module WOOT WOOT easy peezy!
> when using a module in a file from a module thats in another file, you still need to define the module in the file like so:
```rust
user server::Server;
mod server;
```
##### Folder modules
Folders can be used at modules for nesting sub modules. But if you do it that way, you must include a `mod.rs` file so the compiler knows this is a moudle folder. Similar with the `__init__.py` file in python modules. Then you expose the interfaces you want exposed in the mod.rs file like so:
```rust
// mod.rs
pub mod request;
pub mod method;
// but this still requires you to import them like this:
use http::method::Method;
// we want to just do http::Method
// to do that, use the modules as well
// 👇🏻👇🏻👇🏻👇🏻👇🏻
pub use request::Request;
pub use method::Method;
pub mod request;
pub mod method;
```
---
### TCP Communication
[NET Module](https://doc.rust-lang.org/stable/std/net/)
```rust
use std::net::{TcpListener, TcpStream};
let listener = TcpListener::bind(&self.addr).unwrap(); // unwrap will terminate application if errors
```
We want to do something on the new listener, so lets create an infinite loop to continuosly listen for events
> Note: accept() returns a result.. But we dont want to kill the program if it errors, so we handle that
```rust
let listener = ...
loop{
let res = listener.accept();
if res.is_err(){
continue;
}
let stream = res.unwrap(); // we can now safely unwrap since we error checked already
}
```
stream is actually a Tuple, so lets destructure that:
```rust
let (stream, addr) = res.unwrap();
```
A *Better* way to handle results in rust is to use the `match` keyword:
```rust
loop{
match listener.accept(){
Ok((stream, _addr)) => stream,
Err(e) => println!('error: {}', e)
}
}
```
---
### Result Enum
This is the shape of the generic Result Enum - The result enum is auto included into every rust file.
```rust
pub enum Result<T, E> {
Ok(T),
Err(E),
}
```
---
### Loop
In rust, to create a generic infinite loop, you can just use the `loop` keyword, which is equivalent to a `while (true)`.
```rust
loop{
// do something over and over
}
```
We can also label the loops, in case, say, you have a nested loop:
```rust
'outer: loop{
'inner: loop{
break 'outer;
// continue 'outer;
}
}
```
---
### Tuple
Similar like tuples in python - except they have a fixed length, cannot grow or shrink in size
```rust
let tup = ('a', 5, listener) // has fixed length 3
```
---
### Match
Match is essentially a switch statement:
```rust
match 'abcd'{
'abcd' => println!('abcd'),
'abc' => {},
_ => {} // default
}
```
---
### Array
Arrays are also, like tuples, fixed in size and types
```rust
let arr: [u8; 3] = [1,2,3]
```
If you want to use an array as parameters, you should always pass a reference to an array: a slice:
```rust
fn arr(a: &[u8]) {} // now we dont need fixed length
// with fixed length
fn arr(a: [u8; 5]) {}
```
---
### Type Conversion
[STD Convert](https://doc.rust-lang.org/stable/std/convert/)
Rust standard library has a builtin type conversion library
```rust
use std::convert;
```
---
### Options unwrapping
There are different ways to do the same thing in rust. For example, if you get an `Option` returned from a function, you can do a match on it, you can call methods on it, or some other weird thing. Take a look:
```rust
// this is a match, for explicitly checking the some and none variants of the option return type
match path.find('?') {
Some(i) => {},
None => {}
}
// Since option has a method called is_some, we can check if this option has None'd out or not, and if not, do some stuff
let q = path.find('?');
if q.is_some(){
query_string = Some(&path[i+1..]);
path = &path[..i];
}
// This is wierder syntax here, we are conditionally setting a variable if there is SOME-thing, otherwise this condition will None out and not execute
if let Some(i) = path.find('?'){
query_string = Some(&path[i+1..]);
path = &path[..i];
}
```
---
## BackLinks