|
|
@ -1,11 +1,12 @@ |
|
|
|
//! Default Compute@Edge template program.
|
|
|
|
//! Compute@Edge service which renders markdown from external sources.
|
|
|
|
|
|
|
|
use fastly::http::{header, Method, StatusCode}; |
|
|
|
use fastly::{mime, Body, Error, Request, Response}; |
|
|
|
use pulldown_cmark::{html, Options, Parser}; |
|
|
|
use std::io::Read; |
|
|
|
|
|
|
|
/// The configured backend name.
|
|
|
|
/// The configured backend name. This is set up in the Fastly interface, and in fastly.toml
|
|
|
|
/// for local development.
|
|
|
|
const BACKEND_NAME: &str = "upstream_ssl"; |
|
|
|
|
|
|
|
/// The URL of the header markdown content.
|
|
|
@ -13,9 +14,12 @@ const HEADER_URL: &str = "http://nora.codes/edgeblog/_header.md"; |
|
|
|
/// The URL of the footer markdown content.
|
|
|
|
const FOOTER_URL: &str = "http://nora.codes/edgeblog/_footer.md"; |
|
|
|
|
|
|
|
/// This function is invoked for every request. If it returns Err(), the user will see a 500.
|
|
|
|
fn main() -> Result<(), Error> { |
|
|
|
// Get the request sent by the client. This hooks into the Compute@Edge runtime interface.
|
|
|
|
let req = Request::from_client(); |
|
|
|
// Filter request methods...
|
|
|
|
|
|
|
|
// Filter out unrelated request methods.
|
|
|
|
match req.get_method() { |
|
|
|
// Allow GET and HEAD requests.
|
|
|
|
&Method::GET | &Method::HEAD => (), |
|
|
@ -30,33 +34,61 @@ fn main() -> Result<(), Error> { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// Rewrite the / path to index.md, and construct a Request
|
|
|
|
// Rewrite the / path to index.md, and construct a Request for the main body content.
|
|
|
|
let upstream_req = match req.get_path() { |
|
|
|
"/" => Request::get("http://nora.codes/edgeblog/index.md"), |
|
|
|
path => Request::get(format!("http://nora.codes/edgeblog/{}", path)), |
|
|
|
}; |
|
|
|
|
|
|
|
// Get the data for the header portion of the page
|
|
|
|
// Get the data for the body portion of the page, and write the content into the Markdown
|
|
|
|
// document.
|
|
|
|
// This backend response is used as the template for the synthetic response.
|
|
|
|
let mut beresp = upstream_req.send(BACKEND_NAME)?; |
|
|
|
|
|
|
|
// It's important to set Markdown's MIME type as "text" on your webserver;
|
|
|
|
// I used "text/markdown".
|
|
|
|
// Here, we check whether or not the object being requested is text. If so, we'll render it
|
|
|
|
// inline; otherwise, we'll serve it as is. This allows supporting images, other documents,
|
|
|
|
// and so forth without any extra effort.
|
|
|
|
if beresp |
|
|
|
.get_content_type() |
|
|
|
.map(|mime| mime.type_() != mime::TEXT) |
|
|
|
.unwrap_or(true) |
|
|
|
{ |
|
|
|
beresp.send_to_client(); |
|
|
|
return Ok(()); |
|
|
|
} |
|
|
|
|
|
|
|
// Get the data for the header portion of the page, making a backend request to the origin
|
|
|
|
// server. This will be cached.
|
|
|
|
let mut header_body = match Request::get(HEADER_URL).send(BACKEND_NAME) { |
|
|
|
Err(_e) => Body::new(), |
|
|
|
Ok(mut r) => r.take_body(), |
|
|
|
}; |
|
|
|
|
|
|
|
// Get the data for the footer portion of the page
|
|
|
|
// Start off the Markdown document with the header content.
|
|
|
|
let mut md_assembled = String::new(); |
|
|
|
header_body.read_to_string(&mut md_assembled)?; |
|
|
|
md_assembled.push_str("\n"); |
|
|
|
|
|
|
|
beresp.take_body().read_to_string(&mut md_assembled)?; |
|
|
|
|
|
|
|
// Get the data for the footer portion of the page, making a backend request to the origin
|
|
|
|
// server. This will be cached.
|
|
|
|
let mut footer_body = match Request::get(FOOTER_URL).send(BACKEND_NAME) { |
|
|
|
Err(_e) => Body::new(), |
|
|
|
Ok(mut r) => r.take_body(), |
|
|
|
}; |
|
|
|
|
|
|
|
// Write the header into the synthetic body
|
|
|
|
let mut md_assembled = String::new(); |
|
|
|
header_body.read_to_string(&mut md_assembled)?; |
|
|
|
// Write the footer into the synthetic body
|
|
|
|
md_assembled.push_str("\n"); |
|
|
|
footer_body.read_to_string(&mut md_assembled)?; |
|
|
|
|
|
|
|
// Get the geoip info for the client, if possible.
|
|
|
|
const UNKNOWN_LOCATION_STRING: &str = "unknown"; |
|
|
|
let geotext: String = match req.get_client_ip_addr() { |
|
|
|
Some(ip) => match fastly::geo::geo_lookup(ip) { |
|
|
|
None => "an undisclosed location".into(), |
|
|
|
None => UNKNOWN_LOCATION_STRING.into(), |
|
|
|
Some(geo) => format!( |
|
|
|
"AS{} '{}', in {}", |
|
|
|
geo.as_number(), |
|
|
@ -65,24 +97,11 @@ fn main() -> Result<(), Error> { |
|
|
|
) |
|
|
|
.into(), |
|
|
|
}, |
|
|
|
None => "an undisclosed location".into(), |
|
|
|
None => UNKNOWN_LOCATION_STRING.into(), |
|
|
|
}; |
|
|
|
|
|
|
|
// Get the content of the page itself. If this fails, pass on the failure.
|
|
|
|
let mut beresp = upstream_req.send(BACKEND_NAME)?; |
|
|
|
if beresp.get_status() == StatusCode::OK { |
|
|
|
beresp.take_body().read_to_string(&mut md_assembled)?; |
|
|
|
md_assembled = md_assembled.replace("{{geo}}", &geotext); |
|
|
|
} else { |
|
|
|
beresp.send_to_client(); |
|
|
|
return Ok(()); |
|
|
|
} |
|
|
|
|
|
|
|
// Write the footer into the synthetic body
|
|
|
|
md_assembled.push_str("\n"); |
|
|
|
footer_body.read_to_string(&mut md_assembled)?; |
|
|
|
md_assembled.push_str(&format!("\n<h5> Your provider: {} </h5>", geotext)); |
|
|
|
|
|
|
|
let md_assembled = md_assembled.replace("{{geo}}", &geotext); |
|
|
|
let parser = Parser::new_ext(&md_assembled, Options::empty()); |
|
|
|
|
|
|
|
let mut client_body = beresp |
|
|
|