http-backend: buffer headers before sending

Avoid waking up the readers for unnecessary context switches for each line of header data being written, as all the headers are written in short succession. It is unlikely any HTTP/1.x server would want to read a CGI response one-line-at-a-time and trickle each to the client. Instead, I'd expect HTTP servers want to minimize syscall and TCP/IP framing overhead by trying to send all of its response headers in a single syscall or even combining the headers and first chunk of the body with MSG_MORE or writev. Verified by strace-ing response parsing on the CGI side. Signed-off-by: Eric Wong <e@80x24.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Wong committed Aug 9, 2016 at 23:47 UTC b36045c1dc43e36f6fb84b041b59317b7f02b58c
1 file changed +116 -104
http-backend.c
+116 -104
@@ -75,55 +75,57 @@ static void format_write(int fd, const char *fmt, ...)
75 write_or_die(fd, buffer, n);
76 }
77
78 -static void http_status(unsigned code, const char *msg)
78 +static void http_status(struct strbuf *hdr, unsigned code, const char *msg)
79 {
80 - format_write(1, "Status: %u %s\r\n", code, msg);
80 + strbuf_addf(hdr, "Status: %u %s\r\n", code, msg);
81 }
82
83 -static void hdr_str(const char *name, const char *value)
83 +static void hdr_str(struct strbuf *hdr, const char *name, const char *value)
84 {
85 - format_write(1, "%s: %s\r\n", name, value);
85 + strbuf_addf(hdr, "%s: %s\r\n", name, value);
86 }
87
88 -static void hdr_int(const char *name, uintmax_t value)
88 +static void hdr_int(struct strbuf *hdr, const char *name, uintmax_t value)
89 {
90 - format_write(1, "%s: %" PRIuMAX "\r\n", name, value);
90 + strbuf_addf(hdr, "%s: %" PRIuMAX "\r\n", name, value);
91 }
92
93 -static void hdr_date(const char *name, unsigned long when)
93 +static void hdr_date(struct strbuf *hdr, const char *name, unsigned long when)
94 {
95 const char *value = show_date(when, 0, DATE_MODE(RFC2822));
96 - hdr_str(name, value);
96 + hdr_str(hdr, name, value);
97 }
98
99 -static void hdr_nocache(void)
99 +static void hdr_nocache(struct strbuf *hdr)
100 {
101 - hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
102 - hdr_str("Pragma", "no-cache");
103 - hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate");
101 + hdr_str(hdr, "Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
102 + hdr_str(hdr, "Pragma", "no-cache");
103 + hdr_str(hdr, "Cache-Control", "no-cache, max-age=0, must-revalidate");
104 }
105
106 -static void hdr_cache_forever(void)
106 +static void hdr_cache_forever(struct strbuf *hdr)
107 {
108 unsigned long now = time(NULL);
109 - hdr_date("Date", now);
110 - hdr_date("Expires", now + 31536000);
111 - hdr_str("Cache-Control", "public, max-age=31536000");
109 + hdr_date(hdr, "Date", now);
110 + hdr_date(hdr, "Expires", now + 31536000);
111 + hdr_str(hdr, "Cache-Control", "public, max-age=31536000");
112 }
113
114 -static void end_headers(void)
114 +static void end_headers(struct strbuf *hdr)
115 {
116 - write_or_die(1, "\r\n", 2);
116 + strbuf_add(hdr, "\r\n", 2);
117 + write_or_die(1, hdr->buf, hdr->len);
118 + strbuf_release(hdr);
119 }
120
119 -__attribute__((format (printf, 1, 2)))
120 -static NORETURN void not_found(const char *err, ...)
121 +__attribute__((format (printf, 2, 3)))
122 +static NORETURN void not_found(struct strbuf *hdr, const char *err, ...)
123 {
124 va_list params;
125
124 - http_status(404, "Not Found");
125 - hdr_nocache();
126 - end_headers();
126 + http_status(hdr, 404, "Not Found");
127 + hdr_nocache(hdr);
128 + end_headers(hdr);
129
130 va_start(params, err);
131 if (err && *err)
@@ -132,14 +134,14 @@ static NORETURN void not_found(const char *err, ...)
134 exit(0);
135 }
136
135 -__attribute__((format (printf, 1, 2)))
136 -static NORETURN void forbidden(const char *err, ...)
137 +__attribute__((format (printf, 2, 3)))
138 +static NORETURN void forbidden(struct strbuf *hdr, const char *err, ...)
139 {
140 va_list params;
141
140 - http_status(403, "Forbidden");
141 - hdr_nocache();
142 - end_headers();
142 + http_status(hdr, 403, "Forbidden");
143 + hdr_nocache(hdr);
144 + end_headers(hdr);
145
146 va_start(params, err);
147 if (err && *err)
@@ -148,21 +150,23 @@ static NORETURN void forbidden(const char *err, ...)
150 exit(0);
151 }
152
151 -static void select_getanyfile(void)
153 +static void select_getanyfile(struct strbuf *hdr)
154 {
155 if (!getanyfile)
154 - forbidden("Unsupported service: getanyfile");
156 + forbidden(hdr, "Unsupported service: getanyfile");
157 }
158
157 -static void send_strbuf(const char *type, struct strbuf *buf)
159 +static void send_strbuf(struct strbuf *hdr,
160 + const char *type, struct strbuf *buf)
161 {
159 - hdr_int(content_length, buf->len);
160 - hdr_str(content_type, type);
161 - end_headers();
162 + hdr_int(hdr, content_length, buf->len);
163 + hdr_str(hdr, content_type, type);
164 + end_headers(hdr);
165 write_or_die(1, buf->buf, buf->len);
166 }
167
165 -static void send_local_file(const char *the_type, const char *name)
168 +static void send_local_file(struct strbuf *hdr, const char *the_type,
169 + const char *name)
170 {
171 char *p = git_pathdup("%s", name);
172 size_t buf_alloc = 8192;
@@ -172,14 +176,14 @@ static void send_local_file(const char *the_type, const char *name)
176
177 fd = open(p, O_RDONLY);
178 if (fd < 0)
175 - not_found("Cannot open '%s': %s", p, strerror(errno));
179 + not_found(hdr, "Cannot open '%s': %s", p, strerror(errno));
180 if (fstat(fd, &sb) < 0)
181 die_errno("Cannot stat '%s'", p);
182
179 - hdr_int(content_length, sb.st_size);
180 - hdr_str(content_type, the_type);
181 - hdr_date(last_modified, sb.st_mtime);
182 - end_headers();
183 + hdr_int(hdr, content_length, sb.st_size);
184 + hdr_str(hdr, content_type, the_type);
185 + hdr_date(hdr, last_modified, sb.st_mtime);
186 + end_headers(hdr);
187
188 for (;;) {
189 ssize_t n = xread(fd, buf, buf_alloc);
@@ -194,32 +198,32 @@ static void send_local_file(const char *the_type, const char *name)
198 free(p);
199 }
200
197 -static void get_text_file(char *name)
201 +static void get_text_file(struct strbuf *hdr, char *name)
202 {
199 - select_getanyfile();
200 - hdr_nocache();
201 - send_local_file("text/plain", name);
203 + select_getanyfile(hdr);
204 + hdr_nocache(hdr);
205 + send_local_file(hdr, "text/plain", name);
206 }
207
204 -static void get_loose_object(char *name)
208 +static void get_loose_object(struct strbuf *hdr, char *name)
209 {
206 - select_getanyfile();
207 - hdr_cache_forever();
208 - send_local_file("application/x-git-loose-object", name);
210 + select_getanyfile(hdr);
211 + hdr_cache_forever(hdr);
212 + send_local_file(hdr, "application/x-git-loose-object", name);
213 }
214
211 -static void get_pack_file(char *name)
215 +static void get_pack_file(struct strbuf *hdr, char *name)
216 {
213 - select_getanyfile();
214 - hdr_cache_forever();
215 - send_local_file("application/x-git-packed-objects", name);
217 + select_getanyfile(hdr);
218 + hdr_cache_forever(hdr);
219 + send_local_file(hdr, "application/x-git-packed-objects", name);
220 }
221
218 -static void get_idx_file(char *name)
222 +static void get_idx_file(struct strbuf *hdr, char *name)
223 {
220 - select_getanyfile();
221 - hdr_cache_forever();
222 - send_local_file("application/x-git-packed-objects-toc", name);
224 + select_getanyfile(hdr);
225 + hdr_cache_forever(hdr);
226 + send_local_file(hdr, "application/x-git-packed-objects-toc", name);
227 }
228
229 static void http_config(void)
@@ -241,14 +245,14 @@ static void http_config(void)
245 strbuf_release(&var);
246 }
247
244 -static struct rpc_service *select_service(const char *name)
248 +static struct rpc_service *select_service(struct strbuf *hdr, const char *name)
249 {
250 const char *svc_name;
251 struct rpc_service *svc = NULL;
252 int i;
253
254 if (!skip_prefix(name, "git-", &svc_name))
251 - forbidden("Unsupported service: '%s'", name);
255 + forbidden(hdr, "Unsupported service: '%s'", name);
256
257 for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
258 struct rpc_service *s = &rpc_service[i];
@@ -259,14 +263,14 @@ static struct rpc_service *select_service(const char *name)
263 }
264
265 if (!svc)
262 - forbidden("Unsupported service: '%s'", name);
266 + forbidden(hdr, "Unsupported service: '%s'", name);
267
268 if (svc->enabled < 0) {
269 const char *user = getenv("REMOTE_USER");
270 svc->enabled = (user && *user) ? 1 : 0;
271 }
272 if (!svc->enabled)
269 - forbidden("Service not enabled: '%s'", svc->name);
273 + forbidden(hdr, "Service not enabled: '%s'", svc->name);
274 return svc;
275 }
276
@@ -442,23 +446,23 @@ static int show_text_ref(const char *name, const struct object_id *oid,
446 return 0;
447 }
448
445 -static void get_info_refs(char *arg)
449 +static void get_info_refs(struct strbuf *hdr, char *arg)
450 {
451 const char *service_name = get_parameter("service");
452 struct strbuf buf = STRBUF_INIT;
453
450 - hdr_nocache();
454 + hdr_nocache(hdr);
455
456 if (service_name) {
457 const char *argv[] = {NULL /* service name */,
458 "--stateless-rpc", "--advertise-refs",
459 ".", NULL};
456 - struct rpc_service *svc = select_service(service_name);
460 + struct rpc_service *svc = select_service(hdr, service_name);
461
462 strbuf_addf(&buf, "application/x-git-%s-advertisement",
463 svc->name);
460 - hdr_str(content_type, buf.buf);
461 - end_headers();
464 + hdr_str(hdr, content_type, buf.buf);
465 + end_headers(hdr);
466
467 packet_write(1, "# service=git-%s\n", svc->name);
468 packet_flush(1);
@@ -467,9 +471,9 @@ static void get_info_refs(char *arg)
471 run_service(argv, 0);
472
473 } else {
470 - select_getanyfile();
474 + select_getanyfile(hdr);
475 for_each_namespaced_ref(show_text_ref, &buf);
472 - send_strbuf("text/plain", &buf);
476 + send_strbuf(hdr, "text/plain", &buf);
477 }
478 strbuf_release(&buf);
479 }
@@ -494,24 +498,24 @@ static int show_head_ref(const char *refname, const struct object_id *oid,
498 return 0;
499 }
500
497 -static void get_head(char *arg)
501 +static void get_head(struct strbuf *hdr, char *arg)
502 {
503 struct strbuf buf = STRBUF_INIT;
504
501 - select_getanyfile();
505 + select_getanyfile(hdr);
506 head_ref_namespaced(show_head_ref, &buf);
503 - send_strbuf("text/plain", &buf);
507 + send_strbuf(hdr, "text/plain", &buf);
508 strbuf_release(&buf);
509 }
510
507 -static void get_info_packs(char *arg)
511 +static void get_info_packs(struct strbuf *hdr, char *arg)
512 {
513 size_t objdirlen = strlen(get_object_directory());
514 struct strbuf buf = STRBUF_INIT;
515 struct packed_git *p;
516 size_t cnt = 0;
517
514 - select_getanyfile();
518 + select_getanyfile(hdr);
519 prepare_packed_git();
520 for (p = packed_git; p; p = p->next) {
521 if (p->pack_local)
@@ -525,12 +529,12 @@ static void get_info_packs(char *arg)
529 }
530 strbuf_addch(&buf, '\n');
531
528 - hdr_nocache();
529 - send_strbuf("text/plain; charset=utf-8", &buf);
532 + hdr_nocache(hdr);
533 + send_strbuf(hdr, "text/plain; charset=utf-8", &buf);
534 strbuf_release(&buf);
535 }
536
533 -static void check_content_type(const char *accepted_type)
537 +static void check_content_type(struct strbuf *hdr, const char *accepted_type)
538 {
539 const char *actual_type = getenv("CONTENT_TYPE");
540
@@ -538,9 +542,9 @@ static void check_content_type(const char *accepted_type)
542 actual_type = "";
543
544 if (strcmp(actual_type, accepted_type)) {
541 - http_status(415, "Unsupported Media Type");
542 - hdr_nocache();
543 - end_headers();
545 + http_status(hdr, 415, "Unsupported Media Type");
546 + hdr_nocache(hdr);
547 + end_headers(hdr);
548 format_write(1,
549 "Expected POST with Content-Type '%s',"
550 " but received '%s' instead.\n",
@@ -549,23 +553,23 @@ static void check_content_type(const char *accepted_type)
553 }
554 }
555
552 -static void service_rpc(char *service_name)
556 +static void service_rpc(struct strbuf *hdr, char *service_name)
557 {
558 const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
555 - struct rpc_service *svc = select_service(service_name);
559 + struct rpc_service *svc = select_service(hdr, service_name);
560 struct strbuf buf = STRBUF_INIT;
561
562 strbuf_reset(&buf);
563 strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
560 - check_content_type(buf.buf);
564 + check_content_type(hdr, buf.buf);
565
562 - hdr_nocache();
566 + hdr_nocache(hdr);
567
568 strbuf_reset(&buf);
569 strbuf_addf(&buf, "application/x-git-%s-result", svc->name);
566 - hdr_str(content_type, buf.buf);
570 + hdr_str(hdr, content_type, buf.buf);
571
568 - end_headers();
572 + end_headers(hdr);
573
574 argv[0] = svc->name;
575 run_service(argv, svc->buffer_input);
@@ -576,11 +580,13 @@ static int dead;
580 static NORETURN void die_webcgi(const char *err, va_list params)
581 {
582 if (dead <= 1) {
583 + struct strbuf hdr = STRBUF_INIT;
584 +
585 vreportf("fatal: ", err, params);
586
581 - http_status(500, "Internal Server Error");
582 - hdr_nocache();
583 - end_headers();
587 + http_status(&hdr, 500, "Internal Server Error");
588 + hdr_nocache(&hdr);
589 + end_headers(&hdr);
590 }
591 exit(0); /* we successfully reported a failure ;-) */
592 }
@@ -617,7 +623,7 @@ static char* getdir(void)
623 static struct service_cmd {
624 const char *method;
625 const char *pattern;
620 - void (*imp)(char *);
626 + void (*imp)(struct strbuf *, char *);
627 } services[] = {
628 {"GET", "/HEAD$", get_head},
629 {"GET", "/info/refs$", get_info_refs},
@@ -632,6 +638,21 @@ static struct service_cmd {
638 {"POST", "/git-receive-pack$", service_rpc}
639 };
640
641 +static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
642 +{
643 + const char *proto = getenv("SERVER_PROTOCOL");
644 +
645 + if (proto && !strcmp(proto, "HTTP/1.1")) {
646 + http_status(hdr, 405, "Method Not Allowed");
647 + hdr_str(hdr, "Allow",
648 + !strcmp(c->method, "GET") ? "GET, HEAD" : c->method);
649 + } else
650 + http_status(hdr, 400, "Bad Request");
651 + hdr_nocache(hdr);
652 + end_headers(hdr);
653 + return 0;
654 +}
655 +
656 int main(int argc, char **argv)
657 {
658 char *method = getenv("REQUEST_METHOD");
@@ -639,6 +660,7 @@ int main(int argc, char **argv)
660 struct service_cmd *cmd = NULL;
661 char *cmd_arg = NULL;
662 int i;
663 + struct strbuf hdr = STRBUF_INIT;
664
665 git_setup_gettext();
666
@@ -662,18 +684,8 @@ int main(int argc, char **argv)
684 if (!regexec(&re, dir, 1, out, 0)) {
685 size_t n;
686
665 - if (strcmp(method, c->method)) {
666 - const char *proto = getenv("SERVER_PROTOCOL");
667 - if (proto && !strcmp(proto, "HTTP/1.1")) {
668 - http_status(405, "Method Not Allowed");
669 - hdr_str("Allow", !strcmp(c->method, "GET") ?
670 - "GET, HEAD" : c->method);
671 - } else
672 - http_status(400, "Bad Request");
673 - hdr_nocache();
674 - end_headers();
675 - return 0;
676 - }
687 + if (strcmp(method, c->method))
688 + return bad_request(&hdr, c);
689
690 cmd = c;
691 n = out[0].rm_eo - out[0].rm_so;
@@ -685,19 +697,19 @@ int main(int argc, char **argv)
697 }
698
699 if (!cmd)
688 - not_found("Request not supported: '%s'", dir);
700 + not_found(&hdr, "Request not supported: '%s'", dir);
701
702 setup_path();
703 if (!enter_repo(dir, 0))
692 - not_found("Not a git repository: '%s'", dir);
704 + not_found(&hdr, "Not a git repository: '%s'", dir);
705 if (!getenv("GIT_HTTP_EXPORT_ALL") &&
706 access("git-daemon-export-ok", F_OK) )
695 - not_found("Repository not exported: '%s'", dir);
707 + not_found(&hdr, "Repository not exported: '%s'", dir);
708
709 http_config();
710 max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER",
711 max_request_buffer);
712
701 - cmd->imp(cmd_arg);
713 + cmd->imp(&hdr, cmd_arg);
714 return 0;
715 }