Changes with Apache 1.3.24
+ *) When proxy enabled a slow frontend client to read from an
+ expensive backend server, it would wait until it had delivered
+ the response to the slow frontend client completely before
+ closing the backend connection. The backend connection is now
+ closed as soon as the last byte is read from it, freeing up
+ resources that would have been tied up unnecessarily.
+ [Graham Leggett, Igor Sysoev <is@rambler-co.ru>]
+
+ *) The proxy code read chunks from the backend server in a
+ hardcoded amount of 8k. The existing ProxyReceiveBufferSize
+ parameter has been overloaded to specify the size of this buffer.
+ [Graham Leggett, Igor Sysoev <is@rambler-co.ru>]
+
*) [Security] Prevent invalid client hostnames from appearing in
the log file. If a double-reverse lookup was performed (e.g.,
for an "Allow from .my.domain" directive) but failed, then
char **passwordp, char **hostp, int *port);
const char *ap_proxy_date_canon(pool *p, const char *x);
table *ap_proxy_read_headers(request_rec *r, char *buffer, int size, BUFF *f);
-long int ap_proxy_send_fb(BUFF *f, request_rec *r, cache_req *c, off_t len, int nowrite);
+long int ap_proxy_send_fb(BUFF *f, request_rec *r, cache_req *c, off_t len, int nowrite, size_t recv_buffer_size);
void ap_proxy_write_headers(cache_req *c, const char *respline, table *t);
int ap_proxy_liststr(const char *list, const char *key, char **val);
void ap_proxy_hash(const char *it, char *val, int ndepth, int nlength);
/* if cache file is being updated */
if (c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
- ap_proxy_send_fb(c->origfp, r, c, c->len, 1);
- ap_pclosef(r->pool, ap_bfileno(c->origfp, B_WR));
+ ap_proxy_send_fb(c->origfp, r, c, c->len, 1, IOBUFSIZE);
ap_proxy_cache_tidy(c);
}
else
/* are we updating the cache file? */
if (c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
- ap_proxy_send_fb(c->origfp, r, c, c->len, 1);
- ap_pclosef(r->pool, ap_bfileno(c->origfp, B_WR));
+ ap_proxy_send_fb(c->origfp, r, c, c->len, 1, IOBUFSIZE);
ap_proxy_cache_tidy(c);
}
else
/* are we rewriting the cache file? */
if (c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
- ap_proxy_send_fb(c->origfp, r, c, c->len, r->header_only);
- ap_pclosef(r->pool, ap_bfileno(c->origfp, B_WR));
+ ap_proxy_send_fb(c->origfp, r, c, c->len, r->header_only, IOBUFSIZE);
ap_proxy_cache_tidy(c);
return OK;
}
/* no, we not */
- if (!r->header_only)
- ap_proxy_send_fb(cachefp, r, NULL, c->len, 0);
+ if (!r->header_only) {
+ ap_proxy_send_fb(cachefp, r, NULL, c->len, 0, IOBUFSIZE);
+ }
+ else {
+ ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
+ }
- ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
return OK;
}
static long int send_dir(BUFF *data, request_rec *r, cache_req *c, char *cwd)
{
- char buf[IOBUFSIZE];
- char buf2[IOBUFSIZE];
+ char *buf, *buf2;
+ size_t buf_size;
char *filename;
int searchidx = 0;
char *searchptr = NULL;
char *dir, *path, *reldir, *site, *type = NULL;
char *basedir = ""; /* By default, path is relative to the $HOME dir */
+ /* create default sized buffers for the stuff below */
+ buf_size = IOBUFSIZE;
+ buf = ap_palloc(r->pool, buf_size);
+ buf2 = ap_palloc(r->pool, buf_size);
+
/* Save "scheme://site" prefix without password */
site = ap_unparse_uri_components(p, &r->parsed_uri, UNP_OMITPASSWORD|UNP_OMITPATHINFO);
/* ... and path without query args */
path[n-1] = '\0';
/* print "ftp://host/" */
- n = ap_snprintf(buf, sizeof(buf), DOCTYPE_HTML_3_2
+ n = ap_snprintf(buf, buf_size, DOCTYPE_HTML_3_2
"<html><head><title>%s%s%s</title>\n"
"<base href=\"%s%s%s\"></head>\n"
"<body><h2>Directory of "
else
++reldir;
/* print "path/" component */
- ap_snprintf(buf, sizeof(buf), "<a href=\"%s%s/\">%s</a>/",
+ ap_snprintf(buf, buf_size, "<a href=\"%s%s/\">%s</a>/",
basedir,
ap_escape_uri(p, path),
ap_escape_html(p, reldir));
/* If the caller has determined the current directory, and it differs */
/* from what the client requested, then show the real name */
if (cwd == NULL || strncmp (cwd, path, strlen(cwd)) == 0) {
- ap_snprintf(buf, sizeof(buf), "</h2>\n<hr /><pre>");
+ ap_snprintf(buf, buf_size, "</h2>\n<hr /><pre>");
} else {
- ap_snprintf(buf, sizeof(buf), "</h2>\n(%s)\n<hr /><pre>",
+ ap_snprintf(buf, buf_size, "</h2>\n(%s)\n<hr /><pre>",
ap_escape_html(p, cwd));
}
total_bytes_sent += ap_proxy_bputs2(buf, con->client, c);
while (!con->aborted) {
- n = ap_bgets(buf, sizeof buf, data);
+ n = ap_bgets(buf, buf_size, data);
if (n == -1) { /* input error */
if (c != NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req,
if (filename != buf)
*(filename++) = '\0';
*(link_ptr++) = '\0';
- ap_snprintf(buf2, sizeof(buf2), "%s <a href=\"%s\">%s %s</a>\n",
+ ap_snprintf(buf2, buf_size, "%s <a href=\"%s\">%s %s</a>\n",
ap_escape_html(p, buf),
ap_escape_uri(p,filename),
ap_escape_html(p, filename),
ap_escape_html(p, link_ptr));
- ap_cpystrn(buf, buf2, sizeof(buf));
+ ap_cpystrn(buf, buf2, buf_size);
n = strlen(buf);
}
/* Handle unix style or DOS style directory */
/* Special handling for '.' and '..': append slash to link */
if (!strcmp(filename, ".") || !strcmp(filename, "..") || buf[0] == 'd') {
- ap_snprintf(buf2, sizeof(buf2), "%s <a href=\"%s/\">%s</a>\n",
+ ap_snprintf(buf2, buf_size, "%s <a href=\"%s/\">%s</a>\n",
ap_escape_html(p, buf), ap_escape_uri(p,filename),
ap_escape_html(p, filename));
}
else {
- ap_snprintf(buf2, sizeof(buf2), "%s <a href=\"%s\">%s</a>\n",
+ ap_snprintf(buf2, buf_size, "%s <a href=\"%s\">%s</a>\n",
ap_escape_html(p, buf),
ap_escape_uri(p,filename),
ap_escape_html(p, filename));
}
- ap_cpystrn(buf, buf2, sizeof(buf));
+ ap_cpystrn(buf, buf2, buf_size);
n = strlen(buf);
}
/* else??? What about other OS's output formats? */
else {
strcat(buf, "\n"); /* re-append the newline char */
- ap_cpystrn(buf, ap_escape_html(p, buf), sizeof(buf));
+ ap_cpystrn(buf, ap_escape_html(p, buf), buf_size);
}
total_bytes_sent += ap_proxy_bputs2(buf, con->client, c);
total_bytes_sent += ap_proxy_bputs2(ap_psignature("", r), con->client, c);
total_bytes_sent += ap_proxy_bputs2("</body></html>\n", con->client, c);
+ ap_bclose(data);
+
ap_bflush(con->client);
return total_bytes_sent;
/* we need to set this for ap_proxy_send_fb()... */
if (c != NULL)
c->cache_completion = 0;
- ap_proxy_send_fb(data, r, c, -1, 0);
- } else
+ ap_proxy_send_fb(data, r, c, -1, 0, conf->recv_buffer_size);
+ }
+ else {
send_dir(data, r, c, cwd);
- ap_bclose(data);
+ }
+ /* ap_proxy_send_fb() closes the socket */
data = NULL;
dsock = -1;
* content length is not known. We need to make 100% sure c->len is always
* set correctly before we get here to correctly do keepalive.
*/
- ap_proxy_send_fb(f, r, c, c->len, 0);
+ ap_proxy_send_fb(f, r, c, c->len, 0, conf->recv_buffer_size);
}
- ap_proxy_cache_tidy(c);
+ /* ap_proxy_send_fb() closes the socket f for us */
- ap_bclose(f);
+ ap_proxy_cache_tidy(c);
ap_proxy_garbage_coll(r);
return OK;
* - r->connection->client, if nowrite == 0
*/
-long int ap_proxy_send_fb(BUFF *f, request_rec *r, cache_req *c, off_t len, int nowrite)
+long int ap_proxy_send_fb(BUFF *f, request_rec *r, cache_req *c, off_t len, int nowrite, size_t recv_buffer_size)
{
int ok;
- char buf[IOBUFSIZE];
+ char *buf;
+ size_t buf_size;
long total_bytes_rcvd;
register int n, o, w;
conn_rec *con = r->connection;
int alternate_timeouts = 1; /* 1 if we alternate between soft & hard timeouts */
+ /* allocate a buffer to store the bytes in */
+ /* make sure it is at least IOBUFSIZE, as recv_buffer_size may be zero for system default */
+ buf_size = MAX(recv_buffer_size, IOBUFSIZE);
+ buf = ap_palloc(r->pool, buf_size);
+
total_bytes_rcvd = 0;
if (c != NULL)
c->written = 0;
/* Read block from server */
if (-1 == len) {
- n = ap_bread(f, buf, IOBUFSIZE);
+ n = ap_bread(f, buf, buf_size);
}
else {
- n = ap_bread(f, buf, MIN(IOBUFSIZE, len - total_bytes_rcvd));
+ n = ap_bread(f, buf, MIN(buf_size, len - total_bytes_rcvd));
}
if (alternate_timeouts)
o = 0;
total_bytes_rcvd += n;
+ /* if we've received everything... */
+ /* in the case of slow frontends and expensive backends,
+ * we want to avoid leaving a backend connection hanging
+ * while the frontend takes it's time to absorb the bytes.
+ * so: if we just read the last block, we close the backend
+ * connection now instead of later - it's no longer needed.
+ */
+ if (total_bytes_rcvd == len) {
+ ap_bclose(f);
+ f = NULL;
+ }
+
/* Write to cache first. */
/*@@@ XXX FIXME: Assuming that writing the cache file won't time out?!!? */
if (c != NULL && c->fp != NULL) {
} /* loop and ap_bread while "ok" */
- if (!con->aborted)
+ /* if the backend connection is still open, close it */
+ if (f) {
+ ap_bclose(f);
+ }
+
+ if (!con->aborted) {
ap_bflush(con->client);
+ }
ap_kill_timeout(r);
return total_bytes_rcvd;