Skip to content

Commit

Permalink
New example
Browse files Browse the repository at this point in the history
  • Loading branch information
connorslade committed Sep 20, 2023
1 parent f270386 commit fbd77fb
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rust-analyzer.linkedProjects": [
".\\examples\\basic\\Cargo.toml"
]
}
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Coming Soon
- Accept `Into<Header>` in `Context::header` and `Response::header`.
- Create 'header structs' that can be converted into a `Header` and simplify working with headers in responses.
- Use [loopback](https://tools.ietf.org/html/rfc1122), [private](https://tools.ietf.org/html/rfc1918), and [unique local](https://tools.ietf.org/html/rfc4193) addresses for RealIp
- Add is_informational, is_success, is_redirect, is_client_error, and is_server_error methods on status codes.

# 2.2.1

Expand Down
160 changes: 160 additions & 0 deletions examples/basic/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 examples/basic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "basic"
version = "0.1.0"
edition = "2021"

[dependencies]
afire = { path = "../..", features = ["extensions"] }
rand = "0.8.5"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
119 changes: 119 additions & 0 deletions examples/basic/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::{
collections::HashMap,
error,
sync::{Arc, RwLock},
};

use afire::{
extensions::{Logger, RealIp, ServeStatic},
middleware::Middleware,
trace::{set_log_level, Level},
Content, Method, Request, Response, Server,
};
use rand::Rng;
use serde::Deserialize;
use serde_json::json;

// The App struct will be used as the state for our server.
// A reference to the state is passed to middleware and route handlers.
struct App {
// Records the number of times each page has been visited.
analytics: RwLock<HashMap<String, u64>>,
}

fn main() -> Result<(), Box<dyn error::Error>> {
// Show some helpful information during startup.
set_log_level(Level::Trace);

// Create a new afire server on localhost:8080 with 4 worker threads and the App state.
let mut server = Server::<App>::new("localhost", 8080)
.workers(4)
.state(App::new());

// Add extension to serve static files from the ./static directory.
ServeStatic::new("./static").attach(&mut server);

// Add middleware to log requests to the console.
Logger::new().attach(&mut server);

// Add our analytics middleware.
Analytics::new(server.app()).attach(&mut server);

// Add a route at GET /greet/{name} that will greet the user.
server.route(Method::GET, "/greet/{name}", |ctx| {
let name = ctx.param("name");
ctx.text(format!("Hello, {}!", name)).send()?;
Ok(())
});

// Add a route to respond with the client's IP address.
server.route(Method::GET, "/ip", |ctx| {
// Get the client's IP address, even if they are behind a reverse proxy.
let ip = ctx.real_ip();
ctx.text(format!("Your IP is: {}", ip)).send()?;
Ok(())
});

// Add an api route to pick a random number.
server.route(Method::GET, "/random", |ctx| {
#[derive(Deserialize)]
struct Request {
min: u32,
max: u32,
}

let data = serde_json::from_slice::<Request>(&ctx.req.body)?;
let num = rand::thread_rng().gen_range(data.min..=data.max);

ctx.text(json!({ "number": num }))
.content(Content::JSON)
.send()?;
Ok(())
});

server.route(Method::GET, "/analytics", |ctx| {
let res = json!(*ctx.app().analytics.read().unwrap());
ctx.text(res).content(Content::JSON).send()?;
Ok(())
});

server.run()?;
Ok(())
}

struct Analytics {
// Store a reference to the app state so we can access it in the middleware.
app: Arc<App>,
}

impl Middleware for Analytics {
// Pre middleware is run before the route handler.
// You can modify the request, return a response or continue to the next middleware or route handler.
fn end(&self, req: Arc<Request>, res: &Response) {
// Only record successful requests.
if !res.status.is_success() {
return;
}

// Update the analytics with the path of the request.
let mut analytics = self.app.analytics.write().unwrap();
analytics
.entry(req.path.to_owned())
.and_modify(|x| *x += 1)
.or_insert(1);
}
}

impl App {
fn new() -> Self {
Self {
analytics: RwLock::new(HashMap::new()),
}
}
}

impl Analytics {
fn new(app: Arc<App>) -> Self {
Self { app }
}
}
Binary file added examples/basic/static/favicon.ico
Binary file not shown.
23 changes: 23 additions & 0 deletions examples/basic/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>👋 Hello World</title>
</head>
<body>
<h1>👋 Hello World</h1>
<p>
Welcome to <a href="https://github.com/Basicprogrammer10/afire">afire</a>!
</p>

<h2>Routes</h2>
<ul>
<li><a href="/greet/World">Greet</a></li>
<li><a href="/ip">Your Ip</a></li>
<li><a href="/random">Random Number</a></li>
<li><a href="/analytics">Analytics</a></li>
</ul>
</body>
</html>
Binary file removed examples/old-examples/basic/data/favicon.ico
Binary file not shown.
12 changes: 0 additions & 12 deletions examples/old-examples/basic/data/index.html

This file was deleted.

7 changes: 7 additions & 0 deletions lib/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,18 @@ impl<State: 'static + Send + Sync> Context<State> {
return Err(HandleError::ResponseAlreadySent.into());
}

// TODO: Post middleware
self.response
.force_lock()
.write(self.req.socket.clone(), &self.server.default_headers)?;
self.flags.set(ContextFlag::ResponseSent);

// TODO: End middleware
let res = self.response.force_lock();
for i in self.server.middleware.iter() {
i.end_raw(Ok(self.req.clone()), &res);
}

if self.flags.get(ContextFlag::GuaranteedSend) {
self.req.socket.unlock();
}
Expand Down
8 changes: 7 additions & 1 deletion lib/extensions/real_ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use std::net::{IpAddr, Ipv6Addr};

use crate::{HeaderName, Request};
use crate::{Context, HeaderName, Request};

/// Trait that adds methods for getting the real IP of a client through a reverse proxy.
/// If you are using the "X-Forwarded-For" header you can use `req.real_ip()` but if you are using a different header you will have to use `req.real_ip_header(...)`.
Expand Down Expand Up @@ -53,6 +53,12 @@ impl RealIp for Request {
}
}

impl<State: Send + Sync> RealIp for Context<State> {
fn real_ip_header(&self, header: impl Into<HeaderName>) -> IpAddr {
self.req.real_ip_header(header)
}
}

fn is_local(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => ip.is_loopback() || ip.octets()[0] == 172 || ip.is_private(),
Expand Down
Loading

0 comments on commit fbd77fb

Please sign in to comment.