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 <openssl/ssl.h>
45 #include <openssl/err.h>
46 #include <nghttp2/nghttp2.h>
47
48 #include "app_helper.h"
49 #include "HttpServer.h"
50 #include "util.h"
51 #include "tls.h"
52
53 namespace nghttp2 {
54
55 namespace {
parse_push_config(Config & config,const char * optarg)56 int parse_push_config(Config &config, const char *optarg) {
57 const char *eq = strchr(optarg, '=');
58 if (eq == NULL) {
59 return -1;
60 }
61 auto &paths = config.push[std::string(optarg, eq)];
62 auto optarg_end = optarg + strlen(optarg);
63 auto i = eq + 1;
64 for (;;) {
65 const char *j = strchr(i, ',');
66 if (j == NULL) {
67 j = optarg_end;
68 }
69 paths.emplace_back(i, j);
70 if (j == optarg_end) {
71 break;
72 }
73 i = j;
74 ++i;
75 }
76
77 return 0;
78 }
79 } // namespace
80
81 namespace {
print_version(std::ostream & out)82 void print_version(std::ostream &out) {
83 out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl;
84 }
85 } // namespace
86
87 namespace {
print_usage(std::ostream & out)88 void print_usage(std::ostream &out) {
89 out << "Usage: nghttpd [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]\n"
90 << "HTTP/2 server" << std::endl;
91 }
92 } // namespace
93
94 namespace {
print_help(std::ostream & out)95 void print_help(std::ostream &out) {
96 Config config;
97 print_usage(out);
98 out << R"(
99 <PORT> Specify listening port number.
100 <PRIVATE_KEY>
101 Set path to server's private key. Required unless
102 --no-tls is specified.
103 <CERT> Set path to server's certificate. Required unless
104 --no-tls is specified.
105 Options:
106 -a, --address=<ADDR>
107 The address to bind to. If not specified the default IP
108 address determined by getaddrinfo is used.
109 -D, --daemon
110 Run in a background. If -D is used, the current working
111 directory is changed to '/'. Therefore if this option
112 is used, -d option must be specified.
113 -V, --verify-client
114 The server sends a client certificate request. If the
115 client did not return a certificate, the handshake is
116 terminated. Currently, this option just requests a
117 client certificate and does not verify it.
118 -d, --htdocs=<PATH>
119 Specify document root. If this option is not specified,
120 the document root is the current working directory.
121 -v, --verbose
122 Print debug information such as reception/ transmission
123 of frames and name/value pairs.
124 --no-tls Disable SSL/TLS.
125 -c, --header-table-size=<SIZE>
126 Specify decoder header table size.
127 --encoder-header-table-size=<SIZE>
128 Specify encoder header table size. The decoder (client)
129 specifies the maximum dynamic table size it accepts.
130 Then the negotiated dynamic table size is the minimum of
131 this option value and the value which client specified.
132 --color Force colored log output.
133 -p, --push=<PATH>=<PUSH_PATH,...>
134 Push resources <PUSH_PATH>s when <PATH> is requested.
135 This option can be used repeatedly to specify multiple
136 push configurations. <PATH> and <PUSH_PATH>s are
137 relative to document root. See --htdocs option.
138 Example: -p/=/foo.png -p/doc=/bar.css
139 -b, --padding=<N>
140 Add at most <N> bytes to a frame payload as padding.
141 Specify 0 to disable padding.
142 -m, --max-concurrent-streams=<N>
143 Set the maximum number of the concurrent streams in one
144 HTTP/2 session.
145 Default: )"
146 << config.max_concurrent_streams << R"(
147 -n, --workers=<N>
148 Set the number of worker threads.
149 Default: 1
150 -e, --error-gzip
151 Make error response gzipped.
152 -w, --window-bits=<N>
153 Sets the stream level initial window size to 2**<N>-1.
154 -W, --connection-window-bits=<N>
155 Sets the connection level initial window size to
156 2**<N>-1.
157 --dh-param-file=<PATH>
158 Path to file that contains DH parameters in PEM format.
159 Without this option, DHE cipher suites are not
160 available.
161 --early-response
162 Start sending response when request HEADERS is received,
163 rather than complete request is received.
164 --trailer=<HEADER>
165 Add a trailer header to a response. <HEADER> must not
166 include pseudo header field (header field name starting
167 with ':'). The trailer is sent only if a response has
168 body part. Example: --trailer 'foo: bar'.
169 --hexdump Display the incoming traffic in hexadecimal (Canonical
170 hex+ASCII display). If SSL/TLS is used, decrypted data
171 are used.
172 --echo-upload
173 Send back uploaded content if method is POST or PUT.
174 --mime-types-file=<PATH>
175 Path to file that contains MIME media types and the
176 extensions that represent them.
177 Default: )"
178 << config.mime_types_file << R"(
179 --no-content-length
180 Don't send content-length header field.
181 --version Display version information and exit.
182 -h, --help Display this help and exit.
183
184 --
185
186 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
187 10 * 1024). Units are K, M and G (powers of 1024).)"
188 << std::endl;
189 }
190 } // namespace
191
main(int argc,char ** argv)192 int main(int argc, char **argv) {
193 tls::libssl_init();
194
195 #ifndef NOTHREADS
196 tls::LibsslGlobalLock lock;
197 #endif // NOTHREADS
198
199 Config config;
200 bool color = false;
201 auto mime_types_file_set_manually = false;
202
203 while (1) {
204 static int flag = 0;
205 constexpr static option long_options[] = {
206 {"address", required_argument, nullptr, 'a'},
207 {"daemon", no_argument, nullptr, 'D'},
208 {"htdocs", required_argument, nullptr, 'd'},
209 {"help", no_argument, nullptr, 'h'},
210 {"verbose", no_argument, nullptr, 'v'},
211 {"verify-client", no_argument, nullptr, 'V'},
212 {"header-table-size", required_argument, nullptr, 'c'},
213 {"push", required_argument, nullptr, 'p'},
214 {"padding", required_argument, nullptr, 'b'},
215 {"max-concurrent-streams", required_argument, nullptr, 'm'},
216 {"workers", required_argument, nullptr, 'n'},
217 {"error-gzip", no_argument, nullptr, 'e'},
218 {"window-bits", required_argument, nullptr, 'w'},
219 {"connection-window-bits", required_argument, nullptr, 'W'},
220 {"no-tls", no_argument, &flag, 1},
221 {"color", no_argument, &flag, 2},
222 {"version", no_argument, &flag, 3},
223 {"dh-param-file", required_argument, &flag, 4},
224 {"early-response", no_argument, &flag, 5},
225 {"trailer", required_argument, &flag, 6},
226 {"hexdump", no_argument, &flag, 7},
227 {"echo-upload", no_argument, &flag, 8},
228 {"mime-types-file", required_argument, &flag, 9},
229 {"no-content-length", no_argument, &flag, 10},
230 {"encoder-header-table-size", required_argument, &flag, 11},
231 {nullptr, 0, nullptr, 0}};
232 int option_index = 0;
233 int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
234 &option_index);
235 if (c == -1) {
236 break;
237 }
238 switch (c) {
239 case 'a':
240 config.address = optarg;
241 break;
242 case 'D':
243 config.daemon = true;
244 break;
245 case 'V':
246 config.verify_client = true;
247 break;
248 case 'b':
249 config.padding = strtol(optarg, nullptr, 10);
250 break;
251 case 'd':
252 config.htdocs = optarg;
253 break;
254 case 'e':
255 config.error_gzip = true;
256 break;
257 case 'm': {
258 // max-concurrent-streams option
259 auto n = util::parse_uint(optarg);
260 if (n == -1) {
261 std::cerr << "-m: invalid argument: " << optarg << std::endl;
262 exit(EXIT_FAILURE);
263 }
264 config.max_concurrent_streams = n;
265 break;
266 }
267 case 'n': {
268 #ifdef NOTHREADS
269 std::cerr << "-n: WARNING: Threading disabled at build time, "
270 << "no threads created." << std::endl;
271 #else
272 char *end;
273 errno = 0;
274 config.num_worker = strtoul(optarg, &end, 10);
275 if (errno == ERANGE || *end != '\0' || config.num_worker == 0) {
276 std::cerr << "-n: Bad option value: " << optarg << std::endl;
277 exit(EXIT_FAILURE);
278 }
279 #endif // NOTHREADS
280 break;
281 }
282 case 'h':
283 print_help(std::cout);
284 exit(EXIT_SUCCESS);
285 case 'v':
286 config.verbose = true;
287 break;
288 case 'c': {
289 auto n = util::parse_uint_with_unit(optarg);
290 if (n == -1) {
291 std::cerr << "-c: Bad option value: " << optarg << std::endl;
292 exit(EXIT_FAILURE);
293 }
294 if (n > std::numeric_limits<uint32_t>::max()) {
295 std::cerr << "-c: Value too large. It should be less than or equal to "
296 << std::numeric_limits<uint32_t>::max() << std::endl;
297 exit(EXIT_FAILURE);
298 }
299 config.header_table_size = n;
300 break;
301 }
302 case 'p':
303 if (parse_push_config(config, optarg) != 0) {
304 std::cerr << "-p: Bad option value: " << optarg << std::endl;
305 }
306 break;
307 case 'w':
308 case 'W': {
309 char *endptr;
310 errno = 0;
311 auto n = strtoul(optarg, &endptr, 10);
312 if (errno != 0 || *endptr != '\0' || n >= 31) {
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 == -1) {
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 }
411 break;
412 default:
413 break;
414 }
415 }
416 if (argc - optind < (config.no_tls ? 1 : 3)) {
417 print_usage(std::cerr);
418 std::cerr << "Too few arguments" << std::endl;
419 exit(EXIT_FAILURE);
420 }
421
422 config.port = strtol(argv[optind++], nullptr, 10);
423
424 if (!config.no_tls) {
425 config.private_key_file = argv[optind++];
426 config.cert_file = argv[optind++];
427 }
428
429 if (config.daemon) {
430 if (config.htdocs.empty()) {
431 print_usage(std::cerr);
432 std::cerr << "-d option must be specified when -D is used." << std::endl;
433 exit(EXIT_FAILURE);
434 }
435 #ifdef __sgi
436 if (daemon(0, 0, 0, 0) == -1) {
437 #else
438 if (util::daemonize(0, 0) == -1) {
439 #endif
440 perror("daemon");
441 exit(EXIT_FAILURE);
442 }
443 }
444 if (config.htdocs.empty()) {
445 config.htdocs = "./";
446 }
447
448 if (util::read_mime_types(config.mime_types,
449 config.mime_types_file.c_str()) != 0) {
450 if (mime_types_file_set_manually) {
451 std::cerr << "--mime-types-file: Could not open mime types file: "
452 << config.mime_types_file << std::endl;
453 }
454 }
455
456 auto &trailer_names = config.trailer_names;
457 for (auto &h : config.trailer) {
458 trailer_names += h.name;
459 trailer_names += ", ";
460 }
461 if (trailer_names.size() >= 2) {
462 trailer_names.resize(trailer_names.size() - 2);
463 }
464
465 set_color_output(color || isatty(fileno(stdout)));
466
467 struct sigaction act {};
468 act.sa_handler = SIG_IGN;
469 sigaction(SIGPIPE, &act, nullptr);
470
471 reset_timer();
472
473 HttpServer server(&config);
474 if (server.run() != 0) {
475 exit(EXIT_FAILURE);
476 }
477 return 0;
478 }
479
480 } // namespace nghttp2
481
main(int argc,char ** argv)482 int main(int argc, char **argv) {
483 return nghttp2::run_app(nghttp2::main, argc, argv);
484 }
485