This recipe illustrates how to combine multiple servers within a single program. It implements a very simple chat server that is available by telnet or with a MUD client. It also includes a log of recent conversation, which is available through a web interface.

This program is essentially a combination of two other [POE Cookbook] recipes: [Web Server With Components] and [Chat Server]. Please see the other recipes for more in-depth discussion about each part of this program.

#!/usr/bin/perl
use warnings;
use strict;
use CGI qw(:standard);               # For HTML building functions.
use POE;
use POE::Component::Server::HTTP;    # For the web interface.
use POE::Component::Server::TCP;     # For the telnet interface.
sub MAX_LOG_LENGTH () { 50 }
my @chat_log;
### Start the web server.
POE::Component::Server::HTTP->new(
  Port           => 32080,
  ContentHandler => {"/" => \&web_handler},
  Headers        => {Server => 'See http://poe.perl.org/?POE_Cookbook'},
);
### Start the chat server.
POE::Component::Server::TCP->new(
  Alias              => "chat_server",
  Port               => 32081,
  InlineStates       => {send => \&handle_send},
  ClientConnected    => \&client_connected,
  ClientError        => \&client_error,
  ClientDisconnected => \&client_disconnected,
  ClientInput        => \&client_input,
);
### Run the servers together, and exit when they are done.
$poe_kernel->run();
exit 0;
### Handlers for the web server.  These functions are commented at
### http://poe.perl.org/?POE_Cookbook/Web_Server_With_Components
sub web_handler {
  my ($request, $response) = @_;

  # Build the response.
  $response->code(RC_OK);
  $response->push_header("Content-Type", "text/html");
  my $count = @chat_log;
  my $content =
    start_html("Last $count messages.") . h1("Last $count messages.");
  if ($count) {
    $content .= ul(li(\@chat_log));
  }
  else {
    $content .= p("Nothing has been said yet.");
  }
  $content .= end_html();
  $response->content($content);

  # Signal that the request was handled okay.
  return RC_OK;
}
### Handlers for the chat server.  These functions are commented at
### http://poe.perl.org/?POE_Cookbook/Chat_Server
my %users;

sub broadcast {
  my ($sender, $message) = @_;

  # Log it for the web.  This is the only part that's different from
  # the basic chat server.
  push @chat_log, "$sender $message";
  shift @chat_log if @chat_log > MAX_LOG_LENGTH;

  # Send it to everyone.
  foreach my $user (keys %users) {
    if ($user == $sender) {
      $poe_kernel->post($user => send => "You $message");
    }
    else {
      $poe_kernel->post($user => send => "$sender $message");
    }
  }
}

sub handle_send {
  my ($heap, $message) = @_[HEAP, ARG0];
  $heap->{client}->put($message);
}

sub client_connected {
  my $session_id = $_[SESSION]->ID;
  $users{$session_id} = 1;
  broadcast($session_id, "connected.");
}

sub client_disconnected {
  my $session_id = $_[SESSION]->ID;
  delete $users{$session_id};
  broadcast($session_id, "disconnected.");
}

sub client_error {
  my $session_id = $_[SESSION]->ID;
  delete $users{$session_id};
  broadcast($session_id, "disconnected.");
  $_[KERNEL]->yield("shutdown");
}

sub client_input {
  my ($client_host, $session, $input) = @_[KERNEL, SESSION, ARG0];
  broadcast($session->ID, "said: $input");
}