Advanced Rest Api
Asynchronous Applications
from gevent import monkey; monkey.patch_all() from time import sleep
from bottle import route, run
@route('/stream') def stream():
yield 'START' sleep(3) yield 'MIDDLE' sleep(5) yield 'END'
run(host='127.0.0.1', port=8080, server='gevent')
Made easy with the gevent package:
All your code runs in the same thread (no need to restrict access to shared memory, low overhead)
gevent creates a lots of greenbelt objects, which are like small and really cheap threads. greenlets may be run concurrently in one or more thread.
The code executed is switched automatically by gevent when a blocking function is called.
Asynchronous Applications
4
getImage(1)
readfile()
Synchronous:
Asynchronous:
getImage(2)
readfile()
getImage(1)
readfile()
getImage(2)
readfile()
main thread
main thread
@route('/fetch') def fetch():
body = gevent.queue.Queue() worker = SlowDataProvider() worker.on_data(body.put)
worker.on_finish(lambda: body.put(StopIteration)) worker.start()
return body
More Advanced Example:
5 6
Authentication
Stateful Authorization:
Client
Server Authenticate
Sessions:
id:123 -> user:admin Session id = 123
GET images/1 Session id = 123
Authentication
Stateless Authorization:
Client
Server Authenticate
admin,Token = 123 GET images/1 admin,Token = 123
Authentication
Authentication
Stateless Token Based Authorization:
The server does not store any information about who is logged in.
The client sends with each request an object that contains (i) who is he and (ii) a token that proves this.
m=“I’m the admin“, MAC=Token
MAC: Message Authentication Code
10
m secret: K
MAC
MAC algo
Authentication
secret: K
MAC message: m
MAC
MAC algo
message: m
MAC ==?
token
h: X —> Y, |X| > |Y| (usually, {0,1}* —> {0,1}n ) Computationally infeasible to find a collision.
Ex: Used to store passwords, check file integrity, etc.
MD5 (not cryptographic), SHA-1 (a little bit better), SHA-256 (good)
Authentication
Cryptographic Hash Function:
12
secret: K
MAC
h(m||K)
Authentication
secret: K message: m
MAC
h(m||K)
message: m
MAC ==?
Not secured m MAC
token
13
secret: K
HMAC(m,K)
Authentication
secret: K message: m
token
HMAC(m,K)
message: m
==?
HMAC(m,k) = h((K + opad) || h((K + ipad) || m))
+ : xor
||: concat opad : 0x(5c)*
ipad : 0x(36)*
MAC MAC
m MAC
token
14
Authentication
secret: K
HMAC(m,K) user:admin, expire:+1day
client
Authenticate
server
token
images/1, token
MAC
MAC user:admin, expire:+1day
HMAC(m,K)
=? MAC
Authentication
client
Authenticate
authorization server
Token (getImages) Users DB
resource server
images/1, Token (getImages)
import hmac
from datetime import date, timedelta from bottle import route, run, request, abort
secret_key = '123456789'
@route('/authenticate') def authenticate():
if not checkUserAdmin(request.form['login'], request.form['password']):
return abort(401, 'access denied')
msg = 'admin,' + str(date.today() + timedelta(days=2))
mac = hmac.new(secret_key, msg=msg, digestmod='SHA256') token = msg + '.' + mac.hexdigest()
return {'token': token}
Authentication (Server)
17
@route('/images/<id:int>') def getImages(id):
token = request.params['token']
msg, macHex = token.split('.')
mac = hmac.new(secret_key, msg=msg, digestmod='SHA256') if not hmac.compare_digest(mac.hexdigest(), macHex):
return abort(401, 'access denied') user, expire = msg.split(',')
if user != 'admin' or expire < str(datetime.date.today()) return abort(401, 'access denied')
return Images.get(id)
Authentication (Server) Authentication
client authorization server
Users DB
bob’s images
resource server Bob
get bob’s images (password=123) hello
I need your photos
my password is 123
18
Token (bob/getImages)
images/1, Token (bob/getImages)
Bad with a third-party client
19
Authentication (OAuth2)
client
Authorization Grant (bob/getImages)
authorization server
Token (bob/getImages) Users DB
resource server
images/1, Token (bob/getImages)
Bob
hello
I need your photos client needs my photos Authorization Grant (bob/getImages) Authorization Grant
(bob/getImages)
bob’s images
@route('/hello') def hello():
flow = client.flow_from_clientsecrets('client_secrets.json',
scope='https://www.googleapis.com/auth/drive.metadata.readonly') auth_uri = flow.step1_get_authorize_url()
# The user has to visit auth_uri and get a code redirect(auth_uri)
@route('/token_request') def hello():
code = request.query['code']
return {'token': flow.step2_exchange(code)}
@route('/stats') def getStats():
token = request.query['token']
files = getGoogleDriveFiles(token) return Stats.execute(files)
20
Authentication (Client)
Google Authentication Flow:
user
client 2
code 1
1 auth_uri
3 code
3 token token
4
4 code
21
Authentication (Client)
Google Authentication Flow:
@route('/hello') def hello():
flow = client.flow_from_clientsecrets('client_secrets.json',
scope='https://www.googleapis.com/auth/drive.metadata.readonly') auth_uri = flow.step1_get_authorize_url()
# The user has to visit auth_uri and get a code redirect(auth_uri)
@route('/token_request') def hello():
code = request.query['code']
return {'token': flow.step2_exchange(code)}
@route('/stats') def getStats():
token = request.query['token']
files = getGoogleDriveFiles(token) return Stats.execute(files)
user
client 1
3
4
5 /stats?token
5
2
drive/files stats
6
6
Generate XML
import xml.etree.ElementTree as ET root = ET.Element('root')
child = ET.SubElement(root, "child") child.text = "I'am a child"
child.set('class', 'content') print(ET.tostring(root))
# prints <root><child class='content'>I'm a child</child></root>