1. Test

  2. Wide World of WebSockets

    A survey of the current state of
    WebSockets in Perl 5

    Michael Conrad
    mike@nrdvana.net
    CPAN: NERDVANA

    github.com/nrdvana/wide-world-of-websockets
  3. Overview

  4. HTTP Request Lifecycle

    	Familiar to most
    	Client starts TCP & SSL,
    	   maybe re-use
    	Client requests resource
    	Server procs req and responds
    	Always client-driven.
    	What if client wants to listen?
    	
  5. HTTP Long-poll "Comet"

    	Client starts request
    	Server waits until it has something
    	Client renews to keep listening
    	Limited number of connections per server
    	New request means re-authenticating session
    	Doesn't work well for certain problems...
    	
  6. Example: Chat

    	Most common example of websocket
    	Minimal delay on chat messages
    	Not much improvement over Comet
    	Avoids "stampede" of new requests
    	
  7. Example: Monitoring

    	Small frequent data blocks would
    	be really inefficient with Comet
    	
  8. Example: Intelligent File Upload

    https://webdeltasync.github.io/
    	Like rsync.
    	JavaScript can hash file locally
    	only transfer new parts of file.
    	Fast round-trips referring to same file
    	Would be waste to hit different worker each time
    	
  9. Example: Mining BitCoin on Browsers

  10. Example: iPad Apps

    (like these slides!)
    	iPad closed system, no perl
    	can write web-apps
    	event-driven is best model for UI
    	
  11. Why Not TCP

    	Want sockets for javascript
    	Huge potential abuse
    	Something safe, with SSL
    	SOCK_SEQPACKET would be nice
    	
  12. WebSocket Design

        Starts as an HTTP(S) request
    	Server responds with accept/decline
    	Magic key isn't crypto, just cache busting
    	Earlier versions did messy stuff; ignoring
        Changes protocol from command/response to peer-to-peer
        Messages are framed, not pure streams
        Remains within SSL-encapsulated connection
    	
  13. WebSocket Message

  14. WebSocket Tradeoffs

    	Preserving state speeds up handling
    	Lose benefits of "Stateless"
    	
  15. WebSocket Adoption

    	Nearly ubiquitous now
    	Most old phones gone
    	Most old Macs gone
    	Opera matters for Wii
    	Flash workaround if you care
    	
  16. WebSockets In Perl

    But First...
    	Now you know what a websocket is and why you would use it.
    	Goal of this talk is to show all the ways to do websockets in
    	  Perl and discuss pros/cons.
    	However, many of the challenges involved with WebSockets are
    	the more general challenges involved with Event Driven Programming
    	and concurrency, so I'm going to review those topics first, to
    	make sure everyone on same page and using the same terminology.
    	
  17. Event Driven Programming

      * The most basic type of program is one that runs one algorithm to calculate
        an output from an input.
      * You know all steps the program might take in advance, and write the code for
        that sequence of steps.  If you need data that isn't immediately available,
    	you "block" the program until it is and then continue the sequence of steps.
      * The other main kind of program is one whose progessing depends on external events.
        The program has no goal aside from responding to events with various behavior.
      * A web server is a form of event-driven program that we're all familiar with.
      * The web server waits for client requests, then runs the request to completion and
        sends a response.
      * Each perl web framework is an exampe of some manner of tackling the problem of
        how to link an incoming event to the relevant code.
    	
  18. Concurrency

      * Most of the time, you want an event-driven program to be able to respond to
        multiple events at once, so that short events don't have to wait for longer
    	events to finish.  Or, just for capacity.
      * Three main strategies are:
        * Multiple independent processes which each process one event at a time
    	  (usually coordinated via filesystem or database)
    	* Multiple threads where each thread processes one event at a time
    	  (coordinated via shared data and synchronization "objects")
    	* Single thread written in non-blocking style
      * Pro/Con matrix of each mechanism
      * Perl doesn't get option 2, because no threads.  (But I don't really want it anyway,
        because multithread synchronization is a mess.)
      * Frameworks like Plack, Catalyst and Dancer use option 1, though you can get option 3
        if you try hard enough.
      * Frameworks like Mojo lend themselves naturally to option 3, though not required.
    	
  19. Event-driven Concurrency

    Blocking, Multi-ThreadNon-blocking, Event Loop
    • One thread (or process) for
      each external event.
    • Thread blocks while waiting on internal events.
    • Program makes a list of all
      pending external and internal events.
    • Asks Kernel to wake it up when any event is ready.
    	Many threads are expensive
    	Internal events like DNS lookups, database queries
    	When kernel signals ready condition, app must now
    	dispatch between middles of event handlers.
    	
  20. Event-Driven Concurrency

    Multi-ThreadEvent Loop
    Pros
    • Parallel processing
    • Immediate dispatch
    • Obvious to write
    • Easy shared data/state
    • Scales to many events
    • Easy debugging (maybe)
    Cons
    • Non-obvious bugs
    • Slow shared data
    • Awkward cancellation
    • Development effort
    • Scalability
    • No parallelism *
    • Awkward code flow
    • Can't use blocking APIs
    	Multi-thread Pro
    	   things can run in parallel
    	   Every event "ready to run"
    	   natural pattern of fn call
    	Multi-thread Con
    	   segfaults, deadlocks, races
    	   shared data structures need locks
    	     as seen in Java
    	   killing thread is awkward
    	   obvious pattern has many non-obvious pitfalls
    	   One thread per event doesn't scale
    	Eventloop Pro
    	   Single thread, so simple data structures
    	   Per-event cost is just items in a list,
    		so can scale up to extreme number of events.
    	   Single-thread easier to debug than multithread,
    	    but "easy" depends on abstraction
    	Eventloop Con
    	   every event must process quickly
    		(can fork for expensive processing)
    	   Must divide subroutines into
    	     per-event pieces
    	   blocking APIs: DBIC DNS
    	
    	Perl6 attempts fixing multithread
    	Go attempts hybrid approach
    	
  21. Simplified Unix Event Loop

    
    	our %tasks= (
    		{
    			code => sub {...},
    			read_fh => \$handle,
    			write_fh => \$handle,
    			wake_time => $timestamp,
    		}
    	);
    	
    
    	while (!$terminate) {
    		my $need_read= make_fh_bitvector( map $_->{read_fh}, values %tasks );
    		my $need_write= make_fh_bitvector( map $_->{write_fh}, values %tasks );
    		my $check_err= combine_bitvectors( $need_read, $need_write );
    		my $timeout= min( map $_->{wake_time}, values %tasks );
    	   
    		$ret= select( $need_read, $need_write, $check_err, $timeout );
    	
    
    		for ( values %tasks ) {
    			if ( bitvector_contains_fh( $need_read, $_->{read_fh} ) ) {
    				$_->{code}->() // run the callback
    			}
    			...
    			if ( $_->{wake_time} < time) {
    				$_->{code}->()
    			}
    		}
    	
    	wrote simple example, still to messy
    	making even simpler example to skip detail
    	(explain)
    	In addition, there are signals, and child proc
    	Also want a better API to register the callbacks
    	
  22. Event Loop Implementations

    	Lots to choose from
    	LibEV particularly good
    	But There Can Be Only One
    	Need to adapt modules to any event loop
    	
  23. Event Loop Wrappers

    POE oldest, longest back-compat
      Bit awkward, best handling of refs
    AnyEvent is most minimal,
      heavy callbacks, some social drama
      extensive CPAN ecosystem
    IO::Async is a bit of a hybrid
      also good cpan ecosystem
    
  24. WebSockets In Perl

    (finally)
  25. WebSocket Protocol Util

  26. Stand-alone Servers

  27. Stand-alone with AnyEvent

    
    use AnyEvent::WebSocket::Server;
    
    my $server= AnyEvent::WebSocket::Server->new(
      handshake => sub {
        my ($req, $res)= @_;
        ... # decide whether to accept
        return $res, ...; # can append calculated values
      },
    );
    
    
    AnyEvent::Socket::tcp_server undef, 5000, sub {
      my ($fh)= @_;
      $server->establish($fh)->cb(sub {
        my ($conn, ...)= eval { shift->recv };
        if ($@) {
          warn "Rejected connection: $@\n";
          close($fh);
          return;
        }
        $conn->on(each_message => sub { ... });
        $conn->on(finish => sub { ... });
      });
    };
    
  28. PSGI

  29. Plack

    
    	Plack::App::WebSocket->new(
    		on_error => sub {
    			my $env = shift;
    			return [500,
    					["Content-Type" => "text/plain"],
    					["Error: " . $env->{"plack.app.websocket.error"}]];
    		},
    		on_establish => sub {
    			my $conn = shift;   ## Plack::App::WebSocket::Connection object
    			my $env = shift;    ## PSGI env
    			my $hs_res = shift; ## extra results from the handshake callback
    			$conn->on(
    				message => sub {
    					my ($conn, $msg) = @_;
    					$conn->send($msg);
    				},
    				finish => sub {
    					undef $conn;
    					warn "Bye!!\n";
    				},
    			);
    		}
    	);
    	
  30. Web::Simple

    Plack based...
    Just return the same thing when
    websocket URI is requested.
  31. Dancer

  32. Catalyst

  33. Mojolicious

  34. CGI

  35. Hybrid

    Why Make Monoliths?
  36. Hybrid with Traefik

  37. docker-compose.yaml

    
    	version: '3'
    
    	services:
    	  traefik:
    		image: traefik:1.6
    		container_name: traefik
    		volumes:
    		  - "./traefik.toml:/etc/traefik/traefik.toml"
    		  - "./acme:/etc/traefik/acme/"
    		  - "/var/run/docker.sock:/var/run/docker.sock"
    		ports:
    		  - "$MyPublicIP:80:80/tcp"
    		  - "$MyPublicIP:443:443/tcp"
    		environment:
    		  - "TRAEFIK_INSECURE_SKIP=true"
    		networks:
    		  - traefik-net
    
    	networks:
    	  traefik-net:
    		external: true
    	
  38. traefik.toml

    
    	defaultEntryPoints = ["https","http"]
    	debug = true
    
    	[entryPoints]
    	  [entryPoints.http]
    	  address = ":80"
    	  [entryPoints.https]
    	  address = ":443"
    		[entryPoints.https.tls]
    
    	[acme]
    	email = "Myself@example.com"
    	storage = "/etc/traefik/acme/acme.json"
    	onDemand = true
    	onHostRule = true
    	caServer = "https://acme-v02.api.letsencrypt.org/directory"
    	entryPoint = "https"
    	  [acme.httpChallenge]
    	  entryPoint = "http"
    
    	[docker]
    	endpoint = "unix:///var/run/docker.sock"
    	watch = true
    	templateVersion = 2
    	exposedByDefault = false
    	usebindportip = false
    	swarmMode = false
    
    	[api]
    
    	[retry]
    	
  39. Mount a service
    using Docker labels

    
    		--label "traefik.enable=true"
    		--label "traefik.docker.network=traefik-net"
    		--label "traefik.frontend.entryPoints=http,https"
    		--label "traefik.frontend.rule=Host:example.com; PathPrefixStrip: /ws"
    		--label "traefik.port=80"
    	
  40. Summary

    github.com/nrdvana/wide-world-of-websockets