diff --git a/include/httpcgi.h b/include/httpcgi.h index e85948c..eac0689 100644 --- a/include/httpcgi.h +++ b/include/httpcgi.h @@ -126,8 +126,9 @@ struct httpc { UCHAR ssilevel; /* 51 SSI processing level */ #define SSI_LEVEL_MAX 10 /* ... max SSI processing level */ UCHAR content_length_set; /* 52 Content-Length was sent */ - UCHAR unused2; /* 53 available */ - unsigned unused3; /* 54 available */ + UCHAR keepalive; /* 53 keep-alive active */ + unsigned short request_count; /* 54 requests on this connection */ + unsigned short unused3; /* 56 available */ #define CBUFSIZE (0x1000-0x0058) /* ... 4096-88 = 4008 */ UCHAR buf[CBUFSIZE]; /* 58 data buffer */ diff --git a/include/httpd.h b/include/httpd.h index c3ca5d3..0ceee8d 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -145,7 +145,10 @@ struct httpd { UCHAR listen_queue; /* 120 listen backlog */ UCHAR unused_121[3]; /* 121 alignment padding */ char codepage[16]; /* 124 codepage name */ -}; /* 134 */ + UCHAR cfg_keepalive_timeout; /* 134 keepalive idle secs */ + UCHAR cfg_keepalive_max; /* 135 max reqs per connection */ + UCHAR unused_136[2]; /* 136 alignment padding */ +}; /* 138 */ /* HTTP variables */ struct httpv { @@ -210,8 +213,9 @@ struct httpc { UCHAR ssilevel; /* 51 SSI processing level */ #define SSI_LEVEL_MAX 10 /* ... max SSI processing levele*/ UCHAR content_length_set; /* 52 Content-Length was sent */ - UCHAR unused2; /* 53 available */ - unsigned unused3; /* 54 available */ + UCHAR keepalive; /* 53 keep-alive active */ + unsigned short request_count; /* 54 requests on this conn */ + unsigned short unused3; /* 56 available */ #define CBUFSIZE (0x1000-0x0058) /* ... 4096-88 = 4008 */ UCHAR buf[CBUFSIZE]; /* 50 data buffer */ diff --git a/samplib/httpprm0 b/samplib/httpprm0 index 96188a5..7bd3629 100644 --- a/samplib/httpprm0 +++ b/samplib/httpprm0 @@ -17,6 +17,10 @@ # MAXTASK=9 # CLIENT_TIMEOUT=10 # +# --- Keep-Alive --- +# KEEPALIVE_TIMEOUT=5 +# KEEPALIVE_MAX=100 +# # --- Security --- # LOGIN=NONE # diff --git a/src/httpgets.c b/src/httpgets.c index be2151f..8ce46f3 100644 --- a/src/httpgets.c +++ b/src/httpgets.c @@ -11,8 +11,12 @@ int http_gets(HTTPC *httpc, UCHAR *buf, unsigned max) unsigned seconds; unsigned ecb; - seconds = httpd->cfg_client_timeout; - if (seconds == 0) seconds = 10; + if (httpc->request_count > 0 && httpd->cfg_keepalive_timeout) { + seconds = httpd->cfg_keepalive_timeout; + } else { + seconds = httpd->cfg_client_timeout; + if (seconds == 0) seconds = 10; + } time64(&now); __64_add_u32(&now, seconds, &timeout); diff --git a/src/httpin.c b/src/httpin.c index d6c622c..90fdaff 100644 --- a/src/httpin.c +++ b/src/httpin.c @@ -74,6 +74,8 @@ int http_in(HTTPC *httpc) /* HTTP/1.1 requires a Host header */ { UCHAR *ver = http_get_env(httpc, "REQUEST_VERSION"); + UCHAR *conn = http_get_env(httpc, "HTTP_CONNECTION"); + if (ver && http_cmp(ver, "HTTP/1.1") == 0) { UCHAR *host = http_get_env(httpc, "HTTP_HOST"); if (!host || !host[0]) { @@ -83,7 +85,20 @@ int http_in(HTTPC *httpc) httpc->state = CSTATE_DONE; goto quit; } + /* HTTP/1.1: default keep-alive */ + httpc->keepalive = 1; + if (conn && http_cmp(conn, "close") == 0) + httpc->keepalive = 0; + } else { + /* HTTP/1.0: default close */ + httpc->keepalive = 0; + if (conn && http_cmp(conn, "keep-alive") == 0) + httpc->keepalive = 1; } + + /* enforce max requests per connection */ + if (httpc->request_count >= httpc->httpd->cfg_keepalive_max) + httpc->keepalive = 0; } /* next step will parse and do any additional processing */ @@ -92,10 +107,13 @@ int http_in(HTTPC *httpc) goto quit; failed: - // wtof("%s: failed", __func__); - - /* most likely a bad request, reset the connection */ - httpc->state = CSTATE_RESET; + if (httpc->request_count > 0) { + /* keep-alive: idle timeout or client disconnect — just close */ + httpc->state = CSTATE_CLOSE; + } else { + /* first request: bad request, reset the connection */ + httpc->state = CSTATE_RESET; + } quit: // wtof("%s: exit rc=%d", __func__, rc); diff --git a/src/httpprm.c b/src/httpprm.c index 38803a2..97e0ddf 100644 --- a/src/httpprm.c +++ b/src/httpprm.c @@ -87,6 +87,8 @@ http_config(HTTPD *httpd, const char *member) wtof("HTTPD033I MINTASK=%d MAXTASK=%d CLIENT_TIMEOUT=%d", httpd->cfg_mintask, httpd->cfg_maxtask, httpd->cfg_client_timeout); + wtof("HTTPD034I KEEPALIVE_TIMEOUT=%d KEEPALIVE_MAX=%d", + httpd->cfg_keepalive_timeout, httpd->cfg_keepalive_max); return 0; } @@ -120,6 +122,8 @@ set_defaults(HTTPD *httpd) httpd->docroot[0] = '\0'; httpd->codepage[0] = '\0'; httpd->dbg_enabled = 0; + httpd->cfg_keepalive_timeout = 5; + httpd->cfg_keepalive_max = 100; } /* ==================================================================== @@ -357,6 +361,18 @@ parse_keyvalue(HTTPD *httpd, const char *key, const char *value) else if (strcmp(key, "CLIENT_STATS_DATASET") == 0) { if (*value) httpd->st_dataset = strdup(value); } + else if (strcmp(key, "KEEPALIVE_TIMEOUT") == 0) { + i = atoi(value); + if (i < 1) i = 1; + if (i > 255) i = 255; + httpd->cfg_keepalive_timeout = (UCHAR)i; + } + else if (strcmp(key, "KEEPALIVE_MAX") == 0) { + i = atoi(value); + if (i < 1) i = 1; + if (i > 255) i = 255; + httpd->cfg_keepalive_max = (UCHAR)i; + } else if (strcmp(key, "CGI_CONTEXT_POINTERS") == 0) { i = atoi(value); if (i >= HTTPD_CGICTX_MIN) { diff --git a/src/httprese.c b/src/httprese.c index ac56bfd..c4981f2 100644 --- a/src/httprese.c +++ b/src/httprese.c @@ -33,8 +33,6 @@ httprese(HTTPC *httpc) httpc->substate = 0; httpc->chunked = 0; httpc->content_length_set = 0; - httpc->start = 0.0; - httpc->end = 0.0; memset(httpc->buf, 0, CBUFSIZE); /* clear ACEE on UFS session between requests */ @@ -42,11 +40,19 @@ httprese(HTTPC *httpc) ufs_set_acee(httpc->ufs, NULL); } - /* if this is was HTTP1.1 or higher client then we - ** *could* transition to CSTATE_IN. We'll leave that - ** for another time. - */ - httpc->state = CSTATE_CLOSE; + if (httpc->keepalive) { + /* keep connection open for next request */ + httpc->request_count++; + httpc->keepalive = 0; + httpc->start = 0.0; + httpc->end = 0.0; + httpsecs(&httpc->start); + httpc->state = CSTATE_IN; + } else { + httpc->start = 0.0; + httpc->end = 0.0; + httpc->state = CSTATE_CLOSE; + } http_exit("httprese(), rc=%d\n", rc); return rc; diff --git a/src/httpresp.c b/src/httpresp.c index a39fb9d..771f5d8 100644 --- a/src/httpresp.c +++ b/src/httpresp.c @@ -76,8 +76,11 @@ httpresp(HTTPC *httpc, int resp) if (rc) goto quit; } - /* HTTP/1.1: always close for now (keep-alive planned) */ - rc = http_printf(httpc, "Connection: close\r\n"); + if (httpc->keepalive) { + rc = http_printf(httpc, "Connection: keep-alive\r\n"); + } else { + rc = http_printf(httpc, "Connection: close\r\n"); + } if (rc) goto quit; quit: