From 338e22dc69169d851cf0ee0d006845342456927f Mon Sep 17 00:00:00 2001 From: Leonora Tindall Date: Tue, 20 Sep 2022 11:44:20 -0500 Subject: [PATCH] Support images and embeds properly. --- src/main.rs | 69 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index d60a231..d30707a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(()); - } + md_assembled.push_str(&format!("\n
Your provider: {}
", geotext)); - // Write the footer into the synthetic body - md_assembled.push_str("\n"); - footer_body.read_to_string(&mut md_assembled)?; - - let md_assembled = md_assembled.replace("{{geo}}", &geotext); let parser = Parser::new_ext(&md_assembled, Options::empty()); let mut client_body = beresp