This post follows from the second part where we created a simple api.
In the previous post when we fired up our Python-based client it couldn’t talk to the server, and instead gave us a lengthy scolding about CORS, aka Cross-origin Resource Sharing. This error is telling us that when we loaded our webpage from (http://localhost:8000) and tried to access the album API (http://localhost:3000), that we were dealing with different origins.
Browsers default to a same-origin policy, which means that when you load a script on a webpage it can only access the data from another page if it’s from the same origin.. Since origin is defined by port, protocol and domain, even http://site.com, https://site.com and https://site.com:8000 are considered different origins.
What all this is saying is that we need a way to let our client and server talk to one another, or else we’re only going to be able to access our API directly via our browser’s search bar, which is not ideal. In the future we may even want to allow a desktop or phone app to access the API, which it can’t do.
To relax the same-origin policy (SOP) we can use CORS, which is a header-based method that allows us to specify in the response headers what origins the server will allow. It’s important to note that CORS is relaxing the security of the site, since it’s bypassing the same-origin policy which was put in place to protect us from things like CRSF attacks.
For now, let’s see how CORS can help us with our current predicament, by first adding the loosest possible policy to our original API. You can find the complete code in the repo, under server/bin and in the file cors-1.rs. Below is the portion that we’ve changed from the simple api in the previous post.
|
|
To run this, navigate to the server folder and run the following:
$ cargo run --bin cors-1
In another terminal run the same client we did in the last post by navigating to the client/simple-client folder and running:
$ python -m http.server
Unlike in the previous post where we got a CORS error, this should now work just fine and return MK III The Final Concerts [Disc 1] if you’ve stuck with the default album ID of 43.
The only real difference from the previous program is we’ve added CORS, which is wrapped as middleware for our HttpServer.
The term middleware is pretty crap if you ask me. The first time I encountered it I found a number of different explanations in a half dozen contexts. See this Wikipedia article and this Stackoverflow post which kind of say that middleware can be just about any piece of software that isn’t an OS. For us, in our current context, it’s what sits between our server and client and sets the request/response headers. In the above code we’ve told it open everything up, essentially setting the headers to what you find below (more on this later).
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: *
Access-Control-Allow-Origin: *
How Actix middleware works is explained in detail in this post, but you can think of it as taking in a request, performing some operation, and then passing it on to another piece of middleware or onto the server itself. Commonly used middleware aside from CORS include things such as:
But what is CORS middleware actually doing? The browser sends a request, noting its origin in the HTTP request headers, and when the server receives it the CORS middleware checks to see if that origin is allowed. In our case, all origins are allowed, so it sends a response to the client saying its origin is allowed along with the data requested.
To see what has changed in the headers rerun the first server simple-api-1 and then attempt to get the same ID. It should fail with the same CORS error. Now, open your developer tools and find the failed request under Network, click on the 43, and then the Headers tab. You should see something like:
Now compare that to a successful request by running cors-1 as the server and requesting the ID again:
As you can see the response from the server has added the access-control-allow-origin and vary fields, the latter of which informs the browser of the fields which are influenced by our request.
While this works, it’s better practice to tighten up your CORS policy when you can. For starters, let’s assume we’re running an open API and anyone can access it. We’ll want to keep the allow_any_origin as is, but let’s replace allow_any_method with the following:
|
|
This is saying that our API will only accept GET requests. You can test this by adding a post route to your server (see cors-2) or adding the code below, along with the appropriate libraries:
|
|
Then add the service to your server (.service(test)), and then call it via a fetch such as this from your client:
|
|
And you should get a CORS error if you check your Network tab in the developer tools. Interestingly though, if you remove the web::Data from the server and the headers and body from the fetch, it won’t throw an error, even though we have a POST method.