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
}