commit 18ca72abe3c1e23f6448d970a84e8b0843d3b7e1
parent 2c92a238e2fe0a66b074434dc725437a6f133d07
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 19:37:28 +0100
2.48
Diffstat:
22 files changed, 179 insertions(+), 20 deletions(-)
diff --git a/ChangeLog b/ChangeLog
@@ -1,3 +1,8 @@
+2.48 [KK 2009-03-26]
+- Implemented onstart/onend hooks (flags -z, -Z).
+- Sticky HTTP mode inspects the URI (parameter XRTarget) when no
+ sticky cookie is present.
+
2.47 [KK 2009-03-04]
- Stored-ip dispatching was enhanced to "anticipate" reconnects from
previously seen clients. The number of anticipated connections is taken
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
# Top-level Makefile for XR
# -------------------------
-VER = 2.47
+VER = 2.48
PREFIX = $(DESTDIR)/usr
BINDIR = $(PREFIX)/sbin
MANDIR = $(PREFIX)/share/man
diff --git a/doc/xr.odt b/doc/xr.odt
Binary files differ.
diff --git a/doc/xr.pdf b/doc/xr.pdf
Binary files differ.
diff --git a/doc/xrctl.xml.5 b/doc/xrctl.xml.5
@@ -118,6 +118,17 @@ distributed with the sources for a full description.
closing connections are in TIME_WAIT state, use: -->
<closesocketsfast>yes</closesocketsfast>
+ <!-- XR can run specific scripts when client activity starts or
+ ends. When given, the scripts are run with the arguments:
+ the client IP, and the back end (server:port). A very
+ simple script /where/ever/activitystart might e.g. do:
+ echo Client $1 is going to back end $2 >> /tmp/activity.log
+ A very simple script /where/ever/activityend might e.g. do:
+ echo Client $1 is done with back end $2 >> /tmp/activity.log
+ -->
+ <onstart>/where/ever/activitystart</onstart>
+ <onend>/where/ever/activityend</onend>
+
<!-- Access restrictions: we allow from two IP ranges, and deny
from one IP address. The overall results:will be:
- Access will be allowed from 10.*.*.*
diff --git a/test/onend b/test/onend
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo Client $1 was handled by $2 and is now stopping >> /tmp/activity.log
diff --git a/test/onstart b/test/onstart
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+echo Client $1 will be handled by $2 >> /tmp/activity.log
+
+
diff --git a/xr/Dispatchers/httpdispatcher/dispatch.cc b/xr/Dispatchers/httpdispatcher/dispatch.cc
@@ -48,12 +48,14 @@ void HttpDispatcher::dispatch() {
}
targetlist(v);
}
-
+
// Dispatch as a normal backend if sticky HTTP is off, or if the
// sticky target is badly specified.
if (!config.stickyhttp() ||
- sscanf (buf.cookievalue ("XRTarget").c_str(),
- "%d", &stickytarget) < 1 ||
+ (sscanf (buf.cookievalue ("XRTarget").c_str(),
+ "%d", &stickytarget) < 1 &&
+ sscanf (buf.paramvalue ("XRTarget").c_str(),
+ "%d", &stickytarget) < 1) ||
stickytarget >= balancer.nbackends()) {
issticky(false);
TcpDispatcher::dispatch();
@@ -74,6 +76,7 @@ void HttpDispatcher::dispatch() {
issticky(true);
}
}
+
} catch (Error const &e) {
senderrorpage(e.what());
diff --git a/xr/Dispatchers/tcpdispatcher/execute.cc b/xr/Dispatchers/tcpdispatcher/execute.cc
@@ -1,5 +1,24 @@
#include "tcpdispatcher"
+static void sysrun(string const &s) {
+ int ret = system(s.c_str());
+ if (ret == -1) {
+ warnmsg(Mstr("Failed to start command: ") + s + "\n");
+ return;
+ }
+ if (WIFEXITED(ret)) {
+ int stat = WEXITSTATUS(ret);
+ if (stat)
+ warnmsg(Mstr("Command" ) + s +
+ Mstr(" exited with status ") + Mstr(stat) + "\n");
+ else
+ msg(Mstr("Command ") + s +
+ Mstr(" terminated normally.\n"));
+ return;
+ }
+ warnmsg(Mstr("Command ") + s + Mstr(" failed miserably!\n"));
+}
+
void TcpDispatcher::execute() {
Threadlist::clientfd(clientfd());
@@ -25,11 +44,19 @@ void TcpDispatcher::execute() {
(Mstr(" to ") + balancer.backend(targetbackend()).description()) +
(Mstr(", fd ") + backendfd()) + "\n");
+
Threadlist::desc("Serving");
Threadlist::backend(targetbackend());
Threadlist::backendfd(backendfd());
balancer.backend(targetbackend()).startconnection();
+ if (config.onstart().length()) {
+ ostringstream o;
+ o << config.onstart() << ' ' << clientipstr() << ' '
+ << balancer.backend(targetbackend()).description();
+ msg (Mstr("Running onstart script: ") + o.str() + "\n");
+ sysrun(o.str());
+ }
try {
handle();
@@ -39,11 +66,18 @@ void TcpDispatcher::execute() {
Mutex::unlock(&cerr);
}
- balancer.backend(targetbackend()).endconnection();
-
socketclose (clientfd());
socketclose (backendfd());
+ balancer.backend(targetbackend()).endconnection();
+ if (config.onend().length()) {
+ ostringstream o;
+ o << config.onend() << ' ' << clientipstr() << ' '
+ << balancer.backend(targetbackend()).description();
+ msg (Mstr("Running onend script: ") + o.str() + "\n");
+ sysrun(o.str());
+ }
+
msg ((Mstr("Done dispatching to back end fd ") + backendfd()) +
(Mstr(" at ") + balancer.backend(targetbackend()).description()) +
"\n");
diff --git a/xr/config/config b/xr/config/config
@@ -30,7 +30,7 @@ public:
Servertype::Type stype() const { return (styp.type()); }
void stype(string const &s) { styp.type(s); }
string stypestr() const { return (styp.typestr()); }
- string sipaddr() const { return (sip); }
+ string const &sipaddr() const { return (sip); }
int sport() const { return (lport); }
int backends() const { return (blist.size()); }
@@ -67,9 +67,11 @@ public:
unsigned maxconn() const { return (max_conn); }
void maxconn (unsigned m);
- string externalalgorithm() const { return (external_algorithm); }
+ string const &externalalgorithm() const {
+ return (external_algorithm);
+ }
- string pidfile() const { return (pid_file); }
+ string const &pidfile() const { return (pid_file); }
void pidfile (string const &p);
bool prefixtimestamp() const { return (prefix_timestamp); }
@@ -79,16 +81,20 @@ public:
void fastclose (bool f);
bool usewebinterface() const { return use_webinterface; }
- string webinterfaceip() const { return webinterface_ip; }
+ string const &webinterfaceip() const {
+ return webinterface_ip;
+ }
int webinterfaceport() const { return webinterface_port; }
unsigned nserverheaders() const { return (serverheaders.size()); }
- string serverheader (unsigned n) { return (serverheaders[n]); }
+ string const &serverheader (unsigned n) {
+ return (serverheaders[n]);
+ }
void addserverheader (string const &s);
void removeserverheader (unsigned i);
void changeserverheader (unsigned i, string const &s);
- string dumpdir() const { return (dump_dir); }
+ string const &dumpdir() const { return (dump_dir); }
void dumpdir (string s) { dump_dir = s; }
unsigned softmaxconnrate() const { return soft_maxconnrate; }
@@ -131,19 +137,24 @@ public:
return (dmode.modestr());
}
- string softmaxconnexcess() const {
+ string const &softmaxconnexcess() const {
return soft_maxconn_excess_prog;
}
void softmaxconnexcess(string const &s) {
soft_maxconn_excess_prog = s;
}
- string hardmaxconnexcess() const {
+ string const &hardmaxconnexcess() const {
return hard_maxconn_excess_prog;
}
void hardmaxconnexcess(string const &s) {
hard_maxconn_excess_prog = s;
}
+ void onstart(string s) { on_start = s; }
+ string const &onstart() const { return on_start; }
+ void onend(string s) { on_end = s; }
+ string const &onend() const { return on_end; }
+
private:
void setbackend (string const &s, string const &hostmatch,
@@ -191,6 +202,7 @@ private:
static string soft_maxconn_excess_prog;
static string hard_maxconn_excess_prog;
static unsigned dns_cache_timeout;
+ static string on_start, on_end;
};
extern Config config;
diff --git a/xr/config/config1.cc b/xr/config/config1.cc
@@ -38,6 +38,8 @@ unsigned Config::quit_after = 0;
string Config::soft_maxconn_excess_prog = "";
string Config::hard_maxconn_excess_prog = "";
unsigned Config::dns_cache_timeout = 3600;
+string Config::on_start = "";
+string Config::on_end = "";
Config::Config () {
}
diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc
@@ -16,7 +16,7 @@ void Config::parsecmdline (int ac, char **av) {
throw Error("Bad command line '" + cmdline + "'\n" + USAGE);
# define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:g:hH:Il:" \
- "m:M:nPp:Q:r:R:Ss:t:T:u:U:vVW:w:xX"
+ "m:M:nPp:Q:r:R:Ss:t:T:u:U:vVW:w:xXz:Z:"
# ifdef HAVE_GETOPT_LONG
static struct option longopts[] = {
{ "allow-from", required_argument, 0, 'a' },
@@ -56,6 +56,8 @@ void Config::parsecmdline (int ac, char **av) {
{ "web-interface", required_argument, 0, 'W' },
{ "add-xr-version", no_argument, 0, 'X' },
{ "add-x-forwarded-for", no_argument, 0, 'x' },
+ { "onstart", required_argument, 0, 'z' },
+ { "onend", required_argument, 0, 'Z' },
{ 0, 0, 0, 0 }
};
# endif
@@ -227,6 +229,12 @@ void Config::parsecmdline (int ac, char **av) {
case 'x':
add_x_forwarded_for = true;
break;
+ case 'z':
+ onstart(optarg);
+ break;
+ case 'Z':
+ onend(optarg);
+ break;
default:
throw Error("Unknown flag, try 'xr -h' for usage");
break;
diff --git a/xr/etc/Makefile.class b/xr/etc/Makefile.class
@@ -15,4 +15,4 @@ $(BASE)/xr/$(BUILDDIR)/$(DIR)_%.o: %.cc
-DCONF_OPTFLAGS='"$(CONF_OPTFLAGS)"' $(CONF_STRNSTR) \
$(CONF_GETOPT) $(CONF_GETOPT_LONG) $(CONF_INET_ATON) \
-I$(BASE)/xr \
- -c -g -Wall -o $@ $<
+ -c -g -Wall -Werror -o $@ $<
diff --git a/xr/etc/status.xslt b/xr/etc/status.xslt
@@ -411,6 +411,24 @@
</td>
</tr>
<tr>
+ <td>Activity scripts</td>
+ <td>Onstart command</td>
+ <td colspan="2" align="right">
+ <input type="text" size="30" name="onstart" id="onstart"
+ value="{onstart}"
+ onchange="goto('/server/onstart/', 'onstart');"/>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Onend command</td>
+ <td colspan="2" align="right">
+ <input type="text" size="30" name="onend" id="onend"
+ value="{onend}"
+ onchange="goto('/server/onend/', 'onend');"/>
+ </td>
+ </tr>
+ <tr>
<td>Network buffer size</td>
<td colspan="2">bytes</td>
<td>
diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt
@@ -154,6 +154,12 @@ may not exist on your platform):
-x, --add-x-forwarded-for
Adds X-Forwarded-For with external IP address to back end streams in
HTTP messages.
+ -z CMD, --onstart CMD
+ Runs CMD just before connecting a client to a back end. The arguments
+ to the command are: the client's IP address, and the back end address.
+ -Z CMD, --onend CMD
+ Runs CMD after termination of a client. For the arguments of CMD see -z.
+
XR's messages are sent to stderr. Invoke XR daemons using something like
"xr -b ... [other flags] 2>&1 | logger &", or use xrctl.
diff --git a/xr/httpbuffer/cookievalue.cc b/xr/httpbuffer/cookievalue.cc
@@ -4,16 +4,19 @@ string Httpbuffer::cookievalue (string c) {
PROFILE("Httpheader::cookievalue");
string cval = headerval ("Cookie");
- size_t pos;
+ string match = c;
+ if (match[match.size() - 1] != '=')
+ match += '=';
string ret = "";
- if ( (pos = cval.find (c)) != string::npos) {
- pos++;
- pos += c.size();
+ size_t pos = cval.find(match);
+ if (pos != string::npos) {
+ pos += match.size();
for (char ch = cval[pos];
pos < cval.size() && ch != ';' && ch != ',' && ch;
ch = cval[++pos]) {
ret += ch;
}
}
+ msg(Mstr("Cookie value '") + c + Mstr("' : '") + ret + "'\n");
return (ret);
}
diff --git a/xr/httpbuffer/httpbuffer b/xr/httpbuffer/httpbuffer
@@ -34,6 +34,7 @@ public:
void replaceheader (string const &h);
string cookievalue (string var);
+ string paramvalue(string var);
RequestMethod requestmethod();
diff --git a/xr/httpbuffer/paramvalue.cc b/xr/httpbuffer/paramvalue.cc
@@ -0,0 +1,21 @@
+#include "httpbuffer"
+
+string Httpbuffer::paramvalue (string c) {
+ PROFILE("Httpheader::paramvalue");
+
+ string uri = requesturi();
+ string match = c;
+ if (match[match.size() - 1] != '=')
+ match += '=';
+ string ret = "";
+ size_t pos = uri.find(match);
+ if (pos != string::npos) {
+ pos += match.size();
+ for (char ch = uri[pos];
+ pos < uri.size() && ch != '&' && ch != '?';
+ ch = uri[++pos])
+ ret += ch;
+ }
+ msg(Mstr("Param value '") + c + Mstr("' : '") + ret + "'\n");
+ return ret;
+}
diff --git a/xr/mstr/mstr b/xr/mstr/mstr
@@ -7,6 +7,11 @@ class Mstr: public string {
public:
Mstr(string s): string(s) {}
Mstr(char const *s): string(s) {}
+ Mstr(int i) {
+ ostringstream o;
+ o << i;
+ *this = o.str();
+ }
Mstr const &operator+ (int i) {
ostringstream o;
o << i;
diff --git a/xr/webinterface/answer.cc b/xr/webinterface/answer.cc
@@ -397,6 +397,24 @@ void Webinterface::answer(Httpbuffer req) {
return;
}
+ // /server/onstart/
+ // /server/onstart/PROGRAM
+ if (parts.size() == 3 &&
+ parts[0] == "server" && parts[1] == "onstart") {
+ config.onstart(parts[2]);
+ answer_status();
+ return;
+ }
+
+ // /server/onend/
+ // /server/onend/PROGRAM
+ if (parts.size() == 3 &&
+ parts[0] == "server" && parts[1] == "onend") {
+ config.onend(parts[2]);
+ answer_status();
+ return;
+ }
+
// /server/addbackend/IP:PORT
if (parts.size() == 3 &&
parts[0] == "server" && parts[1] == "addbackend") {
diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc
@@ -25,6 +25,8 @@ void Webinterface::answer_status() {
" <dnscachetimeout>" << config.dnscachetimeout() << "</dnscachetimeout>\n"
" <buffersize>" << config.buffersize() << "</buffersize>\n"
" <closesocketsfast>" << config.fastclose() << "</closesocketsfast>\n"
+ " <onstart>" << config.onstart() << "</onstart>\n"
+ " <onend>" << config.onend() << "</onend>\n"
" <checks>\n"
" <wakeupinterval>" << config.wakeupsec() << "</wakeupinterval>\n"
" <checkupinterval>" << config.checkupsec() << "</checkupinterval>\n"
diff --git a/xrctl/xrctl b/xrctl/xrctl
@@ -474,6 +474,8 @@ sub xr_cmdarr {
$default_softmaxconnexcess),
flag($ss, '--dns-cache-timeout', 'dnscachetimeout',
$default_dnscachetimeout),
+ flag($ss, '--onstart', 'onstart'),
+ flag($ss, '--onend', 'onend'),
flag($ss, '--log-traffic-dir', 'logtrafficdir', ''));
for my $k (sort (keys (%boolflags))) {
push (@cmd, $boolflags{$k}) if (istrue($ss->data($k)));