• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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