Contents management server
In this section, we extend the static file server to a contents management server. We will add POST and DELETE method to enable CRUD (Create, Read, Update and Delete).
API design
- Read: GET
/files/<filename>
- Serve the file with the given name
<filename>
on the server. - We have already implemented this in the previous section.
- Serve the file with the given name
- Create and Update:
POST /edit {path:"<filename>", body: "<contents>"}
- Post the file path
<filename>
and the contents of the file<contents>
and create or update the file on the server.
- Post the file path
- Delete: DELETE
/edit?path=<filename>
- Delete the file
<filename>
on the server
- Delete the file
Add dependencies
Add some dependencies to handle json.
# Cargo.toml
json = "0.12"
serde = { version = "1.0", features = ["derive"] } # to serialize/deserialize
serde_json = "1.0"
urlencoding = "2.1.0" # For encoding the filename
Encoding filename
To make it easy to handle files, we encode their names with urlencodings::encode
Add
// src/main.rs
// Newly added here
use std::fs::File;
use std::io::prelude::*;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use urlencoding;
/// Get the new path <root_dir>/<encoded filename>
fn get_path(root_dir: &str, filename: &str) -> PathBuf {
let encoded = urlencoding::encode(&filename); // encode the filename
Path::new(&root_dir).join(Path::new(&encoded.into_owned()))
}
to the src/main.rs
Handle POST method
When we get POST request, then we need to
- obtain the file name and the contents to update from the body of the request and
- update the file with the contents.
We use JSON to send the request with the file name and the new contents as
{
"path": "<the file name of to update>",
"body": "<the contents to be replaced>"
}
Therefore, we firstly add struct
deriving Serialize
and Deserialize
to/from JSON from/to String (html body).
// src/main.rs
#[derive(Debug, Serialize, Deserialize)]
struct NewPageObj {
path: String,
body: String,
}
Then add the function handles POST method.
// src/main.rs
/// Create and Update the file with POST method
async fn post(item: web::Json<NewPageObj>) -> Result<HttpResponse, Error> {
println!("post {:?}", item);
// Get the file path
let path: PathBuf = get_path("public", &item.path);
println!("path: {:?}", path);
// Update the file with the given contents
let mut file = File::create(&path)?;
file.write_all(item.body.as_bytes())?;
// TODO: navigate to the new page created
Ok(HttpResponse::Ok().json("created")) // <- send json response
}
Finally, add the function for routing.
// src/main.rs
// **Newly added here**
// POST the new contents to update the file
.service(web::resource("/edit").route(web::post().to(post)))
Test
Run
cargo run
Test it with
curl -H 'content-type: application/json' -kX POST -d \
'{"path": "filename", "body": "new contents"}' \
https://localhost:8443/edit
and then check with
curl -kX GET https://127.0.0.1:8443/files/filename
Handle DELETE method
We handle DELETE method as well as the POST method. When we get DELETE request, then we need to
- obtain the file name to delete from the the request and
- delete the file.
Http DELETE method (basically) does not have it’s body. Thus, we need to use the other way other than obtaining the file name information from the body.
We are going to use query parameters this time. The request is as follows:
https://127.0.0.1:8443/edit?path=<filename>
Therefore, we firstly add struct
deriving Serialize
and Deserialize
to/from JSON from/to String (query parameters).
// src/main.rs
#[derive(Debug, Serialize, Deserialize)]
struct QueryPath {
path: String,
}
Then add the function handles POST method.
// src/main.rs
/// Delete the file with DELETE method
async fn delete(item: web::Query<QueryPath>) -> Result<HttpResponse, Error> {
println!("delete ? {:?}", item);
let path: PathBuf = get_path("public", &item.path);
println!("path: {:?}", path);
std::fs::remove_file(&path)?;
// TODO: navigate to the root page
Ok(HttpResponse::Ok().json("deleted"))
}
Finally, add the function for routing.
// src/main.rs
// POST the new contents to update the file
.service(web::resource("/edit")
.route(web::post().to(post))
.route(web::delete().to(delete)) // Newly added here
)
Test
Run
cargo run
Test it with
curl -kX DELETE https://localhost:8443/edit?path=filename
and then check with
curl -kX GET https://127.0.0.1:8443/files/filename
which should not work (file not found)