afire Update V1.2.0
published 06/24/2022 • 3m reading time • 311 viewsIt’s been over two months and 48 commits since the last afire release and I have some new and somewhat interesting features to show you!
Info
As always, a full update changelog is on GitHub here.
New Features
Server wide state
Now instead of passing around an Arc to all of your route definitions you can now use the built-in state system! Let’s look at a simple example to just count up with each request.
#[derive(Default)]
struct App {
count: AtomicUsize,
}
fn main() {
let mut server = Server::<App>::new("localhost", 8080).state(App::default());
// Add a stateful catch all route that takes in state and the request
server.stateful_route(Method::ANY, "**", |app, _req| {
// Respond with and increment request count
Response::new().text(app.count.fetch_add(1, Ordering::Relaxed))
});
// Start the server
// This will block the current thread
server.start().unwrap();
}
Because the state is defined with generics if you want to make an afire app without a state you will now have to make the server like this Server::<()>::new(...)
.
I spent a lot of time thinking about if this was acceptable and eventually decided that it is a small price to pay for the convenience of a server wide state in bigger projects.
Error Handling
The internal Error handling system got a total rewrite. Errors can now be sent to the client on the occurrence of an error Parsing the HTTP message, handling the request or an IO error. Instead of the old way of just falling back to default values. This new error enum can be defined as such:
/// Errors that can occur,,,
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
/// Error while handling a Request
Handle(Box<HandleError>),
/// Error while parsing request HTTP
Parse(ParseError),
/// IO Errors
Io(io::ErrorKind),
/// Response does not exist (probably because of an error with the request)
None,
}
/// Errors thet can arize while handling a request
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HandleError {
/// Route matching request path not found
NotFound(Method, String),
/// A route or middleware paniced while running
Panic(Box<Result<Request>>, String),
}
/// Error that can occur while parsing the HTTP of a request
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
/// No `\r\n\r\n` found in request to separate metadata from body
NoSeparator,
/// No Method found in request HTTP
NoMethod,
/// No Path found in request HTTP
NoPath,
/// No Version found in request HTTP
NoVersion,
/// No Request Line found in HTTP
NoRequestLine,
/// Invalid Query in Path
InvalidQuery,
/// Invalid Header in Request HTTP
InvalidHeader(usize),
}
In the case of a Parse or IO error afire will send a response informing the client of the error. As in previous versions you can write your own handler to handle errors
Request ID Middleware
A new extension has been added to afire: Request ID. It just adds a header of a definable name to each incoming request with a number. thats all,,, 🤷♂️ hey it could be useful.
Cache Middleware
Another new extension was added to cache responses. It stores the route paths with the response and uses this to allow for future requests to the same path and method within a timeout to be cached. You can use the extension like this:
Cache::new()
// Cache paths that start with `/cache`
.to_cache(|req| req.path.starts_with("/cache"))
// Disable timeout
.timeout(0)
.attach(&mut server);
Changes
Middleware Organization
In previous versions of afire there were specific features for every built-in extension.
This has been changed to have them all under the extensions
feature.
And instead of using afire::ServeStatic to access one of the extensions you will use afire::extensions::ServeStatic.
Middleware
In previous versions Middleware would take in plain Requests / Responses, now in an effort to reduce cloning the request references are used. Because of the new error handling system outlined earlier the requests take in an afire::Result.
/* OLD */ fn pre(&self, req: Request) -> MiddleRequest
/* NEW */ fn pre(&self, req: &Result<Request>) -> MiddleRequest
/* OLD */ fn post(&self, req: Request, res: Response) -> MiddleResponse
/* NEW */ fn post(&self, req: &Result<Request>, res: &Result<Response>) -> MiddleResponse
/* OLD */ fn end(&self, req: Request, res: Response)
/* NEW */ fn end(&self, req: &Result<Request>, res: &Response)
In your middleware you can match the request, and if it is not Ok() then you can just Continue. If you are feeling extra lazy you can just unwrap and leave your error handler to deal with it :p.
Serve Static
The built-in ServeStatic extension got a bit of a cleanup and some new features! One of which is that you can now set the serve path so static content is not just served on the root. This could allow for serving static content from different places at multiple different web paths.
ServeStatic::new("data/static")
// Set serve path
.path("/static")
// Attatch it to the afire server
.attach(&mut server);
MISC
Now a lot more functions are taking in AsRef<str>
instead of Display
because they are intended to take in strings not just any displayable type.
The Rate limit extension got a performance enhancement when running with thread pool.
Request parsing had gotten a performance boost and should now use less memory.
When afire is building the HTTP response it will not add the Content-Length or any default headers that are already present.
🗑 Removed Things
The ignore_trailing_path_slash
feature was removed in favor or it always being active.
Removed the unhinged build script that I used for generating the README from the lib.rs doc comments.
Turns out that you can import files as doc comments: #![doc = include_str!("../README.md")]
.
Removed the Request.raw_data field because it was about doubling the size of the Request instance.
Removed the Request::body_string function in favor of String::from_utf8() because it gives more control if you want to use the from_utf8_lossy,
Conclusion
welp,,, thats all folks.
This update had a lot of good changes, hopefully I release version 1.3.0 next month. Hopefully I get to adding socket keep alive support which would make it about as fast as actix, which would be really cool. I’m also looking into data streaming for requests and responses so it doesn’t need to load lots of data into memory to send it through the socket.