diff --git a/examples/shrpx.cc b/examples/shrpx.cc index e1088c56..9da9b972 100644 --- a/examples/shrpx.cc +++ b/examples/shrpx.cc @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -33,12 +34,12 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -248,13 +249,23 @@ void save_pid() } } // namespace +namespace { +// Returns true if regular file or symbolic link |path| exists. +bool conf_exists(const char *path) +{ + struct stat buf; + int rv = stat(path, &buf); + return rv == 0 && (buf.st_mode & (S_IFREG | S_IFLNK)); +} +} // namespace + namespace { void fill_default_config() { mod_config()->daemon = false; mod_config()->server_name = "shrpx spdylay/"SPDYLAY_VERSION; - mod_config()->host = "localhost"; + set_config_str(&mod_config()->host, "localhost"); mod_config()->port = 3000; mod_config()->private_key_file = 0; mod_config()->cert_file = 0; @@ -284,43 +295,15 @@ void fill_default_config() // 2**16 = 64KiB, which is SPDY/3 default. mod_config()->spdy_upstream_window_bits = 16; - mod_config()->downstream_host = "localhost"; + set_config_str(&mod_config()->downstream_host, "localhost"); mod_config()->downstream_port = 80; mod_config()->num_worker = 1; mod_config()->spdy_max_concurrent_streams = SPDYLAY_INITIAL_MAX_CONCURRENT_STREAMS; -} -} // namespace -namespace { -int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, - const char *hostport) -{ - // host and port in |hostport| is separated by single ','. - const char *p = strchr(hostport, ','); - if(!p) { - std::cerr << "Invalid host, port: " << hostport << std::endl; - return -1; - } - size_t len = p-hostport; - if(hostlen < len+1) { - std::cerr << "Hostname too long: " << hostport << std::endl; - return -1; - } - memcpy(host, hostport, len); - host[len] = '\0'; - - errno = 0; - unsigned long d = strtoul(p+1, 0, 10); - if(errno == 0 && 1 <= d && d <= std::numeric_limits::max()) { - *port_ptr = d; - return 0; - } else { - std::cerr << "Port is invalid: " << p+1 << std::endl; - return -1; - } + set_config_str(&mod_config()->conf_path, "/etc/shrpx/shrpx.conf"); } } // namespace @@ -339,7 +322,6 @@ void print_usage(std::ostream& out) namespace { void print_help(std::ostream& out) { - fill_default_config(); print_usage(out); out << "\n" << "OPTIONS:\n" @@ -405,6 +387,9 @@ void print_help(std::ostream& out) << " --pid-file= Set path to save PID of this program.\n" << " --user= Run this program as USER. This option is\n" << " intended to be used to drop root privileges.\n" + << " --conf= Load configuration from PATH.\n" + << " Default: " + << get_config()->conf_path << "\n" << " -h, --help Print this help.\n" << std::endl; } @@ -416,11 +401,7 @@ int main(int argc, char **argv) create_config(); fill_default_config(); - char frontend_host[NI_MAXHOST]; - uint16_t frontend_port; - char backend_host[NI_MAXHOST]; - uint16_t backend_port; - + std::vector > cmdcfgs; while(1) { int flag; static option long_options[] = { @@ -442,6 +423,7 @@ int main(int argc, char **argv) {"frontend-spdy-window-bits", required_argument, &flag, 9 }, {"pid-file", required_argument, &flag, 10 }, {"user", required_argument, &flag, 11 }, + {"conf", required_argument, &flag, 12 }, {"help", no_argument, 0, 'h' }, {0, 0, 0, 0 } }; @@ -453,43 +435,29 @@ int main(int argc, char **argv) } switch(c) { case 'D': - mod_config()->daemon = true; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_DAEMON, "yes")); break; case 'h': print_help(std::cout); exit(EXIT_SUCCESS); case 'L': - if(Log::set_severity_level_by_name(optarg) == -1) { - std::cerr << "Invalid severity level: " << optarg << std::endl; - exit(EXIT_SUCCESS); - } + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_LOG_LEVEL, optarg)); break; case 'b': - if(split_host_port(backend_host, sizeof(backend_host), - &backend_port, optarg) == -1) { - exit(EXIT_FAILURE); - } else { - mod_config()->downstream_host = backend_host; - mod_config()->downstream_port = backend_port; - } + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_BACKEND, optarg)); break; case 'f': - if(split_host_port(frontend_host, sizeof(frontend_host), - &frontend_port, optarg) == -1) { - exit(EXIT_FAILURE); - } else { - mod_config()->host = frontend_host; - mod_config()->port = frontend_port; - } + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_FRONTEND, optarg)); break; case 'n': - mod_config()->num_worker = strtol(optarg, 0, 10); + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_WORKERS, optarg)); break; case 'c': - mod_config()->spdy_max_concurrent_streams = strtol(optarg, 0, 10); + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_SPDY_MAX_CONCURRENT_STREAMS, + optarg)); break; case 's': - mod_config()->spdy_proxy = true; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_SPDY_PROXY, "yes")); break; case '?': exit(EXIT_FAILURE); @@ -497,74 +465,57 @@ int main(int argc, char **argv) switch(flag) { case 1: // --add-x-forwarded-for - mod_config()->add_x_forwarded_for = true; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_ADD_X_FORWARDED_FOR, + "yes")); break; - case 2: { + case 2: // --frontend-spdy-read-timeout - timeval tv = {strtol(optarg, 0, 10), 0}; - mod_config()->spdy_upstream_read_timeout = tv; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_FRONTEND_SPDY_READ_TIMEOUT, + optarg)); break; - } - case 3: { + case 3: // --frontend-read-timeout - timeval tv = {strtol(optarg, 0, 10), 0}; - mod_config()->upstream_read_timeout = tv; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_FRONTEND_READ_TIMEOUT, + optarg)); break; - } - case 4: { + case 4: // --frontend-write-timeout - timeval tv = {strtol(optarg, 0, 10), 0}; - mod_config()->upstream_write_timeout = tv; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT, + optarg)); break; - } - case 5: { + case 5: // --backend-read-timeout - timeval tv = {strtol(optarg, 0, 10), 0}; - mod_config()->downstream_read_timeout = tv; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_BACKEND_READ_TIMEOUT, + optarg)); break; - } - case 6: { + case 6: // --backend-write-timeout - timeval tv = {strtol(optarg, 0, 10), 0}; - mod_config()->downstream_write_timeout = tv; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_BACKEND_WRITE_TIMEOUT, + optarg)); break; - } case 7: - mod_config()->accesslog = true; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_ACCESSLOG, "yes")); break; - case 8: { + case 8: // --backend-keep-alive-timeout - timeval tv = {strtol(optarg, 0, 10), 0}; - mod_config()->downstream_idle_read_timeout = tv; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT, + optarg)); break; - } - case 9: { + case 9: // --frontend-spdy-window-bits - errno = 0; - unsigned long int n = strtoul(optarg, 0, 10); - if(errno == 0 && n < 31) { - mod_config()->spdy_upstream_window_bits = n; - } else { - std::cerr << "-w: specify the integer in the range [0, 30], inclusive" - << std::endl; - exit(EXIT_FAILURE); - } + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_FRONTEND_SPDY_WINDOW_BITS, + optarg)); break; - } case 10: - mod_config()->pid_file = optarg; + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_PID_FILE, optarg)); break; - case 11: { - passwd *pwd = getpwnam(optarg); - if(pwd == 0) { - std::cerr << "--user: failed to get uid from " << optarg - << ": " << strerror(errno) << std::endl; - exit(EXIT_FAILURE); - } - mod_config()->uid = pwd->pw_uid; - mod_config()->gid = pwd->pw_gid; + case 11: + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_USER, optarg)); + break; + case 12: + // --conf + set_config_str(&mod_config()->conf_path, optarg); break; - } default: break; } @@ -574,14 +525,33 @@ int main(int argc, char **argv) } } - if(argc-optind < 2) { + if(conf_exists(get_config()->conf_path)) { + if(load_config(get_config()->conf_path) == -1) { + std::cerr << "Failed to load configuration from " + << get_config()->conf_path << std::endl; + exit(EXIT_FAILURE); + } + } + + if((!get_config()->private_key_file || !get_config()->cert_file) && + argc - optind < 2) { print_usage(std::cerr); std::cerr << "Too few arguments" << std::endl; exit(EXIT_FAILURE); } + if(argc - optind >= 2) { + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_PRIVATE_KEY_FILE, + argv[optind++])); + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_CERTIFICATE_FILE, + argv[optind++])); + } - mod_config()->private_key_file = argv[optind++]; - mod_config()->cert_file = argv[optind++]; + for(size_t i = 0, len = cmdcfgs.size(); i < len; ++i) { + if(parse_config(cmdcfgs[i].first, cmdcfgs[i].second) == -1) { + std::cerr << "Failed to parse command-line argument." << std::endl; + exit(EXIT_FAILURE); + } + } char hostport[NI_MAXHOST+16]; bool downstream_ipv6_addr = @@ -598,7 +568,7 @@ int main(int argc, char **argv) downstream_ipv6_addr ? "]" : "", get_config()->downstream_port); } - mod_config()->downstream_hostport = hostport; + set_config_str(&mod_config()->downstream_hostport, hostport); if(cache_downstream_host_address() == -1) { exit(EXIT_FAILURE); diff --git a/examples/shrpx_config.cc b/examples/shrpx_config.cc index a6ddafd9..eb6d5005 100644 --- a/examples/shrpx_config.cc +++ b/examples/shrpx_config.cc @@ -24,8 +24,46 @@ */ #include "shrpx_config.h" +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +using namespace spdylay; + namespace shrpx { +const char SHRPX_OPT_PRIVATE_KEY_FILE[] = "private-key-file"; +const char SHRPX_OPT_CERTIFICATE_FILE[] = "certificate-file"; + +const char SHRPX_OPT_BACKEND[] = "backend"; +const char SHRPX_OPT_FRONTEND[] = "frontend"; +const char SHRPX_OPT_WORKERS[] = "workers"; +const char +SHRPX_OPT_SPDY_MAX_CONCURRENT_STREAMS[] = "spdy-max-concurrent-streams"; +const char SHRPX_OPT_LOG_LEVEL[] = "log-level"; +const char SHRPX_OPT_DAEMON[] = "daemon"; +const char SHRPX_OPT_SPDY_PROXY[] = "spdy-proxy"; +const char SHRPX_OPT_ADD_X_FORWARDED_FOR[] = "add-x-forwarded-for"; +const char +SHRPX_OPT_FRONTEND_SPDY_READ_TIMEOUT[] = "frontend-spdy-read-timeout"; +const char SHRPX_OPT_FRONTEND_READ_TIMEOUT[] = "frontend-read-timeout"; +const char SHRPX_OPT_FRONTEND_WRITE_TIMEOUT[] = "frontend-write-timeout"; +const char SHRPX_OPT_BACKEND_READ_TIMEOUT[] = "backend-read-timeout"; +const char SHRPX_OPT_BACKEND_WRITE_TIMEOUT[] = "backend-write-timeout"; +const char SHRPX_OPT_ACCESSLOG[] = "accesslog"; +const char +SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT[] = "backend-keep-alive-timeout"; +const char SHRPX_OPT_FRONTEND_SPDY_WINDOW_BITS[] = "frontend-spdy-window-bits"; +const char SHRPX_OPT_PID_FILE[] = "pid-file"; +const char SHRPX_OPT_USER[] = "user"; + Config::Config() : verbose(false), daemon(false), @@ -47,7 +85,8 @@ Config::Config() spdy_upstream_window_bits(0), pid_file(0), uid(0), - gid(0) + gid(0), + conf_path(0) {} namespace { @@ -69,4 +108,159 @@ void create_config() config = new Config(); } +namespace { +int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, + const char *hostport) +{ + // host and port in |hostport| is separated by single ','. + const char *p = strchr(hostport, ','); + if(!p) { + std::cerr << "Invalid host, port: " << hostport << std::endl; + return -1; + } + size_t len = p-hostport; + if(hostlen < len+1) { + std::cerr << "Hostname too long: " << hostport << std::endl; + return -1; + } + memcpy(host, hostport, len); + host[len] = '\0'; + + errno = 0; + unsigned long d = strtoul(p+1, 0, 10); + if(errno == 0 && 1 <= d && d <= std::numeric_limits::max()) { + *port_ptr = d; + return 0; + } else { + std::cerr << "Port is invalid: " << p+1 << std::endl; + return -1; + } +} +} // namespace + +void set_config_str(char **destp, const char *val) +{ + if(*destp) { + free(*destp); + } + *destp = strdup(val); +} + +int parse_config(const char *opt, const char *optarg) +{ + char host[NI_MAXHOST]; + uint16_t port; + if(util::strieq(opt, SHRPX_OPT_BACKEND)) { + if(split_host_port(host, sizeof(host), &port, optarg) == -1) { + return -1; + } else { + set_config_str(&mod_config()->downstream_host, host); + mod_config()->downstream_port = port; + } + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND)) { + if(split_host_port(host, sizeof(host), &port, optarg) == -1) { + return -1; + } else { + set_config_str(&mod_config()->host, host); + mod_config()->port = port; + } + } else if(util::strieq(opt, SHRPX_OPT_WORKERS)) { + mod_config()->num_worker = strtol(optarg, 0, 10); + } else if(util::strieq(opt, SHRPX_OPT_SPDY_MAX_CONCURRENT_STREAMS)) { + mod_config()->spdy_max_concurrent_streams = strtol(optarg, 0, 10); + } else if(util::strieq(opt, SHRPX_OPT_LOG_LEVEL)) { + if(Log::set_severity_level_by_name(optarg) == -1) { + std::cerr << "Invalid severity level: " << optarg << std::endl; + return -1; + } + } else if(util::strieq(opt, SHRPX_OPT_DAEMON)) { + mod_config()->daemon = util::strieq(optarg, "yes"); + } else if(util::strieq(opt, SHRPX_OPT_SPDY_PROXY)) { + mod_config()->spdy_proxy = util::strieq(optarg, "yes"); + } else if(util::strieq(opt, SHRPX_OPT_ADD_X_FORWARDED_FOR)) { + mod_config()->add_x_forwarded_for = util::strieq(optarg, "yes"); + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND_SPDY_READ_TIMEOUT)) { + timeval tv = {strtol(optarg, 0, 10), 0}; + mod_config()->spdy_upstream_read_timeout = tv; + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND_READ_TIMEOUT)) { + timeval tv = {strtol(optarg, 0, 10), 0}; + mod_config()->upstream_read_timeout = tv; + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND_WRITE_TIMEOUT)) { + timeval tv = {strtol(optarg, 0, 10), 0}; + mod_config()->upstream_write_timeout = tv; + } else if(util::strieq(opt, SHRPX_OPT_BACKEND_READ_TIMEOUT)) { + timeval tv = {strtol(optarg, 0, 10), 0}; + mod_config()->downstream_read_timeout = tv; + } else if(util::strieq(opt, SHRPX_OPT_BACKEND_WRITE_TIMEOUT)) { + timeval tv = {strtol(optarg, 0, 10), 0}; + mod_config()->downstream_write_timeout = tv; + } else if(util::strieq(opt, SHRPX_OPT_ACCESSLOG)) { + mod_config()->accesslog = util::strieq(optarg, "yes"); + } else if(util::strieq(opt, SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT)) { + timeval tv = {strtol(optarg, 0, 10), 0}; + mod_config()->downstream_idle_read_timeout = tv; + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND_SPDY_WINDOW_BITS)) { + errno = 0; + unsigned long int n = strtoul(optarg, 0, 10); + if(errno == 0 && n < 31) { + mod_config()->spdy_upstream_window_bits = n; + } else { + std::cerr << "-w: specify the integer in the range [0, 30], inclusive" + << std::endl; + return -1; + } + } else if(util::strieq(opt, SHRPX_OPT_PID_FILE)) { + set_config_str(&mod_config()->pid_file, optarg); + } else if(util::strieq(opt, SHRPX_OPT_USER)) { + passwd *pwd = getpwnam(optarg); + if(pwd == 0) { + std::cerr << "--user: failed to get uid from " << optarg + << ": " << strerror(errno) << std::endl; + return -1; + } + mod_config()->uid = pwd->pw_uid; + mod_config()->gid = pwd->pw_gid; + } else if(util::strieq(opt, SHRPX_OPT_PRIVATE_KEY_FILE)) { + set_config_str(&mod_config()->private_key_file, optarg); + } else if(util::strieq(opt, SHRPX_OPT_CERTIFICATE_FILE)) { + set_config_str(&mod_config()->cert_file, optarg); + } else if(util::strieq(opt, "conf")) { + std::cerr << "conf is ignored" << std::endl; + } else { + std::cerr << "Unknown option: " << opt << std::endl; + return -1; + } + return 0; +} + +int load_config(const char *filename) +{ + std::ifstream in(filename, std::ios::binary); + if(!in) { + std::cerr << "Could not open config file " << filename << std::endl; + return -1; + } + std::string line; + int linenum = 0; + while(std::getline(in, line)) { + ++linenum; + if(line.empty() || line[0] == '#') { + continue; + } + size_t i; + size_t size = line.size(); + for(i = 0; i < size && line[i] != '='; ++i); + if(i == size) { + std::cerr << "Bad configuration format at line " << linenum << std::endl; + return -1; + } + line[i] = '\0'; + const char *s = line.c_str(); + if(parse_config(s, s+i+1) == -1) { + return -1; + } + } + return 0; +} + } // namespace shrpx diff --git a/examples/shrpx_config.h b/examples/shrpx_config.h index 103590fe..650060e1 100644 --- a/examples/shrpx_config.h +++ b/examples/shrpx_config.h @@ -33,10 +33,30 @@ #include #include -#include - namespace shrpx { +extern const char SHRPX_OPT_PRIVATE_KEY_FILE[]; +extern const char SHRPX_OPT_CERTIFICATE_FILE[]; + +extern const char SHRPX_OPT_BACKEND[]; +extern const char SHRPX_OPT_FRONTEND[]; +extern const char SHRPX_OPT_WORKERS[]; +extern const char SHRPX_OPT_SPDY_MAX_CONCURRENT_STREAMS[]; +extern const char SHRPX_OPT_LOG_LEVEL[]; +extern const char SHRPX_OPT_DAEMON[]; +extern const char SHRPX_OPT_SPDY_PROXY[]; +extern const char SHRPX_OPT_ADD_X_FORWARDED_FOR[]; +extern const char SHRPX_OPT_FRONTEND_SPDY_READ_TIMEOUT[]; +extern const char SHRPX_OPT_FRONTEND_READ_TIMEOUT[]; +extern const char SHRPX_OPT_FRONTEND_WRITE_TIMEOUT[]; +extern const char SHRPX_OPT_BACKEND_READ_TIMEOUT[]; +extern const char SHRPX_OPT_BACKEND_WRITE_TIMEOUT[]; +extern const char SHRPX_OPT_ACCESSLOG[]; +extern const char SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT[]; +extern const char SHRPX_OPT_FRONTEND_SPDY_WINDOW_BITS[]; +extern const char SHRPX_OPT_PID_FILE[]; +extern const char SHRPX_OPT_USER[]; + union sockaddr_union { sockaddr sa; sockaddr_storage storage; @@ -47,15 +67,15 @@ union sockaddr_union { struct Config { bool verbose; bool daemon; - const char *host; + char *host; uint16_t port; - const char *private_key_file; - const char *cert_file; + char *private_key_file; + char *cert_file; bool verify_client; const char *server_name; - const char *downstream_host; + char *downstream_host; uint16_t downstream_port; - const char *downstream_hostport; + char *downstream_hostport; sockaddr_union downstream_addr; size_t downstream_addrlen; timeval spdy_upstream_read_timeout; @@ -70,9 +90,10 @@ struct Config { bool add_x_forwarded_for; bool accesslog; size_t spdy_upstream_window_bits; - const char* pid_file; + char *pid_file; uid_t uid; gid_t gid; + char *conf_path; Config(); }; @@ -80,6 +101,20 @@ const Config* get_config(); Config* mod_config(); void create_config(); +// Parses option name |opt| and value |optarg|. The results are +// stored into statically allocated Config object. This function +// returns 0 if it succeeds, or -1. +int parse_config(const char *opt, const char *optarg); + +// Loads configurations from |filename| and stores them in statically +// allocated Config object. This function returns 0 if it succeeds, or +// -1. +int load_config(const char *filename); + +// Copies NULL-terminated string |val| to |*destp|. If |*destp| is not +// NULL, it is freed before copying. +void set_config_str(char **destp, const char *val); + } // namespace shrpx #endif // SHRPX_CONFIG_H diff --git a/shrpx.conf.sample b/shrpx.conf.sample new file mode 100644 index 00000000..c53650d8 --- /dev/null +++ b/shrpx.conf.sample @@ -0,0 +1,29 @@ +# +# Sample configuration file for Shrpx. +# +# * Line staring '#' is treated as comment. +# +# * The option name in the configuration file is the long command-line +# option name, stripped leading '--' (e.g., frontend). Put '=' +# between option name and value. Don't put extra leading or trailing +# spaces. +# +# * The options which do not take argument in the command-line *take* +# argument in the configuration file. Specify 'yes' as argument +# (e.g., spdy-proxy=yes). If other string is given, it disables the +# option. +# +# * To specify private key and certificate file, use private-key-file +# and certificate-file. See the examples below. +# +# * conf option cannot be used in the configuration file. It will be +# ignored. +# +# Examples: +# +# frontend=localhost,3000 +# backend=localhost,80 +# private-key-file=/path/to/server.key +# certificate-file=/path/to/server.crt +# spdy-proxy=no +# workers=1