1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
use anyhow::{Context, Result};
use hyper::header::CONTENT_TYPE;
use hyper::{Body, Method, Request, Response, StatusCode};
use log::{debug, error, info, trace};
use multer::Multipart;
use nanoid::nanoid;
use rusqlite::{params, Connection};
use url::form_urlencoded;
use std::collections::HashMap;
use crate::db::init_db;
fn respond_with_shortlink<S: AsRef<str>>(shortlink: S) -> Response<Body> {
info!("Successfully generated shortlink");
Response::builder()
.status(StatusCode::OK)
.header("content-type", "text/html")
.body(Body::from(shortlink.as_ref().to_string()))
.unwrap()
}
fn respond_with_status(s: StatusCode) -> Response<Body> {
Response::builder().status(s).body(Body::empty()).unwrap()
}
fn shorten<S: AsRef<str>>(url: S, conn: &mut Connection) -> Result<String> {
let mut stmt = conn.prepare("select * from urls where link = ?1")?;
let mut rows = stmt.query(params![url.as_ref().to_string()])?;
if let Some(row) = rows.next()? {
return Ok(row.get(1)?);
} else {
let new_id = nanoid!(4);
conn.execute(
"insert into urls (link, shortlink) values (?1, ?2)",
params![url.as_ref().to_string(), new_id],
)?;
return Ok(new_id);
}
}
fn get_link<S: AsRef<str>>(url: S, conn: &mut Connection) -> Result<Option<String>> {
let url = url.as_ref();
let mut stmt = conn.prepare("select * from urls where shortlink = ?1")?;
let mut rows = stmt.query(params![url.to_string()])?;
if let Some(row) = rows.next()? {
return Ok(row.get(0)?);
} else {
return Ok(None);
}
}
async fn process_multipart(
body: Body,
boundary: String,
conn: &mut Connection,
) -> Result<Response<Body>> {
let mut m = Multipart::new(body, boundary);
if let Some(field) = m.next_field().await? {
if field.name() == Some("shorten") {
trace!("Recieved valid multipart request");
let content = field
.text()
.await
.with_context(|| format!("Expected field name"))?;
let shortlink = shorten(content, conn)?;
return Ok(respond_with_shortlink(shortlink));
}
}
trace!("Unprocessable multipart request!");
Ok(respond_with_status(StatusCode::UNPROCESSABLE_ENTITY))
}
pub async fn shortner_service(req: Request<Body>) -> Result<Response<Body>> {
let mut conn = init_db("./urls.db_3").unwrap();
match req.method() {
&Method::POST => {
let boundary = req
.headers()
.get(CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.and_then(|ct| multer::parse_boundary(ct).ok());
if boundary.is_none() {
let b = hyper::body::to_bytes(req)
.await
.with_context(|| format!("Failed to stream request body!"))?;
let params = form_urlencoded::parse(b.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
if let Some(n) = params.get("shorten") {
trace!("POST: {}", &n);
let s = shorten(n, &mut conn)?;
return Ok(respond_with_shortlink(s));
} else {
error!("Invalid form");
return Ok(respond_with_status(StatusCode::UNPROCESSABLE_ENTITY));
}
}
trace!("Attempting to parse multipart request");
return process_multipart(req.into_body(), boundary.unwrap(), &mut conn).await;
}
&Method::GET => {
trace!("GET: {}", req.uri());
let shortlink = req.uri().path().to_string();
let link = get_link(&shortlink[1..], &mut conn);
if let Some(l) = link.unwrap() {
trace!("Found in database, redirecting ...");
Ok(Response::builder()
.header("Location", &l)
.header("content-type", "text/html")
.status(StatusCode::MOVED_PERMANENTLY)
.body(Body::from(format!(
"You will be redirected to: {}. If not, click the link.",
&l
)))?)
} else {
error!("Resource not found");
Ok(respond_with_status(StatusCode::NOT_FOUND))
}
}
_ => Ok(respond_with_status(StatusCode::NOT_FOUND)),
}
}
|