Allow "CORS" (Cross Origin Resource Sharing) in pages served by the GoPiGo

Greetings!

While working on my My “New” Remote Camera Robot Project, I ran into a very annoying problem:

Web requests from the browser to the robot in response to controller actions were being blocked due to CORS “same origin” policy causing strange errors.
(I didn’t have this problem within the browser itself, or at least not so badly.)

Researching this - which took a non-trivial amount of time because the articles are written by people in the outer-limits of geekdom to be read by other uber-geeks.  In other words:  Gibberish.  :wink:

The crux of the matter is that the browser needs “permission” from the originating server to allow other “origins” - like it’s own filesystem! - to provide information.

This is done by adding response headers to the replies returned from the server and - eventually - I found something that actually gave practical advice.

Here’s the Beef:

In the original server code there is a “method”, (or whatever you call it), that responds to requests for the origin “/robot”.  (i.e. Control messages sent to the robot itself instead of just talking to the server.)

Viz.:

@app.route("/robot", methods = ["POST"])
def robot_commands():
    global vposition
    global hposition
    global servo_step_size

    # get the query
    args = request.args
    print(args)
    
<snip all the message processing logic>

    resp = Response()
    resp.mimetype = "application/json"
    resp.status = "OK"
    resp.status_code = 200

    return resp

This handles POST requests and - unless something goes weirdly wrong, it returns an OK/200 response.

To fix the CORS errors that are generated, you need to do two things.

First:
The response to POST requests has to have an additional part:

    resp = Response()
    resp.headers.add("Access-Control-Allow-Origin", "*")
    resp.mimetype = "application/json"
    resp.status = "OK"
    resp.status_code = 200

    return resp

. . . the “headers.add” which adds the CORI response to allow access to anything, anywhere.  Before anyone bitches, yes, I know that this is like solving an access problem by setting permissions to “777” and is about as secure as spaghetti suspenders.

  • First:  I don’t know how to frame a response that would be valid for any robot on any IP or network that is less generous without requiring a, perhaps less knowledgeable, user to hack at the code itself.
  • Second:  Since the site is being served by known code, on a known robot, residing on a known network, it should not be TOO evil.  Sloppy programming and a bad practice?  Yup.  OK for this particular use case?  Yup.

=====================

However, that’s only half the answer.

The other half of the CORS equation is the fact that browsers may elect to “pre-flight” a CORS request by a process known as “pre-flight” which is a way to find out if the browser is cool with the cross origin request before a possibly expensive page request or data transfer takes place.

To account for that, you have to specify a particular request method “OPTIONS” and create a whole new section that tailors a specific response to it.

Viz.:

@app.route("/robot", methods = ["OPTIONS"])
def create_CORS_response():  # allow "cross origins resource sharing"
    resp = Response()
    resp.headers.add("Access-Control-Allow-Origin", "*")
    resp.headers.add('Access-Control-Allow-Headers', "*")
    resp.headers.add('Access-Control-Allow-Methods', "*")
    resp.mimetype = "application/json"
    resp.status = "OK"
    resp.status_code = 200

    return resp

Again, this tells the browser that this server will allow anything from anyone, located anywhere.

As I mentioned above, it’s sloppy and way too permissive.  And just as soon as I figure out a better way to do this, I’ll let you know.

In the meantime, it allows the server to talk to the browser without raising all kinds of errors.

2 Likes