.\" -*- mode: troff; coding: utf-8 -*- .\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. .ie n \{\ . ds C` "" . ds C' "" 'br\} .el\{\ . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Dancer2::Plugin::WebSocket 3" .TH Dancer2::Plugin::WebSocket 3 2021-05-31 "perl v5.38.2" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH NAME Dancer2::Plugin::WebSocket \- add a websocket interface to your Dancers app .SH VERSION .IX Header "VERSION" version 0.3.1 .SH SYNOPSIS .IX Header "SYNOPSIS" \&\fIbin/app.psgi\fR: .PP .Vb 1 \& #!/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 \*(Aq/\*(Aq => MyApp\->to_app; \& } .Ve .PP \&\fIconfig.yml\fR: .PP .Vb 6 \& plugins: \& WebSocket: \& # default values \& serializer: 0 \& login: 0 \& mount_path: /ws .Ve .PP \&\fIMyApp.pm\fR: .PP .Vb 1 \& package MyApp; \& \& use Dancer2; \& use Dancer2::Plugin::WebSocket; \& \& websocket_on_message sub { \& my( $conn, $message ) = @_; \& $conn\->send( $message . \*(Aq world!\*(Aq ); \& }; \& \& get \*(Aq/\*(Aq => sub { \& my $ws_url = websocket_url; \& return <<"END"; \& \& \&

WebSocket client

\& \& END \& }; \& \& get \*(Aq/say_hi\*(Aq => sub { \& $_\->send([ "Hello!" ]) for websocket_connections; \& }; \& \& true; .Ve .SH DESCRIPTION .IX Header "DESCRIPTION" \&\f(CW\*(C`Dancer2::Plugin::WebSocket\*(C'\fR provides an interface to Plack::App::WebSocket and allows to interact with the webSocket connections within the Dancer app. .PP Plack::App::WebSocket, and thus this plugin, requires a plack server that supports the psgi \fIstreaming\fR, \fInonblocking\fR and \fIio\fR. Twiggy is the most popular server fitting the bill. .SH CONFIGURATION .IX Header "CONFIGURATION" .IP serializer 4 .IX Item "serializer" If serializer is set to a \f(CW\*(C`true\*(C'\fR value, messages will be assumed to be JSON objects and will be automatically encoded/decoded using a JSON::MaybeXS serializer. If the value of \f(CW\*(C`serializer\*(C'\fR is a hash, it'll be passed as arguments to the JSON::MaybeXS constructor. .Sp .Vb 5 \& plugins: \& WebSocket: \& serializer: \& utf8: 1 \& allow_nonref: 1 .Ve .Sp By the way, if you want the connection to automatically serialize data structures to JSON on the client side, you can do something like .Sp .Vb 4 \& var mySocket = new WebSocket(urlMySocket); \& mySocket.sendJSON = function(message) { \& return this.send(JSON.stringify(message)) \& }; \& \& // then later... \& mySocket.sendJSON({ whoa: "auto\-serialization ftw!" }); .Ve .IP mount_path 4 .IX Item "mount_path" Path for the websocket mountpoint. Defaults to \f(CW\*(C`/ws\*(C'\fR. .SH "PLUGIN KEYWORDS" .IX Header "PLUGIN KEYWORDS" In the various callbacks, the connection object \f(CW$conn\fR is a Plack::App::WebSocket::Connection object augmented with the Dancer2::Plugin::WebSocket::Connection role. .SS "websocket_on_open sub { ... }" .IX Subsection "websocket_on_open sub { ... }" .Vb 4 \& websocket_on_open sub { \& my( $conn, $env ) = @_; \& ...; \& }; .Ve .PP Code invoked when a new socket is opened. Gets the new connection object and the Plack \&\f(CW$env\fR hash as arguments. .SS "websocket_on_login sub { ... }" .IX Subsection "websocket_on_login sub { ... }" .Vb 4 \& websocket_on_login sub { \& my( $conn, $env ) = @_; \& ...; \& }; .Ve .PP Code invoked when a new socket is opened. Gets the connection object and the Plack \f(CW$env\fR hash as arguments. .PP Example: return true if user is logged in and the webapp http_cookie is the same as the websocket. .PP .Vb 2 \& my $login_conn; \& my $cookie_name = \*(Aqexample.session\*(Aq; \& \& hook before => sub { \& if (defined cookies\->{$cookie_name}) { \& $login_conn\->{\*(Aqcookie_id\*(Aq} = cookies\->{$cookie_name}\->value; \& } \& $login_conn\->{\*(Aqlogin\*(Aq} = logged_in_user ? 1 : 0; \& }; \& \& websocket_on_login sub { \& my( $conn, $env ) = @_; \& \& my ($cookie_id) = ($env\->{\*(AqHTTP_COOKIE\*(Aq} =~ /$cookie_name=(.*);?/g); \& if (($login_conn\->{\*(Aqlogin\*(Aq}) and ($login_conn\->{\*(Aqcookie_id\*(Aq} eq $cookie_id)) { \& return 1; \& } else { \& warn "require login"; \& return 0; \& } \& }; .Ve .SS "websocket_on_close sub { ... }" .IX Subsection "websocket_on_close sub { ... }" .Vb 4 \& websocket_on_close sub { \& my( $conn ) = @_; \& ...; \& }; .Ve .PP Code invoked when a new socket is opened. Gets the connection object as argument. .SS "websocket_on_error sub { ... }" .IX Subsection "websocket_on_error sub { ... }" .Vb 4 \& websocket_on_error sub { \& my( $env ) = @_; \& ...; \& }; .Ve .PP Code invoked when an error is detected. Gets the Plack \&\f(CW$env\fR hash as argument and is expected to return a Plack triplet. .PP If not explicitly set, defaults to .PP .Vb 8 \& websocket_on_error sub { \& my $env = shift; \& return [ \& 500, \& ["Content\-Type" => "text/plain"], \& ["Error: " . $env\->{"plack.app.websocket.error"}] \& ]; \& }; .Ve .SS "websocket_on_message sub { ... }" .IX Subsection "websocket_on_message sub { ... }" .Vb 4 \& websocket_on_message sub { \& my( $conn, $message ) = @_; \& ...; \& }; .Ve .PP Code invoked when a message is received. Gets the connection object and the message as arguments. .PP Note that while \f(CW\*(C`websocket_on_message\*(C'\fR fires for all messages receives, 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 .PP .Vb 7 \& websocket_on_open sub { \& my( $conn, $env ) = @_; \& $conn\->on( message => sub { \& my( $conn, $message ) = @_; \& warn "I\*(Aqm only being executed for messages sent via this connection"; \& }); \& }; .Ve .SS websocket_connections .IX Subsection "websocket_connections" Returns the list of currently open websocket connections. .SS websocket_url .IX Subsection "websocket_url" Returns the full url of the websocket mountpoint. .PP .Vb 3 \& # assuming host is \*(Aqlocalhost:5000\*(Aq \& # and the mountpoint is \*(Aq/ws\*(Aq \& print websocket_url; # => ws://localhost:5000/ws .Ve .SS websocket_mount .IX Subsection "websocket_mount" Returns the mountpoint and the Plack app coderef to be used for \f(CW\*(C`mount\*(C'\fR in \fIapp.psgi\fR. See the SYNOPSIS. .SH GOTCHAS .IX Header "GOTCHAS" It seems that the closing the socket causes Google's chrome to burp the following to the console: .PP .Vb 1 \& WebSocket connection to \*(Aqws://...\*(Aq failed: Received a broken close frame containing a reserved status code. .Ve .PP 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. .SH "SEE ALSO" .IX Header "SEE ALSO" This plugin is nothing much than a sugar topping atop Plack::App::WebSocket, which is itself AnyEvent::WebSocket::Server wrapped in Plackstic. .PP Mojolicious also has nice WebSocket-related offerings. See Mojolicious::Plugin::MountPSGI or . (hi Joel!) .SH AUTHOR .IX Header "AUTHOR" Yanick Champoux .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" This software is copyright (c) 2021, 2019, 2017 by Yanick Champoux. .PP This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.