├── src ├── confd.h ├── log.h ├── confd_shm.h ├── config.json ├── httpd.h ├── confd_shmtx.h ├── util.h ├── config.h ├── log.cc ├── process_manage.h ├── confd_dict.h ├── nginx_opt.h ├── confd_shmtx.cc ├── confd_shm.cc ├── util.cc ├── confd.cc ├── confd_dict.cc ├── nginx_conf_parse.h ├── nginx_opt.cc ├── process_manage.cc └── httpd.cc ├── script ├── reload_confd.crontab └── slave0_sync.py ├── bin ├── nginx_standard.conf ├── config.json └── nginx_websocket.conf ├── test ├── util_test.cc ├── parse_test.cc ├── add_conf_current_test.py ├── confd_lock.cc └── nginx_opt_test.cc ├── template ├── nginx_standard.conf └── nginx_websocket.conf ├── Makefile ├── readme.md └── lib ├── httpd └── src │ └── include │ ├── status_code.hpp │ ├── utility.hpp │ └── server_http.hpp └── jsoncpp └── src └── json └── json-forwards.h /src/confd.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/reload_confd.crontab: -------------------------------------------------------------------------------- 1 | */1 * * * * cd /home/confd/ && ./confd -c config.json -s reload 2 | -------------------------------------------------------------------------------- /bin/nginx_standard.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen %s; 3 | server_name %s; 4 | location / { 5 | proxy_pass http://%s; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOG_H 2 | #define _LOG_H 3 | 4 | #include 5 | 6 | void init_log(int verbosity, const std::string& log_file); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /test/util_test.cc: -------------------------------------------------------------------------------- 1 | #include "../src/util.h" 2 | #include 3 | 4 | int 5 | main(void) 6 | { 7 | std::cout << "MemAvailable: " << get_MemAvailable() << std::endl;; 8 | } 9 | -------------------------------------------------------------------------------- /template/nginx_standard.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen %{listen_port}; 3 | server_name %{server_name}; 4 | location / { 5 | proxy_pass http://%{upstream_name}; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/confd_shm.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFD_SHM_H 2 | #define _CONFD_SHM_H 3 | 4 | #include "config.h" 5 | 6 | typedef struct confd_shm { 7 | char* addr; 8 | size_t size; 9 | } confd_shm_t; 10 | 11 | confd_shm_t* init_shm(size_t shm_size); 12 | bool destory_shm(confd_shm_t *shm); 13 | //bool confd_shm_alloc(confd_shm_t *shm); 14 | //bool confd_shm_free(confd_shm_t *shm); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /bin/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8090, 3 | "addr": "0.0.0.0", 4 | "pid_path": "confd.pid", 5 | "log_path": "confd.log", 6 | "nginx_bin_path": "/home/work/nginx/sbin/nginx", 7 | "nginx_conf_path": "/home/work/nginx/conf/nginx.conf", 8 | "nginx_conf_writen_path": "/home/work/nginx/conf/vhost/", 9 | "shm_size": "10m", 10 | "worker_process": 16, 11 | "verbosity": 1, 12 | "daemon": true 13 | } 14 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8090, 3 | "addr": "0.0.0.0", 4 | "pid_path": "confd.pid", 5 | "log_path": "confd.log", 6 | "nginx_bin_path": "/home/work/nginx/sbin/nginx", 7 | "nginx_conf_path": "/home/work/nginx/conf/nginx.conf", 8 | "nginx_conf_writen_path": "/home/work/nginx/conf/vhost/", 9 | "shm_size": "10m", 10 | "worker_process": 16, 11 | "verbosity": 1, 12 | "daemon": true 13 | } 14 | -------------------------------------------------------------------------------- /src/httpd.h: -------------------------------------------------------------------------------- 1 | #include "../lib/httpd/src/include/server_http.hpp" 2 | 3 | // Added for the json-example 4 | #define BOOST_SPIRIT_THREADSAFE 5 | #include 6 | #include 7 | 8 | // Added for the default_resource example 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | int httpServer(std::string address, int port); 16 | -------------------------------------------------------------------------------- /src/confd_shmtx.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFD_SHMTX_H 2 | #define _CONFD_SHMTX_H 3 | 4 | #define MAX_SEMAPHORE 1 5 | 6 | typedef struct confd_shmtx { 7 | #if (USE_SYSV_SEM) 8 | int sem_id; 9 | #else 10 | int fd; 11 | unsigned char *name; 12 | #endif 13 | } confd_shmtx_t; 14 | 15 | confd_shmtx_t* init_lock(); 16 | int lock(confd_shmtx_t* shmtx); 17 | int unlock(confd_shmtx_t* shmtx); 18 | bool destory_lock(confd_shmtx_t* shmtx); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /bin/nginx_websocket.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen %s; 3 | server_name %s; 4 | location / { 5 | proxy_pass http://%s; 6 | proxy_buffering off; 7 | proxy_http_version 1.1; 8 | proxy_set_header Upgrade $http_upgrade; 9 | proxy_set_header Connection "upgrade"; 10 | proxy_set_header X-Real-IP $remote_addr; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | proxy_set_header Host $http_host; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTIL_H 2 | #define _UTIL_H 3 | 4 | #include 5 | 6 | std::string ltrim(std::string &s); 7 | std::string rtrim(std::string &s); 8 | std::string trim(std::string &s); 9 | size_t parse_bytes_number(const std::string& str); 10 | std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value); 11 | long get_meminfo_kv(const char* k); 12 | long get_MemAvailable(); 13 | std::pair exec_cmd(const char* cmd); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /template/nginx_websocket.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen %{listen_port}; 3 | server_name %{server_name}; 4 | location / { 5 | proxy_pass http://%{upstream_name}; 6 | proxy_buffering off; 7 | proxy_http_version 1.1; 8 | proxy_set_header Upgrade $http_upgrade; 9 | proxy_set_header Connection "upgrade"; 10 | proxy_set_header X-Real-IP $remote_addr; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | proxy_set_header Host $http_host; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/parse_test.cc: -------------------------------------------------------------------------------- 1 | #include "../src/nginx_conf_parse.h" 2 | #include 3 | 4 | int 5 | main(void) 6 | { 7 | nginxConfParse p; 8 | unordered_map> confs = p.parse("/home/work/nginx/conf/nginx.conf"); 9 | for(auto& item: confs) { 10 | for(auto& v: item.second) { 11 | std::cout << item.first << "=>" << v << std::endl; 12 | } 13 | } 14 | if (confs.size() >= 1) { 15 | std::cout << "nginx conf parse ok." << std::endl;; 16 | } else { 17 | throw; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H 2 | #define _CONFIG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | using namespace std; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/log.cc: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace logging = boost::log; 8 | 9 | void init_log(int verbosity, const std::string& log_file) { 10 | using namespace logging; 11 | using namespace logging::trivial; 12 | logging::register_simple_formatter_factory("Severity"); 13 | logging::add_common_attributes(); 14 | std::string log_format = "[%TimeStamp%] [%Severity%] %Message%"; 15 | logging::add_file_log( 16 | keywords::file_name = log_file, 17 | //keywords::rotation_size = 100L * 1024 * 1024, 18 | keywords::format = log_format, 19 | keywords::auto_flush = true, 20 | keywords::filter = severity >= verbosity, 21 | boost::log::keywords::open_mode = ( std::ios::out | std::ios::app) 22 | ); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/process_manage.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROCESS_MANAGE_H 2 | #define _PROCESS_MANAGE_H 3 | 4 | #include "config.h" 5 | 6 | #define MASTERNAME "confd: master process" 7 | #define WORKERNAME "confd: worker process" 8 | 9 | typedef struct confd_signal { 10 | int signo; 11 | const char *opt_name; 12 | void (*handler)(int signo, siginfo_t *siginfo, void *ucontext); 13 | } confd_signal_t; 14 | 15 | typedef struct c_process { 16 | std::string name; 17 | pid_t pid; 18 | bool restart; 19 | } process_t; 20 | 21 | typedef struct _arg { 22 | int argc; 23 | char **argv; 24 | } confd_arg_t; 25 | 26 | void notify_master_process(const char *pid_path, const char *cmd); 27 | bool init_master_process(unordered_map config); 28 | void init_worker_process(confd_arg_t confd_arg, unordered_map config, std::string name); 29 | pid_t spawn_worker_process(std::string name); 30 | 31 | extern confd_arg_t confd_arg; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/confd_dict.h: -------------------------------------------------------------------------------- 1 | #ifndef _MEM_DICT_H 2 | #define _MEM_DICT_H 3 | 4 | #include "config.h" 5 | #include "nginx_opt.h" 6 | 7 | 8 | class confd_dict { 9 | public: 10 | confd_dict(){}; 11 | confd_dict(unordered_map> ss){ 12 | this->ss = ss; 13 | } 14 | std::pair add_item(std::string listen_port, std::string domain, 15 | std::vector value, std::string tmp_style, bool force, HEALTH_CHECK type); 16 | bool shm_sync_to_dict(); 17 | bool dict_sync_to_shm(); 18 | std::string json_stringify(); 19 | std::pair> get_value_by_key(std::string key); 20 | std::pair delete_key(std::string key); 21 | void update_status(bool status); 22 | bool update_status(bool status, u_int solt); 23 | bool status(u_int solt); 24 | private: 25 | unordered_map> ss; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /test/add_conf_current_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import time 4 | import random 5 | import string 6 | import requests 7 | import threading 8 | 9 | def add_randomkey_conf(): 10 | confd_api = "http://127.0.0.1:8090/api/confs" 11 | for i in xrange(2): 12 | post = {} 13 | random_str = ''.join(random.sample(string.ascii_letters + string.digits, 20)) 14 | random_str = random_str + ".firebroo.com" 15 | post["domain"] = random_str 16 | post["upstreams"] = [{"server": "2.2.2.2:3333"}, {"server": "3.3.3.3:3333"}] 17 | post["type"] = "websocket" 18 | post["listen"] = "80" 19 | post["health_check"] = 0 20 | ret = requests.post(confd_api, data = json.dumps(post)) 21 | data = ret.text 22 | json_data = json.loads(data) 23 | if (json_data["code"] != 200): 24 | print "add %s failed." % (random_str) 25 | 26 | 27 | 28 | if __name__ == '__main__': 29 | threads = [] 30 | 31 | start = time.time() 32 | for i in range(100): 33 | t = threading.Thread(target=add_randomkey_conf) 34 | threads.append(t) 35 | for thread in threads: 36 | thread.start() 37 | for thread in threads: 38 | thread.join() 39 | end = time.time() 40 | print "100 threads add 200 domain finish, time: %s" % (end - start) 41 | -------------------------------------------------------------------------------- /test/confd_lock.cc: -------------------------------------------------------------------------------- 1 | #include "../src/confd_shmtx.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | int 11 | main(void) 12 | { 13 | confd_shmtx_t* shmtx = init_lock(); 14 | if(!shmtx) { 15 | printf("init lock failed."); 16 | exit(-1); 17 | } 18 | pid_t pid = fork(); 19 | if (pid < 0) { 20 | printf("fork failed."); 21 | exit(-1); 22 | } 23 | if (pid > 0) { //father 24 | printf("father process try get lock.\n"); 25 | lock(shmtx); 26 | printf("father process get lock, and will sleep 5\n"); 27 | sleep(5); 28 | unlock(shmtx); 29 | printf("father process unlock.\n"); 30 | } else { //children 31 | sleep(1); 32 | printf("children process try get lock.\n"); 33 | lock(shmtx); 34 | printf("children process get lock.\n"); 35 | unlock(shmtx); 36 | printf("children unlock and exit.\n"); 37 | exit(0); 38 | } 39 | int status; 40 | while ((pid = waitpid(-1, &status, 0)) != 0) { //等待所有子进程退出 41 | if (errno == ECHILD) { 42 | printf("wait all children process exit finish.\n"); 43 | break; 44 | } 45 | } 46 | printf("lock test successful.\n"); 47 | destory_lock(shmtx); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX=g++ 2 | INCLUDE = -I./lib/jsoncpp/src/json 3 | LDFLAGS= -Wl,-Bstatic -lboost_log -lboost_log_setup -lboost_filesystem -lboost_system -lboost_regex -lboost_thread -Wl,-Bdynamic -lpthread -O2 4 | CXXFLAGS= -O2 -Wall -std=c++11 -DUSE_BOOST_REGEX -DUSE_MMAP_ANON -DUSE_SYSV_SEM 5 | SRC=src 6 | TEST=test 7 | BIN=bin 8 | TEMPLATE=template 9 | INSTALL=/home/confd/ 10 | SOURCE=$(wildcard $(SRC)/*.cc ./lib/jsoncpp/src/*.cc) 11 | OBJS=$(patsubst %.cc,%.o, $(SOURCE)) 12 | TARGET=confd 13 | 14 | build:$(OBJS) 15 | @test -d $(BIN) || mkdir -p $(BIN) 16 | @echo LINK $(BIN)/$(TARGET) 17 | @$(CXX) -o $(BIN)/$(TARGET) $^ $(LDFLAGS) 18 | @cp $(SRC)/config.json $(BIN) 19 | @cp $(TEMPLATE)/nginx_*.conf $(BIN) 20 | 21 | install: 22 | @test -d $(INSTALL) || mkdir -p $(INSTALL) 23 | cp $(BIN)/$(TARGET) $(INSTALL) 24 | cp $(SRC)/config.json $(INSTALL) 25 | cp $(TEMPLATE)/nginx_*.conf $(INSTALL) 26 | 27 | test_opt: $(SRC)/nginx_opt.o $(TEST)/nginx_opt_test.o $(SRC)/util.o 28 | @test -d $(BIN) || mkdir -p $(BIN) 29 | $(CXX) -g -o $(BIN)/opt $^ $(LDFLAGS) 30 | @cp $(TEMPLATE)/nginx_standard.conf $(BIN) 31 | 32 | test_parse: $(SRC)/util.o $(TEST)/parse_test.o 33 | @test -d $(BIN) || mkdir -p $(BIN) 34 | $(CXX) -g -o $(BIN)/parse $^ $(LDFLAGS) 35 | 36 | test_lock: $(SRC)/confd_shmtx.o $(TEST)/confd_lock.o 37 | @test -d $(BIN) || mkdir -p $(BIN) 38 | $(CXX) -g -o $(BIN)/lock $^ 39 | 40 | test_util: $(SRC)/util.o $(TEST)/util_test.o 41 | @test -d $(BIN) || mkdir -p $(BIN) 42 | $(CXX) -g -o $(BIN)/util $^ $(LDFLAGS) 43 | 44 | .PHONY: test 45 | test: 46 | make test_opt 47 | make test_parse 48 | make test_lock 49 | make test_util 50 | cd bin && ./opt 51 | cd bin && ./parse 52 | cd bin && ./lock 53 | cd bin && ./util 54 | 55 | %.o: %.cc 56 | @echo CXX $< 57 | @$(CXX) $(INCLUDE) $(CXXFLAGS) -c $^ -o $@ 58 | 59 | .PHONY: clean 60 | clean: 61 | rm -f $(OBJS) $(BIN)/$(TARGET) $(BIN)/opt $(BIN)/parse $(BIN)/lock $(TEST)/*.o 62 | 63 | rebuild: clean 64 | @make build -j16 65 | -------------------------------------------------------------------------------- /test/nginx_opt_test.cc: -------------------------------------------------------------------------------- 1 | #include "../src/nginx_opt.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int 7 | main(void) 8 | { 9 | std::string key("abc.firebroo.com"); 10 | std::vector value; 11 | value.push_back("1.1.1.1:80"); 12 | value.push_back("1.1.1.2:80"); 13 | const char* nginx_bin_path = "/home/work/nginx/sbin/nginx"; 14 | const char* nginx_conf_path = "/home/work/nginx/conf/nginx.conf"; 15 | std::pair ret; 16 | 17 | ret = nginx_opt::sync_to_disk("80", key, value, "standard", "/home/work/nginx/conf/vhost/", nginx_bin_path, nginx_conf_path, (HEALTH_CHECK)0); 18 | if (ret.first) { 19 | std::cout << "nginx test ok, sync to disk successful" << std::endl; 20 | } else { 21 | std::cout << "nginx test error: " << ret.second << std::endl; 22 | } 23 | ret = nginx_opt::nginx_conf_reload(nginx_bin_path, nginx_conf_path); 24 | if (ret.first) { 25 | std::cout << "nginx test ok, reload successful." << std::endl; 26 | } else { 27 | std::cout << ret.second << std::endl; 28 | } 29 | ret = nginx_opt::nginx_conf_graceful_reload(nginx_bin_path, nginx_conf_path); 30 | if (ret.first) { 31 | std::cout << "nginx test ok, graceful successful." << std::endl; 32 | } else { 33 | std::cout << ret.second << std::endl; 34 | } 35 | ret = nginx_opt::nginx_conf_test(nginx_bin_path, nginx_conf_path); 36 | if (ret.first) { 37 | std::cout << "nginx test ok, test syntax successful." << std::endl; 38 | } else { 39 | std::cout << ret.second << std::endl; 40 | } 41 | ret = nginx_opt::nginx_worker_used_memsum(); 42 | if (ret.first) { 43 | std::cout << "nginx test ok, nginx_worker process usedmem: " << ret.second << std::endl; 44 | } else { 45 | std::cout << ret.second << std::endl; 46 | } 47 | ret = nginx_opt::nginx_shutting_worker_count(); 48 | if (ret.first) { 49 | std::cout << "nginx test ok, shutting worker: " << std::stoll(ret.second) << std::endl; 50 | } else { 51 | std::cout << ret.second << std::endl; 52 | } 53 | ret = nginx_opt::nginx_process_used_memsum(); 54 | if (ret.first) { 55 | std::cout << "nginx test ok, nginx process usedmem: " << ret.second << std::endl; 56 | } else { 57 | std::cout << ret.second << std::endl; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /script/slave0_sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import time 5 | import requests 6 | import subprocess 7 | import traceback 8 | 9 | class slaveSync: 10 | def __init__(self, solt, logfile): 11 | self.solt = solt 12 | self.status_api = "http://127.0.0.1:8090/api/status/solt/%s" % (self.solt) 13 | self.update_status_api = "http://127.0.0.1:8090/api/status_update/solt/%s" % (self.solt) 14 | self.logfile = logfile 15 | self.log_cursor = open(self.logfile, "a+") 16 | 17 | def __del__(self): 18 | self.log_cursor.close() 19 | 20 | def current_time(self): 21 | return time.strftime('%Y-%m-%d %H:%M:%S ',time.localtime(time.time())) 22 | 23 | def force_sync(self): 24 | cmd = "rsync xxx" 25 | pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 26 | self.log_cursor.write(self.current_time() + pipe.stdout.read()) 27 | return True 28 | 29 | def graceful_reload_nginx(self): 30 | cmd = '/home/work/nginx/sbin/nginx -t 2>&1 && /home/work/nginx/sbin/nginx -s reload 2>&1' 31 | pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 32 | self.log_cursor.write(self.current_time() + pipe.stdout.read()) 33 | return True 34 | 35 | def get_status(self, status_api): 36 | res = requests.get(status_api).json() 37 | return res["status"] 38 | 39 | 40 | def update_status(self, update_status_api): 41 | res = requests.get(update_status_api) 42 | self.log_cursor.write(self.current_time()) 43 | self.log_cursor.write("sync successful, result:\n%s\n" % (res.text)) 44 | 45 | 46 | def loop_sync(self, timer): 47 | while True: 48 | self.log_cursor.write("%s slave %s sync.\n" % (self.current_time(), self.solt)) 49 | try: 50 | if self.get_status(self.status_api): 51 | self.force_sync() and self.graceful_reload_nginx() and self.update_status(self.update_status_api) 52 | except Exception, e: 53 | self.log_cursor.write(self.current_time() + traceback.format_exc()) 54 | self.log_cursor.flush() 55 | time.sleep(timer) 56 | 57 | if __name__ == "__main__": 58 | solt = 0 59 | timer = 10 60 | sync = slaveSync(solt, "slave_sync.log") 61 | sync.loop_sync(timer) 62 | -------------------------------------------------------------------------------- /src/nginx_opt.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGINX_OPT_H 2 | #define _NGINX_OPT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum health_check { 9 | NONE, 10 | TCP, 11 | HTTP 12 | } HEALTH_CHECK; 13 | 14 | typedef struct { 15 | HEALTH_CHECK type; 16 | const char* content; 17 | } health_check_map_t; 18 | 19 | HEALTH_CHECK int_to_health_check_type(int value); 20 | 21 | class nginx_opt { 22 | public: 23 | static const char* upstream_struct_start; 24 | static const char* upstream_struct_server; 25 | static const char* upstream_struct_end; 26 | static const char* nginx_test_ok; 27 | static const char* nginx_http_health_check; 28 | static const char* nginx_tcp_health_check; 29 | private: 30 | static std::string template_replace(const std::string& listen_port, const std::string& server_name, \ 31 | std::string nginx_conf_template); 32 | public: 33 | static std::string gen_upstream(std::string listen_port, std::string key, std::vector value, HEALTH_CHECK type); 34 | static std::string gen_server(std::string listen_port, std::string key, \ 35 | std::vector value, std::string tmp_style); 36 | static std::pair nginx_conf_test(const char* nginx_bin_path, const char* nginx_conf_path); 37 | static std::pair nginx_conf_reload(const char* nginx_bin_path, const char* nginx_conf_path); 38 | static std::pair nginx_conf_graceful_reload(const char* nginx_bin_path, const char* nginx_conf_path); 39 | static std::pair sync_to_disk(std::string port, std::string key, std::vector value, \ 40 | std::string tmp_style, std::string nginx_conf_writen_path, std::string nginx_bin_path, \ 41 | std::string nginx_conf_path, HEALTH_CHECK type); 42 | static std::pair nginx_worker_used_memsum(void); 43 | static std::pair nginx_shutting_worker_count(void); 44 | static std::pair nginx_process_used_memsum(void); 45 | static std::pair delete_conf(std::string& key, std::string& nginx_conf_writen_path); 46 | static bool backup_single_conf(std::string& file_path); 47 | static bool rollback_single_conf(std::string& file_path); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/confd_shmtx.cc: -------------------------------------------------------------------------------- 1 | #include "confd_shmtx.h" 2 | 3 | #if (USE_SYSV_SEM) 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | confd_shmtx_t* 16 | init_lock() 17 | { 18 | int i, ret; 19 | int semid; 20 | union semun { 21 | int val ; 22 | struct semid_ds *buf ; 23 | unsigned short *array ; 24 | struct seminfo *_buf ; 25 | } arg; 26 | 27 | unsigned short buf[MAX_SEMAPHORE] ; 28 | 29 | confd_shmtx_t* shmtx = (confd_shmtx_t*)malloc(sizeof(confd_shmtx_t)); 30 | if (!shmtx) { 31 | printf("malloc() failed.\n"); 32 | return NULL; 33 | } 34 | for(i = 0; i < MAX_SEMAPHORE; ++i) { 35 | /* Initial semaphore */ 36 | buf[i] = i + 1; 37 | } 38 | 39 | semid = semget(IPC_PRIVATE, MAX_SEMAPHORE, IPC_CREAT|0666); 40 | if (semid == -1) { 41 | printf("semget() failed error: %s\n", strerror(errno)); 42 | free(shmtx); 43 | return NULL; 44 | } 45 | 46 | arg.array = buf; 47 | ret = semctl(semid , 0, SETALL, arg); 48 | if (ret == -1) { 49 | printf("semctl() failed error: %s\n", strerror(errno)); 50 | free(shmtx); 51 | return NULL; 52 | } 53 | shmtx->sem_id = semid; 54 | 55 | return shmtx; 56 | } 57 | 58 | int 59 | lock(confd_shmtx_t* shmtx) 60 | { 61 | int i; 62 | struct sembuf sb[MAX_SEMAPHORE]; 63 | 64 | for(i = 0; i < MAX_SEMAPHORE; ++i) { 65 | sb[i].sem_num = i ; 66 | sb[i].sem_op = -1 ; 67 | sb[i].sem_flg = 0 ; 68 | } 69 | 70 | return semop(shmtx->sem_id , sb , MAX_SEMAPHORE); 71 | } 72 | 73 | int 74 | unlock(confd_shmtx_t* shmtx) 75 | { 76 | int i; 77 | struct sembuf sb[MAX_SEMAPHORE]; 78 | 79 | for(i = 0; i < MAX_SEMAPHORE; ++i) { 80 | sb[i].sem_num = i ; 81 | sb[i].sem_op = +1 ; 82 | sb[i].sem_flg = 0 ; 83 | } 84 | 85 | return semop(shmtx->sem_id , sb , MAX_SEMAPHORE); 86 | } 87 | 88 | 89 | bool 90 | destory_lock(confd_shmtx_t* shmtx) 91 | { 92 | int sem_id; 93 | 94 | sem_id = shmtx->sem_id; 95 | free(shmtx); 96 | if (semctl(sem_id, 0, IPC_RMID, 0) == -1) { 97 | printf("semctl(IPC_RMID) failed error: %s\n", strerror(errno)); 98 | return false; 99 | } 100 | return true; 101 | } 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### 特性 2 | * pre-fork网络服务模型,根据cpu数量调控worker进程数量,支持开启端口复用(SO_REUSEPORT); 3 | * 修改配置无需restart进程,支持reload操作实现动态更新,保证HTTP服务稳定性; 4 | * 通过restful api添加配置支持graceful添加,防止内存不足强制reload nginx导致服务异常; 5 | * 简单配置可以通过模板配置添加,复杂配置通过操作服务器本地添加,两种方式添加的结果会自动同步数据; 6 | * master机器提供配置更新状态,slave可以通过一个简单的脚本检更新同步配置,最多支持4096个slave; 7 | * 不依赖数据库,二进制绿色无依赖,方便迁移部署; 8 | 9 | ### 部署 10 | 11 | 编译之前确保安装boost,开发基于boost1.6.8 12 | 13 | ```bash 14 | git clone https://github.com/firebroo/nginx-confd.git 15 | cd nginx-confd && make build && make intall 16 | crontab -l | sed "/confd/d" > /tmp/cron.rule 17 | echo "*/1 * * * * cd /home/confd/ && ./confd -c config.json -s reload" >> /tmp/cron.rule 18 | crontab /tmp/cron.rule 19 | ``` 20 | 21 | ### 启动 22 | master机器 23 | 24 | 必须指定config.json配置文件 25 | ```JavaScript 26 | { 27 | "port": "8090", #HTTP服务监听端口 28 | "addr": "0.0.0.0", #HTTP服务监听地址 29 | "pid_path": "confd.pid", #pid无需关心 30 | "log_path": "confd.log", #日志文件 31 | "nginx_bin_path": "/usr/sbin/nginx", #nginx二进制路径 32 | "nginx_conf_path": "/home/work/nginx/conf/nginx.conf", #nginx启动指定的配置文件 33 | "nginx_conf_writen_path": "/home/work/nginx/conf/vhost/", #HTTP接口添加配置落地目录 34 | "shm_size": "10m", #共享内存大小,根据代理后端服务多少决定,理论10m足够 35 | "worker_process": 4, #进程数量 36 | "verbosity": 1, #日志等级 37 | "daemon": true #后台进程模式 38 | } 39 | ``` 40 | ```bash 41 | cd /home/confd && ./confd -c config.json && tail -f confd.log 42 | ``` 43 | 44 | slave机器 45 | 46 | ```bash 47 | nohup /usr/bin/python slave_sync.py > /dev/null 2>&1 & 48 | ``` 49 | 50 | ### 使用 51 | ```bash 52 | 启动: ./confd -c config.json 53 | 54 | 重新加载: ./confd -c config.json -s reload 55 | 56 | 停止: ./confd -c config.json -s stop 57 | ``` 58 | 59 | ### HTTP服务restful接口 60 | ----------------------------- 61 | 地址: /api/confs 62 | 63 | 方法: POST 64 | 65 | 参数:domain(string), upstreams(dict), type(string), listen(string) 66 | 67 | 标准模块添加e.g. 68 | ```javascript 69 | { 70 | "code" : 200, 71 | "info" : "add successful.", 72 | "query_url" : "127.0.0.1:8090/api/confs/test1.dev.firebroo.com:80" 73 | } 74 | ``` 75 | 76 | ----------------------------- 77 | 地址: /api/confs 78 | 79 | 方法: PUT 80 | 81 | 参数:domain(string), upstreams(dict), type(string), listen(string) 82 | 83 | 标准模块添加e.g. 84 | ```javascript 85 | { 86 | "code" : 200, 87 | "info" : "add successful.", 88 | "query_url" : "127.0.0.1:8090/api/confs/test1.dev.firebroo.com:80" 89 | } 90 | ``` 91 | 92 | 93 | ----------------------------- 94 | 95 | 地址: /api/confs/{$domain} 96 | 97 | 方法: GET 98 | 99 | 参数:$domain 100 | 101 | e.g. 102 | ```javascript 103 | { 104 | "code" : 200, 105 | "data" : 106 | { 107 | "listen" : "443", 108 | "server_name" : "test.firebroo.com", 109 | "upstream" : 110 | [ 111 | "1.1.1.1:80" 112 | ] 113 | } 114 | } 115 | ``` 116 | 117 | ----------------------------- 118 | 119 | 地址: /api/confs/{$domain} 120 | 121 | 方法: DELETE 122 | 123 | 参数:$domain 124 | 125 | e.g. 126 | ```javascript 127 | { 128 | "code" : 200, 129 | "info" : "delete successful." 130 | } 131 | 132 | ``` 133 | 134 | ----------------------------- 135 | 136 | 地址: /api/nginx_health 137 | 138 | 方法: GET 139 | 140 | 参数: NULL 141 | 142 | e.g. 143 | ```javascript 144 | { 145 | "code" : "200", 146 | "data" : 147 | { 148 | "nginx process memsum" : "1200Mb", //nginx 总占用内存 149 | "nginx shutting worker" : "1" //nginx shutting down残留进程占用内存 150 | }, 151 | "error" : "" 152 | } 153 | ``` 154 | -------------------------------------------------------------------------------- /src/confd_shm.cc: -------------------------------------------------------------------------------- 1 | #include "confd_shm.h" 2 | #include "log.h" 3 | 4 | static bool confd_shm_alloc(confd_shm_t *shm); 5 | static bool confd_shm_free(confd_shm_t *shm); 6 | 7 | confd_shm_t* 8 | init_shm(size_t shm_size) 9 | { 10 | confd_shm_t* shm = (confd_shm_t*)malloc(sizeof(confd_shm_t)); 11 | shm->size = shm_size; 12 | 13 | if (!confd_shm_alloc(shm)) { 14 | BOOST_LOG_TRIVIAL(warning) << "alloc shm size(" << shm_size << ") failed."; 15 | return NULL; 16 | } 17 | return shm; 18 | } 19 | 20 | bool 21 | destory_shm(confd_shm_t *shm) 22 | { 23 | if (!confd_shm_free(shm)) { //释放共享内存 24 | BOOST_LOG_TRIVIAL(info) << "shm free failed."; 25 | return false; 26 | } 27 | free(shm); 28 | return true; 29 | } 30 | 31 | #if (USE_MMAP_ANON) 32 | 33 | bool 34 | confd_shm_alloc(confd_shm_t *shm) 35 | { 36 | shm->addr = (char *) mmap(NULL, shm->size, 37 | PROT_READ|PROT_WRITE, 38 | MAP_ANON|MAP_SHARED, -1, 0); 39 | 40 | if (shm->addr == MAP_FAILED) { 41 | BOOST_LOG_TRIVIAL(error) << "mmap ipc_name(/dev/zero) failed, error: " << strerror(errno); 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | 49 | bool 50 | confd_shm_free(confd_shm_t *shm) 51 | { 52 | char buf[1024]; 53 | 54 | if (munmap((void *) shm->addr, shm->size) == -1) { 55 | sprintf(buf, "munmap(%p, %ld) failed", shm->addr, shm->size); 56 | BOOST_LOG_TRIVIAL(warning) << std::string(buf); 57 | return false; 58 | } 59 | return true; 60 | } 61 | 62 | 63 | #elif (USE_MMAP_DEVZERO) 64 | bool 65 | confd_shm_alloc(confd_shm_t *shm) 66 | { 67 | int fd; 68 | 69 | fd = open("/dev/zero", O_RDWR); 70 | if (fd < 0) { 71 | BOOST_LOG_TRIVIAL(error) << "open ipc_name(/dev/zero) failed, error: " << strerror(errno); 72 | return false; 73 | } 74 | shm->addr = (char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 75 | if (shm->addr == MAP_FAILED) { 76 | BOOST_LOG_TRIVIAL(error) << "mmap ipc_name(/dev/zero) failed, error: " << strerror(errno); 77 | return false; 78 | } 79 | if (close(fd) == -1) { 80 | BOOST_LOG_TRIVIAL(warning) << "close ipc_name(/dev/zero) failed, error: " << strerror(errno); 81 | } 82 | return true; 83 | } 84 | 85 | bool 86 | confd_shm_free(confd_shm_t *shm) 87 | { 88 | char buf[1024]; 89 | 90 | if (munmap((void *) shm->addr, shm->size) == -1) { 91 | sprintf(buf, "munmap(%p, %ld) failed", shm->addr, shm->size); 92 | BOOST_LOG_TRIVIAL(warning) << std::string(buf); 93 | return false; 94 | } 95 | return true; 96 | } 97 | 98 | #elif (USE_SHM) 99 | 100 | #include 101 | #include 102 | 103 | bool 104 | confd_shm_alloc(confd_shm_t *shm) 105 | { 106 | int id; 107 | 108 | id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT)); 109 | 110 | if (id == -1) { 111 | BOOST_LOG_TRIVIAL(warning) << "shmget(" << shm->size << ") failed."; 112 | return false; 113 | } 114 | 115 | BOOST_LOG_TRIVIAL(debug) << "shmget id=" << id ; 116 | 117 | shm->addr = (char*) shmat(id, NULL, 0); 118 | 119 | if (shm->addr == (void *) -1) { 120 | BOOST_LOG_TRIVIAL(warning) << "shmat() failed."; 121 | } 122 | 123 | if (shmctl(id, IPC_RMID, NULL) == -1) { 124 | BOOST_LOG_TRIVIAL(warning) << "shmctl(IPC_RMID) failed."; 125 | } 126 | 127 | return (shm->addr == (void *) -1) ? false : true; 128 | } 129 | 130 | 131 | bool 132 | confd_shm_free(confd_shm_t *shm) 133 | { 134 | char buf[1024]; 135 | 136 | if (shmdt(shm->addr) == -1) { 137 | sprintf(buf, "munmap(%p, %ld) failed", shm->addr, shm->size); 138 | BOOST_LOG_TRIVIAL(warning) << std::string(buf); 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | #endif 145 | -------------------------------------------------------------------------------- /src/util.cc: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | std::string 14 | ltrim(std::string &s) 15 | { 16 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { 17 | return !std::isspace(ch); 18 | })); 19 | return s; 20 | } 21 | 22 | std::string 23 | rtrim(std::string &s) 24 | { 25 | s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { 26 | return !std::isspace(ch); 27 | }).base(), s.end()); 28 | return s; 29 | } 30 | 31 | std::string 32 | trim(std::string &s) 33 | { 34 | ltrim(s); 35 | rtrim(s); 36 | return s; 37 | } 38 | 39 | 40 | std::string& 41 | replace_all(std::string& str, const std::string& old_value, const std::string& new_value) 42 | { 43 | while (true){ 44 | std::string::size_type pos(0); 45 | if (!((pos = str.find(old_value)) != std::string::npos)) { 46 | break; 47 | } 48 | str.replace(pos, old_value.length(), new_value); 49 | } 50 | return str; 51 | } 52 | 53 | size_t 54 | parse_bytes_number(const std::string& str) 55 | { 56 | typedef struct units_map { 57 | std::string unit; 58 | size_t size; 59 | } units_map_t; 60 | 61 | units_map_t units_map[] = { 62 | {"k", 1024}, 63 | {"kb", 1024}, 64 | {"m", 1024*1024}, 65 | {"mb", 1024*1024}, 66 | {"g", 1024*1024*1024}, 67 | {"gb", 1024*1024*1024}, 68 | {"t", 1024L*1024*1024*1024}, 69 | {"tb", 1024L*1024*1024*1024}, 70 | {"", 0L} 71 | }; 72 | 73 | units_map_t* unit_item; 74 | std::string str_num = boost::algorithm::trim_copy(str); 75 | size_t scale_factor = 1; 76 | for (unit_item = units_map; unit_item->unit != ""; unit_item++) { 77 | if (boost::algorithm::iends_with(str_num, unit_item->unit)) { 78 | str_num = str_num.substr(0, str_num.size() - unit_item->unit.size()); 79 | scale_factor = unit_item->size; 80 | break; 81 | } 82 | } 83 | char* pend = NULL; 84 | boost::algorithm::trim(str_num); 85 | size_t num = strtoul(str_num.c_str(), &pend, 0); 86 | if (pend == NULL || *pend != '\0') { 87 | throw std::invalid_argument("Invalid bytes number: " + str); 88 | } 89 | return num * scale_factor; 90 | } 91 | 92 | long 93 | get_meminfo_kv(const char* k) 94 | { 95 | long size = -1; 96 | std::string line; 97 | 98 | std::ifstream meminfo_file("/proc/meminfo"); 99 | if (!meminfo_file.is_open()) { 100 | return size; 101 | } 102 | boost::smatch what; 103 | 104 | while (std::getline(meminfo_file, line)) { 105 | if (!boost::algorithm::starts_with(line, k)) { 106 | continue; 107 | } 108 | boost::regex reg(":\\s+(\\d+)(.*)", boost::regex::perl|boost::regex::no_mod_s); 109 | if (boost::regex_search(line, what, reg)) { 110 | std::string size_str(what[1]); 111 | std::string unit(what[2]); 112 | size_str = trim(size_str) + boost::algorithm::to_lower_copy(trim(unit)); 113 | size = parse_bytes_number(size_str); 114 | } 115 | } 116 | meminfo_file.close(); 117 | return size; 118 | } 119 | 120 | long 121 | get_MemAvailable() 122 | { 123 | return get_meminfo_kv("MemAvailable"); 124 | } 125 | 126 | std::pair 127 | exec_cmd(const char* cmd) 128 | { 129 | char buf[10240] = {0}; 130 | std::string stdout_buffer; 131 | 132 | FILE* fp = popen(cmd, "r"); 133 | if (!fp) { 134 | return std::pair(false, std::string("popen(): ") + strerror(errno)); 135 | } 136 | while (!feof(fp)) { 137 | if (fgets(buf, sizeof(buf), fp) != NULL) { 138 | stdout_buffer += std::string(buf); 139 | } 140 | } 141 | 142 | /* while (waitpid(pid, &stat, 0) < 0) 143 | * if (errno != EINTR) 144 | * return(-1); 145 | * pclose 内部也会调用waitpid, master进程异步注册的SIGCHLD信号 146 | * 事件可能会早于期pclose触发,导致这里的waitpid返回errno(SIGCHLD) 147 | */ 148 | if (pclose(fp) == -1) { 149 | if (errno != ECHILD) { //No child processes 150 | return std::pair(false, std::string("pclose(): ") + strerror(errno)); 151 | } 152 | } 153 | 154 | return std::pair(true, stdout_buffer); 155 | } 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/confd.cc: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include "util.h" 3 | #include "config.h" 4 | #include "confd_shm.h" 5 | #include "confd_dict.h" 6 | #include "process_manage.h" 7 | #include "nginx_conf_parse.h" 8 | #include "json.h" 9 | 10 | namespace logging = boost::log; 11 | using namespace logging; 12 | 13 | std::vector children_process_group; 14 | std::unordered_map confd_config; 15 | 16 | void 17 | usage() 18 | { 19 | printf("Usage: confd [-h] -c config\n"); 20 | exit(0); 21 | } 22 | 23 | 24 | bool 25 | parse_config(unordered_map& confd_config, const char* config_file) 26 | { 27 | Json::Value config; 28 | 29 | if (!config_file) { 30 | printf("configuration files must be specified\n"); 31 | return false; 32 | } 33 | 34 | ifstream fin(config_file); 35 | if (!fin) { 36 | printf("open config file(%s) failed: %s\n", config_file, strerror(errno)); 37 | return false; 38 | } 39 | 40 | fin >> config; 41 | fin.close(); 42 | 43 | if (!config["pid_path"].isNull()) { 44 | confd_config["pid_path"] = config["pid_path"].asString(); 45 | } 46 | if (!config["addr"].isNull()) { 47 | confd_config["addr"] = config["addr"].asString(); 48 | } 49 | if (!config["port"].isNull()) { 50 | confd_config["port"] = config["port"].asString(); 51 | } 52 | if (!config["nginx_conf_path"].isNull()) { 53 | confd_config["nginx_conf_path"] = config["nginx_conf_path"].asString(); 54 | } 55 | if (!config["shm_size"].isNull()) { 56 | confd_config["shm_size"] = config["shm_size"].asString(); 57 | } 58 | if (!config["nginx_conf_writen_path"].isNull()) { 59 | confd_config["nginx_conf_writen_path"] = config["nginx_conf_writen_path"].asString(); 60 | } 61 | if (!config["nginx_bin_path"].isNull()) { 62 | confd_config["nginx_bin_path"] = config["nginx_bin_path"].asString(); 63 | } 64 | if (!config["log_path"].isNull()) { 65 | confd_config["log_path"] = config["log_path"].asString(); 66 | } 67 | if (!config["worker_process"].isNull()) { 68 | confd_config["worker_process"] = config["worker_process"].asString(); 69 | } 70 | if (!config["verbosity"].isNull()) { 71 | confd_config["verbosity"] = config["verbosity"].asString(); 72 | } 73 | if (!config["daemon"].isNull()) { 74 | confd_config["daemon"] = config["daemon"].asString(); 75 | } 76 | 77 | return true; 78 | } 79 | 80 | 81 | 82 | bool 83 | confd_daemon(void) 84 | { 85 | int fd; 86 | 87 | switch (fork()) { 88 | case -1: 89 | BOOST_LOG_TRIVIAL(error) << "fork failed."; 90 | return false; 91 | 92 | case 0: 93 | break; 94 | 95 | default: 96 | exit(0); 97 | } 98 | 99 | if (setsid() == -1) { 100 | BOOST_LOG_TRIVIAL(warning) << "setsid() failed."; 101 | return false; 102 | } 103 | 104 | umask(0); 105 | 106 | fd = open("/dev/null", O_RDWR); 107 | if (fd == -1) { 108 | BOOST_LOG_TRIVIAL(warning) << "open(\"/dev/null\") failed."; 109 | return false; 110 | } 111 | 112 | if (dup2(fd, STDIN_FILENO) == -1) { 113 | BOOST_LOG_TRIVIAL(warning) << "dup2(STDIN) failed."; 114 | return false; 115 | } 116 | 117 | if (dup2(fd, STDOUT_FILENO) == -1) { 118 | BOOST_LOG_TRIVIAL(warning) << "dup2(STDOUT) failed."; 119 | return false; 120 | } 121 | 122 | if (dup2(fd, STDERR_FILENO) == -1) { 123 | BOOST_LOG_TRIVIAL(warning) << "dup2(STDERR) failed."; 124 | return false; 125 | } 126 | 127 | if (fd > STDERR_FILENO) { 128 | if (close(fd) == -1) { 129 | BOOST_LOG_TRIVIAL(warning) << "close fd(" << fd << ") failed."; 130 | return false; 131 | } 132 | } 133 | 134 | return true; 135 | } 136 | 137 | 138 | 139 | int 140 | main(int argc, char *argv[]) 141 | { 142 | int i; 143 | const char *cmd = NULL; 144 | const char *config_file = NULL; 145 | confd_arg = { 146 | .argc = argc, 147 | .argv = argv 148 | }; 149 | 150 | while ((i = getopt(argc, argv, "hs:c:")) != -1) { 151 | switch(i){ 152 | case 'h': 153 | usage(); 154 | return -1; 155 | case 's': 156 | cmd = optarg; 157 | break; 158 | case 'c': 159 | config_file = optarg; 160 | break; 161 | default: 162 | break; 163 | } 164 | } 165 | 166 | //parse config 167 | if (!parse_config(confd_config, config_file)) { 168 | exit(-1); 169 | }; 170 | 171 | 172 | //confd opt cmd 173 | if (cmd != NULL && *cmd != '\0') { 174 | notify_master_process(confd_config["pid_path"].c_str(), cmd); 175 | } 176 | 177 | //初始化log 178 | try { 179 | init_log(stoll(confd_config["verbosity"]), confd_config["log_path"]); 180 | } catch (std::exception& e) { 181 | printf("init log failed error: %s", e.what()); 182 | } 183 | 184 | //daemonize? 185 | if (confd_config["daemon"] == "true") { 186 | if (confd_daemon()) { 187 | BOOST_LOG_TRIVIAL(debug) << "daemonize successufl."; 188 | } else { 189 | BOOST_LOG_TRIVIAL(warning) << "daemonize failed."; 190 | } 191 | } 192 | 193 | //初始化主进程 194 | if (!init_master_process(confd_config)) { 195 | exit(-1); 196 | } 197 | 198 | //fork worker process 199 | for (int i = 0; i < stoll(confd_config["worker_process"]); i++) { 200 | std::string name = std::string("worker") + std::to_string(i); 201 | pid_t pid = spawn_worker_process(name); 202 | if (pid == -1) { 203 | BOOST_LOG_TRIVIAL(warning) << "spawn worker process failed"; 204 | continue; 205 | } 206 | process_t new_process = { 207 | .name = name, 208 | .pid = pid, 209 | .restart = 1 210 | }; 211 | children_process_group.push_back(new_process); 212 | } 213 | 214 | 215 | //master process will sleep util received signal 216 | while (true) { 217 | sleep(-1); 218 | } 219 | return 0; 220 | } 221 | -------------------------------------------------------------------------------- /src/confd_dict.cc: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include "confd_dict.h" 3 | #include "confd_shm.h" 4 | #include "confd_shmtx.h" 5 | #include "json.h" 6 | 7 | extern confd_shm_t *shm; 8 | extern confd_shmtx_t *shmtx; 9 | extern confd_shm_t *update; 10 | extern confd_shmtx_t *updatetx; 11 | extern std::unordered_map confd_config; 12 | 13 | std::pair 14 | confd_dict::add_item(std::string listen_port, std::string domain, 15 | std::vector value, std::string tmp_style, bool force, HEALTH_CHECK type) 16 | { 17 | std::string key = domain + "_" + listen_port; 18 | std::string file_path(confd_config["nginx_conf_writen_path"] + key + ".conf"); 19 | 20 | if (force) { //强制覆盖 21 | bool ret = nginx_opt::backup_single_conf(file_path); 22 | if (!ret) { 23 | BOOST_LOG_TRIVIAL(debug) << "backup file " << file_path << " failed."; 24 | return std::pair(false, "backup file failed."); 25 | } 26 | BOOST_LOG_TRIVIAL(debug) << "backup file " << file_path << " successful."; 27 | } else { 28 | if (this->ss.count(key) > 0 ) { 29 | std::string error = "domain(" + domain + ") listen port("+listen_port+") is exists."; 30 | return std::pair(false, error); 31 | } 32 | } 33 | std::pair ret = nginx_opt::sync_to_disk(listen_port, domain, value, tmp_style, \ 34 | confd_config["nginx_conf_writen_path"], confd_config["nginx_bin_path"], \ 35 | confd_config["nginx_conf_path"], type); 36 | if (!ret.first) { 37 | if (force) { 38 | bool ret = nginx_opt::rollback_single_conf(file_path); 39 | if (!ret) { 40 | BOOST_LOG_TRIVIAL(debug) << "rollback file " << file_path << " failed."; 41 | return std::pair(false, "rollback file failed."); 42 | } 43 | BOOST_LOG_TRIVIAL(debug) << "rollback file " << file_path << " successful."; 44 | } 45 | } else { 46 | this->ss[key] = value; 47 | this->dict_sync_to_shm(); 48 | this->update_status(true); 49 | } 50 | 51 | return ret; 52 | } 53 | 54 | bool 55 | confd_dict::status(u_int solt) 56 | { 57 | if (solt > update->size) { 58 | BOOST_LOG_TRIVIAL(warning) << "status(" << solt << ") solt is large than " << update->size; 59 | return false; 60 | } 61 | bool status; 62 | lock(updatetx); 63 | status = (bool)(*((update->addr)+solt)); 64 | unlock(updatetx); 65 | 66 | return status; 67 | } 68 | 69 | void 70 | confd_dict::update_status(bool status) 71 | { 72 | lock(updatetx); 73 | memset(update->addr, status, update->size); 74 | unlock(updatetx); 75 | } 76 | 77 | bool 78 | confd_dict::update_status(bool status, u_int solt) 79 | { 80 | if (solt > update->size) { 81 | return false; 82 | BOOST_LOG_TRIVIAL(warning) << "update_status(" << status << "," << solt << ") solt is large than " << update->size; 83 | } 84 | lock(updatetx); 85 | *((update->addr)+solt) = status; 86 | unlock(updatetx); 87 | 88 | return true; 89 | } 90 | 91 | std::pair 92 | confd_dict::delete_key(std::string key) 93 | { 94 | if (this->ss.count(key) == 0 ) { 95 | return std::pair(false, "domain(" + key + ") doesn't exists."); 96 | } 97 | std::pair ret = nginx_opt::delete_conf(key, confd_config["nginx_conf_writen_path"]); 98 | if (!ret.first) { 99 | BOOST_LOG_TRIVIAL(info) << ret.second; 100 | return ret; 101 | } 102 | if (this->ss.erase(key) <= 0) { //删除key 103 | BOOST_LOG_TRIVIAL(info) << "delete dict key(" + key + ") failed."; 104 | return std::pair(false, "delete dict key(" + key + ") failed."); 105 | } 106 | this->dict_sync_to_shm(); //同步到shm内存 107 | this->update_status(true); 108 | BOOST_LOG_TRIVIAL(info) << ret.second; 109 | BOOST_LOG_TRIVIAL(info) << "delete domain(" << key << ") successful."; 110 | return std::pair(true, "delete successful."); 111 | } 112 | 113 | bool 114 | confd_dict::shm_sync_to_dict() 115 | { 116 | Json::CharReaderBuilder builder; 117 | Json::CharReader* reader = builder.newCharReader(); 118 | Json::Value json_root; 119 | std::string errors; 120 | 121 | size_t str_count = strlen(shm->addr); 122 | char* data_cloned = (char*)malloc(str_count); 123 | lock(shmtx); 124 | strncpy(data_cloned, shm->addr, str_count); 125 | unlock(shmtx); 126 | 127 | if (reader->parse(data_cloned, data_cloned + str_count, &json_root, &errors)) { 128 | Json::Value::Members members; 129 | members = json_root.getMemberNames(); 130 | for (Json::Value::Members::iterator iterMember = members.begin(); iterMember != members.end(); iterMember++){ 131 | std::string key(*iterMember); 132 | Json::Value json_value(json_root[key]); 133 | vector vcs; 134 | for(u_int j = 0; j < json_value.size(); j++) { 135 | vcs.push_back(json_value[j].asString()); 136 | } 137 | this->ss[key] = vcs; 138 | } 139 | } else { 140 | BOOST_LOG_TRIVIAL(warning) << "Parse json str failed error: " << errors; 141 | } 142 | 143 | free(data_cloned); 144 | delete reader; 145 | 146 | return true; 147 | } 148 | 149 | 150 | bool 151 | confd_dict::dict_sync_to_shm() 152 | { 153 | string new_data = this->json_stringify(); 154 | lock(shmtx); 155 | strcpy(shm->addr, new_data.c_str()); 156 | unlock(shmtx); 157 | return true; 158 | } 159 | 160 | std::string 161 | confd_dict::json_stringify() 162 | { 163 | Json::Value root; 164 | for (auto &iter: this->ss) { 165 | vector& vcs(iter.second); 166 | for (auto &it: vcs) { 167 | root[iter.first].append(it); 168 | } 169 | } 170 | return root.toStyledString(); 171 | } 172 | 173 | std::pair> 174 | confd_dict::get_value_by_key(std::string key) 175 | { 176 | if (this->ss.count(key) > 0 ) { 177 | return std::pair>(true, this->ss[key]); 178 | } 179 | return std::pair>(false, std::vector()); 180 | } 181 | -------------------------------------------------------------------------------- /lib/httpd/src/include/status_code.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_WEB_STATUS_CODE_HPP 2 | #define SIMPLE_WEB_STATUS_CODE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace SimpleWeb { 10 | enum class StatusCode { 11 | unknown = 0, 12 | information_continue = 100, 13 | information_switching_protocols, 14 | information_processing, 15 | success_ok = 200, 16 | success_created, 17 | success_accepted, 18 | success_non_authoritative_information, 19 | success_no_content, 20 | success_reset_content, 21 | success_partial_content, 22 | success_multi_status, 23 | success_already_reported, 24 | success_im_used = 226, 25 | redirection_multiple_choices = 300, 26 | redirection_moved_permanently, 27 | redirection_found, 28 | redirection_see_other, 29 | redirection_not_modified, 30 | redirection_use_proxy, 31 | redirection_switch_proxy, 32 | redirection_temporary_redirect, 33 | redirection_permanent_redirect, 34 | client_error_bad_request = 400, 35 | client_error_unauthorized, 36 | client_error_payment_required, 37 | client_error_forbidden, 38 | client_error_not_found, 39 | client_error_method_not_allowed, 40 | client_error_not_acceptable, 41 | client_error_proxy_authentication_required, 42 | client_error_request_timeout, 43 | client_error_conflict, 44 | client_error_gone, 45 | client_error_length_required, 46 | client_error_precondition_failed, 47 | client_error_payload_too_large, 48 | client_error_uri_too_long, 49 | client_error_unsupported_media_type, 50 | client_error_range_not_satisfiable, 51 | client_error_expectation_failed, 52 | client_error_im_a_teapot, 53 | client_error_misdirection_required = 421, 54 | client_error_unprocessable_entity, 55 | client_error_locked, 56 | client_error_failed_dependency, 57 | client_error_upgrade_required = 426, 58 | client_error_precondition_required = 428, 59 | client_error_too_many_requests, 60 | client_error_request_header_fields_too_large = 431, 61 | client_error_unavailable_for_legal_reasons = 451, 62 | server_error_internal_server_error = 500, 63 | server_error_not_implemented, 64 | server_error_bad_gateway, 65 | server_error_service_unavailable, 66 | server_error_gateway_timeout, 67 | server_error_http_version_not_supported, 68 | server_error_variant_also_negotiates, 69 | server_error_insufficient_storage, 70 | server_error_loop_detected, 71 | server_error_not_extended = 510, 72 | server_error_network_authentication_required 73 | }; 74 | 75 | inline const std::map &status_code_strings() { 76 | static const std::map status_code_strings = { 77 | {StatusCode::unknown, ""}, 78 | {StatusCode::information_continue, "100 Continue"}, 79 | {StatusCode::information_switching_protocols, "101 Switching Protocols"}, 80 | {StatusCode::information_processing, "102 Processing"}, 81 | {StatusCode::success_ok, "200 OK"}, 82 | {StatusCode::success_created, "201 Created"}, 83 | {StatusCode::success_accepted, "202 Accepted"}, 84 | {StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"}, 85 | {StatusCode::success_no_content, "204 No Content"}, 86 | {StatusCode::success_reset_content, "205 Reset Content"}, 87 | {StatusCode::success_partial_content, "206 Partial Content"}, 88 | {StatusCode::success_multi_status, "207 Multi-Status"}, 89 | {StatusCode::success_already_reported, "208 Already Reported"}, 90 | {StatusCode::success_im_used, "226 IM Used"}, 91 | {StatusCode::redirection_multiple_choices, "300 Multiple Choices"}, 92 | {StatusCode::redirection_moved_permanently, "301 Moved Permanently"}, 93 | {StatusCode::redirection_found, "302 Found"}, 94 | {StatusCode::redirection_see_other, "303 See Other"}, 95 | {StatusCode::redirection_not_modified, "304 Not Modified"}, 96 | {StatusCode::redirection_use_proxy, "305 Use Proxy"}, 97 | {StatusCode::redirection_switch_proxy, "306 Switch Proxy"}, 98 | {StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"}, 99 | {StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"}, 100 | {StatusCode::client_error_bad_request, "400 Bad Request"}, 101 | {StatusCode::client_error_unauthorized, "401 Unauthorized"}, 102 | {StatusCode::client_error_payment_required, "402 Payment Required"}, 103 | {StatusCode::client_error_forbidden, "403 Forbidden"}, 104 | {StatusCode::client_error_not_found, "404 Not Found"}, 105 | {StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"}, 106 | {StatusCode::client_error_not_acceptable, "406 Not Acceptable"}, 107 | {StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"}, 108 | {StatusCode::client_error_request_timeout, "408 Request Timeout"}, 109 | {StatusCode::client_error_conflict, "409 Conflict"}, 110 | {StatusCode::client_error_gone, "410 Gone"}, 111 | {StatusCode::client_error_length_required, "411 Length Required"}, 112 | {StatusCode::client_error_precondition_failed, "412 Precondition Failed"}, 113 | {StatusCode::client_error_payload_too_large, "413 Payload Too Large"}, 114 | {StatusCode::client_error_uri_too_long, "414 URI Too Long"}, 115 | {StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"}, 116 | {StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"}, 117 | {StatusCode::client_error_expectation_failed, "417 Expectation Failed"}, 118 | {StatusCode::client_error_im_a_teapot, "418 I'm a teapot"}, 119 | {StatusCode::client_error_misdirection_required, "421 Misdirected Request"}, 120 | {StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"}, 121 | {StatusCode::client_error_locked, "423 Locked"}, 122 | {StatusCode::client_error_failed_dependency, "424 Failed Dependency"}, 123 | {StatusCode::client_error_upgrade_required, "426 Upgrade Required"}, 124 | {StatusCode::client_error_precondition_required, "428 Precondition Required"}, 125 | {StatusCode::client_error_too_many_requests, "429 Too Many Requests"}, 126 | {StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"}, 127 | {StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"}, 128 | {StatusCode::server_error_internal_server_error, "500 Internal Server Error"}, 129 | {StatusCode::server_error_not_implemented, "501 Not Implemented"}, 130 | {StatusCode::server_error_bad_gateway, "502 Bad Gateway"}, 131 | {StatusCode::server_error_service_unavailable, "503 Service Unavailable"}, 132 | {StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"}, 133 | {StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"}, 134 | {StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"}, 135 | {StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"}, 136 | {StatusCode::server_error_loop_detected, "508 Loop Detected"}, 137 | {StatusCode::server_error_not_extended, "510 Not Extended"}, 138 | {StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}}; 139 | return status_code_strings; 140 | } 141 | 142 | inline StatusCode status_code(const std::string &status_code_string) noexcept { 143 | class StringToStatusCode : public std::unordered_map { 144 | public: 145 | StringToStatusCode() { 146 | for(auto &status_code : status_code_strings()) 147 | emplace(status_code.second, status_code.first); 148 | } 149 | }; 150 | static StringToStatusCode string_to_status_code; 151 | auto pos = string_to_status_code.find(status_code_string); 152 | if(pos == string_to_status_code.end()) 153 | return StatusCode::unknown; 154 | return pos->second; 155 | } 156 | 157 | inline const std::string &status_code(StatusCode status_code_enum) noexcept { 158 | auto pos = status_code_strings().find(status_code_enum); 159 | if(pos == status_code_strings().end()) { 160 | static std::string empty_string; 161 | return empty_string; 162 | } 163 | return pos->second; 164 | } 165 | } // namespace SimpleWeb 166 | 167 | #endif // SIMPLE_WEB_STATUS_CODE_HPP 168 | -------------------------------------------------------------------------------- /src/nginx_conf_parse.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGINX_CONF_PARSE_H 2 | #define _NGINX_CONF_PARSE_H 3 | 4 | #include "util.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace boost; 16 | using namespace std; 17 | 18 | //void parse(std::string& nginx_conf); 19 | class nginxConfParse { 20 | private: 21 | std::string content; 22 | filesystem::path nginx_conf_path; 23 | public: 24 | nginxConfParse(){}; 25 | nginxConfParse(const char* nginx_conf_path){ 26 | this->nginx_conf_path = filesystem::path(nginx_conf_path); 27 | } 28 | 29 | std::unordered_map> parse(const char* nginx_conf_path) { 30 | std::unordered_map> ss; 31 | 32 | this->nginx_conf_path = filesystem::path(nginx_conf_path); 33 | this->content = this->load_conf_content(this->nginx_conf_path.string()); 34 | this->content = this->remove_comment_and_space_line(this->content); 35 | this->load_include_conf(); 36 | unordered_map> upstreams = this->extract_upstream_struct(this->content); 37 | unordered_map servers = this->extract_server_struct(this->content); 38 | for (auto& server: servers) { 39 | string proxy_pass(server.second); 40 | if (upstreams.count(proxy_pass) > 0) { 41 | ss[server.first] = upstreams[proxy_pass]; 42 | } else { 43 | ss[server.first] = vector(1, server.second); 44 | } 45 | } 46 | return ss; 47 | } 48 | 49 | 50 | 51 | private: 52 | unordered_map> extract_upstream_struct(const std::string& content) { 53 | unordered_map> upstreams; 54 | regex e("upstream\\s+(.*?)\\{(.*?)\\}", boost::regex::perl|boost::regex::mod_s); 55 | boost::sregex_iterator end; 56 | boost::sregex_iterator iter(content.begin(), content.end(), e); 57 | for (; iter != end; iter++) { 58 | string upstream_name((*iter)[1]); 59 | string upstream_servers((*iter)[2]); 60 | regex e2("server\\s+(.*?)(\\s+|;)", boost::regex::perl|boost::regex::no_mod_s); 61 | boost::sregex_iterator end2; 62 | boost::sregex_iterator iter2(upstream_servers.begin(), upstream_servers.end(), e2); 63 | vector vtmp; 64 | for (; iter2 != end2; iter2++) { 65 | string s((*iter2)[1]); 66 | vtmp.push_back(trim(s)); 67 | 68 | } 69 | upstreams[trim(upstream_name)] = vtmp; 70 | } 71 | 72 | return upstreams; 73 | } 74 | 75 | unordered_map extract_server_struct(const std::string& content) { 76 | unordered_map servers; 77 | 78 | boost::sregex_iterator end; 79 | 80 | regex e("server\\s*?\\{.*?\\}", boost::regex::perl|boost::regex::mod_s); 81 | boost::sregex_iterator iter(content.begin(), content.end(), e); 82 | for (; iter != end; iter++) { 83 | string server((*iter)[0]); 84 | boost::smatch listen_what; 85 | boost::smatch server_name_what; 86 | boost::smatch proxy_pass_what; 87 | regex listen_e("^\\s*?listen\\s+(.*?);", boost::regex::perl|boost::regex::no_mod_s); 88 | regex server_name_e("^\\s*server_name\\s+(.*?);", boost::regex::perl|boost::regex::no_mod_s); 89 | regex proxy_pass_e("location\\s+/\\s+\\{.*?proxy_pass(.*?);.*?\\}", boost::regex::perl|boost::regex::mod_s); 90 | 91 | std::string listen, server_name, proxy_pass; 92 | if (!boost::regex_search(server, listen_what, listen_e)) { /*没有搜索到listen line*/ 93 | continue; 94 | } 95 | listen = string(listen_what[1]); 96 | listen = trim(listen); 97 | 98 | if (!boost::regex_search(server, server_name_what, server_name_e)) { /*没有搜索到server_name line*/ 99 | continue; 100 | } 101 | server_name = string(server_name_what[1]); 102 | server_name = trim(server_name); 103 | 104 | if (!boost::regex_search(server, proxy_pass_what, proxy_pass_e)) { 105 | continue; 106 | } 107 | proxy_pass = string(proxy_pass_what[1]); 108 | proxy_pass = this->remove_protocol(trim(proxy_pass)); 109 | proxy_pass = this->variable_replace(proxy_pass, server); //proxy_pass maybe variable 110 | 111 | //multi value server_name 112 | std::vector strs; 113 | boost::split(strs, server_name, boost::is_any_of(" ")); 114 | for (size_t i = 0; i < strs.size(); i++) { 115 | std::string ele = trim(strs[i]); 116 | if (ele.empty()) { /*跳过空元素*/ 117 | continue; 118 | } 119 | std::vector listens; 120 | boost::split(listens, listen, boost::is_any_of(" ")); 121 | for (size_t i = 0; i < listens.size(); i++) { 122 | string port = trim(listens[i]); 123 | if (port.empty() || is_nginx_listen_opt(port)) { /*如果端口是一些特殊flag,比如http2,则跳过*/ 124 | continue; 125 | } 126 | string key = ele + "_" + port; 127 | servers[key] = proxy_pass; 128 | } 129 | } 130 | } 131 | 132 | return servers; 133 | } 134 | 135 | std::string variable_replace(std::string& v, std::string& ctx) { 136 | if (v.substr(0, 1) != "$") { //v is not a variable 137 | return v; 138 | } 139 | 140 | boost::sregex_iterator end; 141 | regex set_e("^\\s*?set\\s+(\\$.*?)\\s+(.*?);", boost::regex::perl|boost::regex::no_mod_s); 142 | boost::sregex_iterator iter(ctx.begin(), ctx.end(), set_e); 143 | for (; iter != end; iter++) { 144 | std::string variable_name((*iter)[1]); 145 | std::string variable_value((*iter)[2]); 146 | if (variable_name == v) { 147 | return variable_value; 148 | } 149 | } 150 | 151 | return v; 152 | } 153 | 154 | bool is_nginx_listen_opt(std::string port) { 155 | static const std::string listen_opt[] = {"default_server", "proxy_protocol", "ssl", "http2", "reuseport"}; 156 | static const size_t listen_opt_count = sizeof(listen_opt)/sizeof(listen_opt[0]); 157 | for (size_t i = 0; i < listen_opt_count; i++) { 158 | if (listen_opt[i] == port) { 159 | return true; 160 | } 161 | } 162 | return false; 163 | } 164 | 165 | std::string remove_protocol(std::string str) { 166 | std::size_t pos = str.find_first_of(":"); 167 | if (pos != std::string::npos) { 168 | return str.substr(pos+strlen("://")); 169 | } 170 | return str; 171 | } 172 | 173 | void load_include_conf(){ 174 | boost::sregex_iterator end; 175 | unordered_map include; 176 | 177 | regex e("^(\\s*?include\\s+(.*?)\\*(\\.conf);)", boost::regex::perl|boost::regex::no_mod_s); 178 | boost::sregex_iterator iter(this->content.begin(), this->content.end(), e); 179 | for (; iter != end; iter++) { 180 | std::string path((*iter)[2]); 181 | if (path.c_str()[0] != '/') { 182 | path = this->nginx_conf_path.parent_path().string() + "/" + path; 183 | } 184 | if (!filesystem::is_directory(path)) { 185 | continue; 186 | } 187 | filesystem::recursive_directory_iterator beg_iter, end_iter; 188 | string all_content; 189 | for (beg_iter = filesystem::recursive_directory_iterator(path); beg_iter != end_iter; ++beg_iter) { 190 | if (filesystem::is_directory(*beg_iter)) { 191 | continue; 192 | } 193 | if (beg_iter->path().extension().string() == (*iter)[3]) { 194 | std::string str_path = beg_iter->path().string(); 195 | string content = load_conf_content(str_path); 196 | all_content = all_content + content; 197 | } 198 | } 199 | all_content = this->remove_comment_and_space_line(all_content); 200 | include[(*iter)[1]] = all_content; 201 | } 202 | for(auto& iter: include) { 203 | this->content = replace_all(this->content, iter.first, iter.second); 204 | } 205 | } 206 | 207 | string load_conf_content(string path){ 208 | ifstream ifs; 209 | ifs.open(path); 210 | if (!ifs) { 211 | return string(""); 212 | } 213 | 214 | ostringstream os; 215 | os << ifs.rdbuf(); 216 | ifs.close(); 217 | return os.str(); 218 | } 219 | 220 | string remove_comment_and_space_line(string& content){ 221 | regex e("^\\s*?#.*?\\n|^\\s*?\\n", boost::regex::perl|boost::regex::no_mod_s); 222 | return regex_replace(content, e, ""); 223 | } 224 | 225 | }; 226 | 227 | #endif 228 | -------------------------------------------------------------------------------- /src/nginx_opt.cc: -------------------------------------------------------------------------------- 1 | #include "nginx_opt.h" 2 | #include "util.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace logging = boost::log; 14 | using namespace logging; 15 | 16 | const char* nginx_opt::upstream_struct_start = "upstream %s {\n"; 17 | const char* nginx_opt::upstream_struct_server = " server %s;\n"; 18 | const char* nginx_opt::upstream_struct_end = "}\n"; 19 | const char* nginx_opt::nginx_test_ok = "nginx: the configuration file %s syntax is ok\n" 20 | "nginx: configuration file %s test is successful\n"; 21 | const char* nginx_opt::nginx_http_health_check = " check interval=10000 rise=2 fall=5 timeout=2000 type=http;\n" 22 | " check_http_send \"HEAD / HTTP/1.0\\r\\n\\r\\n\";\n" 23 | " check_http_expect_alive http_2xx http_3xx;\n"; 24 | const char* nginx_opt::nginx_tcp_health_check = " check interval=10000 rise=2 fall=5 timeout=2000 type=tcp;\n"; 25 | health_check_map_t health_check_map[] = { 26 | {TCP, nginx_opt::nginx_tcp_health_check}, 27 | {HTTP, nginx_opt::nginx_http_health_check}, 28 | {NONE, ""} 29 | }; 30 | 31 | 32 | std::pair 33 | nginx_opt::nginx_conf_test(const char* nginx_bin_path, const char* nginx_conf_path) 34 | { 35 | char cmd[1024] = {0}; 36 | char test_ok[1024] = {0}; 37 | 38 | sprintf(test_ok, nginx_opt::nginx_test_ok, nginx_conf_path, nginx_conf_path); 39 | sprintf(cmd, "%s -c %s -t 2>&1", nginx_bin_path, nginx_conf_path); 40 | 41 | std::pair ret = exec_cmd(cmd); 42 | if (!ret.first) { 43 | return ret; 44 | } 45 | std::string& buffer = ret.second; 46 | if (!strcmp(test_ok, buffer.c_str())) { 47 | return std::pair(true, ""); 48 | } 49 | return std::pair(false, buffer); 50 | } 51 | 52 | std::pair 53 | nginx_opt::nginx_conf_reload(const char* nginx_bin_path, const char* nginx_conf_path) 54 | { 55 | char cmd[1024] = {0}; 56 | 57 | sprintf(cmd, "%s -c %s -s reload 2>&1", nginx_bin_path, nginx_conf_path); 58 | std::pair ret = exec_cmd(cmd); 59 | if (!ret.first) { 60 | return ret; 61 | } 62 | std::string& buffer = ret.second; 63 | if (buffer.empty()) { 64 | return std::pair(true, ""); 65 | } 66 | return std::pair(false, buffer); 67 | } 68 | 69 | std::pair 70 | nginx_opt::nginx_conf_graceful_reload(const char* nginx_bin_path, const char* nginx_conf_path) 71 | { 72 | size_t used_memsum = 0; 73 | 74 | std::pair memsum = nginx_opt::nginx_worker_used_memsum(); 75 | if (!memsum.first) { 76 | return memsum; 77 | } else { 78 | used_memsum += parse_bytes_number(memsum.second); 79 | } 80 | long MemAvailable = get_MemAvailable(); 81 | if (MemAvailable == -1L) { 82 | return std::pair(false, "get key='MemAvailable' from /proc/meminfo failed."); 83 | } 84 | if (MemAvailable >= 0 && (size_t)MemAvailable < used_memsum) { 85 | return std::pair(false, "not enough memory."); 86 | } 87 | return nginx_opt::nginx_conf_reload(nginx_bin_path, nginx_conf_path); 88 | } 89 | 90 | std::string 91 | nginx_opt::gen_upstream(std::string listen_port, std::string key, std::vector value, HEALTH_CHECK type) 92 | { 93 | char upstream[10240]; 94 | char line_server[1024]; 95 | std::string upstream_name = key+"_"+listen_port; 96 | 97 | std::string servers, upstream_format; 98 | for (auto& v: value) { 99 | sprintf(line_server, nginx_opt::upstream_struct_server, v.c_str()); 100 | servers += std::string(line_server); 101 | } 102 | health_check_map_t *health_check_item; 103 | std::string health_check_content(""); 104 | for (health_check_item = health_check_map; health_check_item->type != NONE; health_check_item++) { 105 | if (type == health_check_item->type) { 106 | health_check_content = health_check_item->content; 107 | break; 108 | } 109 | } 110 | upstream_format += std::string(nginx_opt::upstream_struct_start) + \ 111 | health_check_content + std::string(servers) + std::string(nginx_opt::upstream_struct_end); 112 | sprintf(upstream, upstream_format.c_str(), upstream_name.c_str()); 113 | return std::string(upstream); 114 | } 115 | 116 | std::string 117 | nginx_opt::gen_server(std::string listen_port, std::string server_name, std::vector value, std::string tmp_style) 118 | { 119 | std::ifstream ifs; 120 | const char *conf_extension = ".conf"; 121 | 122 | std::string filename = "nginx_" + tmp_style + conf_extension; 123 | boost::filesystem::path file_path= boost::filesystem::path(filename); 124 | if (!boost::filesystem::exists(file_path) || \ 125 | !boost::filesystem::is_regular_file(file_path) || \ 126 | boost::filesystem::extension(file_path) != conf_extension) { 127 | return ""; 128 | } 129 | ifs.open(filename); 130 | if (!ifs) { 131 | return ""; 132 | } 133 | std::ostringstream os; 134 | os << ifs.rdbuf(); 135 | 136 | 137 | std::string nginx_conf_template = std::string(os.str()); 138 | return nginx_opt::template_replace(listen_port, server_name, nginx_conf_template); 139 | } 140 | 141 | std::string 142 | nginx_opt::template_replace(const std::string& listen_port, const std::string& server_name, std::string nginx_conf_template) 143 | { 144 | char buf[10240]; 145 | 146 | std::string upstream_name = server_name + "_" + listen_port; 147 | 148 | std::unordered_map replace_value{ 149 | {"%{listen_port}", listen_port}, 150 | {"%{server_name}", server_name}, 151 | {"%{upstream_name}", upstream_name} 152 | }; 153 | for (auto& item: replace_value) { 154 | while (true) { 155 | std::string copyed = boost::replace_first_copy(nginx_conf_template, item.first, "%s"); 156 | if (copyed == nginx_conf_template) { 157 | break; 158 | } 159 | memset(buf, '\0', 10240); 160 | nginx_conf_template = copyed; 161 | sprintf(buf, nginx_conf_template.c_str(), item.second.c_str()); 162 | nginx_conf_template = std::string(buf); 163 | } 164 | } 165 | 166 | return nginx_conf_template; 167 | } 168 | 169 | std::pair 170 | nginx_opt::delete_conf(std::string& key, std::string& nginx_conf_writen_path) 171 | { 172 | const char *conf_extension = ".conf"; 173 | std::string filepath = nginx_conf_writen_path + key + conf_extension; 174 | if (!boost::filesystem::exists(filepath)) { 175 | return std::pair(false, "unlink filepath("+filepath+") failed, filepath is not exist."); 176 | } 177 | unlink(filepath.c_str()); 178 | return std::pair(true, "unlink filepath("+filepath+") successful."); 179 | } 180 | 181 | std::pair 182 | nginx_opt::sync_to_disk(std::string port, std::string key, std::vector value, 183 | std::string tmp_style, std::string nginx_conf_writen_path, std::string nginx_bin_path, 184 | std::string nginx_conf_path, HEALTH_CHECK type) 185 | { 186 | std::string upstream = nginx_opt::gen_upstream(port, key, value, type); 187 | if (upstream.empty()) { 188 | return std::pair(false, ""); 189 | } 190 | std::string server = nginx_opt::gen_server(port, key, value, tmp_style); 191 | if (server.empty()) { 192 | return std::pair(false, "nginx conf template error."); 193 | } 194 | std::ofstream ofs; 195 | char filepath[10240]; 196 | sprintf(filepath, "%s%s_%s.conf", nginx_conf_writen_path.c_str(), key.c_str(), port.c_str()); 197 | ofs.open(filepath); 198 | if (!ofs) { 199 | std::stringstream ss; 200 | ss << "Open file(" << filepath << ") failed."; 201 | BOOST_LOG_TRIVIAL(error) << ss.str(); 202 | return std::pair(false, ss.str()); 203 | } 204 | ofs << upstream << server; 205 | ofs.close(); 206 | auto ret = nginx_opt::nginx_conf_test(nginx_bin_path.c_str(), nginx_conf_path.c_str()); 207 | if(!ret.first) { 208 | BOOST_LOG_TRIVIAL(info) << ret.second << upstream << server; 209 | unlink(filepath); 210 | return ret; 211 | } 212 | ret = nginx_opt::nginx_conf_graceful_reload(nginx_bin_path.c_str(), nginx_conf_path.c_str()); 213 | if(!ret.first) { 214 | unlink(filepath); 215 | return ret; 216 | } 217 | return std::pair(true, "add successful."); 218 | } 219 | 220 | std::pair 221 | nginx_opt::nginx_worker_used_memsum() 222 | { 223 | const char* cmd = "ps aux | grep 'nginx: worker process' | grep -v 'shutdown' | " 224 | "grep -v 'grep'| awk 'BEGIN{total=0}{total+=$6}END{printf 'total'}'"; 225 | std::pair ret = exec_cmd(cmd); 226 | if (!ret.first) { 227 | return ret; 228 | } 229 | 230 | ret.second += "kb"; //patch unit kb 231 | return ret; 232 | } 233 | 234 | std::pair 235 | nginx_opt::nginx_shutting_worker_count(void) 236 | { 237 | const char* cmd = "ps aux | grep nginx | grep shutting | grep -v 'grep' | wc -l"; 238 | return exec_cmd(cmd); 239 | } 240 | 241 | std::pair 242 | nginx_opt::nginx_process_used_memsum(void) 243 | { 244 | const char* cmd = "ps aux | grep 'nginx:' | grep -v 'grep'| awk 'BEGIN{total=0}{total+=$6}END{printf 'total'}'"; 245 | std::pair ret = exec_cmd(cmd); 246 | if (!ret.first) { 247 | return ret; 248 | } 249 | ret.second = std::to_string(std::stoll(ret.second) / 1024); 250 | ret.second += "Mb"; //patch unit kb 251 | return ret; 252 | } 253 | 254 | HEALTH_CHECK int_to_health_check_type(int value) 255 | { 256 | HEALTH_CHECK type; 257 | 258 | switch (value) { 259 | 260 | case 0: 261 | type = NONE; 262 | break; 263 | case 1: 264 | type = TCP; 265 | break; 266 | case 2: 267 | type = HTTP; 268 | break; 269 | default: 270 | type = NONE; 271 | break; 272 | } 273 | 274 | return type; 275 | } 276 | 277 | bool 278 | nginx_opt::backup_single_conf(std::string& file_path) 279 | { 280 | int ret; 281 | 282 | if (!boost::filesystem::exists(file_path) || !boost::filesystem::is_regular_file(file_path)) { 283 | return true; 284 | } 285 | 286 | ret = rename(file_path.c_str(), (file_path+".bak").c_str()); 287 | if (ret == -1) { 288 | return false; 289 | } 290 | 291 | return true; 292 | } 293 | 294 | bool 295 | nginx_opt::rollback_single_conf(std::string& file_path) 296 | { 297 | int ret; 298 | 299 | std::string back_conf(file_path+".bak"); 300 | if (!boost::filesystem::exists(back_conf) || !boost::filesystem::is_regular_file(back_conf)) { 301 | return true; 302 | } 303 | ret = rename(back_conf.c_str(), file_path.c_str()); 304 | if (ret == -1) { 305 | return false; 306 | } 307 | return true; 308 | } 309 | -------------------------------------------------------------------------------- /lib/jsoncpp/src/json/json-forwards.h: -------------------------------------------------------------------------------- 1 | /// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/). 2 | /// It is intended to be used with #include "json/json-forwards.h" 3 | /// This header provides forward declaration for all JsonCpp types. 4 | 5 | // ////////////////////////////////////////////////////////////////////// 6 | // Beginning of content of file: LICENSE 7 | // ////////////////////////////////////////////////////////////////////// 8 | 9 | /* 10 | The JsonCpp library's source code, including accompanying documentation, 11 | tests and demonstration applications, are licensed under the following 12 | conditions... 13 | 14 | Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all 15 | jurisdictions which recognize such a disclaimer. In such jurisdictions, 16 | this software is released into the Public Domain. 17 | 18 | In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 19 | 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and 20 | The JsonCpp Authors, and is released under the terms of the MIT License (see below). 21 | 22 | In jurisdictions which recognize Public Domain property, the user of this 23 | software may choose to accept it either as 1) Public Domain, 2) under the 24 | conditions of the MIT License (see below), or 3) under the terms of dual 25 | Public Domain/MIT License conditions described here, as they choose. 26 | 27 | The MIT License is about as close to Public Domain as a license can get, and is 28 | described in clear, concise terms at: 29 | 30 | http://en.wikipedia.org/wiki/MIT_License 31 | 32 | The full text of the MIT License follows: 33 | 34 | ======================================================================== 35 | Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors 36 | 37 | Permission is hereby granted, free of charge, to any person 38 | obtaining a copy of this software and associated documentation 39 | files (the "Software"), to deal in the Software without 40 | restriction, including without limitation the rights to use, copy, 41 | modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be 46 | included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 49 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 52 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 53 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 54 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | ======================================================================== 57 | (END LICENSE TEXT) 58 | 59 | The MIT license is compatible with both the GPL and commercial 60 | software, affording one all of the rights of Public Domain with the 61 | minor nuisance of being required to keep the above copyright notice 62 | and license text in the source code. Note also that by accepting the 63 | Public Domain "license" you can re-license your copy using whatever 64 | license you like. 65 | 66 | */ 67 | 68 | // ////////////////////////////////////////////////////////////////////// 69 | // End of content of file: LICENSE 70 | // ////////////////////////////////////////////////////////////////////// 71 | 72 | 73 | 74 | 75 | 76 | #ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED 77 | # define JSON_FORWARD_AMALGAMATED_H_INCLUDED 78 | /// If defined, indicates that the source file is amalgamated 79 | /// to prevent private header inclusion. 80 | #define JSON_IS_AMALGAMATION 81 | 82 | // ////////////////////////////////////////////////////////////////////// 83 | // Beginning of content of file: include/json/config.h 84 | // ////////////////////////////////////////////////////////////////////// 85 | 86 | // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors 87 | // Distributed under MIT license, or public domain if desired and 88 | // recognized in your jurisdiction. 89 | // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE 90 | 91 | #ifndef JSON_CONFIG_H_INCLUDED 92 | #define JSON_CONFIG_H_INCLUDED 93 | #include 94 | #include //typedef String 95 | #include //typedef int64_t, uint64_t 96 | 97 | /// If defined, indicates that json library is embedded in CppTL library. 98 | //# define JSON_IN_CPPTL 1 99 | 100 | /// If defined, indicates that json may leverage CppTL library 101 | //# define JSON_USE_CPPTL 1 102 | /// If defined, indicates that cpptl vector based map should be used instead of 103 | /// std::map 104 | /// as Value container. 105 | //# define JSON_USE_CPPTL_SMALLMAP 1 106 | 107 | // If non-zero, the library uses exceptions to report bad input instead of C 108 | // assertion macros. The default is to use exceptions. 109 | #ifndef JSON_USE_EXCEPTION 110 | #define JSON_USE_EXCEPTION 1 111 | #endif 112 | 113 | /// If defined, indicates that the source file is amalgamated 114 | /// to prevent private header inclusion. 115 | /// Remarks: it is automatically defined in the generated amalgamated header. 116 | // #define JSON_IS_AMALGAMATION 117 | 118 | #ifdef JSON_IN_CPPTL 119 | #include 120 | #ifndef JSON_USE_CPPTL 121 | #define JSON_USE_CPPTL 1 122 | #endif 123 | #endif 124 | 125 | #ifdef JSON_IN_CPPTL 126 | #define JSON_API CPPTL_API 127 | #elif defined(JSON_DLL_BUILD) 128 | #if defined(_MSC_VER) || defined(__MINGW32__) 129 | #define JSON_API __declspec(dllexport) 130 | #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING 131 | #endif // if defined(_MSC_VER) 132 | #elif defined(JSON_DLL) 133 | #if defined(_MSC_VER) || defined(__MINGW32__) 134 | #define JSON_API __declspec(dllimport) 135 | #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING 136 | #endif // if defined(_MSC_VER) 137 | #endif // ifdef JSON_IN_CPPTL 138 | #if !defined(JSON_API) 139 | #define JSON_API 140 | #endif 141 | 142 | // If JSON_NO_INT64 is defined, then Json only support C++ "int" type for 143 | // integer 144 | // Storages, and 64 bits integer support is disabled. 145 | // #define JSON_NO_INT64 1 146 | 147 | #if defined(_MSC_VER) // MSVC 148 | # if _MSC_VER <= 1200 // MSVC 6 149 | // Microsoft Visual Studio 6 only support conversion from __int64 to double 150 | // (no conversion from unsigned __int64). 151 | # define JSON_USE_INT64_DOUBLE_CONVERSION 1 152 | // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' 153 | // characters in the debug information) 154 | // All projects I've ever seen with VS6 were using this globally (not bothering 155 | // with pragma push/pop). 156 | # pragma warning(disable : 4786) 157 | # endif // MSVC 6 158 | 159 | # if _MSC_VER >= 1500 // MSVC 2008 160 | /// Indicates that the following function is deprecated. 161 | # define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) 162 | # endif 163 | 164 | #endif // defined(_MSC_VER) 165 | 166 | // In c++11 the override keyword allows you to explicitly define that a function 167 | // is intended to override the base-class version. This makes the code more 168 | // managable and fixes a set of common hard-to-find bugs. 169 | #if __cplusplus >= 201103L 170 | # define JSONCPP_OVERRIDE override 171 | # define JSONCPP_NOEXCEPT noexcept 172 | #elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900 173 | # define JSONCPP_OVERRIDE override 174 | # define JSONCPP_NOEXCEPT throw() 175 | #elif defined(_MSC_VER) && _MSC_VER >= 1900 176 | # define JSONCPP_OVERRIDE override 177 | # define JSONCPP_NOEXCEPT noexcept 178 | #else 179 | # define JSONCPP_OVERRIDE 180 | # define JSONCPP_NOEXCEPT throw() 181 | #endif 182 | 183 | #ifndef JSON_HAS_RVALUE_REFERENCES 184 | 185 | #if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 186 | #define JSON_HAS_RVALUE_REFERENCES 1 187 | #endif // MSVC >= 2010 188 | 189 | #ifdef __clang__ 190 | #if __has_feature(cxx_rvalue_references) 191 | #define JSON_HAS_RVALUE_REFERENCES 1 192 | #endif // has_feature 193 | 194 | #elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) 195 | #if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) 196 | #define JSON_HAS_RVALUE_REFERENCES 1 197 | #endif // GXX_EXPERIMENTAL 198 | 199 | #endif // __clang__ || __GNUC__ 200 | 201 | #endif // not defined JSON_HAS_RVALUE_REFERENCES 202 | 203 | #ifndef JSON_HAS_RVALUE_REFERENCES 204 | #define JSON_HAS_RVALUE_REFERENCES 0 205 | #endif 206 | 207 | #ifdef __clang__ 208 | # if __has_extension(attribute_deprecated_with_message) 209 | # define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) 210 | # endif 211 | #elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) 212 | # if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) 213 | # define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) 214 | # elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) 215 | # define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) 216 | # endif // GNUC version 217 | #endif // __clang__ || __GNUC__ 218 | 219 | #if !defined(JSONCPP_DEPRECATED) 220 | #define JSONCPP_DEPRECATED(message) 221 | #endif // if !defined(JSONCPP_DEPRECATED) 222 | 223 | #if __GNUC__ >= 6 224 | # define JSON_USE_INT64_DOUBLE_CONVERSION 1 225 | #endif 226 | 227 | #if !defined(JSON_IS_AMALGAMATION) 228 | 229 | # include "version.h" 230 | 231 | # if JSONCPP_USING_SECURE_MEMORY 232 | # include "allocator.h" //typedef Allocator 233 | # endif 234 | 235 | #endif // if !defined(JSON_IS_AMALGAMATION) 236 | 237 | namespace Json { 238 | typedef int Int; 239 | typedef unsigned int UInt; 240 | #if defined(JSON_NO_INT64) 241 | typedef int LargestInt; 242 | typedef unsigned int LargestUInt; 243 | #undef JSON_HAS_INT64 244 | #else // if defined(JSON_NO_INT64) 245 | // For Microsoft Visual use specific types as long long is not supported 246 | #if defined(_MSC_VER) // Microsoft Visual Studio 247 | typedef __int64 Int64; 248 | typedef unsigned __int64 UInt64; 249 | #else // if defined(_MSC_VER) // Other platforms, use long long 250 | typedef int64_t Int64; 251 | typedef uint64_t UInt64; 252 | #endif // if defined(_MSC_VER) 253 | typedef Int64 LargestInt; 254 | typedef UInt64 LargestUInt; 255 | #define JSON_HAS_INT64 256 | #endif // if defined(JSON_NO_INT64) 257 | #if JSONCPP_USING_SECURE_MEMORY 258 | #define JSONCPP_STRING std::basic_string, Json::SecureAllocator > 259 | #define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator > 260 | #define JSONCPP_OSTREAM std::basic_ostream> 261 | #define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator > 262 | #define JSONCPP_ISTREAM std::istream 263 | #else 264 | #define JSONCPP_STRING std::string 265 | #define JSONCPP_OSTRINGSTREAM std::ostringstream 266 | #define JSONCPP_OSTREAM std::ostream 267 | #define JSONCPP_ISTRINGSTREAM std::istringstream 268 | #define JSONCPP_ISTREAM std::istream 269 | #endif // if JSONCPP_USING_SECURE_MEMORY 270 | } // end namespace Json 271 | 272 | #endif // JSON_CONFIG_H_INCLUDED 273 | 274 | // ////////////////////////////////////////////////////////////////////// 275 | // End of content of file: include/json/config.h 276 | // ////////////////////////////////////////////////////////////////////// 277 | 278 | 279 | 280 | 281 | 282 | 283 | // ////////////////////////////////////////////////////////////////////// 284 | // Beginning of content of file: include/json/forwards.h 285 | // ////////////////////////////////////////////////////////////////////// 286 | 287 | // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors 288 | // Distributed under MIT license, or public domain if desired and 289 | // recognized in your jurisdiction. 290 | // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE 291 | 292 | #ifndef JSON_FORWARDS_H_INCLUDED 293 | #define JSON_FORWARDS_H_INCLUDED 294 | 295 | #if !defined(JSON_IS_AMALGAMATION) 296 | #include "config.h" 297 | #endif // if !defined(JSON_IS_AMALGAMATION) 298 | 299 | namespace Json { 300 | 301 | // writer.h 302 | class FastWriter; 303 | class StyledWriter; 304 | 305 | // reader.h 306 | class Reader; 307 | 308 | // features.h 309 | class Features; 310 | 311 | // value.h 312 | typedef unsigned int ArrayIndex; 313 | class StaticString; 314 | class Path; 315 | class PathArgument; 316 | class Value; 317 | class ValueIteratorBase; 318 | class ValueIterator; 319 | class ValueConstIterator; 320 | 321 | } // namespace Json 322 | 323 | #endif // JSON_FORWARDS_H_INCLUDED 324 | 325 | // ////////////////////////////////////////////////////////////////////// 326 | // End of content of file: include/json/forwards.h 327 | // ////////////////////////////////////////////////////////////////////// 328 | 329 | 330 | 331 | 332 | 333 | #endif //ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED 334 | -------------------------------------------------------------------------------- /lib/httpd/src/include/utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_WEB_UTILITY_HPP 2 | #define SIMPLE_WEB_UTILITY_HPP 3 | 4 | #include "status_code.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace SimpleWeb { 12 | inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept { 13 | return str1.size() == str2.size() && 14 | std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { 15 | return tolower(a) == tolower(b); 16 | }); 17 | } 18 | class CaseInsensitiveEqual { 19 | public: 20 | bool operator()(const std::string &str1, const std::string &str2) const noexcept { 21 | return case_insensitive_equal(str1, str2); 22 | } 23 | }; 24 | // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226 25 | class CaseInsensitiveHash { 26 | public: 27 | std::size_t operator()(const std::string &str) const noexcept { 28 | std::size_t h = 0; 29 | std::hash hash; 30 | for(auto c : str) 31 | h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); 32 | return h; 33 | } 34 | }; 35 | 36 | using CaseInsensitiveMultimap = std::unordered_multimap; 37 | 38 | /// Percent encoding and decoding 39 | class Percent { 40 | public: 41 | /// Returns percent-encoded string 42 | static std::string encode(const std::string &value) noexcept { 43 | static auto hex_chars = "0123456789ABCDEF"; 44 | 45 | std::string result; 46 | result.reserve(value.size()); // Minimum size of result 47 | 48 | for(auto &chr : value) { 49 | if(!((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || chr == '-' || chr == '.' || chr == '_' || chr == '~')) 50 | result += std::string("%") + hex_chars[static_cast(chr) >> 4] + hex_chars[static_cast(chr) & 15]; 51 | else 52 | result += chr; 53 | } 54 | 55 | return result; 56 | } 57 | 58 | /// Returns percent-decoded string 59 | static std::string decode(const std::string &value) noexcept { 60 | std::string result; 61 | result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result 62 | 63 | for(std::size_t i = 0; i < value.size(); ++i) { 64 | auto &chr = value[i]; 65 | if(chr == '%' && i + 2 < value.size()) { 66 | auto hex = value.substr(i + 1, 2); 67 | auto decoded_chr = static_cast(std::strtol(hex.c_str(), nullptr, 16)); 68 | result += decoded_chr; 69 | i += 2; 70 | } 71 | else if(chr == '+') 72 | result += ' '; 73 | else 74 | result += chr; 75 | } 76 | 77 | return result; 78 | } 79 | }; 80 | 81 | /// Query string creation and parsing 82 | class QueryString { 83 | public: 84 | /// Returns query string created from given field names and values 85 | static std::string create(const CaseInsensitiveMultimap &fields) noexcept { 86 | std::string result; 87 | 88 | bool first = true; 89 | for(auto &field : fields) { 90 | result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second); 91 | first = false; 92 | } 93 | 94 | return result; 95 | } 96 | 97 | /// Returns query keys with percent-decoded values. 98 | static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept { 99 | CaseInsensitiveMultimap result; 100 | 101 | if(query_string.empty()) 102 | return result; 103 | 104 | std::size_t name_pos = 0; 105 | auto name_end_pos = std::string::npos; 106 | auto value_pos = std::string::npos; 107 | for(std::size_t c = 0; c < query_string.size(); ++c) { 108 | if(query_string[c] == '&') { 109 | auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos); 110 | if(!name.empty()) { 111 | auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos); 112 | result.emplace(std::move(name), Percent::decode(value)); 113 | } 114 | name_pos = c + 1; 115 | name_end_pos = std::string::npos; 116 | value_pos = std::string::npos; 117 | } 118 | else if(query_string[c] == '=') { 119 | name_end_pos = c; 120 | value_pos = c + 1; 121 | } 122 | } 123 | if(name_pos < query_string.size()) { 124 | auto name = query_string.substr(name_pos, name_end_pos - name_pos); 125 | if(!name.empty()) { 126 | auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos); 127 | result.emplace(std::move(name), Percent::decode(value)); 128 | } 129 | } 130 | 131 | return result; 132 | } 133 | }; 134 | 135 | class HttpHeader { 136 | public: 137 | /// Parse header fields 138 | static CaseInsensitiveMultimap parse(std::istream &stream) noexcept { 139 | CaseInsensitiveMultimap result; 140 | std::string line; 141 | getline(stream, line); 142 | std::size_t param_end; 143 | while((param_end = line.find(':')) != std::string::npos) { 144 | std::size_t value_start = param_end + 1; 145 | while(value_start + 1 < line.size() && line[value_start] == ' ') 146 | ++value_start; 147 | if(value_start < line.size()) 148 | result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1)); 149 | 150 | getline(stream, line); 151 | } 152 | return result; 153 | } 154 | 155 | class FieldValue { 156 | public: 157 | class SemicolonSeparatedAttributes { 158 | public: 159 | /// Parse Set-Cookie or Content-Disposition header field value. Attribute values are percent-decoded. 160 | static CaseInsensitiveMultimap parse(const std::string &str) { 161 | CaseInsensitiveMultimap result; 162 | 163 | std::size_t name_start_pos = std::string::npos; 164 | std::size_t name_end_pos = std::string::npos; 165 | std::size_t value_start_pos = std::string::npos; 166 | for(std::size_t c = 0; c < str.size(); ++c) { 167 | if(name_start_pos == std::string::npos) { 168 | if(str[c] != ' ' && str[c] != ';') 169 | name_start_pos = c; 170 | } 171 | else { 172 | if(name_end_pos == std::string::npos) { 173 | if(str[c] == ';') { 174 | result.emplace(str.substr(name_start_pos, c - name_start_pos), std::string()); 175 | name_start_pos = std::string::npos; 176 | } 177 | else if(str[c] == '=') 178 | name_end_pos = c; 179 | } 180 | else { 181 | if(value_start_pos == std::string::npos) { 182 | if(str[c] == '"' && c + 1 < str.size()) 183 | value_start_pos = c + 1; 184 | else 185 | value_start_pos = c; 186 | } 187 | else if(str[c] == '"' || str[c] == ';') { 188 | result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos, c - value_start_pos))); 189 | name_start_pos = std::string::npos; 190 | name_end_pos = std::string::npos; 191 | value_start_pos = std::string::npos; 192 | } 193 | } 194 | } 195 | } 196 | if(name_start_pos != std::string::npos) { 197 | if(name_end_pos == std::string::npos) 198 | result.emplace(str.substr(name_start_pos), std::string()); 199 | else if(value_start_pos != std::string::npos) { 200 | if(str.back() == '"') 201 | result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos, str.size() - 1))); 202 | else 203 | result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos))); 204 | } 205 | } 206 | 207 | return result; 208 | } 209 | }; 210 | }; 211 | }; // namespace SimpleWeb 212 | 213 | class RequestMessage { 214 | public: 215 | /// Parse request line and header fields 216 | static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept { 217 | header.clear(); 218 | std::string line; 219 | getline(stream, line); 220 | std::size_t method_end; 221 | if((method_end = line.find(' ')) != std::string::npos) { 222 | method = line.substr(0, method_end); 223 | 224 | std::size_t query_start = std::string::npos; 225 | std::size_t path_and_query_string_end = std::string::npos; 226 | for(std::size_t i = method_end + 1; i < line.size(); ++i) { 227 | if(line[i] == '?' && (i + 1) < line.size()) 228 | query_start = i + 1; 229 | else if(line[i] == ' ') { 230 | path_and_query_string_end = i; 231 | break; 232 | } 233 | } 234 | if(path_and_query_string_end != std::string::npos) { 235 | if(query_start != std::string::npos) { 236 | path = line.substr(method_end + 1, query_start - method_end - 2); 237 | query_string = line.substr(query_start, path_and_query_string_end - query_start); 238 | } 239 | else 240 | path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1); 241 | 242 | std::size_t protocol_end; 243 | if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) { 244 | if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0) 245 | return false; 246 | version = line.substr(protocol_end + 1, line.size() - protocol_end - 2); 247 | } 248 | else 249 | return false; 250 | 251 | header = HttpHeader::parse(stream); 252 | } 253 | else 254 | return false; 255 | } 256 | else 257 | return false; 258 | return true; 259 | } 260 | }; 261 | 262 | class ResponseMessage { 263 | public: 264 | /// Parse status line and header fields 265 | static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept { 266 | header.clear(); 267 | std::string line; 268 | getline(stream, line); 269 | std::size_t version_end = line.find(' '); 270 | if(version_end != std::string::npos) { 271 | if(5 < line.size()) 272 | version = line.substr(5, version_end - 5); 273 | else 274 | return false; 275 | if((version_end + 1) < line.size()) 276 | status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1); 277 | else 278 | return false; 279 | 280 | header = HttpHeader::parse(stream); 281 | } 282 | else 283 | return false; 284 | return true; 285 | } 286 | }; 287 | } // namespace SimpleWeb 288 | 289 | #ifdef __SSE2__ 290 | #include 291 | namespace SimpleWeb { 292 | inline void spin_loop_pause() noexcept { _mm_pause(); } 293 | } // namespace SimpleWeb 294 | // TODO: need verification that the following checks are correct: 295 | #elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86)) 296 | #include 297 | namespace SimpleWeb { 298 | inline void spin_loop_pause() noexcept { _mm_pause(); } 299 | } // namespace SimpleWeb 300 | #else 301 | namespace SimpleWeb { 302 | inline void spin_loop_pause() noexcept {} 303 | } // namespace SimpleWeb 304 | #endif 305 | 306 | namespace SimpleWeb { 307 | /// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service 308 | class ScopeRunner { 309 | /// Scope count that is set to -1 if scopes are to be canceled 310 | std::atomic count; 311 | 312 | public: 313 | class SharedLock { 314 | friend class ScopeRunner; 315 | std::atomic &count; 316 | SharedLock(std::atomic &count) noexcept : count(count) {} 317 | SharedLock &operator=(const SharedLock &) = delete; 318 | SharedLock(const SharedLock &) = delete; 319 | 320 | public: 321 | ~SharedLock() noexcept { 322 | count.fetch_sub(1); 323 | } 324 | }; 325 | 326 | ScopeRunner() noexcept : count(0) {} 327 | 328 | /// Returns nullptr if scope should be exited, or a shared lock otherwise 329 | std::unique_ptr continue_lock() noexcept { 330 | long expected = count; 331 | while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1)) 332 | spin_loop_pause(); 333 | 334 | if(expected < 0) 335 | return nullptr; 336 | else 337 | return std::unique_ptr(new SharedLock(count)); 338 | } 339 | 340 | /// Blocks until all shared locks are released, then prevents future shared locks 341 | void stop() noexcept { 342 | long expected = 0; 343 | while(!count.compare_exchange_weak(expected, -1)) { 344 | if(expected < 0) 345 | return; 346 | expected = 0; 347 | spin_loop_pause(); 348 | } 349 | } 350 | }; 351 | } // namespace SimpleWeb 352 | 353 | #endif // SIMPLE_WEB_UTILITY_HPP 354 | -------------------------------------------------------------------------------- /src/process_manage.cc: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include "httpd.h" 3 | #include "confd_shm.h" 4 | #include "confd_shmtx.h" 5 | #include "process_manage.h" 6 | #include "confd_dict.h" 7 | #include "nginx_conf_parse.h" 8 | 9 | static void reload_nginx_conf(int signo, siginfo_t *siginfo, void *ucontext); 10 | static void waitpid_handler(int signo, siginfo_t *siginfo, void *ucontext); 11 | static void graceful_shutdown_confd(int signo, siginfo_t *siginfo, void *ucontext); 12 | 13 | static bool load_nginx_conf(const std::string& nginx_bin_path, const std::string& nginx_conf_path); 14 | static bool register_signals(confd_signal_t *signals); 15 | static pid_t get_master_process_pid(const char* pid_path); 16 | static bool writen_pidfile(const char* pid_path); 17 | static bool remove_pidfile(const char* pid_path); 18 | static char* process_rename(confd_arg_t confd_arg, const char* wanted_name); 19 | 20 | confd_shmtx_t* shmtx; 21 | confd_shm_t* shm; 22 | confd_shmtx_t* updatetx; 23 | confd_shm_t* update; 24 | extern std::vector children_process_group; 25 | extern std::unordered_map confd_config; 26 | confd_arg_t confd_arg; 27 | 28 | static confd_signal_t signals[] = { 29 | {SIGHUP, "reload", reload_nginx_conf}, 30 | {SIGTERM, "stop", graceful_shutdown_confd}, 31 | {SIGCHLD, "sigchld", waitpid_handler}, 32 | {0, NULL, NULL} //loop end flag 33 | }; 34 | 35 | char* 36 | process_rename(confd_arg_t confd_arg, const char* wanted_name) 37 | { 38 | /*数据内存分布情况: ./confd\0-c\0config.json*/ 39 | int len = 0; 40 | char** argv = confd_arg.argv; 41 | for(int i = 0; i < confd_arg.argc; i++) { 42 | len += strlen(argv[i]) + 1; 43 | } 44 | char *p = argv[0]; 45 | memset(p, '\0', len); 46 | 47 | strcpy(p, wanted_name); 48 | return p; 49 | } 50 | 51 | bool 52 | register_signals(confd_signal_t *signals) 53 | { 54 | confd_signal_t *sig; 55 | struct sigaction sa; 56 | 57 | for(sig = signals; sig->signo != 0; sig++) { 58 | memset(&sa, '\0', sizeof(struct sigaction)); 59 | 60 | if (sig->handler) { 61 | sa.sa_sigaction = sig->handler; 62 | sa.sa_flags = SA_SIGINFO; 63 | } else { 64 | sa.sa_handler = SIG_IGN; 65 | } 66 | 67 | sigemptyset(&sa.sa_mask); 68 | if (sigaction(sig->signo, &sa, NULL) == -1) { 69 | BOOST_LOG_TRIVIAL(error) << "regesiter signal(" << sig->signo << ")failed."; 70 | return false; 71 | } else { 72 | BOOST_LOG_TRIVIAL(debug) << "regesiter signal(" << sig->signo << ")successful."; 73 | } 74 | 75 | } 76 | 77 | return true; 78 | } 79 | 80 | bool 81 | reset_signals(confd_signal_t *signals) 82 | { 83 | confd_signal_t *sig; 84 | struct sigaction sa; 85 | sa.sa_flags = 0; 86 | 87 | for(sig = signals; sig->signo != 0; sig++) { 88 | memset(&sa, '\0', sizeof(struct sigaction)); 89 | 90 | if (sig->handler) { 91 | sa.sa_handler = SIG_DFL; /*重置为默认处理方式*/ 92 | } else { 93 | sa.sa_handler = SIG_IGN; 94 | } 95 | 96 | sigemptyset(&sa.sa_mask); 97 | if (sigaction(sig->signo, &sa, NULL) == -1) { 98 | BOOST_LOG_TRIVIAL(error) << "reset signal(" << sig->signo << ")failed."; 99 | return false; 100 | } else { 101 | BOOST_LOG_TRIVIAL(debug) << "reset signal(" << sig->signo << ")successful."; 102 | } 103 | 104 | } 105 | 106 | return true; 107 | } 108 | 109 | void 110 | waitpid_handler(int signo, siginfo_t *siginfo, void *ucontext) 111 | { 112 | int status; 113 | pid_t pid; 114 | u_int one; 115 | 116 | BOOST_LOG_TRIVIAL(debug) << "waitpid_handler(): received signal(" << signo << ") from " << siginfo->si_pid; 117 | one = 0; 118 | for ( ;; ) { 119 | pid = waitpid(-1, &status, WNOHANG); 120 | if (pid == 0) { 121 | return; 122 | } 123 | if (pid == -1) { 124 | if (errno == EINTR) { 125 | continue; 126 | } 127 | if (errno == ECHILD && one) { 128 | return; 129 | } 130 | if (errno == ECHILD) { 131 | BOOST_LOG_TRIVIAL(info) << "waitpid() failed"; 132 | return; 133 | } 134 | BOOST_LOG_TRIVIAL(warning) << "waitpid() failed"; 135 | return; 136 | } 137 | 138 | one = 1; 139 | if (WTERMSIG(status)) { 140 | BOOST_LOG_TRIVIAL(warning) << "pid(" << pid 141 | << ") exited on signal=" << WTERMSIG(status) 142 | << " status=" << WEXITSTATUS(status); 143 | } else { 144 | BOOST_LOG_TRIVIAL(info) << "pid(" << pid 145 | << ") exited with code=" << WEXITSTATUS(status); 146 | } 147 | 148 | vector::iterator it; 149 | it = std::find_if(children_process_group.begin(), children_process_group.end(), [pid](process_t const& item) { 150 | return item.pid == pid; 151 | }); 152 | 153 | if (it == children_process_group.end()) { /*没有找到该pid*/ 154 | continue; 155 | } 156 | 157 | process_t old_process = (*it); 158 | 159 | //从子进程组里面删除此子进程 160 | children_process_group.erase(it); 161 | 162 | 163 | BOOST_LOG_TRIVIAL(info) << "process(" << old_process.name << ") exit"; 164 | 165 | if (WEXITSTATUS(status) != 0) { /*致命错误,跳过*/ 166 | BOOST_LOG_TRIVIAL(warning) << "pid(" << pid 167 | << ") exited with fatal code=" << WEXITSTATUS(status); 168 | continue; 169 | } 170 | 171 | if (old_process.restart == 0) { /*无需重启*/ 172 | continue; 173 | } 174 | 175 | //正常退出,尝试重启 176 | pid_t new_pid; 177 | if ((new_pid = spawn_worker_process(old_process.name)) == -1) { 178 | BOOST_LOG_TRIVIAL(warning) << "spawn worker process failed"; 179 | continue; 180 | } 181 | process_t new_process = old_process; 182 | new_process.pid = new_pid; 183 | children_process_group.push_back(new_process); 184 | BOOST_LOG_TRIVIAL(info) << "process(" << new_process.name << ") spawn successful"; 185 | } 186 | } 187 | 188 | pid_t 189 | spawn_worker_process(const std::string name) 190 | { 191 | pid_t pid; 192 | 193 | pid = fork(); 194 | switch (pid) { 195 | 196 | case -1: 197 | BOOST_LOG_TRIVIAL(warning) << "fork() failed"; 198 | return -1; 199 | case 0: 200 | init_worker_process(confd_arg, confd_config, name); 201 | break; 202 | default: 203 | break; 204 | } 205 | 206 | return pid; 207 | } 208 | 209 | void 210 | graceful_shutdown_confd(int signo, siginfo_t *siginfo, void *ucontext) 211 | { 212 | BOOST_LOG_TRIVIAL(debug) << "graceful_shutdown_confd(): received signal(" << signo << ") from " << siginfo->si_pid; 213 | for (auto& children: children_process_group) { //exit all children process 214 | children.restart = 0; //禁止重启子进程 215 | } 216 | std::vector copyed(children_process_group); 217 | for (auto& children: copyed) { //exit all children process 218 | BOOST_LOG_TRIVIAL(debug) << "send sigkill to pid(" << children.pid << ")"; 219 | if (kill(children.pid, SIGKILL)) { 220 | BOOST_LOG_TRIVIAL(warning) << "send sigkill to pid(" << children.pid << ") failed"; 221 | } 222 | } 223 | 224 | pid_t pid; 225 | int status; 226 | while ((pid = waitpid(-1, &status, 0)) != 0) { //等待所有子进程退出 227 | if (errno == ECHILD) { 228 | BOOST_LOG_TRIVIAL(debug) << "wait all children process exit finish."; 229 | break; 230 | } 231 | } 232 | 233 | if (destory_lock(shmtx)) { //delete lock 234 | BOOST_LOG_TRIVIAL(info) << "shmtx lock destory successful."; 235 | } 236 | 237 | if (destory_shm(shm)) { 238 | BOOST_LOG_TRIVIAL(info) << "shm destory successful."; 239 | } 240 | 241 | if (destory_lock(updatetx)) { //delete lock 242 | BOOST_LOG_TRIVIAL(info) << "updatetx lock destory successful."; 243 | } 244 | 245 | if (destory_shm(update)) { 246 | BOOST_LOG_TRIVIAL(info) << "update shm destory successful."; 247 | } 248 | 249 | if (remove_pidfile(confd_config["pid_path"].c_str())) { 250 | BOOST_LOG_TRIVIAL(warning) << "remove pidfile failed: " << strerror(errno); 251 | } 252 | 253 | BOOST_LOG_TRIVIAL(debug) << "shutdown all process finish."; 254 | exit(0); //退出主进程 255 | } 256 | 257 | bool 258 | writen_pidfile(const char* pid_path) 259 | { 260 | ofstream ofs; 261 | pid_t pid = getpid(); 262 | 263 | ofs.open(pid_path); 264 | if (!ofs) { 265 | BOOST_LOG_TRIVIAL(error) << "open pid_path(" << pid_path << ") failed."; 266 | return false; 267 | } 268 | ofs << pid << "\n"; 269 | ofs.close(); 270 | return true; 271 | } 272 | 273 | bool 274 | remove_pidfile(const char* pid_path) 275 | { 276 | return unlink(pid_path); 277 | } 278 | 279 | pid_t 280 | get_master_process_pid(const char* pid_path) 281 | { 282 | pid_t pid; 283 | ifstream ifs; 284 | 285 | ifs.open(pid_path); 286 | if (!ifs) { 287 | return -1; 288 | } 289 | ifs >> pid; 290 | ifs.close(); 291 | return pid; 292 | } 293 | 294 | void 295 | notify_master_process(const char *pid_path, const char *cmd) 296 | { 297 | pid_t pid = get_master_process_pid(pid_path); 298 | if (pid == -1) { 299 | printf("confd: open pid_file(%s) error: %s\n", pid_path, strerror(errno)); 300 | exit(-1); 301 | } 302 | 303 | confd_signal_t* signal; 304 | for (signal = signals; signal->signo != 0; signal++) { 305 | if (!strcmp(cmd, signal->opt_name)) { 306 | if (kill(pid, signal->signo) == -1) { 307 | printf("%s failed error: pid=%d, errno=%s\n", signal->opt_name, pid, strerror(errno)); 308 | } 309 | break; 310 | } 311 | } 312 | if (signal->signo == 0) { 313 | printf("opt failed error: unknown confd optno(%s)\n", cmd); 314 | exit(-1); 315 | } 316 | exit(0); 317 | } 318 | 319 | bool 320 | load_nginx_conf(const std::string& nginx_bin_path, const std::string& nginx_conf_path) 321 | { 322 | std::string status; 323 | nginxConfParse nginx_conf_parse; 324 | 325 | auto ret = nginx_opt::nginx_conf_test(nginx_bin_path.c_str(), nginx_conf_path.c_str()); 326 | if (!ret.first) { 327 | BOOST_LOG_TRIVIAL(error) << "reload nginx failed error: " << ret.second; 328 | return false; 329 | } 330 | //std::unordered_map> dict = nginx_conf_parse.parse(nginx_conf_path.c_str()); 331 | auto dict = nginx_conf_parse.parse(nginx_conf_path.c_str()); 332 | if (dict.size() == 0) { 333 | BOOST_LOG_TRIVIAL(debug) << "nginx_conf_parse result is 0"; 334 | return false; 335 | } 336 | 337 | std::unique_ptr confd_p(new confd_dict(dict)); 338 | 339 | std::string new_data = confd_p->json_stringify(); 340 | if (new_data.empty()) { 341 | BOOST_LOG_TRIVIAL(debug) << "new_data is empty"; 342 | return false; 343 | } 344 | lock(shmtx); 345 | if (strcmp(shm->addr, new_data.c_str()) == 0) { 346 | unlock(shmtx); 347 | return false; 348 | } 349 | 350 | /*复制新解析数据到共享内存地址*/ 351 | strcpy(shm->addr, new_data.c_str()); 352 | unlock(shmtx); 353 | return true; 354 | } 355 | 356 | void 357 | reload_nginx_conf(int signo, siginfo_t *siginfo, void *ucontext) 358 | { 359 | BOOST_LOG_TRIVIAL(debug) << "reload_nginx_conf(): received signal(" << signo << ") from " << siginfo->si_pid; 360 | 361 | bool ret = load_nginx_conf(confd_config["nginx_bin_path"], confd_config["nginx_conf_path"]); 362 | if (ret) { 363 | confd_dict dict; 364 | dict.update_status(true); 365 | BOOST_LOG_TRIVIAL(info) << "reparse && reload nginx_conf successful, status: update"; 366 | } else { 367 | BOOST_LOG_TRIVIAL(info) << "reparse && reload nginx_conf successful, status: no update"; 368 | } 369 | } 370 | 371 | bool 372 | init_master_process(unordered_map config) 373 | { 374 | 375 | process_rename(confd_arg, MASTERNAME); 376 | 377 | if (!writen_pidfile(config["pid_path"].c_str())) { 378 | BOOST_LOG_TRIVIAL(warning) << "writed pidfile(" << config["pid_path"] << ") failed."; 379 | return false; 380 | } 381 | 382 | update = init_shm(sizeof(bool) * 4096); 383 | if (!update) { 384 | BOOST_LOG_TRIVIAL(warning) << "init update shm failed."; 385 | } 386 | 387 | updatetx = init_lock(); 388 | if (!updatetx) { 389 | BOOST_LOG_TRIVIAL(error) << "int updatetx lock failed."; 390 | return false; 391 | } 392 | 393 | shm = init_shm(parse_bytes_number(config["shm_size"])); 394 | if (!shm) { 395 | BOOST_LOG_TRIVIAL(warning) << "init shm failed."; 396 | } 397 | 398 | shmtx = init_lock(); 399 | if (!shmtx) { 400 | BOOST_LOG_TRIVIAL(error) << "int shmtx lock failed."; 401 | return false; 402 | } 403 | 404 | bool ret = load_nginx_conf(config["nginx_bin_path"], config["nginx_conf_path"]); 405 | if (!ret) { 406 | BOOST_LOG_TRIVIAL(error) << "load nginx conf error."; 407 | return false; 408 | } 409 | 410 | if (!register_signals(signals)) { 411 | BOOST_LOG_TRIVIAL(error) << "register signal failed."; 412 | return false; 413 | } 414 | 415 | 416 | return true; 417 | } 418 | 419 | void 420 | init_worker_process(confd_arg_t confd_arg, unordered_map config, std::string name) 421 | { 422 | process_rename(confd_arg, WORKERNAME); 423 | 424 | BOOST_LOG_TRIVIAL(debug) << "start#########process(" << name << ") reset signals############start"; 425 | if (!reset_signals(signals)) { 426 | BOOST_LOG_TRIVIAL(error) << "reset signal failed."; 427 | } 428 | BOOST_LOG_TRIVIAL(debug) << "end##########process(" << name << ") reset signals############end"; 429 | 430 | httpServer(config["addr"], stoll(config["port"])); 431 | exit(0); 432 | } 433 | -------------------------------------------------------------------------------- /src/httpd.cc: -------------------------------------------------------------------------------- 1 | #include "httpd.h" 2 | #include "confd_shmtx.h" 3 | #include "config.h" 4 | #include "confd_dict.h" 5 | #include "confd_shm.h" 6 | #include 7 | #include "json.h" 8 | 9 | 10 | extern confd_shm_t *shm; 11 | extern confd_shmtx_t *shmtx; 12 | 13 | using namespace std; 14 | // Added for the json-example: 15 | using namespace boost::property_tree; 16 | 17 | using HttpServer = SimpleWeb::Server; 18 | 19 | 20 | 21 | int httpServer(std::string address, int port) { 22 | // HTTP-server at port 8080 using 1 thread 23 | // Unless you do more heavy non-threaded processing in the resources, 24 | // 1 thread is usually faster than several threads 25 | HttpServer server; 26 | server.config.port = port; 27 | server.config.address = address; 28 | server.config.reuse_port = true; 29 | 30 | // Add resources using path-regex and method-string, and an anonymous function 31 | // POST-example for the path /string, responds the posted string 32 | // POST-example for the path /json, responds firstName+" "+lastName from the posted json 33 | // Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing 34 | // Example posted json: 35 | // { 36 | // "firstName": "John", 37 | // "lastName": "Smith", 38 | // "age": 25 39 | // } 40 | server.resource["^/json$"]["POST"] = [](shared_ptr response, shared_ptr request) { 41 | try { 42 | ptree pt; 43 | read_json(request->content, pt); 44 | 45 | auto name = pt.get("firstName") + " " + pt.get("lastName"); 46 | 47 | *response << "HTTP/1.1 200 OK\r\n" 48 | << "Content-Length: " << name.length() << "\r\n\r\n" 49 | << name; 50 | } 51 | catch(const exception &e) { 52 | *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" 53 | << e.what(); 54 | } 55 | 56 | 57 | }; 58 | 59 | server.resource["^/api/confs$"]["POST"] = [](shared_ptr response, shared_ptr request) { 60 | string content; 61 | Json::Value root; 62 | try { 63 | ptree pt; 64 | read_json(request->content, pt); 65 | 66 | string domain = pt.get("domain"); 67 | string tmp_type = pt.get("type"); 68 | string listen_port = pt.get("listen"); 69 | HEALTH_CHECK type = int_to_health_check_type(pt.get("health_check")); 70 | //bool force = pt.get("force", false); 71 | ptree upstreams = pt.get_child("upstreams"); // get_child得到数组对象 72 | 73 | std::vector upstream_vcs; 74 | BOOST_FOREACH(boost::property_tree::ptree::value_type &v, upstreams) { 75 | upstream_vcs.push_back(v.second.get("server")); 76 | } 77 | 78 | confd_dict dict; 79 | dict.shm_sync_to_dict(); 80 | std::pair ret = dict.add_item(listen_port, domain, upstream_vcs, tmp_type, false, type); 81 | 82 | root["info"] = ret.second; 83 | if (!ret.first) { 84 | root["code"] = 403; 85 | } else { 86 | root["code"] = 200; 87 | auto header = request->header.find("Host"); 88 | std::string url(header->second); 89 | url += "/api/confs/" + domain + "_" + listen_port; 90 | root["query_url"] = url; 91 | } 92 | } 93 | catch(const exception &e) { 94 | root["code"] = 403; 95 | root["info"] = e.what(); 96 | } 97 | content = root.toStyledString(); 98 | *response << "HTTP/1.1 200 OK\r\n" 99 | << "Content-Length: " << content.length() << "\r\n\r\n" 100 | << content; 101 | }; 102 | 103 | server.resource["^/api/confs$"]["PUT"] = [](shared_ptr response, shared_ptr request) { 104 | string content; 105 | Json::Value root; 106 | try { 107 | ptree pt; 108 | read_json(request->content, pt); 109 | 110 | string domain = pt.get("domain"); 111 | string tmp_type = pt.get("type"); 112 | string listen_port = pt.get("listen"); 113 | HEALTH_CHECK type = int_to_health_check_type(pt.get("health_check")); 114 | ptree upstreams = pt.get_child("upstreams"); // get_child得到数组对象 115 | 116 | std::vector upstream_vcs; 117 | BOOST_FOREACH(boost::property_tree::ptree::value_type &v, upstreams) { 118 | upstream_vcs.push_back(v.second.get("server")); 119 | } 120 | 121 | confd_dict dict; 122 | dict.shm_sync_to_dict(); 123 | std::pair ret = dict.add_item(listen_port, domain, upstream_vcs, tmp_type, true, type); 124 | 125 | root["info"] = ret.second; 126 | if (!ret.first) { 127 | root["code"] = 403; 128 | } else { 129 | root["code"] = 200; 130 | auto header = request->header.find("Host"); 131 | std::string url(header->second); 132 | url += "/api/confs/" + domain + "_" + listen_port; 133 | root["query_url"] = url; 134 | } 135 | } 136 | catch(const exception &e) { 137 | root["code"] = 403; 138 | root["info"] = e.what(); 139 | } 140 | content = root.toStyledString(); 141 | *response << "HTTP/1.1 200 OK\r\n" 142 | << "Content-Length: " << content.length() << "\r\n\r\n" 143 | << content; 144 | }; 145 | 146 | server.resource["^/api/confs$"]["GET"] = [](shared_ptr response, shared_ptr request) { 147 | lock(shmtx); 148 | string content(shm->addr); 149 | unlock(shmtx); 150 | 151 | *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << content.length() << "\r\n\r\n" 152 | << content; 153 | }; 154 | 155 | server.resource["^/api/confs/((.*)_(.*?))"]["GET"] = [](shared_ptr response, shared_ptr request) { 156 | Json::Value root, data, upstreams; 157 | 158 | std::string search = request->path_match[1]; 159 | confd_dict* dict = new confd_dict(); 160 | dict->shm_sync_to_dict(); 161 | std::pair> ret = dict->get_value_by_key(search); 162 | if (ret.first) { 163 | root["code"] = 200; 164 | for(auto& item: ret.second) { 165 | upstreams.append(item); 166 | } 167 | data["listen"] = string(request->path_match[3]); 168 | data["server_name"] = string(request->path_match[2]); 169 | data["upstream"] = upstreams; 170 | } else { 171 | root["code"] = 403; 172 | } 173 | root["data"] = data; 174 | delete dict; 175 | 176 | string content = root.toStyledString(); 177 | *response << "HTTP/1.1 200 OK\r\n" 178 | << "Content-Type: application/json\r\n" 179 | << "Content-Length: " << content.length() << "\r\n\r\n" 180 | << content; 181 | }; 182 | 183 | server.resource["^/api/confs/((.*?)_(.*?))"]["DELETE"] = [](shared_ptr response, shared_ptr request) { 184 | Json::Value root, data, upstreams; 185 | 186 | std::string search = request->path_match[1]; 187 | confd_dict* dict = new confd_dict(); 188 | dict->shm_sync_to_dict(); 189 | std::pair ret = dict->delete_key(search); 190 | if (ret.first) { 191 | root["code"] = 200; 192 | } else { 193 | root["code"] = 403; 194 | } 195 | root["info"] = ret.second; 196 | delete dict; 197 | 198 | string content = root.toStyledString(); 199 | *response << "HTTP/1.1 200 OK\r\n" 200 | << "Content-Type: application/json\r\n" 201 | << "Content-Length: " << content.length() << "\r\n\r\n" 202 | << content; 203 | }; 204 | 205 | // GET-example for the path /match/[number], responds with the matched string in path (number) 206 | // For instance a request GET /match/123 will receive: 123 207 | // GET-example simulating heavy work in a separate thread 208 | server.resource["^/work$"]["GET"] = [](shared_ptr response, shared_ptr /*request*/) { 209 | string content = "hello,world\n"; 210 | *response << "HTTP/1.1 200 OK\r\n" 211 | << "Content-Type: text/html\r\n" 212 | << "Content-Length: " << content.length() << "\r\n\r\n" 213 | << content; 214 | }; 215 | 216 | server.resource["^/api/status/solt/([0-9]+)$"]["GET"] = [](shared_ptr response, shared_ptr request) { 217 | confd_dict dict; 218 | Json::Value root; 219 | 220 | std::string search = request->path_match[1]; 221 | bool status = dict.status(std::stoll(search)); 222 | root["code"] = 200; 223 | root["status"] = status; 224 | string content = root.toStyledString(); 225 | *response << "HTTP/1.1 200 OK\r\n" 226 | << "Content-Type: application/json\r\n" 227 | << "Content-Length: " << content.length() << "\r\n\r\n" 228 | << content; 229 | }; 230 | 231 | server.resource["^/api/status_update/solt/([0-9]+)$"]["GET"] = [](shared_ptr response, shared_ptr request) { 232 | confd_dict dict; 233 | Json::Value root; 234 | 235 | std::string search = request->path_match[1]; 236 | bool ret = dict.update_status(false, std::stoll(search)); 237 | if (ret) { 238 | root["code"] = 200; 239 | root["info"] = "ok."; 240 | } else { 241 | root["code"] = 403; 242 | root["info"] = "failed."; 243 | } 244 | string content = root.toStyledString(); 245 | *response << "HTTP/1.1 200 OK\r\n" 246 | << "Content-Type: application/json\r\n" 247 | << "Content-Length: " << content.length() << "\r\n\r\n" 248 | << content; 249 | }; 250 | 251 | 252 | server.resource["^/api/nginx_health$"]["GET"] = [](shared_ptr response, shared_ptr /*request*/) { 253 | Json::Value root, data; 254 | std::pair ret; 255 | 256 | ret = nginx_opt::nginx_shutting_worker_count(); 257 | root["error"] = ""; 258 | if (!ret.first) { 259 | root["code"] = "403"; 260 | root["error"] = ret.second; 261 | string content = root.toStyledString(); 262 | *response << "HTTP/1.1 200 OK\r\n" 263 | << "Content-Type: text/html\r\n" 264 | << "Content-Length: " << content.length() << "\r\n\r\n" 265 | << content; 266 | return; 267 | } else { 268 | data["nginx shutting worker"] = std::to_string(std::stoll(ret.second)); 269 | } 270 | ret = nginx_opt::nginx_process_used_memsum(); 271 | if (!ret.first) { 272 | root["code"] = "403"; 273 | root["error"] = ret.second; 274 | string content = root.toStyledString(); 275 | *response << "HTTP/1.1 200 OK\r\n" 276 | << "Content-Type: text/html\r\n" 277 | << "Content-Length: " << content.length() << "\r\n\r\n" 278 | << content; 279 | return; 280 | } else { 281 | data["nginx process memsum"] = ret.second; 282 | } 283 | root["code"] = "200"; 284 | root["data"] = data; 285 | string content = root.toStyledString(); 286 | *response << "HTTP/1.1 200 OK\r\n" 287 | << "Content-Type: application/json\r\n" 288 | << "Content-Length: " << content.length() << "\r\n\r\n" 289 | << content; 290 | }; 291 | 292 | // Default GET-example. If no other matches, this anonymous function will be called. 293 | // Will respond with content in the web/-directory, and its subdirectories. 294 | // Default file: index.html 295 | // Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server 296 | server.default_resource["GET"] = [](shared_ptr response, shared_ptr request) { 297 | try { 298 | auto web_root_path = boost::filesystem::canonical("web"); 299 | auto path = boost::filesystem::canonical(web_root_path / request->path); 300 | // Check if path is within web_root_path 301 | if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) || 302 | !equal(web_root_path.begin(), web_root_path.end(), path.begin())) 303 | throw invalid_argument("path must be within root path"); 304 | if(boost::filesystem::is_directory(path)) 305 | path /= "index.html"; 306 | 307 | SimpleWeb::CaseInsensitiveMultimap header; 308 | 309 | auto ifs = make_shared(); 310 | ifs->open(path.string(), ifstream::in | ios::binary | ios::ate); 311 | 312 | if(*ifs) { 313 | auto length = ifs->tellg(); 314 | ifs->seekg(0, ios::beg); 315 | 316 | header.emplace("Content-Length", to_string(length)); 317 | response->write(header); 318 | 319 | // Trick to define a recursive function within this scope (for example purposes) 320 | class FileServer { 321 | public: 322 | static void read_and_send(const shared_ptr &response, const shared_ptr &ifs) { 323 | // Read and send 128 KB at a time 324 | static vector buffer(131072); // Safe when server is running on one thread 325 | streamsize read_length; 326 | if((read_length = ifs->read(&buffer[0], static_cast(buffer.size())).gcount()) > 0) { 327 | response->write(&buffer[0], read_length); 328 | if(read_length == static_cast(buffer.size())) { 329 | response->send([response, ifs](const SimpleWeb::error_code &ec) { 330 | if(!ec) 331 | read_and_send(response, ifs); 332 | else 333 | cerr << "Connection interrupted" << endl; 334 | }); 335 | } 336 | } 337 | } 338 | }; 339 | FileServer::read_and_send(response, ifs); 340 | } 341 | else 342 | throw invalid_argument("could not read file"); 343 | } 344 | catch(const exception &e) { 345 | response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what() + "\n"); 346 | } 347 | }; 348 | 349 | server.on_error = [](shared_ptr /*request*/, const SimpleWeb::error_code & /*ec*/) { 350 | // Handle errors here 351 | // Note that connection timeouts will also call this handle with ec set to SimpleWeb::errc::operation_canceled 352 | }; 353 | 354 | thread server_thread([&server]() { 355 | // Start server 356 | server.start(); 357 | }); 358 | 359 | server_thread.join(); 360 | return 0; 361 | } 362 | -------------------------------------------------------------------------------- /lib/httpd/src/include/server_http.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_HTTP_HPP 2 | #define SERVER_HTTP_HPP 3 | 4 | #include "utility.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef USE_STANDALONE_ASIO 15 | #include 16 | #include 17 | namespace SimpleWeb { 18 | using error_code = std::error_code; 19 | using errc = std::errc; 20 | namespace make_error_code = std; 21 | } // namespace SimpleWeb 22 | #else 23 | #include 24 | #include 25 | namespace SimpleWeb { 26 | namespace asio = boost::asio; 27 | using error_code = boost::system::error_code; 28 | namespace errc = boost::system::errc; 29 | namespace make_error_code = boost::system::errc; 30 | } // namespace SimpleWeb 31 | #endif 32 | 33 | // Late 2017 TODO: remove the following checks and always use std::regex 34 | #ifdef USE_BOOST_REGEX 35 | #include 36 | namespace SimpleWeb { 37 | namespace regex = boost; 38 | } 39 | #else 40 | #include 41 | namespace SimpleWeb { 42 | namespace regex = std; 43 | } 44 | #endif 45 | 46 | namespace SimpleWeb { 47 | template 48 | class Server; 49 | 50 | template 51 | class ServerBase { 52 | protected: 53 | class Session; 54 | 55 | public: 56 | class Response : public std::enable_shared_from_this, public std::ostream { 57 | friend class ServerBase; 58 | friend class Server; 59 | 60 | asio::streambuf streambuf; 61 | 62 | std::shared_ptr session; 63 | long timeout_content; 64 | 65 | Response(std::shared_ptr session, long timeout_content) noexcept : std::ostream(&streambuf), session(std::move(session)), timeout_content(timeout_content) {} 66 | 67 | template 68 | void write_header(const CaseInsensitiveMultimap &header, size_type size) { 69 | bool content_length_written = false; 70 | bool chunked_transfer_encoding = false; 71 | for(auto &field : header) { 72 | if(!content_length_written && case_insensitive_equal(field.first, "content-length")) 73 | content_length_written = true; 74 | else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked")) 75 | chunked_transfer_encoding = true; 76 | 77 | *this << field.first << ": " << field.second << "\r\n"; 78 | } 79 | if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response) 80 | *this << "Content-Length: " << size << "\r\n\r\n"; 81 | else 82 | *this << "\r\n"; 83 | } 84 | 85 | public: 86 | std::size_t size() noexcept { 87 | return streambuf.size(); 88 | } 89 | 90 | /// Use this function if you need to recursively send parts of a longer message 91 | void send(const std::function &callback = nullptr) noexcept { 92 | session->connection->set_timeout(timeout_content); 93 | auto self = this->shared_from_this(); // Keep Response instance alive through the following async_write 94 | asio::async_write(*session->connection->socket, streambuf, [self, callback](const error_code &ec, std::size_t /*bytes_transferred*/) { 95 | self->session->connection->cancel_timeout(); 96 | auto lock = self->session->connection->handler_runner->continue_lock(); 97 | if(!lock) 98 | return; 99 | if(callback) 100 | callback(ec); 101 | }); 102 | } 103 | 104 | /// Write directly to stream buffer using std::ostream::write 105 | void write(const char_type *ptr, std::streamsize n) { 106 | std::ostream::write(ptr, n); 107 | } 108 | 109 | /// Convenience function for writing status line, potential header fields, and empty content 110 | void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 111 | *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; 112 | write_header(header, 0); 113 | } 114 | 115 | /// Convenience function for writing status line, header fields, and content 116 | void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 117 | *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; 118 | write_header(header, content.size()); 119 | if(!content.empty()) 120 | *this << content; 121 | } 122 | 123 | /// Convenience function for writing status line, header fields, and content 124 | void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 125 | *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; 126 | content.seekg(0, std::ios::end); 127 | auto size = content.tellg(); 128 | content.seekg(0, std::ios::beg); 129 | write_header(header, size); 130 | if(size) 131 | *this << content.rdbuf(); 132 | } 133 | 134 | /// Convenience function for writing success status line, header fields, and content 135 | void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 136 | write(StatusCode::success_ok, content, header); 137 | } 138 | 139 | /// Convenience function for writing success status line, header fields, and content 140 | void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 141 | write(StatusCode::success_ok, content, header); 142 | } 143 | 144 | /// Convenience function for writing success status line, and header fields 145 | void write(const CaseInsensitiveMultimap &header) { 146 | write(StatusCode::success_ok, std::string(), header); 147 | } 148 | 149 | /// If true, force server to close the connection after the response have been sent. 150 | /// 151 | /// This is useful when implementing a HTTP/1.0-server sending content 152 | /// without specifying the content length. 153 | bool close_connection_after_response = false; 154 | }; 155 | 156 | class Content : public std::istream { 157 | friend class ServerBase; 158 | 159 | public: 160 | std::size_t size() noexcept { 161 | return streambuf.size(); 162 | } 163 | /// Convenience function to return std::string. The stream buffer is consumed. 164 | std::string string() noexcept { 165 | try { 166 | std::string str; 167 | auto size = streambuf.size(); 168 | str.resize(size); 169 | read(&str[0], static_cast(size)); 170 | return str; 171 | } 172 | catch(...) { 173 | return std::string(); 174 | } 175 | } 176 | 177 | private: 178 | asio::streambuf &streambuf; 179 | Content(asio::streambuf &streambuf) noexcept : std::istream(&streambuf), streambuf(streambuf) {} 180 | }; 181 | 182 | class Request { 183 | friend class ServerBase; 184 | friend class Server; 185 | friend class Session; 186 | 187 | asio::streambuf streambuf; 188 | 189 | Request(std::size_t max_request_streambuf_size, std::shared_ptr remote_endpoint) noexcept 190 | : streambuf(max_request_streambuf_size), content(streambuf), remote_endpoint(std::move(remote_endpoint)) {} 191 | 192 | public: 193 | std::string method, path, query_string, http_version; 194 | 195 | Content content; 196 | 197 | CaseInsensitiveMultimap header; 198 | 199 | regex::smatch path_match; 200 | 201 | std::shared_ptr remote_endpoint; 202 | 203 | /// The time point when the request header was fully read. 204 | std::chrono::system_clock::time_point header_read_time; 205 | 206 | std::string remote_endpoint_address() noexcept { 207 | try { 208 | return remote_endpoint->address().to_string(); 209 | } 210 | catch(...) { 211 | return std::string(); 212 | } 213 | } 214 | 215 | unsigned short remote_endpoint_port() noexcept { 216 | return remote_endpoint->port(); 217 | } 218 | 219 | /// Returns query keys with percent-decoded values. 220 | CaseInsensitiveMultimap parse_query_string() noexcept { 221 | return SimpleWeb::QueryString::parse(query_string); 222 | } 223 | }; 224 | 225 | protected: 226 | class Connection : public std::enable_shared_from_this { 227 | public: 228 | template 229 | Connection(std::shared_ptr handler_runner, Args &&... args) noexcept : handler_runner(std::move(handler_runner)), socket(new socket_type(std::forward(args)...)) {} 230 | 231 | std::shared_ptr handler_runner; 232 | 233 | std::unique_ptr socket; // Socket must be unique_ptr since asio::ssl::stream is not movable 234 | std::mutex socket_close_mutex; 235 | 236 | std::unique_ptr timer; 237 | 238 | std::shared_ptr remote_endpoint; 239 | 240 | void close() noexcept { 241 | error_code ec; 242 | std::unique_lock lock(socket_close_mutex); // The following operations seems to be needed to run sequentially 243 | socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec); 244 | socket->lowest_layer().close(ec); 245 | } 246 | 247 | void set_timeout(long seconds) noexcept { 248 | if(seconds == 0) { 249 | timer = nullptr; 250 | return; 251 | } 252 | 253 | timer = std::unique_ptr(new asio::steady_timer(socket->get_io_service())); 254 | timer->expires_from_now(std::chrono::seconds(seconds)); 255 | auto self = this->shared_from_this(); 256 | timer->async_wait([self](const error_code &ec) { 257 | if(!ec) 258 | self->close(); 259 | }); 260 | } 261 | 262 | void cancel_timeout() noexcept { 263 | if(timer) { 264 | error_code ec; 265 | timer->cancel(ec); 266 | } 267 | } 268 | }; 269 | 270 | class Session { 271 | public: 272 | Session(std::size_t max_request_streambuf_size, std::shared_ptr connection) noexcept : connection(std::move(connection)) { 273 | if(!this->connection->remote_endpoint) { 274 | error_code ec; 275 | this->connection->remote_endpoint = std::make_shared(this->connection->socket->lowest_layer().remote_endpoint(ec)); 276 | } 277 | request = std::shared_ptr(new Request(max_request_streambuf_size, this->connection->remote_endpoint)); 278 | } 279 | 280 | std::shared_ptr connection; 281 | std::shared_ptr request; 282 | }; 283 | 284 | public: 285 | class Config { 286 | friend class ServerBase; 287 | 288 | Config(unsigned short port) noexcept : port(port) {} 289 | 290 | public: 291 | /// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS. Set to 0 get an assigned port. 292 | unsigned short port; 293 | /// If io_service is not set, number of threads that the server will use when start() is called. 294 | /// Defaults to 1 thread. 295 | std::size_t thread_pool_size = 1; 296 | /// Timeout on request handling. Defaults to 5 seconds. 297 | long timeout_request = 5; 298 | /// Timeout on content handling. Defaults to 300 seconds. 299 | long timeout_content = 300; 300 | /// Maximum size of request stream buffer. Defaults to architecture maximum. 301 | /// Reaching this limit will result in a message_size error code. 302 | std::size_t max_request_streambuf_size = std::numeric_limits::max(); 303 | /// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation. 304 | /// If empty, the address will be any address. 305 | std::string address; 306 | /// Set to false to avoid binding the socket to an address that is already in use. Defaults to true. 307 | bool reuse_address = true; 308 | bool reuse_port = false; 309 | }; 310 | /// Set before calling start(). 311 | Config config; 312 | 313 | private: 314 | class regex_orderable : public regex::regex { 315 | std::string str; 316 | 317 | public: 318 | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {} 319 | regex_orderable(std::string regex_str) : regex::regex(regex_str), str(std::move(regex_str)) {} 320 | bool operator<(const regex_orderable &rhs) const noexcept { 321 | return str < rhs.str; 322 | } 323 | }; 324 | 325 | public: 326 | /// Warning: do not add or remove resources after start() is called 327 | std::map::Response>, std::shared_ptr::Request>)>>> resource; 328 | 329 | std::map::Response>, std::shared_ptr::Request>)>> default_resource; 330 | 331 | std::function::Request>, const error_code &)> on_error; 332 | 333 | std::function &, std::shared_ptr::Request>)> on_upgrade; 334 | 335 | /// If you have your own asio::io_service, store its pointer here before running start(). 336 | std::shared_ptr io_service; 337 | 338 | /// If you know the server port in advance, use start() instead. 339 | /// Returns assigned port. If io_service is not set, an internal io_service is created instead. 340 | /// Call before accept_and_run(). 341 | unsigned short bind() { 342 | asio::ip::tcp::endpoint endpoint; 343 | if(config.address.size() > 0) 344 | endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port); 345 | else 346 | endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port); 347 | 348 | if(!io_service) { 349 | io_service = std::make_shared(); 350 | internal_io_service = true; 351 | } 352 | 353 | if(!acceptor) 354 | acceptor = std::unique_ptr(new asio::ip::tcp::acceptor(*io_service)); 355 | acceptor->open(endpoint.protocol()); 356 | acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address)); 357 | if (config.reuse_port) { 358 | int one = 1; 359 | #include 360 | setsockopt(acceptor->native_handle(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one)); 361 | } 362 | acceptor->bind(endpoint); 363 | 364 | after_bind(); 365 | 366 | return acceptor->local_endpoint().port(); 367 | } 368 | 369 | /// If you know the server port in advance, use start() instead. 370 | /// Accept requests, and if io_service was not set before calling bind(), run the internal io_service instead. 371 | /// Call after bind(). 372 | void accept_and_run() { 373 | acceptor->listen(); 374 | accept(); 375 | 376 | if(internal_io_service) { 377 | if(io_service->stopped()) 378 | io_service->reset(); 379 | 380 | // If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling 381 | threads.clear(); 382 | for(std::size_t c = 1; c < config.thread_pool_size; c++) { 383 | threads.emplace_back([this]() { 384 | this->io_service->run(); 385 | }); 386 | } 387 | 388 | // Main thread 389 | if(config.thread_pool_size > 0) 390 | io_service->run(); 391 | 392 | // Wait for the rest of the threads, if any, to finish as well 393 | for(auto &t : threads) 394 | t.join(); 395 | } 396 | } 397 | 398 | /// Start the server by calling bind() and accept_and_run() 399 | void start() { 400 | bind(); 401 | accept_and_run(); 402 | } 403 | 404 | /// Stop accepting new requests, and close current connections. 405 | void stop() noexcept { 406 | if(acceptor) { 407 | error_code ec; 408 | acceptor->close(ec); 409 | 410 | { 411 | std::unique_lock lock(*connections_mutex); 412 | for(auto &connection : *connections) 413 | connection->close(); 414 | connections->clear(); 415 | } 416 | 417 | if(internal_io_service) 418 | io_service->stop(); 419 | } 420 | } 421 | 422 | virtual ~ServerBase() noexcept { 423 | handler_runner->stop(); 424 | stop(); 425 | } 426 | 427 | protected: 428 | bool internal_io_service = false; 429 | 430 | std::unique_ptr acceptor; 431 | std::vector threads; 432 | 433 | std::shared_ptr> connections; 434 | std::shared_ptr connections_mutex; 435 | 436 | std::shared_ptr handler_runner; 437 | 438 | ServerBase(unsigned short port) noexcept : config(port), connections(new std::unordered_set()), connections_mutex(new std::mutex()), handler_runner(new ScopeRunner()) {} 439 | 440 | virtual void after_bind() {} 441 | virtual void accept() = 0; 442 | 443 | template 444 | std::shared_ptr create_connection(Args &&... args) noexcept { 445 | auto connections = this->connections; 446 | auto connections_mutex = this->connections_mutex; 447 | auto connection = std::shared_ptr(new Connection(handler_runner, std::forward(args)...), [connections, connections_mutex](Connection *connection) { 448 | { 449 | std::unique_lock lock(*connections_mutex); 450 | auto it = connections->find(connection); 451 | if(it != connections->end()) 452 | connections->erase(it); 453 | } 454 | delete connection; 455 | }); 456 | { 457 | std::unique_lock lock(*connections_mutex); 458 | connections->emplace(connection.get()); 459 | } 460 | return connection; 461 | } 462 | 463 | void read(const std::shared_ptr &session) { 464 | session->connection->set_timeout(config.timeout_request); 465 | asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) { 466 | session->connection->cancel_timeout(); 467 | auto lock = session->connection->handler_runner->continue_lock(); 468 | if(!lock) 469 | return; 470 | session->request->header_read_time = std::chrono::system_clock::now(); 471 | if((!ec || ec == asio::error::not_found) && session->request->streambuf.size() == session->request->streambuf.max_size()) { 472 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 473 | response->write(StatusCode::client_error_payload_too_large); 474 | response->send(); 475 | if(this->on_error) 476 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 477 | return; 478 | } 479 | if(!ec) { 480 | // request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs: 481 | // "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter" 482 | // The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the 483 | // streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content). 484 | std::size_t num_additional_bytes = session->request->streambuf.size() - bytes_transferred; 485 | 486 | if(!RequestMessage::parse(session->request->content, session->request->method, session->request->path, 487 | session->request->query_string, session->request->http_version, session->request->header)) { 488 | if(this->on_error) 489 | this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); 490 | return; 491 | } 492 | 493 | // If content, read that as well 494 | auto header_it = session->request->header.find("Content-Length"); 495 | if(header_it != session->request->header.end()) { 496 | unsigned long long content_length = 0; 497 | try { 498 | content_length = stoull(header_it->second); 499 | } 500 | catch(const std::exception &) { 501 | if(this->on_error) 502 | this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); 503 | return; 504 | } 505 | if(content_length > num_additional_bytes) { 506 | session->connection->set_timeout(config.timeout_content); 507 | asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { 508 | session->connection->cancel_timeout(); 509 | auto lock = session->connection->handler_runner->continue_lock(); 510 | if(!lock) 511 | return; 512 | if(!ec) { 513 | if(session->request->streambuf.size() == session->request->streambuf.max_size()) { 514 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 515 | response->write(StatusCode::client_error_payload_too_large); 516 | response->send(); 517 | if(this->on_error) 518 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 519 | return; 520 | } 521 | this->find_resource(session); 522 | } 523 | else if(this->on_error) 524 | this->on_error(session->request, ec); 525 | }); 526 | } 527 | else 528 | this->find_resource(session); 529 | } 530 | else if((header_it = session->request->header.find("Transfer-Encoding")) != session->request->header.end() && header_it->second == "chunked") { 531 | auto chunks_streambuf = std::make_shared(this->config.max_request_streambuf_size); 532 | this->read_chunked_transfer_encoded(session, chunks_streambuf); 533 | } 534 | else 535 | this->find_resource(session); 536 | } 537 | else if(this->on_error) 538 | this->on_error(session->request, ec); 539 | }); 540 | } 541 | 542 | void read_chunked_transfer_encoded(const std::shared_ptr &session, const std::shared_ptr &chunks_streambuf) { 543 | session->connection->set_timeout(config.timeout_content); 544 | asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n", [this, session, chunks_streambuf](const error_code &ec, size_t bytes_transferred) { 545 | session->connection->cancel_timeout(); 546 | auto lock = session->connection->handler_runner->continue_lock(); 547 | if(!lock) 548 | return; 549 | if((!ec || ec == asio::error::not_found) && session->request->streambuf.size() == session->request->streambuf.max_size()) { 550 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 551 | response->write(StatusCode::client_error_payload_too_large); 552 | response->send(); 553 | if(this->on_error) 554 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 555 | return; 556 | } 557 | if(!ec) { 558 | std::string line; 559 | getline(session->request->content, line); 560 | bytes_transferred -= line.size() + 1; 561 | line.pop_back(); 562 | unsigned long length = 0; 563 | try { 564 | length = stoul(line, 0, 16); 565 | } 566 | catch(...) { 567 | if(this->on_error) 568 | this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); 569 | return; 570 | } 571 | 572 | auto num_additional_bytes = session->request->streambuf.size() - bytes_transferred; 573 | 574 | if((2 + length) > num_additional_bytes) { 575 | session->connection->set_timeout(config.timeout_content); 576 | asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(2 + length - num_additional_bytes), [this, session, chunks_streambuf, length](const error_code &ec, size_t /*bytes_transferred*/) { 577 | session->connection->cancel_timeout(); 578 | auto lock = session->connection->handler_runner->continue_lock(); 579 | if(!lock) 580 | return; 581 | if(!ec) { 582 | if(session->request->streambuf.size() == session->request->streambuf.max_size()) { 583 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 584 | response->write(StatusCode::client_error_payload_too_large); 585 | response->send(); 586 | if(this->on_error) 587 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 588 | return; 589 | } 590 | this->read_chunked_transfer_encoded_chunk(session, chunks_streambuf, length); 591 | } 592 | else if(this->on_error) 593 | this->on_error(session->request, ec); 594 | }); 595 | } 596 | else 597 | this->read_chunked_transfer_encoded_chunk(session, chunks_streambuf, length); 598 | } 599 | else if(this->on_error) 600 | this->on_error(session->request, ec); 601 | }); 602 | } 603 | 604 | void read_chunked_transfer_encoded_chunk(const std::shared_ptr &session, const std::shared_ptr &chunks_streambuf, unsigned long length) { 605 | std::ostream tmp_stream(chunks_streambuf.get()); 606 | if(length > 0) { 607 | std::unique_ptr buffer(new char[length]); 608 | session->request->content.read(buffer.get(), static_cast(length)); 609 | tmp_stream.write(buffer.get(), static_cast(length)); 610 | if(chunks_streambuf->size() == chunks_streambuf->max_size()) { 611 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 612 | response->write(StatusCode::client_error_payload_too_large); 613 | response->send(); 614 | if(this->on_error) 615 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 616 | return; 617 | } 618 | } 619 | 620 | // Remove "\r\n" 621 | session->request->content.get(); 622 | session->request->content.get(); 623 | 624 | if(length > 0) 625 | read_chunked_transfer_encoded(session, chunks_streambuf); 626 | else { 627 | if(chunks_streambuf->size() > 0) { 628 | std::ostream ostream(&session->request->streambuf); 629 | ostream << chunks_streambuf.get(); 630 | } 631 | this->find_resource(session); 632 | } 633 | } 634 | 635 | void find_resource(const std::shared_ptr &session) { 636 | // Upgrade connection 637 | if(on_upgrade) { 638 | auto it = session->request->header.find("Upgrade"); 639 | if(it != session->request->header.end()) { 640 | // remove connection from connections 641 | { 642 | std::unique_lock lock(*connections_mutex); 643 | auto it = connections->find(session->connection.get()); 644 | if(it != connections->end()) 645 | connections->erase(it); 646 | } 647 | 648 | on_upgrade(session->connection->socket, session->request); 649 | return; 650 | } 651 | } 652 | // Find path- and method-match, and call write 653 | for(auto ®ex_method : resource) { 654 | auto it = regex_method.second.find(session->request->method); 655 | if(it != regex_method.second.end()) { 656 | regex::smatch sm_res; 657 | if(regex::regex_match(session->request->path, sm_res, regex_method.first)) { 658 | session->request->path_match = std::move(sm_res); 659 | write(session, it->second); 660 | return; 661 | } 662 | } 663 | } 664 | auto it = default_resource.find(session->request->method); 665 | if(it != default_resource.end()) 666 | write(session, it->second); 667 | } 668 | 669 | void write(const std::shared_ptr &session, 670 | std::function::Response>, std::shared_ptr::Request>)> &resource_function) { 671 | session->connection->set_timeout(config.timeout_content); 672 | auto response = std::shared_ptr(new Response(session, config.timeout_content), [this](Response *response_ptr) { 673 | auto response = std::shared_ptr(response_ptr); 674 | response->send([this, response](const error_code &ec) { 675 | if(!ec) { 676 | if(response->close_connection_after_response) 677 | return; 678 | 679 | auto range = response->session->request->header.equal_range("Connection"); 680 | for(auto it = range.first; it != range.second; it++) { 681 | if(case_insensitive_equal(it->second, "close")) 682 | return; 683 | else if(case_insensitive_equal(it->second, "keep-alive")) { 684 | auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection); 685 | this->read(new_session); 686 | return; 687 | } 688 | } 689 | if(response->session->request->http_version >= "1.1") { 690 | auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection); 691 | this->read(new_session); 692 | return; 693 | } 694 | } 695 | else if(this->on_error) 696 | this->on_error(response->session->request, ec); 697 | }); 698 | }); 699 | 700 | try { 701 | resource_function(response, session->request); 702 | } 703 | catch(const std::exception &) { 704 | if(on_error) 705 | on_error(session->request, make_error_code::make_error_code(errc::operation_canceled)); 706 | return; 707 | } 708 | } 709 | }; 710 | 711 | template 712 | class Server : public ServerBase {}; 713 | 714 | using HTTP = asio::ip::tcp::socket; 715 | 716 | template <> 717 | class Server : public ServerBase { 718 | public: 719 | Server() noexcept : ServerBase::ServerBase(80) {} 720 | 721 | protected: 722 | void accept() override { 723 | auto connection = create_connection(*io_service); 724 | 725 | acceptor->async_accept(*connection->socket, [this, connection](const error_code &ec) { 726 | auto lock = connection->handler_runner->continue_lock(); 727 | if(!lock) 728 | return; 729 | 730 | // Immediately start accepting a new connection (unless io_service has been stopped) 731 | if(ec != asio::error::operation_aborted) 732 | this->accept(); 733 | 734 | auto session = std::make_shared(config.max_request_streambuf_size, connection); 735 | 736 | if(!ec) { 737 | asio::ip::tcp::no_delay option(true); 738 | error_code ec; 739 | session->connection->socket->set_option(option, ec); 740 | 741 | this->read(session); 742 | } 743 | else if(this->on_error) 744 | this->on_error(session->request, ec); 745 | }); 746 | } 747 | }; 748 | } // namespace SimpleWeb 749 | 750 | #endif /* SERVER_HTTP_HPP */ 751 | --------------------------------------------------------------------------------