Difference between revisions of "Server.pl"

From Organic Design wiki
m
m
 
(37 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
{{legacy}}
 +
<source lang="perl">
 
use IO::Socket;
 
use IO::Socket;
 
use IO::Select;
 
use IO::Select;
Line 7: Line 9:
 
%::streams = ();
 
%::streams = ();
 
$::port = shift or 80;
 
$::port = shift or 80;
 +
$0 = "$::daemon: $::peer (http$::port)";
 +
my $subname = $::subname;
  
# Initialise server listening on a port
+
# Initialise server listening on our port
# - we need some graceful trapping here
+
do { unless ( $::server = new IO::Socket::INET
$::server = 0;
 
do {
 
 
 
$::server = new IO::Socket::INET
 
 
Listen => 1,
 
Listen => 1,
 
LocalPort => $::port,
 
LocalPort => $::port,
Proto => 'tcp';
+
Proto => 'tcp',
 
+
ReuseAddr => 1 ) {
unless ( $::server ) {
+
logAdd "Failed to bind to Port$::port, waiting 10 seconds...";
logAdd "Failed to bind to Port$::port, waiting 10 seconds...";
+
sleep(10);
sleep(10);
+
}
}
 
 
 
 
} until ( $::server );
 
} until ( $::server );
 
$::server->autoflush();
 
 
$::select = new IO::Select $::server;
 
$::select = new IO::Select $::server;
$0 = "$::daemon: $::peer (http$::port)";
 
 
logAdd "Listening on port $::port";
 
logAdd "Listening on port $::port";
  
Line 35: Line 30:
 
$::template =~ s/<!-- peer -->/$::peer/g;
 
$::template =~ s/<!-- peer -->/$::peer/g;
 
$::deny = readFile "$::peer.401";
 
$::deny = readFile "$::peer.401";
 
# Views
 
 
my $views = '';
 
my $views = '';
 
$views .= "*[[$_]]\n" for ( 'Update', 'Filter', 'Events', 'Edit' );
 
$views .= "*[[$_]]\n" for ( 'Update', 'Filter', 'Events', 'Edit' );
 
$::template =~ s/<!-- views -->/ wikiParse($views) /e;
 
$::template =~ s/<!-- views -->/ wikiParse($views) /e;
 
# Navigation
 
 
my $nav = "*[[$peer|Gir Home]]\n*[http://www.organicdesign.co.nz/User:$peer User:$peer]\n";
 
my $nav = "*[[$peer|Gir Home]]\n*[http://www.organicdesign.co.nz/User:$peer User:$peer]\n";
 
$nav .= "*[[$_]]\n" for (
 
$nav .= "*[[$_]]\n" for (
Line 53: Line 44:
 
"wikiSync/cmd|Manual wikiSync",
 
"wikiSync/cmd|Manual wikiSync",
 
"swfCompile/cmd|Manual swfCompile",
 
"swfCompile/cmd|Manual swfCompile",
 +
"serverBackup/cmd|Backup server now",
 
"wikiBackup/cmd|Backup wiki now",
 
"wikiBackup/cmd|Backup wiki now",
 
"peerBackup/cmd|Backup peer now",
 
"peerBackup/cmd|Backup peer now",
Line 60: Line 52:
 
$::template =~ s/<!-- navigation -->/ wikiParse($nav) /e;
 
$::template =~ s/<!-- navigation -->/ wikiParse($nav) /e;
  
# Main server loop
+
# Simple HTTP server loop
 
while(1) {
 
while(1) {
 
for my $handle ( $::select->can_read(1) ) {
 
for my $handle ( $::select->can_read(1) ) {
 
my $stream = fileno $handle;
 
my $stream = fileno $handle;
 +
$::subname = "$subname/stream$stream";
 +
 +
# Handle is the server, set up a new stream
 
if ( $handle == $::server ) {
 
if ( $handle == $::server ) {
# Handle is the server, set up a new peer
 
# - but it can't be identified until its msg processed
 
 
my $newhandle = $::server->accept;
 
my $newhandle = $::server->accept;
 
$stream = fileno $newhandle;
 
$stream = fileno $newhandle;
Line 74: Line 67:
 
logAdd "New connection: Stream$stream", 'main/new';
 
logAdd "New connection: Stream$stream", 'main/new';
 
}
 
}
 +
 +
# Handle is an existing stream with data to read
 +
# NOTE: we should disconnect after certain size limit
 +
# - Process (and remove) all complete messages from this peer's buffer
 
elsif ( sysread $handle, my $input, 10000 ) {
 
elsif ( sysread $handle, my $input, 10000 ) {
# Handle is an existing stream with data to read
 
# NOTE: we should disconnect after certain size limit)
 
# - Process (and remove) all complete messages from this peer's buffer
 
 
$::streams{$stream}{buffer} .= $input;
 
$::streams{$stream}{buffer} .= $input;
if ( $::streams{$stream}{buffer} =~ s/^(.*\r?\n\r?\n)//s ) {
+
if ( $::streams{$stream}{buffer} =~ s/^(.*\r?\n\r?\n\x00?)//s ) {
 
processMessage( $stream, $_ ) for split /\r?\n\r?\n\x00?/, $1;
 
processMessage( $stream, $_ ) for split /\r?\n\r?\n\x00?/, $1;
 
}
 
}
 
}
 
}
 +
 +
# Handle is an existing stream with no more data to read
 
else {
 
else {
# Handle is an existing stream with no more data to read
 
 
$::select->remove( $handle );
 
$::select->remove( $handle );
 
delete $::streams{$stream};
 
delete $::streams{$stream};
Line 90: Line 85:
 
logAdd "Stream$stream disconnected.";
 
logAdd "Stream$stream disconnected.";
 
}
 
}
 +
 
}
 
}
 
}
 
}
Line 106: Line 102:
 
# Extract info from the HTTP GET request
 
# Extract info from the HTTP GET request
 
my ( $title, $ct, $ver ) =
 
my ( $title, $ct, $ver ) =
$msg =~ /GET\s+\/+(.*?)(\/(.+))?\s+(HTTP\/[0-9.]+)/
+
$msg =~ /GET\s+\/+(.*?)(\/(.+))?\s+(HTTP\/[0-9.]+)/ ? ( $1, $3, $4 ) : ( $peer, '', 'HTTP/1.1' );
? ( $1, $3, $4 )
 
: ( $peer, '', 'HTTP/1.1' );
 
  
 
# If request authenticates service it, else return 401
 
# If request authenticates service it, else return 401
if (
+
if ( $ct =~ /^cmd/ ? ( $msg =~ /Authorization: Basic (\w+)/ and decode_base64($1) eq "$::peer:$::pwd1" ) : 1 ) {
$ct =~ /^cmd/
 
? ( $msg =~ /Authorization: Basic (\w+)/ and decode_base64($1) eq "$::peer:$::pwd1" )
 
: 1 ) {
 
  
 
# Process a command
 
# Process a command
Line 129: Line 120:
 
logAdd "PEER command \"$title\" received from an instance of \"$type\"";
 
logAdd "PEER command \"$title\" received from an instance of \"$type\"";
  
# Respond to change-event from XmlWiki
+
# peerd-child is doing initial connection, keep handle in global scope
if ( $title eq 'change' ) {
+
# - peerd could talk back here over this stream, but doesn't currently
print $::peerdHandle "Change event received from OrganicDesign Wiki.\x00";
+
if ( $title eq 'connect' and $type eq 'peerd' ) {
 +
$::peerdHandle = $handle;
 +
}
 +
 
 +
# Forward any commands from XmlWiki to peerd unchanged
 +
elsif ( $type eq 'notify-peer.php' ) {
 +
print $::peerdHandle "$title/$type\x00" if defined $::peerdHandle;
 
}
 
}
  
# Spawn an interfaceHandler child to deal with a SWF connection request
+
# SWF initiating connection, ask peerd to connect back on a new stream for a session
 
elsif ( $title eq 'connect' and $type eq 'interface' ) {
 
elsif ( $title eq 'connect' and $type eq 'interface' ) {
# Preserve this stream-handle in global namespace for access by the spawned child
+
# Send stream-id of swf, so we know which one is wanted by the connection back from peerd
$::swfHandle = $handle;
+
print $::peerdHandle "connect-request/stream$stream\x00" if defined $::peerdHandle;
# Remove the handle from this context, but don't close it
+
# Remove the handle from this (server.pl) context, but don't close it
 +
# - also keep our local %stream info to give to session-handler
 
$::select->remove( $handle );
 
$::select->remove( $handle );
delete $::streams{$stream};
 
# and spawn the child process
 
spawn 'sessionHandler';
 
 
}
 
}
  
# peerd-child is connecting, keep handle in global scope
+
# peerd is connecting back from session request, so we can now spawn the session-handler
elsif ( $title eq 'connect' and $type eq 'peerd' ) {
+
elsif ( $title eq 'session' and $type =~ /^peerd\/stream([0-9]+)/ ) {
$::peerdHandle = $handle;
+
# Spawn a new session-handler child and pass the peerd and peer.swf stream-handles
$::select->remove( $handle );
+
spawn 'sessionHandler', $handle, $::streams{$1}{handle};
delete $::streams{$stream};
+
# We can remove the interface from %streams now too
 +
delete $::streams{$1};
 
}
 
}
  
Line 209: Line 205:
 
}
 
}
 
}
 
}
 +
</source>
 +
[[Category:PERL]]

Latest revision as of 13:31, 2 August 2017

Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, now this page is for historic record only.
use IO::Socket;
use IO::Select;
use MIME::Base64;

sub server {

	%::streams = ();
	$::port = shift or 80;
	$0 = "$::daemon: $::peer (http$::port)";
	my $subname = $::subname;

	# Initialise server listening on our port
	do { unless ( $::server = new IO::Socket::INET
			Listen => 1,
			LocalPort => $::port,
			Proto => 'tcp',
			ReuseAddr => 1 ) {
				logAdd "Failed to bind to Port$::port, waiting 10 seconds...";
				sleep(10);
				}
		} until ( $::server );
	$::select = new IO::Select $::server;
	logAdd "Listening on port $::port";

	# HTML interface environment
	$::template = readFile 'peer.html';
	$::template =~ s/<!-- daemon -->/$::title ($::daemon)/g;
	$::template =~ s/<!-- peer -->/$::peer/g;
	$::deny = readFile "$::peer.401";
	my $views = '';
	$views .= "*[[$_]]\n" for ( 'Update', 'Filter', 'Events', 'Edit' );
	$::template =~ s/<!-- views -->/ wikiParse($views) /e;
	my $nav = "*[[$peer|Gir Home]]\n*[http://www.organicdesign.co.nz/User:$peer User:$peer]\n";
	$nav .= "*[[$_]]\n" for (
		"env/cmd|Environment Info",
		"peerlog/cmd|Peer Log",
		"syslog/cmd|Syslog",
		"restart/cmd|Restart $::peer",
		"stop/cmd|Stop $::peer",
		"reboot/cmd|Restart this server",
		"fileSync/cmd|Manual fileSync",
		"wikiSync/cmd|Manual wikiSync",
		"swfCompile/cmd|Manual swfCompile",
		"serverBackup/cmd|Backup server now",
		"wikiBackup/cmd|Backup wiki now",
		"peerBackup/cmd|Backup peer now",
		"scpBackups/cmd|SCP Backup now",
		"Yi|Hexagrams"
		);
	$::template =~ s/<!-- navigation -->/ wikiParse($nav) /e;

	# Simple HTTP server loop
	while(1) {
		for my $handle ( $::select->can_read(1) ) {
			my $stream = fileno $handle;
			$::subname = "$subname/stream$stream";

			# Handle is the server, set up a new stream
			if ( $handle == $::server ) {
				my $newhandle = $::server->accept;
				$stream = fileno $newhandle;
				$::select->add( $newhandle );
				$::streams{$stream}{buffer} = '';
				$::streams{$stream}{handle} = $newhandle;
				logAdd "New connection: Stream$stream", 'main/new';
				}

			# Handle is an existing stream with data to read
			# NOTE: we should disconnect after certain size limit
			# - Process (and remove) all complete messages from this peer's buffer
			elsif ( sysread $handle, my $input, 10000 ) {
				$::streams{$stream}{buffer} .= $input;
				if ( $::streams{$stream}{buffer} =~ s/^(.*\r?\n\r?\n\x00?)//s ) {
					processMessage( $stream, $_ ) for split /\r?\n\r?\n\x00?/, $1;
					}
				}

			# Handle is an existing stream with no more data to read
			else {
				$::select->remove( $handle );
				delete $::streams{$stream};
				$handle->close();
				logAdd "Stream$stream disconnected.";
				}

			}
		}
	}

# Process an incoming HTTP message
sub processMessage {
	my ( $stream, $msg ) = @_;
	my $handle = $::streams{$stream}{handle};
	my $headers = '';
	my $http = '200 OK';
	my $respond = 1;
	my $date = strftime "%a, %d %b %Y %H:%M:%S %Z", localtime;
	my $article = $::deny;

	# Extract info from the HTTP GET request
	my ( $title, $ct, $ver ) =
		$msg =~ /GET\s+\/+(.*?)(\/(.+))?\s+(HTTP\/[0-9.]+)/ ? ( $1, $3, $4 ) : ( $peer, '', 'HTTP/1.1' );

	# If request authenticates service it, else return 401
	if ( $ct =~ /^cmd/ ? ( $msg =~ /Authorization: Basic (\w+)/ and decode_base64($1) eq "$::peer:$::pwd1" ) : 1 ) {

		# Process a command
		if ( $ct =~ /^cmd(.*)/ ) {
			logAdd "Executing $title command.";
			$article = '<pre>'.command($title).'</pre>';
			$ct = '' if $1 ne '/raw';
			}

		# Process request from other peer or peer-related process
		# - doesn't return an HTTP response
		elsif ( $ct =~ /^peer\/(.*)/ ) {
			my $type = $1;
			logAdd "PEER command \"$title\" received from an instance of \"$type\"";

			# peerd-child is doing initial connection, keep handle in global scope
			# - peerd could talk back here over this stream, but doesn't currently
			if ( $title eq 'connect' and $type eq 'peerd' ) {
				$::peerdHandle = $handle;
				}

			# Forward any commands from XmlWiki to peerd unchanged
			elsif ( $type eq 'notify-peer.php' ) {
				print $::peerdHandle "$title/$type\x00" if defined $::peerdHandle;
				}

			# SWF initiating connection, ask peerd to connect back on a new stream for a session
			elsif ( $title eq 'connect' and $type eq 'interface' ) {
				# Send stream-id of swf, so we know which one is wanted by the connection back from peerd
				print $::peerdHandle "connect-request/stream$stream\x00" if defined $::peerdHandle;
				# Remove the handle from this (server.pl) context, but don't close it
				# - also keep our local %stream info to give to session-handler
				$::select->remove( $handle );
				}

			# peerd is connecting back from session request, so we can now spawn the session-handler
			elsif ( $title eq 'session' and $type =~ /^peerd\/stream([0-9]+)/ ) {
				# Spawn a new session-handler child and pass the peerd and peer.swf stream-handles
				spawn 'sessionHandler', $handle, $::streams{$1}{handle};
				# We can remove the interface from %streams now too
				delete $::streams{$1};
				}

			# These peer requests don't want HTTP returned currently
			$respond = 0;
			}

		# Process request for raw SWF
		elsif ( $ct eq 'application/x-shockwave-flash' ) {
			$headers .= "Content-Disposition: inline;filename=$::peer.swf\r\n";
			logAdd "SWF movie \"$title\" requested";
			$article = readFile $title;
			}

		# Process request for SWF embedded in HTML
		elsif ( $ct eq 'swf' ) {
			my $width = 640;
			my $height = 480;
			my $file = "/$::peer.swf/application/x-shockwave-flash";
			$article = "<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"
	codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0\"
	width=\"$width\" height=\"$height\" id=\"$file\" align=\"\"
	type=\"application/x-shockwave-flash\" data=\"$file\">
	<param name=\"movie\" value=\"$file\">
	<param name=\"quality\" value=\"high\">
	<param name=\"bgcolor\" value=\"cccccc\">
	<embed src=\"$file\" quality=\"high\" bgcolor=\"cccccc\"
		width=\"$width\" height=\"$height\" align=\"\"
		name=\"$file\" type=\"application/x-shockwave-flash\"
		pluginspage=\"http://www.macromedia.com/go/getflashplayer\" />
	</object>";
			$ct = '';
			}

		# If still non processed, treat request as local filename to return
		else {
			logAdd "$title($ct) requested";
			$article = readFile $title;
			}

		# Render article
		unless ( $ct ) {
			$ct = 'text/html';
			my $tmp = $::template;
			$tmp =~ s/<!-- title -->/$title/g;
			$tmp =~ s/<!-- content -->/ wikiParse $article /e;
			$article = $tmp;
			}
		}
	else { $http = "401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"private\"" }

	# Send response back to requestor
	if ( $respond ) {
		$headers = "$ver $http\r\nDate: $date\r\nServer: $::daemon\r\n$headers";
		$headers .= "Content-Type: $ct\r\n";
		$headers .= "Connection: close\r\n";
		$headers .= "Content-Length: ".(length $article)."\r\n";
		print $handle "$headers\r\n$article";
		}
	}