Transcript
Page 1: Gamers do REST - EuroPython 2014

GAMERS DO RESTNOT REALLY

Page 2: Gamers do REST - EuroPython 2014

(these are real graphs, trust me)

Page 3: Gamers do REST - EuroPython 2014

(these are real graphs, trust me)

Page 4: Gamers do REST - EuroPython 2014

ABOUT MEAngel Ramboi

programmer, gamer, geek @demonware

Page 5: Gamers do REST - EuroPython 2014
Page 6: Gamers do REST - EuroPython 2014

ABOUT DEMONWAREACTIVISION-BLIZZARD

Dublin

Vancouver

Shanghai

Page 7: Gamers do REST - EuroPython 2014

WHAT DO WE DO?We enable gamers to find one another

and then shoot each other in the face.

Page 8: Gamers do REST - EuroPython 2014

WHAT WE ACTUALLY DOleaderboardsmatchmakinganti-cheataccounts managementand more …

70+ SERVICES

Page 9: Gamers do REST - EuroPython 2014
Page 10: Gamers do REST - EuroPython 2014
Page 11: Gamers do REST - EuroPython 2014

WE HIRE SUPERHEROES

demonware.net/jobs

Page 12: Gamers do REST - EuroPython 2014

TALK OVERVIEWAPI design

Process and tools

App configuration

Handling errors/logging/metrics

Authentication/authorization

Page 13: Gamers do REST - EuroPython 2014

WHY RESTinteroperability

scalability

Page 14: Gamers do REST - EuroPython 2014

API DESIGNfollowing the REST principles outlined in Roy Fielding's thesis

GET, POST, PUT, DELETE verbs for API CRUD

HTTP as the communication protocol

JSON representation

pragmatic approach: “good enough” > perfect

Page 15: Gamers do REST - EuroPython 2014

API DESIGNGET /v1.0/users/1/ HTTP/1.1Accept: application/json

{ "userName": "cmac1", "email": "[email protected]", "firstName" : "Connor", "lastName" : "MacLeod", "dateOfBirth": "1518-03-18", "country": "GB", "gender": "male", "created": "1986-03-07T13:37:00Z", "quotes": { "href": "/v1.0/users/1/quotes/", "summary": { "quotes": [ "There can be only one.", "I am Connor MacLeod of the Clan MacLeod." ] } }}

Page 16: Gamers do REST - EuroPython 2014

PROCESS AND TOOLS

Page 17: Gamers do REST - EuroPython 2014

DEVELOPING WITH AGILITYScrum or Kanban

Continuous integration

Page 18: Gamers do REST - EuroPython 2014

TECH STACKPython 2.7

Django 1.6

MySQL 5.6

CentOS 6

Apache + mod_wsgi

Page 19: Gamers do REST - EuroPython 2014

CODE AND DEPLOYMENT

Page 20: Gamers do REST - EuroPython 2014

SCHEMA MIGRATIONS

Page 21: Gamers do REST - EuroPython 2014

SCHEMA MIGRATIONSPercona Toolkit to the rescue with “online-shema-change”

www.percona.com/software/percona-toolkit

creates an already altered tablesetup triggers for insert/update/deletecopies the datarenames the tables

Downside: uses a lot of space as it duplicates all the data

Page 22: Gamers do REST - EuroPython 2014

APP CONFIGURATIONYAML 1.2---django: DEBUG: False ALLOWED_HOSTS: ["*"] TIME_ZONE: UTC LANGUAGE_CODE: en-us USE_I18N: True SECRET_KEY: "secret key :)" TEMPLATE_LOADERS: - django.template.loaders.filesystem.Loader INSTALLED_APPS: - django.contrib.contenttypes ...

Cross project & Validation

Page 23: Gamers do REST - EuroPython 2014

APP CONFIGURATIONminimum_age = Option( type={ 'type': 'integer', 'valid': [['>=', 0]] }, default=13, description='The minimum age of a user.')

Page 24: Gamers do REST - EuroPython 2014

JSON VALIDATION{ "title": "Example Schema", "type": "object", "properties": { "username": { "type": "string", "pattern": "̂[a-z0-9_-]{3,15}$" }, "age": { "description": "Age in years", "type": "integer", "minimum": 0 } }}

json-schema.orgpypi.python.org/pypi/jsonschema

Page 25: Gamers do REST - EuroPython 2014

JSON VALIDATION{ "title": "Example Schema", "type": "object", "properties": { "firstName": { "type": "string", "minLength": 2, "maxLength": 100 }, "lastName": { "type": "string", "minLength": 2, "maxLength": 100 } }, "required": ["firstName", "lastName"]}

json-schema.orgpypi.python.org/pypi/jsonschema

Page 26: Gamers do REST - EuroPython 2014

JSON VALIDATION{ "title": "Example Schema", "type": "object", "properties": { "gender": { "type": "string", "enum": ["male", "female", "other"], "exceptions": { "required": errors.GenderMissingError, "type": errors.InvalidGenderError, "enum": errors.InvalidGenderError } } }, "required": ["gender"]}

json-schema.orgpypi.python.org/pypi/jsonschema

Page 27: Gamers do REST - EuroPython 2014

ERROR HANDLINGclass ErrorHandlingMiddleware(object): def process_exception(self, request, exception): return format_and_render_error( request, exception )

Page 28: Gamers do REST - EuroPython 2014

ERROR HANDLING{ "error": { "msg": "Request data validation failed, see context for more details.", "code": 227000, "name": "Error:ClientError:InvalidRequest:DataInvalid", "context": [ { "msg": "Email [email protected] already exists", "code": 288000, "name": "Error:ClientError:Conflict:EmailExists" }, { "msg": "Username cmac1 already exists", "code": 289000, "name": "Error:ClientError:Conflict:UsernameExists" } ] }}

Page 29: Gamers do REST - EuroPython 2014

LOGGING

Page 30: Gamers do REST - EuroPython 2014

LOGGING

Page 31: Gamers do REST - EuroPython 2014

LOGGING// Bad message - not suitable/useful for production.logger.debug("Variable x={}".format(var))

// Good message - suitable for production.logger.error( "Request {request} failed unexpectedly for reason {reason} " "resulting in client error {error}" .format({ "request": req, "reason": expl, "error": client_error_code }))

Page 32: Gamers do REST - EuroPython 2014

LOGGING2014-05-10T22:58:56.394565+00:00 level=error project=highlander app=usersview=get_UsersView client=127.0.0.1 method=GET path=/v1.0/users/2msg=Error:NotFound(No user with user_id 2 could be found.There can be only one!)

Page 33: Gamers do REST - EuroPython 2014

METRICS

Page 34: Gamers do REST - EuroPython 2014

METRICSclass MetricsMiddleware(object):

def process_request(self, request): request.metrics_start_time = time.time()

def process_response(self, request, response): if hasattr(request, 'metrics_start_time'): request_time = (time.time() - request.metrics_start_time) * 1000 metrics.write( name='request_time', value=request_time ) return response

Page 35: Gamers do REST - EuroPython 2014

AUTHWe use JSON Web Tokens:

JSON Web Signature (JWS) objectsJSON Web Encryption (JWE) objects

JOSE is a framework intended to provide a method to securelytransfer claims:

github.com/Demonware/josepypi.python.org/pypi/jose

% pip install jose

Page 36: Gamers do REST - EuroPython 2014

JSON WEB SIGNATURE (JWS) OBJECTSimport josefrom time import time

claims = { 'iss': 'http://www.example.com', 'exp': int(time()) + 3600, 'sub': 42}jwk = {'k': 'password'}

jws = jose.sign(claims, jwk, alg='HS256')

JWS(header='eyJhbGciOiAiSFMyNTYifQ', payload='eyJpc3MiOiAiaHR0cDovL3d3dy5leGFtcGxlLmNvbSIsICJzdWIiOiA0MiwgImV4cCI6IDEzOTU2NzQ0Mjd9', signature='WYApAiwiKd-eDClA1fg7XFrnfHzUTgrmdRQY4M19Vr8')

jwt = jose.serialize_compact(jws)

# on the client sidejose.verify(jose.deserialize_compact(jwt), jwk)

Page 37: Gamers do REST - EuroPython 2014

JSON WEB ENCRYPTION (JWE) OBJECTSimport josefrom time import timefrom Crypto.PublicKey import RSA

claims = { 'iss': 'http://www.example.com', 'exp': int(time()) + 3600, 'sub': 42}key = RSA.generate(2048)pub_jwk = {'k': key.publickey().exportKey('PEM')}

jwe = jose.encrypt(claims, pub_jwk)

JWE(header='eyJhbGciOiAiUlNBLU9BRVAiLCAiZW5jIjogIkExMjhDQkMtSFMyNTYifQ', cek='SsLgP2bNKYDYGzHvLYY7rsVEBHSms6_jW-WfglHqD9giJhWwrOwqLZOaoOycsf_EBJCkHq9-vbxRb7WiNdy_C9J0_RnRRBGII6z_G4bVb18bkbJMeZMV6vpUut_iuRWoct_weg_VZ3iR2xMbl-yE8Hnc63pAGJcIwngfZ3sMX8rBeni_koxCc88LhioP8zRQxNkoNpvw-kTCz0xv6SU_zL8p79_-_2zilVyMt76Pc7WV46iI3EWIvP6SG04sguaTzrDXCLp6ykLGaXB7NRFJ5PJ9Lmh5yinAJzCdWQ-4XKKkNPorSiVmRiRSQ4z0S2eo2LtvqJhXCrghKpBNgbtnJQ', iv='Awelp3ryBVpdFhRckQ-KKw', ciphertext='1MyZ-3nky1EFO4UgTB-9C2EHpYh1Z-ij0RbiuuMez70nIH7uqL9hlhskutO0oPjqdpmNc9glSmO9pheMH2DVag', tag='Xccck85XZMvG-fAJ6oDnAw')

jwt = jose.serialize_compact(jwe)

# on the client sidepriv_jwk = {'k': key.exportKey('PEM')}jwt = jose.decrypt(jose.deserialize_compact(jwt), priv_jwk)

Page 38: Gamers do REST - EuroPython 2014

SUMMARYRest is awesome

Be Pragmatic

Monitor Everything

We are hiring!!!