Introducing @now/rust - ZEIT

Last year, Rust was voted Stack Overflow's most loved language for the second year in a row. That isn’t surprising at all, given its incredible performance, memory efficiency, safety, and, combined with Cargo, its commitment towards developer productivity.

Today, we're proud to announce official support for Rust through @now/rust.

Rust is widely respected for its blazing fast speed

Our mission at is to make cloud computing accessible for all. Rust has seen rapid growth in its adoption, and we're proud to be able to support the community.

To demonstrate the @now/rust Builder, we built Rust Scraper, a web scraper that utilises Servo — a high performance browser engine from Mozilla. You pass in a URL and a CSS selector through its query parameters, and it returns scraped data. In this blog post, we walk through its creation and deployment.

Pass in a URL and a CSS selector through its query parameters, and Rust Scraper returns scraped data

Parsing Markup for Desired Elements

We start by creating a Rust data structure called ElementResponse for each individual element, and another called ResponseBody for a collection of ElementResponses. Creating these data structures help us define the shape of our JSON response. When we respond with ResponseBody, the serializer is derived automatically, and the lambda serializes to JSON for us.
use serde_derive::Serialize; #[derive(Debug, Serialize)]
struct ElementResponse { innerHTML: String, innerText: Vec<String>,
} #[derive(Debug, Serialize)]
struct ResponseBody { selector: String, elements: Vec<ElementResponse>,

Structs defined within

With our primary data structures in place, we can now focus on our core operation — extracting selectors from the HTML. We make use of scraper to help parse and query CSS selectors.
use scraper::{Html, Selector}; fn extract_selector(html: &String, selector_str: &str) -> ResponseBody { let fragment = Html::parse_document(html); let selector = Selector::parse(selector_str).unwrap(); let elements: Vec<_> = fragment .select(&selector) .map(|el| ElementResponse { innerHTML: el.inner_html(), innerText: el.text().map(|text| text.to_string()).collect(), }) .collect(); let res = ResponseBody { selector: selector_str.to_string(), elements, }; res

extract_selector function within It takes an HTML string and CSS selector string as input and returns aResponseBody with the scraped elements.

Request Handler

Now that we have a mechanism to parse the elements, we need to create an HTTP request handler that parses the incoming request, passes it to extract_selector, and responds with the final data we're interested in.

For Rust projects on Now, we need to call the function handler.

use http::{self, StatusCode};
use reqwest::{header, Client};
use serde_json;
use std::collections::HashMap;
use url::Url; fn handler(request: Request<()>) -> http::Result<Response<String>> { let uri_str = request.uri().to_string(); let url = Url::parse(&uri_str).unwrap(); let hash_query: HashMap<_, _> = url.query_pairs().to_owned().collect(); match (hash_query.get("url"), hash_query.get("selector")) { (Some(ref url), Some(ref selector)) => { let url = format!("http://{}", url); let url = Url::parse(&url).expect("Failed to parse URL"); let client = Client::new(); let mut res = client .get(url) .header(header::ACCEPT, "text/html") .send() .expect("Failed to send HTTP request"); assert_eq!(res.status(), StatusCode::OK); let page_html = res.text().expect("Failed to get response HTML"); let content = extract_selector(&page_html, selector); let content_str = serde_json::to_string_pretty(&content).expect("Failed to serialize to JSON"); let response = Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, "application/json") .body(content_str) .expect("Failed to render response"); Ok(response) } _ => Response::builder() .status(StatusCode::BAD_REQUEST) .body("`selector` and `url` are required query params".to_string()), }

handler function within

Preparing for Serverless Deployment

With the application-specific heavy lifting done, we can now configure the project for deployment.

First we list our dependencies within Cargo.toml:

name = "rust-scraper"
version = "0.1.0"
edition = "2018" [dependencies]
http = "0.1"
lambda_runtime = "*"
scraper = "*"
url = "*"
reqwest = "*"
serde = "*"
serde_json = "*"
serde_derive = "*"

Cargo.toml lists all the dependencies used within

Finally, we add in our now.json configuration:
{ "version": 2, "name": "rust-scraper", "builds": [{ "src": "", "use": "@now/rust", }]

now.json specifies the use of `@now/rust` Builder.

With Now installed, we're ready to deploy:
$ now

With some additional optimization work to cache several popular Rust packages, deployments should normally be fast. Ongoing work on the Rust compiler currently makes deploying Rust projects with additional dependencies, that are not cached, take longer.

The source for this demo is publicly available on GitHub.

Conclusion, Further Reading and Acknowledgements

Now that @now/rust is officially supported, we can't wait to learn about the Rust apps you build with it! As always, please send us your thoughts, feedback, and comments around @now/rust over Twitter or on Spectrum. The @now/rust Builder is completely open source. We welcome contributions and encourage you to create your own Builders for your favourite technology - we have a detailed guide in place to help you.

For further reading on Rust and deploying with Now, the community offers the following incredible resources:

  1. The official Rust book is the most comprehensive resource for learning Rust.
  2. Rust by Example helps you learn the language by solving little Rust exercises online.
  3. Idiomatic Rust is a peer-reviewed collection of resources about writing idiomatic Rust.
  4. @now/rust docs explains when and how to use the Rust Builder to deploy to the Now platform.