Skip to content

Commit

Permalink
Handle content too large correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
levkk committed Nov 11, 2024
1 parent 172ead8 commit f1a9e9d
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 24 deletions.
10 changes: 9 additions & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ members = [
"rwf-tests",
"examples/request-tracking",
"examples/engine",
"rwf-admin",
"rwf-admin", "examples/files",
]
exclude = ["examples/rails", "rwf-ruby", "examples/django"]
8 changes: 8 additions & 0 deletions examples/files/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "files"
version = "0.1.0"
edition = "2021"

[dependencies]
rwf = { version = "0.1.7", path = "../../rwf" }
tokio = { version = "1", features = ["full"] }
Empty file.
37 changes: 37 additions & 0 deletions examples/files/src/controllers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use rwf::prelude::*;

#[derive(Default, macros::PageController)]
pub struct Upload;

#[async_trait]
impl PageController for Upload {
async fn get(&self, _req: &Request) -> Result<Response, Error> {
render!("templates/upload.html")
}

async fn post(&self, req: &Request) -> Result<Response, Error> {
let form_data = req.form_data()?;
if let Some(file) = form_data.file("file") {
let redirect = format!("/ok?name={}&size={}", file.name, file.body.len());
Ok(Response::new().redirect(redirect))
} else {
Ok(Response::bad_request())
}
}
}

#[derive(Default)]
pub struct UploadOk;

#[async_trait]
impl Controller for UploadOk {
async fn handle(&self, req: &Request) -> Result<Response, Error> {
let name = req.query().get_required::<String>("name")?;
let size = req.query().get_required::<i64>("size")?;

render!("templates/ok.html",
"name" => rwf::http::urlencode(&name),
"size" => size
)
}
}
17 changes: 17 additions & 0 deletions examples/files/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mod controllers;
mod models;

use rwf::http::{self, Server};
use rwf::prelude::*;

#[tokio::main]
async fn main() -> Result<(), http::Error> {
Logger::init();

Server::new(vec![
route!("/" => controllers::Upload),
route!("/ok" => controllers::UploadOk),
])
.launch("0.0.0.0:8000")
.await
}
1 change: 1 addition & 0 deletions examples/files/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Empty file added examples/files/static/.gitkeep
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions examples/files/templates/head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<head>
<%= rwf_head() %>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script async src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</head>
26 changes: 26 additions & 0 deletions examples/files/templates/ok.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<html lang="en-US">
<%% "templates/head.html" %>
<body>
<div class="container my-5">
<h3 class="mb-4">Upload successful</h3>
<table>
<thead>
<tr>
<th>File name</th>
<th>File size</th>
</tr>
</thead>
<tbody>
<tr>
<td class="pe-5"><%= name %></td>
<td class=""><%= size %> bytes</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-end">
<a href="/" class="btn btn-primary btn-lg">Back</a>
</div>
</div>
</body>
</html>
27 changes: 27 additions & 0 deletions examples/files/templates/upload.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en-US">
<%% "templates/head.html" %>
<body>
<div class="container my-5">
<h3 class="mb-4">Upload file</h3>
<form method="post" action="/" enctype="multipart/form-data">
<%= csrf_token() %>
<div class="mb-3">
<label class="form-label">Comment</label>
<input type="text" name="comment" class="form-control">
</div>

<div class="mb-3">
<label class="form-label">Upload file</label>
<input type="file" name="file" class="form-control">
</div>

<div class="d-flex justify-content-end">
<button class="btn btn-primary" type="submit">
Upload
</button>
</div>
</form>
</div>
</body>
</html>
2 changes: 1 addition & 1 deletion rwf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rwf"
version = "0.1.6"
version = "0.1.7"
edition = "2021"
license = "MIT"
description = "Framework for building web applications in the Rust programming language"
Expand Down
6 changes: 4 additions & 2 deletions rwf/src/http/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use thiserror::Error;

use super::Head;

#[derive(Error, Debug)]
pub enum Error {
#[error("io error: {0}")]
Expand Down Expand Up @@ -39,15 +41,15 @@ pub enum Error {
Forbidden,

#[error("content too large")]
ContentTooLarge,
ContentTooLarge(Head),
}

impl Error {
pub fn code(&self) -> u16 {
match self {
Self::MissingParameter => 400,
Self::Forbidden => 403,
Self::ContentTooLarge => 413,
Self::ContentTooLarge(_) => 413,
_ => 500,
}
}
Expand Down
15 changes: 14 additions & 1 deletion rwf/src/http/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,22 @@ impl Request {
pub async fn read(peer: SocketAddr, mut stream: impl AsyncRead + Unpin) -> Result<Self, Error> {
let head = Head::read(&mut stream).await?;
let content_length = head.content_length().unwrap_or(0);

// Handle requests which are too large.
if content_length > get_config().general.max_request_size {
return Err(Error::ContentTooLarge);
// Throw away whatever we receive.
let mut throw_away = vec![0u8; 4096];
let mut content_length = content_length as i64;
loop {
let read = stream.read(&mut throw_away).await?;
content_length -= read as i64;
if content_length <= 0 {
break;
}
}
return Err(Error::ContentTooLarge(head));
}

let mut body = vec![0u8; content_length];
stream
.read_exact(&mut body)
Expand Down
50 changes: 32 additions & 18 deletions rwf/src/http/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,24 @@ impl Server {
loop {
let request = match Request::read(peer_addr, &mut stream).await {
Ok(request) => request,
Err(err) => {
Err(ref err) => {
match err {
Error::ContentTooLarge(head) => {
let response = Response::content_too_large();
let _ = Self::send_response(&mut stream, response).await;

info!(
"{} {} {} 413",
head.method().to_string().purple(),
head.path().base().purple(),
std::any::type_name::<Self>().green(),
);
}

_ => (),
}
debug!(
"{} client {:?} disconnected: {:?}",
"{} client {:?} disconnected: {}",
"http".purple(),
peer_addr,
err
Expand Down Expand Up @@ -140,15 +155,8 @@ impl Server {
// Log request.
Self::log(&request, handler.controller_name(), &response, duration);

// Send reply to client.
match response.send(&mut stream).await {
Ok(_) => (),
Err(err) => {
debug!("{} error {:?}", peer_addr, err);
}
}

if stream.flush().await.is_err() {
if let Err(err) = Self::send_response(&mut stream, response).await {
debug!("{} error {:?}", peer_addr, err);
break;
}

Expand All @@ -174,13 +182,9 @@ impl Server {
Self::log(&request, std::any::type_name::<Self>(), &response, duration);

// Send reply to client.
match response.send(&mut stream).await {
Ok(_) => {
let _ = stream.flush().await;
}
Err(err) => {
debug!("{} error {:?}", peer_addr, err);
}
if let Err(err) = Self::send_response(&mut stream, response).await {
debug!("{} error {:?}", peer_addr, err);
break;
}
}
}
Expand All @@ -203,4 +207,14 @@ impl Server {
duration,
);
}

async fn send_response(
mut stream: impl AsyncWrite + Unpin,
response: Response,
) -> Result<(), Error> {
response.send(&mut stream).await?;
stream.flush().await?;

Ok(())
}
}

0 comments on commit f1a9e9d

Please sign in to comment.