Dancer2...bSocket(3)

πŸ“¦ version 0.3.1, in opensuse-leap 16.0

πŸ’… Dancer2::Plugin::WebSocket πŸ¦‹

okay bestie, welcome to the manual page for Dancer2::Plugin::WebSocket β€” straight outta the User Contributed Perl Documentation, manual page Dancer2...bSocket(3). It's giving add-a-websocket-interface-to-your-Dancers-app energy, and version 0.3.1 is in the building. no cap.

✨ NAME ✨

Dancer2::Plugin::WebSocket β€” add a websocket interface to your Dancers app.

🌸 VERSION 🌸

version 0.3.1

πŸ¦‹ SYNOPSIS πŸ¦‹

okay so here's the whole setup, bestie β€” three files, all the vibes. Start with bin/app.psgi:

#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;
use lib "$FindBin::Bin/../lib";

use Plack::Builder;

use MyApp;

builder {
    mount( MyApp->websocket_mount );
    mount '/' => MyApp->to_app;
}

Then config.yml:

plugins:
    WebSocket:
        # default values
        serializer: 0
        login: 0
        mount_path: /ws

And MyApp.pm:

package MyApp;

use Dancer2;
use Dancer2::Plugin::WebSocket;

websocket_on_message sub {
  my( $conn, $message ) = @_;
  $conn->send( $message . ' world!' );
};

get '/' => sub {
  my $ws_url = websocket_url;
  return <<"END";
    <html>
      <head><script>
          var urlMySocket = "$ws_url";

          var mySocket = new WebSocket(urlMySocket);

          mySocket.onmessage = function (evt) {
            console.log( "Got message " + evt.data );
          };

          mySocket.onopen = function(evt) {
            console.log("opening");
            setTimeout( function() {
              mySocket.send('hello'); }, 2000 );
          };

    </script></head>
    <body><h1>WebSocket client</h1></body>
  </html>
END
};

get '/say_hi' => sub {
  $_->send([ "Hello!" ]) for websocket_connections;
};

true;

πŸ’œ DESCRIPTION πŸ’œ

Dancer2::Plugin::WebSocket provides an interface to Plack::App::WebSocket and lets you interact with the webSocket connections inside the Dancer app. iykyk.

Plack::App::WebSocket β€” and therefore this plugin β€” requires a Plack server that supports the psgi streaming, nonblocking and io. Twiggy is the most popular server fitting the bill. slay.

πŸŽ€ CONFIGURATION πŸŽ€

🌸 serializer 🌸

If serializer is set to a "true" value, messages will be assumed to be JSON objects and will be automatically encoded/decoded using a JSON::MaybeXS serializer. If the value of serializer is a hash, it'll be passed as arguments to the JSON::MaybeXS constructor.

plugins:
    WebSocket:
        serializer:
            utf8:         1
            allow_nonref: 1

By the way, if you want the connection to automatically serialize data structures to JSON on the client side, you can do something like:

var mySocket = new WebSocket(urlMySocket);
mySocket.sendJSON = function(message) {
    return this.send(JSON.stringify(message))
};

// then later...
mySocket.sendJSON({ whoa: "auto-serialization ftw!" });

πŸŽ€ mount_path πŸŽ€

Path for the websocket mountpoint. Defaults to "/ws".

Also, the default values block in the SYNOPSIS includes serializer: 0, login: 0, and mount_path: /ws β€” login is just chillin' there with no extra paragraph, and we're not inventing one. no cap.

🌈 PLUGIN KEYWORDS 🌈

In the various callbacks, the connection object $conn is a Plack::App::WebSocket::Connection object augmented with the Dancer2::Plugin::WebSocket::Connection role. main character energy.

🌟 websocket_on_open sub { ... } 🌟

websocket_on_open sub {
    my( $conn, $env ) = @_;
    ...;
};

Code invoked when a new socket is opened. Gets the new connection object and the Plack $env hash as arguments.

🌟 websocket_on_login sub { ... } 🌟

websocket_on_login sub {
    my( $conn, $env ) = @_;
    ...;
};

Code invoked when a new socket is opened. Gets the connection object and the Plack $env hash as arguments.

Example: return true if user is logged in and the webapp http_cookie is the same as the websocket.

my $login_conn;
my $cookie_name = 'example.session';

hook before => sub {
    if (defined cookies->{$cookie_name}) {
        $login_conn->{'cookie_id'} = cookies->{$cookie_name}->value;
    }
    $login_conn->{'login'} = logged_in_user ? 1 : 0;
};

websocket_on_login sub {
    my( $conn, $env ) = @_;

    my ($cookie_id) = ($env->{'HTTP_COOKIE'} =~ /$cookie_name=(.*);?/g);
    if (($login_conn->{'login'}) and ($login_conn->{'cookie_id'} eq $cookie_id)) {
        return 1;
    } else {
        warn "require login";
        return 0;
    }
};

🌟 websocket_on_close sub { ... } 🌟

websocket_on_close sub {
    my( $conn ) = @_;
    ...;
};

Code invoked when a new socket is opened. Gets the connection object as argument.

🌟 websocket_on_error sub { ... } 🌟

websocket_on_error sub {
    my( $env ) = @_;
    ...;
};

Code invoked when an error is detected. Gets the Plack $env hash as argument and is expected to return a Plack triplet.

If not explicitly set, defaults to:

websocket_on_error sub {
    my $env = shift;
    return [
        500,
        ["Content-Type" => "text/plain"],
        ["Error: " . $env->{"plack.app.websocket.error"}]
    ];
};

🌟 websocket_on_message sub { ... } 🌟

websocket_on_message sub {
    my( $conn, $message ) = @_;
    ...;
};

Code invoked when a message is received. Gets the connection object and the message as arguments.

Note that while websocket_on_message fires for all messages received, you can also be a little more selective. Indeed, each connection, being a Plack::App::WebSocket::Connection object, can have its own (multiple) handlers. So you can do things like:

websocket_on_open sub {
  my( $conn, $env ) = @_;
  $conn->on( message => sub {
    my( $conn, $message ) = @_;
    warn "I'm only being executed for messages sent via this connection";
  });
};

🌟 websocket_connections 🌟

Returns the list of currently open websocket connections.

🌟 websocket_url 🌟

Returns the full url of the websocket mountpoint.

# assuming host is 'localhost:5000'
# and the mountpoint is '/ws'
print websocket_url;  # => ws://localhost:5000/ws

🌟 websocket_mount 🌟

Returns the mountpoint and the Plack app coderef to be used for "mount" in app.psgi. See the SYNOPSIS.

πŸ’€ GOTCHAS πŸ’€

It seems that closing the socket causes Google's chrome to burp the following to the console:

WebSocket connection to 'ws://...' failed: Received a broken close frame containing a reserved status code.

Firefox seems to be happy, though. The issue is probably somewhere deep in AnyEvent::WebSocket::Server. Since the socket is being closed anyway, I am not overly worried about it.

πŸ‘‘ SEE ALSO πŸ‘‘

This plugin is basically nothing more than a sugar topping atop Plack::App::WebSocket, which is itself AnyEvent::WebSocket::Server wrapped in Plackstic. iconic.

Mojolicious also has nice WebSocket-related offerings. See Mojolicious::Plugin::MountPSGI or <http://mojolicious.org/perldoc/Mojolicious/Guides/Cookbook#Web-server-embedding>. (hi Joel!)

πŸ¦‹ AUTHOR πŸ¦‹

Yanick Champoux <yanick@cpan.org>

This software is copyright (c) 2021, 2019, 2017 by Yanick Champoux.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


okay bestie, that's the whole vibe check 🫢 this page is from perl v5.42.0, dated 2021-05-31, manual page Dancer2...bSocket(3) β€” now go slay your websocket era πŸ’…βœ¨

πŸ“¦ where this page comes from