Make a request() utility

This commit is contained in:
spikecodes 2020-11-18 18:50:59 -08:00
parent f455e2095d
commit 0054557c86
8 changed files with 105 additions and 64 deletions

34
Cargo.lock generated
View File

@ -71,7 +71,7 @@ dependencies = [
"log", "log",
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project 1.0.1", "pin-project 1.0.2",
"rand", "rand",
"regex", "regex",
"serde", "serde",
@ -239,7 +239,7 @@ dependencies = [
"fxhash", "fxhash",
"log", "log",
"mime", "mime",
"pin-project 1.0.1", "pin-project 1.0.2",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
@ -466,9 +466,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.62" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" checksum = "ad9c6140b5a2c7db40ea56eb1821245e5362b44385c05b76288b1a599934ac87"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -752,7 +752,7 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
"pin-project 1.0.1", "pin-project 1.0.2",
"pin-utils", "pin-utils",
"proc-macro-hack", "proc-macro-hack",
"proc-macro-nested", "proc-macro-nested",
@ -914,7 +914,7 @@ dependencies = [
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project 1.0.1", "pin-project 1.0.2",
"socket2", "socket2",
"tokio", "tokio",
"tower-service", "tower-service",
@ -1056,9 +1056,9 @@ checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [ dependencies = [
"scopeguard", "scopeguard",
] ]
@ -1288,9 +1288,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [ dependencies = [
"instant", "instant",
"lock_api", "lock_api",
@ -1329,11 +1329,11 @@ dependencies = [
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841" checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
dependencies = [ dependencies = [
"pin-project-internal 1.0.1", "pin-project-internal 1.0.2",
] ]
[[package]] [[package]]
@ -1349,9 +1349,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86" checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.7", "quote 1.0.7",
@ -2096,9 +2096,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
version = "0.1.14" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f98e67a4d84f730d343392f9bfff7d21e3fca562b9cb7a43b768350beeddc6" checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
dependencies = [ dependencies = [
"tinyvec", "tinyvec",
] ]

View File

@ -9,9 +9,9 @@ edition = "2018"
[dependencies] [dependencies]
actix-web = "3.2.0" actix-web = "3.2.0"
reqwest = "0.10.8"
askama = "0.8.0" askama = "0.8.0"
serde = "1.0.117" serde = "1.0.117"
serde_json = "1.0" serde_json = "1.0"
reqwest = "0.10.8"
pulldown-cmark = "0.8.0" pulldown-cmark = "0.8.0"
chrono = "0.4.19" chrono = "0.4.19"

View File

@ -24,6 +24,7 @@ async fn favicon() -> HttpResponse {
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
// start http server // start http server
println!("Running Libreddit on 0.0.0.0:8080!"); println!("Running Libreddit on 0.0.0.0:8080!");
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
// GENERAL SERVICES // GENERAL SERVICES

View File

@ -20,7 +20,7 @@ struct PopularTemplate {
// RENDER // RENDER
async fn render(sub_name: String, sort: String) -> Result<HttpResponse> { async fn render(sub_name: String, sort: String) -> Result<HttpResponse> {
let posts: Vec<Post> = posts(sub_name, &sort).await; let posts: Vec<Post> = posts(sub_name, &sort).await?;
let s = PopularTemplate { posts: posts, sort: sort }.render().unwrap(); let s = PopularTemplate { posts: posts, sort: sort }.render().unwrap();
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))

View File

@ -6,7 +6,7 @@ use pulldown_cmark::{html, Options, Parser};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
use utils::{val, Comment, Flair, Params, Post}; use utils::{val, Comment, Flair, Params, Post, request};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -19,8 +19,8 @@ struct PostTemplate {
async fn render(id: String, sort: String) -> Result<HttpResponse> { async fn render(id: String, sort: String) -> Result<HttpResponse> {
println!("id: {}", id); println!("id: {}", id);
let post: Post = fetch_post(&id).await; let post: Post = fetch_post(&id).await?;
let comments: Vec<Comment> = fetch_comments(id, &sort).await.unwrap(); let comments: Vec<Comment> = fetch_comments(id, &sort).await?;
let s = PostTemplate { let s = PostTemplate {
comments: comments, comments: comments,
@ -86,18 +86,19 @@ async fn markdown_to_html(md: &str) -> String {
} }
// POSTS // POSTS
async fn fetch_post(id: &String) -> Post { async fn fetch_post(id: &String) -> Result<Post> {
// Build the Reddit JSON API url
let url: String = format!("https://reddit.com/{}.json", id); let url: String = format!("https://reddit.com/{}.json", id);
let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap();
let data: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON"); // Send a request to the url, receive JSON in response
let res = request(url).await;
let post_data: &serde_json::Value = &data[0]["data"]["children"][0]; let post_data: &serde_json::Value = &res[0]["data"]["children"][0];
let unix_time: i64 = post_data["data"]["created_utc"].as_f64().unwrap().round() as i64; let unix_time: i64 = post_data["data"]["created_utc"].as_f64().unwrap().round() as i64;
let score = post_data["data"]["score"].as_i64().unwrap(); let score = post_data["data"]["score"].as_i64().unwrap();
Post { let post = Post {
title: val(post_data, "title").await, title: val(post_data, "title").await,
community: val(post_data, "subreddit").await, community: val(post_data, "subreddit").await,
body: markdown_to_html(post_data["data"]["selftext"].as_str().unwrap()).await, body: markdown_to_html(post_data["data"]["selftext"].as_str().unwrap()).await,
@ -115,17 +116,20 @@ async fn fetch_post(id: &String) -> Post {
"white".to_string() "white".to_string()
}, },
), ),
} };
Ok(post)
} }
// COMMENTS // COMMENTS
async fn fetch_comments(id: String, sort: &String) -> Result<Vec<Comment>, Box<dyn std::error::Error>> { async fn fetch_comments(id: String, sort: &String) -> Result<Vec<Comment>> {
// Build the Reddit JSON API url
let url: String = format!("https://reddit.com/{}.json?sort={}", id, sort); let url: String = format!("https://reddit.com/{}.json?sort={}", id, sort);
let resp: String = reqwest::get(&url).await?.text().await?;
let data: serde_json::Value = serde_json::from_str(resp.as_str())?; // Send a request to the url, receive JSON in response
let res = request(url).await;
let comment_data = data[1]["data"]["children"].as_array().unwrap(); let comment_data = res[1]["data"]["children"].as_array().unwrap();
let mut comments: Vec<Comment> = Vec::new(); let mut comments: Vec<Comment> = Vec::new();

View File

@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
pub use utils::{val, Flair, Params, Post, Subreddit}; pub use utils::{val, Flair, Params, Post, Subreddit, request};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -17,8 +17,8 @@ struct SubredditTemplate {
} }
async fn render(sub_name: String, sort: String) -> Result<HttpResponse> { async fn render(sub_name: String, sort: String) -> Result<HttpResponse> {
let mut sub: Subreddit = subreddit(&sub_name).await; let mut sub: Subreddit = subreddit(&sub_name).await?;
let posts: Vec<Post> = posts(sub_name, &sort).await; let posts: Vec<Post> = posts(sub_name, &sort).await?;
sub.icon = if sub.icon != "" { sub.icon = if sub.icon != "" {
format!(r#"<img class="subreddit_icon" src="{}">"#, sub.icon) format!(r#"<img class="subreddit_icon" src="{}">"#, sub.icon)
@ -41,35 +41,37 @@ async fn page(web::Path(sub): web::Path<String>, params: web::Query<Params>) ->
} }
// SUBREDDIT // SUBREDDIT
async fn subreddit(sub: &String) -> Subreddit { async fn subreddit(sub: &String) -> Result<Subreddit> {
// Make a GET request to the Reddit's JSON API for the metadata of this subreddit // Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/r/{}/about.json", sub); let url: String = format!("https://www.reddit.com/r/{}/about.json", sub);
let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap();
// Parse the response from Reddit as JSON // Send a request to the url, receive JSON in response
let data: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON"); let res = request(url).await;
let icon: String = String::from(data["data"]["community_icon"].as_str().unwrap()); //val(&data, "community_icon"); let icon: String = String::from(res["data"]["community_icon"].as_str().unwrap()); //val(&data, "community_icon");
let icon_split: std::str::Split<&str> = icon.split("?"); let icon_split: std::str::Split<&str> = icon.split("?");
let icon_parts: Vec<&str> = icon_split.collect(); let icon_parts: Vec<&str> = icon_split.collect();
Subreddit { let sub = Subreddit {
name: val(&data, "display_name").await, name: val(&res, "display_name").await,
title: val(&data, "title").await, title: val(&res, "title").await,
description: val(&data, "public_description").await, description: val(&res, "public_description").await,
icon: String::from(icon_parts[0]), icon: String::from(icon_parts[0]),
} };
Ok(sub)
} }
// POSTS // POSTS
pub async fn posts(sub: String, sort: &String) -> Vec<Post> { pub async fn posts(sub: String, sort: &String) -> Result<Vec<Post>> {
// Make a GET request to the Reddit's JSON API for the content of this subreddit // Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/r/{}/{}.json", sub, sort); let url: String = format!("https://www.reddit.com/r/{}/{}.json", sub, sort);
let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap();
// Parse the response from Reddit as JSON // Send a request to the url, receive JSON in response
let popular: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON"); let res = request(url).await;
let post_list = popular["data"]["children"].as_array().unwrap();
// Fetch the list of posts from the JSON response
let post_list = res["data"]["children"].as_array().unwrap();
let mut posts: Vec<Post> = Vec::new(); let mut posts: Vec<Post> = Vec::new();
@ -101,5 +103,5 @@ pub async fn posts(sub: String, sort: &String) -> Vec<Post> {
), ),
}); });
} }
posts Ok(posts)
} }

View File

@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
use utils::{nested_val, val, Flair, Params, Post, User}; use utils::{nested_val, val, Flair, Params, Post, User, request};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -35,27 +35,30 @@ async fn page(web::Path(username): web::Path<String>, params: web::Query<Params>
// USER // USER
async fn user(name: &String) -> User { async fn user(name: &String) -> User {
// Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/user/{}/about.json", name); let url: String = format!("https://www.reddit.com/user/{}/about.json", name);
let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap();
let data: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON"); // Send a request to the url, receive JSON in response
let res = request(url).await;
User { User {
name: name.to_string(), name: name.to_string(),
icon: nested_val(&data, "subreddit", "icon_img").await, icon: nested_val(&res, "subreddit", "icon_img").await,
karma: data["data"]["total_karma"].as_i64().unwrap(), karma: res["data"]["total_karma"].as_i64().unwrap(),
banner: nested_val(&data, "subreddit", "banner_img").await, banner: nested_val(&res, "subreddit", "banner_img").await,
description: nested_val(&data, "subreddit", "public_description").await, description: nested_val(&res, "subreddit", "public_description").await,
} }
} }
// POSTS // POSTS
async fn posts(sub: String, sort: &String) -> Vec<Post> { async fn posts(sub: String, sort: &String) -> Vec<Post> {
// Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/u/{}/.json?sort={}", sub, sort); let url: String = format!("https://www.reddit.com/u/{}/.json?sort={}", sub, sort);
let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap();
let popular: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON"); // Send a request to the url, receive JSON in response
let post_list = popular["data"]["children"].as_array().unwrap(); let res = request(url).await;
let post_list = res["data"]["children"].as_array().unwrap();
let mut posts: Vec<Post> = Vec::new(); let mut posts: Vec<Post> = Vec::new();

View File

@ -61,3 +61,34 @@ pub async fn nested_val(j: &serde_json::Value, n: &str, k: &str) -> String {
pub struct Params { pub struct Params {
pub sort: Option<String>, pub sort: Option<String>,
} }
// Make a request to a Reddit API and parse the JSON response
#[allow(dead_code)]
pub async fn request(url: String) -> serde_json::Value {
// --- actix-web::client ---
// let client = actix_web::client::Client::default();
// let res = client
// .get(url)
// .send()
// .await?
// .body()
// .limit(1000000)
// .await?;
// let body = std::str::from_utf8(res.as_ref())?; // .as_ref converts Bytes to [u8]
// --- surf ---
// let req = surf::get(url);
// let client = surf::client().with(surf::middleware::Redirect::new(5));
// let mut res = client.send(req).await.unwrap();
// let body = res.body_string().await.unwrap();
// --- reqwest ---
let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap();
// Parse the response from Reddit as JSON
let json: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON");
json
}