1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2012 Tatsuhiro Tsujikawa
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25 #include "nghttp2_config.h"
26
27 #ifdef __sgi
28 # define daemon _daemonize
29 #endif
30
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif // HAVE_UNISTD_H
34 #include <signal.h>
35 #include <getopt.h>
36
37 #include <cstdlib>
38 #include <cstring>
39 #include <cassert>
40 #include <string>
41 #include <iostream>
42 #include <string>
43
44 #include <nghttp2/nghttp2.h>
45
46 #include "app_helper.h"
47 #include "HttpServer.h"
48 #include "util.h"
49 #include "tls.h"
50
51 namespace nghttp2 {
52
53 namespace {
parse_push_config(Config & config,const char * optarg)54 int parse_push_config(Config &config, const char *optarg) {
55 const char *eq = strchr(optarg, '=');
56 if (eq == nullptr) {
57 return -1;
58 }
59 auto &paths = config.push[std::string(optarg, eq)];
60 auto optarg_end = optarg + strlen(optarg);
61 auto i = eq + 1;
62 for (;;) {
63 const char *j = strchr(i, ',');
64 if (j == nullptr) {
65 j = optarg_end;
66 }
67 paths.emplace_back(i, j);
68 if (j == optarg_end) {
69 break;
70 }
71 i = j;
72 ++i;
73 }
74
75 return 0;
76 }
77 } // namespace
78
79 namespace {
print_version(std::ostream & out)80 void print_version(std::ostream &out) {
81 out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl;
82 }
83 } // namespace
84
85 namespace {
print_usage(std::ostream & out)86 void print_usage(std::ostream &out) {
87 out << "Usage: nghttpd [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]\n"
88 << "HTTP/2 server" << std::endl;
89 }
90 } // namespace
91
92 namespace {
print_help(std::ostream & out)93 void print_help(std::ostream &out) {
94 Config config;
95 print_usage(out);
96 out << R"(
97 <PORT> Specify listening port number.
98 <PRIVATE_KEY>
99 Set path to server's private key. Required unless
100 --no-tls is specified.
101 <CERT> Set path to server's certificate. Required unless
102 --no-tls is specified.
103 Options:
104 -a, --address=<ADDR>
105 The address to bind to. If not specified the default IP
106 address determined by getaddrinfo is used.
107 -D, --daemon
108 Run in a background. If -D is used, the current working
109 directory is changed to '/'. Therefore if this option
110 is used, -d option must be specified.
111 -V, --verify-client
112 The server sends a client certificate request. If the
113 client did not return a certificate, the handshake is
114 terminated. Currently, this option just requests a
115 client certificate and does not verify it.
116 -d, --htdocs=<PATH>
117 Specify document root. If this option is not specified,
118 the document root is the current working directory.
119 -v, --verbose
120 Print debug information such as reception/ transmission
121 of frames and name/value pairs.
122 --no-tls Disable SSL/TLS.
123 -c, --header-table-size=<SIZE>
124 Specify decoder header table size.
125 --encoder-header-table-size=<SIZE>
126 Specify encoder header table size. The decoder (client)
127 specifies the maximum dynamic table size it accepts.
128 Then the negotiated dynamic table size is the minimum of
129 this option value and the value which client specified.
130 --color Force colored log output.
131 -p, --push=<PATH>=<PUSH_PATH,...>
132 Push resources <PUSH_PATH>s when <PATH> is requested.
133 This option can be used repeatedly to specify multiple
134 push configurations. <PATH> and <PUSH_PATH>s are
135 relative to document root. See --htdocs option.
136 Example: -p/=/foo.png -p/doc=/bar.css
137 -b, --padding=<N>
138 Add at most <N> bytes to a frame payload as padding.
139 Specify 0 to disable padding.
140 -m, --max-concurrent-streams=<N>
141 Set the maximum number of the concurrent streams in one
142 HTTP/2 session.
143 Default: )"
144 << config.max_concurrent_streams << R"(
145 -n, --workers=<N>
146 Set the number of worker threads.
147 Default: 1
148 -e, --error-gzip
149 Make error response gzipped.
150 -w, --window-bits=<N>
151 Sets the stream level initial window size to 2**<N>-1.
152 -W, --connection-window-bits=<N>
153 Sets the connection level initial window size to
154 2**<N>-1.
155 --dh-param-file=<PATH>
156 Path to file that contains DH parameters in PEM format.
157 Without this option, DHE cipher suites are not
158 available.
159 --early-response
160 Start sending response when request HEADERS is received,
161 rather than complete request is received.
162 --trailer=<HEADER>
163 Add a trailer header to a response. <HEADER> must not
164 include pseudo header field (header field name starting
165 with ':'). The trailer is sent only if a response has
166 body part. Example: --trailer 'foo: bar'.
167 --hexdump Display the incoming traffic in hexadecimal (Canonical
168 hex+ASCII display). If SSL/TLS is used, decrypted data
169 are used.
170 --echo-upload
171 Send back uploaded content if method is POST or PUT.
172 --mime-types-file=<PATH>
173 Path to file that contains MIME media types and the
174 extensions that represent them.
175 Default: )"
176 << config.mime_types_file << R"(
177 --no-content-length
178 Don't send content-length header field.
179 --ktls Enable ktls.
180 --no-rfc7540-pri
181 Disable RFC7540 priorities.
182 --version Display version information and exit.
183 -h, --help Display this help and exit.
184
185 --
186
187 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
188 10 * 1024). Units are K, M and G (powers of 1024).)"
189 << std::endl;
190 }
191 } // namespace
192
main(int argc,char ** argv)193 int main(int argc, char **argv) {
194 Config config;
195 bool color = false;
196 auto mime_types_file_set_manually = false;
197
198 while (1) {
199 static int flag = 0;
200 constexpr static option long_options[] = {
201 {"address", required_argument, nullptr, 'a'},
202 {"daemon", no_argument, nullptr, 'D'},
203 {"htdocs", required_argument, nullptr, 'd'},
204 {"help", no_argument, nullptr, 'h'},
205 {"verbose", no_argument, nullptr, 'v'},
206 {"verify-client", no_argument, nullptr, 'V'},
207 {"header-table-size", required_argument, nullptr, 'c'},
208 {"push", required_argument, nullptr, 'p'},
209 {"padding", required_argument, nullptr, 'b'},
210 {"max-concurrent-streams", required_argument, nullptr, 'm'},
211 {"workers", required_argument, nullptr, 'n'},
212 {"error-gzip", no_argument, nullptr, 'e'},
213 {"window-bits", required_argument, nullptr, 'w'},
214 {"connection-window-bits", required_argument, nullptr, 'W'},
215 {"no-tls", no_argument, &flag, 1},
216 {"color", no_argument, &flag, 2},
217 {"version", no_argument, &flag, 3},
218 {"dh-param-file", required_argument, &flag, 4},
219 {"early-response", no_argument, &flag, 5},
220 {"trailer", required_argument, &flag, 6},
221 {"hexdump", no_argument, &flag, 7},
222 {"echo-upload", no_argument, &flag, 8},
223 {"mime-types-file", required_argument, &flag, 9},
224 {"no-content-length", no_argument, &flag, 10},
225 {"encoder-header-table-size", required_argument, &flag, 11},
226 {"ktls", no_argument, &flag, 12},
227 {"no-rfc7540-pri", no_argument, &flag, 13},
228 {nullptr, 0, nullptr, 0}};
229 int option_index = 0;
230 int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
231 &option_index);
232 if (c == -1) {
233 break;
234 }
235 switch (c) {
236 case 'a':
237 config.address = optarg;
238 break;
239 case 'D':
240 config.daemon = true;
241 break;
242 case 'V':
243 config.verify_client = true;
244 break;
245 case 'b': {
246 auto n = util::parse_uint(optarg);
247 if (!n) {
248 std::cerr << "-b: Bad option value: " << optarg << std::endl;
249 exit(EXIT_FAILURE);
250 }
251 config.padding = *n;
252 break;
253 }
254 case 'd':
255 config.htdocs = optarg;
256 break;
257 case 'e':
258 config.error_gzip = true;
259 break;
260 case 'm': {
261 // max-concurrent-streams option
262 auto n = util::parse_uint(optarg);
263 if (!n) {
264 std::cerr << "-m: invalid argument: " << optarg << std::endl;
265 exit(EXIT_FAILURE);
266 }
267 config.max_concurrent_streams = *n;
268 break;
269 }
270 case 'n': {
271 #ifdef NOTHREADS
272 std::cerr << "-n: WARNING: Threading disabled at build time, "
273 << "no threads created." << std::endl;
274 #else
275 auto n = util::parse_uint(optarg);
276 if (!n) {
277 std::cerr << "-n: Bad option value: " << optarg << std::endl;
278 exit(EXIT_FAILURE);
279 }
280 config.num_worker = *n;
281 #endif // NOTHREADS
282 break;
283 }
284 case 'h':
285 print_help(std::cout);
286 exit(EXIT_SUCCESS);
287 case 'v':
288 config.verbose = true;
289 break;
290 case 'c': {
291 auto n = util::parse_uint_with_unit(optarg);
292 if (!n) {
293 std::cerr << "-c: Bad option value: " << optarg << std::endl;
294 exit(EXIT_FAILURE);
295 }
296 if (n > std::numeric_limits<uint32_t>::max()) {
297 std::cerr << "-c: Value too large. It should be less than or equal to "
298 << std::numeric_limits<uint32_t>::max() << std::endl;
299 exit(EXIT_FAILURE);
300 }
301 config.header_table_size = *n;
302 break;
303 }
304 case 'p':
305 if (parse_push_config(config, optarg) != 0) {
306 std::cerr << "-p: Bad option value: " << optarg << std::endl;
307 }
308 break;
309 case 'w':
310 case 'W': {
311 auto n = util::parse_uint(optarg);
312 if (!n || n > 30) {
313 std::cerr << "-" << static_cast<char>(c)
314 << ": specify the integer in the range [0, 30], inclusive"
315 << std::endl;
316 exit(EXIT_FAILURE);
317 }
318
319 if (c == 'w') {
320 config.window_bits = *n;
321 } else {
322 config.connection_window_bits = *n;
323 }
324
325 break;
326 }
327 case '?':
328 util::show_candidates(argv[optind - 1], long_options);
329 exit(EXIT_FAILURE);
330 case 0:
331 switch (flag) {
332 case 1:
333 // no-tls option
334 config.no_tls = true;
335 break;
336 case 2:
337 // color option
338 color = true;
339 break;
340 case 3:
341 // version
342 print_version(std::cout);
343 exit(EXIT_SUCCESS);
344 case 4:
345 // dh-param-file
346 config.dh_param_file = optarg;
347 break;
348 case 5:
349 // early-response
350 config.early_response = true;
351 break;
352 case 6: {
353 // trailer option
354 auto header = optarg;
355 auto value = strchr(optarg, ':');
356 if (!value) {
357 std::cerr << "--trailer: invalid header: " << optarg << std::endl;
358 exit(EXIT_FAILURE);
359 }
360 *value = 0;
361 value++;
362 while (isspace(*value)) {
363 value++;
364 }
365 if (*value == 0) {
366 // This could also be a valid case for suppressing a header
367 // similar to curl
368 std::cerr << "--trailer: invalid header - value missing: " << optarg
369 << std::endl;
370 exit(EXIT_FAILURE);
371 }
372 config.trailer.emplace_back(header, value, false);
373 util::inp_strlower(config.trailer.back().name);
374 break;
375 }
376 case 7:
377 // hexdump option
378 config.hexdump = true;
379 break;
380 case 8:
381 // echo-upload option
382 config.echo_upload = true;
383 break;
384 case 9:
385 // mime-types-file option
386 mime_types_file_set_manually = true;
387 config.mime_types_file = optarg;
388 break;
389 case 10:
390 // no-content-length option
391 config.no_content_length = true;
392 break;
393 case 11: {
394 // encoder-header-table-size option
395 auto n = util::parse_uint_with_unit(optarg);
396 if (!n) {
397 std::cerr << "--encoder-header-table-size: Bad option value: "
398 << optarg << std::endl;
399 exit(EXIT_FAILURE);
400 }
401 if (n > std::numeric_limits<uint32_t>::max()) {
402 std::cerr << "--encoder-header-table-size: Value too large. It "
403 "should be less than or equal to "
404 << std::numeric_limits<uint32_t>::max() << std::endl;
405 exit(EXIT_FAILURE);
406 }
407 config.encoder_header_table_size = *n;
408 break;
409 }
410 case 12:
411 // tls option
412 config.ktls = true;
413 break;
414 case 13:
415 // no-rfc7540-pri option
416 config.no_rfc7540_pri = true;
417 break;
418 }
419 break;
420 default:
421 break;
422 }
423 }
424 if (argc - optind < (config.no_tls ? 1 : 3)) {
425 print_usage(std::cerr);
426 std::cerr << "Too few arguments" << std::endl;
427 exit(EXIT_FAILURE);
428 }
429
430 {
431 auto portStr = argv[optind++];
432 auto n = util::parse_uint(portStr);
433 if (!n || n > std::numeric_limits<uint16_t>::max()) {
434 std::cerr << "<PORT>: Bad value: " << portStr << std::endl;
435 exit(EXIT_FAILURE);
436 }
437 config.port = *n;
438 }
439
440 if (!config.no_tls) {
441 config.private_key_file = argv[optind++];
442 config.cert_file = argv[optind++];
443 }
444
445 if (config.daemon) {
446 if (config.htdocs.empty()) {
447 print_usage(std::cerr);
448 std::cerr << "-d option must be specified when -D is used." << std::endl;
449 exit(EXIT_FAILURE);
450 }
451 #ifdef __sgi
452 if (daemon(0, 0, 0, 0) == -1) {
453 #else
454 if (util::daemonize(0, 0) == -1) {
455 #endif
456 perror("daemon");
457 exit(EXIT_FAILURE);
458 }
459 }
460 if (config.htdocs.empty()) {
461 config.htdocs = "./";
462 }
463
464 if (util::read_mime_types(config.mime_types,
465 config.mime_types_file.c_str()) != 0) {
466 if (mime_types_file_set_manually) {
467 std::cerr << "--mime-types-file: Could not open mime types file: "
468 << config.mime_types_file << std::endl;
469 }
470 }
471
472 auto &trailer_names = config.trailer_names;
473 for (auto &h : config.trailer) {
474 trailer_names += h.name;
475 trailer_names += ", ";
476 }
477 if (trailer_names.size() >= 2) {
478 trailer_names.resize(trailer_names.size() - 2);
479 }
480
481 set_color_output(color || isatty(fileno(stdout)));
482
483 struct sigaction act {};
484 act.sa_handler = SIG_IGN;
485 sigaction(SIGPIPE, &act, nullptr);
486
487 reset_timer();
488
489 HttpServer server(&config);
490 if (server.run() != 0) {
491 exit(EXIT_FAILURE);
492 }
493 return 0;
494 }
495
496 } // namespace nghttp2
497
main(int argc,char ** argv)498 int main(int argc, char **argv) {
499 return nghttp2::run_app(nghttp2::main, argc, argv);
500 }
501