shrpx: Support configuration file

By default, configuration file is /etc/shrpx/shrpx.conf.  It can be
overridden using --conf option. See shrpx.conf.sample to know how to
write shrpx.conf. The configurations given in shrpx.conf will be
overridden by the options specified in cmmand-line.
This commit is contained in:
Tatsuhiro Tsujikawa 2012-08-02 00:06:41 +09:00
parent 28e477eb3a
commit 1dd61d5973
4 changed files with 349 additions and 121 deletions

View File

@ -26,6 +26,7 @@
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <signal.h>
@ -33,12 +34,12 @@
#include <arpa/inet.h>
#include <unistd.h>
#include <getopt.h>
#include <pwd.h>
#include <limits>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include <openssl/ssl.h>
#include <openssl/err.h>
@ -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<uint16_t>::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=<PATH> Set path to save PID of this program.\n"
<< " --user=<USER> Run this program as USER. This option is\n"
<< " intended to be used to drop root privileges.\n"
<< " --conf=<PATH> 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<std::pair<const char*, const char*> > 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);

View File

@ -24,8 +24,46 @@
*/
#include "shrpx_config.h"
#include <pwd.h>
#include <netdb.h>
#include <cstring>
#include <cerrno>
#include <limits>
#include <fstream>
#include <iostream>
#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<uint16_t>::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

View File

@ -33,10 +33,30 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
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

29
shrpx.conf.sample Normal file
View File

@ -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