2020-11-22 Writing Servers in Perl using Mojo::IOLoop

I’m thinking of rewriting Phoebe using Mojo::IOLoop instead of Net::Server. One problem I needed to solve was listening on different ports (e.g. 443 and 1965), for different hostnames (alexschroeder.ch, emacswiki.org, communitywiki.org, transjovian.org, toki.transjovian.org, vault.transjovian.org, next.oddmuse.org), with different TLS certificates...

Phoebe

This is what I had:

Mojo::IOLoop->server({port ⇒ ... , address ⇒ ... , tls ⇒ 1, tls_cert ⇒ ..., tls_key ⇒ ... } ⇒ sub { ... })

I started wondering: how can I have multiple ports, multiple addresses, and multiple TLS certs in here? Can they take arrays? The man pages was inconclusive. I looked at the source. The address and port are passed on to IO::Socket::IP. I saw something about PeerAddrInfo... and it got more and more confusing, so I asked on the Perl IRC channel.

“What on earth are you doing, and why do you believe that’s the solution? – LeoNerd

I don’t know why I often end up in these dead ends when I read the man pages for these packages.

If you want to listen on multiple ports, you use multiple servers – LeoNerd

I... I guess that makes sense!

So here’s something that works:

use Mojo::IOLoop;
use Modern::Perl;

# Listen on port 3000
for my $address (qw(localhost 127.0.0.2)) {
  for my $port (qw(79 70)) {
    Mojo::IOLoop->server({address => $address, port => $port} => sub {
      my ($loop, $stream) = @_;
      $stream->on(read => \&echo)})}};

sub echo {
  my ($stream, $bytes) = @_;
  # Write response
  $stream->write("Echo: $bytes");
  $stream->close_gracefully();
}

# Start event loop if necessary
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

Starting it needs sudo because the ports are below 1024: gopher is on port 70 and finger is on port 79.

sudo perl test.pl

Testing it:

$ echo alex | netcat localhost 70
Echo: alex
$ echo alex | netcat localhost 79
Echo: alex
$ echo alex | netcat 127.0.0.1 79
Ncat: Connection refused.
$ echo alex | netcat 127.0.0.2 79
Echo: alex
$ finger alex@localhost
Echo: alex
$ finger berta@127.0.0.1
finger: connect: Connection refused
$ finger berta@127.0.0.2
Echo: berta

Yes! I think I can work with that. 🙂

​#Perl