From 5b2a45069ddcae89c762de426e50fd4438b8de9d Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 2 Sep 2014 16:20:42 +0000 Subject: FastCGI did not support persistent connections. Add initial support for persistent connections with FastCGI by implementing chunked Transfer-Encoding. This only works with HTTP/1.1. With input and help from florian@ who found some FastCGI edge cases. OK florian@ --- usr.sbin/httpd/httpd.h | 6 ++- usr.sbin/httpd/server.c | 22 +++++++++- usr.sbin/httpd/server_fcgi.c | 100 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 107 insertions(+), 21 deletions(-) (limited to 'usr.sbin') diff --git a/usr.sbin/httpd/httpd.h b/usr.sbin/httpd/httpd.h index 586ec09a192..82f90be10b9 100644 --- a/usr.sbin/httpd/httpd.h +++ b/usr.sbin/httpd/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.56 2014/09/01 09:32:43 reyk Exp $ */ +/* $OpenBSD: httpd.h,v 1.57 2014/09/02 16:20:41 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -295,6 +295,8 @@ struct client { int clt_fcgi_toread; int clt_fcgi_padding_len; int clt_fcgi_type; + int clt_fcgi_chunked; + int clt_fcgi_end; struct evbuffer *clt_srvevb; struct evbuffer *clt_log; @@ -478,6 +480,8 @@ void server_sendlog(struct server_config *, int, const char *, ...) void server_close(struct client *, const char *); void server_dump(struct client *, const void *, size_t); int server_client_cmp(struct client *, struct client *); +int server_bufferevent_printf(struct client *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); int server_bufferevent_print(struct client *, const char *); int server_bufferevent_write_buffer(struct client *, struct evbuffer *); diff --git a/usr.sbin/httpd/server.c b/usr.sbin/httpd/server.c index a7051f79a85..ed321af5752 100644 --- a/usr.sbin/httpd/server.c +++ b/usr.sbin/httpd/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.40 2014/08/27 09:51:53 reyk Exp $ */ +/* $OpenBSD: server.c,v 1.41 2014/09/02 16:20:41 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -1120,6 +1120,26 @@ server_bufferevent_add(struct event *ev, int timeout) return (event_add(ev, ptv)); } +int +server_bufferevent_printf(struct client *clt, const char *fmt, ...) +{ + int ret; + va_list ap; + char *str; + + va_start(ap, fmt); + ret = vasprintf(&str, fmt, ap); + va_end(ap); + + if (ret == -1) + return (ret); + + ret = server_bufferevent_print(clt, str); + free(str); + + return (ret); +} + int server_bufferevent_print(struct client *clt, const char *str) { diff --git a/usr.sbin/httpd/server_fcgi.c b/usr.sbin/httpd/server_fcgi.c index e4771b9af31..c51d4b5516d 100644 --- a/usr.sbin/httpd/server_fcgi.c +++ b/usr.sbin/httpd/server_fcgi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_fcgi.c,v 1.37 2014/09/01 12:28:11 reyk Exp $ */ +/* $OpenBSD: server_fcgi.c,v 1.38 2014/09/02 16:20:41 reyk Exp $ */ /* * Copyright (c) 2014 Florian Obser @@ -87,6 +87,7 @@ struct server_fcgi_param { int server_fcgi_header(struct client *, u_int); void server_fcgi_read(struct bufferevent *, void *); int server_fcgi_writeheader(struct client *, struct kv *, void *); +int server_fcgi_writechunk(struct client *); int server_fcgi_getheaders(struct client *); int fcgi_add_param(struct server_fcgi_param *, const char *, const char *, struct client *); @@ -347,11 +348,14 @@ server_fcgi(struct httpd *env, struct client *clt) fcgi_add_stdin(clt, NULL); } - /* - * persist is not supported yet because we don't get the - * Content-Length from slowcgi and don't support chunked encoding. - */ - clt->clt_persist = 0; + if (strcmp(desc->http_version, "HTTP/1.1") == 0) { + clt->clt_fcgi_chunked = 1; + } else { + /* HTTP/1.0 does not support chunked encoding */ + clt->clt_fcgi_chunked = 0; + clt->clt_persist = 0; + } + clt->clt_fcgi_end = 0; clt->clt_done = 0; free(script); @@ -458,8 +462,9 @@ server_fcgi_read(struct bufferevent *bev, void *arg) /* XXX error handling */ evbuffer_add(clt->clt_srvevb, buf, len); clt->clt_fcgi_toread -= len; - DPRINTF("%s: len: %lu toread: %d state: %d", __func__, len, - clt->clt_fcgi_toread, clt->clt_fcgi_state); + DPRINTF("%s: len: %lu toread: %d state: %d type: %d", + __func__, len, clt->clt_fcgi_toread, + clt->clt_fcgi_state, clt->clt_fcgi_type); if (clt->clt_fcgi_toread != 0) return; @@ -488,9 +493,10 @@ server_fcgi_read(struct bufferevent *bev, void *arg) /* fallthrough if content_len == 0 */ case FCGI_READ_CONTENT: - if (clt->clt_fcgi_type == FCGI_STDERR && - EVBUFFER_LENGTH(clt->clt_srvevb) > 0) { - if ((ptr = get_string( + switch (clt->clt_fcgi_type) { + case FCGI_STDERR: + if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 && + (ptr = get_string( EVBUFFER_DATA(clt->clt_srvevb), EVBUFFER_LENGTH(clt->clt_srvevb))) != NULL) { @@ -498,14 +504,27 @@ server_fcgi_read(struct bufferevent *bev, void *arg) IMSG_LOG_ERROR, "%s", ptr); free(ptr); } - } - if (clt->clt_fcgi_type == FCGI_STDOUT && - EVBUFFER_LENGTH(clt->clt_srvevb) > 0) { - if (++clt->clt_chunk == 1) - server_fcgi_header(clt, - server_fcgi_getheaders(clt)); - server_bufferevent_write_buffer(clt, - clt->clt_srvevb); + break; + case FCGI_STDOUT: + if (++clt->clt_chunk == 1) { + if (server_fcgi_header(clt, + server_fcgi_getheaders(clt)) + == -1) { + server_abort_http(clt, 500, + "malformed fcgi headers"); + return; + } + if (!EVBUFFER_LENGTH(clt->clt_srvevb)) + break; + } + /* FALLTHROUGH */ + case FCGI_END_REQUEST: + if (server_fcgi_writechunk(clt) == -1) { + server_abort_http(clt, 500, + "encoding error"); + return; + } + break; } evbuffer_drain(clt->clt_srvevb, EVBUFFER_LENGTH(clt->clt_srvevb)); @@ -537,6 +556,7 @@ server_fcgi_header(struct client *clt, u_int code) struct http_descriptor *resp = clt->clt_descresp; const char *error; char tmbuf[32]; + struct kv *kv, key; if (desc == NULL || (error = server_httperror_byid(code)) == NULL) return (-1); @@ -553,6 +573,22 @@ server_fcgi_header(struct client *clt, u_int code) if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL) return (-1); + /* Set chunked encoding */ + if (clt->clt_fcgi_chunked) { + /* XXX Should we keep and handle Content-Length instead? */ + key.kv_key = "Content-Length"; + if ((kv = kv_find(&resp->http_headers, &key)) != NULL) + kv_delete(&resp->http_headers, kv); + + /* + * XXX What if the FastCGI added some kind of Transfer-Encoding? + * XXX like gzip, deflate or even "chunked"? + */ + if (kv_add(&resp->http_headers, + "Transfer-Encoding", "chunked") == NULL) + return (-1); + } + /* Is it a persistent connection? */ if (clt->clt_persist) { if (kv_add(&resp->http_headers, @@ -617,6 +653,32 @@ server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg) return (ret); } +int +server_fcgi_writechunk(struct client *clt) +{ + struct evbuffer *evb = clt->clt_srvevb; + size_t len; + + if (clt->clt_fcgi_type == FCGI_END_REQUEST) { + len = 0; + } else + len = EVBUFFER_LENGTH(evb); + + /* If len is 0, make sure to write the end marker only once */ + if (len == 0 && clt->clt_fcgi_end++) + return (0); + + if (clt->clt_fcgi_chunked) { + if (server_bufferevent_printf(clt, "%zx\r\n", len) == -1 || + server_bufferevent_write_chunk(clt, evb, len) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) + return (-1); + } else + return (server_bufferevent_write_buffer(clt, evb)); + + return (0); +} + int server_fcgi_getheaders(struct client *clt) { -- cgit v1.2.3