├── check ├── supervise ├── olivehc_control ├── olivehc_control.conf ├── nginx-jstore ├── config ├── README └── ngx_http_jstore_filter_module.c ├── utils ├── Makefile ├── socktcp.h ├── slab.h ├── hash.h ├── idx_pointer.h ├── string.h ├── epoll.h ├── timer.h ├── slab.c ├── ipbucket.h ├── hash.c ├── socktcp.c ├── timer.c └── list.h ├── event.h ├── make_device.sh ├── conf.h ├── format.h ├── http.h ├── Makefile ├── olivehc.conf.sample ├── worker.h ├── olivehc_hit.sh ├── device.h ├── request.h ├── event.c ├── olivehc.h ├── server.h ├── format.c ├── README.md ├── olivehc.c ├── worker.c ├── http.c ├── conf.c ├── request.c ├── device.c └── server.c /check: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollcp/olivehc/HEAD/check -------------------------------------------------------------------------------- /supervise: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollcp/olivehc/HEAD/supervise -------------------------------------------------------------------------------- /olivehc_control: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollcp/olivehc/HEAD/olivehc_control -------------------------------------------------------------------------------- /olivehc_control.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollcp/olivehc/HEAD/olivehc_control.conf -------------------------------------------------------------------------------- /nginx-jstore/config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_jstore_filter_module 2 | HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_jstore_filter_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_jstore_filter_module.c" 4 | -------------------------------------------------------------------------------- /utils/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -g -O -pipe -Wall 3 | 4 | SOURCES = $(wildcard *.c) 5 | OBJS = $(patsubst %.c,%.o,$(SOURCES)) 6 | 7 | TARGET : $(OBJS) 8 | 9 | clean : 10 | rm -f $(OBJS) depends 11 | 12 | depends : $(SOURCES) 13 | $(CC) -MM $(CFLAGS) *.c > depends 14 | 15 | include depends 16 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils for request event management. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_EVENT_H_ 9 | #define _OHC_EVENT_H_ 10 | 11 | #include "olivehc.h" 12 | 13 | int event_add_read(ohc_request_t *r, req_handler_f *handler); 14 | int event_add_write(ohc_request_t *r, req_handler_f *handler); 15 | int event_add_keepalive(ohc_request_t *r, req_handler_f *handler); 16 | void event_del(ohc_request_t *r); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /utils/socktcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils for TCP socket. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_SOCKTCP_H_ 9 | #define _OHC_SOCKTCP_H_ 10 | 11 | int tcp_bind(unsigned short port); 12 | int tcp_listen(int fd); 13 | int tcp_accept(int fd, struct sockaddr_in *client); 14 | 15 | int set_sndbuf(int fd, int value); 16 | int set_rcvbuf(int fd, int value); 17 | int set_defer_accept(int fd, int value); 18 | int set_cork(int fd, int cork); 19 | 20 | int set_nonblock(int fd); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /make_device.sh: -------------------------------------------------------------------------------- 1 | #1048576 = 1024 * 1024 2 | dd if=/dev/zero of=./data/cache_file0 bs=10M count=10240 3 | dd if=/dev/zero of=./data/cache_file1 bs=10M count=10240 4 | dd if=/dev/zero of=./data/cache_file2 bs=10M count=10240 5 | dd if=/dev/zero of=./data/cache_file3 bs=10M count=10240 6 | dd if=/dev/zero of=./data/cache_file4 bs=10M count=10240 7 | dd if=/dev/zero of=./data/cache_file5 bs=10M count=10240 8 | dd if=/dev/zero of=./data/cache_file6 bs=10M count=10240 9 | dd if=/dev/zero of=./data/cache_file7 bs=1G count=0 seek=100 10 | -------------------------------------------------------------------------------- /conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Parse configure file. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_CONF_H_ 9 | #define _OHC_CONF_H_ 10 | 11 | #include "olivehc.h" 12 | 13 | struct ohc_conf_s { 14 | int threads; 15 | int device_badblock_percent; 16 | ohc_flag_t device_check_270G; 17 | time_t quit_timeout; 18 | 19 | char error_log[PATH_LENGTH]; 20 | FILE *error_filp; 21 | 22 | struct list_head servers; 23 | struct list_head devices; 24 | 25 | }; 26 | 27 | ohc_conf_t *conf_parse(const char *filename); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /utils/slab.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * A simple slab. 4 | * 5 | * Auther: Wu Bingzheng 6 | * 7 | **/ 8 | 9 | #ifndef _SLAB_H_ 10 | #define _SLAB_H_ 11 | 12 | #include "list.h" 13 | 14 | typedef struct ohc_slab_s { 15 | struct hlist_head block_head; 16 | unsigned item_size; 17 | } ohc_slab_t; 18 | 19 | /* make sure: sizeof(type) >= sizeof(struct hlist_node) */ 20 | #define OHC_SLAB_INIT(type) \ 21 | {HLIST_HEAD_INIT, sizeof(type)+sizeof(ohc_slab_t *)} 22 | 23 | void *slab_alloc(ohc_slab_t *slab); 24 | void slab_free(void *p); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /format.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Manage device file. Load items from device when 3 | * starts, and store items to device when quits. 4 | * 5 | * Author: Wu Bingzheng 6 | * 7 | */ 8 | 9 | #ifndef _OHC_FORMAT_H_ 10 | #define _OHC_FORMAT_H_ 11 | 12 | #include "olivehc.h" 13 | 14 | /* ohc_item_t on disk */ 15 | struct ohc_format_item_s { 16 | unsigned char hash_id[16]; 17 | off_t offset; 18 | uint32_t length; 19 | int32_t expire; 20 | unsigned short headers_len; 21 | short server_index; 22 | }; 23 | 24 | int format_store_device(unsigned short *ports, ohc_device_t *device); 25 | int format_load_device(ohc_device_t *device); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /utils/hash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Linear dynamic hashing 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _HASH_H_ 9 | #define _HASH_H_ 10 | 11 | #include "list.h" 12 | 13 | typedef struct ohc_hash_s ohc_hash_t; 14 | 15 | typedef struct { 16 | unsigned char id[16]; 17 | struct hlist_node node; 18 | } ohc_hash_node_t; 19 | 20 | ohc_hash_t *hash_init(); 21 | void hash_destroy(ohc_hash_t *hash); 22 | 23 | void hash_add(ohc_hash_t *hash, ohc_hash_node_t *hnode, unsigned char *str, int len); 24 | ohc_hash_node_t *hash_get(ohc_hash_t *hash, unsigned char *str, int len, unsigned char *hash_id); 25 | void hash_del(ohc_hash_t *hash, ohc_hash_node_t *hnode); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /http.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Parse HTTP request, make HTTP response. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_HTTP_H_ 9 | #define _OHC_HTTP_H_ 10 | 11 | #include "olivehc.h" 12 | #include "request.h" 13 | 14 | enum http_methods { 15 | OHC_HTTP_METHOD_GET, 16 | OHC_HTTP_METHOD_HEAD, 17 | OHC_HTTP_METHOD_PUT, 18 | OHC_HTTP_METHOD_POST, 19 | OHC_HTTP_METHOD_PURGE, 20 | OHC_HTTP_METHOD_DELETE, 21 | OHC_HTTP_METHOD_INVALID, 22 | }; 23 | 24 | struct http_method_s { 25 | string_t str; 26 | void *data; 27 | }; 28 | extern struct http_method_s http_methods[]; 29 | 30 | #define RANGE_NO_SET -1 31 | 32 | int http_request_parse(ohc_request_t *r); 33 | string_t *http_code_page(int code); 34 | ssize_t http_decode_uri(const char *uri, ssize_t len, char *output); 35 | ssize_t http_make_200_response_header(ssize_t content_length, char *output); 36 | ssize_t http_make_206_response_header(ssize_t range_start, ssize_t range_end, 37 | ssize_t body_len, char *output); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -g -O -pipe -Wall 3 | LINK = gcc 4 | LDFLAGS = -lpthread -lssl 5 | 6 | SOURCES = $(wildcard *.c) 7 | OBJS = $(patsubst %.c,%.o,$(SOURCES)) 8 | 9 | TARGET = olivehc 10 | 11 | olivehc : $(OBJS) 12 | make -C utils 13 | $(LINK) $(LDFLAGS) -o $@ $^ utils/*.o 14 | # install: 15 | mkdir -p output/bin && \ 16 | mkdir -p output/conf && \ 17 | mkdir -p output/log && \ 18 | mkdir -p output/data 19 | 20 | cp olivehc output/bin && \ 21 | cp olivehc_hit.sh output/bin &&\ 22 | cp olivehc_control output/bin &&\ 23 | cp make_device.sh output/bin &&\ 24 | cp check output/bin &&\ 25 | cp supervise output/bin 26 | 27 | test -f output/conf/olivehc.conf || \ 28 | cp olivehc.conf.sample output/conf/olivehc.conf 29 | cp olivehc_control output/conf 30 | 31 | clean : 32 | make -C utils clean 33 | rm -f $(OBJS) depends olivehc output 34 | 35 | depends : $(SOURCES) 36 | $(CC) -MM $(CFLAGS) *.c > depends 37 | 38 | include depends 39 | -------------------------------------------------------------------------------- /utils/idx_pointer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Use index as pointer, in order to save memory. 4 | * 5 | * Auther: Wu Bingzheng 6 | * 7 | **/ 8 | 9 | #ifndef _IDX_POINTER_H_ 10 | #define _IDX_POINTER_H_ 11 | 12 | #define IPT_ARRAY_SIZE 4096 13 | 14 | typedef struct { 15 | short lowest; 16 | void *array[IPT_ARRAY_SIZE]; 17 | } idx_pointer_t; 18 | 19 | #define IDX_POINTER_INIT() {0, {0,}} 20 | 21 | static inline void *idx_pointer_get(idx_pointer_t *ipt, short index) 22 | { 23 | return ipt->array[index]; 24 | } 25 | 26 | static inline short idx_pointer_add(idx_pointer_t *ipt, void *pointer) 27 | { 28 | short index = ipt->lowest; 29 | 30 | if(index >= IPT_ARRAY_SIZE) { 31 | return -1; 32 | } 33 | 34 | while(ipt->array[index] != NULL) { 35 | index++; 36 | } 37 | ipt->lowest = index + 1; 38 | 39 | ipt->array[index] = pointer; 40 | return index; 41 | } 42 | 43 | static inline void idx_pointer_delete(idx_pointer_t *ipt, short index) 44 | { 45 | ipt->array[index] = NULL; 46 | if(index < ipt->lowest) { 47 | ipt->lowest = index; 48 | } 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /olivehc.conf.sample: -------------------------------------------------------------------------------- 1 | # OliveHC configure file 2 | 3 | 4 | # include included/file/path 5 | 6 | # threads 4 7 | # quit_timeout 60 8 | # error_log error.log 9 | # device_badblock_percent 1 10 | # device_check_270G on 11 | 12 | device file/path1 13 | device file/path2 14 | 15 | listen 8535 16 | # capacity 0 17 | # connections_limit 1000 18 | # access_log access.log 19 | # keepalive_timeout 60 20 | # request_timeout 60 21 | # recv_timeout 60 22 | # send_timeout 60 23 | # expire_default 259200 # 3days 24 | # expire_force 0 25 | # item_max_size 100M 26 | # server_dump on 27 | # status_period 60 28 | # shutdown_if_not_store off 29 | # rcvbuf 0 30 | # sndbuf 0 31 | 32 | ## URL is used as the item key by default. If the following 3 33 | ## commands are set, the query string, Host header, and OHC-Key 34 | ## header will be used as part of the key respectively. 35 | # key_include_host off 36 | # key_include_query off 37 | # key_include_ohc_key off 38 | 39 | ## Filter long tail cold item. See README.md for detail. 40 | # passby_enable off 41 | # passby_begin_item_nr 1000000 42 | # passby_begin_consumed 100G 43 | # passby_limit_nr 1000000 44 | # passby_expire 3600 45 | 46 | listen 8838 47 | 48 | # vim: set tw=0 shiftwidth=4 tabstop=4 expandtab: 49 | -------------------------------------------------------------------------------- /worker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Worker thread management, and communication between master. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_WORKER_H_ 9 | #define _OHC_WORKER_H_ 10 | 11 | #include "olivehc.h" 12 | 13 | struct ohc_worker_s { 14 | struct list_head wnode; 15 | struct list_head working_requests; 16 | struct list_head blocked_requests; 17 | ohc_timer_t timer; 18 | 19 | time_t quit_time; 20 | pthread_t tid; 21 | int epoll_fd; 22 | 23 | /* only master update this, and worker check this. */ 24 | int request_nr; 25 | 26 | /** 27 | * two pipes. 28 | * 1. master write request into @dispatch_fd, worker read 29 | * @receive_fd to receive request; 30 | * 2. worker write the finished requests into @return_fd, 31 | * and master receive the requests from @receive_fd. 32 | **/ 33 | int recycle_fd; 34 | int dispatch_fd; 35 | int receive_fd; 36 | int return_fd; 37 | 38 | }; 39 | 40 | int worker_conf_check(ohc_conf_t *conf_cycle); 41 | void worker_conf_load(ohc_conf_t *conf_cycle); 42 | void worker_conf_rollback(ohc_conf_t *conf_cycle); 43 | 44 | void worker_quit(time_t quit_time); 45 | 46 | /* master calls */ 47 | int worker_request_dispatch(ohc_request_t *r, req_handler_f *handler); 48 | void worker_request_recycle(ohc_worker_t *worker); 49 | 50 | /* worker calls */ 51 | int worker_request_return(ohc_request_t *r, req_handler_f *handler); 52 | void worker_request_receive(ohc_worker_t *worker); 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /utils/string.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils for string. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_STRING_H 9 | #define _OHC_STRING_H 10 | 11 | #include 12 | #include 13 | 14 | #define bit_set(num, set) num |= 1L << (set) 15 | #define bit_clear(num, set) num &= ~(1L << (set)) 16 | #define bit_mask(num) ((1L << (num)) - 1) 17 | 18 | static inline int char2hex(char c) 19 | { 20 | if(c >= '0' && c <= '9') 21 | return c - '0'; 22 | c &= 0xdf; 23 | if(c >= 'A' && c <= 'F') 24 | return c - 'A' + 10; 25 | return -1; 26 | } 27 | 28 | static inline int numlen(long n) 29 | { 30 | int len = 0; 31 | if(n == 0) return 1; 32 | while(n) { 33 | len++; 34 | n /= 10; 35 | } 36 | return len; 37 | } 38 | 39 | static inline char *strwhite(char *str) 40 | { 41 | return str + strcspn(str, "\t\n "); 42 | } 43 | static inline char *strnonwhite(char *str) 44 | { 45 | return str + strspn(str, "\t\n "); 46 | } 47 | 48 | /* return 0~63. return 0 if n=0 or n=1 both. */ 49 | static inline int bit_highest(uint64_t n) 50 | { 51 | long index; 52 | __asm__ ( 53 | "bsrq %1, %0" 54 | :"=r"(index) 55 | :"m"(n) 56 | ); 57 | 58 | return index; 59 | } 60 | 61 | typedef struct { 62 | char *base; 63 | size_t len; 64 | } string_t; 65 | #define STRING_INIT(s) {s, sizeof(s) - 1} 66 | 67 | static inline char *strshow(string_t *str) 68 | { 69 | if(str->base == NULL) { 70 | return "-"; 71 | } 72 | str->base[str->len] = '\0'; 73 | return str->base; 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /nginx-jstore/README: -------------------------------------------------------------------------------- 1 | 2 | Because OliveHC doesn't have the ability to access the origin when request missing, so we need the WebServer to fetch the item from origin. Besides the WebServer can also act as load balance. 3 | 4 | We use Nginx as WebServer, and this is a Nginx filter module named `jstore`, which stores the missing item to OliveHC, by subrequest. 5 | 6 | This is tested on Nginx 1.3 ~ 1.4. (The origin's chunked response with not be stored in Nginx 1.2.) 7 | 8 | The Nginx configuration is as follows: 9 | 10 | upstream origin { 11 | server www.a.shifen.com; 12 | } 13 | upstream olivehc { 14 | server 127.0.0.1:9512; 15 | } 16 | server { 17 | listen 8099 default; 18 | proxy_intercept_errors on; 19 | proxy_ignore_client_abort on; 20 | proxy_set_header Host "$host"; 21 | 22 | location / { 23 | if ($request_method !~ (HEAD|GET)) { return 405; } 24 | proxy_pass http://olivehc; # request cache. return to client, if hit 25 | error_page 400 404 405 500 502 504 = @pass; # jump to @pass, if cache miss(404) or fail(5xx) 26 | } 27 | location @pass { 28 | proxy_http_version 1.1; # enable chunked 29 | proxy_pass http://origin; # request origin 30 | jstore @store; # create a subrequest to @store 31 | } 32 | location @store { 33 | proxy_pass http://olivehc; # store the missing item 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /olivehc_hit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #by:lidongye@baidu.com 3 | #hit ratio olivehc 4 | echo "========================================" 5 | echo "olivehc_hit_ratio" 6 | echo "usage:no argu" 7 | echo ' 8 | listen 9139 #image 9 | listen 9140 #cfg 10 | listen 9141 #beijing_image 11 | listen 9142 #beijing_cfg 12 | listen 9150 #2d 13 | listen 9151 #3d' 14 | echo "========================================" 15 | echo status | nc 127.1 5210 | \ 16 | grep -A 100 'listen' | \ 17 | awk '{if ($12 > 0) {print $12 "\t" $14 "\t" $2} }' | \ 18 | awk ' NR==1 {printf "%10s\t%10s\t%10s\t%10s\n",$1,$2,"ratio",$3} \ 19 | NR>=2 { ratio = $2*100/$1 ; printf "%10s\t%10s\t%10s%\t%10s\n",$1,$2,ratio,$3 } ' 20 | echo "========================================" 21 | echo status | nc 127.1 5210 | \ 22 | grep -A 100 'listen' | \ 23 | awk '{if ($19 > 0) {print $19 "\t" $21 "\t" $2} }' | \ 24 | awk ' NR==1 {printf "%10s\t%10s\t%10s\t%10s\n",$1,$2,"ratio",$3} \ 25 | NR>=2 { ratio = $2*100/$1 ; printf "%10s\t%10s\t%10s%\t%10s\n",$1,$2,ratio,$3 } ' 26 | 27 | echo "========================================" 28 | echo "last 60seconds hit ratio" 29 | echo status | nc 127.1 5210 | \ 30 | grep -A 100 'listen' | \ 31 | awk '{if ($13 > 0) {print $13 "\t" $15 "\t" $2} }' | \ 32 | awk ' NR==1 {printf "%10s\t%10s\t%10s\t%10s\n",$1,$2,"ratio",$3} \ 33 | NR>=2 { ratio = $2*100/$1 ; printf "%10s\t%10s\t%10s%\t%10s\n",$1,$2,ratio,$3 } ' 34 | 35 | echo "========================================" 36 | echo "device used percent:" 37 | echo status | nc 127.1 5210 | grep '++' | awk '{ratio = $4*100/$3 ; printf "%10s:%s%\n",$2,ratio}' 38 | -------------------------------------------------------------------------------- /utils/epoll.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Better interfaces for Epoll. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_EPOLL_H_ 9 | #define _OHC_EPOLL_H_ 10 | 11 | #include 12 | 13 | static inline int epoll_add(int epoll_fd, int sock_fd, uint32_t event, void *data) 14 | { 15 | struct epoll_event ev; 16 | ev.events = event; 17 | ev.data.ptr = data; 18 | return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &ev); 19 | } 20 | 21 | static inline int epoll_add_read(int epoll_fd, int sock_fd, void *data) 22 | { 23 | return epoll_add(epoll_fd, sock_fd, EPOLLIN, data); 24 | } 25 | 26 | static inline int epoll_add_write(int epoll_fd, int sock_fd, void *data) 27 | { 28 | return epoll_add(epoll_fd, sock_fd, EPOLLOUT, data); 29 | } 30 | 31 | static inline int epoll_add_rdwr(int epoll_fd, int sock_fd, void *data) 32 | { 33 | return epoll_add(epoll_fd, sock_fd, EPOLLIN | EPOLLOUT, data); 34 | } 35 | 36 | static inline int epoll_mod(int epoll_fd, int sock_fd, uint32_t event, void *data) 37 | { 38 | struct epoll_event ev; 39 | ev.events = event; 40 | ev.data.ptr = data; 41 | return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sock_fd, &ev); 42 | } 43 | 44 | static inline int epoll_mod_read(int epoll_fd, int sock_fd, void *data) 45 | { 46 | return epoll_mod(epoll_fd, sock_fd, EPOLLIN, data); 47 | } 48 | 49 | static inline int epoll_mod_write(int epoll_fd, int sock_fd, void *data) 50 | { 51 | return epoll_mod(epoll_fd, sock_fd, EPOLLOUT, data); 52 | } 53 | 54 | static inline int epoll_del(int epoll_fd, int sock_fd) 55 | { 56 | return epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, 0); 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /utils/timer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Timer and time management. 3 | * 4 | * Auther: Wu Bingzheng 5 | * 6 | **/ 7 | 8 | #ifndef _OHC_TIMER_H_ 9 | #define _OHC_TIMER_H_ 10 | 11 | #include 12 | #include "list.h" 13 | 14 | #define LEN_TIME_FARMAT_RFC1123 sizeof("Sun, 06 Nov 1994 08:49:23 GMT") 15 | #define LEN_TIME_FARMAT_LOG sizeof("1994-11-06 08:49:23") 16 | 17 | #define BIG_TIME 3638880000 18 | 19 | typedef struct { 20 | struct list_head tgroup_head; 21 | struct list_head expires; 22 | 23 | time_t now; 24 | char format_rfc1123[LEN_TIME_FARMAT_RFC1123]; 25 | char format_log[LEN_TIME_FARMAT_LOG]; 26 | } ohc_timer_t; 27 | 28 | typedef struct ohc_timer_group_s ohc_timer_group_t; 29 | typedef struct { 30 | struct list_head tnode_node; 31 | time_t timeout; 32 | ohc_timer_group_t *group; 33 | } ohc_timer_node_t; 34 | 35 | time_t timer_parse_rfc1123(char *p); 36 | void timer_destroy(ohc_timer_t *timer); 37 | time_t timer_closest(ohc_timer_t *timer); 38 | struct list_head *timer_expire(ohc_timer_t *timer); 39 | int timer_add(ohc_timer_t *timer, ohc_timer_node_t *tnode, time_t timeout); 40 | void timer_del(ohc_timer_node_t *tnode); 41 | int timer_update(ohc_timer_node_t *tnode, time_t timeout); 42 | void timer_init(ohc_timer_t *timer); 43 | char *timer_format_rfc1123(ohc_timer_t *timer); 44 | char *timer_format_log(ohc_timer_t *timer); 45 | 46 | static inline time_t timer_now(ohc_timer_t *timer) 47 | { 48 | return timer->now; 49 | } 50 | 51 | static inline time_t timer_refresh(ohc_timer_t *timer) 52 | { 53 | timer->now = time(NULL); 54 | timer->format_rfc1123[0] = '\0'; 55 | timer->format_log[0] = '\0'; 56 | 57 | return timer->now; 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /device.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Devices and free blocks management. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_STORE_H_ 9 | #define _OHC_STORE_H_ 10 | 11 | #include "olivehc.h" 12 | 13 | struct ohc_device_s { 14 | unsigned deleted:1; 15 | unsigned kicked:1; 16 | 17 | int fd; 18 | int index; 19 | int used; 20 | char filename[PATH_LENGTH]; 21 | dev_t dev; 22 | ino_t inode; 23 | long item_nr; 24 | long fblock_nr; 25 | size_t capacity; 26 | size_t consumed; 27 | size_t badblock; 28 | 29 | struct list_head order_head; 30 | struct list_head dnode; 31 | 32 | struct ohc_device_s *conf; 33 | }; 34 | 35 | typedef struct { 36 | /* @order_node and @fblock must be together, to 37 | * distinguish ohc_item_t and ohc_free_block_t. */ 38 | struct list_head order_node; 39 | unsigned fblock:1; 40 | 41 | short device_index; 42 | 43 | /* since sendfile(2) supports only 0x4020010000, so 40bits is enough */ 44 | unsigned long offset:40; 45 | 46 | off_t block_size; 47 | struct list_head bucket_node; 48 | } ohc_free_block_t; 49 | 50 | #define DEVICES_LIMIT IPT_ARRAY_SIZE 51 | 52 | ohc_device_t *device_of_item(ohc_item_t *item); 53 | 54 | int device_conf_check(ohc_conf_t *conf_cycle); 55 | void device_conf_load(ohc_conf_t *conf_cycle); 56 | void device_conf_rollback(ohc_conf_t *conf_cycle); 57 | 58 | int device_free_block_extend(size_t target); 59 | size_t device_get_free_block(ohc_item_t *item); 60 | size_t device_return_free_block(ohc_item_t *item); 61 | size_t device_cut_free_block(ohc_item_t *item); 62 | void device_load_post(ohc_device_t *device); 63 | 64 | void device_format_load(void); 65 | void device_format_store(void); 66 | void device_routine(void); 67 | void device_status(FILE *filp); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /request.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow of request. Read request, write response. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OHC_REQUEST_H_ 9 | #define _OHC_REQUEST_H_ 10 | 11 | #include "olivehc.h" 12 | 13 | #define REQ_BUF_SIZE 4096 14 | /* a request, include its downstream connection */ 15 | struct ohc_request_s { 16 | ohc_server_t *server; 17 | 18 | ohc_item_t *item; 19 | 20 | ohc_worker_t *worker_thread; 21 | 22 | unsigned events:2; 23 | unsigned keepalive:1; 24 | unsigned active:1; 25 | unsigned connection_broken:1; 26 | unsigned cork:1; 27 | unsigned range_set:1; 28 | unsigned disk_error:1; 29 | 30 | /* request line and headers */ 31 | int method; 32 | ssize_t content_length; 33 | ssize_t range_start; 34 | ssize_t range_end; 35 | string_t range; 36 | string_t uri; 37 | string_t host; 38 | string_t ohc_key; 39 | string_t put_headers[10]; /* at most #(http_request_header_put) */ 40 | int put_header_nr; 41 | int put_header_length; 42 | time_t expire; 43 | 44 | time_t start_time; 45 | 46 | /* in GET, record sendfile process size; 47 | * in PUT, record recv item process size. */ 48 | size_t process_size; 49 | 50 | size_t output_size; 51 | size_t input_size; 52 | 53 | int http_code; 54 | int error_number; 55 | char *error_reason; 56 | char *step; 57 | 58 | req_handler_f *event_handler; 59 | 60 | char *buf_pos; 61 | char _buffer[REQ_BUF_SIZE]; 62 | 63 | int sock_fd; 64 | struct sockaddr_in client; 65 | 66 | ohc_timer_node_t tnode; 67 | struct list_head rnode; 68 | }; 69 | 70 | void request_process_entry(ohc_server_t *s, int sock_fd, struct sockaddr_in *client); 71 | void request_timeout_handler(ohc_request_t *r); 72 | void request_clean(struct list_head *requests, int keepalive_only); 73 | int request_check_quit(int keepalive_only); 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /event.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils for request event management. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "event.h" 9 | 10 | #define OHC_EV_READ 1 11 | #define OHC_EV_WRITE 2 12 | 13 | static int event_add(ohc_request_t *r, req_handler_f *handler, int event, time_t timeout) 14 | { 15 | uint32_t epoll_ev = (event == OHC_EV_READ) ? EPOLLIN : EPOLLOUT; 16 | int epoll_fd = r->worker_thread ? r->worker_thread->epoll_fd : master_epoll_fd; 17 | ohc_timer_t *timer; 18 | 19 | if(r->events == event) { 20 | timer_update(&r->tnode, timeout); 21 | 22 | } else if(r->events) { 23 | if(epoll_mod(epoll_fd, r->sock_fd, epoll_ev, r) != 0) { 24 | return OHC_ERROR; 25 | } 26 | if(timer_update(&r->tnode, timeout) != 0) { 27 | return OHC_ERROR; 28 | } 29 | } else { 30 | timer = r->worker_thread ? &r->worker_thread->timer : &master_timer; 31 | if(epoll_add(epoll_fd, r->sock_fd, epoll_ev, r) != 0) { 32 | return OHC_ERROR; 33 | } 34 | if(timer_add(timer, &r->tnode, timeout) != 0) { 35 | return OHC_ERROR; 36 | } 37 | } 38 | 39 | r->events = event; 40 | r->event_handler = handler; 41 | return OHC_OK; 42 | } 43 | 44 | int event_add_read(ohc_request_t *r, req_handler_f *handler) 45 | { 46 | return event_add(r, handler, OHC_EV_READ, r->server->recv_timeout); 47 | } 48 | 49 | int event_add_write(ohc_request_t *r, req_handler_f *handler) 50 | { 51 | return event_add(r, handler, OHC_EV_WRITE, r->server->send_timeout); 52 | } 53 | 54 | int event_add_keepalive(ohc_request_t *r, req_handler_f *handler) 55 | { 56 | return event_add(r, handler, OHC_EV_READ, r->server->keepalive_timeout); 57 | } 58 | 59 | void event_del(ohc_request_t *r) 60 | { 61 | if(r->events) { 62 | int epoll_fd = r->worker_thread ? r->worker_thread->epoll_fd : master_epoll_fd; 63 | epoll_del(epoll_fd, r->sock_fd); 64 | 65 | timer_del(&r->tnode); 66 | 67 | r->events = 0; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /olivehc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Include and define all things. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #ifndef _OLIVEHC_H_ 9 | #define _OLIVEHC_H_ 10 | 11 | #define OHC_VERSION "OliveHC 1.2.2" 12 | 13 | #define OHC_OK 0 14 | #define OHC_ERROR -1 15 | #define OHC_DONE -2 16 | #define OHC_AGAIN -3 17 | #define OHC_DECLINE -4 18 | 19 | #define PATH_LENGTH 1024 20 | 21 | #define LOOP_LIMIT 1000 22 | 23 | #define EVENT_TYPE_SOCKET 0 24 | #define EVENT_TYPE_LISTEN 1 25 | #define EVENT_TYPE_PIPE 2 26 | #define EVENT_TYPE_MASK 3UL 27 | 28 | typedef char ohc_flag_t; 29 | 30 | //daemon() 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | 54 | /* utils */ 55 | #include "utils/list.h" 56 | #include "utils/socktcp.h" 57 | #include "utils/string.h" 58 | #include "utils/slab.h" 59 | #include "utils/hash.h" 60 | #include "utils/epoll.h" 61 | #include "utils/timer.h" 62 | #include "utils/ipbucket.h" 63 | #include "utils/idx_pointer.h" 64 | 65 | 66 | typedef struct ohc_request_s ohc_request_t; 67 | typedef struct ohc_item_s ohc_item_t; 68 | typedef struct ohc_server_s ohc_server_t; 69 | typedef struct ohc_device_s ohc_device_t; 70 | typedef struct ohc_worker_s ohc_worker_t; 71 | typedef struct ohc_format_item_s ohc_format_item_t; 72 | typedef struct ohc_conf_s ohc_conf_t; 73 | typedef void req_handler_f(ohc_request_t *r); 74 | 75 | extern int master_epoll_fd; 76 | extern ohc_timer_t master_timer; 77 | extern struct list_head master_requests; 78 | 79 | #include "conf.h" 80 | #include "format.h" 81 | #include "http.h" 82 | #include "server.h" 83 | #include "worker.h" 84 | #include "device.h" 85 | #include "request.h" 86 | #include "event.h" 87 | 88 | void log_error(FILE *filp, const char *prefix, int errnum, const char *fmt, ...); 89 | 90 | extern FILE *error_filp; 91 | #define log_error_run(errnum, fmt, ...) \ 92 | log_error(error_filp, timer_format_log(&master_timer), errnum, fmt, ##__VA_ARGS__) 93 | 94 | extern FILE *admin_out_filp; 95 | #define log_error_admin(errnum, fmt, ...) \ 96 | log_error(admin_out_filp, "ERROR:", errnum, fmt, ##__VA_ARGS__) 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /utils/slab.c: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * A simple slab. 4 | * 5 | * Auther: Wu Bingzheng 6 | * 7 | **/ 8 | 9 | /* 10 | * ohc_slab_t slab_block_t slab_block_t 11 | * +----+ +------+<--\ +------+<--\ 12 | * |head|--->| |---+-->| | | 13 | * | | | | | | | | 14 | * | | | | | | | | 15 | * +----+ +======+ | +======+ | 16 | * |leader|--/ |leader|--/ 17 | * return->+ - - -| + - - -| 18 | * | | | | 19 | * | | | | 20 | * +------+ +------+ 21 | * |leader| |leader| 22 | * + - - -| + - - -| 23 | * | | | | 24 | * | | | | 25 | * +------+ +------+ 26 | * | | | | 27 | * ... ... 28 | * | | | | 29 | * +------+ +------+ 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include "slab.h" 36 | 37 | typedef struct { 38 | struct hlist_node block_node; 39 | struct hlist_head item_head; 40 | ohc_slab_t *slab; 41 | int frees; 42 | } slab_block_t; 43 | 44 | static inline int slab_buckets(ohc_slab_t *slab) 45 | { 46 | /* malloc use @brk if size<128K */ 47 | return (128*1024 - sizeof(slab_block_t) - 100) / slab->item_size; 48 | } 49 | 50 | void *slab_alloc(ohc_slab_t *slab) 51 | { 52 | slab_block_t *sblock; 53 | uintptr_t leader; 54 | struct hlist_node *p; 55 | int buckets; 56 | int i; 57 | 58 | if(hlist_empty(&slab->block_head)) { 59 | buckets = slab_buckets(slab); 60 | sblock = malloc(sizeof(slab_block_t) + slab->item_size * buckets); 61 | if(sblock == NULL) { 62 | return NULL; 63 | } 64 | 65 | sblock->slab = slab; 66 | sblock->frees = buckets; 67 | hlist_add_head(&sblock->block_node, &slab->block_head); 68 | INIT_HLIST_HEAD(&sblock->item_head); 69 | 70 | leader = (uintptr_t)sblock + sizeof(slab_block_t); 71 | for(i = 0; i < buckets; i++) { 72 | *((slab_block_t **)leader) = sblock; 73 | p = (struct hlist_node *)(leader + sizeof(slab_block_t *)); 74 | hlist_add_head(p, &sblock->item_head); 75 | leader += slab->item_size; 76 | } 77 | 78 | } else { 79 | sblock = list_entry(slab->block_head.first, slab_block_t, block_node); 80 | } 81 | 82 | p = sblock->item_head.first; 83 | hlist_del(p); 84 | 85 | sblock->frees--; 86 | if(sblock->frees == 0) { 87 | /* if no free items, we throw the block away */ 88 | hlist_del(&sblock->block_node); 89 | } 90 | 91 | return p; 92 | } 93 | 94 | void slab_free(void *p) 95 | { 96 | uintptr_t leader = (uintptr_t)p - sizeof(slab_block_t **); 97 | slab_block_t *sblock = *((slab_block_t **)leader); 98 | ohc_slab_t *slab = sblock->slab; 99 | 100 | if(sblock->frees == 0) { 101 | /* if there WAS no free item in this block, we catch it again */ 102 | hlist_add_head(&sblock->block_node, &slab->block_head); 103 | } 104 | 105 | hlist_add_head((struct hlist_node *)p, &sblock->item_head); 106 | 107 | sblock->frees++; 108 | if(sblock->frees == slab_buckets(slab) && sblock->block_node.next) { 109 | /* never free the first slab-block */ 110 | hlist_del(&sblock->block_node); 111 | free(sblock); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /utils/ipbucket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Space management. Cut device into blocks in sizes 3 | * as (double and interpolation): 4 | * 5 | * [1] [2] 3 [4] 5 6 7 [8] 10 12 14 [16] 20 24 28 [32] 40 48 56 [64] ... 6 | * 7 | * Author: Wu Bingzheng 8 | * 9 | */ 10 | 11 | #ifndef _OHC_IPBUCKET_H_ 12 | #define _OHC_IPBUCKET_H_ 13 | 14 | #include "list.h" 15 | #include "string.h" 16 | 17 | /* we define ohc_item_t.length as uint32_t for saving memory, 18 | * so IPB_END must not be larger than 32. */ 19 | 20 | #define IPB_BEGIN 9 21 | #define IPB_END 32 22 | #define IPB_BUCKETS ((IPB_END - IPB_BEGIN) * 4 - 4 + 1) 23 | 24 | #define IPB_1ST (1L << IPB_BEGIN) 25 | #define IPB_2ND (IPB_1ST * 2) 26 | #define IPB_3RD (IPB_1ST * 3) 27 | #define IPB_4TH (IPB_1ST * 4) 28 | 29 | typedef struct { 30 | struct list_head queue[IPB_BUCKETS]; 31 | } ohc_ipbucket_t; 32 | 33 | 34 | static inline void ipbucket_init(ohc_ipbucket_t *ipb) 35 | { 36 | int i; 37 | for(i = 0; i < IPB_BUCKETS; i++) { 38 | INIT_LIST_HEAD(&ipb->queue[i]); 39 | } 40 | } 41 | 42 | static inline void ipbucket_destory(ohc_ipbucket_t *ipb) 43 | { 44 | int i; 45 | /* only delete the list-heads from list, and 46 | * the caller should handle the list-nodes. */ 47 | for(i = 0; i < IPB_BUCKETS; i++) { 48 | list_del(&ipb->queue[i]); 49 | } 50 | } 51 | 52 | static inline size_t ipbucket_block_size(size_t size) 53 | { 54 | size_t mask; 55 | 56 | if(size <= IPB_4TH) { 57 | mask = bit_mask(IPB_BEGIN); 58 | } else { 59 | mask = bit_mask(bit_highest(size) - 2); 60 | } 61 | return (size + mask) & ~mask; 62 | } 63 | 64 | static inline int ipbucket_index(size_t size, int up) 65 | { 66 | int first, index, subidx, tail; 67 | 68 | if(size > (1L << IPB_END)) { 69 | return up ? -1 : IPB_BUCKETS - 1; 70 | } 71 | if(size <= IPB_4TH) { 72 | if(up) { 73 | if(size <= IPB_1ST) { 74 | return 0; 75 | } else if(size <= IPB_2ND) { 76 | return 1; 77 | } else if(size <= IPB_3RD) { 78 | return 2; 79 | } else { 80 | return 3; 81 | } 82 | } else { 83 | if(size < IPB_2ND) { 84 | return 0; 85 | } else if(size < IPB_3RD) { 86 | return 1; 87 | } else if(size < IPB_4TH) { 88 | return 2; 89 | } else { 90 | return 3; 91 | } 92 | } 93 | } 94 | 95 | first = bit_highest(size); 96 | index = (first - IPB_BEGIN) * 4 - 5; 97 | subidx = (size >> (first-2)) & 0x3; 98 | if(up) { 99 | tail = size & bit_mask(first - 2) ? 1 : 0; 100 | } else { 101 | tail = 0; 102 | } 103 | 104 | return index + subidx + tail; 105 | } 106 | 107 | static inline void ipbucket_add(ohc_ipbucket_t *ipb, struct list_head *node, size_t size) 108 | { 109 | struct list_head *p = &ipb->queue[ipbucket_index(size, 0)]; 110 | 111 | if(size == ipbucket_block_size(size)) { 112 | list_add(node, p); 113 | } else { 114 | list_add_tail(node, p); 115 | } 116 | } 117 | 118 | static inline void ipbucket_del(struct list_head *node) 119 | { 120 | if(node->prev) { 121 | list_del(node); 122 | } 123 | } 124 | 125 | static inline void ipbucket_update(ohc_ipbucket_t *ipb, struct list_head *node, size_t size) 126 | { 127 | list_del(node); 128 | ipbucket_add(ipb, node, size); 129 | } 130 | 131 | static inline struct list_head *ipbucket_get(ohc_ipbucket_t *ipb, size_t size) 132 | { 133 | struct list_head *p; 134 | int index = ipbucket_index(size, 1); 135 | 136 | if(index == -1) { 137 | return NULL; 138 | } 139 | 140 | for(; index < IPB_BUCKETS; index++) { 141 | if(!list_empty(&ipb->queue[index])) { 142 | p = ipb->queue[index].next; 143 | list_del(p); 144 | return p; 145 | } 146 | } 147 | return NULL; 148 | } 149 | 150 | /* return an almost biggest block, but don't unlink it. */ 151 | static inline struct list_head *ipbucket_biggest(ohc_ipbucket_t *ipb) 152 | { 153 | int index; 154 | for(index = IPB_BUCKETS - 1; index >= 0; index--) { 155 | if(!list_empty(&ipb->queue[index])) { 156 | return ipb->queue[index].prev; 157 | } 158 | } 159 | return NULL; 160 | } 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Manage servers and items. Create/destroy/update servers, 3 | * get/put/delete items, etc. 4 | * 5 | * Author: Wu Bingzheng 6 | * 7 | */ 8 | 9 | #ifndef _OHC_SERVER_H_ 10 | #define _OHC_SERVER_H_ 11 | 12 | #include "olivehc.h" 13 | 14 | struct ohc_server_s { 15 | struct list_head snode; 16 | 17 | struct list_head lru_head; 18 | struct list_head passby_lru_head; 19 | 20 | unsigned short listen_port; 21 | int listen_fd; 22 | 23 | ohc_hash_t *hash; 24 | 25 | size_t capacity; 26 | size_t consumed; 27 | size_t content; 28 | long item_nr; 29 | 30 | ohc_server_t *conf; 31 | 32 | int index; 33 | 34 | unsigned short clear; 35 | 36 | unsigned deleted:1; 37 | 38 | ohc_flag_t server_dump; 39 | ohc_flag_t shutdown_if_not_store; 40 | ohc_flag_t key_include_host; 41 | ohc_flag_t key_include_ohc_key; 42 | ohc_flag_t key_include_query; 43 | 44 | ohc_flag_t passby_enable; 45 | long passby_begin_item_nr; 46 | size_t passby_begin_consumed; 47 | long passby_limit_nr; 48 | time_t passby_expire; 49 | 50 | long passby_item_nr; 51 | 52 | size_t sndbuf; 53 | size_t rcvbuf; 54 | int connections; 55 | int connections_limit; 56 | int send_timeout; 57 | int recv_timeout; 58 | int request_timeout; 59 | int keepalive_timeout; 60 | char access_log[PATH_LENGTH]; 61 | FILE *access_filp; 62 | size_t item_max_size; 63 | time_t expire_default; 64 | time_t expire_force; 65 | 66 | /* statistics */ 67 | long gets; 68 | long hits; 69 | long passby_hits; 70 | long gets_last_period; 71 | long hits_last_period; 72 | long passby_hits_last_period; 73 | long gets_current_period; 74 | long hits_current_period; 75 | long passby_hits_current_period; 76 | 77 | long puts; 78 | long stores; 79 | long passby_stores; 80 | long puts_last_period; 81 | long stores_last_period; 82 | long passby_stores_last_period; 83 | long puts_current_period; 84 | long stores_current_period; 85 | long passby_stores_current_period; 86 | 87 | long deletes; 88 | long deletes_current_period; 89 | long deletes_last_period; 90 | 91 | size_t output_size_last_period; 92 | size_t output_size_current_period; 93 | size_t input_size_last_period; 94 | size_t input_size_current_period; 95 | 96 | time_t last_clear; 97 | time_t status_period; 98 | }; 99 | 100 | struct ohc_item_s { 101 | ohc_hash_node_t hnode; 102 | struct list_head order_node; 103 | struct list_head lru_node; 104 | 105 | /* since the number of items is huge, so we try our 106 | * best to minimize the size of ohc_item_s. */ 107 | 108 | /* we supports 4G at most */ 109 | uint32_t length; 110 | 111 | /* 2038 is enough... */ 112 | int32_t expire; 113 | 114 | /* since sendfile(2) supports only 0x4020010000, so 40bits is enough */ 115 | unsigned long offset:40; 116 | 117 | unsigned putting:1; 118 | unsigned deleted:1; 119 | unsigned badblock:1; 120 | 121 | short server_index; 122 | short device_index; 123 | 124 | unsigned short headers_len; 125 | unsigned short used; 126 | unsigned short clear; 127 | }; 128 | 129 | #define SERVERS_LIMIT IPT_ARRAY_SIZE 130 | 131 | void server_dump_ports(unsigned short *ports); 132 | ohc_server_t *server_of_item(ohc_item_t *item); 133 | ohc_server_t *server_by_port(unsigned short port); 134 | 135 | int server_conf_check(ohc_conf_t *conf_cycle); 136 | void server_conf_load(ohc_conf_t *conf_cycle); 137 | void server_conf_rollback(ohc_conf_t *conf_cycle); 138 | 139 | int server_clear(unsigned short port); 140 | void server_stop_service(void); 141 | 142 | int server_request_get_handler(ohc_request_t *r); 143 | int server_request_put_handler(ohc_request_t *r); 144 | int server_request_delete_handler(ohc_request_t *r); 145 | void server_request_finalize(ohc_request_t *r); 146 | 147 | int server_item_valid(ohc_item_t *item); 148 | int server_load_fm_item(ohc_server_t *s, ohc_device_t *d, 149 | ohc_format_item_t *fm_item); 150 | 151 | void server_item_delete(ohc_item_t *item); 152 | 153 | void server_listen_handler(ohc_server_t *s); 154 | 155 | void server_routine(void); 156 | void server_status(FILE *filp); 157 | #endif 158 | -------------------------------------------------------------------------------- /utils/hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Linear dynamic hashing 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "hash.h" 13 | #include "list.h" 14 | 15 | typedef int hindex_t; 16 | 17 | /* hash bucket size range from 2^4 to 2^28*/ 18 | #define HASH_BUCKET_SIZE_BEGIN (1<<4) 19 | #define HASH_BUCKET_SIZE_MAX (1<<28) 20 | 21 | struct ohc_hash_s { 22 | struct hlist_head *buckets; 23 | hindex_t bucket_size; 24 | long items; 25 | 26 | /* previous buckets, in split process */ 27 | struct hlist_head *prev_buckets; 28 | /* split pointer in linear hashing */ 29 | hindex_t split; 30 | }; 31 | 32 | inline static int md5_equal(unsigned char *id1, unsigned char *id2) 33 | { 34 | uint64_t *p = (uint64_t *)id1; 35 | uint64_t *q = (uint64_t *)id2; 36 | return (*p == *q) && (*(p+1) == *(q+1)); 37 | } 38 | 39 | inline static hindex_t hash_index(ohc_hash_t *hash, unsigned char *id) 40 | { 41 | uint64_t *p = (uint64_t *)id; 42 | return (*p ^ *(p+1)) & (hash->bucket_size - 1); 43 | } 44 | 45 | ohc_hash_t *hash_init(void) 46 | { 47 | ohc_hash_t *hash; 48 | 49 | hash = malloc(sizeof(ohc_hash_t)); 50 | if(hash == NULL) { 51 | return NULL; 52 | } 53 | 54 | hash->items = 0; 55 | hash->split = 0; 56 | hash->prev_buckets = NULL; 57 | hash->bucket_size = HASH_BUCKET_SIZE_BEGIN; 58 | 59 | /* @calloc do the same thing with INIT_HLIST_HEAD. */ 60 | hash->buckets = calloc(hash->bucket_size, sizeof(struct hlist_head)); 61 | if(hash->buckets == NULL) { 62 | free(hash); 63 | return NULL; 64 | } 65 | 66 | return hash; 67 | } 68 | 69 | void hash_destroy(ohc_hash_t *hash) 70 | { 71 | free(hash->buckets); 72 | if(hash->prev_buckets) { 73 | free(hash->prev_buckets); 74 | } 75 | free(hash); 76 | } 77 | 78 | /* expansion: double the hash buckets */ 79 | static void hash_expansion(ohc_hash_t *hash) 80 | { 81 | ohc_hash_node_t *hnode; 82 | struct hlist_node *p, *safe; 83 | void *newb; 84 | 85 | #define HASH_COLLISIONS 10 86 | /* expansion */ 87 | if(hash->items / hash->bucket_size >= HASH_COLLISIONS 88 | && hash->prev_buckets == NULL 89 | && hash->bucket_size < HASH_BUCKET_SIZE_MAX) { 90 | 91 | newb = calloc(hash->bucket_size * 2, sizeof(struct hlist_head)); 92 | if(newb == NULL) { 93 | /* if calloc fails, do nothing */ 94 | return; 95 | } 96 | hash->bucket_size *= 2; 97 | hash->prev_buckets = hash->buckets; 98 | hash->buckets = newb; 99 | hash->split = 0; 100 | } 101 | 102 | /* split a bucket */ 103 | if(hash->prev_buckets != NULL) { 104 | 105 | for(p = hash->prev_buckets[hash->split].first; p; p = safe) { 106 | safe = p->next; 107 | hlist_del(p); 108 | 109 | hnode = list_entry(p, ohc_hash_node_t, node); 110 | hlist_add_head(p, &hash->buckets[hash_index(hash, hnode->id)]); 111 | } 112 | 113 | hash->split++; 114 | if(hash->split == hash->bucket_size / 2) { 115 | /* expansion finish */ 116 | free(hash->prev_buckets); 117 | hash->prev_buckets = NULL; 118 | } 119 | } 120 | } 121 | 122 | void hash_add(ohc_hash_t *hash, ohc_hash_node_t *hnode, unsigned char *str, int len) 123 | { 124 | if(str) { 125 | MD5(str, len, hnode->id); 126 | } 127 | 128 | hlist_add_head(&hnode->node, &hash->buckets[hash_index(hash, hnode->id)]); 129 | 130 | hash->items++; 131 | hash_expansion(hash); 132 | } 133 | 134 | static ohc_hash_node_t *hash_search(struct hlist_head *slot, unsigned char *id) 135 | { 136 | struct hlist_node *p; 137 | ohc_hash_node_t *hnode; 138 | 139 | hlist_for_each(p, slot) { 140 | hnode = list_entry(p, ohc_hash_node_t, node); 141 | if(md5_equal(hnode->id, id)) { 142 | return hnode; 143 | } 144 | } 145 | 146 | return NULL; 147 | } 148 | 149 | ohc_hash_node_t *hash_get(ohc_hash_t *hash, unsigned char *str, int len, unsigned char *hash_id) 150 | { 151 | unsigned char id_buf[16]; 152 | unsigned char *id; 153 | hindex_t index; 154 | hindex_t pbsize; 155 | ohc_hash_node_t *ret; 156 | 157 | hash_expansion(hash); 158 | 159 | id = hash_id ? hash_id : id_buf; 160 | MD5(str, len, id); 161 | index = hash_index(hash, id); 162 | 163 | ret = hash_search(&hash->buckets[index], id); 164 | if(ret != NULL) { 165 | return ret; 166 | } 167 | 168 | if(hash->prev_buckets != NULL) { 169 | pbsize = hash->bucket_size / 2; 170 | if(index >= pbsize) { 171 | index -= pbsize; 172 | } 173 | return hash_search(&hash->prev_buckets[index], id); 174 | } 175 | 176 | return NULL; 177 | } 178 | 179 | void hash_del(ohc_hash_t *hash, ohc_hash_node_t *hnode) 180 | { 181 | hlist_del(&hnode->node); 182 | hash->items--; 183 | } 184 | -------------------------------------------------------------------------------- /utils/socktcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils for TCP socket. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #define _GNU_SOURCE 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | //TCP_CORK 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "socktcp.h" 24 | 25 | static int idle_fd = -1; 26 | 27 | static int get_tcp_rmem(int *s) 28 | { 29 | FILE *fp; 30 | int n; 31 | 32 | fp = fopen("/proc/sys/net/ipv4/tcp_rmem", "r"); 33 | if(fp == NULL) { 34 | return 1; 35 | } 36 | 37 | n = fscanf(fp, "%d%d%d", &s[0], &s[1], &s[2]); 38 | fclose(fp); 39 | return n == 3 ? 0 : 1; 40 | } 41 | 42 | static int get_tcp_wmem(int *s) 43 | { 44 | FILE *fp; 45 | int n; 46 | 47 | fp = fopen("/proc/sys/net/ipv4/tcp_wmem", "r"); 48 | if(fp == NULL) { 49 | return 1; 50 | } 51 | 52 | n = fscanf(fp, "%d%d%d", &s[0], &s[1], &s[2]); 53 | fclose(fp); 54 | return n == 3 ? 0 : 1; 55 | } 56 | 57 | int set_sndbuf(int fd, int value) 58 | { 59 | int mems[3]; 60 | 61 | if(value == 0) { 62 | if(get_tcp_wmem(mems) != 0) { 63 | return -1; 64 | } 65 | value = mems[1]; 66 | } 67 | 68 | if(setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(int)) < 0) { 69 | return -1; 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | int set_rcvbuf(int fd, int value) 76 | { 77 | int mems[3]; 78 | 79 | if(value == 0) { 80 | if(get_tcp_rmem(mems) != 0) { 81 | return -1; 82 | } 83 | value = mems[1]; 84 | } 85 | 86 | if(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(int)) < 0) { 87 | return -1; 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | int set_cork(int fd, int cork) 94 | { 95 | return setsockopt(fd, IPPROTO_TCP, TCP_CORK, &cork, sizeof(int)); 96 | } 97 | 98 | int set_defer_accept(int fd, int value) 99 | { 100 | return setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &value, sizeof(int)); 101 | } 102 | 103 | int set_nonblock(int fd) 104 | { 105 | return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 106 | } 107 | 108 | int tcp_bind(unsigned short port) 109 | { 110 | int fd, val; 111 | struct sockaddr_in servaddr; 112 | //address family internet 113 | fd = socket(AF_INET, SOCK_STREAM, 0); 114 | if(fd < 0) { 115 | return -1; 116 | } 117 | //set non block 118 | if(set_nonblock(fd) != 0) { 119 | return -1; 120 | } 121 | 122 | val = 1; 123 | if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)) < 0) { 124 | return -1; 125 | } 126 | 127 | if(set_defer_accept(fd, 60) < 0) { 128 | return -1; 129 | } 130 | 131 | bzero(&servaddr, sizeof(servaddr)); 132 | servaddr.sin_family = AF_INET; 133 | //host to net long 134 | servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 135 | //host to net short 136 | servaddr.sin_port = htons(port); 137 | if(bind(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { 138 | close(fd); 139 | return -1; 140 | } 141 | 142 | return fd; 143 | } 144 | 145 | int tcp_listen(int fd) 146 | { 147 | if(idle_fd == -1) { 148 | idle_fd = open("/dev/null", O_RDONLY); 149 | } 150 | return listen(fd, 1000); 151 | } 152 | 153 | static void clear_backlog(int s) 154 | { 155 | while(idle_fd >= 0) { 156 | close(idle_fd); 157 | idle_fd = accept(s, NULL, 0); 158 | } 159 | 160 | idle_fd = open("/dev/null", O_RDONLY); 161 | } 162 | // test the vesion of glib 163 | #if __GLIBC_PREREQ(2, 10) 164 | #define OHC_HAVE_ACCEPT4 165 | #endif 166 | 167 | int tcp_accept(int s, struct sockaddr_in *client) 168 | { 169 | socklen_t addrlen = sizeof(struct sockaddr_in); 170 | int fd; 171 | 172 | again: 173 | #ifdef OHC_HAVE_ACCEPT4 174 | fd = accept4(s, (struct sockaddr *)client, &addrlen, SOCK_NONBLOCK); 175 | #else 176 | fd = accept(s, (struct sockaddr *)client, &addrlen); 177 | #endif 178 | if(fd < 0) { 179 | if(errno == EINTR) 180 | goto again; 181 | if(errno == EMFILE || errno == ENFILE) 182 | clear_backlog(s); 183 | return fd; 184 | } 185 | 186 | #ifndef OHC_HAVE_ACCEPT4 187 | /* we don't use set_nonblock(), to cut down one syscall. */ 188 | static int flag_init = -1; 189 | if(flag_init == -1) { 190 | flag_init = fcntl(fd, F_GETFL); 191 | } 192 | fcntl(fd, F_SETFL, flag_init | O_NONBLOCK); 193 | #endif 194 | return fd; 195 | } 196 | -------------------------------------------------------------------------------- /format.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Load items from device when starts, and store items to device when quits. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "format.h" 9 | 10 | /* 11 | * Format of device file: 12 | * 13 | * ohc_superblock_t 14 | * server-listen-port[SERVERS_LIMIT] 15 | * ohc_format_item_t[] 16 | * items-body 17 | * ... 18 | */ 19 | 20 | 21 | #define OHC_FM_MAGIC 0x2143484556494c4fL /* OLIVEHC! */ 22 | #define OHC_FM_VERSION 1 23 | 24 | typedef struct { 25 | uint64_t magic; 26 | int version; 27 | uint64_t checksum; 28 | long item_nr; 29 | } ohc_superblock_t; 30 | 31 | #define SERVER_PORTS_SIZE (sizeof(unsigned short) * SERVERS_LIMIT) 32 | #define OHC_FM_INFO_SIZE (sizeof(ohc_superblock_t) + SERVER_PORTS_SIZE) 33 | #define OHC_FM_CHS_FEED 0x57eb0b4eecfeb465L 34 | 35 | static uint64_t format_checksum(void *buf, size_t len) 36 | { 37 | uint64_t *p = buf; 38 | uint64_t checksum = 0; 39 | int i; 40 | 41 | for(i = 0; i < len / sizeof(uint64_t); i++) { 42 | checksum ^= p[i]; 43 | } 44 | return checksum; 45 | } 46 | 47 | int format_store_device(unsigned short *server_ports, ohc_device_t *device) 48 | { 49 | struct list_head *p; 50 | ohc_superblock_t superb; 51 | ohc_item_t *item; 52 | ohc_free_block_t *fblock; 53 | ohc_format_item_t fm_item; 54 | ohc_server_t *server; 55 | 56 | FILE *filp = fdopen(device->fd, "r+"); 57 | if(filp == NULL) { 58 | return OHC_ERROR; 59 | } 60 | 61 | /* init superblock */ 62 | superb.magic = OHC_FM_MAGIC; 63 | superb.version = OHC_FM_VERSION; 64 | superb.checksum = 0; 65 | superb.item_nr = 0; 66 | 67 | /* servers */ 68 | if(fseek(filp, sizeof(superb), SEEK_SET) < 0) { 69 | return OHC_ERROR; 70 | } 71 | if(fwrite(server_ports, SERVER_PORTS_SIZE, 1, filp) < 1) { 72 | return OHC_ERROR; 73 | } 74 | 75 | /* items */ 76 | list_for_each(p, &device->order_head) { 77 | fblock = list_entry(p, ohc_free_block_t, order_node); 78 | if(fblock->fblock) { 79 | continue; 80 | } 81 | 82 | item = list_entry(p, ohc_item_t, order_node); 83 | if(!server_item_valid(item)) { 84 | continue; 85 | } 86 | 87 | server = server_of_item(item); 88 | if(!server->server_dump) { 89 | continue; 90 | } 91 | 92 | memcpy(fm_item.hash_id, item->hnode.id, 16); 93 | fm_item.expire = item->expire; 94 | fm_item.length = item->length; 95 | fm_item.headers_len = item->headers_len; 96 | fm_item.server_index = server->index; 97 | fm_item.offset = item->offset; 98 | if(fwrite(&fm_item, sizeof(ohc_format_item_t), 1, filp) < 1) { 99 | return OHC_ERROR; 100 | } 101 | 102 | superb.item_nr++; 103 | } 104 | 105 | if(superb.item_nr == 0) { 106 | return OHC_ERROR; 107 | } 108 | 109 | /* superblock */ 110 | superb.checksum ^= format_checksum(&superb, sizeof(superb)); 111 | superb.checksum ^= format_checksum(server_ports, SERVER_PORTS_SIZE); 112 | superb.checksum ^= OHC_FM_CHS_FEED; 113 | 114 | if(fseek(filp, 0, SEEK_SET) < 0) { 115 | return OHC_ERROR; 116 | } 117 | if(fwrite(&superb, sizeof(ohc_superblock_t), 1, filp) < 1) { 118 | return OHC_ERROR; 119 | } 120 | 121 | fclose(filp); 122 | return OHC_OK; 123 | } 124 | 125 | int format_load_device(ohc_device_t *device) 126 | { 127 | unsigned char buffer[OHC_FM_INFO_SIZE]; 128 | unsigned short *server_ports; 129 | ohc_superblock_t *superb; 130 | ohc_server_t *server; 131 | ohc_server_t *disk_servers[SERVERS_LIMIT]; 132 | ohc_format_item_t fm_item; 133 | FILE *filp; 134 | long i; 135 | off_t override; 136 | int rc = OHC_ERROR; 137 | 138 | time_t now = timer_now(&master_timer); 139 | 140 | filp = fopen(device->filename, "r+"); 141 | if(filp == NULL) { 142 | return OHC_ERROR; 143 | } 144 | if(fread(buffer, OHC_FM_INFO_SIZE, 1, filp) < 1) { 145 | goto out; 146 | } 147 | superb = (ohc_superblock_t *)&buffer[0]; 148 | server_ports = (unsigned short *)(superb + 1); 149 | 150 | /* check */ 151 | if(superb->magic != OHC_FM_MAGIC || superb->version != OHC_FM_VERSION) { 152 | goto out; 153 | } 154 | 155 | if(format_checksum(buffer, OHC_FM_INFO_SIZE) != OHC_FM_CHS_FEED) { 156 | goto out; 157 | } 158 | 159 | /* build @disk_servers */ 160 | for(i = 0; i < SERVERS_LIMIT; i++) { 161 | if(server_ports[i] != 0) { 162 | disk_servers[i] = server_by_port(server_ports[i]); 163 | } 164 | } 165 | 166 | /* load items! */ 167 | override = OHC_FM_INFO_SIZE + superb->item_nr * sizeof(ohc_format_item_t); 168 | if(fseek(filp, OHC_FM_INFO_SIZE, SEEK_SET) < 0) { 169 | goto out; 170 | } 171 | for(i = 0; i < superb->item_nr; i++) { 172 | if(fread(&fm_item, sizeof(ohc_format_item_t), 1, filp) < 1) { 173 | goto out; 174 | } 175 | 176 | if(fm_item.offset < override || fm_item.expire <= now) { 177 | continue; 178 | } 179 | 180 | server = disk_servers[fm_item.server_index]; 181 | if(server == NULL) { 182 | continue; 183 | } 184 | 185 | server_load_fm_item(server, device, &fm_item); 186 | } 187 | device_load_post(device); 188 | 189 | /* clear the magic */ 190 | if(fseek(filp, 0, SEEK_SET) < 0) { 191 | goto out; 192 | } 193 | if(fwrite("FeiLiWuShi", 10, 1, filp) < 1) { 194 | goto out; 195 | } 196 | rc = OHC_OK; 197 | 198 | out: 199 | fclose(filp); 200 | return rc; 201 | } 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OliveHC - Olive HTTP Cache # 2 | 3 | OliveHC is a simple, reliable and high-performance cache. 4 | 5 | This project is designed and used by Baidu CDN team, but also applied by several other departments in company. 6 | 7 | It only works on Linux/x86_64 by now. 8 | 9 | 10 | ## Easy to Use ## 11 | 12 | The function is simple. The configuration is simple and can be modified by reloading. 13 | 14 | Install: 15 | 16 | make # "output/" will be created 17 | 18 | After creating a simple configure file, as the next section, you can start up: 19 | 20 | ./olivehc # "olivehc.pid" will be created 21 | 22 | Quit: 23 | 24 | echo quit | nc 127.1 5210 25 | 26 | Reload configure: 27 | 28 | echo reload | nc 127.1 5210 29 | 30 | Running status: 31 | 32 | echo status | nc 127.1 5210 33 | 34 | 35 | ## Configuration File ## 36 | 37 | See `olivehc.conf.sample`. 38 | 39 | Only `device` and `server` are required. Use `device` to assign some files for storage, and `server` to assign some listen ports. 40 | 41 | Other commands are optional, and their default values are given in `olivehc.conf.sample`. 42 | 43 | 44 | ## Multi Server ## 45 | 46 | You can assign more than one `server`, and each can be independent configured, like capacity, timeouts, and so on. 47 | 48 | 49 | ## HTTP Interface ## 50 | 51 | More suitable for WebServer cache (compared with memcached protocol). 52 | 53 | GET/HEAD, PUT/POST, and DELETE methods are supported. 54 | 55 | Range read is supported, but no range store. 56 | 57 | The only diffirence between PUT and POST is that, if the item exists, PUT will replace it, while POST just give up. 58 | 59 | When storing an item, besides its body, all HTTP request headers (except `Connection`) are stored too, as the response headers for the following GET/HEAD requests. 60 | 61 | 62 | ## Item Management ## 63 | 64 | OliveHC manages the items itself, while not use the disk file system, in order to avoid frequent `open`/`close`/`unlink` syscall, for better performance. 65 | 66 | Item meta data stay in memory while the process is working. They will be dumped onto store devices only when OliveHC quits, for persistence. If OliveHC quits abnormally, all data lose. 67 | 68 | Each item meta takes about 88 bytes, so 100 million items takes about 9GB memory. 69 | 70 | 71 | ## Store Device ## 72 | 73 | OliveHC does not support multi-level storage for heat stratification, but only file storage, and using operating system cache for heat management. For the [reason](https://www.varnish-cache.org/trac/wiki/ArchitectNotes). 74 | 75 | Storage based on file, which supports 2 kinds: normal file and block device file(like disk or disk patition). Block device file is suggested, which makes sure that items are physically contiguous on disk. You can use `/dev/ramdisk` for pure memory cache. 76 | 77 | Because of the limit 0x4020010000 (about 270GB) of `sendfile`, the size of store device must be less that this value. If you want to use a disk which is too large, you can partition it into several small partitions. 78 | 79 | 80 | ## Multi Thread ## 81 | 82 | OliveHC has one master thread and several worker threads. 83 | 84 | Master thread processes requests and manages items. Worker threads only make disk IO, to take advantage of multiple disks, and avoid the influence of blocked IO operation. 85 | 86 | As a result, though multi thread, besides the simple communication between master and each worker by pipe, there is no other synchronization(like lock) needed. 87 | 88 | 89 | ## Expire ## 90 | 91 | The expire time of each item is set as: use the `expire_force` value of its server's configuration, if set (default is not set); otherwise use the `Cache-Control` or `Expires` header value in PUT/POST requset; otherwise use the `expire_default` value(default is 3days) of its server's configuration. 92 | 93 | If the space usage of a server exceed its capacity, its items are expired in accordance with LRU. 94 | 95 | In order to avoid the rushing of a large number of long tail cold items (the rushing includes crowding out hot items, and disk writing operation), OliveHC can filter cold items. If turing on this feature, for the first store request of each item, we only record its URL, but not store its data; if a second store request comes again, before the URL expired, we store the item really. 96 | 97 | You can set the threashold of space usage percent at which enables the feature, and the limit of cold URLS number. 98 | 99 | 100 | ## Space Division ## 101 | 102 | Fixed length division is used when allocating space for items. The method is that, inserting 3 equal difference values bewteen each two equal ratio values. Like: 103 | 104 | ... [4] 5 6 7 [8] 10 12 14 [16] ... 105 | 106 | This method brings about 8% space waste. 107 | 108 | Besides, if the size of items covers a large range, it makes space fragment. 109 | 110 | 111 | ## Working with Nginx ## 112 | 113 | Because OliveHC does not have the ability to access the origin when request missing, so we need the WebServer to fetch the item from origin. 114 | 115 | We use Nginx as WebServer, and add a filter module `jstore`, to store the missing item to OliveHC, by subrequest. 116 | 117 | See `nginx-jstore\` for the code and usage. 118 | -------------------------------------------------------------------------------- /utils/timer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Timer and time management. 4 | * 5 | * Auther: Wu Bingzheng 6 | * 7 | **/ 8 | 9 | #include "timer.h" 10 | #include "list.h" 11 | #include "slab.h" 12 | #include 13 | #include 14 | 15 | struct ohc_timer_group_s { 16 | struct list_head tnode_head; 17 | struct list_head tgroup_node; 18 | 19 | time_t timeout; 20 | ohc_timer_t *timer; 21 | }; 22 | 23 | void timer_init(ohc_timer_t *timer) 24 | { 25 | INIT_LIST_HEAD(&timer->tgroup_head); 26 | INIT_LIST_HEAD(&timer->expires); 27 | timer_refresh(timer); 28 | } 29 | 30 | static char *month_str[] = { 31 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 32 | }; 33 | static char *week_str[] = { 34 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 35 | }; 36 | 37 | /* parse RFC1123 time string */ 38 | time_t timer_parse_rfc1123(char *p) 39 | { 40 | struct tm t; 41 | int i; 42 | time_t ret; 43 | #define D2(p) ((*(p)-'0')*10 + *((p)+1)-'0') 44 | #define INVALID_TIME (time_t)-1 45 | 46 | if(strncmp(p + 25, " GMT", 4)) { 47 | return INVALID_TIME; 48 | } 49 | t.tm_mday = D2(p + 5); 50 | t.tm_year = D2(p + 12) * 100 + D2(p + 14) - 1900; 51 | t.tm_hour = D2(p + 17); 52 | t.tm_min = D2(p + 20); 53 | t.tm_sec = D2(p + 23); 54 | for(i = 0; i < 12; i++) { 55 | if(strncmp(p + 8, month_str[i], 3) == 0) { 56 | break; 57 | } 58 | } 59 | if(i == 12) { 60 | return INVALID_TIME; 61 | } 62 | t.tm_mon = i; 63 | ret = mktime(&t); 64 | if(ret == INVALID_TIME) { 65 | return INVALID_TIME; 66 | } 67 | return ret + t.tm_gmtoff; 68 | } 69 | 70 | /* return RFC1123 time string */ 71 | char *timer_format_rfc1123(ohc_timer_t *timer) 72 | { 73 | struct tm tm; 74 | if(timer->format_rfc1123[0] == '\0') { 75 | gmtime_r(&timer->now, &tm); 76 | snprintf(timer->format_rfc1123, LEN_TIME_FARMAT_RFC1123, 77 | "%s, %02d %s %d %02d:%02d:%02d GMT", 78 | week_str[tm.tm_wday], tm.tm_mday, month_str[tm.tm_mon], 79 | tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); 80 | } 81 | 82 | return timer->format_rfc1123; 83 | } 84 | 85 | /* return human readable time string */ 86 | char *timer_format_log(ohc_timer_t *timer) 87 | { 88 | struct tm tm; 89 | if(timer->format_log[0] == '\0') { 90 | localtime_r(&timer->now, &tm); 91 | snprintf(timer->format_log, LEN_TIME_FARMAT_LOG, 92 | "%d-%02d-%02d %02d:%02d:%02d", 93 | tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 94 | tm.tm_hour, tm.tm_min, tm.tm_sec); 95 | } 96 | 97 | return timer->format_log; 98 | } 99 | 100 | /* return the closest timer's timeout (return a big time if no timer) */ 101 | time_t timer_closest(ohc_timer_t *timer) 102 | { 103 | time_t closest = BIG_TIME; 104 | struct list_head *p, *safe; 105 | ohc_timer_group_t *tgroup; 106 | ohc_timer_node_t *tnode; 107 | 108 | list_for_each_safe(p, safe, &timer->tgroup_head) { 109 | 110 | tgroup = list_entry(p, ohc_timer_group_t, tgroup_node); 111 | if(list_empty(&tgroup->tnode_head)) { 112 | list_del(&tgroup->tgroup_node); 113 | free(tgroup); 114 | continue; 115 | } 116 | 117 | tnode = list_entry(tgroup->tnode_head.next, 118 | ohc_timer_node_t, tnode_node); 119 | if(tnode->timeout < closest) { 120 | closest = tnode->timeout; 121 | } 122 | } 123 | 124 | timer_refresh(timer); 125 | return (closest <= timer->now) ? 0 : closest - timer->now; 126 | } 127 | 128 | /* find expired nodes, delete them, and return them by a list. */ 129 | struct list_head *timer_expire(ohc_timer_t *timer) 130 | { 131 | struct list_head *p, *q, *safeq; 132 | ohc_timer_group_t *tgroup; 133 | ohc_timer_node_t *tnode; 134 | 135 | timer_refresh(timer); 136 | list_for_each(p, &timer->tgroup_head) { 137 | tgroup = list_entry(p, ohc_timer_group_t, tgroup_node); 138 | list_for_each_safe(q, safeq, &tgroup->tnode_head) { 139 | 140 | tnode = list_entry(q, ohc_timer_node_t, tnode_node); 141 | if(tnode->timeout > timer->now) { 142 | break; 143 | } 144 | 145 | list_del(q); 146 | list_add_tail(q, &timer->expires); 147 | } 148 | } 149 | 150 | return &timer->expires; 151 | } 152 | 153 | /* add a timer */ 154 | int timer_add(ohc_timer_t *timer, ohc_timer_node_t *tnode, time_t timeout) 155 | { 156 | struct list_head *p; 157 | ohc_timer_group_t *tgroup; 158 | 159 | /* search for the timer_group with @timeout */ 160 | list_for_each(p, &timer->tgroup_head) { 161 | tgroup = list_entry(p, ohc_timer_group_t, tgroup_node); 162 | if(tgroup->timeout == timeout) { 163 | break; 164 | } 165 | } 166 | 167 | /* not found, create one */ 168 | if(p == &timer->tgroup_head) { 169 | tgroup = (ohc_timer_group_t *)malloc(sizeof(ohc_timer_group_t)); 170 | if(tgroup == NULL) { 171 | return 1; 172 | } 173 | 174 | tgroup->timeout = timeout; 175 | tgroup->timer = timer; 176 | INIT_LIST_HEAD(&tgroup->tnode_head); 177 | list_add(&tgroup->tgroup_node, &timer->tgroup_head); 178 | } 179 | 180 | /* add timer */ 181 | list_add_tail(&tnode->tnode_node, &tgroup->tnode_head); 182 | tnode->timeout = timer->now + timeout; 183 | tnode->group = tgroup; 184 | 185 | return 0; 186 | } 187 | 188 | /* delete a timer */ 189 | void timer_del(ohc_timer_node_t *tnode) 190 | { 191 | list_del(&tnode->tnode_node); 192 | } 193 | 194 | /* update a timer with new timeout (if @timeout==0, use previous timeout) */ 195 | int timer_update(ohc_timer_node_t *tnode, time_t timeout) 196 | { 197 | ohc_timer_group_t *tgroup = tnode->group; 198 | 199 | if(timeout == 0 || timeout == tgroup->timeout) { 200 | tnode->timeout = tgroup->timer->now + tgroup->timeout; 201 | list_del(&tnode->tnode_node); 202 | list_add_tail(&tnode->tnode_node, &tgroup->tnode_head); 203 | return 0; 204 | } else { 205 | timer_del(tnode); 206 | return timer_add(tgroup->timer, tnode, timeout); 207 | } 208 | } 209 | 210 | /* destroy */ 211 | void timer_destroy(ohc_timer_t *timer) 212 | { 213 | struct list_head *p; 214 | list_for_each(p, &timer->tgroup_head) { 215 | free(list_entry(p, ohc_timer_group_t, tgroup_node)); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /utils/list.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copied from include/linux/list.h 3 | **/ 4 | 5 | #ifndef _OHC_LIST_H_ 6 | #define _OHC_LIST_H_ 7 | 8 | #include 9 | struct list_head { 10 | struct list_head *next, *prev; 11 | }; 12 | 13 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 14 | 15 | #define LIST_HEAD(name) \ 16 | struct list_head name = LIST_HEAD_INIT(name) 17 | 18 | #define INIT_LIST_HEAD(ptr) do { \ 19 | (ptr)->next = (ptr); (ptr)->prev = (ptr); \ 20 | } while (0) 21 | 22 | /* 23 | * Insert a new entry between two known consecutive entries. 24 | * 25 | * This is only for internal list manipulation where we know 26 | * the prev/next entries already! 27 | */ 28 | static __inline__ void __list_add(struct list_head * nnew, 29 | struct list_head * prev, 30 | struct list_head * next) 31 | { 32 | next->prev = nnew; 33 | nnew->next = next; 34 | nnew->prev = prev; 35 | prev->next = nnew; 36 | } 37 | 38 | /** 39 | * list_add - add a new entry 40 | * @nnew: new entry to be added 41 | * @head: list head to add it after 42 | * 43 | * Insert a new entry after the specified head. 44 | * This is good for implementing stacks. 45 | */ 46 | static __inline__ void list_add(struct list_head *nnew, struct list_head *head) 47 | { 48 | __list_add(nnew, head, head->next); 49 | } 50 | 51 | /** 52 | * list_add_tail - add a new entry 53 | * @nnew: new entry to be added 54 | * @head: list head to add it before 55 | * 56 | * Insert a new entry before the specified head. 57 | * This is useful for implementing queues. 58 | */ 59 | static __inline__ void list_add_tail(struct list_head *nnew, struct list_head *head) 60 | { 61 | __list_add(nnew, head->prev, head); 62 | } 63 | 64 | /* 65 | * Delete a list entry by making the prev/next entries 66 | * point to each other. 67 | * 68 | * This is only for internal list manipulation where we know 69 | * the prev/next entries already! 70 | */ 71 | static __inline__ void __list_del(struct list_head * prev, 72 | struct list_head * next) 73 | { 74 | next->prev = prev; 75 | prev->next = next; 76 | } 77 | 78 | /** 79 | * list_del - deletes entry from list. 80 | * @entry: the element to delete from the list. 81 | * Note: list_empty on entry does not return true after this, the entry is in an undefined state. 82 | */ 83 | static __inline__ void list_del(struct list_head *entry) 84 | { 85 | __list_del(entry->prev, entry->next); 86 | entry->next = entry->prev = 0; 87 | } 88 | 89 | /** 90 | * list_del_init - deletes entry from list and reinitialize it. 91 | * @entry: the element to delete from the list. 92 | */ 93 | static __inline__ void list_del_init(struct list_head *entry) 94 | { 95 | __list_del(entry->prev, entry->next); 96 | INIT_LIST_HEAD(entry); 97 | } 98 | 99 | /** 100 | * list_empty - tests whether a list is empty 101 | * @head: the list to test. 102 | */ 103 | static __inline__ int list_empty(struct list_head *head) 104 | { 105 | return head->next == head; 106 | } 107 | 108 | /** 109 | * list_splice - join two lists 110 | * @list: the new list to add. 111 | * @head: the place to add it in the first list. 112 | */ 113 | static __inline__ void list_splice(struct list_head *list, struct list_head *head) 114 | { 115 | struct list_head *first = list->next; 116 | 117 | if (first != list) { 118 | struct list_head *last = list->prev; 119 | struct list_head *at = head->next; 120 | 121 | first->prev = head; 122 | head->next = first; 123 | 124 | last->next = at; 125 | at->prev = last; 126 | } 127 | } 128 | 129 | /** 130 | * list_entry - get the struct for this entry 131 | * @ptr: the &struct list_head pointer. 132 | * @type: the type of the struct this is embedded in. 133 | * @member: the name of the list_struct within the struct. 134 | */ 135 | #define list_entry(ptr, type, member) \ 136 | ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 137 | 138 | /** 139 | * list_for_each - iterate over a list 140 | * @pos: the &struct list_head to use as a loop counter. 141 | * @head: the head for your list. 142 | */ 143 | #define list_for_each(pos, head) \ 144 | for (pos = (head)->next; pos != (head); \ 145 | pos = pos->next) 146 | 147 | /** 148 | * list_for_each_safe - iterate over a list safe against removal of list entry 149 | * @pos: the &struct list_head to use as a loop counter. 150 | * @n: another &struct list_head to use as temporary storage 151 | * @head: the head for your list. 152 | */ 153 | #define list_for_each_safe(pos, n, head) \ 154 | for (pos = (head)->next, n = pos->next; pos != (head); \ 155 | pos = n, n = pos->next) 156 | 157 | #define list_for_each_reverse_safe(pos, n, head) \ 158 | for (pos = (head)->prev, n = pos->prev; pos != (head); \ 159 | pos = n, n = pos->prev) 160 | 161 | /* 162 | * Double linked lists with a single pointer list head. 163 | * Mostly useful for hash tables where the two pointer list head is 164 | * too wasteful. 165 | * You lose the ability to access the tail in O(1). 166 | */ 167 | 168 | struct hlist_node { 169 | struct hlist_node *next, **pprev; 170 | }; 171 | struct hlist_head { 172 | struct hlist_node *first; 173 | }; 174 | 175 | #define HLIST_HEAD_INIT { .first = NULL } 176 | #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } 177 | #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) 178 | static inline void INIT_HLIST_NODE(struct hlist_node *h) 179 | { 180 | h->next = NULL; 181 | h->pprev = NULL; 182 | } 183 | 184 | static inline int hlist_unhashed(const struct hlist_node *h) 185 | { 186 | return !h->pprev; 187 | } 188 | 189 | static inline int hlist_empty(const struct hlist_head *h) 190 | { 191 | return !h->first; 192 | } 193 | 194 | static inline void __hlist_del(struct hlist_node *n) 195 | { 196 | struct hlist_node *next = n->next; 197 | struct hlist_node **pprev = n->pprev; 198 | *pprev = next; 199 | if (next) 200 | next->pprev = pprev; 201 | } 202 | 203 | static inline void hlist_del(struct hlist_node *n) 204 | { 205 | __hlist_del(n); 206 | n->next = NULL; 207 | n->pprev = NULL; 208 | } 209 | 210 | static inline void hlist_del_init(struct hlist_node *n) 211 | { 212 | if (!hlist_unhashed(n)) { 213 | __hlist_del(n); 214 | INIT_HLIST_NODE(n); 215 | } 216 | } 217 | 218 | static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) 219 | { 220 | struct hlist_node *first = h->first; 221 | n->next = first; 222 | if (first) 223 | first->pprev = &n->next; 224 | h->first = n; 225 | n->pprev = &h->first; 226 | } 227 | 228 | /* next must be != NULL */ 229 | static inline void hlist_add_before(struct hlist_node *n, 230 | struct hlist_node *next) 231 | { 232 | n->pprev = next->pprev; 233 | n->next = next; 234 | next->pprev = &n->next; 235 | *(n->pprev) = n; 236 | } 237 | 238 | static inline void hlist_add_after(struct hlist_node *n, 239 | struct hlist_node *next) 240 | { 241 | next->next = n->next; 242 | n->next = next; 243 | next->pprev = &n->next; 244 | 245 | if(next->next) 246 | next->next->pprev = &next->next; 247 | } 248 | 249 | /* 250 | * Move a list from one list head to another. Fixup the pprev 251 | * reference of the first entry if it exists. 252 | */ 253 | static inline void hlist_move_list(struct hlist_head *old, 254 | struct hlist_head *nnew) 255 | { 256 | nnew->first = old->first; 257 | if (nnew->first) 258 | nnew->first->pprev = &nnew->first; 259 | old->first = NULL; 260 | } 261 | 262 | #define hlist_entry(ptr, type, member) container_of(ptr,type,member) 263 | 264 | #define hlist_for_each(pos, head) \ 265 | for (pos = (head)->first; pos ; \ 266 | pos = pos->next) 267 | 268 | #endif 269 | -------------------------------------------------------------------------------- /olivehc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Entry of OliveHC, and master thread. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "olivehc.h" 9 | 10 | /* set by configure file or command options */ 11 | static char *conf_filename = "olivehc.conf"; 12 | static char error_log[PATH_LENGTH]; 13 | static time_t quit_timeout; 14 | 15 | int master_epoll_fd; 16 | ohc_timer_t master_timer; 17 | LIST_HEAD(master_requests); 18 | FILE *error_filp; 19 | FILE *admin_out_filp; 20 | static time_t quit_time = 0; 21 | 22 | static int olivehc_global_conf_check(ohc_conf_t *conf_cycle) 23 | { 24 | conf_cycle->error_filp = NULL; 25 | if(strcmp(conf_cycle->error_log, error_log)) { 26 | conf_cycle->error_filp = fopen(conf_cycle->error_log, "a"); 27 | if(conf_cycle->error_filp == NULL) { 28 | log_error_admin(errno, "open error log"); 29 | return OHC_ERROR; 30 | } 31 | } 32 | return OHC_OK; 33 | } 34 | 35 | static void olivehc_global_conf_load(ohc_conf_t *conf_cycle) 36 | { 37 | quit_timeout = conf_cycle->quit_timeout; 38 | 39 | if(conf_cycle->error_filp) { 40 | fclose(error_filp); 41 | error_filp = conf_cycle->error_filp; 42 | strcpy(error_log, conf_cycle->error_log); 43 | conf_cycle->error_filp = NULL; 44 | } 45 | } 46 | 47 | static void olivehc_global_conf_rollback(ohc_conf_t *conf_cycle) 48 | { 49 | if(conf_cycle->error_filp) { 50 | fclose(conf_cycle->error_filp); 51 | } 52 | } 53 | 54 | static int olivehc_load_conf(void) 55 | { 56 | #define CONF_CHECK(f) if(f(conf_cycle) != OHC_OK) goto rollback 57 | errno = 0; 58 | 59 | ohc_conf_t *conf_cycle = conf_parse(conf_filename); 60 | if(conf_cycle == NULL) { 61 | return OHC_ERROR; 62 | } 63 | 64 | CONF_CHECK(olivehc_global_conf_check); 65 | CONF_CHECK(device_conf_check); 66 | CONF_CHECK(server_conf_check); 67 | CONF_CHECK(worker_conf_check); 68 | 69 | olivehc_global_conf_load(conf_cycle); 70 | device_conf_load(conf_cycle); 71 | server_conf_load(conf_cycle); 72 | worker_conf_load(conf_cycle); 73 | 74 | return OHC_OK; 75 | 76 | rollback: 77 | olivehc_global_conf_rollback(conf_cycle); 78 | device_conf_rollback(conf_cycle); 79 | server_conf_rollback(conf_cycle); 80 | worker_conf_rollback(conf_cycle); 81 | return OHC_ERROR; 82 | } 83 | 84 | static void olivehc_quit(void) 85 | { 86 | if(quit_time != 0) { /* already in quiting */ 87 | return; 88 | } 89 | 90 | quit_time = timer_now(&master_timer) + quit_timeout; 91 | server_stop_service(); 92 | worker_quit(quit_time); 93 | } 94 | 95 | static void olivehc_status(FILE *filp) 96 | { 97 | device_status(filp); 98 | server_status(filp); 99 | } 100 | 101 | /* handler of admin port, print the current status */ 102 | static void olivehc_admin_handler(int admin_fd) 103 | { 104 | char buf[100]; 105 | int sock_fd; 106 | ssize_t rc; 107 | 108 | sock_fd = tcp_accept(admin_fd, NULL); 109 | if(sock_fd == -1) { 110 | return; 111 | } 112 | 113 | rc = recv(sock_fd, buf, 99, 0); 114 | if(rc < 0) { 115 | return; 116 | } 117 | buf[rc] = '\0'; 118 | 119 | admin_out_filp = fdopen(sock_fd, "w"); 120 | if(admin_out_filp == NULL) { 121 | return; 122 | } 123 | 124 | if(strncmp(buf, "status", 6) == 0) { 125 | olivehc_status(admin_out_filp); 126 | 127 | } else if(strncmp(buf, "reload", 6) == 0) { 128 | rc = olivehc_load_conf(); 129 | if(rc == OHC_OK) { 130 | fputs("Reload successfully!\n", admin_out_filp); 131 | } 132 | 133 | } else if(strncmp(buf, "quit", 4) == 0) { 134 | olivehc_quit(); 135 | fputs("Quiting...\n", admin_out_filp); 136 | 137 | } else if(strncmp(buf, "clear ", 6) == 0) { 138 | rc = server_clear((unsigned short)atoi(buf + 6)); 139 | if(rc == OHC_OK) { 140 | fputs("Server cleared!\n", admin_out_filp); 141 | } 142 | 143 | } else { 144 | fputs("Invalid command!\n", admin_out_filp); 145 | fputs("Usage: status|reload|quit|clear SERVER\n", admin_out_filp); 146 | } 147 | 148 | fclose(admin_out_filp); 149 | admin_out_filp = NULL; 150 | } 151 | 152 | /* entry of the master thread */ 153 | static void olivehc_master_entry(int admin_fd) 154 | { 155 | #define MAX_EVENTS 512 156 | 157 | struct epoll_event events[MAX_EVENTS]; 158 | struct list_head *p, *expires, *safep; 159 | ohc_timer_node_t *tnode; 160 | int rc, i, type; 161 | void *ptr; 162 | ohc_request_t *r; 163 | time_t last, now; 164 | last = timer_now(&master_timer); 165 | 166 | 167 | device_format_load(); 168 | 169 | while(quit_time == 0 || !request_check_quit(quit_time > last)) { 170 | 171 | rc = epoll_wait(master_epoll_fd, events, MAX_EVENTS, 1000); 172 | if(rc == -1 && errno != EINTR) { 173 | log_error_run(errno, "master epoll_wait"); 174 | } 175 | 176 | /* ready events */ 177 | timer_refresh(&master_timer); 178 | for(i = 0; i < rc; i++) { 179 | type = (uintptr_t)events[i].data.ptr & EVENT_TYPE_MASK; 180 | ptr = (void *)((uintptr_t)events[i].data.ptr & ~EVENT_TYPE_MASK); 181 | 182 | switch(type) { 183 | case EVENT_TYPE_LISTEN: 184 | if(ptr == NULL) { 185 | olivehc_admin_handler(admin_fd); 186 | } else { 187 | server_listen_handler((ohc_server_t *)ptr); 188 | } 189 | break; 190 | 191 | case EVENT_TYPE_PIPE: 192 | worker_request_recycle((ohc_worker_t *)ptr); 193 | break; 194 | 195 | default: /* socket */ 196 | r = ptr; 197 | r->event_handler(r); 198 | } 199 | } 200 | 201 | /* timeout requests */ 202 | expires = timer_expire(&master_timer); 203 | list_for_each_safe(p, safep, expires) { 204 | tnode = list_entry(p, ohc_timer_node_t, tnode_node); 205 | r = list_entry(tnode, ohc_request_t, tnode); 206 | 207 | /* request_timeout_handler() will delete @p from @expires */ 208 | request_timeout_handler(r); 209 | } 210 | 211 | /* routines */ 212 | now = timer_now(&master_timer); 213 | if(now != last) { 214 | last = now; 215 | 216 | server_routine(); 217 | device_routine(); 218 | fflush(error_filp); 219 | } 220 | } 221 | 222 | device_format_store(); 223 | } 224 | 225 | int main(int argc, char **argv) 226 | { 227 | char *pid_filename = "olivehc.pid"; 228 | int admin_port = 5210; 229 | char *prefix = NULL; 230 | int daemon_mode = 1, ch; 231 | int admin_fd; 232 | 233 | char *help = "Usage: olivehc [-hvb][-c conf_file][-p prefix][-a admin][-i pid]\n" 234 | "\th: print help\n" 235 | "\tv: print version\n" 236 | "\tb: block, non-daemon mode, for debug\n" 237 | "\tc: set configure file [olivehc.conf]\n" 238 | "\tp: set prefix [.]\n" 239 | "\ta: set admin port [5210]\n" 240 | "\ti: set pid file [olivehc.pid]\n"; 241 | 242 | /* parse and process options */ 243 | while((ch = getopt(argc, argv, "hvbc:p:a:i:")) != -1) { 244 | switch(ch) { 245 | case 'h': 246 | printf("%s", help); 247 | return 0; 248 | case 'v': 249 | printf("%s\n", OHC_VERSION); 250 | return 0; 251 | case 'b': 252 | daemon_mode = 0; 253 | break; 254 | case 'c': 255 | conf_filename = optarg; 256 | break; 257 | case 'p': 258 | prefix = optarg; 259 | break; 260 | case 'a': 261 | admin_port = atoi(optarg); 262 | break; 263 | case 'i': 264 | pid_filename = optarg; 265 | break; 266 | default: 267 | printf("%s", help); 268 | return 1; 269 | } 270 | } 271 | 272 | if(prefix) { 273 | if(chdir(prefix) < 0) { 274 | perror("error in set prefix"); 275 | return 1; 276 | } 277 | } 278 | 279 | /* get into daemon_mode */ 280 | if(daemon_mode) { 281 | if(daemon(1, 1) < 0) { 282 | perror("error in get daemon"); 283 | return 1; 284 | } 285 | /* stderr is set to error_filp, which will be 286 | * closed in olivehc_global_conf_load() later. */ 287 | close(0); 288 | close(1); 289 | } 290 | error_log[0] = '\0'; 291 | error_filp = stderr; 292 | 293 | /* admin_out_filp is init as stderr, so error message in 294 | * olivehc_load_conf() will print out; 295 | * then stderr will be closed in olivehc_global_conf_load(), 296 | * and admin_out_filp will not be used, until be re-set in 297 | * olivehc_admin_handler() before calling olivehc_load_conf(). */ 298 | admin_out_filp = stderr; 299 | 300 | /* master timer */ 301 | timer_init(&master_timer); 302 | 303 | /* master epoll */ 304 | master_epoll_fd = epoll_create(100); 305 | if(master_epoll_fd < 0) { 306 | perror("error in open epoll"); 307 | return 1; 308 | } 309 | 310 | /* admin port */ 311 | admin_fd = tcp_bind(admin_port); 312 | if(admin_fd < 0) { 313 | perror("error in bind admin port"); 314 | return 1; 315 | } 316 | /* constant 1000 */ 317 | tcp_listen(admin_fd); 318 | if(epoll_add_read(master_epoll_fd, admin_fd, (void *)EVENT_TYPE_LISTEN) < 0) { 319 | perror("error in add admin port in epoll"); 320 | return 1; 321 | } 322 | 323 | /* signal ignore*/ 324 | signal(SIGPIPE, SIG_IGN); 325 | 326 | /* load configure */ 327 | if(olivehc_load_conf() == OHC_ERROR) { 328 | return 1; 329 | } 330 | 331 | /* pid file */ 332 | FILE *pid_filp = fopen(pid_filename, "w"); 333 | if(pid_filp == NULL) { 334 | perror("error in open pid file"); 335 | return 1; 336 | } 337 | fprintf(pid_filp, "%d\n", getpid()); 338 | fclose(pid_filp); 339 | 340 | /* run olivehc! */ 341 | olivehc_master_entry(admin_fd); 342 | 343 | /* quit */ 344 | unlink(pid_filename); 345 | return 0; 346 | } 347 | 348 | void log_error(FILE *filp, const char *prefix, int errnum, const char *fmt, ...) 349 | { 350 | if(prefix) { 351 | fputs(prefix, filp); 352 | fputs(" ", filp); 353 | } 354 | 355 | va_list args; 356 | va_start(args, fmt); 357 | vfprintf(filp, fmt, args); 358 | va_end(args); 359 | 360 | if(errnum) { 361 | fprintf(filp, " [%s]\n", strerror(errnum)); 362 | } else { 363 | fputs("\n", filp); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /worker.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Worker thread management, and communication between master. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "worker.h" 9 | 10 | static struct list_head *current_worker = NULL; 11 | 12 | static int workers = 0; 13 | static int new_workers = 0; 14 | 15 | static void worker_destory(ohc_worker_t *worker) 16 | { 17 | epoll_del(worker->epoll_fd, worker->receive_fd); 18 | epoll_del(master_epoll_fd, worker->recycle_fd); 19 | close(worker->receive_fd); 20 | close(worker->dispatch_fd); 21 | close(worker->recycle_fd); 22 | close(worker->return_fd); 23 | close(worker->epoll_fd); 24 | timer_destroy(&worker->timer); 25 | 26 | /* @wnode was deleted already. */ 27 | 28 | free(worker); 29 | } 30 | 31 | static int worker_check_quit(ohc_worker_t *worker) 32 | { 33 | if(worker->quit_time <= timer_now(&worker->timer)) { 34 | request_clean(&worker->working_requests, 0); 35 | } 36 | return worker->request_nr == 0; 37 | } 38 | 39 | static void *worker_entry(void *data) 40 | { 41 | #define MAX_EVENTS 512 42 | ohc_worker_t *worker = data; 43 | ohc_request_t *r; 44 | struct epoll_event events[MAX_EVENTS]; 45 | struct list_head *p, *expires, *safep; 46 | ohc_timer_node_t *tnode; 47 | int rc, i, type; 48 | void *ptr; 49 | 50 | pthread_detach(pthread_self()); 51 | 52 | while(worker->quit_time == 0 || !worker_check_quit(worker)) { 53 | 54 | rc = epoll_wait(worker->epoll_fd, events, MAX_EVENTS, 1000); 55 | if(rc == -1) { 56 | log_error_run(errno, "worker epoll_wait"); 57 | } 58 | 59 | /* try return_fd, if any blocked requests */ 60 | request_clean(&worker->blocked_requests, 0); 61 | 62 | /* ready events */ 63 | timer_refresh(&worker->timer); 64 | for(i = 0; i < rc; i++) { 65 | 66 | ptr = events[i].data.ptr; 67 | type = ((uintptr_t)ptr) & EVENT_TYPE_MASK; 68 | ptr = (void *)(((uintptr_t)ptr) & ~EVENT_TYPE_MASK); 69 | 70 | /* @receive_fd is ready */ 71 | if(type == EVENT_TYPE_PIPE) { 72 | worker_request_receive(worker); 73 | 74 | /* ready requests */ 75 | } else { 76 | r = ptr; 77 | r->event_handler(r); 78 | } 79 | } 80 | 81 | /* timeout requests */ 82 | expires = timer_expire(&worker->timer); 83 | list_for_each_safe(p, safep, expires) { 84 | tnode = list_entry(p, ohc_timer_node_t, tnode_node); 85 | r = list_entry(tnode, ohc_request_t, tnode); 86 | 87 | /* request_timeout_handler() will delete @p from @expires */ 88 | request_timeout_handler(r); 89 | } 90 | } 91 | 92 | worker_destory(worker); 93 | return NULL; 94 | } 95 | 96 | static int worker_create(void) 97 | { 98 | ohc_worker_t *worker; 99 | int fd[2]; 100 | 101 | worker = malloc(sizeof(ohc_worker_t)); 102 | if(worker == NULL) { 103 | goto fail0; 104 | } 105 | 106 | /* 2 pipes. */ 107 | if(pipe(fd) < 0) { 108 | goto fail1; 109 | } 110 | worker->receive_fd = fd[0]; 111 | worker->dispatch_fd = fd[1]; 112 | if(set_nonblock(fd[0]) < 0 || set_nonblock(fd[1]) < 0) { 113 | goto fail2; 114 | } 115 | 116 | if(pipe(fd) < 0) { 117 | goto fail2; 118 | } 119 | worker->recycle_fd = fd[0]; 120 | worker->return_fd = fd[1]; 121 | if(set_nonblock(fd[0]) < 0 || set_nonblock(fd[1]) < 0) { 122 | goto fail3; 123 | } 124 | 125 | /* create epoll, and add recycle_fd */ 126 | worker->epoll_fd = epoll_create(100); 127 | if(worker->epoll_fd < 0) { 128 | goto fail3; 129 | } 130 | 131 | if(epoll_add_read(worker->epoll_fd, worker->receive_fd, 132 | (void *)EVENT_TYPE_PIPE) < 0) { 133 | goto fail4; 134 | } 135 | if(epoll_add_read(master_epoll_fd, worker->recycle_fd, 136 | (void *)((uintptr_t)worker | EVENT_TYPE_PIPE))) { 137 | goto fail5; 138 | } 139 | 140 | /* each worker thread has its own timer */ 141 | timer_init(&worker->timer); 142 | 143 | worker->quit_time = 0; 144 | worker->request_nr = 0; 145 | INIT_LIST_HEAD(&worker->working_requests); 146 | INIT_LIST_HEAD(&worker->blocked_requests); 147 | 148 | /* no head in worker-list, used in worker_request_dispatch() */ 149 | if(current_worker == NULL) { 150 | INIT_LIST_HEAD(&worker->wnode); 151 | current_worker = &worker->wnode; 152 | } else { 153 | list_add(&worker->wnode, current_worker); 154 | } 155 | 156 | /* create thread 157 | * Return: 0 on ok, error number on failure 158 | * arg: worker 159 | */ 160 | if(pthread_create(&worker->tid, NULL, worker_entry, worker) != 0) { 161 | goto fail6; 162 | } 163 | 164 | workers++; 165 | return OHC_OK; 166 | 167 | fail6: 168 | list_del(&worker->wnode); 169 | timer_destroy(&worker->timer); 170 | epoll_del(master_epoll_fd, worker->recycle_fd); 171 | fail5: 172 | epoll_del(worker->epoll_fd, worker->receive_fd); 173 | fail4: 174 | close(worker->epoll_fd); 175 | fail3: 176 | close(worker->recycle_fd); 177 | close(worker->return_fd); 178 | fail2: 179 | close(worker->receive_fd); 180 | close(worker->dispatch_fd); 181 | fail1: 182 | free(worker); 183 | fail0: 184 | return OHC_ERROR; 185 | } 186 | 187 | /* Delete @num workers from the worker-list. 188 | * @quit_time: quit deadline, no limit if 0. 189 | * No new request will be dispatched to the deleted worker, and 190 | * it will quit (and call worker_destory) after returning all 191 | * existing requests. 192 | * We always delete @current_worker->next, because worker_conf_check() 193 | * call worker_create() to add new workers behind @current_worker, 194 | * and worker_conf_rollback() call worker_delete() to delete 195 | * the new workers. */ 196 | static void worker_delete(int num, time_t quit_time) 197 | { 198 | ohc_worker_t *worker; 199 | int i; 200 | 201 | for(i = 0; i < num; i++) { 202 | worker = list_entry(current_worker->next, ohc_worker_t, wnode); 203 | list_del(&worker->wnode); 204 | worker->quit_time = quit_time ? quit_time : BIG_TIME; 205 | workers--; 206 | } 207 | } 208 | 209 | int worker_conf_check(ohc_conf_t *conf_cycle) 210 | { 211 | int i; 212 | 213 | if(conf_cycle->threads == 0) { 214 | log_error_admin(0, "threads must be large than 0"); 215 | return OHC_ERROR; 216 | } 217 | 218 | /* Because worker_create() maybe fail, so we try to create 219 | * some workers here, if needed. 220 | * But we do not delete workers here (we do it in worker_conf_load) 221 | * if needed. Because if we need to rollback later, we have 222 | * to re-create the deleted workers. */ 223 | new_workers = conf_cycle->threads - workers; 224 | for(i = 0; i < new_workers; i++) { 225 | if(worker_create() == OHC_ERROR) { 226 | log_error_admin(errno, "create worker"); 227 | new_workers = i; 228 | return OHC_ERROR; 229 | } 230 | } 231 | return OHC_OK; 232 | } 233 | 234 | void worker_conf_load(ohc_conf_t *conf_cycle) 235 | { 236 | worker_delete(workers - conf_cycle->threads, 0); 237 | new_workers = 0; 238 | } 239 | 240 | void worker_conf_rollback(ohc_conf_t *conf_cycle) 241 | { 242 | worker_delete(new_workers, 0); 243 | } 244 | 245 | void worker_quit(time_t quit_time) 246 | { 247 | worker_delete(workers, quit_time); 248 | } 249 | 250 | /* worker_request_dispatch() and worker_request_return() call this, to 251 | * send @r into @target thread. */ 252 | static int worker_do_request_write(ohc_request_t *r, req_handler_f *handler, ohc_worker_t *target) 253 | { 254 | ohc_worker_t *old = r->worker_thread; 255 | int fd = target ? target->dispatch_fd : old->return_fd; 256 | int rc; 257 | 258 | event_del(r); 259 | r->event_handler = handler; 260 | 261 | r->worker_thread = target; 262 | list_del(&r->rnode); 263 | 264 | rc = write(fd, &r, sizeof(ohc_request_t *)); 265 | if(rc < 0) { 266 | r->worker_thread = old; 267 | list_add(&r->rnode, old ? &old->blocked_requests : &master_requests); 268 | log_error_run(errno, "worker_do_request_write (%p)", target); 269 | return OHC_ERROR; 270 | } 271 | 272 | if(rc != sizeof(ohc_request_t *)) { 273 | log_error_run(0, "!!! worker_do_request_write (%p) %d", target, rc); 274 | exit(1); 275 | } 276 | 277 | return OHC_OK; 278 | } 279 | 280 | /* master call this to dispatch a request to some worker */ 281 | int worker_request_dispatch(ohc_request_t *r, req_handler_f *handler) 282 | { 283 | if(workers == 0) { 284 | return OHC_ERROR; 285 | } 286 | 287 | ohc_worker_t *target = list_entry(current_worker, ohc_worker_t, wnode); 288 | current_worker = current_worker->next; 289 | 290 | int rc = worker_do_request_write(r, handler, target); 291 | if(rc == OHC_OK) { 292 | target->request_nr++; 293 | } 294 | 295 | return rc; 296 | } 297 | 298 | /* worker call this to return a finished request to master */ 299 | int worker_request_return(ohc_request_t *r, req_handler_f *handler) 300 | { 301 | return worker_do_request_write(r, handler, NULL); 302 | } 303 | 304 | 305 | /* worker_request_recycle() and worker_request_receive() call this, to 306 | * read requests from @fd, and add it @target. */ 307 | static int worker_do_request_read(int fd, struct list_head *target) 308 | { 309 | ohc_request_t *reqs[50], *r; 310 | int rc, i, count = 0; 311 | 312 | do { 313 | rc = read(fd, reqs, sizeof(reqs)); 314 | if(rc < 0) { 315 | if(errno != EAGAIN) { 316 | log_error_run(errno, "worker_do_request_read (%p)", target); 317 | } 318 | break; 319 | } 320 | 321 | if(rc & (sizeof(ohc_request_t *) - 1)) { 322 | log_error_run(0, "!!! worker_do_request_read (%p) %d", target, rc); 323 | exit(1); 324 | } 325 | 326 | for(i = 0; i < rc / sizeof(ohc_request_t *); i++) { 327 | r = reqs[i]; 328 | list_add(&r->rnode, target); 329 | r->event_handler(r); 330 | count++; 331 | } 332 | 333 | } while(rc == sizeof(reqs)); 334 | 335 | return count; 336 | } 337 | 338 | /* master call this to recycle a finished request from worker */ 339 | void worker_request_recycle(ohc_worker_t *worker) 340 | { 341 | int count = worker_do_request_read(worker->recycle_fd, &master_requests); 342 | worker->request_nr -= count; 343 | } 344 | 345 | /* worker call this to receive a request from master */ 346 | void worker_request_receive(ohc_worker_t *worker) 347 | { 348 | worker_do_request_read(worker->receive_fd, &worker->working_requests); 349 | } 350 | -------------------------------------------------------------------------------- /http.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Parse HTTP request, make HTTP response. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "http.h" 9 | 10 | 11 | typedef int http_parse_f(ohc_request_t *, char *, ssize_t); 12 | struct http_header_s { 13 | string_t name; 14 | http_parse_f *handler; 15 | }; 16 | 17 | static int http_parse_put_content_length(ohc_request_t *r, char *p, ssize_t len) 18 | { 19 | char *endp; 20 | r->content_length = strtoul(p, &endp, 10); 21 | if(endp == p || endp != p + len) { 22 | r->error_reason = "InvalidContentLength"; 23 | return OHC_ERROR; 24 | } 25 | return OHC_OK; 26 | } 27 | 28 | static int http_parse_host(ohc_request_t *r, char *p, ssize_t len) 29 | { 30 | r->host.base = p; 31 | r->host.len = len; 32 | return OHC_OK; 33 | } 34 | 35 | static int http_parse_ohc_key(ohc_request_t *r, char *p, ssize_t len) 36 | { 37 | r->ohc_key.base = p; 38 | r->ohc_key.len = len; 39 | return OHC_OK; 40 | } 41 | 42 | static int http_parse_put_cache_control(ohc_request_t *r, char *p, ssize_t len) 43 | { 44 | char *value, *endp; 45 | 46 | if(r->server->expire_force != 0) { 47 | return OHC_DECLINE; 48 | } 49 | if(strncmp(p, "max-age=", 8) != 0) { 50 | r->error_reason = "UnhandledCacheControl"; 51 | return OHC_ERROR; 52 | } 53 | 54 | value = p + 8; 55 | r->expire = strtoul(value, &endp, 10); 56 | if(endp == p || endp != p + len) { 57 | r->error_reason = "InvalidMaxAge"; 58 | return OHC_ERROR; 59 | } 60 | if(r->expire == 0) { 61 | r->error_reason = "max-age=0"; 62 | return OHC_ERROR; 63 | } 64 | r->expire += timer_now(&master_timer); 65 | 66 | return OHC_DECLINE; 67 | } 68 | 69 | static int http_parse_put_expires(ohc_request_t *r, char *p, ssize_t len) 70 | { 71 | if(r->server->expire_force != 0 || r->expire != 0) { 72 | return OHC_DECLINE; 73 | } 74 | r->expire = timer_parse_rfc1123(p); 75 | if(r->expire == (time_t)-1 || r->expire <= timer_now(&master_timer)) { 76 | r->error_reason = "InvalidExpires"; 77 | return OHC_ERROR; 78 | } 79 | return OHC_DECLINE; 80 | } 81 | 82 | static int http_parse_get_range(ohc_request_t *r, char *p, ssize_t len) 83 | { 84 | ssize_t start, end; 85 | char *begin = p; 86 | char *q; 87 | 88 | r->range.base = begin; 89 | r->range.len = len; 90 | 91 | if(strncmp(p, "bytes=", 6)) { 92 | goto fail; 93 | } 94 | p += 6; 95 | 96 | /* start */ 97 | q = p; 98 | start = 0; 99 | while (*p >= '0' && *p <= '9') { 100 | start = start * 10 + *p++ - '0'; 101 | } 102 | if(p == q) { 103 | start = RANGE_NO_SET; 104 | } 105 | 106 | /* - */ 107 | if(*p++ != '-') { 108 | goto fail; 109 | } 110 | 111 | /* end */ 112 | q = p; 113 | end = 0; 114 | while (*p >= '0' && *p <= '9') { 115 | end = end * 10 + *p++ - '0'; 116 | } 117 | if(p == q) { 118 | end = RANGE_NO_SET; 119 | } 120 | 121 | /* check */ 122 | if(start == RANGE_NO_SET && end == RANGE_NO_SET) { 123 | goto fail; 124 | } 125 | if(start != RANGE_NO_SET && end != RANGE_NO_SET && start > end) { 126 | goto fail; 127 | } 128 | 129 | while(*p == ' ') p++; 130 | if(*p == ',') { 131 | /* not surport multi-range */ 132 | return OHC_OK; 133 | } 134 | if(*p != '\r') { 135 | goto fail; 136 | } 137 | 138 | r->range_set = 1; 139 | r->range_start = start; 140 | r->range_end = end; 141 | return OHC_OK; 142 | fail: 143 | r->error_reason = "InvalidRange"; 144 | return OHC_ERROR; 145 | } 146 | 147 | static int http_parse_connection(ohc_request_t *r, char *p, ssize_t len) 148 | { 149 | if(strncmp(p, "close", 5) == 0) { 150 | r->keepalive = 0; 151 | } 152 | return OHC_OK; 153 | } 154 | 155 | #define GENERAL_HEADERS \ 156 | {STRING_INIT("Host:"), http_parse_host}, \ 157 | {STRING_INIT("OHC-Key:"), http_parse_ohc_key}, \ 158 | {STRING_INIT("Connection:"), http_parse_connection}, \ 159 | {STRING_INIT(""), NULL}, 160 | 161 | static struct http_header_s http_request_header_get[] = { 162 | {STRING_INIT("Range:"), http_parse_get_range}, 163 | GENERAL_HEADERS 164 | }; 165 | 166 | static struct http_header_s http_request_header_put[] = { 167 | {STRING_INIT("Content-Length:"), http_parse_put_content_length}, 168 | {STRING_INIT("Cache-Control:"), http_parse_put_cache_control}, 169 | {STRING_INIT("Expires"), http_parse_put_expires}, 170 | GENERAL_HEADERS 171 | }; 172 | 173 | static struct http_header_s http_request_header_delete[] = { 174 | GENERAL_HEADERS 175 | }; 176 | 177 | static void http_add_put_headers(ohc_request_t *r, char *base, ssize_t len) 178 | { 179 | string_t *p; 180 | 181 | if(r->put_header_nr == 0) { 182 | r->put_headers[0].base = base; 183 | r->put_headers[0].len = len; 184 | r->put_header_nr = 1; 185 | } else { 186 | p = &r->put_headers[r->put_header_nr - 1]; 187 | if(p->base + p->len == base) { 188 | p->len += len; 189 | } else { 190 | p++; 191 | p->base = base; 192 | p->len = len; 193 | r->put_header_nr++; 194 | } 195 | } 196 | } 197 | 198 | struct http_method_s http_methods[] = { 199 | {STRING_INIT("GET "), http_request_header_get}, 200 | {STRING_INIT("HEAD "), http_request_header_get}, 201 | {STRING_INIT("PUT "), http_request_header_put}, 202 | {STRING_INIT("POST "), http_request_header_put}, 203 | {STRING_INIT("PURGE "), http_request_header_delete}, 204 | {STRING_INIT("DELETE "), http_request_header_delete}, 205 | {STRING_INIT("XXX "), NULL} 206 | }; 207 | 208 | 209 | /* parse HTTP request. do not mark the status if not complete, so 210 | * work fast for one-packet-request, and slowly for others. */ 211 | int http_request_parse(ohc_request_t *r) 212 | { 213 | struct http_header_s *headers = NULL, *h; 214 | struct http_method_s *method; 215 | char *p, *q; 216 | char *name, *value_base; 217 | ssize_t value_len; 218 | int rc = OHC_OK; 219 | int is_store; 220 | 221 | p = r->_buffer; 222 | *r->buf_pos = '\0'; 223 | r->put_header_nr = 0; 224 | r->put_headers[0].base = NULL; 225 | r->put_headers[0].len = 0; 226 | r->put_header_length = 0; 227 | 228 | /* method */ 229 | for(method = http_methods; method->data; method++) { 230 | if(strncmp(p, method->str.base, method->str.len) == 0) { 231 | r->method = method - http_methods; 232 | p += method->str.len; 233 | headers = method->data; 234 | break; 235 | } 236 | } 237 | if(method->data == NULL) { 238 | r->error_reason = "UnknownMethod"; 239 | goto fail; 240 | } 241 | is_store = (headers == http_request_header_put); 242 | 243 | /* uri */ 244 | while(*p == ' ') p++; 245 | if((q = strchr(p, ' ')) == NULL) { 246 | goto not_complete; 247 | } 248 | r->uri.base = p; 249 | r->uri.len = q - p; 250 | p = q + 1; 251 | 252 | /* HTTP/1.1 */ 253 | while(*p == ' ') p++; 254 | if(strncasecmp(p, "HTTP/", 5) != 0) { 255 | r->error_reason = "NotHTTP"; 256 | goto fail; 257 | } 258 | if((q = strchr(p + 5, '\r')) == NULL) { 259 | goto not_complete; 260 | } 261 | p = q + 2; 262 | 263 | /* headers */ 264 | while(1) { 265 | while(*p == ' ') p++; 266 | if(*p == '\r' && *(p+1) == '\n') { 267 | break; 268 | } 269 | 270 | /* header name */ 271 | if((q = strchr(p, ':')) == NULL) { 272 | goto not_complete; 273 | } 274 | name = p; 275 | p = q + 1; 276 | 277 | /* header value */ 278 | while(*p == ' ') p++; 279 | if((q = strchr(p, '\r')) == NULL || *(q+1) == '\0') { 280 | goto not_complete; 281 | } 282 | value_base = p; 283 | value_len = q - p; 284 | p = q + 2; 285 | 286 | for(h = headers; h->handler; h++) { 287 | if(strncasecmp(name, h->name.base, h->name.len) == 0) { 288 | 289 | rc = h->handler(r, value_base, value_len); 290 | if(rc == OHC_ERROR) { 291 | goto fail; 292 | } 293 | break; 294 | } 295 | } 296 | 297 | /* if PUT/POST, record the un-handled headers */ 298 | if(is_store && (h->handler == NULL || rc == OHC_DECLINE)) { 299 | 300 | http_add_put_headers(r, name, p - name); 301 | r->put_header_length += p - name; 302 | } 303 | } 304 | 305 | if(is_store) { 306 | if(r->content_length == -1) { 307 | r->error_reason = "NoContentLengthinPUT"; 308 | goto fail; 309 | } 310 | 311 | if(r->buf_pos - p - 2 > r->content_length) { 312 | r->error_reason = "BodyLargerThanDeclared"; 313 | goto fail; 314 | } 315 | 316 | /* add the pre-read body */ 317 | http_add_put_headers(r, p, r->buf_pos - p); 318 | 319 | r->put_header_length += http_make_200_response_header(r->content_length, NULL); 320 | r->put_header_length += 2; /* "\r\n" */ 321 | } 322 | 323 | return OHC_DONE; 324 | 325 | not_complete: 326 | if(strstr(p, "\r\n\r\n")) { 327 | goto fail; 328 | } 329 | return OHC_AGAIN; 330 | 331 | fail: 332 | r->keepalive = 0; 333 | r->http_code = 400; 334 | return OHC_ERROR; 335 | } 336 | 337 | string_t *http_code_page(int code) 338 | { 339 | #define HTTP_PAGE_CONLEN(code, desc) \ 340 | {code, STRING_INIT("HTTP/1.1 " #code " " desc "\r\nContent-Length: 0\r\n\r\n")} 341 | #define HTTP_PAGE_NOLEN(code, desc) \ 342 | {code, STRING_INIT("HTTP/1.1 " #code " " desc "\r\n\r\n")} 343 | 344 | static struct http_page_s { 345 | int code; 346 | string_t page; 347 | } http_pages[] = { 348 | HTTP_PAGE_NOLEN (201, "Created"), 349 | HTTP_PAGE_NOLEN (204, "No Content"), 350 | HTTP_PAGE_CONLEN(400, "Bad Request"), 351 | HTTP_PAGE_CONLEN(404, "Not Found"), 352 | HTTP_PAGE_CONLEN(413, "Request Entity Too Large"), 353 | HTTP_PAGE_CONLEN(416, "Requested Range Not Satisfiable"), 354 | HTTP_PAGE_CONLEN(500, "Internal Server Error"), 355 | }; 356 | int i; 357 | 358 | for(i = 0; i < sizeof(http_pages) / sizeof(struct http_page_s); i++) { 359 | if(http_pages[i].code == code) { 360 | return &http_pages[i].page; 361 | } 362 | } 363 | 364 | return NULL; /* should not happen */ 365 | } 366 | 367 | ssize_t http_make_206_response_header(ssize_t range_start, ssize_t range_end, 368 | ssize_t body_len, char *output) 369 | { 370 | return sprintf(output, "HTTP/1.1 206 Partial Content\r\n" 371 | "Content-Length: %ld\r\n" 372 | "Content-Range: bytes %ld-%ld/%ld\r\n", 373 | range_end - range_start + 1, 374 | range_start, range_end, body_len); 375 | } 376 | 377 | ssize_t http_make_200_response_header(ssize_t content_length, char *output) 378 | { 379 | #define RESP_200_CONLEN "HTTP/1.1 200 OK\r\nContent-Length: " 380 | if(output) { 381 | return sprintf(output, RESP_200_CONLEN "%ld\r\n", content_length); 382 | } else { 383 | return sizeof(RESP_200_CONLEN "\r\n") - 1 + numlen(content_length); 384 | } 385 | } 386 | 387 | ssize_t http_decode_uri(const char *uri, ssize_t len, char *output) 388 | { 389 | int a, b; 390 | ssize_t i, output_len = 0; 391 | 392 | for(i = 0; i < len; i++) { 393 | if(uri[i] == '%') { 394 | a = char2hex(uri[i+1]); 395 | b = char2hex(uri[i+2]); 396 | if(a >= 0 && b >= 0) { 397 | output[output_len++] = (a << 4) + b; 398 | i += 2; 399 | continue; 400 | } 401 | } 402 | output[output_len++] = uri[i]; 403 | } 404 | return output_len; 405 | } 406 | -------------------------------------------------------------------------------- /conf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Parse configure file. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "conf.h" 9 | 10 | #define COMMAND_LENGTH PATH_LENGTH 11 | #define ARGUMENT_LENGTH PATH_LENGTH 12 | 13 | #define OHC_CONF_DONE (char *)OHC_DONE 14 | #define OHC_CONF_OK (char *)OHC_OK 15 | #define OHC_CONF_AGAIN (char *)OHC_AGAIN 16 | 17 | 18 | static ohc_conf_t conf_cycle; 19 | static ohc_server_t default_server; 20 | static LIST_HEAD(reserved_servers); 21 | static LIST_HEAD(reserved_devices); 22 | 23 | typedef struct command_s ohc_conf_command_t; 24 | typedef const char *conf_parse_f(ohc_conf_command_t *, void *, char *); 25 | 26 | //24=8+12+4 27 | struct command_s { 28 | char *name; 29 | conf_parse_f *set_handler; 30 | int offset; 31 | }; 32 | 33 | /* handler of PATH type argument */ 34 | static const char *conf_set_path(ohc_conf_command_t *cmd, void *data, char *arg) 35 | { 36 | char *dest = ((char *)data) + cmd->offset; 37 | 38 | strncpy(dest, arg, PATH_LENGTH); 39 | return (dest[PATH_LENGTH - 1] != '\0') ? "path too long" : OHC_CONF_OK; 40 | } 41 | 42 | /* handler of INT type argument */ 43 | static const char *conf_set_int(ohc_conf_command_t *cmd, void *data, char *arg) 44 | { 45 | char *endp; 46 | long n = strtoul(arg, &endp, 0); 47 | if(*endp != '\0' || n > INT_MAX || n < 0) { 48 | return "invalid integer"; 49 | } 50 | 51 | *((int *)(((char *)data) + cmd->offset)) = (int)n; 52 | return OHC_CONF_OK; 53 | } 54 | 55 | /* handler of SIZE type argument, supporting K,M,G,T */ 56 | static const char *conf_set_size(ohc_conf_command_t *cmd, void *data, char *arg) 57 | { 58 | char *endp; 59 | int shift; 60 | long n = strtol(arg, &endp, 0); 61 | if(endp == arg || n == LONG_MAX || n < 0) { 62 | return "invalid size"; 63 | } 64 | 65 | switch(*endp) { 66 | case 'T': shift = 40; endp++; break; 67 | case 'G': shift = 30; endp++; break; 68 | case 'M': shift = 20; endp++; break; 69 | case 'K': shift = 10; endp++; break; 70 | case '\0': shift = 0; break; 71 | default: return "invalid size"; 72 | } 73 | 74 | if(*endp != '\0') { 75 | return "invalid size"; 76 | } 77 | 78 | if(shift != 0 && (n & (~0UL << (64 - shift)))) { 79 | return "invalid size"; 80 | } 81 | 82 | *((size_t *)(((char *)data) + cmd->offset)) = n << shift; 83 | return OHC_CONF_OK; 84 | } 85 | 86 | /* handler of FLAG type argument, only 'on' and 'off' are allowed */ 87 | static const char *conf_set_flag(ohc_conf_command_t *cmd, void *data, char *arg) 88 | { 89 | ohc_flag_t flag; 90 | 91 | if(strcmp(arg, "on") == 0) { 92 | flag = 1; 93 | } else if(strcmp(arg, "off") == 0) { 94 | flag = 0; 95 | } else { 96 | return "invalid flag"; 97 | } 98 | 99 | *((ohc_flag_t *)(((char *)data) + cmd->offset)) = flag; 100 | return OHC_CONF_OK; 101 | } 102 | 103 | static const char *conf_new_device(ohc_conf_command_t *cmd, void *data, char *arg) 104 | { 105 | ohc_device_t *device; 106 | struct list_head *p; 107 | 108 | if(!list_empty(&reserved_devices)) { 109 | p = reserved_devices.next; 110 | list_del(p); 111 | device = list_entry(p, ohc_device_t, dnode); 112 | } else { 113 | device = malloc(sizeof(ohc_device_t)); 114 | if(device == NULL) { 115 | return "NoMem"; 116 | } 117 | } 118 | bzero(device, sizeof(ohc_device_t)); 119 | 120 | device->fd = -1; 121 | strcpy(device->filename, arg); 122 | list_add_tail(&device->dnode, &conf_cycle.devices); 123 | return OHC_CONF_OK; 124 | } 125 | 126 | static const char *conf_new_server(ohc_conf_command_t *cmd, void *data, char *arg) 127 | { 128 | ohc_server_t *server; 129 | struct list_head *p; 130 | char *endp; 131 | long port = strtol(arg, &endp, 0); 132 | if(*endp != '\0' || port < 0 || port > 65535) { 133 | return "invalid port"; 134 | } 135 | 136 | if(!list_empty(&reserved_servers)) { 137 | p = reserved_servers.next; 138 | list_del(p); 139 | server = list_entry(p, ohc_server_t, snode); 140 | } else { 141 | server = malloc(sizeof(ohc_server_t)); 142 | if(server == NULL) { 143 | return "NoMem"; 144 | } 145 | } 146 | 147 | *server = default_server; 148 | server->listen_port = (unsigned short)port; 149 | server->listen_fd = -1; 150 | list_add_tail(&server->snode, &conf_cycle.servers); 151 | 152 | return OHC_CONF_OK; 153 | } 154 | 155 | /* all configure commands, except 'include' */ 156 | #define COMMAND_NUMBER (int)(sizeof(g_commands) / sizeof(ohc_conf_command_t)) 157 | static ohc_conf_command_t g_commands[] = { 158 | /* global */ 159 | { "threads", 160 | conf_set_int, 161 | offsetof(ohc_conf_t, threads) 162 | }, 163 | { "error_log", 164 | conf_set_path, 165 | offsetof(ohc_conf_t, error_log) 166 | }, 167 | { "quit_timeout", 168 | conf_set_int, 169 | offsetof(ohc_conf_t, quit_timeout) 170 | }, 171 | { "device_badblock_percent", 172 | conf_set_int, 173 | offsetof(ohc_conf_t, device_badblock_percent) 174 | }, 175 | { "device_check_270G", 176 | conf_set_flag, 177 | offsetof(ohc_conf_t, device_check_270G) 178 | }, 179 | { "device", 180 | conf_new_device, 181 | 0 182 | }, 183 | 184 | /* server */ 185 | { "listen", 186 | conf_new_server, 187 | 0 188 | }, 189 | { "capacity", 190 | conf_set_size, 191 | offsetof(ohc_server_t, capacity) 192 | }, 193 | { "access_log", 194 | conf_set_path, 195 | offsetof(ohc_server_t, access_log) 196 | }, 197 | { "connections_limit", 198 | conf_set_int, 199 | offsetof(ohc_server_t, connections_limit) 200 | }, 201 | { "rcvbuf", 202 | conf_set_size, 203 | offsetof(ohc_server_t, rcvbuf) 204 | }, 205 | { "sndbuf", 206 | conf_set_size, 207 | offsetof(ohc_server_t, sndbuf) 208 | }, 209 | { "recv_timeout", 210 | conf_set_int, 211 | offsetof(ohc_server_t, recv_timeout) 212 | }, 213 | { "request_timeout", 214 | conf_set_int, 215 | offsetof(ohc_server_t, request_timeout) 216 | }, 217 | { "send_timeout", 218 | conf_set_int, 219 | offsetof(ohc_server_t, send_timeout) 220 | }, 221 | { "expire_default", 222 | conf_set_int, 223 | offsetof(ohc_server_t, expire_default) 224 | }, 225 | { "expire_force", 226 | conf_set_int, 227 | offsetof(ohc_server_t, expire_force) 228 | }, 229 | { "keepalive_timeout", 230 | conf_set_int, 231 | offsetof(ohc_server_t, keepalive_timeout) 232 | }, 233 | { "item_max_size", 234 | conf_set_size, 235 | offsetof(ohc_server_t, item_max_size) 236 | }, 237 | { "key_include_host", 238 | conf_set_flag, 239 | offsetof(ohc_server_t, key_include_host) 240 | }, 241 | { "key_include_ohc_key", 242 | conf_set_flag, 243 | offsetof(ohc_server_t, key_include_ohc_key) 244 | }, 245 | { "key_include_query", 246 | conf_set_flag, 247 | offsetof(ohc_server_t, key_include_query) 248 | }, 249 | { "server_dump", 250 | conf_set_flag, 251 | offsetof(ohc_server_t, server_dump) 252 | }, 253 | { "shutdown_if_not_store", 254 | conf_set_flag, 255 | offsetof(ohc_server_t, shutdown_if_not_store) 256 | }, 257 | { "passby_enable", 258 | conf_set_flag, 259 | offsetof(ohc_server_t, passby_enable) 260 | }, 261 | { "passby_begin_item_nr", 262 | conf_set_int, 263 | offsetof(ohc_server_t, passby_begin_item_nr) 264 | }, 265 | { "passby_begin_consumed", 266 | conf_set_size, 267 | offsetof(ohc_server_t, passby_begin_consumed) 268 | }, 269 | { "passby_limit_nr", 270 | conf_set_int, 271 | offsetof(ohc_server_t, passby_limit_nr) 272 | }, 273 | { "passby_expire", 274 | conf_set_int, 275 | offsetof(ohc_server_t, passby_expire) 276 | }, 277 | { "status_period", 278 | conf_set_int, 279 | offsetof(ohc_server_t, status_period) 280 | }, 281 | }; 282 | 283 | static const char *conf_readline(FILE *fp, char *cmd, char *arg) 284 | { 285 | char buffer[PATH_LENGTH]; 286 | char *cmd_start, *cmd_end, *arg_start, *arg_end, *line_end; 287 | 288 | if(fgets(buffer, PATH_LENGTH, fp) == NULL) { 289 | return OHC_CONF_DONE; 290 | } 291 | 292 | cmd_start = strnonwhite(buffer); 293 | if(*cmd_start == '\0' || *cmd_start == '#') { 294 | return OHC_CONF_AGAIN; 295 | } 296 | 297 | cmd_end = strwhite(cmd_start); 298 | if(*cmd_end == '\0') { 299 | return "miss argument"; 300 | } 301 | 302 | arg_start = strnonwhite(cmd_end); 303 | if(*arg_start == '\0') { 304 | return "miss argument"; 305 | } 306 | 307 | arg_end = strwhite(arg_start); 308 | 309 | line_end = strnonwhite(arg_end); 310 | if(*line_end != '\0' && *line_end != '#') { 311 | return "too many arguments"; 312 | } 313 | 314 | *cmd_end = *arg_end = '\0'; 315 | memcpy(cmd, cmd_start, cmd_end - cmd_start + 1); 316 | memcpy(arg, arg_start, arg_end - arg_start + 1); 317 | return OHC_CONF_OK; 318 | } 319 | 320 | /* parse one file. @deep is used to control include-endless-loop. */ 321 | static int conf_parse_file(const char *filename, int deep) 322 | { 323 | char cmd[COMMAND_LENGTH]; 324 | char arg[ARGUMENT_LENGTH]; 325 | ohc_conf_command_t *c; 326 | void *context; 327 | FILE *fp; 328 | int line = 0, i; 329 | const char *msg_rc; 330 | static int server_context_begin = -1; 331 | 332 | /* init server_context_begin */ 333 | if(server_context_begin == -1) { 334 | for(i = 0; i < COMMAND_NUMBER; i++) { 335 | if(strcmp("listen", g_commands[i].name) == 0) { 336 | server_context_begin = i; 337 | break; 338 | } 339 | } 340 | } 341 | 342 | if(deep > 10) { 343 | log_error_admin(0, "configure file include too deep"); 344 | return OHC_ERROR; 345 | } 346 | 347 | fp = fopen(filename, "r"); 348 | if(fp == NULL) { 349 | log_error_admin(errno, "open conf file %s ", filename); 350 | return OHC_ERROR; 351 | } 352 | 353 | while(1) { 354 | /* read line from conf file */ 355 | line++; 356 | msg_rc = conf_readline(fp, cmd, arg); 357 | if(msg_rc == OHC_CONF_AGAIN) { 358 | continue; 359 | } 360 | if(msg_rc != OHC_CONF_OK) { 361 | break; 362 | } 363 | 364 | /* "include" */ 365 | if(strcmp(cmd, "include") == 0) { 366 | char *included_file = arg; 367 | char buffer[PATH_LENGTH]; 368 | char *dir = strrchr(filename, '/'); 369 | if(dir) { 370 | int len = dir - filename + 1; 371 | memcpy(buffer, filename, len); 372 | strncpy(buffer + len, arg, PATH_LENGTH - len); 373 | included_file = buffer; 374 | } 375 | 376 | if(conf_parse_file(included_file, deep + 1) != OHC_OK) { 377 | msg_rc = "error in include file"; 378 | break; 379 | } 380 | continue; 381 | } 382 | 383 | /* normal commands */ 384 | for(i = 0; i < COMMAND_NUMBER; i++) { 385 | c = &g_commands[i]; 386 | if(strcmp(cmd, c->name) == 0) { 387 | break; 388 | } 389 | } 390 | 391 | if(i == COMMAND_NUMBER) { 392 | msg_rc = "no command match"; 393 | break; 394 | } 395 | 396 | if(i < server_context_begin) { 397 | if(!list_empty(&conf_cycle.servers)) { 398 | msg_rc = "global command in server context"; 399 | break; 400 | } 401 | context = &conf_cycle; 402 | } else { 403 | context = list_empty(&conf_cycle.servers) 404 | ? &default_server 405 | : list_entry(conf_cycle.servers.prev, ohc_server_t, snode); 406 | } 407 | 408 | /* call handler */ 409 | msg_rc = c->set_handler(c, context, arg); 410 | if(msg_rc != OHC_CONF_OK) { 411 | break; 412 | } 413 | } 414 | 415 | fclose(fp); 416 | 417 | if(msg_rc != OHC_CONF_DONE) { 418 | log_error_admin(0, "%s in line %d in conf file %s", 419 | msg_rc, line, filename); 420 | return OHC_ERROR; 421 | } 422 | return OHC_OK; 423 | } 424 | 425 | /* parse file and put the result into conf_cycle. entry of this file. */ 426 | ohc_conf_t *conf_parse(const char *filename) 427 | { 428 | /* reuse the servers and devices, in order to avoid @conf_destroy. */ 429 | static int first = 1; 430 | if(first) { 431 | first = 0; 432 | } else { 433 | list_splice(&conf_cycle.servers, &reserved_servers); 434 | list_splice(&conf_cycle.devices, &reserved_devices); 435 | } 436 | 437 | /* init conf */ 438 | INIT_LIST_HEAD(&conf_cycle.devices); 439 | INIT_LIST_HEAD(&conf_cycle.servers); 440 | conf_cycle.threads = 4; 441 | conf_cycle.quit_timeout = 60; 442 | conf_cycle.device_badblock_percent = 1; 443 | conf_cycle.device_check_270G = 1; 444 | strcpy(conf_cycle.error_log, "error.log"); 445 | 446 | /* init default_server */ 447 | bzero(&default_server, sizeof(default_server)); 448 | default_server.capacity = 0; 449 | default_server.connections_limit = 1000; 450 | default_server.send_timeout = 60; 451 | default_server.recv_timeout = 60; 452 | default_server.request_timeout = 60; 453 | default_server.keepalive_timeout = 60; 454 | default_server.item_max_size = 100 << 20; /*100M*/ 455 | default_server.expire_default = 259200; /*3days*/ 456 | default_server.expire_force = 0; 457 | default_server.sndbuf = 0; 458 | default_server.rcvbuf = 0; 459 | default_server.server_dump = 1; 460 | default_server.shutdown_if_not_store = 0; 461 | default_server.key_include_query = 0; 462 | default_server.key_include_host = 0; 463 | default_server.key_include_ohc_key = 0; 464 | default_server.passby_enable = 0; 465 | default_server.passby_begin_item_nr = 1000*1000; 466 | default_server.passby_begin_consumed = 100L << 30; /*100G*/ 467 | default_server.passby_limit_nr = 1000*1000; 468 | default_server.passby_expire = 3600; 469 | default_server.status_period = 60; 470 | strcpy(default_server.access_log, "access.log"); 471 | 472 | /* parse */ 473 | if(conf_parse_file(filename, 0) == OHC_ERROR) { 474 | return NULL; 475 | } 476 | return &conf_cycle; 477 | } 478 | -------------------------------------------------------------------------------- /nginx-jstore/ngx_http_jstore_filter_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * store response to a upstream 4 | * 5 | * Created on: 2011-11-26 6 | * Author: Wu Bingzheng 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct { 15 | /* response body chain */ 16 | ngx_chain_t *buf_chain_head; 17 | ngx_chain_t *buf_chain_tail; 18 | 19 | off_t copied_length; 20 | 21 | unsigned subr_sent:1; 22 | 23 | unsigned store_enable:1; 24 | } ngx_http_jstore_ctx_t; 25 | 26 | typedef struct { 27 | ngx_str_t target; 28 | off_t max_size; 29 | ngx_flag_t check_cacheable; 30 | } ngx_http_jstore_loc_conf_t; 31 | 32 | static ngx_command_t ngx_http_jstore_commands[] = { 33 | { 34 | ngx_string("jstore"), 35 | NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 36 | ngx_conf_set_str_slot, 37 | NGX_HTTP_LOC_CONF_OFFSET, 38 | offsetof(ngx_http_jstore_loc_conf_t, target), 39 | NULL 40 | }, 41 | { 42 | ngx_string("jstore_max_size"), 43 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 44 | ngx_conf_set_size_slot, 45 | NGX_HTTP_LOC_CONF_OFFSET, 46 | offsetof(ngx_http_jstore_loc_conf_t, max_size), 47 | NULL 48 | }, 49 | { 50 | ngx_string("jstore_check_cacheable"), 51 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 52 | ngx_conf_set_flag_slot, 53 | NGX_HTTP_LOC_CONF_OFFSET, 54 | offsetof(ngx_http_jstore_loc_conf_t, check_cacheable), 55 | NULL 56 | }, 57 | ngx_null_command 58 | }; 59 | 60 | static ngx_int_t ngx_http_jstore_init(ngx_conf_t *cf); 61 | static void *ngx_http_jstore_create_loc_conf(ngx_conf_t *cf); 62 | static char *ngx_http_jstore_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 63 | 64 | 65 | static ngx_http_module_t ngx_http_jstore_filter_module_ctx = { 66 | NULL, /* preconfiguration */ 67 | ngx_http_jstore_init, /* postconfiguration */ 68 | 69 | NULL, /* create main configuration */ 70 | NULL, /* init main configuration */ 71 | 72 | NULL, /* create server configuration */ 73 | NULL, /* merge server configuration */ 74 | 75 | ngx_http_jstore_create_loc_conf, /* create location configuration */ 76 | ngx_http_jstore_merge_loc_conf, /* merge location configuration */ 77 | }; 78 | 79 | ngx_module_t ngx_http_jstore_filter_module = { 80 | NGX_MODULE_V1, 81 | &ngx_http_jstore_filter_module_ctx, /* module context */ 82 | ngx_http_jstore_commands, /* module directives */ 83 | NGX_HTTP_MODULE, /* module type */ 84 | NULL, /* init master */ 85 | NULL, /* init module */ 86 | NULL, /* init process */ 87 | NULL, /* init thread */ 88 | NULL, /* exit thread */ 89 | NULL, /* exit process */ 90 | NULL, /* exit master */ 91 | NGX_MODULE_V1_PADDING 92 | }; 93 | 94 | 95 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 96 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 97 | 98 | /* the space following "POST" is nessicery because nginx copy it; 99 | * the comment following space is used as marking one request is 100 | * jstore's subrequest. */ 101 | #define JSUBR_METHOD_STR "POST #jstore-subr" 102 | #define JSUBR_METHOD_LEN 4 103 | 104 | static ngx_int_t 105 | ngx_http_jstore_is_subrequest(ngx_http_request_t *r) 106 | { 107 | return (r != r->main) && (r->method_name.data == (u_char *)JSUBR_METHOD_STR); 108 | } 109 | 110 | static ngx_int_t 111 | ngx_http_jstore_handler(ngx_http_request_t *r) 112 | { 113 | ngx_http_jstore_loc_conf_t *jlcf; 114 | ngx_http_jstore_ctx_t *ctx; 115 | 116 | jlcf = ngx_http_get_module_loc_conf(r, ngx_http_jstore_filter_module); 117 | if(jlcf == NULL || jlcf->target.data == NULL) { 118 | return NGX_DECLINED; 119 | } 120 | 121 | /* store only when the last status is 404, which means cache miss; 122 | * while not store if 50x, which means cache fails. */ 123 | if(r->upstream == NULL || r->upstream->headers_in.status_n != 404) { 124 | return NGX_DECLINED; 125 | } 126 | 127 | /* check only once. */ 128 | ctx = ngx_http_get_module_ctx(r, ngx_http_jstore_filter_module); 129 | if(ctx != NULL) { 130 | ctx->store_enable = 0; 131 | return NGX_DECLINED; 132 | } 133 | 134 | ctx = ngx_palloc(r->pool, sizeof(ngx_http_jstore_ctx_t)); 135 | if(ctx == NULL) { 136 | return NGX_DECLINED; 137 | } 138 | 139 | /* other members will be init later */ 140 | ctx->store_enable = 0; 141 | ngx_http_set_ctx(r, ctx, ngx_http_jstore_filter_module); 142 | 143 | return NGX_DECLINED; 144 | } 145 | 146 | 147 | static ngx_int_t 148 | ngx_http_jstore_check_cacheable(ngx_http_request_t *r) 149 | { 150 | ngx_array_t *cache_control; 151 | ngx_table_elt_t *h; 152 | ngx_uint_t i; 153 | time_t expires; 154 | 155 | #if (NGX_HTTP_CACHE) 156 | if(r->cache && r->cache->valid_sec != 0) { 157 | return 1; 158 | } 159 | #endif 160 | if(r->upstream->cacheable) { 161 | return 1; 162 | } 163 | 164 | cache_control = &r->upstream->headers_in.cache_control; 165 | if(cache_control && cache_control->elts) { 166 | h = *((ngx_table_elt_t **)cache_control->elts); 167 | for(i = 0; i < cache_control->nelts; i++, h++) { 168 | 169 | if(ngx_strncasecmp(h->value.data, "max-age=", 8) != 0 170 | || h->value.data[8] == '0') { 171 | return 0; 172 | } 173 | } 174 | } 175 | 176 | h = r->upstream->headers_in.expires; 177 | if(h) { 178 | expires = ngx_http_parse_time(h->value.data, h->value.len); 179 | if (expires == NGX_ERROR || expires < ngx_time()) { 180 | return 0; 181 | } 182 | } 183 | 184 | return 1; 185 | } 186 | 187 | static ngx_int_t 188 | ngx_http_jstore_header_filter(ngx_http_request_t *r) 189 | { 190 | ngx_http_jstore_loc_conf_t *jlcf; 191 | ngx_http_jstore_ctx_t *ctx; 192 | off_t content_length; 193 | 194 | if(ngx_http_jstore_is_subrequest(r)) { 195 | goto pipe; 196 | } 197 | 198 | ctx = ngx_http_get_module_ctx(r, ngx_http_jstore_filter_module); 199 | if(ctx == NULL) { 200 | goto pipe; 201 | } 202 | 203 | ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "jstore header filter"); 204 | 205 | jlcf = ngx_http_get_module_loc_conf(r, ngx_http_jstore_filter_module); 206 | 207 | content_length = r->headers_out.content_length_n; 208 | if(content_length > jlcf->max_size 209 | || (content_length < 0 && !r->upstream->headers_in.chunked) 210 | || r->headers_out.status != NGX_HTTP_OK 211 | || (r->headers_in.range != NULL && r->allow_ranges) 212 | || r->header_only) { 213 | 214 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "jstore: not store1"); 215 | goto pipe; 216 | } 217 | 218 | if(jlcf->check_cacheable && !ngx_http_jstore_check_cacheable(r)) { 219 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "jstore: not store2"); 220 | goto pipe; 221 | } 222 | 223 | ctx->store_enable = 1; 224 | ctx->buf_chain_head = NULL; 225 | ctx->buf_chain_tail = NULL; 226 | ctx->copied_length = 0; 227 | ctx->subr_sent = 0; 228 | 229 | pipe: 230 | return ngx_http_next_header_filter(r); 231 | } 232 | 233 | static ngx_int_t 234 | ngx_http_jstore_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 235 | { 236 | ngx_http_core_main_conf_t *cmcf; 237 | ngx_http_jstore_loc_conf_t *jlcf; 238 | ngx_http_jstore_ctx_t *ctx; 239 | ngx_http_request_t *sr = NULL; 240 | ngx_http_upstream_t *u; 241 | ngx_chain_t *cl, *jchain; 242 | ngx_buf_t *buf, *jbuf; 243 | ngx_int_t rc, rc_next; 244 | ssize_t n; 245 | off_t clen; 246 | off_t content_length = r->headers_out.content_length_n; 247 | 248 | if(in == NULL) { 249 | goto pipe; 250 | } 251 | 252 | /* skip jstore's subrequest's response body */ 253 | if(ngx_http_jstore_is_subrequest(r)) { 254 | for(cl = in; cl; cl = cl->next) { 255 | buf = cl->buf; 256 | 257 | buf->file_pos = buf->file_last; 258 | buf->pos = buf->last; 259 | buf->sync = 1; 260 | buf->memory = 0; 261 | buf->in_file = 0; 262 | } 263 | return NGX_OK; 264 | } 265 | 266 | ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "jstore body filter"); 267 | 268 | ctx = ngx_http_get_module_ctx(r, ngx_http_jstore_filter_module); 269 | if(ctx == NULL || !ctx->store_enable || ctx->subr_sent) { 270 | goto pipe; 271 | } 272 | 273 | for(cl = in; cl; cl = cl->next) { 274 | 275 | buf = cl->buf; 276 | if(ngx_buf_special(buf)) { 277 | continue; 278 | } 279 | 280 | clen = ngx_buf_size(buf); 281 | 282 | if(content_length >= 0 && clen + ctx->copied_length > content_length) { 283 | ngx_log_error(NGX_LOG_ERR,r->connection->log, 0, 284 | "body(%d) is larger than Content-Length(%d)", 285 | clen + ctx->copied_length, content_length); 286 | goto fail; 287 | } 288 | 289 | /* alloc new buffer, and link it */ 290 | jbuf = ngx_create_temp_buf(r->pool, clen); 291 | if(jbuf == NULL) { 292 | goto fail; 293 | } 294 | 295 | jchain = ngx_alloc_chain_link(r->pool); 296 | if(jchain == NULL) { 297 | goto pipe; 298 | } 299 | 300 | if(ctx->buf_chain_head == NULL) { 301 | ctx->buf_chain_head = jchain; 302 | } else { 303 | ctx->buf_chain_tail->next = jchain; 304 | } 305 | ctx->buf_chain_tail = jchain; 306 | jchain->buf = jbuf; 307 | jchain->next = NULL; 308 | 309 | /* copy content */ 310 | if(buf->in_file && buf->file){ 311 | n = ngx_read_file(buf->file, jbuf->start, clen, buf->file_pos); 312 | if(n < 0){ 313 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 314 | 0, "ngx_read_file failed! ret: %d", n); 315 | goto fail; 316 | } 317 | buf->file->offset -= n; 318 | 319 | } else { 320 | ngx_memcpy(jbuf->start, buf->pos, clen); 321 | } 322 | 323 | jbuf->last += clen; 324 | ctx->copied_length += clen; 325 | } 326 | 327 | /* upstream done? buffering and non-buffering */ 328 | u = r->upstream; 329 | if(!(u->pipe && u->pipe->upstream_done) && u->length != 0) { 330 | goto pipe; 331 | } 332 | 333 | /* should not happen */ 334 | if(content_length >= 0 && ctx->copied_length != content_length) { 335 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "JSTORE bug!!"); 336 | goto fail; 337 | } 338 | 339 | /* now we have collect all response, so send a subrequest to store it */ 340 | 341 | /* send the main request first */ 342 | rc_next = ngx_http_next_body_filter(r, in); 343 | 344 | ctx->subr_sent = 1; 345 | 346 | /* create subrequest */ 347 | rc = ngx_http_subrequest(r, &(r->uri), &(r->args), &sr, NULL, 0); 348 | if(rc == NGX_ERROR) { 349 | goto fail; 350 | } 351 | 352 | /* method */ 353 | sr->method = NGX_HTTP_POST; 354 | sr->method_name.data = JSUBR_METHOD_STR; 355 | sr->method_name.len = JSUBR_METHOD_LEN; 356 | 357 | /* request headers */ 358 | sr->headers_in.headers = u->headers_in.headers; 359 | sr->headers_in.content_length_n = ctx->copied_length; 360 | 361 | /* $proxy_internal_body_length is cacheable, and the subrequest 362 | * has different content-lenght with main request, so we have 363 | * to clear the variables. */ 364 | cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); 365 | sr->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts 366 | * sizeof(ngx_http_variable_value_t)); 367 | 368 | /* request body */ 369 | sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); 370 | if(sr->request_body == NULL) { 371 | goto fail; 372 | } 373 | 374 | sr->request_body->bufs = ctx->buf_chain_head; 375 | 376 | /* ngx_http_subrequest() and ngx_http_named_location() both 377 | * increase @r->count, but we want it to be incresed once 378 | * actually. */ 379 | r->count--; 380 | 381 | /* jump to the named location */ 382 | ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "jstore: named-location"); 383 | jlcf = ngx_http_get_module_loc_conf(r, ngx_http_jstore_filter_module); 384 | ngx_http_named_location(sr, &jlcf->target); 385 | 386 | return rc_next; 387 | 388 | fail: 389 | ngx_http_set_ctx(r, NULL, ngx_http_jstore_filter_module); 390 | pipe: 391 | return ngx_http_next_body_filter(r, in); 392 | } 393 | 394 | 395 | static char * 396 | ngx_http_jstore_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 397 | { 398 | ngx_http_jstore_loc_conf_t *prev = parent; 399 | ngx_http_jstore_loc_conf_t *conf = child; 400 | 401 | ngx_conf_merge_value(conf->max_size, prev->max_size, 100<<20); /* 100M */ 402 | ngx_conf_merge_value(conf->check_cacheable, prev->check_cacheable, 1); 403 | return NGX_CONF_OK; 404 | } 405 | 406 | static void * 407 | ngx_http_jstore_create_loc_conf(ngx_conf_t *cf) 408 | { 409 | ngx_http_jstore_loc_conf_t *jlcf; 410 | jlcf = ngx_palloc(cf->pool, sizeof(ngx_http_jstore_loc_conf_t)); 411 | if(jlcf == NULL) { 412 | return NGX_CONF_ERROR; 413 | } 414 | 415 | jlcf->target.data = NULL; 416 | jlcf->max_size = NGX_CONF_UNSET_SIZE; 417 | jlcf->check_cacheable = NGX_CONF_UNSET; 418 | return jlcf; 419 | } 420 | 421 | 422 | static ngx_int_t 423 | ngx_http_jstore_init(ngx_conf_t *cf) 424 | { 425 | ngx_http_handler_pt *h; 426 | ngx_http_core_main_conf_t *cmcf; 427 | 428 | /* handler part */ 429 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 430 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); 431 | if (h == NULL) { 432 | return NGX_ERROR; 433 | } 434 | *h = ngx_http_jstore_handler; 435 | 436 | /* filter part */ 437 | ngx_http_next_body_filter = ngx_http_top_body_filter; 438 | ngx_http_top_body_filter = ngx_http_jstore_body_filter; 439 | 440 | ngx_http_next_header_filter = ngx_http_top_header_filter; 441 | ngx_http_top_header_filter = ngx_http_jstore_header_filter; 442 | 443 | return NGX_OK; 444 | } 445 | 446 | /* vim: set tw=0 shiftwidth=4 tabstop=4 expandtab: */ 447 | -------------------------------------------------------------------------------- /request.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow of request. Read request, write response. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #define _XOPEN_SOURCE 500 /* for pwrite */ 9 | #include "request.h" 10 | 11 | static int connections_total = 0; 12 | 13 | static void request_read_request_header(ohc_request_t *r); 14 | 15 | static inline void request_cork_set(ohc_request_t *r) 16 | { 17 | set_cork(r->sock_fd, 1); 18 | r->cork = 1; 19 | } 20 | 21 | static inline void request_cork_clear(ohc_request_t *r) 22 | { 23 | if(r->cork) { 24 | set_cork(r->sock_fd, 0); 25 | r->cork = 0; 26 | } 27 | } 28 | 29 | static void request_reset(ohc_request_t *r) 30 | { 31 | r->item = NULL; 32 | r->worker_thread = NULL; 33 | r->events = 0; 34 | r->keepalive = r->server->keepalive_timeout ? 1 : 0; 35 | r->active = 0; 36 | r->connection_broken = 0; 37 | r->cork = 0; 38 | r->range_set = 0; 39 | r->disk_error = 0; 40 | r->output_size = 0; 41 | r->input_size = 0; 42 | r->event_handler = NULL; 43 | r->method = OHC_HTTP_METHOD_INVALID; 44 | r->uri.base = NULL; 45 | r->host.base = NULL; 46 | r->ohc_key.base = NULL; 47 | r->range.base = NULL; 48 | r->content_length = -1; 49 | r->http_code = 0; 50 | r->expire = 0; 51 | r->start_time = timer_now(&master_timer); 52 | r->error_reason = NULL; 53 | r->error_number = 0; 54 | r->buf_pos = r->_buffer; 55 | r->process_size = 0; 56 | 57 | /* other members will be set later */ 58 | } 59 | 60 | /* Send a short buffer at the beginning of response. so we assume 61 | * that the socket sndbuf is empty and there is no block. 62 | * If blocked, in case, we do not record the breakpoint, and 63 | * return OHC_ERROR (while not OHC_AGAIN). */ 64 | static int request_send_buffer(ohc_request_t *r, char *buffer, ssize_t length) 65 | { 66 | ssize_t rc; 67 | 68 | interupted: 69 | rc = send(r->sock_fd, buffer, length, 0); 70 | if(rc != length) { 71 | if(rc < 0 && errno == EINTR) { 72 | goto interupted; 73 | } 74 | log_error_run(0, "request_send_buffer() blocks: %ld %ld", length, rc); 75 | r->error_reason = "SendError"; 76 | r->error_number = errno; 77 | r->connection_broken = 1; 78 | return OHC_ERROR; 79 | } 80 | 81 | r->output_size += rc; 82 | return OHC_OK; 83 | } 84 | 85 | static int request_send_file(ohc_request_t *r, off_t start, off_t length) 86 | { 87 | ssize_t rc; 88 | off_t off; 89 | ohc_device_t *device = device_of_item(r->item); 90 | 91 | off = start + r->process_size; 92 | interupted: 93 | rc = sendfile(r->sock_fd, device->fd, &off, length - r->process_size); 94 | if(rc == -1) { 95 | if(errno == EAGAIN) { 96 | return OHC_AGAIN; 97 | } 98 | if(errno == EINTR) { 99 | goto interupted; 100 | } 101 | 102 | if(errno == EIO) { 103 | r->disk_error = 1; 104 | log_error_run(errno, "sendfile server:%d, " 105 | "device:%s, off:%ld, len:%ld", 106 | r->server->listen_port, device->filename, 107 | off, length - r->process_size); 108 | } else { 109 | r->connection_broken = 1; 110 | } 111 | r->error_reason = "SendfileError"; 112 | r->error_number = errno; 113 | return OHC_ERROR; 114 | } 115 | 116 | r->output_size += rc; 117 | r->process_size += rc; 118 | if(length != r->process_size) { 119 | return OHC_AGAIN; 120 | } 121 | 122 | return OHC_OK; 123 | } 124 | 125 | 126 | static int request_write_disk(ohc_request_t *r, char *buffer, off_t length) 127 | { 128 | ohc_item_t *item = r->item; 129 | ohc_device_t *device; 130 | int rc; 131 | 132 | /* item may be NULL, if we are not going to store the item, 133 | * such as the item is too big, or store it as passby. */ 134 | if(item == NULL) { 135 | goto out; 136 | } 137 | 138 | device = device_of_item(item); 139 | rc = pwrite(device->fd, buffer, length, item->offset + r->process_size); 140 | if(rc != length) { 141 | log_error_run(errno, "pwrite, server:%d, device:%s, " 142 | "off:%ld, len:%ld, ret:%ld", 143 | r->server->listen_port, device->filename, 144 | item->offset + r->process_size, length, rc); 145 | r->http_code = 500; 146 | r->disk_error = 1; 147 | r->error_reason = "WriteDiskError"; 148 | r->error_number = errno; 149 | return OHC_ERROR; 150 | } 151 | 152 | out: 153 | r->process_size += length; 154 | return OHC_OK; 155 | } 156 | 157 | static void request_do_finalize(ohc_request_t *r) 158 | { 159 | ohc_server_t *s = r->server; 160 | FILE *fp = s->access_filp; 161 | 162 | server_request_finalize(r); 163 | event_del(r); 164 | s->output_size_current_period += r->output_size; 165 | s->input_size_current_period += r->input_size; 166 | 167 | /* log */ 168 | if(r->active) { 169 | 170 | fprintf(fp, "%s %s %d %ld %s%s %s %s %s", 171 | timer_format_log(&master_timer), 172 | inet_ntoa(r->client.sin_addr), 173 | r->http_code, 174 | timer_now(&master_timer) - r->start_time, 175 | http_methods[r->method].str.base, 176 | strshow(&r->uri), strshow(&r->host), 177 | strshow(&r->ohc_key), strshow(&r->range)); 178 | 179 | if(r->error_reason) { 180 | if(r->http_code == 204) { 181 | fprintf(fp, " [%s]\n", r->error_reason); 182 | 183 | } else if(r->error_number) { 184 | fprintf(fp, " [%s(%s) while %s]\n", r->error_reason, 185 | strerror(r->error_number), r->step); 186 | } else { 187 | fprintf(fp, " [%s while %s]\n", 188 | r->error_reason, r->step); 189 | } 190 | } else { 191 | fputs("\n", fp); 192 | } 193 | } 194 | 195 | if(r->keepalive && !r->connection_broken) { 196 | request_reset(r); 197 | event_add_keepalive(r, request_read_request_header); 198 | return; 199 | } 200 | 201 | list_del(&r->rnode); 202 | s->connections--; 203 | connections_total--; 204 | close(r->sock_fd); 205 | slab_free(r); 206 | } 207 | 208 | static void request_finalize(ohc_request_t *r) 209 | { 210 | if(!r->connection_broken && r->output_size == 0) { 211 | /* don't check @request_send_buffer's return, for simple.*/ 212 | string_t *page = http_code_page(r->http_code); 213 | request_send_buffer(r, page->base, page->len); 214 | } 215 | 216 | if(r->worker_thread) { 217 | worker_request_return(r, request_do_finalize); 218 | } else { 219 | request_do_finalize(r); 220 | } 221 | } 222 | 223 | static void request_put_read_request_body(ohc_request_t *r) 224 | { 225 | ssize_t rc; 226 | #define RECV_BUF_SIZE (100*1024) 227 | char buf[RECV_BUF_SIZE]; 228 | size_t item_len = r->content_length + r->put_header_length; 229 | 230 | r->step = "ReadBody"; 231 | 232 | /* receive from socket, and write into disk file */ 233 | while(r->process_size < item_len) { 234 | 235 | /* receive */ 236 | rc = recv(r->sock_fd, buf, RECV_BUF_SIZE, 0); 237 | if(rc == -1) { 238 | if(errno == EAGAIN) { 239 | goto again; 240 | } else if(errno == EINTR) { 241 | continue; 242 | } else { 243 | r->error_reason = "ReceiveError"; 244 | r->error_number = errno; 245 | r->connection_broken = 1; 246 | goto finish; 247 | } 248 | } 249 | if(rc == 0) { 250 | r->error_reason = "ClientClose"; 251 | r->connection_broken = 1; 252 | goto finish; 253 | } 254 | r->input_size += rc; 255 | if(rc > item_len - r->process_size) { 256 | r->error_reason = "BodyLargerThanDeclared"; 257 | r->http_code = 400; 258 | r->keepalive = 0; 259 | goto finish; 260 | } 261 | 262 | /* write */ 263 | rc = request_write_disk(r, buf, rc); 264 | if(rc == OHC_ERROR) { 265 | goto finish; 266 | } 267 | } 268 | 269 | finish: 270 | request_finalize(r); 271 | return; 272 | 273 | again: 274 | event_add_read(r, request_put_read_request_body); 275 | return; 276 | } 277 | 278 | 279 | static void request_put_read_request_body_preread(ohc_request_t *r) 280 | { 281 | ssize_t len; 282 | ssize_t rc; 283 | char buffer[REQ_BUF_SIZE]; 284 | int i; 285 | string_t *s; 286 | 287 | r->step = "PreReadBody"; 288 | 289 | len = http_make_200_response_header(r->content_length, buffer); 290 | for(i = 0; i < r->put_header_nr; i++) { 291 | s = &r->put_headers[i]; 292 | memcpy(buffer + len, s->base, s->len); 293 | len += s->len; 294 | } 295 | 296 | rc = request_write_disk(r, buffer, len); 297 | if(rc == OHC_ERROR) { 298 | request_finalize(r); 299 | return; 300 | } 301 | 302 | request_put_read_request_body(r); 303 | } 304 | 305 | static void request_get_write_response(ohc_request_t *r) 306 | { 307 | int rc; 308 | 309 | r->step = "WriteResponse"; 310 | 311 | rc = request_send_file(r, r->item->offset, 312 | (r->method == OHC_HTTP_METHOD_HEAD) 313 | ? r->item->headers_len : r->item->length); 314 | 315 | if(rc == OHC_AGAIN) { 316 | event_add_write(r, request_get_write_response); 317 | } else { /* rc == OHC_OK || rc == OHC_ERROR */ 318 | request_finalize(r); 319 | } 320 | } 321 | 322 | static void request_get_write_response_206_body(ohc_request_t *r) 323 | { 324 | int rc; 325 | 326 | r->step = "WriteBody"; 327 | 328 | rc = request_send_file(r, r->item->offset + r->item->headers_len + r->range_start, 329 | r->range_end - r->range_start + 1); 330 | 331 | request_cork_clear(r); 332 | 333 | if(rc == OHC_AGAIN) { 334 | event_add_write(r, request_get_write_response_206_body); 335 | } else { /* rc == OHC_OK || rc == OHC_ERROR */ 336 | request_finalize(r); 337 | } 338 | } 339 | 340 | static void request_get_write_response_206_header_disk(ohc_request_t *r) 341 | { 342 | ohc_item_t *item = r->item; 343 | ssize_t body_len = item->length - item->headers_len; 344 | ssize_t off = http_make_200_response_header(body_len, NULL); 345 | int rc; 346 | 347 | r->step = "WriteHeaderDisk"; 348 | 349 | rc = request_send_file(r, item->offset + off, item->headers_len - off); 350 | 351 | if(rc == OHC_AGAIN) { 352 | request_cork_clear(r); 353 | event_add_write(r, request_get_write_response_206_header_disk); 354 | return; 355 | } 356 | if(rc == OHC_ERROR) { 357 | request_finalize(r); 358 | return; 359 | } 360 | 361 | /* rc == OHC_OK */ 362 | if(r->method == OHC_HTTP_METHOD_HEAD) { 363 | request_cork_clear(r); 364 | request_finalize(r); 365 | } else { 366 | /* reset @process_size, because there is a new round of 367 | * @request_send_file later. */ 368 | r->process_size = 0; 369 | request_get_write_response_206_body(r); 370 | } 371 | } 372 | 373 | static void request_get_write_response_206_header_mem(ohc_request_t *r) 374 | { 375 | int rc; 376 | char buffer[1000]; 377 | ssize_t length; 378 | ssize_t body_len = r->item->length - r->item->headers_len; 379 | 380 | r->step = "WriteHeaderMem"; 381 | 382 | if(r->range_start == RANGE_NO_SET) { 383 | r->range_start = r->range_end > body_len 384 | ? 0 : body_len - r->range_end; 385 | r->range_end = body_len - 1; 386 | } else if(r->range_end == RANGE_NO_SET) { 387 | r->range_end = body_len - 1; 388 | } else if(r->range_end >= body_len) { 389 | r->range_end = body_len - 1; 390 | } else {} 391 | 392 | if(r->range_start >= body_len) { 393 | r->http_code = 416; 394 | request_finalize(r); 395 | return; 396 | } 397 | 398 | request_cork_set(r); 399 | 400 | length = http_make_206_response_header(r->range_start, 401 | r->range_end, body_len, buffer); 402 | rc = request_send_buffer(r, buffer, length); 403 | if(rc != OHC_OK) { 404 | request_finalize(r); 405 | return; 406 | } 407 | 408 | request_get_write_response_206_header_disk(r); 409 | } 410 | 411 | static void request_read_request_header(ohc_request_t *r) 412 | { 413 | int rc; 414 | 415 | r->step = "ReadHeader"; 416 | 417 | /* receive */ 418 | interupted: 419 | rc = recv(r->sock_fd, r->buf_pos, 420 | r->_buffer + REQ_BUF_SIZE - r->buf_pos - 1, 0); 421 | if(rc == -1) { 422 | if(errno == EAGAIN) { 423 | goto again; 424 | } else if(errno == EINTR) { 425 | goto interupted; 426 | } else { 427 | r->connection_broken = 1; 428 | r->error_reason = "ReceiveError"; 429 | r->error_number = errno; 430 | goto fail; 431 | } 432 | } 433 | if(rc == 0) { 434 | r->error_reason = "ClientClose"; 435 | r->connection_broken = 1; 436 | goto fail; 437 | } 438 | 439 | r->active = 1; 440 | r->buf_pos += rc; 441 | r->input_size += rc; 442 | 443 | /* http parse */ 444 | rc = http_request_parse(r); 445 | if(rc == OHC_AGAIN) { 446 | 447 | /* request is too huge */ 448 | if(r->buf_pos - r->_buffer >= REQ_BUF_SIZE - 1) { 449 | r->error_reason = "RequestHeadTooBig"; 450 | r->http_code = 400; 451 | goto fail; 452 | } 453 | 454 | goto again; 455 | } 456 | if(rc == OHC_ERROR) { 457 | goto fail; 458 | } 459 | 460 | /* rc == OHC_DONE, parse ok */ 461 | 462 | switch(r->method) { 463 | case OHC_HTTP_METHOD_GET: 464 | case OHC_HTTP_METHOD_HEAD: 465 | rc = server_request_get_handler(r); 466 | if(rc == OHC_ERROR) { 467 | r->http_code = 404; 468 | goto fail; 469 | } 470 | 471 | if(r->range_set) { 472 | r->http_code = 206; 473 | rc = worker_request_dispatch(r, request_get_write_response_206_header_mem); 474 | } else { 475 | r->http_code = 200; 476 | rc = worker_request_dispatch(r, request_get_write_response); 477 | } 478 | if(rc == OHC_ERROR) { 479 | r->http_code = 500; 480 | r->error_reason = "TooBusy"; 481 | r->error_number = errno; 482 | goto fail; 483 | } 484 | break; 485 | 486 | case OHC_HTTP_METHOD_PUT: 487 | case OHC_HTTP_METHOD_POST: 488 | rc = server_request_put_handler(r); 489 | if(rc == OHC_ERROR) { 490 | r->http_code = 500; 491 | goto fail; 492 | } 493 | if(rc == OHC_DECLINE) { 494 | r->http_code = 204; 495 | if(r->server->shutdown_if_not_store) { 496 | r->keepalive = 0; 497 | goto fail; 498 | } 499 | } else { 500 | r->http_code = 201; 501 | } 502 | 503 | rc = worker_request_dispatch(r, request_put_read_request_body_preread); 504 | if(rc == OHC_ERROR) { 505 | r->http_code = 500; 506 | r->error_reason = "TooBusy"; 507 | r->error_number = errno; 508 | goto fail; 509 | } 510 | break; 511 | 512 | case OHC_HTTP_METHOD_PURGE: 513 | case OHC_HTTP_METHOD_DELETE: 514 | rc = server_request_delete_handler(r); 515 | if(rc == OHC_ERROR) { 516 | r->http_code = 404; 517 | goto fail; 518 | } 519 | r->http_code = 204; 520 | request_finalize(r); 521 | break; 522 | 523 | default: 524 | ; 525 | } 526 | 527 | return; 528 | 529 | again: 530 | event_add_read(r, request_read_request_header); 531 | return; 532 | fail: 533 | request_finalize(r); 534 | return; 535 | } 536 | 537 | /* entry of request process, called when receive a new request */ 538 | void request_process_entry(ohc_server_t *s, int sock_fd, struct sockaddr_in *client) 539 | { 540 | static ohc_slab_t request_slab = OHC_SLAB_INIT(ohc_request_t); 541 | 542 | ohc_request_t *r; 543 | 544 | if(s->connections >= s->connections_limit) { 545 | log_error_run(0, "exceed connections limit in server %d", s->listen_port); 546 | close(sock_fd); 547 | return; 548 | } 549 | 550 | r = (ohc_request_t *)slab_alloc(&request_slab); 551 | if(r == NULL) { 552 | log_error_run(0, "NoMem"); 553 | close(sock_fd); 554 | return; 555 | } 556 | 557 | list_add(&r->rnode, &master_requests); 558 | s->connections++; 559 | connections_total++; 560 | 561 | r->server = s; 562 | r->sock_fd = sock_fd; 563 | r->client = *client; 564 | 565 | request_reset(r); 566 | 567 | request_read_request_header(r); 568 | } 569 | 570 | void request_timeout_handler(ohc_request_t *r) 571 | { 572 | r->connection_broken = 1; 573 | r->error_reason = "Timeout"; 574 | 575 | request_finalize(r); 576 | } 577 | 578 | /* close requests linked on @requests. 579 | * if @keepalive_only is set, clean requests only in keepalive idle. */ 580 | void request_clean(struct list_head *requests, int keepalive_only) 581 | { 582 | struct list_head *p, *safe; 583 | ohc_request_t *r; 584 | list_for_each_safe(p, safe, requests) { 585 | r = list_entry(p, ohc_request_t, rnode); 586 | if(keepalive_only && r->active) { 587 | continue; 588 | } 589 | 590 | if(r->event_handler != request_finalize) { 591 | r->connection_broken = 1; 592 | r->error_reason = "CleanByQuitTimeout"; 593 | } 594 | request_finalize(r); 595 | } 596 | } 597 | 598 | /* check can we quit, if there is no request */ 599 | int request_check_quit(int keepalive_only) 600 | { 601 | request_clean(&master_requests, keepalive_only); 602 | return connections_total == 0; 603 | } 604 | -------------------------------------------------------------------------------- /device.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Devices and free blocks management. 3 | * 4 | * Author: Wu Bingzheng 5 | * 6 | */ 7 | 8 | #include "device.h" 9 | 10 | /* init in device_conf_load() */ 11 | static ohc_ipbucket_t free_blocks; 12 | 13 | static LIST_HEAD(devices); 14 | static LIST_HEAD(deleted_devices); 15 | 16 | static int device_badblock_percent; 17 | static int device_check_270G; 18 | 19 | /* this makes things complicated, but it's useful for saving 20 | * memory, in ohc_item_t and ohc_free_block_t. */ 21 | static idx_pointer_t device_indexs = IDX_POINTER_INIT(); 22 | 23 | 24 | static inline ohc_device_t *device_of_fblock(ohc_free_block_t *fblock) 25 | { 26 | return idx_pointer_get(&device_indexs, fblock->device_index); 27 | } 28 | 29 | inline ohc_device_t *device_of_item(ohc_item_t *item) 30 | { 31 | return idx_pointer_get(&device_indexs, item->device_index); 32 | } 33 | 34 | 35 | static inline void device_ipbucket_add(ohc_free_block_t *fblock) 36 | { 37 | ipbucket_add(&free_blocks, &fblock->bucket_node, fblock->block_size); 38 | } 39 | 40 | static inline void device_ipbucket_update(ohc_free_block_t *fblock) 41 | { 42 | ipbucket_update(&free_blocks, &fblock->bucket_node, fblock->block_size); 43 | } 44 | 45 | /* add a free block (with @offset and @size) into @device's order list, 46 | * before @base. */ 47 | static ohc_free_block_t *device_fblock_insert(ohc_device_t *device, 48 | struct list_head *base, off_t offset, size_t size) 49 | { 50 | static ohc_slab_t free_block_slab = OHC_SLAB_INIT(ohc_free_block_t); 51 | 52 | ohc_free_block_t *fblock; 53 | 54 | fblock = slab_alloc(&free_block_slab); 55 | if(fblock == NULL) { 56 | return NULL; 57 | } 58 | fblock->fblock = 1; 59 | fblock->device_index = device->index; 60 | fblock->offset = offset; 61 | fblock->block_size = size; 62 | list_add_tail(&fblock->order_node, base); 63 | device_ipbucket_add(fblock); 64 | 65 | device->fblock_nr++; 66 | return fblock; 67 | } 68 | 69 | /* delete free block @fblock */ 70 | static void device_fblock_delete(ohc_free_block_t *fblock) 71 | { 72 | ohc_device_t *device = device_of_fblock(fblock); 73 | device->fblock_nr--; 74 | ipbucket_del(&fblock->bucket_node); 75 | list_del(&fblock->order_node); 76 | slab_free(fblock); 77 | } 78 | 79 | /* remove the conf_device from conf_cycle.devices list, 80 | * and add it to the real devices list, so it becomes 81 | * the new device */ 82 | static void device_add(ohc_device_t *conf_device) 83 | { 84 | ohc_device_t *d = conf_device; 85 | 86 | list_del(&d->dnode); 87 | list_add_tail(&d->dnode, &devices); 88 | 89 | INIT_LIST_HEAD(&d->order_head); 90 | conf_device->index = idx_pointer_add(&device_indexs, conf_device); 91 | if(d->capacity != 0) { 92 | if(device_fblock_insert(d, &d->order_head, 0, d->capacity) == NULL) { 93 | conf_device->kicked = 1; 94 | log_error_admin(0, "add device %s [NOMEM]", d->filename); 95 | return; 96 | } 97 | } 98 | 99 | /* other fields were set to zero, when malloc the conf_server */ 100 | } 101 | 102 | static void device_update(ohc_device_t *d, ohc_device_t *conf_device) 103 | { 104 | strcpy(d->filename, conf_device->filename); 105 | 106 | /* keep in order */ 107 | list_del(&d->dnode); 108 | list_add_tail(&d->dnode, &devices); 109 | } 110 | 111 | static void device_delete(ohc_device_t *d) 112 | { 113 | list_del(&d->dnode); 114 | 115 | if(d->kicked) { 116 | free(d); 117 | } else { 118 | d->deleted = 1; 119 | list_add(&d->dnode, &deleted_devices); 120 | } 121 | } 122 | 123 | static void device_destroy(ohc_device_t *d) 124 | { 125 | struct list_head *p, *safe; 126 | ohc_free_block_t *fblock; 127 | ohc_item_t *item; 128 | int count = 0; 129 | 130 | list_for_each_safe(p, safe, &d->order_head) { 131 | fblock = list_entry(p, ohc_free_block_t, order_node); 132 | if(fblock->fblock) { 133 | device_fblock_delete(fblock); 134 | } else { 135 | item = list_entry(p, ohc_item_t, order_node); 136 | server_item_delete(item); 137 | } 138 | 139 | if(count++ >= LOOP_LIMIT) { 140 | break; 141 | } 142 | } 143 | 144 | /* we want to close the fd as soon as possible, while not 145 | * need to wait for order_head empty. */ 146 | if(d->used == 0 && d->fd != -1) { 147 | close(d->fd); 148 | d->fd = -1; 149 | } 150 | 151 | if(!list_empty(&d->order_head)) { 152 | return; 153 | } 154 | 155 | list_del(&d->dnode); 156 | idx_pointer_delete(&device_indexs, d->index); 157 | free(d); 158 | } 159 | 160 | /* Kick a device if it becomes bad. 161 | * But we still keep a 'kicked' device (.kicked=1), 162 | * for show status and re-load configure. */ 163 | static void device_kick(ohc_device_t *device) 164 | { 165 | ohc_device_t *bad_dev = malloc(sizeof(ohc_device_t)); 166 | if(bad_dev == NULL) { 167 | return; 168 | } 169 | 170 | *bad_dev = *device; 171 | 172 | bad_dev->kicked = 1; 173 | INIT_LIST_HEAD(&bad_dev->order_head); 174 | 175 | list_add(&bad_dev->dnode, &device->dnode); 176 | device_delete(device); 177 | } 178 | 179 | static ohc_device_t *device_search_inode(dev_t dev, ino_t inode, 180 | struct list_head *head, ohc_device_t *stop) 181 | { 182 | struct list_head *p; 183 | ohc_device_t *d; 184 | 185 | list_for_each(p, head) { 186 | d = list_entry(p, ohc_device_t, dnode); 187 | if(d == stop) { 188 | return NULL; 189 | } 190 | if(d->inode == inode && d->dev == dev) { 191 | return d; 192 | } 193 | } 194 | return NULL; 195 | } 196 | 197 | static inline ohc_device_t *device_check_same(struct list_head *head, ohc_device_t *d) 198 | { 199 | return device_search_inode(d->dev, d->inode, head, d); 200 | } 201 | 202 | int device_conf_check(ohc_conf_t *conf_cycle) 203 | { 204 | struct list_head *p; 205 | ohc_device_t *d, *d2; 206 | const char *msg; 207 | struct stat filestat; 208 | int count = 0; 209 | 210 | if(list_empty(&conf_cycle->devices)) { 211 | log_error_admin(0, "you must set at least 1 device"); 212 | return OHC_ERROR; 213 | } 214 | if(conf_cycle->device_badblock_percent >= 100) { 215 | log_error_admin(0, "device_badblock_percent must be less than 100"); 216 | return OHC_ERROR; 217 | } 218 | 219 | list_for_each(p, &devices) { 220 | d = list_entry(p, ohc_device_t, dnode); 221 | d->conf = NULL; 222 | } 223 | 224 | /* deleted devices take slots in @device_indexs */ 225 | list_for_each(p, &deleted_devices) { 226 | count++; 227 | } 228 | 229 | list_for_each(p, &conf_cycle->devices) { 230 | d = list_entry(p, ohc_device_t, dnode); 231 | 232 | if(++count >= DEVICES_LIMIT - 1) { 233 | msg = "too many devices"; 234 | goto fail; 235 | } 236 | 237 | if(stat(d->filename, &filestat) < 0) { 238 | msg = "error in stat device"; 239 | goto fail; 240 | } 241 | 242 | /* @st_dev and @st_ino work well for block-device too */ 243 | d->dev = filestat.st_dev; 244 | d->inode = filestat.st_ino; 245 | 246 | d2 = device_check_same(&deleted_devices, d); 247 | if(d2 != NULL && d2->fd != -1) { 248 | msg = "in deleting, try later"; 249 | goto fail; 250 | } 251 | 252 | /* check exist? */ 253 | d2 = device_check_same(&devices, d); 254 | if(d2 != NULL) { 255 | if(d2->conf != NULL) { 256 | msg = "duplicate devices"; 257 | goto fail; 258 | } 259 | 260 | d2->conf = d; 261 | d->conf = d2; 262 | continue; 263 | } 264 | 265 | /* new device, init it */ 266 | 267 | if(device_check_same(&conf_cycle->devices, d)) { 268 | msg = "duplicate devices"; 269 | goto fail; 270 | } 271 | 272 | d->fd = open(d->filename, O_RDWR); 273 | if(d->fd < 0) { 274 | msg = "error in open device"; 275 | goto fail; 276 | } 277 | 278 | if(S_ISREG(filestat.st_mode)) { 279 | d->capacity = filestat.st_size & ~0x1FFL; 280 | 281 | } else if(S_ISBLK(filestat.st_mode)) { 282 | d->capacity = 0; 283 | if(ioctl(d->fd, BLKGETSIZE, &d->capacity) < 0) { 284 | msg = "error in ioctl device"; 285 | goto fail; 286 | } 287 | d->capacity = d->capacity << 9; /* sector size is 512 */ 288 | 289 | } else { 290 | msg = "error file type(only allow REGULAR and BLOCK)"; 291 | goto fail; 292 | } 293 | 294 | if(d->capacity > 0x4020010000) { 295 | /* sendfile supports only 0x4020010000 */ 296 | if(conf_cycle->device_check_270G) { 297 | msg = "file is too big(at most 0x4020010000)"; 298 | goto fail; 299 | } 300 | d->capacity = 0x4020010000; 301 | } 302 | } 303 | return OHC_OK; 304 | 305 | fail: 306 | /* the open FDs will be closed in device_conf_rollback() later */ 307 | log_error_admin(errno, "%s of device '%s'", msg, d->filename); 308 | return OHC_ERROR; 309 | } 310 | 311 | void device_conf_load(ohc_conf_t *conf_cycle) 312 | { 313 | struct list_head *p, *safe; 314 | ohc_device_t *d; 315 | 316 | /* init free_blocks. executes only once */ 317 | static int first = 1; 318 | if(first) { 319 | first = 0; 320 | ipbucket_init(&free_blocks); 321 | } 322 | 323 | device_badblock_percent = conf_cycle->device_badblock_percent; 324 | device_check_270G = conf_cycle->device_check_270G; 325 | 326 | list_for_each_safe(p, safe, &devices) { 327 | d = list_entry(p, ohc_device_t, dnode); 328 | if(d->conf == NULL) { 329 | device_delete(d); 330 | } 331 | } 332 | 333 | list_for_each_safe(p, safe, &conf_cycle->devices) { 334 | d = list_entry(p, ohc_device_t, dnode); 335 | if(d->conf) { 336 | device_update(d->conf, d); 337 | } else { 338 | device_add(d); 339 | } 340 | } 341 | } 342 | 343 | void device_conf_rollback(ohc_conf_t *conf_cycle) 344 | { 345 | struct list_head *p; 346 | ohc_device_t *d; 347 | 348 | list_for_each(p, &conf_cycle->devices) { 349 | d = list_entry(p, ohc_device_t, dnode); 350 | if(d->fd >= 0) { 351 | close(d->fd); 352 | } 353 | } 354 | } 355 | 356 | static void device_delete_item(ohc_device_t *d, struct list_head *p) 357 | { 358 | if(p == &d->order_head) { 359 | return; 360 | } 361 | 362 | ohc_free_block_t *fblock = list_entry(p, ohc_free_block_t, order_node); 363 | if(fblock->fblock) { 364 | return; 365 | } 366 | 367 | ohc_item_t *item = list_entry(p, ohc_item_t, order_node); 368 | if(item->putting || item->used) { /* do not delete hot item */ 369 | return; 370 | } 371 | server_item_delete(item); 372 | } 373 | 374 | /* delete items to extend free block, to make a big one */ 375 | int device_free_block_extend(size_t target) 376 | { 377 | ohc_free_block_t *fblock; 378 | ohc_device_t *d; 379 | struct list_head *p, *next; 380 | size_t last = 0; 381 | int i; 382 | 383 | target = ipbucket_block_size(target); 384 | 385 | for(i = 0; i < LOOP_LIMIT; i++) { 386 | p = ipbucket_biggest(&free_blocks); 387 | if(p == NULL) { 388 | return OHC_ERROR; 389 | } 390 | 391 | fblock = list_entry(p, ohc_free_block_t, bucket_node); 392 | if(fblock->block_size >= target) { 393 | return OHC_OK; 394 | } 395 | 396 | if(fblock->block_size == last) { 397 | return OHC_ERROR; 398 | } 399 | last = fblock->block_size; 400 | 401 | d = device_of_fblock(fblock); 402 | if(d->deleted) { 403 | device_fblock_delete(fblock); 404 | continue; 405 | } 406 | 407 | /* device_delete_item() makes @fblock invalid, so 408 | * we have to remember @next before call it. */ 409 | next = fblock->order_node.next; 410 | device_delete_item(d, fblock->order_node.prev); 411 | device_delete_item(d, next); 412 | } 413 | return OHC_ERROR; 414 | } 415 | 416 | /* @server module call this to allocate a free block for a new item. 417 | * Set @item's @device and @offset member, and return free-block's 418 | * size, if alloc successfully. 419 | * Return 0 if fail. */ 420 | size_t device_get_free_block(ohc_item_t *item) 421 | { 422 | ohc_free_block_t *fblock; 423 | ohc_device_t *device; 424 | struct list_head *p; 425 | size_t bsize; 426 | int try = 0; 427 | 428 | try_again: 429 | p = ipbucket_get(&free_blocks, item->length); 430 | if(p == NULL) { 431 | return 0; 432 | } 433 | fblock = list_entry(p, ohc_free_block_t, bucket_node); 434 | device = device_of_fblock(fblock); 435 | 436 | if(device->deleted) { 437 | device_fblock_delete(fblock); 438 | if(try++ < LOOP_LIMIT) { 439 | goto try_again; 440 | } 441 | return 0; 442 | } 443 | 444 | bsize = ipbucket_block_size(item->length); 445 | if(fblock->block_size > bsize) { 446 | /* fblock is bigger than needed, so cut bsize from rear */ 447 | 448 | item->offset = fblock->offset + fblock->block_size - bsize; 449 | list_add(&item->order_node, &fblock->order_node); 450 | 451 | fblock->block_size -= bsize; 452 | device_ipbucket_add(fblock); 453 | 454 | } else if(fblock->block_size == bsize) { 455 | /* fit exactly */ 456 | item->offset = fblock->offset; 457 | 458 | list_add(&item->order_node, &fblock->order_node); 459 | device_fblock_delete(fblock); 460 | } else { 461 | /* should not be here */ 462 | log_error_run(0, "oops!"); 463 | exit(1); 464 | } 465 | 466 | device->item_nr++; 467 | device->consumed += bsize; 468 | item->device_index = device->index; 469 | return bsize; 470 | } 471 | 472 | /* @server module call this to free a free block when delete a item */ 473 | size_t device_return_free_block(ohc_item_t *item) 474 | { 475 | ohc_free_block_t *prev = NULL, *next = NULL; 476 | ohc_device_t *device = device_of_item(item); 477 | off_t bsize; 478 | int badp; 479 | struct list_head *order = &item->order_node; 480 | int forward = 0, backward = 0; 481 | 482 | bsize = ipbucket_block_size(item->length); 483 | 484 | /* just delete item from order-list, if the device is deleted or bad. */ 485 | if(device->deleted) { 486 | goto done; 487 | } 488 | if(item->badblock) { 489 | device->badblock += bsize; 490 | badp = device->badblock * 100 / device->capacity; 491 | if(badp > device_badblock_percent) { 492 | log_error_run(0, "kick device '%s', badblock:%ld(%d%%)", 493 | device->filename, device->badblock, badp); 494 | device_kick(device); 495 | } 496 | goto done; 497 | } 498 | 499 | /* ok, now recycle the item's block */ 500 | 501 | if(order->prev != &device->order_head) { 502 | prev = list_entry(order->prev, ohc_free_block_t, order_node); 503 | forward = prev->fblock && (prev->offset + prev->block_size == item->offset); 504 | } 505 | if(order->next != &device->order_head) { 506 | next = list_entry(order->next, ohc_free_block_t, order_node); 507 | backward = next->fblock && (next->offset == item->offset + bsize); 508 | } 509 | 510 | if(forward && backward) { 511 | prev->block_size += bsize + next->block_size; 512 | device_ipbucket_update(prev); 513 | 514 | device_fblock_delete(next); 515 | 516 | } else if(forward) { 517 | prev->block_size += bsize; 518 | device_ipbucket_update(prev); 519 | 520 | } else if(backward) { 521 | next->offset -= bsize; 522 | next->block_size += bsize; 523 | device_ipbucket_update(next); 524 | 525 | } else { 526 | /* we don't care the return value here */ 527 | device_fblock_insert(device, order, item->offset, bsize); 528 | } 529 | 530 | device->item_nr--; 531 | device->consumed -= bsize; 532 | 533 | done: 534 | list_del(order); 535 | return bsize; 536 | } 537 | 538 | /* @format module call this to cut a free-block from the beginning 539 | * of the remaining space */ 540 | size_t device_cut_free_block(ohc_item_t *item) 541 | { 542 | ohc_free_block_t *current; 543 | size_t bsize, step, gap; 544 | ohc_device_t *device = device_of_item(item); 545 | 546 | current = list_entry(device->order_head.prev, ohc_free_block_t, order_node); 547 | bsize = ipbucket_block_size(item->length); 548 | gap = item->offset - current->offset; 549 | step = bsize + gap; 550 | 551 | if(item->offset < current->offset || current->block_size < step) { 552 | log_error_run(0, "wrong olivehc dump device %s", device->filename); 553 | return 0; 554 | } 555 | 556 | if(gap > 0) { 557 | /* we don't care the return value here */ 558 | device_fblock_insert(device, ¤t->order_node, 559 | current->offset, gap); 560 | } 561 | 562 | list_add_tail(&item->order_node, ¤t->order_node); 563 | 564 | /* we don't call ipbucket_update(current) here, while call it 565 | * in device_load_post() later. */ 566 | current->offset += step; 567 | current->block_size -= step; 568 | 569 | device->item_nr++; 570 | device->consumed += bsize; 571 | return bsize; 572 | } 573 | 574 | /* @format module call this, after finish loading items of a device, 575 | * to update the remaining space */ 576 | void device_load_post(ohc_device_t *device) 577 | { 578 | ohc_free_block_t *current; 579 | current = list_entry(device->order_head.prev, ohc_free_block_t, order_node); 580 | 581 | if(current->block_size == 0) { 582 | device_fblock_delete(current); 583 | } else { 584 | device_ipbucket_update(current); 585 | } 586 | } 587 | 588 | void device_format_load(void) 589 | { 590 | struct list_head *p; 591 | ohc_device_t *d; 592 | 593 | list_for_each(p, &devices) { 594 | d = list_entry(p, ohc_device_t, dnode); 595 | format_load_device(d); 596 | } 597 | } 598 | 599 | void device_format_store(void) 600 | { 601 | struct list_head *p; 602 | ohc_device_t *d; 603 | unsigned short server_ports[SERVERS_LIMIT]; 604 | 605 | server_dump_ports(server_ports); 606 | 607 | list_for_each(p, &devices) { 608 | d = list_entry(p, ohc_device_t, dnode); 609 | format_store_device(server_ports, d); 610 | } 611 | } 612 | 613 | /* regular routine, called by master thread */ 614 | void device_routine(void) 615 | { 616 | struct list_head *p, *safep; 617 | ohc_device_t *d; 618 | 619 | list_for_each_safe(p, safep, &deleted_devices) { 620 | d = list_entry(p, ohc_device_t, dnode); 621 | device_destroy(d); 622 | } 623 | } 624 | 625 | void device_status(FILE *filp) 626 | { 627 | struct list_head *p; 628 | ohc_device_t *d; 629 | 630 | fputs("\n+ device capacity consumed badblock status\n", filp); 631 | list_for_each(p, &devices) { 632 | d = list_entry(p, ohc_device_t, dnode); 633 | fprintf(filp, "++ %s %ld %ld %ld %s\n", 634 | d->filename, d->capacity, d->consumed, 635 | d->badblock, d->kicked ? "kicked" : "ok"); 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Manage servers and items. Create/destroy/update servers, 3 | * get/put/delete items, etc. 4 | * 5 | * Author: Wu Bingzheng 6 | * 7 | */ 8 | 9 | #include "server.h" 10 | 11 | 12 | static ohc_slab_t item_slab = OHC_SLAB_INIT(ohc_item_t); 13 | 14 | static LIST_HEAD(servers); 15 | static LIST_HEAD(deleted_servers); 16 | 17 | static LIST_HEAD(shared_lru_head); 18 | 19 | /* this makes things complicated, but it's useful for saving 20 | * memory, in ohc_item_t. */ 21 | static idx_pointer_t server_indexs = IDX_POINTER_INIT(); 22 | 23 | ohc_server_t *server_of_item(ohc_item_t *item) 24 | { 25 | return idx_pointer_get(&server_indexs, item->server_index); 26 | } 27 | 28 | void server_dump_ports(unsigned short *ports) 29 | { 30 | struct list_head *p; 31 | ohc_server_t *s; 32 | 33 | bzero(ports, sizeof(unsigned short) * SERVERS_LIMIT); 34 | list_for_each(p, &servers) { 35 | s = list_entry(p, ohc_server_t, snode); 36 | ports[s->index] = s->listen_port; 37 | } 38 | } 39 | 40 | static void server_listen_close(ohc_server_t *s) 41 | { 42 | epoll_del(master_epoll_fd, s->listen_fd); 43 | close(s->listen_fd); 44 | } 45 | 46 | static int server_listen_start(ohc_server_t *s) 47 | { 48 | tcp_listen(s->listen_fd); 49 | return epoll_add_read(master_epoll_fd, s->listen_fd, 50 | (void *)((uintptr_t)s | EVENT_TYPE_LISTEN)); 51 | } 52 | 53 | /* we don't check their return values */ 54 | static void server_listen_set(ohc_server_t *s) 55 | { 56 | if(s->sndbuf != 0) { 57 | set_sndbuf(s->listen_fd, s->sndbuf); 58 | } 59 | if(s->rcvbuf != 0) { 60 | set_rcvbuf(s->listen_fd, s->rcvbuf); 61 | } 62 | if(s->request_timeout != 60) { 63 | set_defer_accept(s->listen_fd, s->request_timeout); 64 | } 65 | } 66 | 67 | /* we don't check their return values */ 68 | static void server_listen_update(ohc_server_t *s, ohc_server_t *conf_server) 69 | { 70 | if(conf_server->sndbuf != s->sndbuf) { 71 | set_sndbuf(s->listen_fd, conf_server->sndbuf); 72 | s->sndbuf = conf_server->sndbuf; 73 | } 74 | if(conf_server->rcvbuf != s->rcvbuf) { 75 | set_rcvbuf(s->listen_fd, conf_server->rcvbuf); 76 | s->rcvbuf = conf_server->rcvbuf; 77 | } 78 | if(conf_server->request_timeout != s->request_timeout) { 79 | set_defer_accept(s->listen_fd, conf_server->request_timeout); 80 | s->request_timeout = conf_server->request_timeout; 81 | } 82 | } 83 | 84 | 85 | /* remove the conf_server from conf_cycle.servers list, 86 | * and add it to the real servers list, so it becomes 87 | * the new server */ 88 | static void server_create(ohc_server_t *conf_server) 89 | { 90 | list_del(&conf_server->snode); 91 | list_add_tail(&conf_server->snode, &servers); 92 | 93 | server_listen_start(conf_server); 94 | server_listen_set(conf_server); 95 | INIT_LIST_HEAD(&conf_server->lru_head); 96 | INIT_LIST_HEAD(&conf_server->passby_lru_head); 97 | conf_server->index = idx_pointer_add(&server_indexs, conf_server); 98 | 99 | /* other fields were set to zero, when malloc the conf_server */ 100 | } 101 | 102 | /* move the server to deleted_servers list, in order 103 | * to free its items bit by bit. */ 104 | static void server_delete(ohc_server_t *s) 105 | { 106 | s->deleted = 1; 107 | server_listen_close(s); 108 | list_del(&s->snode); 109 | list_add(&s->snode, &deleted_servers); 110 | } 111 | 112 | /* update a server by @conf_server */ 113 | static void server_update(ohc_server_t *s, ohc_server_t *conf_server) 114 | { 115 | /* make in order */ 116 | list_del(&s->snode); 117 | list_add_tail(&s->snode, &servers); 118 | 119 | if(conf_server->access_filp) { 120 | fclose(s->access_filp); 121 | s->access_filp = conf_server->access_filp; 122 | strcpy(s->access_log, conf_server->access_log); 123 | } 124 | 125 | s->capacity = conf_server->capacity; 126 | s->send_timeout = conf_server->send_timeout; 127 | s->recv_timeout = conf_server->recv_timeout; 128 | s->item_max_size = conf_server->item_max_size; 129 | s->passby_enable = conf_server->passby_enable; 130 | s->passby_begin_item_nr = conf_server->passby_begin_item_nr; 131 | s->passby_begin_consumed = conf_server->passby_begin_consumed; 132 | s->passby_limit_nr = conf_server->passby_limit_nr; 133 | s->passby_expire = conf_server->passby_expire; 134 | s->keepalive_timeout = conf_server->keepalive_timeout; 135 | s->server_dump = conf_server->server_dump; 136 | s->shutdown_if_not_store = conf_server->shutdown_if_not_store; 137 | s->key_include_query = conf_server->key_include_query; 138 | s->key_include_host = conf_server->key_include_host; 139 | s->key_include_ohc_key = conf_server->key_include_ohc_key; 140 | s->expire_default = conf_server->expire_default; 141 | s->expire_force = conf_server->expire_force; 142 | s->status_period = conf_server->status_period; 143 | server_listen_update(s, conf_server); 144 | } 145 | 146 | static ohc_server_t *server_search_port(unsigned short port, 147 | struct list_head *head, ohc_server_t *stop) 148 | { 149 | struct list_head *p; 150 | ohc_server_t *s; 151 | 152 | list_for_each(p, head) { 153 | s = list_entry(p, ohc_server_t, snode); 154 | if(s == stop) { 155 | return NULL; 156 | } 157 | if(s->listen_port == port) { 158 | return s; 159 | } 160 | } 161 | return NULL; 162 | } 163 | 164 | ohc_server_t *server_by_port(unsigned short port) 165 | { 166 | return server_search_port(port, &servers, NULL); 167 | } 168 | 169 | static ohc_server_t *server_check_same(struct list_head *head, ohc_server_t *s) 170 | { 171 | return server_search_port(s->listen_port, head, s); 172 | } 173 | 174 | int server_conf_check(ohc_conf_t *conf_cycle) 175 | { 176 | struct list_head *p; 177 | ohc_server_t *s, *s2; 178 | const char *msg; 179 | int count = 0; 180 | 181 | if(list_empty(&conf_cycle->servers)) { 182 | log_error_admin(0, "you must set at least 1 server"); 183 | return OHC_ERROR; 184 | } 185 | 186 | list_for_each(p, &servers) { 187 | s = list_entry(p, ohc_server_t, snode); 188 | s->conf = NULL; 189 | } 190 | 191 | /* deleted servers take slots in @server_indexs */ 192 | list_for_each(p, &deleted_servers) { 193 | count++; 194 | } 195 | 196 | list_for_each(p, &conf_cycle->servers) { 197 | s = list_entry(p, ohc_server_t, snode); 198 | 199 | if(++count >= SERVERS_LIMIT - 1) { 200 | msg = "too many servers"; 201 | goto fail; 202 | } 203 | 204 | if(s->send_timeout <= 0) { 205 | msg = "send_timeout must be positive"; 206 | goto fail; 207 | } 208 | if(s->recv_timeout <= 0) { 209 | msg = "recv_timeout must be positive"; 210 | goto fail; 211 | } 212 | if(s->status_period == 0) { 213 | msg = "status_period must be positive"; 214 | goto fail; 215 | } 216 | /* we don't check sndbuf and rcvbuf */ 217 | 218 | s2 = server_check_same(&servers, s); 219 | if(s2 != NULL) { 220 | if(s2->conf != NULL) { 221 | msg = "duplicated listen port"; 222 | goto fail; 223 | } 224 | s->conf = s2; 225 | s2->conf = s; 226 | } else { 227 | /* new server, so init it */ 228 | 229 | if(server_check_same(&conf_cycle->servers, s)) { 230 | msg = "duplicated listen port"; 231 | goto fail; 232 | } 233 | 234 | s->listen_fd = tcp_bind(s->listen_port); 235 | if(s->listen_fd < 0) { 236 | msg = "error in bind port"; 237 | goto fail; 238 | } 239 | 240 | s->hash = hash_init(); 241 | if(s->hash == NULL) { 242 | msg = "no mem when init hash"; 243 | goto fail; 244 | } 245 | } 246 | 247 | if(s->conf == NULL || strcmp(s->access_log, s->conf->access_log)) { 248 | s->access_filp = fopen(s->access_log, "a"); 249 | if(s->access_filp == NULL) { 250 | msg = "error in open log file"; 251 | goto fail; 252 | } 253 | } 254 | } 255 | 256 | return OHC_OK; 257 | fail: 258 | log_error_admin(errno, "%s in server %d", msg, s->listen_port); 259 | return OHC_ERROR; 260 | } 261 | 262 | void server_conf_load(ohc_conf_t *conf_cycle) 263 | { 264 | struct list_head *p, *safe; 265 | ohc_server_t *s; 266 | 267 | list_for_each_safe(p, safe, &servers) { 268 | s = list_entry(p, ohc_server_t, snode); 269 | if(s->conf == NULL) { 270 | server_delete(s); 271 | } 272 | } 273 | 274 | list_for_each_safe(p, safe, &conf_cycle->servers) { 275 | s = list_entry(p, ohc_server_t, snode); 276 | if(s->conf) { 277 | server_update(s->conf, s); 278 | } else { 279 | server_create(s); 280 | } 281 | } 282 | } 283 | 284 | void server_conf_rollback(ohc_conf_t *conf_cycle) 285 | { 286 | struct list_head *p; 287 | ohc_server_t *s; 288 | 289 | list_for_each(p, &conf_cycle->servers) { 290 | s = list_entry(p, ohc_server_t, snode); 291 | if(s->access_filp) { 292 | fclose(s->access_filp); 293 | } 294 | if(s->listen_fd >= 0) { 295 | close(s->listen_fd); 296 | } 297 | if(s->hash) { 298 | hash_destroy(s->hash); 299 | } 300 | } 301 | } 302 | 303 | void server_stop_service(void) 304 | { 305 | struct list_head *p; 306 | ohc_server_t *s; 307 | 308 | list_for_each(p, &servers) { 309 | s = list_entry(p, ohc_server_t, snode); 310 | server_listen_close(s); 311 | } 312 | } 313 | 314 | int server_clear(unsigned short port) 315 | { 316 | ohc_server_t *s = server_by_port(port); 317 | if(s == NULL) { 318 | log_error_admin(0, "no matched server"); 319 | return OHC_ERROR; 320 | } 321 | 322 | s->clear++; 323 | return OHC_OK; 324 | } 325 | 326 | static inline struct list_head *server_lru_head(ohc_server_t *s) 327 | { 328 | return s->capacity ? &s->lru_head : &shared_lru_head; 329 | } 330 | 331 | /* @format module call this to add an item, when load an item from device */ 332 | int server_load_fm_item(ohc_server_t *s, ohc_device_t *device, 333 | ohc_format_item_t *fm_item) 334 | { 335 | ohc_item_t *item; 336 | size_t block_size; 337 | 338 | item = slab_alloc(&item_slab); 339 | if(item == NULL) { 340 | return OHC_ERROR; 341 | } 342 | 343 | item->putting = 0; 344 | item->badblock = 0; 345 | item->deleted = 0; 346 | item->used = 0; 347 | item->clear = 0; 348 | item->server_index = s->index; 349 | item->length = fm_item->length; 350 | item->expire = fm_item->expire; 351 | item->headers_len = fm_item->headers_len; 352 | item->offset = fm_item->offset; 353 | item->device_index = device->index; 354 | 355 | block_size = device_cut_free_block(item); 356 | if(block_size == 0) { 357 | slab_free(item); 358 | return OHC_ERROR; 359 | } 360 | 361 | memcpy(item->hnode.id, fm_item->hash_id, 16); 362 | hash_add(s->hash, &item->hnode, NULL, 0); 363 | list_add(&item->lru_node, server_lru_head(s)); 364 | 365 | s->consumed += block_size; 366 | s->content += item->length; 367 | s->item_nr++; 368 | 369 | return OHC_OK; 370 | } 371 | 372 | /* pass-by item. only ID(uri), but no data(body). */ 373 | typedef struct { 374 | /* @hnode and @passby must be together, to 375 | * distinguish ohc_item_t and ohc_passby_item_t. */ 376 | ohc_hash_node_t hnode; 377 | unsigned passby:1; 378 | 379 | int32_t expire; 380 | struct list_head lru_node; 381 | } ohc_passby_item_t; 382 | 383 | 384 | static void server_passby_item_delete(ohc_server_t *s, 385 | ohc_passby_item_t *passby_item) 386 | { 387 | hash_del(s->hash, &passby_item->hnode); 388 | list_del(&passby_item->lru_node); 389 | slab_free(passby_item); 390 | s->passby_item_nr--; 391 | } 392 | 393 | void server_item_delete(ohc_item_t *item) 394 | { 395 | ohc_server_t *s = server_of_item(item); 396 | size_t block_size; 397 | 398 | /* 1st time get in here for the @item */ 399 | if(item->deleted == 0) { 400 | hash_del(s->hash, &item->hnode); 401 | } 402 | 403 | /* if used, delete later */ 404 | if(item->used != 0 || item->putting) { 405 | item->deleted = 1; 406 | return; 407 | } 408 | 409 | /* delete the item actally */ 410 | list_del(&item->lru_node); 411 | s->content -= item->length; 412 | block_size = device_return_free_block(item); 413 | s->consumed -= block_size; 414 | s->item_nr--; 415 | slab_free(item); 416 | } 417 | 418 | inline int server_item_valid(ohc_item_t *item) 419 | { 420 | return !device_of_item(item)->deleted 421 | && !server_of_item(item)->deleted 422 | && item->clear == server_of_item(item)->clear 423 | && item->expire > timer_now(&master_timer); 424 | } 425 | 426 | static void server_item_expire(ohc_server_t *s, size_t target) 427 | { 428 | ohc_item_t *item; 429 | ohc_passby_item_t *passby_item; 430 | struct list_head *p, *safe; 431 | time_t now = timer_now(&master_timer); 432 | size_t before = s->consumed; 433 | int count = 0; 434 | 435 | list_for_each_reverse_safe(p, safe, &s->lru_head) { 436 | item = list_entry(p, ohc_item_t, lru_node); 437 | 438 | if(before - s->consumed >= target && server_item_valid(item)) { 439 | break; 440 | } 441 | 442 | server_item_delete(item); 443 | 444 | if(count++ >= LOOP_LIMIT) { 445 | break; 446 | } 447 | } 448 | 449 | list_for_each_reverse_safe(p, safe, &s->passby_lru_head) { 450 | passby_item = list_entry(p, ohc_passby_item_t, lru_node); 451 | 452 | if(s->passby_enable && s->passby_item_nr < s->passby_limit_nr 453 | && passby_item->expire > now) { 454 | break; 455 | } 456 | 457 | server_passby_item_delete(s, passby_item); 458 | 459 | if(count++ >= LOOP_LIMIT) { 460 | break; 461 | } 462 | } 463 | } 464 | 465 | static void server_shared_expire(size_t target) 466 | { 467 | ohc_item_t *item; 468 | struct list_head *p, *safe; 469 | size_t size = 0; 470 | int count = 0; 471 | 472 | list_for_each_reverse_safe(p, safe, &shared_lru_head) { 473 | item = list_entry(p, ohc_item_t, lru_node); 474 | 475 | if(size >= target && server_item_valid(item)) { 476 | break; 477 | } 478 | 479 | size += item->length; 480 | server_item_delete(item); 481 | 482 | if(count++ >= LOOP_LIMIT) { 483 | break; 484 | } 485 | } 486 | } 487 | 488 | static ohc_hash_node_t *server_hash_get(ohc_request_t *r, unsigned char *hash_id) 489 | { 490 | ohc_server_t *s = r->server; 491 | char key[REQ_BUF_SIZE]; /* REQ_BUF_SIZE is just enough */ 492 | ssize_t length; 493 | 494 | /* key_include_query */ 495 | if(s->key_include_query) { 496 | length = r->uri.len; 497 | } else { 498 | r->uri.base[r->uri.len] = '?'; 499 | length = strchr(r->uri.base, '?') - r->uri.base; 500 | } 501 | length = http_decode_uri(r->uri.base, length, key); 502 | 503 | /* key_include_host */ 504 | if(s->key_include_host && r->host.base) { 505 | memcpy(key + length, r->host.base, r->host.len); 506 | length += r->host.len; 507 | } 508 | 509 | /* key_include_ohc_key */ 510 | if(s->key_include_ohc_key && r->ohc_key.base) { 511 | memcpy(key + length, r->ohc_key.base, r->ohc_key.len); 512 | length += r->ohc_key.len; 513 | } 514 | 515 | return hash_get(s->hash, (unsigned char *)key, length, hash_id); 516 | } 517 | 518 | 519 | /* request module call this, in a GET request, to get the item */ 520 | int server_request_get_handler(ohc_request_t *r) 521 | { 522 | ohc_item_t *item; 523 | ohc_passby_item_t *passby_item; 524 | ohc_hash_node_t *hnode; 525 | ohc_server_t *s = r->server; 526 | 527 | s->gets++; 528 | s->gets_current_period++; 529 | 530 | hnode = server_hash_get(r, NULL); 531 | if(hnode == NULL) { 532 | return OHC_ERROR; 533 | } 534 | 535 | passby_item = list_entry(hnode, ohc_passby_item_t, hnode); 536 | if(passby_item->passby) { 537 | list_del(&passby_item->lru_node); 538 | list_add(&passby_item->lru_node, &s->passby_lru_head); 539 | s->passby_hits++; 540 | s->passby_hits_current_period++; 541 | return OHC_ERROR; 542 | } 543 | 544 | item = list_entry(hnode, ohc_item_t, hnode); 545 | if(item->deleted || item->putting) { 546 | return OHC_ERROR; 547 | } 548 | if(!server_item_valid(item)) { 549 | server_item_delete(item); 550 | return OHC_ERROR; 551 | } 552 | 553 | s->hits++; 554 | s->hits_current_period++; 555 | device_of_item(item)->used++; 556 | 557 | item->used++; 558 | r->item = item; 559 | 560 | /* update LRU */ 561 | list_del(&item->lru_node); 562 | list_add(&item->lru_node, server_lru_head(s)); 563 | 564 | return OHC_OK; 565 | } 566 | 567 | static int server_passby_store(ohc_server_t *s, unsigned char *hash_id) 568 | { 569 | static ohc_slab_t passby_item_slab = OHC_SLAB_INIT(ohc_passby_item_t); 570 | 571 | ohc_passby_item_t *passby_item; 572 | 573 | if(!s->passby_enable || s->item_nr < s->passby_begin_item_nr 574 | || s->consumed < s->passby_begin_consumed) { 575 | return OHC_DECLINE; 576 | } 577 | 578 | passby_item = slab_alloc(&passby_item_slab); 579 | if(passby_item == NULL) { 580 | log_error_run(0, "NoMem"); 581 | return OHC_ERROR; 582 | } 583 | 584 | passby_item->passby = 1; 585 | passby_item->expire = timer_now(&master_timer) + s->passby_expire; 586 | memcpy(passby_item->hnode.id, hash_id, 16); 587 | hash_add(s->hash, &passby_item->hnode, NULL, 0); 588 | list_add(&passby_item->lru_node, &s->passby_lru_head); 589 | s->passby_item_nr++; 590 | s->passby_stores++; 591 | s->passby_stores_current_period++; 592 | 593 | return OHC_OK; 594 | } 595 | 596 | /* @request module call this, in a PUT request, to put an item */ 597 | int server_request_put_handler(ohc_request_t *r) 598 | { 599 | ohc_item_t *item; 600 | ohc_passby_item_t *passby_item; 601 | ohc_hash_node_t *hnode; 602 | ohc_server_t *s; 603 | unsigned char hash_id[16]; 604 | size_t block_size; 605 | time_t now; 606 | int try = 0; 607 | 608 | s = r->server; 609 | s->puts++; 610 | s->puts_current_period++; 611 | 612 | /* check size */ 613 | if(s->item_max_size != 0 && r->content_length > s->item_max_size) { 614 | r->error_reason = "TooBigItem1"; 615 | return OHC_DECLINE; 616 | } 617 | if(s->capacity != 0 && r->content_length + r->put_header_length > s->capacity) { 618 | r->error_reason = "TooBigItem2"; 619 | return OHC_DECLINE; 620 | } 621 | 622 | /* check expire */ 623 | now = timer_now(&master_timer); 624 | if(s->expire_force != 0) { 625 | r->expire = s->expire_force + now; 626 | 627 | } else if(r->expire == 0) { 628 | if(s->expire_default == 0) { 629 | r->error_reason = "Expired"; 630 | return OHC_DECLINE; 631 | } 632 | r->expire = s->expire_default + now; 633 | 634 | } else {} 635 | 636 | /* check exist */ 637 | hnode = server_hash_get(r, hash_id); 638 | if(hnode == NULL) { 639 | if(server_passby_store(s, hash_id) == OHC_OK) { 640 | r->error_reason = "StorePassby"; 641 | return OHC_DECLINE; 642 | } 643 | } else { 644 | passby_item = list_entry(hnode, ohc_passby_item_t, hnode); 645 | item = list_entry(hnode, ohc_item_t, hnode); 646 | if(passby_item->passby) { 647 | server_passby_item_delete(s, passby_item); 648 | 649 | } else if(r->method == OHC_HTTP_METHOD_PUT || !server_item_valid(item)) { 650 | server_item_delete(item); 651 | 652 | } else { 653 | r->error_reason = "Exist"; 654 | return OHC_DECLINE; 655 | } 656 | } 657 | 658 | /* check done, store the item now */ 659 | 660 | item = slab_alloc(&item_slab); 661 | if(item == NULL) { 662 | log_error_run(0, "NoMem"); 663 | return OHC_ERROR; 664 | } 665 | item->length = r->content_length + r->put_header_length; 666 | item->headers_len = r->put_header_length; 667 | 668 | try_again: 669 | block_size = device_get_free_block(item); 670 | if(block_size == 0) { 671 | /* If fails in getting free block, expire some items and try again. 672 | * The following expire order is complicated, and there is no 673 | * specific reason for the order. Just feeling. */ 674 | if(try++ < 2 && !list_empty(&s->lru_head) 675 | && s->consumed + item->length*2 > s->capacity) { 676 | server_item_expire(s, item->length * 2); 677 | goto try_again; 678 | } 679 | if(try++ < 5 && !list_empty(&shared_lru_head)) { 680 | server_shared_expire(item->length * 2); 681 | goto try_again; 682 | } 683 | if(try++ < 9 && !list_empty(&s->lru_head)) { 684 | server_item_expire(s, item->length * 2); 685 | goto try_again; 686 | } 687 | if(try++ < 12) { 688 | device_free_block_extend(item->length); 689 | goto try_again; 690 | } 691 | 692 | slab_free(item); 693 | r->error_reason = "NoSpace"; 694 | log_error_run(0, "space(%ld) alloc fail in server %d", 695 | item->length, s->listen_port); 696 | return OHC_ERROR; 697 | } 698 | 699 | /* done. update something */ 700 | r->item = item; 701 | item->putting = 1; 702 | item->badblock = 0; 703 | item->deleted = 0; 704 | item->used = 0; 705 | item->clear = s->clear; 706 | item->expire = r->expire; 707 | item->server_index = s->index; 708 | memcpy(item->hnode.id, hash_id, 16); 709 | hash_add(s->hash, &item->hnode, NULL, 0); 710 | list_add(&item->lru_node, server_lru_head(s)); 711 | s->consumed += block_size; 712 | s->content += item->length; 713 | s->item_nr++; 714 | s->stores++; 715 | s->stores_current_period++; 716 | device_of_item(item)->used++; 717 | 718 | return OHC_OK; 719 | } 720 | 721 | /* @request module call this, in a DELETE request, to delete an item */ 722 | int server_request_delete_handler(ohc_request_t *r) 723 | { 724 | ohc_hash_node_t *hnode; 725 | ohc_item_t *item; 726 | ohc_passby_item_t *passby_item; 727 | ohc_server_t *s = r->server; 728 | 729 | s->deletes++; 730 | s->deletes_current_period++; 731 | 732 | hnode = server_hash_get(r, NULL); 733 | if(hnode == NULL) { 734 | return OHC_ERROR; 735 | } 736 | 737 | passby_item = list_entry(hnode, ohc_passby_item_t, hnode); 738 | if(passby_item->passby) { 739 | server_passby_item_delete(s, passby_item); 740 | } else { 741 | item = list_entry(hnode, ohc_item_t, hnode); 742 | server_item_delete(item); 743 | } 744 | return OHC_OK; 745 | } 746 | 747 | /* @request module call this, when a request finishs */ 748 | void server_request_finalize(ohc_request_t *r) 749 | { 750 | ohc_item_t *item = r->item; 751 | int not_finish = 0; 752 | 753 | if(item == NULL) { 754 | return; 755 | } 756 | r->item = NULL; 757 | 758 | device_of_item(item)->used--; 759 | 760 | if(item->putting) { 761 | item->putting = 0; 762 | 763 | if(r->process_size < item->length) { 764 | not_finish = 1; 765 | } 766 | 767 | } else { 768 | item->used--; 769 | } 770 | 771 | if(r->disk_error) { 772 | item->badblock = 1; 773 | server_item_delete(item); 774 | 775 | } else if(item->deleted || not_finish) { 776 | server_item_delete(item); 777 | } 778 | } 779 | 780 | /* server port listen handler, called on new request */ 781 | void server_listen_handler(ohc_server_t *s) 782 | { 783 | int sock_fd; 784 | struct sockaddr_in client; 785 | 786 | while(1) { 787 | sock_fd = tcp_accept(s->listen_fd, &client); 788 | if(sock_fd == -1) { 789 | if(errno == EAGAIN) { 790 | return; 791 | } 792 | log_error_run(errno, "accept in server %d", s->listen_port); 793 | return; 794 | } 795 | 796 | request_process_entry(s, sock_fd, &client); 797 | } 798 | } 799 | 800 | static void server_destroy(ohc_server_t *s) 801 | { 802 | /* If s->capacity==0, server_item_expire() does not works, because 803 | * the items are linked on shared_lru_head. 804 | * So maybe we need hash_pop()? */ 805 | server_item_expire(s, s->consumed); 806 | 807 | if(s->item_nr != 0 || s->passby_item_nr != 0) { 808 | return; 809 | } 810 | 811 | list_del(&s->snode); 812 | hash_destroy(s->hash); 813 | fclose(s->access_filp); 814 | idx_pointer_delete(&server_indexs, s->index); 815 | free(s); 816 | } 817 | 818 | /* regular routine, called by master thread */ 819 | void server_routine(void) 820 | { 821 | struct list_head *p, *safep; 822 | ohc_server_t *s; 823 | time_t now; 824 | 825 | now = timer_now(&master_timer); 826 | list_for_each(p, &servers) { 827 | s = list_entry(p, ohc_server_t, snode); 828 | 829 | /* update statistics */ 830 | if(now - s->last_clear >= s->status_period) { 831 | s->last_clear = now; 832 | 833 | s->gets_last_period = s->gets_current_period; 834 | s->hits_last_period = s->hits_current_period; 835 | s->passby_hits_last_period = s->passby_hits_current_period; 836 | s->gets_current_period = 0; 837 | s->hits_current_period = 0; 838 | s->passby_hits_current_period = 0; 839 | 840 | s->puts_last_period = s->puts_current_period; 841 | s->stores_last_period = s->stores_current_period; 842 | s->passby_stores_last_period = s->passby_stores_current_period; 843 | s->puts_current_period = 0; 844 | s->stores_current_period = 0; 845 | s->passby_stores_current_period = 0; 846 | 847 | s->deletes_last_period = s->deletes_current_period; 848 | s->deletes_current_period = 0; 849 | 850 | s->output_size_last_period = s->output_size_current_period; 851 | s->input_size_last_period = s->input_size_current_period; 852 | s->output_size_current_period = 0; 853 | s->input_size_current_period = 0; 854 | } 855 | 856 | /* expire item if over-size */ 857 | server_item_expire(s, s->consumed > s->capacity ? s->consumed - s->capacity : 0); 858 | 859 | fflush(s->access_filp); 860 | } 861 | 862 | server_shared_expire(0); 863 | 864 | /* clear deleted servers */ 865 | list_for_each_safe(p, safep, &deleted_servers) { 866 | s = list_entry(p, ohc_server_t, snode); 867 | server_destroy(s); 868 | } 869 | } 870 | 871 | void server_status(FILE *filp) 872 | { 873 | struct list_head *p; 874 | ohc_server_t *s; 875 | 876 | fputs("\n- listen capacity period " 877 | "| consumed content items passbyitems connections " 878 | "| gets _gets hits _hits passbyhits _passbyhits " 879 | "| puts _puts stores _stores passbystores _passbystores " 880 | "| deletes _deletes " 881 | "| output input\n", filp); 882 | 883 | list_for_each(p, &servers) { 884 | s = list_entry(p, ohc_server_t, snode); 885 | fprintf(filp, "-- %d %ld %ld " 886 | "| %ld %ld %ld %ld %d " 887 | "| %ld %ld %ld %ld %ld %ld " 888 | "| %ld %ld %ld %ld %ld %ld " 889 | "| %ld %ld " 890 | "| %ld %ld\n", 891 | s->listen_port, s->capacity, s->status_period, 892 | s->consumed, s->content, s->item_nr, s->passby_item_nr, s->connections, 893 | s->gets, s->gets_last_period, s->hits, s->hits_last_period, 894 | s->passby_hits, s->passby_hits_last_period, 895 | s->puts, s->puts_last_period, s->stores, s->stores_last_period, 896 | s->passby_stores, s->passby_stores_last_period, 897 | s->deletes, s->deletes_last_period, 898 | s->output_size_last_period, s->input_size_last_period); 899 | } 900 | } 901 | --------------------------------------------------------------------------------