Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2016 - Sam Bolgert - REST Websockets API with Django Channels

PyBay
August 20, 2016

2016 - Sam Bolgert - REST Websockets API with Django Channels

Description
Building REST APIs over HTTP has been discussed time and again. But could we do the same with WebSockets? What is the performance benefit? What learnings can we carry over from HTTP to WS? This talk will describe how engineers can build a REST API over WebSockets using Django and Channels. It is largely based on my experiences trying to build a REST WebSocket API.

Abstract
Websockets have been around for a number of years but popular web frameworks have been slow to integrate because of their asynchronous nature. With Django Channels we’ve finally broken that frontier in a synchronous way. How will developers use this new territory? I will outline some of my explorations that I have serialized into a library I call channels-api. It takes familiar patterns from Django Rest Framework and applies them to websocket land. I will walk through a sample project to demonstrate the configuration and installation of the library. I will demonstrate using these patterns we can create a “REST like” API relatively quickly. We can also implement new features that HTTP doesn’t support like server side push.

Bio
Author of channels-api library. Former Lead engineer at a number of startups.

https://youtu.be/HzC_pUhoW0I

PyBay

August 20, 2016
Tweet

More Decks by PyBay

Other Decks in Programming

Transcript

  1. Roadmap • What is a "RESTful" WebSocket • How to

    implement using Django Channels • Channels Api
  2. REST WebSockets Forma2ng • Accepts JSON input • Returns JSON

    output • Format will be par9ally determined by channels
  3. HTTP REST Routes | HTTP Verb | Path | Action

    | Used For | |-----------|----------------|----------|--------------------------------| | GET | /liveblogs | index | Retrieving a list of liveblogs | | GET | /liveblogs/:id | retrieve | Retrieve a specific liveblog | | POST | /liveblogs | create | Create a new liveblog | | PUT | /liveblogs/:id | update | Updating a specific liveblog | | DELETE | /liveblogs/:id | delete | Delete a liveblog |
  4. REST WebSocket Routes • No HTTP Verbs • No Request

    Paths • WebSockets are a persistent bidirec;onal connec;on
  5. REST WebSocket Routes | Stream | Action | ID Required

    | Data Required | |-------------|----------|-------------|---------------| | liveblogs | index | False | False | | liveblogs | retrieve | True | False | | liveblogs | create | False | True | | liveblogs | update | True | True | | liveblogs | delete | True | False |
  6. WebSocket only func/onality • WebSockets are bidirec0onal • Server can

    send updates to client without a request • No more polling for updates
  7. REST WebSocket Routes | Stream | Action | ID Required

    | Data Required | |-------------|-----------|-------------|---------------| | liveblogs | index | False | False | | liveblogs | retrieve | True | False | | liveblogs | create | False | True | | liveblogs | update | True | True | | liveblogs | delete | True | False | | liveblogs | subscribe | False | True |
  8. REST WebSocket Subscrip2ons • create • update • updates to

    specific id • delete • specific id deleted
  9. Errors • How will a client know a request failed?

    • Error codes and messages • response_status • errors • request_id
  10. What is a REST WebSocket? • JSON Forma,ed • CRUD

    Interface • Supports subscrip=ons to events: create, update, delete • Errors via response codes and messages
  11. Consumers • Similar to Django's View but different • Are

    mapped to individual "channels" instead of HTTP Routes
  12. # consumers.py from channels.generic import websockets class ExampleConsumer(websockets.JsonWebsocketConsumer): def receive(self,

    content): print(content) # {"test": "data"} # routing.py from channels.routing import route_class channel_routing = [ route_class(ExampleConsumer) ]
  13. WebsocketDemul-plexer channels.generic.websockets.WebsocketDemultiplexe r • Subclasses JsonWebsocketConsumer • Responsible for rou2ng

    to mul2ple streams over a single WebSocket connec2on • Defining the stream and payload keys to JSON
  14. //send.js var ws = new WebSocket("ws://localhost:8000/") var data = {

    stream: "liveblogs", payload: {} } var msg = JSON.stringify(data) ws.send(msg)
  15. # consumers.py from channels.generic import websockets class ExampleDemultiplexer(websockets.WebsocketDemultiplexer): mapping =

    { "liveblogs": "liveblog_channel" } # routing.py channel_routing = [ route_class(ExampleDemultiplexer) ]
  16. Bindings • Automate the process of tying Django models to

    javascript • Supports inbound messages • Supports outbound messages via signals • Requires use of Demul=plexer
  17. var ws = new WebSocket('ws://localhost:8000/') var data = { stream:

    "liveblogs", payload: { action: "create", data: { title: "the latest event" } } } var msg = JSON.stringify(data) ws.send(msg)
  18. # bindings.py from channels import bindings from .models import Liveblog

    class LiveblogBinding(bindings.WebsocketBinding): model = Liveblog stream = "liveblogs"
  19. # bindings.py from channels import bindings from .models import Liveblog

    class LiveblogBinding(bindings.WebsocketBinding): model = Liveblog stream = "liveblogs" # routing.py from channels.routing import route, route_class channel_routing = [ route_class(ExampleDemultiplexer), route("liveblog_channel", LiveblogBinding.consumer) ]
  20. Channels API channels_api.bindings.ResourceBinding • ResourceBinding • composed of mixins simliar

    to rest_framework.viewsets.ModelViewSet • implements retrieve, list, create, update, delete, subscribe • based on rest_framework.serializers.ModelSerializer
  21. var ws = new WebSocket("ws://localhost:8000/") var data = { stream:

    "liveblogs", payload: { action: "subscribe", data: { action: "create" }, request_id: "some-request-uuid" } } var msg = JSON.stringify(data) ws.onmessage = function(message){ console.log(message) } ws.send(msg)
  22. # bindings.py from channels_api.bindings import ResourceBinding from rest_framework import serializers

    from .models import Liveblog from .serializer import LiveblogSerializer class LiveblogSerializer(serializers.ModelSerializer): class Meta: model = Liveblog fields = ('id', 'title') read_only_fields = ('slug', )
  23. # bindings.py from channels_api.bindings import ResourceBinding from rest_framework import serializers

    from .models import Liveblog from .serializer import LiveblogSerializer class LiveblogSerializer(serializers.ModelSerializer): class Meta: model = Liveblog fields = ('id', 'title') read_only_fields = ('slug', ) class LiveblogResourceBinding(ResourceBinding): model = Liveblog stream = "liveblogs" serializer_class = LiveblogSerializer queryset = Liveblog.objects.all()
  24. # bindings.py from channels_api.bindings import ResourceBinding from rest_framework import serializers

    from .models import Liveblog from .serializer import LiveblogSerializer class LiveblogSerializer(serializers.ModelSerializer): class Meta: model = Liveblog fields = ('id', 'title') read_only_fields = ('slug', ) class LiveblogResourceBinding(ResourceBinding): model = Liveblog stream = "liveblogs" serializer_class = LiveblogSerializer queryset = Liveblog.objects.all() # routing.py from channels.routing import route, route_class channel_routing = [ route_class(ExampleDemultiplexer), route("liveblog_channel", LiveblogResourceBinding.consumer) ]