Skip to content

Instantly share code, notes, and snippets.

@motoom
Created June 2, 2018 14:40
Show Gist options
  • Save motoom/95f8440c11aeff99e0f0378106b316d4 to your computer and use it in GitHub Desktop.
Save motoom/95f8440c11aeff99e0f0378106b316d4 to your computer and use it in GitHub Desktop.
CherryPy API server able to serve JSON to webbrowsers using CORS
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test API</title>
</head>
<body>
<h1>Test API using CORS</h1>
<p>Result:</p>
<p id="result"></p>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script>
$.ajax({
url: "http://127.0.0.1:8000/api/search",
dataType: "json",
contentType: "application/json; charset=utf-8",
method: "POST",
data: JSON.stringify({
q: "banana"
}),
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa("joe" + ":" + "secret"));
},
success: function(data) {
$("#result").html("OK: " + JSON.stringify(data));
},
error: function() {
alert("Error");
}
});
</script>
</body>
</html>
# Demonstrates CORS using a webbrowser's Ajax.
#
# Status: Works
#
# Run corsapiserver-test.html in a browser for a HTML/Ajax POC page.
import cherrypy
import json
class CorsAuthBasicTool(cherrypy.Tool):
def __init__(self, priority=5):
cherrypy.Tool.__init__(self, "before_handler", self.basic_cors_auth, priority=priority)
def basic_cors_auth(self, realm, checkpassword, debug=False, accept_charset="utf-8"):
if cherrypy.request.method != "OPTIONS":
# Delegate to CherryPy's basic auth handler
return cherrypy.lib.auth_basic.basic_auth(realm, checkpassword, debug=debug) # Later versions also require: , accept_charset=accept_charset)
class API(object):
@cherrypy.expose
def search(self):
# Still have to handle OPTIONS request here; but it'll be called even if no credentials are given.
if cherrypy.request.method == "OPTIONS":
cherrypy.response.headers["Access-Control-Allow-Methods"] = "POST, OPTIONS"
cherrypy.response.headers["Access-Control-Allow-Credentials"] = "true"
cherrypy.response.headers["Access-Control-Max-Age"] = "86400"
cherrypy.response.headers["Access-Control-Allow-Headers"] = "X-Mobile, Authorization, Origin, X-Requested-With, Content-Type, Accept"
return ""
# The actual serving of the POST request.
if cherrypy.request.body.length:
data = cherrypy.request.body.read()
vars = json.loads(data)
result = dict(msg="You searched for %s" % vars["q"])
else:
result = dict(msg="You did a %s request with headers: %r" % (cherrypy.request.method, cherrypy.request.headers))
s = json.dumps(result)
cherrypy.response.headers["Content-Type"] = "application/json"
return s
class Root(object):
@cherrypy.expose
def index(self):
return "Hello from index of Root."
API_KEY_USER = "joe"
API_KEY_PASSWD = "secret"
if __name__ == "__main__":
root = Root()
root.api = API()
cherrypy.tools.cors_auth_basic = CorsAuthBasicTool()
conf = {
"global": {
"server.socket_host": "0.0.0.0",
"server.socket_port": 8000,
},
"/": {
},
"/api": {
"tools.cors_auth_basic.on": True,
"tools.cors_auth_basic.debug": True,
"tools.cors_auth_basic.realm": "Generic API",
"tools.cors_auth_basic.checkpassword": cherrypy.lib.auth_basic.checkpassword_dict({API_KEY_USER: API_KEY_PASSWD}),
# required for CORS
"tools.response_headers.on": True,
"tools.response_headers.headers": [
("Access-Control-Allow-Origin", "*"),
],
},
}
cherrypy.quickstart(root, "/", conf)
"""
# Preflight; should work without authentication
curl -i -X OPTIONS http://127.0.0.1:8000/api/search
# This is an OPTIONS with authentication, you can do that with curl but not with a webbrowser:
curl -i -X OPTIONS --user joe:secret http://127.0.0.1:8000/api/search
# Calling the actual API with authentication
curl -sH "Content-Type: application/json" --data '{"q": "Banana"}' --user joe:secret http://127.0.0.1:8000/api/search | json_pp
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment