Skip to content

Commit

Permalink
Added caching capabilities on images and posts on server using redis.
Browse files Browse the repository at this point in the history
  • Loading branch information
luyangliuable committed Jun 1, 2024
1 parent 3af4855 commit 220494e
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 22 deletions.
32 changes: 32 additions & 0 deletions server/Cargo.lock

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

3 changes: 1 addition & 2 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ hyper = "1.2.0"
bytes = "1.1"
reqwest = { version = "0.11.27", features = ["json", "stream"] }
tokio-util = {version="0.7.10", features=["io"]}


redis = "0.25.3"

[dependencies.mongodb]
version = "2.2.0"
Expand Down
11 changes: 9 additions & 2 deletions server/src/api/local_image.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use rocket::{http::{ContentType, Status}, serde::json::Json, State, form::FromForm};
use mongodb::{bson::doc, results::{InsertOneResult, UpdateResult}};
use crate::{models::local_image_model::LocalImage, controller::local_image_controller::LocalImageController};
use redis::{Commands, Client, RedisResult};

#[post("/image", data = "<new_image>")]
pub fn index_local_image(controller: &State<LocalImageController>, new_image: Json<LocalImage>) -> Result<Json<InsertOneResult>, Status> {
Expand All @@ -18,8 +19,14 @@ pub struct GetLocalImageQuery {
}

#[get("/image/<id>?<query..>")]
pub fn get_local_image(id: String, controller: &State<LocalImageController>, query: GetLocalImageQuery) -> Result<(ContentType, Vec<u8>), Status> {
controller.get(id, query.compression.unwrap_or(100))
pub fn get_local_image(
id: String,
redis: &State<redis::Client>,
controller: &State<LocalImageController>,
query: GetLocalImageQuery,
) -> Result<(ContentType, Vec<u8>), Status> {
let mut con = redis.get_connection().map_err(|_| Status::InternalServerError)?;
controller.get(id.clone(), query.compression.unwrap_or(100), &mut con)
}

#[put("/image/<id>", data = "<new_image>")]
Expand Down
5 changes: 3 additions & 2 deletions server/src/api/posts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use crate::{models::post_model::Post, controller::post_controller::PostControlle
use mongodb::{bson::doc, results::{ UpdateResult, InsertOneResult, DeleteResult }};

#[get("/posts/<id>")]
pub async fn get_post(controller: &State<PostController>, id: String) -> Result<Json<Post>, Status> {
controller.inner().get(id)
pub async fn get_post(controller: &State<PostController>, id: String, redis: &State<redis::Client>) -> Result<Json<Post>, Status> {
let mut con = redis.get_connection().map_err(|_| Status::InternalServerError)?;
controller.inner().get(id, &mut con)
}

#[get("/posts")]
Expand Down
27 changes: 26 additions & 1 deletion server/src/controller/local_image_controller.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rocket::{http::{ContentType, Status}, serde::json::Json};
extern crate log;
use redis::{Commands, Client, RedisResult};
use mongodb::{bson::{doc, to_bson, oid::ObjectId}, results::{InsertOneResult, UpdateResult}};
use std::str::FromStr;
use std::io::{BufWriter, Cursor};
Expand Down Expand Up @@ -70,8 +71,21 @@ impl LocalImageController {
Ok(compressed_image)
}

pub fn get(&self, id: String, compression_percentage: u8) -> Result<(ContentType, Vec<u8>), Status> {
pub fn get(
&self,
id: String,
compression_percentage: u8,
redis_con: &mut redis::Connection
) -> Result<(ContentType, Vec<u8>), Status> {
let object_id = ObjectId::from_str(&id).expect("Failed to convert id to ObjectId");

// If already cached return cached version instead
let key = format!("{}_{}", id, compression_percentage);
let value: Option<Vec<u8>> = redis_con.get(&key).unwrap_or(None);
if let Some(image) = value {
return Ok((ContentType::JPEG, image));
}

let local_image_result = match self.repo.0.get(object_id) {
Ok(local_image) => local_image,
Err(_) => return Err(Status::NotFound),
Expand All @@ -89,19 +103,30 @@ impl LocalImageController {
"png" => ContentType::PNG,
"jpg" | "jpeg" => ContentType::JPEG,
"gif" => {
let _: () = Self::cache_data(redis_con, &key, file_bytes.clone())?;
return Ok((ContentType::GIF, file_bytes))
},
"pdf" => {
let _: () = Self::cache_data(redis_con, &key, file_bytes.clone())?;
return Ok((ContentType::PDF, file_bytes))
},
_ => return Err(Status::UnsupportedMediaType),
};
let img = image::load_from_memory(&file_bytes).map_err(|_| Status::InternalServerError)?;
let resized_img = self.resize_image(&img, content_type.clone(), compression_percentage);
let compressed_image = self.compress_image(resized_img, content_type.clone(), compression_percentage)?;

// Cache data for next time
let _: () = Self::cache_data(redis_con, &key, compressed_image.clone())?;

Ok((content_type, compressed_image))
}

fn cache_data(redis_con: &mut redis::Connection, key: &String, data: Vec<u8>) -> Result<(), Status> {
let _: () = redis_con.set(&key, data).map_err(|_| Status::InternalServerError)?;
Ok(())
}

pub fn update(&self, id: String, new_image: LocalImage) -> Result<Json<UpdateResult>, Status> {
let mut new_image_data = new_image;

Expand Down
43 changes: 35 additions & 8 deletions server/src/controller/post_controller.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use rocket::http::Status;
use rocket::serde::json::Json;
use async_trait::async_trait;
use redis::Commands;
use std::str::FromStr;
use crate::{utils::markdown_util, models::post_model::Post, repository::post_repo::PostRepo, controller::traits::controller::Controller};
use crate::{utils::markdown_util, models::post_model::Post, repository::post_repo::PostRepo};

use mongodb::{
bson::{doc, to_bson, oid::ObjectId },
Expand All @@ -18,17 +18,44 @@ impl PostController {
PostController { repo }
}

pub fn get(&self, id: String) -> Result<Json<Post>, Status> {

fn cache_data(redis_con: &mut redis::Connection, key: &String, data: Post) -> Result<(), Status> {
match serde_json::to_string(&data) {
Ok(json_string) => {
let _: () = redis_con.set(&key, json_string).map_err(|_| Status::InternalServerError)?;
}
Err(e) => {
return Err(Status::InternalServerError)
},
};

Ok(())
}

pub fn get(&self, id: String, redis_con: &mut redis::Connection) -> Result<Json<Post>, Status> {

// check if cached file exists
let value: Option<String> = redis_con.get(&id).unwrap_or(None);
if let Some(res) = value {
let post: Post = serde_json::from_str(&res).unwrap();
return Ok(Json(post));
}

ObjectId::from_str(&id)
.map_err(|_| Status::BadRequest)
.and_then(|object_id| self.repo.0.get(object_id).map_err(|_| Status::InternalServerError))
.and_then(|blog_post| match blog_post.active {
Some(false) => Err(Status::NotFound),
_ => Ok(blog_post),
})
.and_then(|blog_post| {
match blog_post.active {
Some(false) => Err(Status::NotFound),
_ => Ok(blog_post)
}
markdown_util::get_post_content_for_post(blog_post)
.map_err(|_| Status::NotFound)
.and_then(|blog_post_content| {
let _: () = Self::cache_data(redis_con, &id, blog_post_content.clone())?;
Ok(blog_post_content)
})
})
.and_then(|blog_post| markdown_util::get_post_content_for_post(blog_post).map_err(|_| Status::NotFound))
.map(Json)
}

Expand Down
4 changes: 4 additions & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ fn index() -> &'static str {
async fn rocket() -> _ {
println!("Starting server...");

let redis = redis::Client::open("redis://127.0.0.1/").unwrap();

// Initialize database repositories.
let blog_repo = BlogRepo::init();
let post_repo = PostRepo(MongoRepo::<Post>::init("Post", &*DB).await);
Expand All @@ -50,6 +52,8 @@ async fn rocket() -> _ {

// TODO: Convert this into a toml file and load it
rocket::build()
.manage(redis)

.manage(blog_repo)
.manage(user_repo)
.manage(message_repo)
Expand Down
2 changes: 1 addition & 1 deletion server/src/models/post_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rocket::serde::{Serialize, Deserialize};
use chrono::{Utc, DateTime};
use crate::models::utils::date_format::date_format;

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Post {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
Expand Down
7 changes: 1 addition & 6 deletions server/src/utils/markdown_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn markdown_store_location() -> Result<String, Error> {
util::get_env_variable(env_variable_key)
}

pub fn get_path_of_stored_markdown(mut previous_post: &Post) -> Result<String, Error> {
pub fn get_path_of_stored_markdown(previous_post: &Post) -> Result<String, Error> {
let store_location = markdown_store_location()?;

// comment this out because I decided with with my own file names instead of uuid for ease of identification
Expand All @@ -21,21 +21,16 @@ pub fn get_path_of_stored_markdown(mut previous_post: &Post) -> Result<String, E
// };

let file_name = &previous_post.file_name;

let year = &previous_post.year;
let month = &previous_post.month;

Ok(format!("{}/{}/{}/{}.md", store_location, year, month, file_name))
}

pub fn get_post_content_for_post(mut previous_post: Post) -> Result<Post, Error> {
let path = get_path_of_stored_markdown(&previous_post)?;

let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;

previous_post.body = contents;

Ok(previous_post)
}

0 comments on commit 220494e

Please sign in to comment.