☁️ Rust Cloudflare Workers #
In this post, we step beyond initial setup and the basics of Rust Cloudflare workers, where you write worker code in the Rust language and compile to WASM. In a previous post, we saw how to set up your system for writing Rust, as well as building out a router for a Rust API. Here, we look at parsing form data on an incoming API HTTP request and also sending HTTP requests to external servers.
Turnstile #
We will step things up a notch, adding an API route which can be used as a serverless backend for Turnstile CAPTCHA challenges. These are the checks websites add to verify the user is not a bot, and so protect forms, for example, from abuse. Turnstile is a CAPTCHA offering from Cloudflare, which does not require users to solve puzzles. This is in contrast to alternatives such as Arkose MetaKey, Google’s reCAPTCHA or hCaptcha. Cloudflare JavaScript code runs code in the browser, which it can use to assess whether the use is a human or bot, and without asking them to count traffic lights.
Like other CAPTCHAs, Cloudflare submits some analysis data from the user browser and responds (to the browser) with code. A second step, run on your site backend, involves submitting the browser response to Cloudflare, along with a secret site key to get Cloudflare's verdict on whether the visitor is a human or bot. We focus on this second step here, which can be useful for static or JAMStack websites.
You might want to read a previous article on getting started with Rust Cloudflare workers, before carrying on, as this post using that, previous, post’s code as a starting point.
🗳 Poll #
⚙️ Project Setup #
In the previous, getting started with Rust Cloudflare workers tutorial, we built up a basic REST API, based on the worker Rust template
. To start, create a new Git branch in your repo from that tutorial or get the code from the Rodney Lab GitHub repo , then runpnpm install
in the project directory.
🤖 CAPTCHA API Route #
We will start by adding a new CAPTHCA route to the router code, in src/lib.rs
. The code will listen for form submissions sent from the frontend. In a real world application,
that POST
request would contain form field data, filled out by the
site visitor, as well as a cf-turnstile-response
field. Here, we won’t
look at business logic for processing the form inputs, so just pull that cf-turnstile-response
field off the incoming POST request.
We call req.form_data()
to pull form fields off the request. This returns
a Rust result , which we match on. If, for example, the request was sent in the wrong format, and we
weren’t able to parse it as form data, we would end up in the Err
arm, and would return a Bad request
response.
Assuming the request is well-formed, we next want to try to get the value, for the cf-turnstile-response
field.
The form data might be well-formed, but missing the cf-turnstile-response
field. Options, in Rust , are used to represent a variable which can have a value of a known type or an alternative,
unexpected or invalid value. value
, in the code block above, is
the identifier we assigned to the form data, and calling get
on it
returns a Rust Option. That Option will be None
, if the cf-turnstile-response
is missing. When the field exists, it could be a file or field string value. We only care about
the field value, and anything else (represented by the _
should result
in a bad request response).
The code above works just fine, though we can make it a little cleaner using Rust if
let
syntax, in place of the inner match:
How does that work? Let’s see an example. We get a thermometer reading. This will be a
number (64-bit float) if everything went well. If the thermometer got unplugged, though, we will
get nothing back. Using an option to represent this, the temperature could be Some(25.0)
, as an example of a normal reading, or None
when there is no reading
for some reason.
For our Some(25.0)
example, here, value
will be 25.0
, and we can print the temperature. For the None
case, we have no need for value
, and just say there is no
reading.
Our turnstile example takes it up another level, as we specify we only use the true
if value.get("cf-turnstile-response")
is Some
, and also it is of FormEntry::Field
type.
⚽️ Reading Request Headers #
Next, we can, optionally, pass the client IP address to Cloudflare in our verification request (we
look at that request itself in a moment). The IP address might be included in an HTTP header on
the incoming form request to our API. We can check for the CF-Connecting-IP
header and get its value with:
The req.headers().get
function call returns a Result
, like (req.form_data()
). “unwrap
” here, is a way of handling the result without pattern matching, causing a panic in the
event of an error.
Pulling it all together, we can build an HTTP request, to send to Turnstile for the verification
result using the reqwest
crate:
To be able to use reqwest
, though, we should add it in Cargo.toml
:
Of course, we have only prepared the request, and not sent it! Here is the missing code:
We are sending the data as POST
form request. Naturally, reqwest
supports JSON POST
requests
as well as GET
and other types of HTTP request .
🤫 Secrets and Environment Variables #
In the previous post, we saw how you can read meta from Cargo.toml
, made available as environment variables by Rust. Let’s look a little more at environment
variable handling. Wrangler lets you define variables in the wrangler.toml
file. If you need to store secret variables, hang on a moment, we will see something designed specifically
for secrets in a moment.
We expect visitors to submit forms from a browser, and so we need to cater for CORS requests . Specifically, our response needs to include the Access-Control-Allow-Origin
header. To see an example of storing variables in wrangler.toml
, we will add a list of allowed CORS origins there. Update wrangler.toml
:
We can then access that parameter from Rust code using:
Secrets #
You can push secrets into your Cloudflare production environment from the Terminal:
However, for local development, use a .dev.vars
file in your project
root directory:
Then to access this value from your Rust code, use:
🧰 CORS Header Utility Function #
We will add a utility function, before wrapping up with the final version of the code. You can add
this code in src/lib.rs
before the main
function:
The CAPTCHA route code will call this function, passing in the request headers and the comma
separated list of allowed CORS origins. The request headers should include an Origin
header, which we can check against the list of allowed origins.
Two points to note here are:
-
the use of
expect
to handle results; and -
the Rust closure in lines
40
-44
.
expect
works like unwrap
and causes
a panic when the result is not OK. The difference is that it takes an argument, which gives some feedback
if it does panic.
cors_origin.split(',')
in line 40
splits
a comma separated list, in a string such as "http://127.0.0.1:3001,https://example.com"
into a vector of strings like: ["http://127.0.0.1:3001", "https://example.com"]
and then returns an iterator over that vector. We can then call the any
closure on that value, looking for matches, and return true
if there
is a match. This functional approach provides a cleaner alternative than an explicit loop over the
vector elements, in this case.
🖥️ Final Router Code #
Pulling everything together, your final version will look something like this:
src/lib.rs
— click to expand code.
Here, we just put everything together, also adding an extra captcha
route, listening for CORS OPTIONS
requests.
💯 Rust Cloudflare Workers: Check your Work #
To test this out, you might want to spin up a quick frontend using Deno Fresh or Astro. You will need to configure Turnstile in your Cloudflare account to get a site key and secret key.
🙌🏽 Rust Cloudflare Workers: Wrapping Up #
We continued our exploration of Rust and Cloudflare workers adding new routes to the API. More specifically, we have seen:
- how to parse form data on an incoming HTTP request using Rust Cloudflare workers;
- how to use reqwest to make REST requests to external servers; and
- examples of adding response headers on API endpoints.
The Rust Cloudflare Workers Turnstile Example code is in the Rodney Lab GitHub repo .
I hope you found this article useful, and am keen to hear where you will go next, as well as potential improvements, both to the Rust code and explanations above. Also, let me know if you would like to see another edition in this series, and which feature would be interesting to explore.
🏁 Rust Cloudflare Workers: Summary #
Which Rust crate can you use to send HTTP requests in an API? #
- Reqwest is a fantastic, high-level crate for sending REST requests. It uses async methods, by default, but also offers blocking alternatives. On top, reqwest uses rustls under the hood. With that being a pure Rust TLS implementation, reqwest will work just fine in WASM environments.
What is the difference between Rust unwrap and expect error-handling methods? #
- With expect, you can include an error message to show when the error arm is hit. Both unwrap and expect cause execution to panic, just unwrap has no arguments.
When would you use an Option in Rust? #
- A great example of using an Option in Rust is for representing empirical data, like temperature measurements. You might want to use a single identifier to represent both valid temperature readings and also those where something went wrong. Using a Rust Option, the variable would be `Some(x)` when the reading is valid and `None` otherwise.
🙏🏽 Rust Cloudflare Workers: Feedback #
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.
Just put together an extension to the Getting Started with 🦀 Rust Cloudflare Workers post.
— Rodney (@askRodney) June 14, 2023
We build out the router seeing how to parse form data, and also make REST requests to external services.
Hope you find it useful!
#learnrust #askRodneyhttps://t.co/4nXdrOgXFx
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram . Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also, subscribe to the newsletter to keep up-to-date with our latest projects.