PageRenderTime 27ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/poe-d.pl

https://github.com/Sec42/beopardy
Perl | 335 lines | 255 code | 44 blank | 36 comment | 14 complexity | bb429cc20c56a6b07fa475bd3032e336 MD5 | raw file
  1. #!/usr/bin/perl
  2. use warnings;
  3. use strict;
  4. use feature 'switch';
  5. use CGI qw(:standard); # For HTML building functions.
  6. use Data::Dumper;
  7. $Data::Dumper::Indent = 1;
  8. use POE;
  9. use POE::Component::Server::HTTP; # For the web interface.
  10. use POE::Component::Server::TCP; # For the telnet interface.
  11. use POE::Filter::WebSocket;
  12. use POE::Wheel::ReadLine; # For the cli interface.
  13. use Protocol::WebSocket::Handshake::Server; # WebSocket implementation
  14. use Protocol::WebSocket::Frame;
  15. use JSON;
  16. use Bpardy;
  17. sub MAX_LOG_LENGTH () { 50 }
  18. my @chat_log;
  19. ### Start the web server.
  20. POE::Component::Server::HTTP->new(
  21. Port => 32080,
  22. ContentHandler => {"/" => \&web_handler, },
  23. StreamHandler => \&stream,
  24. Headers => {Server => 'POEpardy/1.0'},
  25. );
  26. ### Start the websocket-server.
  27. POE::Component::Server::TCP->new(
  28. Alias => "ws_server",
  29. Port => 8090,
  30. InlineStates => {
  31. send => \&ws_handle_send,
  32. announce => \&ws_handle_announce,
  33. },
  34. ClientFilter => 'POE::Filter::Stream',
  35. ClientConnected => \&ws_connected,
  36. ClientError => \&ws_error,
  37. ClientDisconnected => \&ws_disconnected,
  38. ClientInput => \&ws_input,
  39. );
  40. ### Start the chat server.
  41. POE::Component::Server::TCP->new(
  42. Alias => "chat_server",
  43. Port => 32082,
  44. InlineStates => {send => \&handle_send, announce => \&handle_send},
  45. ClientConnected => \&client_connected,
  46. ClientError => \&client_error,
  47. ClientDisconnected => \&client_disconnected,
  48. ClientInput => \&client_input,
  49. );
  50. ### Start the cli.
  51. POE::Session->create(
  52. inline_states => {
  53. _start => \&cli_init,
  54. send => \&console_output,
  55. announce => \&console_announce,
  56. cli_input => \&console_input,
  57. },
  58. );
  59. ### Run the servers together, and exit when they are done.
  60. $poe_kernel->run();
  61. exit 0;
  62. my %users;
  63. ###
  64. ### Handlers for the cli.
  65. ###
  66. sub cli_init {
  67. my $heap = $_[HEAP];
  68. my $session_id = $_[SESSION]->ID;
  69. $heap->{cli_wheel} = POE::Wheel::ReadLine->new(InputEvent => 'cli_input');
  70. $heap->{cli_wheel}->get("=> ");
  71. $users{$session_id} = 1;
  72. $_[KERNEL]->yield("cli_input","load Runde1");
  73. Bpardy::setdebug(sub { $heap->{cli_wheel}->put("dbg: @_");});
  74. };
  75. sub console_input {
  76. my ($heap, $input, $exception) = @_[HEAP, ARG0, ARG1];
  77. if (defined $input) {
  78. $heap->{cli_wheel}->addhistory($input);
  79. # $heap->{cli_wheel}->put("You Said: $input");
  80. handle_command($input);
  81. } elsif ($exception eq 'cancel') {
  82. $heap->{cli_wheel}->put("Canceled.");
  83. } else {
  84. $heap->{cli_wheel}->put("Bye.");
  85. delete $heap->{cli_wheel};
  86. exit(-1); # XXX: should be a clean shutdown.
  87. return;
  88. }
  89. # Prompt for the next bit of input.
  90. $heap->{cli_wheel}->get("=> ");
  91. };
  92. sub console_output {
  93. my ($heap, $input) = @_[HEAP, ARG0];
  94. $heap->{cli_wheel}->put("+ $input");
  95. }
  96. sub console_announce {
  97. my ($heap, $input) = @_[HEAP, ARG0];
  98. my $d= Data::Dumper->Dump([$input],["announcing"]);
  99. $d=~s/\n/\r\n/g; # Needed due to raw tty mode.
  100. $heap->{cli_wheel}->put("+ ".$d);
  101. }
  102. ###
  103. ### Handlers for the web server.
  104. ###
  105. sub web_handler {
  106. my ($request, $response) = @_;
  107. # Build the response.
  108. $response->code(RC_OK);
  109. $response->push_header("Content-Type", "text/html");
  110. my $count = @chat_log;
  111. my $content =
  112. start_html("Last $count messages.") . h1("Last $count messages.");
  113. if ($count) {
  114. $content .= ul(li(\@chat_log));
  115. }
  116. else {
  117. $content .= p("Nothing has been said yet.");
  118. }
  119. $content .= end_html();
  120. $response->content($content);
  121. # Signal that the request was handled okay.
  122. return RC_OK;
  123. }
  124. ###
  125. ### Handlers for the websocket server.
  126. ###
  127. my %wsstate;
  128. sub ws_handle_send {
  129. my ($heap, $message) = @_[HEAP, ARG0];
  130. my $session_id = $_[SESSION]->ID;
  131. return if(!$users{$session_id}); # I'm a zombie
  132. my $frame=$wsstate{$session_id}{frame};
  133. $heap->{client}->put(
  134. $frame->new($message)->to_string
  135. );
  136. }
  137. sub ws_handle_announce {
  138. my ($heap, $message) = @_[HEAP, ARG0];
  139. my $session_id = $_[SESSION]->ID;
  140. return if(!$users{$session_id}); # I'm a zombie
  141. my $frame=$wsstate{$session_id}{frame};
  142. $heap->{client}->put(
  143. $frame->new(encode_json($message))->to_string
  144. );
  145. }
  146. sub ws_connected {
  147. my $session_id = $_[SESSION]->ID;
  148. $wsstate{$session_id}{state} = "startup";
  149. console("WebSocket ($session_id) connected.");
  150. }
  151. sub ws_disconnected {
  152. my $session_id = $_[SESSION]->ID;
  153. delete $wsstate{$session_id};
  154. delete $users{$session_id};
  155. console("WebSocket ($session_id) disconnected.");
  156. }
  157. sub ws_error {
  158. my $session_id = $_[SESSION]->ID;
  159. delete $wsstate{$session_id};
  160. delete $users{$session_id};
  161. console("WebSocket ($session_id) error-disconnected.");
  162. $_[KERNEL]->yield("shutdown");
  163. }
  164. sub ws_input {
  165. my ($client_host, $session, $chunk) = @_[KERNEL, SESSION, ARG0];
  166. my $session_id = $_[SESSION]->ID;
  167. $wsstate{$session_id}{hs} = Protocol::WebSocket::Handshake::Server->new
  168. if (!defined $wsstate{$session_id}{hs});
  169. my $hs=$wsstate{$session_id}{hs};
  170. if (!$hs->is_done) {
  171. $hs->parse($chunk);
  172. if ($hs->is_done) {
  173. $wsstate{$session_id}{frame}=Protocol::WebSocket::Frame->new;
  174. $_[HEAP]{client}->put($hs->to_string);
  175. console("WSConnect done.");
  176. $users{$session_id} = 1;
  177. }
  178. return;
  179. }
  180. my $frame=$wsstate{$session_id}{frame};
  181. $frame->append($chunk);
  182. while (my $message = $frame->next) {
  183. # $_[HEAP]{client}->put($frame->new($message)->to_string);
  184. # XXX: Maybe move to separate sub?
  185. my $client=$_[HEAP]->{client};
  186. console("socketinput: $message");
  187. my @cmd=split(/ /,$message);
  188. given($cmd[0]){
  189. when("board"){
  190. $client->put($frame->new(encode_json({
  191. board => $Bpardy::game->{board},
  192. categories => $Bpardy::game->{cats},
  193. players => $Bpardy::game->{names},
  194. }))->to_string);
  195. }
  196. when("question"){
  197. # $client->put($frame->new(
  198. # encode_json({question =>Bpardy::ask($cmd[1])})
  199. # )->to_string
  200. # );
  201. announce({buzzer => 1,foo => 2});
  202. };
  203. default{
  204. console("unhandled input from WebSocket: $message");
  205. };
  206. };
  207. };
  208. }
  209. ###
  210. ### Handlers for the chat server.
  211. ###
  212. sub broadcast {
  213. my ($sender, $message) = @_;
  214. # Log it for the web. This is the only part that's different from
  215. # the basic chat server.
  216. push @chat_log, "$sender $message";
  217. shift @chat_log if @chat_log > MAX_LOG_LENGTH;
  218. # Send it to everyone.
  219. foreach my $user (keys %users) {
  220. if ($user == $sender) {
  221. $poe_kernel->post($user => send => "You $message");
  222. }
  223. else {
  224. $poe_kernel->post($user => send => "$sender $message");
  225. }
  226. }
  227. }
  228. sub handle_send {
  229. my ($heap, $message) = @_[HEAP, ARG0];
  230. $heap->{client}->put($message);
  231. }
  232. sub client_connected {
  233. my $session_id = $_[SESSION]->ID;
  234. $users{$session_id} = 1;
  235. broadcast($session_id, "connected.");
  236. }
  237. sub client_disconnected {
  238. my $session_id = $_[SESSION]->ID;
  239. delete $users{$session_id};
  240. broadcast($session_id, "disconnected.");
  241. }
  242. sub client_error {
  243. my $session_id = $_[SESSION]->ID;
  244. delete $users{$session_id};
  245. broadcast($session_id, "disconnected(error).");
  246. $_[KERNEL]->yield("shutdown");
  247. }
  248. sub client_input {
  249. my ($client_host, $session, $input) = @_[KERNEL, SESSION, ARG0];
  250. broadcast($session->ID, "said: $input");
  251. handle_command($input);
  252. }
  253. ###
  254. ### Other functions.
  255. ###
  256. sub handle_command{
  257. my @cmd=split(/ /,shift);
  258. given($cmd[0]){
  259. when ("load"){
  260. my $q=Bpardy::load($cmd[1]);
  261. console("Loading result: $q");
  262. }
  263. when ("question"){
  264. my $q=Bpardy::ask($cmd[1]);
  265. console("Your q is:".encode_json($q));
  266. };
  267. when ("board"){
  268. console("Board: ".encode_json({board => $Bpardy::game->{board}}));
  269. };
  270. when ("buzzer"){
  271. announce({buzzer => 1});
  272. };
  273. default {
  274. console("Unknown command: >@cmd<");
  275. };
  276. };
  277. };
  278. sub console { # Log something to everyone. (Maybe skip WebSocket?)
  279. my ($message) = @_;
  280. # Send it to everyone.
  281. foreach my $user (keys %users) {
  282. $poe_kernel->post($user => send => "$message");
  283. }
  284. }
  285. sub announce { # Announce game events to everyone.
  286. my ($message) = @_;
  287. # Send it to everyone.
  288. foreach my $user (keys %users) {
  289. $poe_kernel->post($user => "announce" , $message);
  290. }
  291. }