commit 0b9caf29b3c486b8429435b39efedcacafeb94b2
parent 34ac55e5fe559d5a0bce225a0e4469bc15c2a7e8
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 19:37:14 +0100
2.43
Diffstat:
25 files changed, 234 insertions(+), 76 deletions(-)
diff --git a/ChangeLog b/ChangeLog
@@ -1,3 +1,9 @@
+2.43 [KK 2009-02-09]
+- Added Httpbuffer::replaceheader() methods
+- Added flag -I (to replace Host: headers), integrated in xrctl /
+ webinterface
+- Webinterface reports approx. # of open fd's and the limit (in activity)
+
2.42 [KK 2009-01-28]
- Bugfix in "xrctl generateconfig". The activity info introduced
before (which the web interface now emites), confused xrctl.
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
# Top-level Makefile for XR
# -------------------------
-VER = 2.42
+VER = 2.43
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/test/sampleconf.xml b/test/sampleconf.xml
@@ -143,10 +143,12 @@
no header for the XR version,
a header X-Forwarded-For: client-ip
no sticky http sessions
+ modification of the Host: header to the back end server name
two serverheaders to insert -->
<addxrversion>off</addxrversion>
<addxforwardedfor>on</addxforwardedfor>
<stickyhttp>off</stickyhttp>
+ <replacehostheader>on</replacehostheader>
<serverheaders>
<header>MyFirstHeader: Whatever</header>
<header>MySecondHeader: WhateverElse</header>
diff --git a/xr/config/config b/xr/config/config
@@ -61,6 +61,9 @@ public:
bool stickyhttp() const { return (sticky_http); }
void stickyhttp(bool b);
+ bool replacehostheader() const { return replace_host_header; }
+ void replacehostheader(bool s) { replace_host_header = s; }
+
unsigned maxconn() const { return (max_conn); }
void maxconn (unsigned m);
@@ -166,6 +169,7 @@ private:
static bool debug_flag;
static bool add_x_forwarded_for;
static bool sticky_http;
+ static bool replace_host_header;
static unsigned max_conn;
static string external_algorithm;
static string pid_file;
diff --git a/xr/config/config1.cc b/xr/config/config1.cc
@@ -16,6 +16,7 @@ bool Config::add_xr_version = false;
bool Config::debug_flag = false;
bool Config::add_x_forwarded_for = false;
bool Config::sticky_http = false;
+bool Config::replace_host_header = false;
unsigned Config::max_conn = 0;
string Config::external_algorithm = "";
string Config::pid_file = "";
diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc
@@ -13,50 +13,50 @@ void Config::parsecmdline (int ac, char **av) {
}
// Not a single argument? Usage.
if (ac == 1)
- throw static_cast<Error>("Bad command line '") +
- cmdline + "'\n" + USAGE;
+ throw Error("Bad command line '" + cmdline + "'\n" + USAGE);
-# define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:g:hH:l:" \
+# 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"
# ifdef HAVE_GETOPT_LONG
static struct option longopts[] = {
- { "allow-from", required_argument, 0, 'a' },
- { "deny-from", required_argument, 0, 'A' },
- { "backend", required_argument, 0, 'b' },
- { "buffer-size", required_argument, 0, 'B' },
- { "checkup-interval", required_argument, 0, 'c' },
- { "close-sockets-fast", no_argument, 0, 'C' },
- { "debug", no_argument, 0, 'D' },
- { "dispatch-mode", required_argument, 0, 'd' },
- { "hard-maxconn-excess", required_argument, 0, 'E' },
- { "soft-maxconn-excess", required_argument, 0, 'e' },
- { "dns-cache-timeout", required_argument, 0, 'F' },
- { "foreground", no_argument, 0, 'f' },
- { "backend-check", required_argument, 0, 'g' },
- { "help", no_argument, 0, 'h' },
- { "add-server-header", required_argument, 0, 'H' },
- { "log-traffic-dir", required_argument, 0, 'l' },
- { "max-connections", required_argument, 0, 'm' },
- { "host-match", required_argument, 0, 'M' },
- { "tryout", no_argument, 0, 'n' },
- { "prefix-timestamp", no_argument, 0, 'P' },
- { "pidfile", required_argument, 0, 'p' },
- { "soft-maxconnrate", required_argument, 0, 'r' },
- { "quit-after", required_argument, 0, 'Q' },
- { "hard-maxconnrate", required_argument, 0, 'R' },
- { "server", required_argument, 0, 's' },
- { "sticky-http", no_argument, 0, 'S' },
- { "backend-timeout", required_argument, 0, 't' },
- { "client-timeout", required_argument, 0, 'T' },
- { "time-interval", required_argument, 0, 'u' },
- { "defer-time", required_argument, 0, 'U' },
- { "verbose", no_argument, 0, 'v' },
- { "version", no_argument, 0, 'V' },
- { "wakeup-interval", required_argument, 0, 'w' },
- { "web-interface", required_argument, 0, 'W' },
- { "add-xr-version", no_argument, 0, 'X' },
- { "add-x-forwarded-for", no_argument, 0, 'x' },
- { 0, 0, 0, 0 }
+ { "allow-from", required_argument, 0, 'a' },
+ { "deny-from", required_argument, 0, 'A' },
+ { "backend", required_argument, 0, 'b' },
+ { "buffer-size", required_argument, 0, 'B' },
+ { "checkup-interval", required_argument, 0, 'c' },
+ { "close-sockets-fast", no_argument, 0, 'C' },
+ { "debug", no_argument, 0, 'D' },
+ { "dispatch-mode", required_argument, 0, 'd' },
+ { "hard-maxconn-excess", required_argument, 0, 'E' },
+ { "soft-maxconn-excess", required_argument, 0, 'e' },
+ { "dns-cache-timeout", required_argument, 0, 'F' },
+ { "foreground", no_argument, 0, 'f' },
+ { "backend-check", required_argument, 0, 'g' },
+ { "help", no_argument, 0, 'h' },
+ { "add-server-header", required_argument, 0, 'H' },
+ { "replace-host-header", no_argument, 0, 'I' },
+ { "log-traffic-dir", required_argument, 0, 'l' },
+ { "max-connections", required_argument, 0, 'm' },
+ { "host-match", required_argument, 0, 'M' },
+ { "tryout", no_argument, 0, 'n' },
+ { "prefix-timestamp", no_argument, 0, 'P' },
+ { "pidfile", required_argument, 0, 'p' },
+ { "soft-maxconnrate", required_argument, 0, 'r' },
+ { "quit-after", required_argument, 0, 'Q' },
+ { "hard-maxconnrate", required_argument, 0, 'R' },
+ { "server", required_argument, 0, 's' },
+ { "sticky-http", no_argument, 0, 'S' },
+ { "backend-timeout", required_argument, 0, 't' },
+ { "client-timeout", required_argument, 0, 'T' },
+ { "time-interval", required_argument, 0, 'u' },
+ { "defer-time", required_argument, 0, 'U' },
+ { "verbose", no_argument, 0, 'v' },
+ { "version", no_argument, 0, 'V' },
+ { "wakeup-interval", required_argument, 0, 'w' },
+ { "web-interface", required_argument, 0, 'W' },
+ { "add-xr-version", no_argument, 0, 'X' },
+ { "add-x-forwarded-for", no_argument, 0, 'x' },
+ { 0, 0, 0, 0 }
};
# endif
@@ -121,6 +121,9 @@ void Config::parsecmdline (int ac, char **av) {
case 'H':
addserverheader (optarg);
break;
+ case 'I':
+ replacehostheader(true);
+ break;
case 'l':
dumpdir (optarg);
break;
diff --git a/xr/etc/status.xslt b/xr/etc/status.xslt
@@ -128,13 +128,28 @@
<td colspan="5"><hr/></td>
</tr>
<tr>
+ <td colspan="3">Number of threads</td>
+ <td><xsl:value-of select="/status/activity/threadcount"/></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td colspan="3">Used file descriptors (approx.)</td>
+ <td><xsl:value-of select="/status/activity/openfiles"/></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td colspan="3">File descriptor limit</td>
+ <td><xsl:value-of select="/status/activity/maxopenfiles"/></td>
+ <td></td>
+ </tr>
+ <tr>
<td><b>Thread</b></td>
<td><b>Description</b></td>
<td><b>Back end</b></td>
<td><b>Duration</b></td>
<td></td>
</tr>
- <xsl:apply-templates select="/status/activity/thread">
+ <xsl:apply-templates select="/status/activity/threadlist/thread">
<xsl:sort select="duration" data-type="number"/>
</xsl:apply-templates>
</table>
@@ -143,7 +158,7 @@
</table>
</xsl:template>
-<xsl:template match="/status/activity/thread">
+<xsl:template match="/status/activity/threadlist/thread">
<tr>
<td><xsl:value-of select="id"/></td>
<td><xsl:value-of select="description"/></td>
@@ -700,6 +715,26 @@
</xsl:choose>
</td>
</tr>
+ <tr>
+ <td></td>
+ <td colspan="2">Replace Host: headers</td>
+ <td>
+ <xsl:choose>
+ <xsl:when test="replacehostheader = 0">
+ <select onchange="goto('/server/replacehostheader/on', '');">
+ <option value="yes">yes</option>
+ <option value="no" selected="1">no</option>
+ </select>
+ </xsl:when>
+ <xsl:otherwise>
+ <select onchange="goto('/server/replacehostheader/off', '');">
+ <option value="yes" selected="1">yes</option>
+ <option value="no">no</option>
+ </select>
+ </xsl:otherwise>
+ </xsl:choose>
+ </td>
+ </tr>
<xsl:apply-templates select="/status/server/http/serverheaders"/>
</xsl:template>
diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt
@@ -79,7 +79,12 @@ may not exist on your platform):
-h, -?, --help
This text.
-H HDR, --add-server-header HDR
- Inserts HDR into back end bound HTTP messages.
+ Inserts HDR into back end bound HTTP messages. The header value is
+ appended when a pre-existing header is present.
+ -I HDR, --replace-host-header
+ Inserts "Host: <backend>" into back end bound HTTP messages.
+ Pre-existing Host headers are overwritten. The value of <backend> is
+ the server name as in the setting of --backend (-b).
-l DIR, --log-traffic-dir DIR
Log passing traffic with dumps in DIR. Only for debugging, slows
down the balancer.
diff --git a/xr/httpbuffer/addheader.cc b/xr/httpbuffer/addheader.cc
@@ -1,7 +1,10 @@
#include "httpbuffer"
-void Httpbuffer::addheader (string var, string val) {
+void Httpbuffer::addheader (string const &var, string const &val) {
PROFILE("Httpbuffer::addheader(string,string)");
+
+ if (!headersreceived())
+ return;
string old = headerval(var);
if (old.size()) {
diff --git a/xr/httpbuffer/addheader1.cc b/xr/httpbuffer/addheader1.cc
@@ -1,7 +1,10 @@
#include "httpbuffer"
-void Httpbuffer::addheader (string h) {
+void Httpbuffer::addheader (string const &h) {
PROFILE("Httpbuffer::addheader(string)");
+
+ if (!headersreceived())
+ return;
unsigned i;
for (i = 0; i < h.size(); i++)
@@ -12,5 +15,6 @@ void Httpbuffer::addheader (string h) {
i++;
string val = h.substr(i);
addheader (var, val);
+ return;
}
}
diff --git a/xr/httpbuffer/headerval.cc b/xr/httpbuffer/headerval.cc
@@ -1,28 +1,28 @@
#include "httpbuffer"
-string Httpbuffer::headerval (string var) {
+string Httpbuffer::headerval (string const &var) {
PROFILE("Httpbuffer::headerval");
- string ret;
-
if (!headersreceived())
return ("");
- if (var[var.size() - 1] != ':')
- var += ":";
+ string myvar = var;
+ if (myvar[myvar.size() - 1] != ':')
+ myvar += ":";
unsigned int start;
- if ( (!(start = strfind(var.c_str()))) ||
+ if ( (!(start = strfind(myvar.c_str()))) ||
(start >= bodystart) )
return ("");
- start += var.size();
+ start += myvar.size();
for (char ch = charat(start); ch && isspace(ch); ch = charat(++start))
;
+ string ret;
for (char ch = charat(start); ch && ch != '\r' && ch != '\n';
ch = charat(++start))
ret += ch;
- debugmsg ("Header " + var + " '" + ret + "'\n");
+ debugmsg ("Header " + myvar + " '" + ret + "'\n");
return (ret);
}
diff --git a/xr/httpbuffer/httpbuffer b/xr/httpbuffer/httpbuffer
@@ -19,16 +19,19 @@ public:
bool headersreceived();
- string headerval (string var);
+ string headerval (string const &var);
string &firstline();
bool setversion(char v);
- void setheader (string var, string val);
- void setheader (string h);
+ void setheader (string const &var, string const &val);
+ void setheader (string const &h);
- void addheader (string var, string val);
- void addheader (string h);
+ void addheader (string const &var, string const &val);
+ void addheader (string const &h);
+
+ void replaceheader (string const &var, string const &val);
+ void replaceheader (string const &h);
string cookievalue (string var);
diff --git a/xr/httpbuffer/replaceheader1.cc b/xr/httpbuffer/replaceheader1.cc
@@ -0,0 +1,17 @@
+#include "httpbuffer"
+
+void Httpbuffer::replaceheader(string const &h) {
+ PROFILE("Httpbuffer::replacehader(string)");
+
+ unsigned i;
+ for (i = 0; i < h.size(); i++)
+ if (h[i] == ':') {
+ string var = h.substr(0, i);
+ i++;
+ while (isspace(h[i]))
+ i++;
+ string val = h.substr(i);
+ replaceheader(var, val);
+ return;
+ }
+}
diff --git a/xr/httpbuffer/replaceheader2.cc b/xr/httpbuffer/replaceheader2.cc
@@ -0,0 +1,16 @@
+#include "httpbuffer"
+
+void Httpbuffer::replaceheader(string const &var, string const &val) {
+ PROFILE("Httpbuffer::replacehader(string,string)");
+
+ if (!headersreceived())
+ return;
+
+ unsigned off = findheader(var);
+ if (off) {
+ unsigned nl = charfind('\n', off);
+ if (nl)
+ removeat(off, nl - off + 1);
+ }
+ setheader(var, val);
+}
diff --git a/xr/httpbuffer/setheader.cc b/xr/httpbuffer/setheader.cc
@@ -1,12 +1,15 @@
#include "httpbuffer"
-void Httpbuffer::setheader (string var, string val) {
+void Httpbuffer::setheader (string const &var, string const &val) {
PROFILE("Httpbuffer::setheader");
- if (!bodystart)
+ if (!headersreceived())
return;
- if (var[var.size() - 1] != ':')
- var += ':';
+
+ string myvar = var;
+
+ if (myvar[myvar.size() - 1] != ':')
+ myvar += ':';
// Find position beyond first \n
unsigned i;
@@ -15,6 +18,6 @@ void Httpbuffer::setheader (string var, string val) {
// Poke in the header.
string h;
- h = var + ' ' + val + "\r\n";
+ h = myvar + ' ' + val + "\r\n";
insertat(i + 1, h.c_str());
}
diff --git a/xr/httpbuffer/setheader1.cc b/xr/httpbuffer/setheader1.cc
@@ -1,6 +1,6 @@
#include "httpbuffer"
-void Httpbuffer::setheader (string h) {
+void Httpbuffer::setheader (string const &h) {
PROFILE("Httpbuffer::setheader(string)");
unsigned i;
diff --git a/xr/httpdispatcher/handle.cc b/xr/httpdispatcher/handle.cc
@@ -12,6 +12,16 @@ void HttpDispatcher::handle() {
for (unsigned n = 0; n < config.nserverheaders(); n++)
buf.setheader (config.serverheader(n));
+ // Patch up the Host: header if requested so.
+ if (config.replacehostheader())
+ buf.replaceheader("Host:",
+ balancer.backend(targetbackend()).server());
+ /*
+ * Httpbuffer::replaceheader() is built but not used,
+ * e.g.:
+ * buf.replaceheader("MyHeader:", "MyValue");
+ */
+
// Flush client info received so far to the back end.
debugmsg("Sending client request to back end\n");
buf.netwrite(backendfd(), config.backend_timeout());
@@ -75,6 +85,10 @@ void HttpDispatcher::handle() {
if (sock == clientfd()) {
othersock = backendfd();
timeout = config.backend_timeout();
+ // Re-patch Host header if requested
+ if (config.replacehostheader())
+ buf.replaceheader("Host:",
+ balancer.backend(targetbackend()).server());
} else {
othersock = clientfd();
timeout = config.client_timeout();
diff --git a/xr/netbuffer/netbuffer b/xr/netbuffer/netbuffer
@@ -55,6 +55,7 @@ private:
void check_space(unsigned extra);
string printable(char c) const;
+ string printable() const;
char *buf_data;
unsigned buf_sz;
diff --git a/xr/netbuffer/printable1.cc b/xr/netbuffer/printable1.cc
@@ -0,0 +1,9 @@
+#include "netbuffer"
+
+string Netbuffer::printable() const {
+ string ret;
+
+ for (unsigned i = 0; i < buf_sz; i++)
+ ret += printable(buf_data[i]);
+ return ret;
+}
diff --git a/xr/netbuffer/removeat.cc b/xr/netbuffer/removeat.cc
@@ -3,11 +3,11 @@
bool Netbuffer::removeat(unsigned index, unsigned len) {
if (index >= buf_sz)
return false;
-
+
if (index + len >= buf_sz)
buf_sz = index;
else {
- memmove (buf_data + index + len, buf_data + index,
+ memmove (buf_data + index, buf_data + index + len,
buf_sz - index - len);
buf_sz -= len;
}
diff --git a/xr/webinterface/answer.cc b/xr/webinterface/answer.cc
@@ -173,6 +173,14 @@ void Webinterface::answer(Httpbuffer req) {
return;
}
+ // /server/replacehostheader/BOOLEAN
+ if (parts.size() == 3 &&
+ parts[0] == "server" && parts[1] == "replacehostheader") {
+ config.replacehostheader (str2bool(parts[2], "replacehostheader"));
+ answer_status();
+ return;
+ }
+
// /server/newheader/NEWHEADER
if (parts.size() == 3 &&
parts[0] == "server" && parts[1] == "newheader") {
diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc
@@ -67,6 +67,7 @@ void Webinterface::answer_status() {
" <addxrversion>" << config.addxrversion() << "</addxrversion>\n"
" <addxforwardedfor>" << config.addxforwardedfor() << "</addxforwardedfor>\n"
" <stickyhttp>" << config.stickyhttp() << "</stickyhttp>\n"
+ " <replacehostheader>" << config.replacehostheader() << "</replacehostheader>\n"
" <serverheaders>\n"
;
for (unsigned i = 0; i < config.nserverheaders(); i++)
@@ -104,26 +105,46 @@ void Webinterface::answer_status() {
" </backend>\n"
;
- o << " <activity>\n";
+ o <<
+ " <activity>\n"
+ " <threadlist>\n";
+ unsigned nthreads = 0, max_open_files;
+ struct rlimit rl;
+ if (getrlimit(RLIMIT_NOFILE, &rl))
+ throw Error("Failed to get limit for open files");
+ max_open_files = unsigned(rl.rlim_cur);
for (Threadmap::iterator it = Threadlist::map().begin();
it != Threadlist::map().end();
it++) {
+ nthreads++;
pthread_t thread_id = (*it).first;
Threadinfo thread_info = (*it).second;
o <<
- " <thread>\n"
- " <id>" << thread_id << "</id>\n"
- " <description>" << thread_info.desc() << "</description>\n"
- " <backend>" << thread_info.backend() << "</backend>\n"
- " <address>";
+ " <thread>\n"
+ " <id>" << thread_id << "</id>\n"
+ " <description>" << thread_info.desc() << "</description>\n"
+ " <backend>" << thread_info.backend() << "</backend>\n"
+ " <address>";
if (thread_info.backend() >= 0)
o << balancer.backend(thread_info.backend()).description();
o <<
"</address>\n"
- " <duration>" << thread_info.timestamp().elapsed() << "</duration>\n"
- " </thread>\n";
+ " <duration>" << thread_info.timestamp().elapsed() << "</duration>\n"
+ " </thread>\n";
}
- o << " </activity>\n";
+ /* The estimate of the number of used fd's is:
+ * Base is 5 (stdin/stdout/stderr, listen-fd for service, listen-fd
+ * for webinterface
+ * Plus 2 x #-threads (1 to client, 1 to backend)
+ * There will be fd's in use for shared libs etc., but we don't see
+ * those..
+ */
+ o <<
+ " </threadlist>\n"
+ " <threadcount>" << nthreads << "</threadcount>\n"
+ " <openfiles>" << nthreads * 2 + 5 << "</openfiles>\n"
+ " <maxopenfiles>" << max_open_files << "</maxopenfiles>\n"
+ " </activity>\n";
o <<
"</status>\n\n";
diff --git a/xrctl/xrctl b/xrctl/xrctl
@@ -498,6 +498,9 @@ sub xr_cmdarr {
push (@cmd, '--sticky-http')
if ($sp->data('stickyhttp') and
istrue($sp->data('stickyhttp')));
+ push (@cmd, '--replace-host-header')
+ if ($sp->data('replacehostheader') and
+ istrue($sp->data('replacehostheader')));
for (my $i = 0; ; $i++) {
my $h = $sp->data('header', $i) or last;
push (@cmd, '--add-server-header', $h);