changeset 14:28af4b0b32c1

Keepalive: experimental fastcgi support. Works only with patched nginx and must be explicitly activated by define (./configure --with-cc-opt="-D NGX_UPSTREAM_KEEPALIVE_PATCHED").
author Maxim Dounin <mdounin@mdounin.ru>
date Wed, 15 Apr 2009 19:22:11 +0400
parents 181a487581b7
children 4fe7e417c424
files ngx_http_upstream_keepalive_module.c t/fastcgi-keepalive.t
diffstat 2 files changed, 178 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ngx_http_upstream_keepalive_module.c
+++ b/ngx_http_upstream_keepalive_module.c
@@ -313,8 +313,12 @@ ngx_http_upstream_free_keepalive_peer(ng
 
     if (!kp->failed
         && pc->connection != NULL
+#if (NGX_UPSTREAM_KEEPALIVE_PATCHED)
+        && u->keepalive)
+#else
         && (status == NGX_HTTP_NOT_FOUND
             || (status == NGX_HTTP_OK && u->header_sent && u->length == 0)))
+#endif
     {
         c = pc->connection;
 
new file mode 100644
--- /dev/null
+++ b/t/fastcgi-keepalive.t
@@ -0,0 +1,174 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+
+# Test for fastcgi backend with keepalive.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->plan(6)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+master_process off;
+daemon         off;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    access_log    off;
+
+    client_body_temp_path  %%TESTDIR%%/client_body_temp;
+    fastcgi_temp_path      %%TESTDIR%%/fastcgi_temp;
+    proxy_temp_path        %%TESTDIR%%/proxy_temp;
+
+    upstream backend {
+        server 127.0.0.1:8081;
+        keepalive 1;
+    }
+
+    server {
+        listen       localhost:8080;
+        server_name  localhost;
+
+        location / {
+            fastcgi_pass backend;
+        }
+    }
+}
+
+EOF
+
+$t->run_daemon(\&fastcgi_test_daemon);
+$t->run();
+
+###############################################################################
+
+like(http_get('/'), qr/SEE-THIS/, 'fastcgi request');
+like(http_get('/redir'), qr/302/, 'fastcgi redirect');
+like(http_get('/'), qr/^request: 3$/m, 'fastcgi third request');
+
+{
+local $TODO = 'needs experimental patches';
+
+like(http_get('/single'), qr/^connection: 1$/m, 'single connection used');
+
+}
+
+# New connection to fastcgi application should be established after HEAD
+# requests since nginx doesn't read whole response (as it doesn't need
+# body).
+
+unlike(http_head('/head'), qr/SEE-THIS/, 'no data in HEAD');
+
+{
+local $TODO = 'needs experimental patches';
+
+like(http_get('/after'), qr/^connection: 2$/m, 'new connection after HEAD');
+}
+
+###############################################################################
+
+# Simple FastCGI responder implementation.  Unlike FCGI and FCGI::Async it's
+# able to count connections.
+
+# http://www.fastcgi.com/devkit/doc/fcgi-spec.html
+
+sub fastcgi_read_record($) {
+	my ($socket) = @_;
+
+	my ($n, $h, $header);
+
+	$n = $socket->read($header, 8);
+	return undef if !defined $n or $n != 8;
+
+	@{$h}{qw/ version type id clen plen /} = unpack("CCnnC", $header);
+
+	$n = $socket->read($h->{content}, $h->{clen});
+	return undef if $n != $h->{clen};
+
+	$n = $socket->read($h->{padding}, $h->{plen});
+	return undef if $n != $h->{plen};
+
+	$h->{socket} = $socket;
+	return $h;
+}
+
+sub fastcgi_respond($$) {
+	my ($h, $body) = @_;
+
+	# stdout
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id},
+		length($body), 0));
+	$h->{socket}->write($body);
+
+	select(undef, undef, undef, 0.1);
+
+	# close stdout
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id}, 0, 0));
+
+	select(undef, undef, undef, 0.1);
+
+	# end request
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 3, $h->{id}, 8, 0));
+	$h->{socket}->write(pack("NCxxx", 0, 0));
+}
+
+sub fastcgi_test_daemon {
+	my $server = IO::Socket::INET->new(
+		Proto => 'tcp',
+		LocalAddr => '127.0.0.1:8081',
+		Listen => 5,
+		Reuse => 1
+	)
+		or die "Can't create listening socket: $!\n";
+
+	local $SIG{PIPE} = 'IGNORE';
+
+	my $ccount = 0;
+	my $rcount = 0;
+
+	while (my $client = $server->accept()) {
+		$client->autoflush(1);
+		Test::Nginx::log_core('||', "fastcgi connection");
+
+		$ccount++;
+
+		while (my $h = fastcgi_read_record($client)) {
+			Test::Nginx::log_core('||', "fastcgi record: "
+				. " $h->{version}, $h->{type}, $h->{id}, "
+				. "'$h->{content}'");
+
+			# skip everything unless stdin, then respond
+			next if $h->{type} != 5;
+
+			$rcount++;
+
+			# respond
+			fastcgi_respond($h, <<EOF);
+Location: http://localhost:8080/redirect
+Content-Type: text/html
+
+SEE-THIS
+request: $rcount
+connection: $ccount
+EOF
+		}
+
+		close $client;
+	}
+}
+
+###############################################################################