556 lines
15 KiB
C
556 lines
15 KiB
C
#include <getopt.h>
|
|
#include <easy_io.h>
|
|
#include <easy_string.h>
|
|
#include <easy_http_handler.h>
|
|
#include <fcntl.h>
|
|
#include <sys/syscall.h>
|
|
#include <arpa/inet.h>
|
|
#include <linux/if.h>
|
|
#include <sys/ioctl.h>
|
|
#include <easy_socket.h>
|
|
#if HAVE_SYS_INOTIFY_H
|
|
#include <sys/inotify.h>
|
|
#endif
|
|
|
|
// 命令行参数结构
|
|
#define HTTP_STATUS_PATH_LEN 128
|
|
#define HTTP_STATUS_MAX_BUFFER 16384
|
|
typedef struct http_status_dirent_t {
|
|
long d_ino;
|
|
__kernel_off_t d_off;
|
|
unsigned short d_reclen;
|
|
char d_name[0];
|
|
} http_status_dirent_t;
|
|
typedef struct http_status_file_t {
|
|
char *name;
|
|
easy_hash_list_t node;
|
|
} http_status_file_t;
|
|
typedef struct http_status_vip_t {
|
|
uint64_t addr;
|
|
easy_hash_list_t node;
|
|
} http_status_vip_t;
|
|
typedef struct cmdline_param {
|
|
char host[64];
|
|
char monitor_dir[HTTP_STATUS_PATH_LEN];
|
|
char pidfile[HTTP_STATUS_PATH_LEN];
|
|
int port;
|
|
int daemon;
|
|
uint64_t qps;
|
|
|
|
// watcher
|
|
ev_timer vip_watcher;
|
|
ev_io dir_watcher;
|
|
int ifd;
|
|
|
|
// dir
|
|
int dir_cnt;
|
|
uint64_t dir_code;
|
|
easy_pool_t *dir_pool;
|
|
easy_hash_t *dir_table;
|
|
// vip
|
|
int vip_cnt;
|
|
uint64_t vip_code;
|
|
easy_pool_t *vip_pool;
|
|
easy_hash_t *vip_table;
|
|
} cmdline_param;
|
|
|
|
/*************************************************************************************************
|
|
* 函数定义部分
|
|
*************************************************************************************************/
|
|
cmdline_param cp;
|
|
static int parse_cmd_line(int argc, char *const argv[], cmdline_param *cp);
|
|
static int http_status_on_process(easy_request_t *r);
|
|
static int http_status_vip_exist(char *vip);
|
|
static int http_status_file_exist(char *statusfile);
|
|
static int http_status_file_cmp(const void *a, const void *b);
|
|
static void http_status_read_vip(struct ev_loop *loop, ev_timer *w, int revents);
|
|
static void http_status_read_dir(struct ev_loop *loop, ev_io *w, int revents);
|
|
static void print_usage(char *prog_name);
|
|
static void daemonize(const char *pidfile);
|
|
static int http_status_start_notify();
|
|
|
|
/**
|
|
* 程序入口
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
easy_listen_t *listen;
|
|
easy_io_handler_pt io_handler;
|
|
int ret;
|
|
|
|
// default
|
|
memset(&cp, 0, sizeof(cmdline_param));
|
|
lnprintf(cp.monitor_dir, HTTP_STATUS_PATH_LEN, "/var/run/http_status");
|
|
lnprintf(cp.pidfile, HTTP_STATUS_PATH_LEN, "/var/run/http_status.pid");
|
|
cp.port = 8001;
|
|
cp.ifd = -1;
|
|
|
|
// parse cmd line
|
|
if (parse_cmd_line(argc, argv, &cp) == EASY_ERROR)
|
|
return EASY_ERROR;
|
|
|
|
// 检查必需参数
|
|
if (cp.port == 0) {
|
|
print_usage(argv[0]);
|
|
return EASY_ERROR;
|
|
}
|
|
|
|
// daemon
|
|
if (cp.daemon)
|
|
daemonize(cp.pidfile);
|
|
|
|
// 对easy_io初始化, 设置io的线程数, file的线程数
|
|
if (!easy_io_create(1)) {
|
|
easy_error_log("easy_io_init error.\n");
|
|
return EASY_ERROR;
|
|
}
|
|
|
|
// 为监听端口设置处理函数,并增加一个监听端口
|
|
memset(&io_handler, 0, sizeof(io_handler));
|
|
io_handler.decode = easy_http_server_on_decode;
|
|
io_handler.encode = easy_http_server_on_encode;
|
|
io_handler.process = http_status_on_process;
|
|
|
|
if ((listen = easy_io_add_listen(NULL, cp.port, &io_handler)) == NULL) {
|
|
easy_error_log("easy_io_add_listen error, port: %d, %s\n",
|
|
cp.port, strerror(errno));
|
|
return EASY_ERROR;
|
|
} else {
|
|
easy_error_log("listen start, port = %d\n", cp.port);
|
|
}
|
|
|
|
// 加入notify
|
|
http_status_start_notify();
|
|
|
|
// 起线程并开始
|
|
if (easy_io_start()) {
|
|
easy_error_log("easy_io_start error.\n");
|
|
return EASY_ERROR;
|
|
}
|
|
|
|
// 等待线程退出
|
|
ret = easy_io_wait();
|
|
easy_io_destroy();
|
|
|
|
if (cp.dir_pool) easy_pool_destroy(cp.dir_pool);
|
|
|
|
if (cp.vip_pool) easy_pool_destroy(cp.vip_pool);
|
|
|
|
if (cp.ifd >= 0) close(cp.ifd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 命令行帮助
|
|
*/
|
|
static void print_usage(char *prog_name)
|
|
{
|
|
fprintf(stderr, "%s [-p port] [-i ip] [-d] [-m monitor_dir]\n"
|
|
" -p, --port port to listen, default: 8001\n"
|
|
" -i, --ip ip address to bind, default: 0.0.0.0\n"
|
|
" -d, --daemon fork the daemon into the background\n"
|
|
" -m, --monitor monitor directory, default: /var/run/http_status\n"
|
|
" --pidfile pid file, default: /var/run/http_status.pid\n"
|
|
" -h, --help display this help and exit\n"
|
|
" -V, --version version and build time\n\n"
|
|
"eg: %s -p 5000\n\n", prog_name, prog_name);
|
|
}
|
|
|
|
/**
|
|
* 解析命令行
|
|
*/
|
|
static int parse_cmd_line(int argc, char *const argv[], cmdline_param *cp)
|
|
{
|
|
int opt;
|
|
const char *opt_string = "hVp:i:m:d1:";
|
|
struct option long_opts[] = {
|
|
{"port", 1, NULL, 'p'},
|
|
{"ip", 1, NULL, 'i'},
|
|
{"daemon", 0, NULL, 'd'},
|
|
{"monitor_dir", 1, NULL, 'm'},
|
|
{"pidfile", 1, NULL, '1'},
|
|
{"help", 0, NULL, 'h'},
|
|
{"version", 0, NULL, 'V'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
opterr = 0;
|
|
|
|
while ((opt = getopt_long(argc, argv, opt_string, long_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'p':
|
|
cp->port = atoi(optarg);
|
|
break;
|
|
|
|
case 'i':
|
|
lnprintf(cp->host, 64, "%s", optarg);
|
|
break;
|
|
|
|
case 'd':
|
|
cp->daemon = 1;
|
|
break;
|
|
|
|
case 'm':
|
|
if (realpath(optarg, cp->monitor_dir) == NULL) {
|
|
cp->monitor_dir[0] = '\0';
|
|
fprintf(stderr, "directory: %s not found.\n", optarg);
|
|
return EASY_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
case '1':
|
|
lnprintf(cp->pidfile, HTTP_STATUS_PATH_LEN, "%s", optarg);
|
|
break;
|
|
|
|
case 'V':
|
|
fprintf(stderr, "BUILD_TIME: %s %s\n", __DATE__, __TIME__);
|
|
return EASY_ERROR;
|
|
|
|
case 'h':
|
|
print_usage(argv[0]);
|
|
return EASY_ERROR;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return EASY_OK;
|
|
}
|
|
|
|
/**
|
|
* 处理函数
|
|
*/
|
|
static int http_status_on_process(easy_request_t *r)
|
|
{
|
|
char *service, *port, *vip;
|
|
char *k, *v, *nk;
|
|
char statusfile[HTTP_STATUS_PATH_LEN];
|
|
easy_http_request_t *p;
|
|
|
|
r->opacket = p = (easy_http_request_t *)r->ipacket;
|
|
|
|
// qps
|
|
if (p->str_path.len == 4 && memcmp(p->str_path.data, "/qps", 4) == 0) {
|
|
easy_io_thread_t *ioth = (easy_io_thread_t *)easy_thread_pool_index(easy_io_var.io_thread_pool, 0);
|
|
double qps = cp.qps / (ev_now(ioth->loop) - easy_io_var.start_time);
|
|
easy_http_request_printf(p, "QPS: %f\n", qps);
|
|
return EASY_OK;
|
|
}
|
|
|
|
// path
|
|
easy_http_add_header(r->ms->pool, p->headers_out, "Cache-Control", "private");
|
|
|
|
if (p->str_path.len != 7 || memcmp(p->str_path.data, "/status", 7)) {
|
|
easy_buf_string_set(&p->status_line, "404 Not Found - No such function defined!");
|
|
return EASY_OK;
|
|
}
|
|
|
|
// parse SERVICE, PORT, VIP
|
|
service = "status.html";
|
|
port = vip = NULL;
|
|
k = p->str_query_string.data;
|
|
|
|
while(k) {
|
|
if ((nk = strchr(k, '&')) != NULL)
|
|
(*nk ++) = '\0';
|
|
|
|
if ((v = strchr(k, '=')) != NULL) {
|
|
(*v ++) = '\0';
|
|
easy_string_toupper(k);
|
|
|
|
if (strcmp(k, "SERVICE") == 0)
|
|
service = v;
|
|
else if (strcmp(k, "PORT") == 0)
|
|
port = v;
|
|
else if (strcmp(k, "VIP") == 0)
|
|
vip = v;
|
|
}
|
|
|
|
k = nk;
|
|
}
|
|
|
|
// 多端口
|
|
if (port) {
|
|
lnprintf(statusfile, HTTP_STATUS_PATH_LEN, "%s.%s", service, port);
|
|
service = statusfile;
|
|
}
|
|
|
|
// 文件存在否?
|
|
char *msg = "404 Not Found - Service not aviliable!";
|
|
|
|
if (cp.dir_table && http_status_file_exist(service)) {
|
|
if (vip) {
|
|
if (http_status_vip_exist(vip)) {
|
|
msg = "200 OK - Service active within VIP!";
|
|
} else {
|
|
msg = "404 Not Found - Service active but not in VIP!";
|
|
}
|
|
} else {
|
|
msg = "200 OK - Service active!";
|
|
}
|
|
}
|
|
|
|
cp.qps ++;
|
|
easy_buf_string_set(&p->status_line, msg);
|
|
//easy_buf_string_set(&p->output_line, msg);
|
|
easy_http_request_printf(p, msg);
|
|
|
|
return EASY_OK;
|
|
}
|
|
|
|
/**
|
|
* http_status_file_cmp
|
|
*/
|
|
static int http_status_file_cmp(const void *a, const void *b)
|
|
{
|
|
http_status_file_t *f = (http_status_file_t *) b;
|
|
return strcmp((char *)a, f->name);
|
|
}
|
|
|
|
/**
|
|
* 文件存在否?
|
|
*/
|
|
static int http_status_file_exist(char *statusfile)
|
|
{
|
|
uint64_t key;
|
|
key = easy_hash_code(statusfile, strlen(statusfile), 5);
|
|
return easy_hash_find_ex(cp.dir_table, key, http_status_file_cmp, statusfile) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* VIP存在否?
|
|
*/
|
|
static int http_status_vip_exist(char *vip)
|
|
{
|
|
char *s, *e;
|
|
uint64_t ip;
|
|
int ret = 0;
|
|
|
|
if (cp.vip_table == NULL)
|
|
return 0;
|
|
|
|
s = vip;
|
|
|
|
while(s) {
|
|
if ((e = strchr(s, ',')) != NULL)
|
|
(*e++) = '\0';
|
|
|
|
if ((ip = inet_addr(s)) == INADDR_NONE)
|
|
return 0;
|
|
|
|
if (easy_hash_find(cp.vip_table, ip) == NULL)
|
|
return 0;
|
|
|
|
ret ++;
|
|
s = e;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 检查目录及vip
|
|
*/
|
|
void http_status_check_dirvip(struct ev_loop *loop, ev_timer *w, int revents)
|
|
{
|
|
static int cnt = 0;
|
|
http_status_read_dir(NULL, NULL, 0);
|
|
|
|
if (cnt ++ % 30 == 0) http_status_read_vip(NULL, NULL, 0);
|
|
}
|
|
|
|
static int http_status_start_notify()
|
|
{
|
|
easy_io_thread_t *ioth;
|
|
|
|
#if HAVE_SYS_INOTIFY_H
|
|
int fd, wd;
|
|
|
|
fd = inotify_init();
|
|
|
|
if (fd < 0) {
|
|
easy_error_log("Fail to initialize inotify.\n");
|
|
return EASY_ERROR;
|
|
}
|
|
|
|
wd = inotify_add_watch(fd, cp.monitor_dir, IN_MOVE | IN_CREATE | IN_DELETE | IN_UNMOUNT);
|
|
|
|
if (wd < 0) {
|
|
close(fd);
|
|
easy_error_log("Can't add watch for %s.\n", cp.monitor_dir);
|
|
return EASY_ERROR;
|
|
}
|
|
|
|
cp.ifd = fd;
|
|
easy_socket_non_blocking(fd);
|
|
|
|
// 定时检测VIP地址及目录
|
|
ioth = (easy_io_thread_t *)easy_thread_pool_index(easy_io_var.io_thread_pool, 0);
|
|
ev_io_init (&cp.dir_watcher, http_status_read_dir, fd, EV_READ);
|
|
ev_io_start (ioth->loop, &cp.dir_watcher);
|
|
ev_timer_init (&cp.vip_watcher, http_status_read_vip, 0., 60.0);
|
|
ev_timer_start (ioth->loop, &cp.vip_watcher);
|
|
easy_baseth_on_wakeup(ioth);
|
|
#else
|
|
ioth = (easy_io_thread_t *)easy_thread_pool_index(easy_io_var.io_thread_pool, 0);
|
|
ev_timer_init (&cp.vip_watcher, http_status_check_dirvip, 0., 2.0);
|
|
ev_timer_start (ioth->loop, &cp.vip_watcher);
|
|
easy_baseth_on_wakeup(ioth);
|
|
#endif
|
|
|
|
// 先加载一次
|
|
http_status_read_dir(NULL, NULL, 0);
|
|
http_status_read_vip(NULL, NULL, 0);
|
|
|
|
return EASY_OK;
|
|
}
|
|
|
|
/**
|
|
* 检查文件是否存在
|
|
*/
|
|
static void http_status_read_dir(struct ev_loop *loop, ev_io *w, int revents)
|
|
{
|
|
char buffer[HTTP_STATUS_MAX_BUFFER], *p;
|
|
int fd, cnt, n;
|
|
uint64_t dir_code;
|
|
|
|
easy_debug_log("http_status_read_dir: %s", cp.monitor_dir);
|
|
|
|
// 读掉notify里的数据
|
|
if (w && cp.ifd >= 0) {
|
|
do {
|
|
n = read(cp.ifd, buffer, HTTP_STATUS_MAX_BUFFER);
|
|
} while(n == HTTP_STATUS_MAX_BUFFER);
|
|
}
|
|
|
|
// 读目录
|
|
fd = open(cp.monitor_dir, O_RDONLY | O_NONBLOCK | O_DIRECTORY);
|
|
cnt = syscall(SYS_getdents, fd, buffer, HTTP_STATUS_MAX_BUFFER);
|
|
close(fd);
|
|
|
|
// 比较hashcode
|
|
dir_code = easy_hash_code(buffer, cnt, 5);
|
|
|
|
if (cp.dir_cnt == cnt && cp.dir_code == dir_code)
|
|
return;
|
|
|
|
if (cp.dir_pool) easy_pool_destroy(cp.dir_pool);
|
|
|
|
cp.dir_cnt = cnt;
|
|
cp.dir_code = dir_code;
|
|
cp.dir_pool = easy_pool_create(2048);
|
|
n = offsetof(http_status_file_t, node);
|
|
cp.dir_table = easy_hash_create(cp.dir_pool, 128, n);
|
|
|
|
// 复制
|
|
p = (char *)easy_pool_alloc(cp.dir_pool, cnt);
|
|
memcpy(p, buffer, cnt);
|
|
|
|
// 建立table
|
|
n = 0;
|
|
http_status_dirent_t *dirp;
|
|
http_status_file_t *file;
|
|
|
|
while(n < cnt) {
|
|
dirp = (http_status_dirent_t *)(p + n);
|
|
|
|
if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) {
|
|
file = (http_status_file_t *)easy_pool_alloc(cp.dir_pool, sizeof(http_status_file_t));
|
|
file->name = dirp->d_name;
|
|
easy_debug_log("found file: %s\n", file->name);
|
|
dir_code = easy_hash_code(file->name, strlen(file->name), 5);
|
|
easy_hash_add(cp.dir_table, dir_code, &file->node);
|
|
}
|
|
|
|
// next
|
|
n += dirp->d_reclen;
|
|
}
|
|
}
|
|
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
static void http_status_read_vip(struct ev_loop *loop, ev_timer *w, int revents)
|
|
{
|
|
int fd, ret __attribute__ ((unused)), n;
|
|
struct ifconf ifc;
|
|
struct ifreq *ifr;
|
|
uint64_t vip_code;
|
|
easy_addr_t ip;
|
|
|
|
ret = 0;
|
|
|
|
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
|
|
return;
|
|
|
|
ifc.ifc_len = sizeof(struct ifreq) * 64;
|
|
char buffer[ifc.ifc_len];
|
|
ifc.ifc_buf = buffer;
|
|
|
|
if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0)
|
|
goto error_exit;
|
|
|
|
// 比较hashcode
|
|
vip_code = easy_hash_code(buffer, ifc.ifc_len, 5);
|
|
|
|
if (cp.vip_cnt == ifc.ifc_len && cp.vip_code == vip_code)
|
|
goto error_exit;
|
|
|
|
if (cp.vip_pool) easy_pool_destroy(cp.vip_pool);
|
|
|
|
cp.vip_cnt = ifc.ifc_len;
|
|
cp.vip_code = vip_code;
|
|
cp.vip_pool = easy_pool_create(0);
|
|
n = offsetof(http_status_vip_t, node);
|
|
cp.vip_table = easy_hash_create(cp.vip_pool, 64, n);
|
|
|
|
// 只从中lo:
|
|
ifr = ifc.ifc_req;
|
|
http_status_vip_t *vip;
|
|
|
|
for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq), ifr++) {
|
|
if (strncmp(ifr->ifr_name, "lo", 2))
|
|
continue;
|
|
|
|
memcpy(&ip, &(ifr->ifr_addr), sizeof(uint64_t));
|
|
|
|
if (ioctl(fd, SIOCGIFNETMASK, ifr) || (~ * ((uint64_t *)&ifr->ifr_addr) >> 32))
|
|
continue;
|
|
|
|
vip = (http_status_vip_t *)easy_pool_alloc(cp.vip_pool, sizeof(http_status_vip_t));
|
|
easy_debug_log("found vip: %s\n", easy_inet_addr_to_str(&ip, buffer, 32));
|
|
vip->addr = ip.u.addr;
|
|
easy_hash_add(cp.vip_table, vip->addr, &vip->node);
|
|
}
|
|
|
|
error_exit:
|
|
close(fd);
|
|
}
|
|
|
|
static void daemonize(const char *pidfile)
|
|
{
|
|
int fd, pid;
|
|
|
|
pid = fork();
|
|
|
|
if (pid < 0) exit(1);
|
|
|
|
if (pid > 0) exit(0);
|
|
|
|
setsid();
|
|
easy_ignore(chdir("/"));
|
|
easy_ignore(daemon(0, 0));
|
|
|
|
if ((fd = open(pidfile, O_RDWR | O_CREAT, 0640)) >= 0) {
|
|
char str[32];
|
|
lnprintf(str, 32, "%d\n", getpid());
|
|
easy_ignore(write(fd, str, strlen(str)));
|
|
close(fd);
|
|
}
|
|
|
|
}
|