├── .gitignore ├── Makefile ├── Makefile_release ├── README.md ├── async_log.c ├── async_log.h ├── cJSON.c ├── cJSON.h ├── config.c ├── config.h ├── config ├── sparrow.conf └── test.conf ├── dir.part1 ├── dir.part2 ├── ev_loop.c ├── ev_loop.h ├── exist.sh ├── file.c ├── file.h ├── global.h ├── livechat.sql ├── mime.h ├── min_heap.c ├── min_heap.h ├── performance_test ├── nginx_ab.png ├── nginx_ab_online.jpg ├── nginx_sparrow_webb.jpg ├── nginx_webbench.png ├── sparrow_ab.png ├── sparrow_ab_online.jpg ├── sparrow_webbench.png └── 性能测试1.PNG ├── picohttpparser.c ├── picohttpparser.h ├── sparrow ├── sparrow.c ├── sparrow.h ├── sparrow_main_r ├── task.sql ├── thread_manage.c ├── thread_manage.h ├── tool.sh ├── url.c ├── url.h ├── util.h └── www ├── .res ├── 404.jpg ├── back.ico ├── dir.css ├── dir.png ├── favicon.ico ├── file.ico ├── github24.ico ├── home.ico ├── logo.png └── weibo24.ico ├── 404.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | #custom 32 | 33 | core 34 | test/ 35 | www/myfolder/ 36 | test 37 | test.c 38 | #website/ 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := sparrow 2 | OBJS := sparrow.o thread_manage.o file.o ev_loop.o config.o async_log.o url.o min_heap.o cJSON.o picohttpparser.o 3 | CC := gcc 4 | DEBUG := -g -Wall 5 | CFLAGS := -Wall -c $(DEBUG) 6 | LFLAGS := -pthread -lrt -lm 7 | 8 | sparrow: $(OBJS) 9 | $(CC) $^ $(LFLAGS) -o $@ 10 | 11 | sparrow.o: sparrow.c sparrow.h global.h ev_loop.h async_log.h thread_manage.h config.h file.h mime.h url.h min_heap.h cJSON.h picohttpparser.h 12 | $(CC) $(CFLAGS) $< 13 | 14 | thread_manage.o: thread_manage.c thread_manage.h sparrow.h global.h async_log.h ev_loop.h config.h file.h mime.h 15 | $(CC) $(CFLAGS) $< 16 | 17 | ev_loop.o: ev_loop.c ev_loop.h async_log.h 18 | $(CC) $(CFLAGS) $< 19 | 20 | file.o: file.c file.h async_log.h config.h global.h 21 | $(CC) $(CFLAGS) $< 22 | 23 | async_log.o: async_log.c async_log.h 24 | $(CC) $(CFLAGS) $< 25 | 26 | config.o: config.c config.h 27 | $(CC) $(CFLAGS) $< 28 | 29 | url.o: url.c url.h 30 | $(CC) $(CFLAGS) $< 31 | 32 | min_heap.o: min_heap.c min_heap.h ev_loop.h 33 | $(CC) $(CFLAGS) $< 34 | 35 | cJSON.o: cJSON.c cJSON.h 36 | $(CC) $(CFLAGS) $< 37 | picohttpparser.o: picohttpparser.c picohttpparser.h 38 | $(CC) $(CFLAGS) $< 39 | 40 | clean: 41 | rm $(OBJS) $(BIN) -f 42 | -------------------------------------------------------------------------------- /Makefile_release: -------------------------------------------------------------------------------- 1 | OBJS = sparrow.o thread_manage.o file.o ev_loop.o config.o async_log.o url.o min_heap.o cJSON.o picohttpparser.o 2 | CC = gcc 3 | 4 | CFLAGS = -Wall -c 5 | LFLAGS = -Wall -pthread 6 | 7 | sparrow_main_r: $(OBJS) 8 | $(CC) $(LFLAGS) $(OBJS) -lrt -lm -o sparrow_main_r 9 | 10 | sparrow.o: sparrow.c sparrow.h global.h ev_loop.h async_log.h thread_manage.h config.h file.h mime.h url.h min_heap.h cJSON.h picohttpparser.h 11 | $(CC) $(CFLAGS) sparrow.c 12 | 13 | thread_manage.o: thread_manage.c thread_manage.h sparrow.h global.h async_log.h ev_loop.h config.h file.h mime.h 14 | $(CC) $(CFLAGS) -pthread thread_manage.c 15 | 16 | ev_loop.o: ev_loop.c ev_loop.h async_log.h 17 | $(CC) $(CFLAGS) ev_loop.c 18 | 19 | file.o: file.c file.h async_log.h config.h global.h 20 | $(CC) $(CFLAGS) file.c 21 | 22 | async_log.o: async_log.c async_log.h 23 | $(CC) $(CFLAGS) -pthread async_log.c -lrt 24 | 25 | config.o: config.c config.h 26 | $(CC) $(CFLAGS) config.c 27 | 28 | url.o: url.c url.h 29 | $(CC) $(CFLAGS) url.c 30 | 31 | min_heap.o: min_heap.c min_heap.h ev_loop.h 32 | $(CC) $(CFLAGS) min_heap.c 33 | 34 | cJSON.o: cJSON.c cJSON.h 35 | $(CC) $(CFLAGS) cJSON.c 36 | picohttpparser.o: picohttpparser.c picohttpparser.h 37 | $(CC) $(CFLAGS) picohttpparser.c 38 | 39 | clean: 40 | rm *.o sparrow 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Sparrow 2 | ![sparrow](www/.res/logo.png) 3 | 4 | 5 | `sparrow` 是一个简单高效的http server,纯C语言开发(基于Linux),目前主要用在本人个人的网站. 6 | 7 | --------------- 8 | #Online demo 9 | 10 | * [@blog](http://chengshuguang.com) 11 | * [@chat room](http://chengshuguang.com/ajax.html) 12 | 13 | 14 | -------------- 15 | #150行的一个demo httpd,帮你理解整个结构 16 | `如果需要理解代码的结构,建议去看我单独列出来得一个demo server的代码` 17 | * [demo server](https://github.com/codercheng/ev_lib/blob/master/ev_httpd.c) 18 | 这是一个基于epoll而写的一个简单高效的http server, 该server能轻松应对并发问题(单进程+eventloop+IO复用+非阻塞IO). 该httpd目前的功能很简单,接收http请求,返回“hello evlib”。 19 | 20 | --------------- 21 | #News 22 | * 2015-04-10日晚11:20分: 23 | 24 | 问题:对于异步日志,当程序奔溃的时候(SIGSEGV),在内存中得log还没有来得及写入disk。 25 | 26 | 添加了捕获SIGSEGV逻辑,当发生段错误的时候,把内存中得日志,写入到disk。 27 | 28 | * 2015-03-27日晚10点22分: 29 | 30 | 通过`lsof -p pid`来观察线上服务器的打开文件时,发现竟然上千,全部指向几个.html文件。 31 | 测试,发现,仅当304的时候,打开文件数会不断上升,即:304发生时,没有关闭对应打开的文件。 32 | 解决方案:304之前,不需要打开请求的文件,全部待304确定之后,在看有没有必要打开。 33 | 34 | * 2015-03-26日凌晨1点: 35 | 36 | 修掉一个严重bug,该bug会导致stack smashing. 37 | 38 | 原因:`sprintf() 会导致数组溢出`。 解决:全部用`snprintf()`替换掉。指定最大长度。 39 | 40 | ---------------- 41 | ###How To Use 42 | * make 43 | * 在config/sparrow.conf中配置server。[可选] 44 | * ./sparrow 45 | * open in browser http://127.0.0.1:xxxx/ [端口xxxx, config/sparrow.conf中配置] 46 | * http://127.0.0.1:xxxx/myfolder To Test Folder 47 | * `注意: 如果要测性能,最好修改一下系统的最大打开文件描述符,临时修改:sudo ulimit -n 10000` 48 | 49 | ---------------- 50 | ###功能 51 | * 支持GET(POST暂时不支持) 52 | * 支持目录的访问 53 | * 支持多线程(可配置工作线程数目) 54 | * 非阻塞I/O 55 | * 支持sendfile, tcp_cock等高效操作 56 | * 支持404, 500 57 | * 支持304 not modified 58 | * 支持`长连接,http Keepalive`,不支持http pipeling 59 | * 支持`定时任务(timerfd + 最小堆)` 60 | * 支持异步的日志: 61 | * 支持零点自动切换日志文件 62 | * 完善清晰的格式 63 | * 多线程支持 64 | * 支持`epoll` 65 | * 支持文件配置 66 | * 不依赖任何第三方库 67 | 68 | ----------------- 69 | ###待续... 70 | * timer heap大小是固定的,超出会崩溃(由于很多cb为空的timer存在) 71 | * __大量并发连接的情况下,偶尔会出现少量close_wait情况:初步分析如下,目前的连接关闭都是在服务端主动关闭,客户端被动关闭,但是在服务端只注册了可写事件的时候,此时客户端主动断开,那么无处处理断开导致的可读事件去close,出现close_wait__ 72 | * __bug:当目录下文件过大时,buf溢出问题__ 73 | * 均衡工作线程的地方,现在用的是随机分配,增加统计每个线程中的任务数,然后分配 74 | * how to reduce time\_wait in server side? May be it will work that [close() when finishing a request in server side --> register EV\_READ. send connection close in http header, thus client closing the conn actively!] 75 | * chunked 编码支持 76 | 77 | * __bug:在网速基本为0的情况下,连接远端服务器,结果把log输入到了网页,未重现__ 78 | * 添加一个检测程序,当程序死掉或者不能访问的时候, 重启进程。(确保存活,并确保线程没有崩)(一个检测shell,确保进程存活同时网络可用,epoll出错后直接退出。) 79 | * 添加音乐服务 80 | * 项目栏目,增加标签/关键字,比如:C,JAVA, 多线程,网络等 81 | 82 | * __bug:在网速基本为0的情况下,连接远端服务器,结果把log输入到了网页,未重现(问题找到了:因为配置文件默认log是开启的,然后再配置文件还没有加载完的时候,连接先过来了,那么还是按默认的要打印log,但是输出到网页,我想应该是刚好的打开log的文件的fd一样了?具体在分析)__ 83 | * 添加一个检测程序,当程序死掉或者不能访问的时候, 重启进程。(确保存活,并确保线程没有崩) 84 | 85 | 86 | 87 | ---------------- 88 | ###笔记 89 | * 2014-9-16: fix bug of image corrupt and segment err (http_code not init) 90 | * 2014-9-17: 解决大量请求的时候服务器崩溃。原因:clear()操作放到了close(fd)之后,在多线程的环境下,当线程1执行完了close(fd),此时在clear之前被线程2打断,线程2重用了上面的fd,并执行后续操作,但是当线程1恢复过来继续执行clear()操作,却把fd重置了,那么线程2在执行fd相关操作的时候就会出现Segmentation Fault.(PS: 多线成环境下找到SEG ERR 发生的点还真是不容易,分析了core文件,勉强出现的信息还能看,但是不具体,为什么?). 91 | * 2014-10-1:增加一个日志开关,可以选择 92 | * 2014-10-2: bug:解决多线程环境下,高并发的时候会出现 epoll_ctl add 出错,报错File exist。 93 | error in multithreaded program "epollControl: does not exist (No such file or directory)"单线程不会出错,肯定是多线程的时候某个地方没考虑到. __epoll_ctl man page tells me that the reason for this error is:"ENOENT: op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not in epfd."__ 94 | _错误是连接退出是active应该没有被重置_ 95 | * 2014-10-3:增加了配置文件,并重构了代码,tag v0.09 96 | * 2014-10-10:解决,当连接超过max_event时会发生数组溢出而崩溃,所以需要限制连接,设置一个最大值 97 | * 2014-10-14:解决,url中包含中文而不能匹配文件名的问题。 98 | * 2014-10-15:fix bug:__目录下输出的子目录和文件排序问题__ 99 | * 2014-10-17:fix bug:目录名中含有小数点'.'访问出错 100 | * 2014-10-19:增加个人website前端模板 101 | 102 | ##性能测试(offline/online)(_sparrow VS nginx_) 103 | _note: 这并不是一个公平的性能对比,由于本人对nginx并不是那么的熟,nginx基本上就是用的默认的配置,而且nginx的版本也不是很新。以下对比仅仅是一个粗略的参考_ 104 | 105 | `cpu: Intel(R) Core(TM) i3-2100 CPU @ 3.10GHz , 4 cores; mem: 1G` 106 | 107 | ----------------- 108 | ##Offline 109 | ####ab 测试 110 | `ab -n 10000 -c 500 http://127.0.0.1:xxxx/` 111 | 112 | __sparrow__ 113 | 114 | ![sparrow_ab](performance_test/sparrow_ab.png) 115 | 116 | __nginx__ 117 | 118 | ![nginx_ab](performance_test/nginx_ab.png) 119 | 120 | ------------------ 121 | ####webbench 测试 122 | `webbench -c 500 -t 60 http://127.0.0.1:xxxx/` 123 | 124 | __sparrow__ 125 | 126 | ![sparrow_webb](performance_test/sparrow_webbench.png) 127 | 128 | __nginx__ 129 | 130 | ![nginx_webb](performance_test/nginx_webbench.png) 131 | ----------------- 132 | ##Online 133 | `阿里云,1核cpu,1G内存,1M带宽,Ubuntu12.04` 134 | 135 | `ab -n 500 -c 10 http://xxx.xxx.xxx.xxx:xxxx/` 136 | 137 | __sparrow__ 138 | 139 | ![sparrow_ab_online](performance_test/sparrow_ab_online.jpg) 140 | 141 | __nginx__ 142 | 143 | ![nginx_ab_online](performance_test/nginx_ab_online.jpg) 144 | 145 | 146 | `webbech -c 500 -t 60 http://xxxx` 147 | 148 | `sparrow, nginx respectively` 149 | 150 | ![nginx_sparrow_webb](performance_test/nginx_sparrow_webb.jpg) 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /async_log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "async_log.h" 17 | 18 | 19 | int g_time_out_sec; 20 | int g_min_log_level; 21 | 22 | 23 | static log_buf_t *w_buf; 24 | static log_buf_t *r_buf; 25 | 26 | static int log_fd; 27 | static pthread_t log_pid; 28 | static uint32_t time_zone; 29 | static char log_path[MAX_PATH]; 30 | 31 | static int cur_log_year; 32 | static int cur_log_mon; 33 | static int cur_log_day; 34 | static int cur_log_day_num; 35 | static int g_new_fd = 0; 36 | 37 | static pthread_mutex_t mutex; 38 | static pthread_cond_t cond; 39 | 40 | 41 | static void log_write_impl(const char *file, int line, const char *log_level_str, const char *format, va_list ap); 42 | static void write_to_file_inner(int fd, const char* buf, int size); 43 | static time_info_t get_cur_time(void); 44 | static int create_new_log(void); 45 | static void *write_to_file(void *arg); 46 | static void safe_exit(int sig); 47 | 48 | 49 | void set_time_out_sec(int second) { 50 | g_time_out_sec = second; 51 | } 52 | void set_min_log_level(int log_level) { 53 | g_min_log_level = log_level; 54 | } 55 | 56 | 57 | void log_write(const char *file, int line, uint8_t log_level, const char *format, ...) { 58 | va_list ap; 59 | va_start(ap, format); 60 | 61 | if (log_level >= g_min_log_level) { 62 | const char *log_level_str; 63 | switch (log_level) { 64 | case LOG_TRACE: 65 | log_level_str = LOG_TRACE_STR; 66 | break; 67 | case LOG_DEBUG: 68 | log_level_str = LOG_DEBUG_STR; 69 | break; 70 | case LOG_INFO: 71 | log_level_str = LOG_INFO_STR; 72 | break; 73 | case LOG_WARN: 74 | log_level_str = LOG_WARN_STR; 75 | break; 76 | case LOG_ERROR: 77 | log_level_str = LOG_ERROR_STR; 78 | break; 79 | case LOG_FATAL: 80 | log_level_str = LOG_FATAL_STR; 81 | break; 82 | default: 83 | break; 84 | } 85 | log_write_impl(file, line, log_level_str, format, ap); 86 | } 87 | va_end(ap); 88 | } 89 | 90 | 91 | static 92 | void log_write_impl(const char *file, int line, const char *log_level_str, const char *format, va_list ap) { 93 | char buf_text[1024]; 94 | char buf_time[32]; 95 | memset(buf_text, 0, sizeof(buf_text)); 96 | memset(buf_time, 0, sizeof(buf_time)); 97 | int count_text; 98 | int count_time; 99 | 100 | /** 101 | * thus, no need to call a system call gettid() everytime 102 | */ 103 | static __thread int t_tid = -1; 104 | 105 | if (t_tid == -1) { 106 | t_tid = gettid(); 107 | } 108 | 109 | count_text = snprintf(buf_text, 1024, " %-6s %d %s:%d ", log_level_str, t_tid, file, line); 110 | count_text += vsnprintf(buf_text + count_text, 1024-count_text, format, ap); 111 | if (count_text > 1024) { 112 | count_text = 1024; 113 | } 114 | if (buf_text[count_text - 1] != '\n') { 115 | buf_text[count_text] = '\n'; 116 | buf_text[++count_text] = '\0'; 117 | } 118 | else { 119 | buf_text[count_text] = '\0'; 120 | } 121 | 122 | time_info_t ti; 123 | 124 | while (1) { 125 | pthread_mutex_lock(&mutex); 126 | 127 | /****************************************************************/ 128 | /** 129 | * 这个地方可以优化一下 130 | * 当第一次失败后,返回来在写日志的时候,又重新写了一遍时间 131 | * 是否合理,还需要在仔细琢磨一下 132 | */ 133 | ti = get_cur_time(); 134 | 135 | count_time = snprintf(buf_time, 32, "[ %02d:%02d:%02d.%06ld ]", ti.hour, ti.min, ti.sec, ti.usec); 136 | 137 | /****************************************************************/ 138 | 139 | /** 140 | * create a new log file 141 | */ 142 | if (ti.day_num > cur_log_day_num) { 143 | g_new_fd = 1; 144 | pthread_cond_signal(&cond); 145 | pthread_mutex_unlock(&mutex); 146 | } 147 | /** 148 | * buf is full 149 | */ 150 | if (w_buf->pos + count_time + count_text >= MAX_BUF_SIZE) { 151 | pthread_cond_signal(&cond); 152 | pthread_mutex_unlock(&mutex); 153 | } 154 | else { 155 | strncpy(w_buf->buf + w_buf->pos, buf_time, count_time); 156 | w_buf->pos += count_time; 157 | strncpy(w_buf->buf + w_buf->pos, buf_text, count_text); 158 | w_buf->pos += count_text; 159 | 160 | pthread_mutex_unlock(&mutex); 161 | break; 162 | } 163 | } 164 | } 165 | 166 | 167 | static 168 | void write_to_file_inner(int fd, const char* buf, int size) 169 | { 170 | int ret = -1; 171 | do 172 | { 173 | ret = write(fd, buf, size); 174 | } while ((ret < 0) && (errno == EINTR)); 175 | } 176 | 177 | static 178 | void *write_to_file(void *arg) { 179 | int ret; 180 | struct timespec ts; 181 | int new_fd = -1; 182 | 183 | while (1) { 184 | pthread_mutex_lock(&mutex); 185 | 186 | clock_gettime(CLOCK_REALTIME, &ts); 187 | ts.tv_sec += g_time_out_sec; 188 | if (r_buf->pos == 0) { 189 | ret = pthread_cond_timedwait(&cond, &mutex, &ts); 190 | // if(ret == ETIMEDOUT) { 191 | // debug_printf("time out\n"); 192 | // } else if(ret == 0) { 193 | // debug_printf("signaled\n"); 194 | // } else { 195 | // debug_printf("error in pthread_cond_timedwait\n"); 196 | // } 197 | if (ret == -1) { 198 | fprintf(stderr, "error in pthread_cond_timedwait\n"); 199 | } 200 | } 201 | /** 202 | * swap the read buf and write buf 203 | */ 204 | log_buf_t *tmp = r_buf; 205 | r_buf = w_buf; 206 | w_buf = tmp; 207 | 208 | /** 209 | * if g_new_fd, create a new log file 210 | */ 211 | if (g_new_fd) { 212 | new_fd = create_new_log(); 213 | g_new_fd = 0; 214 | } 215 | 216 | pthread_mutex_unlock(&mutex); 217 | 218 | if (r_buf->pos) { 219 | write_to_file_inner(log_fd, r_buf->buf, r_buf->pos); 220 | r_buf->pos = 0; 221 | } 222 | 223 | if (new_fd != -1) { 224 | int tmp_fd = log_fd; 225 | log_fd = new_fd; 226 | close(tmp_fd); 227 | } 228 | }//end while 229 | return NULL; 230 | } 231 | 232 | static 233 | int create_new_log(void) { 234 | char buf[MAX_PATH]; 235 | int fd; 236 | 237 | time_t tt; 238 | struct tm ts; 239 | time(&tt); 240 | localtime_r(&tt, &ts); 241 | 242 | cur_log_year = ts.tm_year + 1900; 243 | cur_log_mon = ts.tm_mon + 1; 244 | cur_log_day = ts.tm_mday; 245 | //sprintf(buf,"log/"); 246 | if (access("log", F_OK) != 0) { 247 | if (mkdir("log", 0755) < 0) { 248 | fprintf(stderr, "create log folder error\n"); 249 | } 250 | } 251 | 252 | int count = snprintf(buf, MAX_PATH, "%s/log/%04d-%02d-%02d.log", log_path, cur_log_year, cur_log_mon, cur_log_day); 253 | buf[count] = '\0'; 254 | fd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644); 255 | if (fd == -1) { 256 | fprintf(stderr, "error create log fd\n"); 257 | } 258 | return fd; 259 | } 260 | 261 | static 262 | time_info_t get_cur_time(void) { 263 | time_info_t ti; 264 | struct timeval tv; 265 | 266 | gettimeofday(&tv, NULL); 267 | int t = tv.tv_sec % (60 * 60 * 24); 268 | ti.hour = t / (60 * 60) + time_zone; 269 | if (ti.hour >= 24) { 270 | ti.hour -= 24; 271 | } 272 | t %= (60 * 60); 273 | ti.min = t / 60; 274 | ti.sec = t % 60; 275 | ti.usec = tv.tv_usec; 276 | ti.day_num = (tv.tv_sec + time_zone*(60 * 60)) / (60 * 60 * 24); 277 | return ti; 278 | } 279 | 280 | static 281 | void safe_exit(int sig) { 282 | log_destroy(); 283 | exit(1); 284 | } 285 | 286 | void log_init(int second, int log_level) { 287 | int ret; 288 | struct timezone tz; 289 | struct timeval tv; 290 | time_info_t ti; 291 | 292 | /** 293 | * 294 | */ 295 | signal(SIGINT, safe_exit); 296 | signal(SIGTERM, safe_exit); 297 | signal(SIGQUIT, safe_exit); 298 | signal(SIGSEGV, safe_exit); 299 | 300 | set_time_out_sec(second); 301 | set_min_log_level(log_level); 302 | 303 | ret = gettimeofday(&tv, &tz); 304 | assert(ret == 0); 305 | time_zone = -tz.tz_minuteswest / 60; 306 | 307 | int t = tv.tv_sec % (60 * 60 * 24); 308 | ti.hour = t / (60 * 60) + time_zone; 309 | if (ti.hour >= 24) { 310 | ti.hour -= 24; 311 | } 312 | t %= (60 * 60); 313 | ti.min = t / 60; 314 | ti.sec = t % 60; 315 | ti.usec = tv.tv_usec; 316 | 317 | cur_log_day_num = (tv.tv_sec + time_zone*(60 * 60)) / (60 * 60 * 24); 318 | 319 | getcwd(log_path, MAX_PATH); 320 | log_fd = create_new_log(); 321 | 322 | w_buf = (log_buf_t *)malloc(sizeof(log_buf_t)); 323 | r_buf = (log_buf_t *)malloc(sizeof(log_buf_t)); 324 | 325 | pthread_mutex_init(&mutex, NULL); 326 | pthread_cond_init(&cond, NULL); 327 | 328 | char buf[256]; 329 | int count = 0; 330 | 331 | ret = pthread_create(&log_pid, NULL, write_to_file, NULL); 332 | pthread_detach(log_pid); 333 | 334 | if (ret == -1) { 335 | count = snprintf(buf, 256, "[ %02d:%02d:%02d.%06ld ] %-6s %ld %s:%d %s\n", ti.hour, ti.min, ti.sec, ti.usec, \ 336 | LOG_ERROR_STR, gettid(), __FILE__, __LINE__, "create async_log thread error"); 337 | buf[count] = '\0'; 338 | write_to_file_inner(log_fd, buf, count); 339 | } 340 | else { 341 | count = snprintf(buf, 256, "[ %02d:%02d:%02d.%06ld ] %-6s %ld %s:%d %s\n", ti.hour, ti.min, ti.sec, ti.usec, \ 342 | LOG_INFO_STR, gettid(), __FILE__, __LINE__, "create async_log thread successfully"); 343 | buf[count] = '\0'; 344 | write_to_file_inner(log_fd, buf, count); 345 | } 346 | } 347 | void log_destroy() { 348 | log_info("Log is going to be closed..."); 349 | /** 350 | * write the data, which haven't writen to the log file in the buf, to log file 351 | */ 352 | write_to_file_inner(log_fd, w_buf->buf, w_buf->pos); 353 | 354 | pthread_mutex_destroy(&mutex); 355 | pthread_cond_destroy(&cond); 356 | free(w_buf); 357 | free(r_buf); 358 | } -------------------------------------------------------------------------------- /async_log.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOG_H_ 2 | #define _LOG_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" 10 | { 11 | #endif 12 | 13 | #define MAX_PATH 256 14 | #define MAX_BUF_SIZE 1024 * 1024 * 4 15 | 16 | #define LOG_TRACE_STR "TRACE" 17 | #define LOG_DEBUG_STR "DEBUG" 18 | #define LOG_INFO_STR "INFO" 19 | #define LOG_WARN_STR "WARN" 20 | #define LOG_ERROR_STR "ERROR" 21 | #define LOG_FATAL_STR "FATAL" 22 | 23 | 24 | #define log_trace(format, ...) log_write(__FILE__, __LINE__, LOG_TRACE, format, ##__VA_ARGS__); 25 | #define log_debug(format, ...) log_write(__FILE__, __LINE__, LOG_DEBUG, format, ##__VA_ARGS__); 26 | #define log_info(format, ...) log_write(__FILE__, __LINE__, LOG_INFO, format, ##__VA_ARGS__); 27 | #define log_warn(format, ...) log_write(__FILE__, __LINE__, LOG_WARN, format, ##__VA_ARGS__); 28 | #define log_error(format, ...) log_write(__FILE__, __LINE__, LOG_ERROR, format, ##__VA_ARGS__); 29 | #define log_fatal(format, ...) log_write(__FILE__, __LINE__, LOG_FATAL, format, ##__VA_ARGS__); 30 | 31 | #define gettid() syscall(__NR_gettid) 32 | 33 | typedef struct log_buf { 34 | char buf[MAX_BUF_SIZE]; 35 | int pos; 36 | }log_buf_t; 37 | 38 | 39 | enum { 40 | LOG_TRACE = 0, 41 | LOG_DEBUG = 1, 42 | LOG_INFO = 2, 43 | LOG_WARN = 3, 44 | LOG_ERROR = 4, 45 | LOG_FATAL = 5 46 | }; 47 | 48 | typedef struct time_info { 49 | int day_num; 50 | int hour; 51 | int min; 52 | int sec; 53 | long int usec; 54 | }time_info_t; 55 | 56 | 57 | extern void log_write(const char *file, int line, uint8_t log_level, const char *format, ...); 58 | extern void set_time_out_sec(int second); 59 | extern void set_min_log_level(int log_level); 60 | extern void log_init(int second, int log_level); 61 | extern void log_destroy(); 62 | 63 | 64 | #ifdef __cplusplus 65 | } 66 | #endif 67 | 68 | #endif// _LOG_H_ -------------------------------------------------------------------------------- /cJSON.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | /* cJSON */ 24 | /* JSON parser in C. */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "cJSON.h" 34 | 35 | static const char *ep; 36 | 37 | const char *cJSON_GetErrorPtr(void) { return ep; } 38 | 39 | static int cJSON_strcasecmp(const char *s1, const char *s2) 40 | { 41 | if (!s1) return (s1 == s2) ? 0 : 1; if (!s2) return 1; 42 | for (; tolower(*s1) == tolower(*s2); ++s1, ++s2) if (*s1 == 0) return 0; 43 | return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); 44 | } 45 | 46 | static void *(*cJSON_malloc)(size_t sz) = malloc; 47 | static void(*cJSON_free)(void *ptr) = free; 48 | 49 | static char* cJSON_strdup(const char* str) 50 | { 51 | size_t len; 52 | char* copy; 53 | 54 | len = strlen(str) + 1; 55 | if (!(copy = (char*)cJSON_malloc(len))) return 0; 56 | memcpy(copy, str, len); 57 | return copy; 58 | } 59 | 60 | void cJSON_InitHooks(cJSON_Hooks* hooks) 61 | { 62 | if (!hooks) { /* Reset hooks */ 63 | cJSON_malloc = malloc; 64 | cJSON_free = free; 65 | return; 66 | } 67 | 68 | cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc; 69 | cJSON_free = (hooks->free_fn) ? hooks->free_fn : free; 70 | } 71 | 72 | /* Internal constructor. */ 73 | static cJSON *cJSON_New_Item(void) 74 | { 75 | cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); 76 | if (node) memset(node, 0, sizeof(cJSON)); 77 | return node; 78 | } 79 | 80 | /* Delete a cJSON structure. */ 81 | void cJSON_Delete(cJSON *c) 82 | { 83 | cJSON *next; 84 | while (c) 85 | { 86 | next = c->next; 87 | if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); 88 | if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); 89 | if (c->string) cJSON_free(c->string); 90 | cJSON_free(c); 91 | c = next; 92 | } 93 | } 94 | 95 | /* Parse the input text to generate a number, and populate the result into item. */ 96 | static const char *parse_number(cJSON *item, const char *num) 97 | { 98 | double n = 0, sign = 1, scale = 0; int subscale = 0, signsubscale = 1; 99 | 100 | if (*num == '-') sign = -1, num++; /* Has sign? */ 101 | if (*num == '0') num++; /* is zero */ 102 | if (*num >= '1' && *num <= '9') do n = (n*10.0) + (*num++ - '0'); while (*num >= '0' && *num <= '9'); /* Number? */ 103 | if (*num == '.' && num[1] >= '0' && num[1] <= '9') { num++; do n = (n*10.0) + (*num++ - '0'), scale--; while (*num >= '0' && *num <= '9'); } /* Fractional part? */ 104 | if (*num == 'e' || *num == 'E') /* Exponent? */ 105 | { 106 | num++; if (*num == '+') num++; else if (*num == '-') signsubscale = -1, num++; /* With sign? */ 107 | while (*num >= '0' && *num <= '9') subscale = (subscale * 10) + (*num++ - '0'); /* Number? */ 108 | } 109 | 110 | n = sign*n*pow(10.0, (scale + subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ 111 | 112 | item->valuedouble = n; 113 | item->valueint = (int)n; 114 | item->type = cJSON_Number; 115 | return num; 116 | } 117 | 118 | /* Render the number nicely from the given item into a string. */ 119 | static char *print_number(cJSON *item) 120 | { 121 | char *str; 122 | double d = item->valuedouble; 123 | if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) 124 | { 125 | str = (char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ 126 | if (str) sprintf(str, "%d", item->valueint); 127 | } 128 | else 129 | { 130 | str = (char*)cJSON_malloc(64); /* This is a nice tradeoff. */ 131 | if (str) 132 | { 133 | if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60)sprintf(str, "%.0f", d); 134 | else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str, "%e", d); 135 | else sprintf(str, "%f", d); 136 | } 137 | } 138 | return str; 139 | } 140 | 141 | static unsigned parse_hex4(const char *str) 142 | { 143 | unsigned h = 0; 144 | if (*str >= '0' && *str <= '9') h += (*str) - '0'; else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; else return 0; 145 | h = h << 4; str++; 146 | if (*str >= '0' && *str <= '9') h += (*str) - '0'; else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; else return 0; 147 | h = h << 4; str++; 148 | if (*str >= '0' && *str <= '9') h += (*str) - '0'; else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; else return 0; 149 | h = h << 4; str++; 150 | if (*str >= '0' && *str <= '9') h += (*str) - '0'; else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; else return 0; 151 | return h; 152 | } 153 | 154 | /* Parse the input text into an unescaped cstring, and populate item. */ 155 | static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; 156 | static const char *parse_string(cJSON *item, const char *str) 157 | { 158 | const char *ptr = str + 1; char *ptr2; char *out; int len = 0; unsigned uc, uc2; 159 | if (*str != '\"') { ep = str; return 0; } /* not a string! */ 160 | 161 | while (*ptr != '\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ 162 | 163 | out = (char*)cJSON_malloc(len + 1); /* This is how long we need for the string, roughly. */ 164 | if (!out) return 0; 165 | 166 | ptr = str + 1; ptr2 = out; 167 | while (*ptr != '\"' && *ptr) 168 | { 169 | if (*ptr != '\\') *ptr2++ = *ptr++; 170 | else 171 | { 172 | ptr++; 173 | switch (*ptr) 174 | { 175 | case 'b': *ptr2++ = '\b'; break; 176 | case 'f': *ptr2++ = '\f'; break; 177 | case 'n': *ptr2++ = '\n'; break; 178 | case 'r': *ptr2++ = '\r'; break; 179 | case 't': *ptr2++ = '\t'; break; 180 | case 'u': /* transcode utf16 to utf8. */ 181 | uc = parse_hex4(ptr + 1); ptr += 4; /* get the unicode char. */ 182 | 183 | if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) break; /* check for invalid. */ 184 | 185 | if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ 186 | { 187 | if (ptr[1] != '\\' || ptr[2] != 'u') break; /* missing second-half of surrogate. */ 188 | uc2 = parse_hex4(ptr + 3); ptr += 6; 189 | if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */ 190 | uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); 191 | } 192 | 193 | len = 4; if (uc < 0x80) len = 1; else if (uc < 0x800) len = 2; else if (uc < 0x10000) len = 3; ptr2 += len; 194 | 195 | switch (len) { 196 | case 4: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; 197 | case 3: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; 198 | case 2: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; 199 | case 1: *--ptr2 = (uc | firstByteMark[len]); 200 | } 201 | ptr2 += len; 202 | break; 203 | default: *ptr2++ = *ptr; break; 204 | } 205 | ptr++; 206 | } 207 | } 208 | *ptr2 = 0; 209 | if (*ptr == '\"') ptr++; 210 | item->valuestring = out; 211 | item->type = cJSON_String; 212 | return ptr; 213 | } 214 | 215 | /* Render the cstring provided to an escaped version that can be printed. */ 216 | static char *print_string_ptr(const char *str) 217 | { 218 | const char *ptr; char *ptr2, *out; int len = 0; unsigned char token; 219 | 220 | if (!str) return cJSON_strdup(""); 221 | ptr = str; while ((token = *ptr) && ++len) { if (strchr("\"\\\b\f\n\r\t", token)) len++; else if (token<32) len += 5; ptr++; } 222 | 223 | out = (char*)cJSON_malloc(len + 3); 224 | if (!out) return 0; 225 | 226 | ptr2 = out; ptr = str; 227 | *ptr2++ = '\"'; 228 | while (*ptr) 229 | { 230 | if ((unsigned char)*ptr>31 && *ptr != '\"' && *ptr != '\\') *ptr2++ = *ptr++; 231 | else 232 | { 233 | *ptr2++ = '\\'; 234 | switch (token = *ptr++) 235 | { 236 | case '\\': *ptr2++ = '\\'; break; 237 | case '\"': *ptr2++ = '\"'; break; 238 | case '\b': *ptr2++ = 'b'; break; 239 | case '\f': *ptr2++ = 'f'; break; 240 | case '\n': *ptr2++ = 'n'; break; 241 | case '\r': *ptr2++ = 'r'; break; 242 | case '\t': *ptr2++ = 't'; break; 243 | default: sprintf(ptr2, "u%04x", token); ptr2 += 5; break; /* escape and print */ 244 | } 245 | } 246 | } 247 | *ptr2++ = '\"'; *ptr2++ = 0; 248 | return out; 249 | } 250 | /* Invote print_string_ptr (which is useful) on an item. */ 251 | static char *print_string(cJSON *item) { return print_string_ptr(item->valuestring); } 252 | 253 | /* Predeclare these prototypes. */ 254 | static const char *parse_value(cJSON *item, const char *value); 255 | static char *print_value(cJSON *item, int depth, int fmt); 256 | static const char *parse_array(cJSON *item, const char *value); 257 | static char *print_array(cJSON *item, int depth, int fmt); 258 | static const char *parse_object(cJSON *item, const char *value); 259 | static char *print_object(cJSON *item, int depth, int fmt); 260 | 261 | /* Utility to jump whitespace and cr/lf */ 262 | static const char *skip(const char *in) { while (in && *in && (unsigned char)*in <= 32) in++; return in; } 263 | 264 | /* Parse an object - create a new root, and populate. */ 265 | cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, int require_null_terminated) 266 | { 267 | const char *end = 0; 268 | cJSON *c = cJSON_New_Item(); 269 | ep = 0; 270 | if (!c) return 0; /* memory fail */ 271 | 272 | end = parse_value(c, skip(value)); 273 | if (!end) { cJSON_Delete(c); return 0; } /* parse failure. ep is set. */ 274 | 275 | /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ 276 | if (require_null_terminated) { end = skip(end); if (*end) { cJSON_Delete(c); ep = end; return 0; } } 277 | if (return_parse_end) *return_parse_end = end; 278 | return c; 279 | } 280 | /* Default options for cJSON_Parse */ 281 | cJSON *cJSON_Parse(const char *value) { return cJSON_ParseWithOpts(value, 0, 0); } 282 | 283 | /* Render a cJSON item/entity/structure to text. */ 284 | char *cJSON_Print(cJSON *item) { return print_value(item, 0, 1); } 285 | char *cJSON_PrintUnformatted(cJSON *item) { return print_value(item, 0, 0); } 286 | 287 | /* Parser core - when encountering text, process appropriately. */ 288 | static const char *parse_value(cJSON *item, const char *value) 289 | { 290 | if (!value) return 0; /* Fail on null. */ 291 | if (!strncmp(value, "null", 4)) { item->type = cJSON_NULL; return value + 4; } 292 | if (!strncmp(value, "false", 5)) { item->type = cJSON_False; return value + 5; } 293 | if (!strncmp(value, "true", 4)) { item->type = cJSON_True; item->valueint = 1; return value + 4; } 294 | if (*value == '\"') { return parse_string(item, value); } 295 | if (*value == '-' || (*value >= '0' && *value <= '9')) { return parse_number(item, value); } 296 | if (*value == '[') { return parse_array(item, value); } 297 | if (*value == '{') { return parse_object(item, value); } 298 | 299 | ep = value; return 0; /* failure. */ 300 | } 301 | 302 | /* Render a value to text. */ 303 | static char *print_value(cJSON *item, int depth, int fmt) 304 | { 305 | char *out = 0; 306 | if (!item) return 0; 307 | switch ((item->type) & 255) 308 | { 309 | case cJSON_NULL: out = cJSON_strdup("null"); break; 310 | case cJSON_False: out = cJSON_strdup("false"); break; 311 | case cJSON_True: out = cJSON_strdup("true"); break; 312 | case cJSON_Number: out = print_number(item); break; 313 | case cJSON_String: out = print_string(item); break; 314 | case cJSON_Array: out = print_array(item, depth, fmt); break; 315 | case cJSON_Object: out = print_object(item, depth, fmt); break; 316 | } 317 | return out; 318 | } 319 | 320 | /* Build an array from input text. */ 321 | static const char *parse_array(cJSON *item, const char *value) 322 | { 323 | cJSON *child; 324 | if (*value != '[') { ep = value; return 0; } /* not an array! */ 325 | 326 | item->type = cJSON_Array; 327 | value = skip(value + 1); 328 | if (*value == ']') return value + 1; /* empty array. */ 329 | 330 | item->child = child = cJSON_New_Item(); 331 | if (!item->child) return 0; /* memory fail */ 332 | value = skip(parse_value(child, skip(value))); /* skip any spacing, get the value. */ 333 | if (!value) return 0; 334 | 335 | while (*value == ',') 336 | { 337 | cJSON *new_item; 338 | if (!(new_item = cJSON_New_Item())) return 0; /* memory fail */ 339 | child->next = new_item; new_item->prev = child; child = new_item; 340 | value = skip(parse_value(child, skip(value + 1))); 341 | if (!value) return 0; /* memory fail */ 342 | } 343 | 344 | if (*value == ']') return value + 1; /* end of array */ 345 | ep = value; return 0; /* malformed. */ 346 | } 347 | 348 | /* Render an array to text */ 349 | static char *print_array(cJSON *item, int depth, int fmt) 350 | { 351 | char **entries; 352 | char *out = 0, *ptr, *ret; int len = 5; 353 | cJSON *child = item->child; 354 | int numentries = 0, i = 0, fail = 0; 355 | 356 | /* How many entries in the array? */ 357 | while (child) numentries++, child = child->next; 358 | /* Explicitly handle numentries==0 */ 359 | if (!numentries) 360 | { 361 | out = (char*)cJSON_malloc(3); 362 | if (out) strcpy(out, "[]"); 363 | return out; 364 | } 365 | /* Allocate an array to hold the values for each */ 366 | entries = (char**)cJSON_malloc(numentries*sizeof(char*)); 367 | if (!entries) return 0; 368 | memset(entries, 0, numentries*sizeof(char*)); 369 | /* Retrieve all the results: */ 370 | child = item->child; 371 | while (child && !fail) 372 | { 373 | ret = print_value(child, depth + 1, fmt); 374 | entries[i++] = ret; 375 | if (ret) len += strlen(ret) + 2 + (fmt ? 1 : 0); else fail = 1; 376 | child = child->next; 377 | } 378 | 379 | /* If we didn't fail, try to malloc the output string */ 380 | if (!fail) out = (char*)cJSON_malloc(len); 381 | /* If that fails, we fail. */ 382 | if (!out) fail = 1; 383 | 384 | /* Handle failure. */ 385 | if (fail) 386 | { 387 | for (i = 0; i < numentries; i++) if (entries[i]) cJSON_free(entries[i]); 388 | cJSON_free(entries); 389 | return 0; 390 | } 391 | 392 | /* Compose the output array. */ 393 | *out = '['; 394 | ptr = out + 1; *ptr = 0; 395 | for (i = 0; i < numentries; i++) 396 | { 397 | strcpy(ptr, entries[i]); ptr += strlen(entries[i]); 398 | if (i != numentries - 1) { *ptr++ = ','; if (fmt)*ptr++ = ' '; *ptr = 0; } 399 | cJSON_free(entries[i]); 400 | } 401 | cJSON_free(entries); 402 | *ptr++ = ']'; *ptr++ = 0; 403 | return out; 404 | } 405 | 406 | /* Build an object from the text. */ 407 | static const char *parse_object(cJSON *item, const char *value) 408 | { 409 | cJSON *child; 410 | if (*value != '{') { ep = value; return 0; } /* not an object! */ 411 | 412 | item->type = cJSON_Object; 413 | value = skip(value + 1); 414 | if (*value == '}') return value + 1; /* empty array. */ 415 | 416 | item->child = child = cJSON_New_Item(); 417 | if (!item->child) return 0; 418 | value = skip(parse_string(child, skip(value))); 419 | if (!value) return 0; 420 | child->string = child->valuestring; child->valuestring = 0; 421 | if (*value != ':') { ep = value; return 0; } /* fail! */ 422 | value = skip(parse_value(child, skip(value + 1))); /* skip any spacing, get the value. */ 423 | if (!value) return 0; 424 | 425 | while (*value == ',') 426 | { 427 | cJSON *new_item; 428 | if (!(new_item = cJSON_New_Item())) return 0; /* memory fail */ 429 | child->next = new_item; new_item->prev = child; child = new_item; 430 | value = skip(parse_string(child, skip(value + 1))); 431 | if (!value) return 0; 432 | child->string = child->valuestring; child->valuestring = 0; 433 | if (*value != ':') { ep = value; return 0; } /* fail! */ 434 | value = skip(parse_value(child, skip(value + 1))); /* skip any spacing, get the value. */ 435 | if (!value) return 0; 436 | } 437 | 438 | if (*value == '}') return value + 1; /* end of array */ 439 | ep = value; return 0; /* malformed. */ 440 | } 441 | 442 | /* Render an object to text. */ 443 | static char *print_object(cJSON *item, int depth, int fmt) 444 | { 445 | char **entries = 0, **names = 0; 446 | char *out = 0, *ptr, *ret, *str; int len = 7, i = 0, j; 447 | cJSON *child = item->child; 448 | int numentries = 0, fail = 0; 449 | /* Count the number of entries. */ 450 | while (child) numentries++, child = child->next; 451 | /* Explicitly handle empty object case */ 452 | if (!numentries) 453 | { 454 | out = (char*)cJSON_malloc(fmt ? depth + 4 : 3); 455 | if (!out) return 0; 456 | ptr = out; *ptr++ = '{'; 457 | if (fmt) { *ptr++ = '\n'; for (i = 0; i < depth - 1; i++) *ptr++ = '\t'; } 458 | *ptr++ = '}'; *ptr++ = 0; 459 | return out; 460 | } 461 | /* Allocate space for the names and the objects */ 462 | entries = (char**)cJSON_malloc(numentries*sizeof(char*)); 463 | if (!entries) return 0; 464 | names = (char**)cJSON_malloc(numentries*sizeof(char*)); 465 | if (!names) { cJSON_free(entries); return 0; } 466 | memset(entries, 0, sizeof(char*)*numentries); 467 | memset(names, 0, sizeof(char*)*numentries); 468 | 469 | /* Collect all the results into our arrays: */ 470 | child = item->child; depth++; if (fmt) len += depth; 471 | while (child) 472 | { 473 | names[i] = str = print_string_ptr(child->string); 474 | entries[i++] = ret = print_value(child, depth, fmt); 475 | if (str && ret) len += strlen(ret) + strlen(str) + 2 + (fmt ? 2 + depth : 0); else fail = 1; 476 | child = child->next; 477 | } 478 | 479 | /* Try to allocate the output string */ 480 | if (!fail) out = (char*)cJSON_malloc(len); 481 | if (!out) fail = 1; 482 | 483 | /* Handle failure */ 484 | if (fail) 485 | { 486 | for (i = 0; i < numentries; i++) { if (names[i]) cJSON_free(names[i]); if (entries[i]) cJSON_free(entries[i]); } 487 | cJSON_free(names); cJSON_free(entries); 488 | return 0; 489 | } 490 | 491 | /* Compose the output: */ 492 | *out = '{'; ptr = out + 1; if (fmt)*ptr++ = '\n'; *ptr = 0; 493 | for (i = 0; i < numentries; i++) 494 | { 495 | if (fmt) for (j = 0; j < depth; j++) *ptr++ = '\t'; 496 | strcpy(ptr, names[i]); ptr += strlen(names[i]); 497 | *ptr++ = ':'; if (fmt) *ptr++ = '\t'; 498 | strcpy(ptr, entries[i]); ptr += strlen(entries[i]); 499 | if (i != numentries - 1) *ptr++ = ','; 500 | if (fmt) *ptr++ = '\n'; *ptr = 0; 501 | cJSON_free(names[i]); cJSON_free(entries[i]); 502 | } 503 | 504 | cJSON_free(names); cJSON_free(entries); 505 | if (fmt) for (i = 0; ichild; int i = 0; while (c)i++, c = c->next; return i; } 512 | cJSON *cJSON_GetArrayItem(cJSON *array, int item) { cJSON *c = array->child; while (c && item>0) item--, c = c->next; return c; } 513 | cJSON *cJSON_GetObjectItem(cJSON *object, const char *string) { cJSON *c = object->child; while (c && cJSON_strcasecmp(c->string, string)) c = c->next; return c; } 514 | 515 | /* Utility for array list handling. */ 516 | static void suffix_object(cJSON *prev, cJSON *item) { prev->next = item; item->prev = prev; } 517 | /* Utility for handling references. */ 518 | static cJSON *create_reference(cJSON *item) { cJSON *ref = cJSON_New_Item(); if (!ref) return 0; memcpy(ref, item, sizeof(cJSON)); ref->string = 0; ref->type |= cJSON_IsReference; ref->next = ref->prev = 0; return ref; } 519 | 520 | /* Add item to array/object. */ 521 | void cJSON_AddItemToArray(cJSON *array, cJSON *item) { cJSON *c = array->child; if (!item) return; if (!c) { array->child = item; } else { while (c && c->next) c = c->next; suffix_object(c, item); } } 522 | void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) { if (!item) return; if (item->string) cJSON_free(item->string); item->string = cJSON_strdup(string); cJSON_AddItemToArray(object, item); } 523 | void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) { cJSON_AddItemToArray(array, create_reference(item)); } 524 | void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) { cJSON_AddItemToObject(object, string, create_reference(item)); } 525 | 526 | cJSON *cJSON_DetachItemFromArray(cJSON *array, int which) { 527 | cJSON *c = array->child; while (c && which > 0) c = c->next, which--; if (!c) return 0; 528 | if (c->prev) c->prev->next = c->next; if (c->next) c->next->prev = c->prev; if (c == array->child) array->child = c->next; c->prev = c->next = 0; return c; 529 | } 530 | void cJSON_DeleteItemFromArray(cJSON *array, int which) { cJSON_Delete(cJSON_DetachItemFromArray(array, which)); } 531 | cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string) { int i = 0; cJSON *c = object->child; while (c && cJSON_strcasecmp(c->string, string)) i++, c = c->next; if (c) return cJSON_DetachItemFromArray(object, i); return 0; } 532 | void cJSON_DeleteItemFromObject(cJSON *object, const char *string) { cJSON_Delete(cJSON_DetachItemFromObject(object, string)); } 533 | 534 | /* Replace array/object items with new ones. */ 535 | void cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) { 536 | cJSON *c = array->child; while (c && which > 0) c = c->next, which--; if (!c) return; 537 | newitem->next = c->next; newitem->prev = c->prev; if (newitem->next) newitem->next->prev = newitem; 538 | if (c == array->child) array->child = newitem; else newitem->prev->next = newitem; c->next = c->prev = 0; cJSON_Delete(c); 539 | } 540 | void cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem){ int i = 0; cJSON *c = object->child; while (c && cJSON_strcasecmp(c->string, string))i++, c = c->next; if (c){ newitem->string = cJSON_strdup(string); cJSON_ReplaceItemInArray(object, i, newitem); } } 541 | 542 | /* Create basic types: */ 543 | cJSON *cJSON_CreateNull(void) { cJSON *item = cJSON_New_Item(); if (item)item->type = cJSON_NULL; return item; } 544 | cJSON *cJSON_CreateTrue(void) { cJSON *item = cJSON_New_Item(); if (item)item->type = cJSON_True; return item; } 545 | cJSON *cJSON_CreateFalse(void) { cJSON *item = cJSON_New_Item(); if (item)item->type = cJSON_False; return item; } 546 | cJSON *cJSON_CreateBool(int b) { cJSON *item = cJSON_New_Item(); if (item)item->type = b ? cJSON_True : cJSON_False; return item; } 547 | cJSON *cJSON_CreateNumber(double num) { cJSON *item = cJSON_New_Item(); if (item){ item->type = cJSON_Number; item->valuedouble = num; item->valueint = (int)num; }return item; } 548 | cJSON *cJSON_CreateString(const char *string) { cJSON *item = cJSON_New_Item(); if (item){ item->type = cJSON_String; item->valuestring = cJSON_strdup(string); }return item; } 549 | cJSON *cJSON_CreateArray(void) { cJSON *item = cJSON_New_Item(); if (item)item->type = cJSON_Array; return item; } 550 | cJSON *cJSON_CreateObject(void) { cJSON *item = cJSON_New_Item(); if (item)item->type = cJSON_Object; return item; } 551 | 552 | /* Create Arrays: */ 553 | cJSON *cJSON_CreateIntArray(const int *numbers, int count) { int i; cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); for (i = 0; a && i < count; i++){ n = cJSON_CreateNumber(numbers[i]); if (!i)a->child = n; else suffix_object(p, n); p = n; }return a; } 554 | cJSON *cJSON_CreateFloatArray(const float *numbers, int count) { int i; cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); for (i = 0; a && i < count; i++){ n = cJSON_CreateNumber(numbers[i]); if (!i)a->child = n; else suffix_object(p, n); p = n; }return a; } 555 | cJSON *cJSON_CreateDoubleArray(const double *numbers, int count) { int i; cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); for (i = 0; a && i < count; i++){ n = cJSON_CreateNumber(numbers[i]); if (!i)a->child = n; else suffix_object(p, n); p = n; }return a; } 556 | cJSON *cJSON_CreateStringArray(const char **strings, int count) { int i; cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); for (i = 0; a && i < count; i++){ n = cJSON_CreateString(strings[i]); if (!i)a->child = n; else suffix_object(p, n); p = n; }return a; } 557 | 558 | /* Duplication */ 559 | cJSON *cJSON_Duplicate(cJSON *item, int recurse) 560 | { 561 | cJSON *newitem, *cptr, *nptr = 0, *newchild; 562 | /* Bail on bad ptr */ 563 | if (!item) return 0; 564 | /* Create new item */ 565 | newitem = cJSON_New_Item(); 566 | if (!newitem) return 0; 567 | /* Copy over all vars */ 568 | newitem->type = item->type&(~cJSON_IsReference), newitem->valueint = item->valueint, newitem->valuedouble = item->valuedouble; 569 | if (item->valuestring) { newitem->valuestring = cJSON_strdup(item->valuestring); if (!newitem->valuestring) { cJSON_Delete(newitem); return 0; } } 570 | if (item->string) { newitem->string = cJSON_strdup(item->string); if (!newitem->string) { cJSON_Delete(newitem); return 0; } } 571 | /* If non-recursive, then we're done! */ 572 | if (!recurse) return newitem; 573 | /* Walk the ->next chain for the child. */ 574 | cptr = item->child; 575 | while (cptr) 576 | { 577 | newchild = cJSON_Duplicate(cptr, 1); /* Duplicate (with recurse) each item in the ->next chain */ 578 | if (!newchild) { cJSON_Delete(newitem); return 0; } 579 | if (nptr) { nptr->next = newchild, newchild->prev = nptr; nptr = newchild; } /* If newitem->child already set, then crosswire ->prev and ->next and move on */ 580 | else { newitem->child = newchild; nptr = newchild; } /* Set newitem->child and move to it */ 581 | cptr = cptr->next; 582 | } 583 | return newitem; 584 | } 585 | 586 | void cJSON_Minify(char *json) 587 | { 588 | char *into = json; 589 | while (*json) 590 | { 591 | if (*json == ' ') json++; 592 | else if (*json == '\t') json++; // Whitespace characters. 593 | else if (*json == '\r') json++; 594 | else if (*json == '\n') json++; 595 | else if (*json == '/' && json[1] == '/') while (*json && *json != '\n') json++; // double-slash comments, to end of line. 596 | else if (*json == '/' && json[1] == '*') { while (*json && !(*json == '*' && json[1] == '/')) json++; json += 2; } // multiline comments. 597 | else if (*json == '\"'){ *into++ = *json++; while (*json && *json != '\"'){ if (*json == '\\') *into++ = *json++; *into++ = *json++; }*into++ = *json++; } // string literals, which are \" sensitive. 598 | else *into++ = *json++; // All other characters. 599 | } 600 | *into = 0; // and null-terminate. 601 | } 602 | -------------------------------------------------------------------------------- /cJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | /* cJSON Types: */ 32 | #define cJSON_False 0 33 | #define cJSON_True 1 34 | #define cJSON_NULL 2 35 | #define cJSON_Number 3 36 | #define cJSON_String 4 37 | #define cJSON_Array 5 38 | #define cJSON_Object 6 39 | 40 | #define cJSON_IsReference 256 41 | 42 | /* The cJSON structure: */ 43 | typedef struct cJSON { 44 | struct cJSON *next, *prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 45 | struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 46 | 47 | int type; /* The type of the item, as above. */ 48 | 49 | char *valuestring; /* The item's string, if type==cJSON_String */ 50 | int valueint; /* The item's number, if type==cJSON_Number */ 51 | double valuedouble; /* The item's number, if type==cJSON_Number */ 52 | 53 | char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 54 | } cJSON; 55 | 56 | typedef struct cJSON_Hooks { 57 | void *(*malloc_fn)(size_t sz); 58 | void(*free_fn)(void *ptr); 59 | } cJSON_Hooks; 60 | 61 | /* Supply malloc, realloc and free functions to cJSON */ 62 | extern void cJSON_InitHooks(cJSON_Hooks* hooks); 63 | 64 | 65 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ 66 | extern cJSON *cJSON_Parse(const char *value); 67 | /* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ 68 | extern char *cJSON_Print(cJSON *item); 69 | /* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ 70 | extern char *cJSON_PrintUnformatted(cJSON *item); 71 | /* Delete a cJSON entity and all subentities. */ 72 | extern void cJSON_Delete(cJSON *c); 73 | 74 | /* Returns the number of items in an array (or object). */ 75 | extern int cJSON_GetArraySize(cJSON *array); 76 | /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ 77 | extern cJSON *cJSON_GetArrayItem(cJSON *array, int item); 78 | /* Get item "string" from object. Case insensitive. */ 79 | extern cJSON *cJSON_GetObjectItem(cJSON *object, const char *string); 80 | 81 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 82 | extern const char *cJSON_GetErrorPtr(void); 83 | 84 | /* These calls create a cJSON item of the appropriate type. */ 85 | extern cJSON *cJSON_CreateNull(void); 86 | extern cJSON *cJSON_CreateTrue(void); 87 | extern cJSON *cJSON_CreateFalse(void); 88 | extern cJSON *cJSON_CreateBool(int b); 89 | extern cJSON *cJSON_CreateNumber(double num); 90 | extern cJSON *cJSON_CreateString(const char *string); 91 | extern cJSON *cJSON_CreateArray(void); 92 | extern cJSON *cJSON_CreateObject(void); 93 | 94 | /* These utilities create an Array of count items. */ 95 | extern cJSON *cJSON_CreateIntArray(const int *numbers, int count); 96 | extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count); 97 | extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count); 98 | extern cJSON *cJSON_CreateStringArray(const char **strings, int count); 99 | 100 | /* Append item to the specified array/object. */ 101 | extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); 102 | extern void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); 103 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 104 | extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 105 | extern void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); 106 | 107 | /* Remove/Detatch items from Arrays/Objects. */ 108 | extern cJSON *cJSON_DetachItemFromArray(cJSON *array, int which); 109 | extern void cJSON_DeleteItemFromArray(cJSON *array, int which); 110 | extern cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string); 111 | extern void cJSON_DeleteItemFromObject(cJSON *object, const char *string); 112 | 113 | /* Update array items. */ 114 | extern void cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); 115 | extern void cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem); 116 | 117 | /* Duplicate a cJSON item */ 118 | extern cJSON *cJSON_Duplicate(cJSON *item, int recurse); 119 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 120 | need to be released. With recurse!=0, it will duplicate any children connected to the item. 121 | The item->next and ->prev pointers are always zero on return from Duplicate. */ 122 | 123 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 124 | extern cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, int require_null_terminated); 125 | 126 | extern void cJSON_Minify(char *json); 127 | 128 | /* Macros for creating things quickly. */ 129 | #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) 130 | #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) 131 | #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) 132 | #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) 133 | #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) 134 | #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) 135 | 136 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 137 | #define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) 138 | 139 | #ifdef __cplusplus 140 | } 141 | #endif 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | 8 | ////////////////////////////////////////////////// 9 | /*initialize the conf object*/ 10 | config_t conf = { 11 | DEF_LISTEN_PORT, 12 | DEF_MAX_EVENT, 13 | DEF_USE_EPOLL_ET, 14 | DEF_USE_TCP_CORK, 15 | DEF_ROOT_DIR, 16 | DEF_CACHE_CONTROL_MAX_AGE, 17 | DEF_LOG_ENABLE, 18 | DEF_LOG_TIME_OUT, 19 | DEF_LOG_LEVEL, 20 | DEF_WORKER_THREAD_NUM, 21 | DEF_HOME_PAGE, 22 | DEF_MAX_SUB_ITEM_NUM 23 | }; 24 | ///////////////////////////////////////////////// 25 | 26 | /** 27 | * remove the space character at the left of the str 28 | */ 29 | static 30 | char * trim_left(char *str) { 31 | char *p; 32 | if (str == NULL) 33 | return NULL; 34 | p = str; 35 | while (*p == ' ' && *p != '\0') { 36 | ++p; 37 | } 38 | return memmove(str, p, strlen(p) + 1); 39 | } 40 | 41 | /** 42 | * remove the space character at the right of the str 43 | */ 44 | static 45 | char * trim_right(char *str) { 46 | int len; 47 | if (str == NULL) 48 | return NULL; 49 | 50 | len = strlen(str); 51 | while (len) { 52 | if (*(str + len - 1) == ' ') { 53 | *(str + len - 1) = '\0'; 54 | } 55 | else { 56 | break; 57 | } 58 | len--; 59 | } 60 | return str; 61 | } 62 | 63 | /** 64 | * get the key and value of the line 65 | */ 66 | static 67 | int get_kv(char *line, char **key, char **value) { 68 | char *p; 69 | char *t; 70 | trim_left(line); 71 | p = strchr(line, '='); 72 | if (p == NULL) { 73 | fprintf(stderr, "BAD FORMAT:%s\n", line); 74 | return -1; 75 | } 76 | *key = line; 77 | *value = p + 1; 78 | *p = '\0'; 79 | trim_right(*key); 80 | 81 | 82 | //remove the '\n' at the end of the line 83 | t = strrchr(*value, '\n'); 84 | if (t != NULL) 85 | *t = '\0'; 86 | t = strrchr(*value, '\r'); 87 | if (t != NULL) 88 | *t = '\0'; 89 | trim_left(*value); 90 | trim_right(*value); 91 | return 0; 92 | } 93 | 94 | /** 95 | * read the configuration file, and export to conf object 96 | */ 97 | int read_config(config_t *conf) { 98 | char config_line[512]; 99 | FILE *fp = fopen(CONFIG_FILE_PATH, "r"); 100 | if (fp == NULL) { 101 | fprintf(stderr, "Can not open config file:%s\n", strerror(errno)); 102 | return -1; 103 | } 104 | 105 | while (!feof(fp)) { 106 | int ret; 107 | char *key = NULL; 108 | char *value = NULL; 109 | memset(config_line, 0, sizeof(config_line)); 110 | fgets(config_line, 512, fp); 111 | 112 | trim_left(config_line); 113 | if (config_line[0] == '#' || config_line[0] == '\n' || config_line[0] == '\r' || strcmp(config_line, "") == 0) 114 | continue; 115 | 116 | ret = get_kv(config_line, &key, &value); 117 | if (ret != -1) { 118 | if (IS_EQUAL(key, "listen_port")) { 119 | conf->listen_port = atoi(value); 120 | } 121 | else if (IS_EQUAL(key, "max_conn")) { 122 | conf->max_conn = atoi(value); 123 | } 124 | else if (IS_EQUAL(key, "use_epoll_et")) { 125 | conf->use_epoll_et = atoi(value); 126 | } 127 | else if (IS_EQUAL(key, "use_tcp_cork")) { 128 | conf->use_tcp_cork = atoi(value); 129 | } 130 | else if (IS_EQUAL(key, "root_dir")) { 131 | if (strcmp(value, "") == 0) 132 | continue; 133 | strcpy(conf->root_dir, value); 134 | int len = strlen(conf->root_dir); 135 | //路径不需要加 '/' 136 | if (len != 1) { 137 | if (conf->root_dir[len - 1] == '/') { 138 | conf->root_dir[len - 1] = '\0'; 139 | } 140 | } 141 | // if(conf->root_dir[len-1] != '/') { 142 | // conf->root_dir[len] = '/'; 143 | // conf->root_dir[len+1] = '\0'; 144 | // } 145 | 146 | } 147 | else if (IS_EQUAL(key, "cache_control_max_age")) { 148 | conf->cache_control_max_age = atoi(value); 149 | } 150 | else if (IS_EQUAL(key, "log_time_out")) { 151 | conf->log_time_out = atoi(value); 152 | } 153 | else if (IS_EQUAL(key, "log_level")) { 154 | conf->log_level = atoi(value); 155 | } 156 | else if (IS_EQUAL(key, "worker_thread_num")) { 157 | conf->worker_thread_num = atoi(value); 158 | } 159 | else if (IS_EQUAL(key, "log_enable")) { 160 | conf->log_enable = atoi(value); 161 | } 162 | else if (IS_EQUAL(key, "default_home_page")) { 163 | strcpy(conf->def_home_page, value); 164 | } 165 | else if (IS_EQUAL(key, "max_sub_item_num")) { 166 | conf->max_sub_item_num = atoi(value); 167 | } 168 | else { 169 | continue; 170 | } 171 | } 172 | 173 | } 174 | fclose(fp); 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H 2 | #define _CONFIG_H 3 | 4 | #ifdef __cplusplus 5 | 6 | extern "C" 7 | { 8 | #endif 9 | 10 | #define CONFIG_FILE_PATH "config/sparrow.conf" 11 | 12 | /** 13 | * default configuration value 14 | */ 15 | #define DEF_LISTEN_PORT 6868 16 | #define DEF_MAX_EVENT 1024 17 | #define DEF_USE_EPOLL_ET 1 18 | #define DEF_USE_TCP_CORK 1 19 | #define DEF_ROOT_DIR "./www" 20 | 21 | #define DEF_CACHE_CONTROL_MAX_AGE 300 22 | 23 | #define DEF_LOG_TIME_OUT 60 24 | #define DEF_LOG_LEVEL 2 25 | #define DEF_LOG_ENABLE 1 26 | 27 | #define DEF_WORKER_THREAD_NUM 3 28 | #define DEF_HOME_PAGE "index.html" 29 | 30 | #define DEF_MAX_SUB_ITEM_NUM 100 31 | 32 | #define IS_EQUAL(str1, str2) strcmp(str1, str2)== 0 33 | 34 | typedef struct { 35 | int listen_port; 36 | int max_conn; 37 | int use_epoll_et; 38 | int use_tcp_cork; 39 | 40 | char root_dir[512]; 41 | 42 | int cache_control_max_age; 43 | 44 | //log 45 | int log_enable; 46 | int log_time_out; 47 | int log_level; 48 | 49 | int worker_thread_num; 50 | 51 | char def_home_page[128]; 52 | 53 | //folder 54 | int max_sub_item_num; 55 | 56 | } config_t; 57 | 58 | extern config_t conf; 59 | extern int read_config(config_t *conf); 60 | 61 | #ifdef __cplusplus 62 | } 63 | #endif 64 | 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /config/sparrow.conf: -------------------------------------------------------------------------------- 1 | ############# SERVER ################### 2 | 3 | # The listen port of the http server 4 | listen_port = 80 5 | 6 | # The number of the worker threads 7 | worker_thread_num = 3 8 | 9 | # The maximum number of concurrent connections 10 | max_conn = 1024 11 | 12 | # Whether to use the EPOLLET model in the event loops 13 | use_epoll_et = 1 14 | 15 | # Whether to use the TCP_CORK option 16 | use_tcp_cork = 1 17 | 18 | # The default home page 19 | default_home_page = index.html 20 | 21 | # The path of working directory 22 | root_dir = ./www/ 23 | 24 | # The cache retention time [second] 25 | cache_control_max_age = 300 26 | 27 | ################# LOG #################### 28 | # Whether to start the log 29 | log_enable = 0 30 | # The timeout time to write the log to the log file [second] 31 | log_time_out = 600 32 | 33 | # The minimum log level[0:trace, 1:debug, 2:info, 3:warn, 4:error, 5:fatal] 34 | log_level = 2 35 | 36 | 37 | ############### FOLDER ################### 38 | # The maximum number of sub-items of one folder 39 | max_sub_item_num = 50 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /config/test.conf: -------------------------------------------------------------------------------- 1 | listen_port = 8080 2 | 3 | worker_thread_num = 3 4 | 5 | max_conn = 1024 6 | 7 | use_epoll_et = 1 8 | 9 | use_tcp_cork = 1 10 | 11 | default_home_page = index.html 12 | 13 | root_dir = ./www/ 14 | 15 | cacha_control_max_age = 300 16 | 17 | log_enable = 0 18 | 19 | log_time_out = 300 20 | log_level = 2 21 | 22 | max_sub_item_num = 50 23 | -------------------------------------------------------------------------------- /dir.part1: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Good Good Study, Day Day Up 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /dir.part2: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 | -------------------------------------------------------------------------------- /ev_loop.c: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * @author simoncheng 3 | * @data 2014.8 4 | * @version v0.1 5 | * this program is an encapsulation of the EPOLL API which is frequently 6 | * used in backend systems based on linux. 7 | * it likes a simplified libev library which only supports the events 8 | * on fds. timer and signal will be added later. 9 | ***********************************************************************/ 10 | 11 | 12 | #include "ev_loop.h" 13 | #include "async_log.h" 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | static int lock_ = 0; 33 | 34 | /** 35 | * create and init an event loop 36 | * @param maxevent max count event(fd) the loop support 37 | * @param et whether use EPOLLET 38 | */ 39 | ev_loop_t *ev_create_loop(int maxevent, int et) { 40 | ev_loop_t *loop; 41 | loop = (ev_loop_t*)malloc(sizeof(ev_loop_t)); 42 | loop->maxevent = maxevent; 43 | loop->etmodel = et; 44 | loop->epfd = epoll_create1(0); 45 | if (loop->epfd == -1) { 46 | return NULL; 47 | } 48 | loop->events = (struct epoll_event *)malloc(maxevent * sizeof(struct epoll_event)); 49 | 50 | //init 51 | LOCK(lock_); 52 | if (fd_records == NULL) { 53 | fd_records = (fd_record_t *)malloc(maxevent * sizeof(fd_record_t)); 54 | int i; 55 | for (i = 0; i < maxevent; i++) { 56 | fd_records[i].active = 0; 57 | fd_records[i].events = 0; 58 | fd_records[i].cb_read = NULL; 59 | fd_records[i].cb_write = NULL; 60 | 61 | fd_records[i].ffd = NO_FILE_FD; 62 | fd_records[i].write_pos = 0; 63 | fd_records[i].read_pos = 0; 64 | fd_records[i].total_len = 0; 65 | memset(fd_records[i].buf, 0, MAXBUFSIZE); 66 | memset(fd_records[i].path, 0, sizeof(fd_records[i].path)); 67 | fd_records[i].http_code = 200; 68 | fd_records[i].keep_alive = 0; 69 | fd_records[i].timer_ptr = NULL; 70 | } 71 | printf("init\n"); 72 | } 73 | //printf("after init\n"); 74 | UNLOCK(lock_); 75 | 76 | return loop; 77 | } 78 | 79 | /** 80 | * register events on fd in the loop 81 | */ 82 | int ev_register(ev_loop_t*loop, int fd, EV_TYPE events, cb_func_t cb) { 83 | if (!(events & EV_READ || events & EV_WRITE)) { 84 | fprintf(stderr, "invalid events\n"); 85 | return -1; 86 | } 87 | if (fd < 0) { 88 | fprintf(stderr, "invalid fd\n"); 89 | return -1; 90 | } 91 | /** 92 | * events registerd already, just change the cb 93 | */ 94 | if ((fd_records[fd].events & events) == events) { 95 | //printf("ev_registered already\n");; 96 | if (fd_records[fd].events & EV_READ) { 97 | fd_records[fd].cb_read = cb; 98 | } 99 | if (fd_records[fd].events & EV_WRITE) { 100 | // printf("+++++++++EV_WRITE++++++++\n"); 101 | fd_records[fd].cb_write = cb; 102 | } 103 | } 104 | else { /*new add event*/ 105 | if (EV_READ & events) { 106 | fd_records[fd].cb_read = cb; 107 | } 108 | if (EV_WRITE & events) { 109 | fd_records[fd].cb_write = cb; 110 | } 111 | fd_records[fd].events |= events; 112 | 113 | struct epoll_event ev; 114 | ev.events = fd_records[fd].events; 115 | if (loop->etmodel) { 116 | ev.events |= EPOLLET; 117 | } 118 | ev.data.fd = fd; 119 | if (events == EV_WRITE/*fd_records[fd].active*/) {/*mod*/ 120 | if (-1 == epoll_ctl(loop->epfd, EPOLL_CTL_MOD, fd, &ev)) { 121 | fprintf(stderr, "epoll_ctl mod in ev_register: %s, fd:%d, %ld\n", strerror(errno), fd, gettid()); 122 | ev_clear(fd); 123 | close(fd); 124 | return -1; 125 | } 126 | } 127 | else if (events == EV_READ) {/*add*/ 128 | if (-1 == epoll_ctl(loop->epfd, EPOLL_CTL_ADD, fd, &ev)) { 129 | fprintf(stderr, "epoll_ctl add in ev_register: %s, fd:%d, %ld\n", strerror(errno), fd, gettid()); 130 | ev_unregister(loop, fd); 131 | close(fd); 132 | return -1; 133 | } 134 | } 135 | else { 136 | fprintf(stderr, "epoll_ctl undefined events\n"); 137 | close(fd); 138 | return -1; 139 | } 140 | 141 | } 142 | fd_records[fd].active = 1; 143 | return 0; 144 | } 145 | 146 | /** 147 | * unregister the fd from the loop 148 | */ 149 | int ev_unregister(ev_loop_t *loop, int fd) { 150 | struct epoll_event ev; 151 | if (-1 == epoll_ctl(loop->epfd, EPOLL_CTL_DEL, fd, &ev)) { 152 | fprintf(stderr, "epoll_ctl del in ev_unregister:%s\n", strerror(errno)); 153 | ev_clear(fd); 154 | return -1; 155 | } 156 | ev_clear(fd); 157 | return 0; 158 | } 159 | /** 160 | * stop the events on the fd, not unregister the fd 161 | */ 162 | int ev_stop(ev_loop_t *loop, int fd, EV_TYPE events) { 163 | /*fd in use, and evnets on fd*/ 164 | if (/*fd_records[fd].active &&*/ (fd_records[fd].events & events)) { 165 | if ((fd_records[fd].events & EV_READ) && (events & EV_READ)) { 166 | fd_records[fd].events &= (~EV_READ); 167 | } 168 | if ((fd_records[fd].events & EV_WRITE) && (events & EV_WRITE)) { 169 | fd_records[fd].events &= (~EV_WRITE); 170 | } 171 | 172 | struct epoll_event ev; 173 | ev.events = fd_records[fd].events; 174 | if (loop->etmodel) { 175 | ev.events |= EPOLLET; 176 | } 177 | ev.data.fd = fd; 178 | 179 | if (-1 == epoll_ctl(loop->epfd, EPOLL_CTL_MOD, fd, &ev)) { 180 | fprintf(stderr, "epoll_ctl mod in ev_stop\n"); 181 | ev_clear(fd); 182 | return -1; 183 | } 184 | 185 | // if(!(fd_records[fd].events & EV_READ || fd_records[fd].events &EV_WRITE)) { 186 | // //printf("here...\n"); 187 | // //return ev_unregister(loop, fd); 188 | // } 189 | return 0; 190 | } 191 | return 0; 192 | } 193 | 194 | /** 195 | * run loop, this is start of the loop 196 | */ 197 | int ev_run_loop(ev_loop_t *loop) { 198 | while (1) { 199 | int num; 200 | num = epoll_wait(loop->epfd, loop->events, loop->maxevent, -1); 201 | if (num == -1) { 202 | /** 203 | * fix gbd can not run 204 | */ 205 | if (errno == EINTR) 206 | continue; 207 | fprintf(stderr, "epoll wait error: %s\n", strerror(errno)); 208 | return -1; 209 | } 210 | int i; 211 | for (i = 0; i < num; i++) { 212 | int fd = loop->events[i].data.fd; 213 | fd_record_t record = fd_records[fd]; 214 | if (EV_READ & loop->events[i].events) { 215 | if (record.cb_read != NULL) 216 | (*(record.cb_read))(loop, fd, EV_READ); 217 | } 218 | /*pre-step may have unregisterd the fd, make sure the fd is active!*/ 219 | if ((EV_WRITE & loop->events[i].events) && fd_records[fd].active) { 220 | if (record.cb_write != NULL) 221 | (*(record.cb_write))(loop, fd, EV_WRITE); 222 | } 223 | 224 | } 225 | } 226 | } 227 | 228 | /** 229 | * a build-in tcp server, return the listen socket 230 | */ 231 | int tcp_server(int port) { 232 | signal(SIGPIPE, SIG_IGN); 233 | struct sockaddr_in server_addr; 234 | int listen_sock; 235 | 236 | listen_sock = socket(AF_INET, SOCK_STREAM, 0); 237 | if (-1 == listen_sock) { 238 | fprintf(stderr, "listen err\n"); 239 | } 240 | 241 | memset(&server_addr, 0, sizeof(server_addr)); 242 | 243 | server_addr.sin_port = htons(port); 244 | server_addr.sin_family = AF_INET; 245 | server_addr.sin_addr.s_addr = htonl(INADDR_ANY); 246 | 247 | int ret; 248 | ret = bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)); 249 | if (-1 == ret) { 250 | fprintf(stderr, "bind err: %s\n", strerror(errno)); 251 | return -1; 252 | } 253 | 254 | setnonblocking(listen_sock); 255 | 256 | int reuse = 1; 257 | setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); 258 | 259 | if (-1 == listen(listen_sock, SOMAXCONN)) { 260 | fprintf(stderr, "listen err\n"); 261 | return -1; 262 | } 263 | return listen_sock; 264 | 265 | } 266 | 267 | /** 268 | * set a fd to non-blocking 269 | */ 270 | int setnonblocking(int fd) { 271 | int flags; 272 | if (-1 == (flags = fcntl(fd, F_GETFL, 0))) { 273 | perror("setnonblocking error"); 274 | return -1; 275 | } 276 | return fcntl(fd, F_SETFL, flags | O_NONBLOCK); 277 | } 278 | 279 | 280 | void ev_clear(int fd) { 281 | fd_records[fd].active = 0; 282 | fd_records[fd].events = 0; 283 | fd_records[fd].cb_read = NULL; 284 | fd_records[fd].cb_write = NULL; 285 | if (fd_records[fd].ffd != NO_FILE_FD) { 286 | close(fd_records[fd].ffd); 287 | fd_records[fd].ffd = NO_FILE_FD; 288 | } 289 | fd_records[fd].ffd = NO_FILE_FD; 290 | fd_records[fd].write_pos = 0; 291 | fd_records[fd].total_len = 0; 292 | fd_records[fd].read_pos = 0; 293 | fd_records[fd].http_code = 200; 294 | fd_records[fd].keep_alive = 0; 295 | fd_records[fd].timer_ptr = NULL; 296 | memset(fd_records[fd].path, 0, sizeof(fd_records[fd].path)); 297 | memset(fd_records[fd].buf, 0, MAXBUFSIZE); 298 | } 299 | 300 | -------------------------------------------------------------------------------- /ev_loop.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * @author simoncheng 3 | * @data 2014.8 4 | * @version v0.1 5 | * this program is an encapsulation of the EPOLL API which is frequently 6 | * used in backend systems based on linux. 7 | * it likes a simplified libev library which only supports the events 8 | * on fds. timer and signal will be added later. 9 | ***********************************************************************/ 10 | #ifndef _EV_LOOP_H 11 | #define _EV_LOOP_H 12 | 13 | 14 | #ifdef __cplusplus 15 | extern "C" 16 | { 17 | #endif 18 | 19 | 20 | #include 21 | #include 22 | #include "global.h" 23 | //#include "min_heap.h" 24 | 25 | #define EV_TYPE __uint32_t 26 | 27 | /** 28 | * use when init fd_records in muti_threads environment 29 | */ 30 | #define LOCK(Lock) while(!__sync_bool_compare_and_swap(&Lock, 0, 1)) 31 | #define UNLOCK(Lock) Lock = 0 32 | 33 | enum { 34 | EV_READ = EPOLLIN, 35 | EV_WRITE = EPOLLOUT 36 | }; 37 | 38 | 39 | typedef struct ev_loop_t{ 40 | int epfd; 41 | int maxevent; 42 | int etmodel; 43 | //fd_record_t *fd_records; 44 | struct epoll_event *events; 45 | 46 | //timer 47 | //struct ev_timer_t **heap; 48 | void **heap; 49 | int heap_size; 50 | int heap_capacity; 51 | int timer_fd; 52 | }ev_loop_t; 53 | 54 | typedef void* (*cb_func_t) (ev_loop_t *loop, int fd, EV_TYPE events); 55 | 56 | typedef struct { 57 | int active; 58 | 59 | EV_TYPE events; 60 | cb_func_t cb_read; 61 | cb_func_t cb_write; 62 | 63 | int ffd; 64 | unsigned int write_pos; 65 | unsigned int read_pos; 66 | unsigned int total_len; 67 | char buf[MAXBUFSIZE]; 68 | int http_code; 69 | char path[256]; 70 | int keep_alive; 71 | 72 | void* timer_ptr; 73 | } fd_record_t; 74 | 75 | //muti-threads share the fd_records 76 | fd_record_t *fd_records; 77 | 78 | ev_loop_t *ev_create_loop(int maxevent, int et); 79 | int ev_register(ev_loop_t*loop, int fd, EV_TYPE events, cb_func_t cb); 80 | int ev_unregister(ev_loop_t *loop, int fd); 81 | int ev_stop(ev_loop_t *loop, int fd, EV_TYPE events); 82 | int ev_run_loop(ev_loop_t *loop); 83 | void ev_clear(int fd); 84 | 85 | int tcp_server(int port); 86 | int setnonblocking(int fd); 87 | 88 | 89 | #ifdef __cplusplus 90 | } 91 | #endif 92 | 93 | #endif -------------------------------------------------------------------------------- /exist.sh: -------------------------------------------------------------------------------- 1 | while true 2 | do 3 | pidof ./sparrow_main_d || ./sparrow_main_d & 4 | pidof ./sparrow || ./sparrow & 5 | sleep 300 6 | done 7 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "file.h" 11 | #include "global.h" 12 | #include "config.h" 13 | #include "async_log.h" 14 | 15 | 16 | 17 | static 18 | int items_cmp(const void *a, const void *b) { 19 | item_t *p1 = (item_t *)a; 20 | item_t *p2 = (item_t *)b; 21 | if (p1->dir != p2->dir) { 22 | return (p1->dir > p2->dir) ? -1 : 1; 23 | } 24 | else { 25 | return (p1->m_time > p2->m_time) ? -1 : 1; 26 | } 27 | } 28 | 29 | int isItFolder(const char *path){ 30 | struct stat s; 31 | if (0 == stat(path, &s)){ 32 | if (S_ISDIR(s.st_mode)) 33 | return 1; 34 | } 35 | return 0; 36 | } 37 | 38 | int isItFile(const char *path){ 39 | struct stat s; 40 | if (0 == stat(path, &s)){ 41 | if (S_ISREG(s.st_mode)) 42 | return 1; 43 | } 44 | return 0; 45 | } 46 | 47 | /* 48 | * get the parent path, 49 | * e.g. ./www/fdler --> ./www/ 50 | */ 51 | void get_parent_path(char *path, char *parent) { 52 | char *p; 53 | strcpy(parent, path); 54 | int len = strlen(parent); 55 | if (parent[len - 1] == '/') { 56 | parent[len - 1] = '\0'; 57 | } 58 | p = strrchr(parent, '/'); 59 | parent[p - parent + 1] = '\0'; 60 | } 61 | 62 | int block_read(const char *filename, char *buf, int max_size) { 63 | int n = 0; 64 | int fd = open(filename, O_RDONLY); 65 | if (fd == -1) { 66 | if (conf.log_enable) { 67 | log_error("open err: %s\n", filename); 68 | } 69 | else { 70 | fprintf(stderr, "error open %s\n", filename); 71 | } 72 | return -1; 73 | } 74 | while (1) { 75 | int t; 76 | t = read(fd, buf, max_size - n); 77 | if (t > 0) { 78 | n += t; 79 | } 80 | else if (t == -1) { 81 | if (conf.log_enable) { 82 | log_error("read\n"); 83 | } 84 | else { 85 | fprintf(stderr, "block read err\n"); 86 | } 87 | } 88 | else if (t == 0) { 89 | break; 90 | } 91 | } 92 | 93 | buf[n] = '\0'; 94 | close(fd); 95 | return 0; 96 | } 97 | 98 | 99 | int dir_html_maker(char *buf, char *path) { 100 | /*buf - dir_first_part - dir_second_part*/ 101 | int max_len_limit = MAXBUFSIZE - 1024 - 512; 102 | 103 | struct dirent *temp_path; 104 | struct stat s; 105 | DIR *dir; 106 | char newpath[512]; 107 | 108 | dir = opendir(path); 109 | if (dir == NULL){ 110 | //perror("opendir error"); 111 | if (conf.log_enable) { 112 | log_error("opendir error:%s\n", strerror(errno)); 113 | } 114 | else { 115 | fprintf(stderr, "block read err:%s\n", strerror(errno)); 116 | } 117 | return -1; 118 | } 119 | 120 | char prefix[64]; 121 | char *p = strrchr(path, '/'); 122 | strcpy(prefix, p + 1); 123 | int len = strlen(prefix); 124 | if (len != 0) { 125 | prefix[len] = '/'; 126 | prefix[len + 1] = '\0'; 127 | } 128 | 129 | int pos = 0; 130 | int ret; 131 | if (strcmp(path, conf.root_dir) != 0) { 132 | //add back operation 133 | if (len == 0) { 134 | ret = sprintf(buf + pos, "\n"); 135 | } 136 | else { 137 | ret = sprintf(buf + pos, "\n"); 138 | } 139 | pos += ret; 140 | } 141 | 142 | int max_item_num = conf.max_sub_item_num; 143 | //set the maximum value of items 144 | item_t * items = (item_t *)malloc(max_item_num * sizeof(item_t)); 145 | int item_cnt = 0; 146 | 147 | while ((temp_path = readdir(dir)) != NULL) { 148 | 149 | if (!strcmp(temp_path->d_name, ".") || !strcmp(temp_path->d_name, "..") || !strcmp(temp_path->d_name, ".res")) 150 | continue; 151 | 152 | if (path[strlen(path) - 1] == '/') 153 | sprintf(newpath, "%s%s", path, temp_path->d_name); 154 | else 155 | sprintf(newpath, "%s/%s", path, temp_path->d_name); 156 | 157 | lstat(newpath, &s); 158 | 159 | if (S_ISDIR(s.st_mode)) { 160 | sprintf(items[item_cnt].path, "%s", temp_path->d_name); 161 | items[item_cnt].dir = 1; 162 | items[item_cnt].size = s.st_size; 163 | items[item_cnt].m_time = s.st_mtime; 164 | item_cnt++; 165 | } 166 | else if (S_ISREG(s.st_mode)) { 167 | sprintf(items[item_cnt].path, "%s", temp_path->d_name); 168 | items[item_cnt].dir = 0; 169 | items[item_cnt].size = s.st_size; 170 | items[item_cnt].m_time = s.st_mtime; 171 | item_cnt++; 172 | } 173 | 174 | //make sure the newest item will be included. 175 | if (item_cnt >= max_item_num) { 176 | break; 177 | } 178 | } 179 | closedir(dir); 180 | 181 | qsort(items, item_cnt, sizeof(item_t), items_cmp); 182 | int i; 183 | for (i = 0; i max_len_limit) 185 | break; 186 | if (items[i].dir) { 187 | ret = sprintf(buf + pos, "\n", \ 188 | prefix, items[i].path, items[i].path); 189 | pos += ret; 190 | } 191 | else { 192 | ret = sprintf(buf + pos, "\n", \ 193 | prefix, items[i].path, items[i].path); 194 | pos += ret; 195 | } 196 | } 197 | 198 | free(items); 199 | return 0; 200 | } -------------------------------------------------------------------------------- /file.h: -------------------------------------------------------------------------------- 1 | #ifndef _FILE_H 2 | #define _FILE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" 6 | { 7 | #endif 8 | 9 | 10 | typedef struct { 11 | char path[256]; 12 | time_t m_time;//modify time 13 | int dir; 14 | int size; 15 | } item_t; 16 | 17 | int isItFolder(const char *path); 18 | 19 | int isItFile(const char *path); 20 | 21 | void get_parent_path(char *path, char *parent); 22 | 23 | int block_read(const char *filename, char *buf, int max_size); 24 | 25 | int dir_html_maker(char *buf, char *path); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | 32 | #endif -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | #ifndef _GLOBAL_H 2 | #define _GLOBAL_H 3 | 4 | #define NO_FILE_FD -1 5 | #define TCP_RECV_BUF 16*1024 6 | #define TCP_SEND_BUF 16*1024 7 | 8 | //buf size must be large enough to contain the tcp recv/send buf in ET model 9 | #define MAXBUFSIZE 64*1024 10 | 11 | 12 | #define header_200_ok "HTTP/1.1 200 OK\r\nServer: Sparrow/v0.1\r\n" \ 13 | "Accept-Charset: utf-8\r\nAccept-Language: en-US,en;q=0.5,zh-CN;q=0.5\r\n" 14 | #define header_404_not_found "HTTP/1.1 404 Not Found\r\nServer: Sparrow/v0.1\r\n" \ 15 | "Accept-Charset: utf-8\r\nAccept-Language: en-US,en;q=0.5,zh-CN;q=0.5\r\n" 16 | #define header_304_not_modified "HTTP/1.1 304 Not Modified\r\n" 17 | 18 | #define DIR_FIRST_PART "dir.part1" 19 | #define DIR_SECOND_PART "dir.part2" 20 | // 21 | //for debug 22 | //#define _DEBUG 23 | 24 | #endif 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /livechat.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE chatmessage; 2 | 3 | CREATE TABLE message ( 4 | mid BIGINT NOT NULL AUTO_INCREMENT, 5 | mtime VARCHAR(16) NOT NULL, 6 | mbody TEXT NOT NULL, 7 | PRIMARY KEY (mid) 8 | ); 9 | 10 | INSERT INTO chatmessage.message VALUES(NULL, sql, ); 11 | 12 | 13 | create user simon IDENTIFIED by '135016'; 14 | grant select, insert on chatmessage.message TO 'forchat'@'%'; 15 | -------------------------------------------------------------------------------- /mime.h: -------------------------------------------------------------------------------- 1 | #ifndef _MIME_H 2 | #define _MIME_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | char l_type[64]; 8 | char s_type[16]; 9 | } mime_type_t; 10 | 11 | int cmp(const void *a, const void *b) { 12 | return strcmp((*(mime_type_t *)a).s_type, (*(mime_type_t *)b).s_type); 13 | } 14 | 15 | int mime_type_binary_search(mime_type_t *mime_type, int n, char *target) { 16 | int left = 0; 17 | int right = n - 1; 18 | while (left <= right) { 19 | int mid = left + ((right - left) >> 1); 20 | if (strcmp(mime_type[mid].s_type, target) == 0) { 21 | return mid; 22 | } 23 | else if (strcmp(mime_type[mid].s_type, target) > 0) { 24 | right = mid - 1; 25 | } 26 | else { 27 | left = mid + 1; 28 | } 29 | } 30 | return -1; 31 | } 32 | 33 | mime_type_t mime_type[] = { 34 | { "application/envoy", "evy" }, 35 | { "application/fractals", "fif" }, 36 | { "application/futuresplash", "spl" }, 37 | { "application/hta", "hta" }, 38 | { "application/internet-property-stream", "acx" }, 39 | { "application/mac-binhex40", "hqx" }, 40 | { "application/msword", "doc" }, 41 | { "application/msword", "dot" }, 42 | { "application/octet-stream", "bin" }, 43 | { "application/octet-stream", "class" }, 44 | { "application/octet-stream", "dms" }, 45 | { "application/octet-stream", "exe" }, 46 | { "application/octet-stream", "lha" }, 47 | { "application/octet-stream", "lzh" }, 48 | { "application/oda", "oda" }, 49 | { "application/olescript", "axs" }, 50 | { "application/pdf", "pdf" }, 51 | { "application/pics-rules", "prf" }, 52 | { "application/pkcs10", "p10" }, 53 | { "application/pkix-crl", "crl" }, 54 | { "application/postscript", "ai" }, 55 | { "application/postscript", "eps" }, 56 | { "application/postscript", "ps" }, 57 | { "application/rtf", "rtf" }, 58 | { "application/set-payment-initiation", "setpay" }, 59 | { "application/set-registration-initiation", "setreg" }, 60 | { "application/vnd.ms-excel", "xla" }, 61 | { "application/vnd.ms-excel", "xlc" }, 62 | { "application/vnd.ms-excel", "xlm" }, 63 | { "application/vnd.ms-excel", "xls" }, 64 | { "application/vnd.ms-excel", "xlt" }, 65 | { "application/vnd.ms-excel", "xlw" }, 66 | { "application/vnd.ms-outlook", "msg" }, 67 | { "application/vnd.ms-pkicertstore", "sst" }, 68 | { "application/vnd.ms-pkiseccat", "cat" }, 69 | { "application/vnd.ms-pkistl", "stl" }, 70 | { "application/vnd.ms-powerpoint", "pot" }, 71 | { "application/vnd.ms-powerpoint", "pps" }, 72 | { "application/vnd.ms-powerpoint", "ppt" }, 73 | { "application/vnd.ms-project", "mpp" }, 74 | { "application/vnd.ms-works", "wcm" }, 75 | { "application/vnd.ms-works", "wdb" }, 76 | { "application/vnd.ms-works", "wks" }, 77 | { "application/vnd.ms-works", "wps" }, 78 | { "application/winhlp", "hlp" }, 79 | { "application/x-bcpio", "bcpio" }, 80 | { "application/x-cdf", "cdf" }, 81 | { "application/x-compress", "z" }, 82 | { "application/x-compressed", "tgz" }, 83 | { "application/x-cpio", "cpio" }, 84 | { "application/x-csh", "csh" }, 85 | { "application/x-director", "dcr" }, 86 | { "application/x-director", "dir" }, 87 | { "application/x-director", "dxr" }, 88 | { "application/x-dvi", "dvi" }, 89 | { "application/x-gtar", "gtar" }, 90 | { "application/x-gzip", "gz" }, 91 | { "application/x-hdf", "hdf" }, 92 | { "application/x-internet-signup", "ins" }, 93 | { "application/x-internet-signup", "isp" }, 94 | { "application/x-iphone", "iii" }, 95 | { "application/x-javascript", "js" }, 96 | { "application/x-latex", "latex" }, 97 | { "application/x-msaccess", "mdb" }, 98 | { "application/x-mscardfile", "crd" }, 99 | { "application/x-msclip", "clp" }, 100 | { "application/x-msdownload", "dll" }, 101 | { "application/x-msmediaview", "m13" }, 102 | { "application/x-msmediaview", "m14" }, 103 | { "application/x-msmediaview", "mvb" }, 104 | { "application/x-msmetafile", "wmf" }, 105 | { "application/x-msmoney", "mny" }, 106 | { "application/x-mspublisher", "pub" }, 107 | { "application/x-msschedule", "scd" }, 108 | { "application/x-msterminal", "trm" }, 109 | { "application/x-mswrite", "wri" }, 110 | { "application/x-netcdf", "cdf" }, 111 | { "application/x-netcdf", "nc" }, 112 | { "application/x-perfmon", "pma" }, 113 | { "application/x-perfmon", "pmc" }, 114 | { "application/x-perfmon", "pml" }, 115 | { "application/x-perfmon", "pmr" }, 116 | { "application/x-perfmon", "pmw" }, 117 | { "application/x-pkcs12", "p12" }, 118 | { "application/x-pkcs12", "pfx" }, 119 | { "application/x-pkcs7-certificates", "p7b" }, 120 | { "application/x-pkcs7-certificates", "spc" }, 121 | { "application/x-pkcs7-certreqresp", "p7r" }, 122 | { "application/x-pkcs7-mime", "p7c" }, 123 | { "application/x-pkcs7-mime", "p7m" }, 124 | { "application/x-pkcs7-signature", "p7s" }, 125 | { "application/x-sh", "sh" }, 126 | { "application/x-shar", "shar" }, 127 | { "application/x-shockwave-flash", "swf" }, 128 | { "application/x-stuffit", "sit" }, 129 | { "application/x-sv4cpio", "sv4cpio" }, 130 | { "application/x-sv4crc", "sv4crc" }, 131 | { "application/x-tar", "tar" }, 132 | { "application/x-tcl", "tcl" }, 133 | { "application/x-tex", "tex" }, 134 | { "application/x-texinfo", "texi" }, 135 | { "application/x-texinfo", "texinfo" }, 136 | { "application/x-troff", "roff" }, 137 | { "application/x-troff", "t" }, 138 | { "application/x-troff", "tr" }, 139 | { "application/x-troff-man", "man" }, 140 | { "application/x-troff-me", "me" }, 141 | { "application/x-troff-ms", "ms" }, 142 | { "application/x-ustar", "ustar" }, 143 | { "application/x-wais-source", "src" }, 144 | { "application/x-x509-ca-cert", "cer" }, 145 | { "application/x-x509-ca-cert", "crt" }, 146 | { "application/x-x509-ca-cert", "der" }, 147 | { "application/ynd.ms-pkipko", "pko" }, 148 | { "application/zip", "zip" }, 149 | { "audio/basic", "au" }, 150 | { "audio/basic", "snd" }, 151 | { "audio/mid", "mid" }, 152 | { "audio/mid", "rmi" }, 153 | { "audio/mpeg", "mp3" }, 154 | { "audio/x-aiff", "aif" }, 155 | { "audio/x-aiff", "aifc" }, 156 | { "audio/x-aiff", "aiff" }, 157 | { "audio/x-mpegurl", "m3u" }, 158 | { "audio/x-pn-realaudio", "ra" }, 159 | { "audio/x-pn-realaudio", "ram" }, 160 | { "audio/x-wav", "wav" }, 161 | { "image/bmp", "bmp" }, 162 | { "image/cis-cod", "cod" }, 163 | { "image/gif", "gif" }, 164 | { "image/ief", "ief" }, 165 | { "image/jpeg", "jpe" }, 166 | { "image/jpeg", "jpeg" }, 167 | { "image/jpeg", "jpg" }, 168 | { "image/png", "png" }, 169 | { "image/png", "PNG" }, 170 | { "image/pipeg", "jfif" }, 171 | { "image/svg+xml", "svg" }, 172 | { "image/tiff", "tif" }, 173 | { "image/tiff", "tiff" }, 174 | { "image/x-cmu-raster", "ras" }, 175 | { "image/x-cmx", "cmx" }, 176 | { "image/x-icon", "ico" }, 177 | { "image/x-portable-anymap", "pnm" }, 178 | { "image/x-portable-bitmap", "pbm" }, 179 | { "image/x-portable-graymap", "pgm" }, 180 | { "image/x-portable-pixmap", "ppm" }, 181 | { "image/x-rgb", "rgb" }, 182 | { "image/x-xbitmap", "xbm" }, 183 | { "image/x-xpixmap", "xpm" }, 184 | { "image/x-xwindowdump", "xwd" }, 185 | { "message/rfc822", "mht" }, 186 | { "message/rfc822", "mhtml" }, 187 | { "message/rfc822", "nws" }, 188 | { "text/css", "css" }, 189 | { "text/h323", "323" }, 190 | { "text/html", "htm" }, 191 | { "text/html", "html" }, 192 | { "text/html", "stm" }, 193 | { "text/iuls", "uls" }, 194 | { "text/plain", "bas" }, 195 | { "text/plain", "c" }, 196 | { "text/plain", "h" }, 197 | { "text/plain", "txt" }, 198 | { "text/richtext", "rtx" }, 199 | { "text/scriptlet", "sct" }, 200 | { "text/tab-separated-values", "tsv" }, 201 | { "text/webviewhtml", "htt" }, 202 | { "text/x-component", "htc" }, 203 | { "text/x-setext", "etx" }, 204 | { "text/x-vcard", "vcf" }, 205 | { "video/mpeg", "mp2" }, 206 | { "video/mpeg", "mpa" }, 207 | { "video/mpeg", "mpe" }, 208 | { "video/mpeg", "mpeg" }, 209 | { "video/mpeg", "mpg" }, 210 | { "video/mpeg", "mpv2" }, 211 | { "video/quicktime", "mov" }, 212 | { "video/quicktime", "qt" }, 213 | { "video/x-la-asf", "lsf" }, 214 | { "video/x-la-asf", "lsx" }, 215 | { "video/x-ms-asf", "asf" }, 216 | { "video/x-ms-asf", "asr" }, 217 | { "video/x-ms-asf", "asx" }, 218 | { "video/x-msvideo", "avi" }, 219 | { "video/x-sgi-movie", "movie" }, 220 | { "x-world/x-vrml", "flr" }, 221 | { "x-world/x-vrml", "vrml" }, 222 | { "x-world/x-vrml", "wrl" }, 223 | { "x-world/x-vrml", "wrz" }, 224 | { "x-world/x-vrml", "xaf" }, 225 | { "x-world/x-vrml", "xof" } 226 | }; 227 | 228 | 229 | #endif 230 | -------------------------------------------------------------------------------- /min_heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "min_heap.h" 11 | #include "ev_loop.h" 12 | 13 | 14 | #define RSHIFT(x) ((x) >> 1) 15 | #define LSHIFT(x) ((x) << 1) 16 | #define LCHILD(x) LSHIFT(x) 17 | #define RCHILD(x) (LSHIFT(x)|1) 18 | #define PARENT(x) (RSHIFT(x)) 19 | 20 | static 21 | int timer_cmp_lt(struct timespec ts1, struct timespec ts2) { 22 | if (ts1.tv_sec > ts2.tv_sec) { 23 | return 0; 24 | } 25 | else if (ts1.tv_sec == ts2.tv_sec) { 26 | if (ts1.tv_nsec > ts2.tv_nsec) { 27 | return 0; 28 | } 29 | else if (ts1.tv_nsec == ts2.tv_nsec) { 30 | return 0; 31 | } 32 | else { 33 | return 1; 34 | } 35 | } 36 | else { 37 | return 1; 38 | } 39 | } 40 | 41 | static 42 | void heap_percolate_up(ev_timer_t **heap, int pos) { 43 | ev_timer_t *timer = heap[pos]; 44 | while ((pos > 1) && (timer_cmp_lt(timer->ts, heap[PARENT(pos)]->ts))) { 45 | heap[pos] = heap[PARENT(pos)]; 46 | pos = PARENT(pos); 47 | } 48 | heap[pos] = timer; 49 | } 50 | 51 | static 52 | void heap_percolate_down(ev_timer_t **heap, int pos, int heap_size) { 53 | ev_timer_t *timer = heap[pos]; 54 | while (LCHILD(pos) <= heap_size) { 55 | int s_pos = LCHILD(pos); 56 | //right child exist and right is smaller 57 | if (s_pos + 1 <= heap_size && timer_cmp_lt(heap[s_pos + 1]->ts, heap[s_pos]->ts)) { 58 | s_pos++; 59 | } 60 | 61 | if (timer_cmp_lt(timer->ts, heap[s_pos]->ts)) { 62 | break; 63 | } 64 | heap[pos] = heap[s_pos]; 65 | pos = s_pos; 66 | } 67 | heap[pos] = timer; 68 | } 69 | 70 | static 71 | void heap_add(ev_loop_t *loop, ev_timer_t *timer) { 72 | loop->heap[++(loop->heap_size)] = timer; 73 | if (timer == NULL) { 74 | // printf("add timer,but timer == null\n"); 75 | } 76 | // printf("head_size_now:%d\n", loop->heap_size); 77 | heap_percolate_up((ev_timer_t **)(loop->heap), loop->heap_size); 78 | } 79 | 80 | static 81 | struct timespec double2timespec(double timeout) { 82 | long long int sec = (long long int)timeout; 83 | long long int nsec = (long long int)((timeout - (double)sec) * 1000000000); 84 | 85 | struct timespec ts; 86 | clock_gettime(CLOCK_MONOTONIC, &ts); 87 | ts.tv_sec += sec; 88 | ts.tv_nsec += nsec; 89 | if (ts.tv_nsec >= 1000000000) { 90 | ts.tv_nsec %= 1000000000; 91 | ts.tv_sec++; 92 | } 93 | return ts; 94 | } 95 | 96 | static 97 | ev_timer_t* heap_top(ev_timer_t **heap) { 98 | return heap[1]; 99 | } 100 | 101 | static 102 | void heap_pop(ev_loop_t *loop) { 103 | if (loop->heap_size < 1) 104 | return; 105 | free(loop->heap[1]); 106 | loop->heap[1] = loop->heap[loop->heap_size]; 107 | loop->heap[loop->heap_size] = NULL; 108 | loop->heap_size--; 109 | heap_percolate_down((ev_timer_t **)loop->heap, 1, loop->heap_size); 110 | } 111 | 112 | 113 | /********************************************************* 114 | ********************* extern **************************** 115 | *********************************************************/ 116 | 117 | //ev_timer_t ** 118 | void timer_heap_init(ev_loop_t *loop, int capacity) { 119 | loop->heap = (void **)malloc((capacity + 1)*sizeof(ev_timer_t*)); 120 | //memset(heap, 0, sizeof(heap)); 121 | int i; 122 | for (i = 0; i <= capacity; i++) { 123 | loop->heap[i] = NULL; 124 | } 125 | loop->heap_size = 0; 126 | loop->heap_capacity = capacity; 127 | 128 | 129 | if ((loop->timer_fd = timerfd_create(CLOCK_MONOTONIC, 0)) < 0) { 130 | printf("create timefd err\n"); 131 | } 132 | // printf("tfd:%d\n", loop->timer_fd); 133 | setnonblocking(loop->timer_fd); 134 | 135 | struct itimerspec newValue; 136 | bzero(&newValue, sizeof(newValue)); 137 | struct timespec ts; 138 | ts.tv_sec = 0; 139 | ts.tv_nsec = 0; 140 | newValue.it_value = ts; 141 | if (timerfd_settime(loop->timer_fd, 0, &newValue, NULL) < 0) { 142 | printf("err in settime\n"); 143 | } 144 | ev_register(loop, loop->timer_fd, EV_READ, check_timer); 145 | } 146 | 147 | 148 | void add_timer(ev_loop_t *loop, double timeout, cb_timer_t cb, 149 | uint8_t repeat, uint8_t groupid, void *ptr) { 150 | 151 | if (loop->heap_size >= loop->heap_capacity) { 152 | ev_timer_t **temp = (ev_timer_t **)malloc((2 * (loop->heap_capacity) + 1)*sizeof(ev_timer_t *)); 153 | if (temp == NULL) { 154 | fprintf(stderr, "err in add timer when malloc:%s\n", strerror(errno)); 155 | return; 156 | } 157 | int i; 158 | for (i = 0; i < 2 * (loop->heap_capacity) + 1; i++) { 159 | temp[i] = NULL; 160 | } 161 | loop->heap_capacity *= 2; 162 | for (i = 0; i <= loop->heap_size; i++) { 163 | temp[i] = loop->heap[i]; 164 | } 165 | free(loop->heap); 166 | loop->heap = (void **)temp; 167 | } 168 | int fd = (int)ptr; 169 | 170 | struct timespec ts; 171 | ts = double2timespec(timeout); 172 | 173 | ev_timer_t *timer = (ev_timer_t*)malloc(sizeof(ev_timer_t)); 174 | if (timer == NULL) { 175 | fprintf(stderr, "malloc error:%s\n", strerror(errno)); 176 | return; 177 | } 178 | timer->timeout = timeout; 179 | timer->ts = ts; 180 | timer->cb = cb; 181 | timer->fd = fd; 182 | timer->repeat = repeat; 183 | timer->groupid = groupid; 184 | 185 | fd_records[fd].timer_ptr = timer; 186 | heap_add(loop, timer); 187 | 188 | /* two special conditions which need to settime */ 189 | /* 1. first timer event */ 190 | /* 2. the newly add timer is the new heap top */ 191 | /* that means new's ts < old heap top's ts */ 192 | if (loop->heap_size == 1 || heap_top((ev_timer_t **)loop->heap) == timer) { 193 | ts = tick(loop); 194 | struct itimerspec newValue; 195 | bzero(&newValue, sizeof(newValue)); 196 | newValue.it_value = ts; 197 | 198 | if (timerfd_settime(loop->timer_fd, 0, &newValue, NULL) != 0) { 199 | fprintf(stderr, "ERROR: timerfd_settime error: %s\n", strerror(errno)); 200 | } 201 | } 202 | } 203 | 204 | 205 | struct 206 | timespec tick(ev_loop_t *loop) { 207 | //printf("tick\n"); 208 | struct timespec ts; 209 | clock_gettime(CLOCK_MONOTONIC, &ts); 210 | //time now >= heap top 211 | while (heap_top((ev_timer_t **)(loop->heap)) != NULL && !timer_cmp_lt(ts, heap_top((ev_timer_t **)(loop->heap))->ts)) { 212 | ////////////////////////////////////////// 213 | //heap != null, and delete all the cb==null timer in the head 214 | int bcontinue = 0; 215 | while (heap_top((ev_timer_t **)(loop->heap)) != NULL && heap_top((ev_timer_t **)(loop->heap))->cb == NULL) { 216 | heap_pop(loop); 217 | // printf("+++++++++(cb == null)+++++++\n"); 218 | bcontinue = 1; 219 | } 220 | if (bcontinue) { 221 | continue; 222 | } 223 | ////////////////////////////////, int *heap_size///////// 224 | (*(heap_top((ev_timer_t **)(loop->heap))->cb))(loop, heap_top((ev_timer_t **)(loop->heap))); 225 | if (!heap_top((ev_timer_t **)(loop->heap))->repeat) { 226 | heap_pop(loop); 227 | } 228 | else { 229 | //coming soon... 230 | heap_top((ev_timer_t **)(loop->heap))->ts = double2timespec(heap_top((ev_timer_t **)(loop->heap))->timeout); 231 | heap_percolate_down((ev_timer_t **)(loop->heap), 1, loop->heap_size); 232 | } 233 | /* important: update the current time, because you */ 234 | /* never know how long the callback func costs */ 235 | clock_gettime(CLOCK_MONOTONIC, &ts); 236 | } 237 | if (heap_top((ev_timer_t **)(loop->heap)) == NULL) { 238 | ts.tv_sec = 0; 239 | ts.tv_nsec = 0; 240 | return ts; 241 | } 242 | 243 | long int sec_tmp = heap_top((ev_timer_t **)loop->heap)->ts.tv_sec; 244 | long int nsec_tmp = heap_top((ev_timer_t **)loop->heap)->ts.tv_nsec; 245 | 246 | if (ts.tv_nsec > heap_top((ev_timer_t **)(loop->heap))->ts.tv_nsec) { 247 | sec_tmp--; 248 | nsec_tmp += 1000000000; 249 | } 250 | ts.tv_sec = sec_tmp - ts.tv_sec; 251 | ts.tv_nsec = nsec_tmp - ts.tv_nsec; 252 | 253 | return ts; 254 | } 255 | 256 | void* check_timer(ev_loop_t *loop, int tfd, EV_TYPE events) { 257 | uint64_t data; 258 | read(loop->timer_fd, &data, 8); 259 | 260 | struct timespec ts; 261 | ts = tick(loop); 262 | struct itimerspec newValue; 263 | bzero(&newValue, sizeof(newValue)); 264 | newValue.it_value = ts; 265 | 266 | int ret; 267 | ret = timerfd_settime(loop->timer_fd, 0, &newValue, NULL); 268 | if (ret == -1) { 269 | printf("timerfd_settime err:%s\n", strerror(errno)); 270 | } 271 | return NULL; 272 | } 273 | 274 | void delete_timer(ev_loop_t *loop, int sockfd) { 275 | ev_timer_t * timer = (ev_timer_t *)(fd_records[sockfd].timer_ptr); 276 | if (timer != NULL) { 277 | timer->cb = NULL; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /min_heap.h: -------------------------------------------------------------------------------- 1 | #ifndef _MIN_HEAP_H 2 | #define _MIN_HEAP_H 3 | 4 | 5 | #ifdef __cplusplus 6 | extern "C" 7 | { 8 | #endif 9 | 10 | 11 | #include 12 | #include 13 | #include "ev_loop.h" 14 | struct ev_timer_t; 15 | typedef void(*cb_timer_t)(ev_loop_t *loop, struct ev_timer_t *timer); 16 | 17 | 18 | typedef struct ev_timer_t{ 19 | uint8_t groupid; 20 | uint8_t repeat; 21 | double timeout; 22 | struct timespec ts; 23 | cb_timer_t cb; 24 | union { 25 | int fd; 26 | void *ptr; 27 | }; 28 | //struct ev_timer_t *next; 29 | } ev_timer_t; 30 | 31 | extern void timer_heap_init(ev_loop_t *loop, int capacity); 32 | extern void add_timer(ev_loop_t *loop, double timeout, cb_timer_t cb, uint8_t repeat, uint8_t groupid, void *ptr); 33 | extern struct timespec tick(ev_loop_t *loop); 34 | extern void* check_timer(ev_loop_t *loop, int tfd, EV_TYPE events); 35 | extern void delete_timer(ev_loop_t *loop, int sockfd); 36 | //ev_timer_t *freelist; 37 | 38 | #ifdef __cplusplus 39 | } 40 | #endif 41 | 42 | #endif -------------------------------------------------------------------------------- /performance_test/nginx_ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/nginx_ab.png -------------------------------------------------------------------------------- /performance_test/nginx_ab_online.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/nginx_ab_online.jpg -------------------------------------------------------------------------------- /performance_test/nginx_sparrow_webb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/nginx_sparrow_webb.jpg -------------------------------------------------------------------------------- /performance_test/nginx_webbench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/nginx_webbench.png -------------------------------------------------------------------------------- /performance_test/sparrow_ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/sparrow_ab.png -------------------------------------------------------------------------------- /performance_test/sparrow_ab_online.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/sparrow_ab_online.jpg -------------------------------------------------------------------------------- /performance_test/sparrow_webbench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/sparrow_webbench.png -------------------------------------------------------------------------------- /performance_test/性能测试1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/performance_test/性能测试1.PNG -------------------------------------------------------------------------------- /picohttpparser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, 3 | * Shigeo Mitsunari 4 | * 5 | * The software is licensed under either the MIT License (below) or the Perl 6 | * license. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #ifdef __SSE4_2__ 31 | # ifdef _MSC_VER 32 | # include 33 | # else 34 | # include 35 | # endif 36 | #endif 37 | #include "picohttpparser.h" 38 | 39 | /* $Id: b6ad4bc0aab735da6f932bf26bcef58961364b4a $ */ 40 | 41 | #if __GNUC__ >= 3 42 | # define likely(x) __builtin_expect(!!(x), 1) 43 | # define unlikely(x) __builtin_expect(!!(x), 0) 44 | #else 45 | # define likely(x) (x) 46 | # define unlikely(x) (x) 47 | #endif 48 | 49 | #ifdef _MSC_VER 50 | # define ALIGNED(n) _declspec(align(n)) 51 | #else 52 | # define ALIGNED(n) __attribute__((aligned(n))) 53 | #endif 54 | 55 | #define IS_PRINTABLE_ASCII(c) ((unsigned char)(c) - 040u < 0137u) 56 | 57 | #define CHECK_EOF() \ 58 | if (buf == buf_end) { \ 59 | *ret = -2; \ 60 | return NULL; \ 61 | } 62 | 63 | #define EXPECT_CHAR(ch) \ 64 | CHECK_EOF(); \ 65 | if (*buf++ != ch) { \ 66 | *ret = -1; \ 67 | return NULL; \ 68 | } 69 | 70 | #define ADVANCE_TOKEN(tok, toklen) do { \ 71 | const char* tok_start = buf; \ 72 | static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \ 73 | int found2; \ 74 | buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \ 75 | if (! found2) { \ 76 | CHECK_EOF(); \ 77 | } \ 78 | while (1) { \ 79 | if (*buf == ' ') { \ 80 | break; \ 81 | } else if (unlikely(! IS_PRINTABLE_ASCII(*buf))) { \ 82 | if ((unsigned char)*buf < '\040' || *buf == '\177') { \ 83 | *ret = -1; \ 84 | return NULL; \ 85 | } \ 86 | } \ 87 | ++buf; \ 88 | CHECK_EOF(); \ 89 | } \ 90 | tok = tok_start; \ 91 | toklen = buf - tok_start; \ 92 | } while (0) 93 | 94 | static const char* token_char_map = 95 | "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 96 | "\0\1\1\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" 97 | "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" 98 | "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" 99 | "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 100 | "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 101 | "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 102 | "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; 103 | 104 | static const char* findchar_fast(const char* buf, const char* buf_end, const char *ranges, size_t ranges_size, int* found) 105 | { 106 | *found = 0; 107 | #if __SSE4_2__ 108 | if (likely(buf_end - buf >= 16)) { 109 | __m128i ranges16 = _mm_loadu_si128((const __m128i*)ranges); 110 | 111 | size_t left = (buf_end - buf) & ~15; 112 | do { 113 | __m128i b16 = _mm_loadu_si128((void*)buf); 114 | int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); 115 | if (unlikely(r != 16)) { 116 | buf += r; 117 | *found = 1; 118 | break; 119 | } 120 | buf += 16; 121 | left -= 16; 122 | } while (likely(left != 0)); 123 | } 124 | #endif 125 | return buf; 126 | } 127 | 128 | static const char* get_token_to_eol(const char* buf, const char* buf_end, 129 | const char** token, size_t* token_len, 130 | int* ret) 131 | { 132 | const char* token_start = buf; 133 | 134 | #ifdef __SSE4_2__ 135 | static const char ranges1[] = 136 | "\0\010" 137 | /* allow HT */ 138 | "\012\037" 139 | /* allow SP and up to but not including DEL */ 140 | "\177\177" 141 | /* allow chars w. MSB set */ 142 | ; 143 | int found; 144 | buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); 145 | if (found) 146 | goto FOUND_CTL; 147 | #else 148 | /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ 149 | while (likely(buf_end - buf >= 8)) { 150 | #define DOIT() if (unlikely(! IS_PRINTABLE_ASCII(*buf))) goto NonPrintable; ++buf 151 | DOIT(); DOIT(); DOIT(); DOIT(); 152 | DOIT(); DOIT(); DOIT(); DOIT(); 153 | #undef DOIT 154 | continue; 155 | NonPrintable: 156 | if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { 157 | goto FOUND_CTL; 158 | } 159 | ++buf; 160 | } 161 | #endif 162 | for (; ; ++buf) { 163 | CHECK_EOF(); 164 | if (unlikely(! IS_PRINTABLE_ASCII(*buf))) { 165 | if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { 166 | goto FOUND_CTL; 167 | } 168 | } 169 | } 170 | FOUND_CTL: 171 | if (likely(*buf == '\015')) { 172 | ++buf; 173 | EXPECT_CHAR('\012'); 174 | *token_len = buf - 2 - token_start; 175 | } else if (*buf == '\012') { 176 | *token_len = buf - token_start; 177 | ++buf; 178 | } else { 179 | *ret = -1; 180 | return NULL; 181 | } 182 | *token = token_start; 183 | 184 | return buf; 185 | } 186 | 187 | static const char* is_complete(const char* buf, const char* buf_end, 188 | size_t last_len, int* ret) 189 | { 190 | int ret_cnt = 0; 191 | buf = last_len < 3 ? buf : buf + last_len - 3; 192 | 193 | while (1) { 194 | CHECK_EOF(); 195 | if (*buf == '\015') { 196 | ++buf; 197 | CHECK_EOF(); 198 | EXPECT_CHAR('\012'); 199 | ++ret_cnt; 200 | } else if (*buf == '\012') { 201 | ++buf; 202 | ++ret_cnt; 203 | } else { 204 | ++buf; 205 | ret_cnt = 0; 206 | } 207 | if (ret_cnt == 2) { 208 | return buf; 209 | } 210 | } 211 | 212 | *ret = -2; 213 | return NULL; 214 | } 215 | 216 | /* *_buf is always within [buf, buf_end) upon success */ 217 | static const char* parse_int(const char* buf, const char* buf_end, int* value, 218 | int* ret) 219 | { 220 | int v; 221 | CHECK_EOF(); 222 | if (! ('0' <= *buf && *buf <= '9')) { 223 | *ret = -1; 224 | return NULL; 225 | } 226 | v = 0; 227 | for (; ; ++buf) { 228 | CHECK_EOF(); 229 | if ('0' <= *buf && *buf <= '9') { 230 | v = v * 10 + *buf - '0'; 231 | } else { 232 | break; 233 | } 234 | } 235 | 236 | *value = v; 237 | return buf; 238 | } 239 | 240 | /* returned pointer is always within [buf, buf_end), or null */ 241 | static const char* parse_http_version(const char* buf, const char* buf_end, 242 | int* minor_version, int* ret) 243 | { 244 | EXPECT_CHAR('H'); EXPECT_CHAR('T'); EXPECT_CHAR('T'); EXPECT_CHAR('P'); 245 | EXPECT_CHAR('/'); EXPECT_CHAR('1'); EXPECT_CHAR('.'); 246 | return parse_int(buf, buf_end, minor_version, ret); 247 | } 248 | 249 | static const char* parse_headers(const char* buf, const char* buf_end, 250 | struct phr_header* headers, 251 | size_t* num_headers, size_t max_headers, 252 | int* ret) 253 | { 254 | for (; ; ++*num_headers) { 255 | CHECK_EOF(); 256 | if (*buf == '\015') { 257 | ++buf; 258 | EXPECT_CHAR('\012'); 259 | break; 260 | } else if (*buf == '\012') { 261 | ++buf; 262 | break; 263 | } 264 | if (*num_headers == max_headers) { 265 | *ret = -1; 266 | return NULL; 267 | } 268 | if (! (*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { 269 | if (! token_char_map[(unsigned char)*buf]) { 270 | *ret = -1; 271 | return NULL; 272 | } 273 | /* parsing name, but do not discard SP before colon, see 274 | * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ 275 | headers[*num_headers].name = buf; 276 | static const char ALIGNED(16) ranges1[] = "::\x00\037"; 277 | int found; 278 | buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); 279 | if (! found) { 280 | CHECK_EOF(); 281 | } 282 | while (1) { 283 | if (*buf == ':') { 284 | break; 285 | } else if (*buf < ' ') { 286 | *ret = -1; 287 | return NULL; 288 | } 289 | ++buf; 290 | CHECK_EOF(); 291 | } 292 | headers[*num_headers].name_len = buf - headers[*num_headers].name; 293 | ++buf; 294 | for (; ; ++buf) { 295 | CHECK_EOF(); 296 | if (! (*buf == ' ' || *buf == '\t')) { 297 | break; 298 | } 299 | } 300 | } else { 301 | headers[*num_headers].name = NULL; 302 | headers[*num_headers].name_len = 0; 303 | } 304 | if ((buf = get_token_to_eol(buf, buf_end, &headers[*num_headers].value, 305 | &headers[*num_headers].value_len, ret)) 306 | == NULL) { 307 | return NULL; 308 | } 309 | } 310 | return buf; 311 | } 312 | 313 | static const char* parse_request(const char* buf, const char* buf_end, 314 | const char** method, size_t* method_len, 315 | const char** path, size_t* path_len, 316 | int* minor_version, struct phr_header* headers, 317 | size_t* num_headers, size_t max_headers, 318 | int* ret) 319 | { 320 | /* skip first empty line (some clients add CRLF after POST content) */ 321 | CHECK_EOF(); 322 | if (*buf == '\015') { 323 | ++buf; 324 | EXPECT_CHAR('\012'); 325 | } else if (*buf == '\012') { 326 | ++buf; 327 | } 328 | 329 | /* parse request line */ 330 | ADVANCE_TOKEN(*method, *method_len); 331 | ++buf; 332 | ADVANCE_TOKEN(*path, *path_len); 333 | ++buf; 334 | if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { 335 | return NULL; 336 | } 337 | if (*buf == '\015') { 338 | ++buf; 339 | EXPECT_CHAR('\012'); 340 | } else if (*buf == '\012') { 341 | ++buf; 342 | } else { 343 | *ret = -1; 344 | return NULL; 345 | } 346 | 347 | return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); 348 | } 349 | 350 | int phr_parse_request(const char* buf_start, size_t len, const char** method, 351 | size_t* method_len, const char** path, size_t* path_len, 352 | int* minor_version, struct phr_header* headers, 353 | size_t* num_headers, size_t last_len) 354 | { 355 | const char * buf = buf_start, * buf_end = buf_start + len; 356 | size_t max_headers = *num_headers; 357 | int r; 358 | 359 | *method = NULL; 360 | *method_len = 0; 361 | *path = NULL; 362 | *path_len = 0; 363 | *minor_version = -1; 364 | *num_headers = 0; 365 | 366 | /* if last_len != 0, check if the request is complete (a fast countermeasure 367 | againt slowloris */ 368 | if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { 369 | return r; 370 | } 371 | 372 | if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, 373 | minor_version, headers, num_headers, max_headers, 374 | &r)) 375 | == NULL) { 376 | return r; 377 | } 378 | 379 | return (int)(buf - buf_start); 380 | } 381 | 382 | static const char* parse_response(const char* buf, const char* buf_end, 383 | int* minor_version, int* status, 384 | const char** msg, size_t* msg_len, 385 | struct phr_header* headers, 386 | size_t* num_headers, size_t max_headers, 387 | int* ret) 388 | { 389 | /* parse "HTTP/1.x" */ 390 | if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { 391 | return NULL; 392 | } 393 | /* skip space */ 394 | if (*buf++ != ' ') { 395 | *ret = -1; 396 | return NULL; 397 | } 398 | /* parse status code */ 399 | if ((buf = parse_int(buf, buf_end, status, ret)) == NULL) { 400 | return NULL; 401 | } 402 | /* skip space */ 403 | if (*buf++ != ' ') { 404 | *ret = -1; 405 | return NULL; 406 | } 407 | /* get message */ 408 | if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { 409 | return NULL; 410 | } 411 | 412 | return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); 413 | } 414 | 415 | int phr_parse_response(const char* buf_start, size_t len, int* minor_version, 416 | int* status, const char** msg, size_t* msg_len, 417 | struct phr_header* headers, size_t* num_headers, 418 | size_t last_len) 419 | { 420 | const char * buf = buf_start, * buf_end = buf + len; 421 | size_t max_headers = *num_headers; 422 | int r; 423 | 424 | *minor_version = -1; 425 | *status = 0; 426 | *msg = NULL; 427 | *msg_len = 0; 428 | *num_headers = 0; 429 | 430 | /* if last_len != 0, check if the response is complete (a fast countermeasure 431 | against slowloris */ 432 | if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { 433 | return r; 434 | } 435 | 436 | if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, 437 | headers, num_headers, max_headers, &r)) 438 | == NULL) { 439 | return r; 440 | } 441 | 442 | return (int)(buf - buf_start); 443 | } 444 | 445 | int phr_parse_headers(const char* buf_start, size_t len, 446 | struct phr_header* headers, size_t* num_headers, 447 | size_t last_len) 448 | { 449 | const char* buf = buf_start, * buf_end = buf + len; 450 | size_t max_headers = *num_headers; 451 | int r; 452 | 453 | *num_headers = 0; 454 | 455 | /* if last_len != 0, check if the response is complete (a fast countermeasure 456 | against slowloris */ 457 | if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { 458 | return r; 459 | } 460 | 461 | if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) 462 | == NULL) { 463 | return r; 464 | } 465 | 466 | return (int)(buf - buf_start); 467 | } 468 | 469 | enum { 470 | CHUNKED_IN_CHUNK_SIZE, 471 | CHUNKED_IN_CHUNK_EXT, 472 | CHUNKED_IN_CHUNK_DATA, 473 | CHUNKED_IN_CHUNK_CRLF, 474 | CHUNKED_IN_TRAILERS_LINE_HEAD, 475 | CHUNKED_IN_TRAILERS_LINE_MIDDLE 476 | }; 477 | 478 | static int decode_hex(int ch) 479 | { 480 | if ('0' <= ch && ch <= '9') { 481 | return ch - '0'; 482 | } else if ('A' <= ch && ch <= 'F') { 483 | return ch - 'A' + 0xa; 484 | } else if ('a' <= ch && ch <= 'f') { 485 | return ch - 'a' + 0xa; 486 | } else { 487 | return -1; 488 | } 489 | } 490 | 491 | ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, 492 | size_t *_bufsz) 493 | { 494 | size_t dst = 0, src = 0, bufsz = *_bufsz; 495 | ssize_t ret = -2; /* incomplete */ 496 | 497 | while (1) { 498 | switch (decoder->_state) { 499 | case CHUNKED_IN_CHUNK_SIZE: 500 | for (; ; ++src) { 501 | int v; 502 | if (src == bufsz) 503 | goto Exit; 504 | if ((v = decode_hex(buf[src])) == -1) { 505 | if (decoder->_hex_count == 0) { 506 | ret = -1; 507 | goto Exit; 508 | } 509 | break; 510 | } 511 | if (decoder->_hex_count == sizeof(size_t) * 2) { 512 | ret = -1; 513 | goto Exit; 514 | } 515 | decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; 516 | ++decoder->_hex_count; 517 | } 518 | decoder->_hex_count = 0; 519 | decoder->_state = CHUNKED_IN_CHUNK_EXT; 520 | /* fallthru */ 521 | case CHUNKED_IN_CHUNK_EXT: 522 | /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ 523 | for (; ; ++src) { 524 | if (src == bufsz) 525 | goto Exit; 526 | if (buf[src] == '\012') 527 | break; 528 | } 529 | ++src; 530 | if (decoder->bytes_left_in_chunk == 0) { 531 | if (decoder->consume_trailer) { 532 | decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; 533 | break; 534 | } else { 535 | goto Complete; 536 | } 537 | } 538 | decoder->_state = CHUNKED_IN_CHUNK_DATA; 539 | /* fallthru */ 540 | case CHUNKED_IN_CHUNK_DATA: 541 | { 542 | size_t avail = bufsz - src; 543 | if (avail < decoder->bytes_left_in_chunk) { 544 | if (dst != src) 545 | memmove(buf + dst, buf + src, avail); 546 | src += avail; 547 | dst += avail; 548 | decoder->bytes_left_in_chunk -= avail; 549 | goto Exit; 550 | } 551 | if (dst != src) 552 | memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); 553 | src += decoder->bytes_left_in_chunk; 554 | dst += decoder->bytes_left_in_chunk; 555 | decoder->bytes_left_in_chunk = 0; 556 | decoder->_state = CHUNKED_IN_CHUNK_CRLF; 557 | } 558 | /* fallthru */ 559 | case CHUNKED_IN_CHUNK_CRLF: 560 | for (; ; ++src) { 561 | if (src == bufsz) 562 | goto Exit; 563 | if (buf[src] != '\015') 564 | break; 565 | } 566 | if (buf[src] != '\012') { 567 | ret = -1; 568 | goto Exit; 569 | } 570 | ++src; 571 | decoder->_state = CHUNKED_IN_CHUNK_SIZE; 572 | break; 573 | case CHUNKED_IN_TRAILERS_LINE_HEAD: 574 | for (; ; ++src) { 575 | if (src == bufsz) 576 | goto Exit; 577 | if (buf[src] != '\015') 578 | break; 579 | } 580 | if (buf[src++] == '\012') 581 | goto Complete; 582 | decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; 583 | /* fallthru */ 584 | case CHUNKED_IN_TRAILERS_LINE_MIDDLE: 585 | for (; ; ++src) { 586 | if (src == bufsz) 587 | goto Exit; 588 | if (buf[src] == '\012') 589 | break; 590 | } 591 | ++src; 592 | decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; 593 | break; 594 | default: 595 | assert(!"decoder is corrupt"); 596 | } 597 | } 598 | 599 | Complete: 600 | ret = bufsz - src; 601 | Exit: 602 | if (dst != src) 603 | memmove(buf + dst, buf + src, bufsz - src); 604 | *_bufsz = dst; 605 | return ret; 606 | } 607 | 608 | #undef CHECK_EOF 609 | #undef EXPECT_CHAR 610 | #undef ADVANCE_TOKEN 611 | -------------------------------------------------------------------------------- /picohttpparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, 3 | * Shigeo Mitsunari 4 | * 5 | * The software is licensed under either the MIT License (below) or the Perl 6 | * license. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #ifndef picohttpparser_h 28 | #define picohttpparser_h 29 | 30 | #include 31 | 32 | #ifdef _MSC_VER 33 | # define ssize_t intptr_t 34 | #endif 35 | 36 | /* $Id: 73c4d5cc45f4024fa6439010fa7370df474834aa $ */ 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | /* contains name and value of a header (name == NULL if is a continuing line 43 | * of a multiline header */ 44 | struct phr_header { 45 | const char* name; 46 | size_t name_len; 47 | const char* value; 48 | size_t value_len; 49 | }; 50 | 51 | /* returns number of bytes consumed if successful, -2 if request is partial, 52 | * -1 if failed */ 53 | int phr_parse_request(const char* buf, size_t len, const char** method, 54 | size_t* method_len, const char** path, 55 | size_t* path_len, int* minor_version, 56 | struct phr_header* headers, size_t* num_headers, 57 | size_t last_len); 58 | 59 | /* ditto */ 60 | int phr_parse_response(const char* _buf, size_t len, int *minor_version, 61 | int *status, const char **msg, size_t *msg_len, 62 | struct phr_header* headers, size_t* num_headers, 63 | size_t last_len); 64 | 65 | /* ditto */ 66 | int phr_parse_headers(const char* buf, size_t len, struct phr_header* headers, 67 | size_t* num_headers, size_t last_len); 68 | 69 | /* should be zero-filled before start */ 70 | struct phr_chunked_decoder { 71 | size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ 72 | char consume_trailer; /* if trailing headers should be consumed */ 73 | char _hex_count; 74 | char _state; 75 | }; 76 | 77 | /* the function rewrites the buffer given as (buf, bufsz) removing the chunked- 78 | * encoding headers. When the function returns without an error, bufsz is 79 | * updated to the length of the decoded data available. Applications should 80 | * repeatedly call the function while it returns -2 (incomplete) every time 81 | * supplying newly arrived data. If the end of the chunked-encoded data is 82 | * found, the function returns a non-negative number indicating the number of 83 | * octets left undecoded at the tail of the supplied buffer. Returns -1 on 84 | * error. 85 | */ 86 | ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, 87 | size_t *bufsz); 88 | 89 | #ifdef __cplusplus 90 | } 91 | #endif 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /sparrow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/sparrow -------------------------------------------------------------------------------- /sparrow.c: -------------------------------------------------------------------------------- 1 | #include "async_log.h" 2 | #include "ev_loop.h" 3 | #include "global.h" 4 | #include "thread_manage.h" 5 | #include "sparrow.h" 6 | #include "mime.h" 7 | #include "file.h" 8 | #include "config.h" 9 | #include "url.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "util.h" 31 | #include "min_heap.h" 32 | #include "cJSON.h" 33 | #include "picohttpparser.h" 34 | 35 | char *work_dir; 36 | 37 | int listen_sock; 38 | ev_loop_t * listen_loop = NULL; 39 | 40 | char dir_first_part[1024]; 41 | char dir_second_part[512]; 42 | 43 | unsigned long long int round_robin_num = 0; 44 | 45 | static 46 | int str_equal(char* str, size_t len, const char* t) 47 | { 48 | return memcmp(str_2_lower(str, len), t, len) == 0; 49 | } 50 | 51 | int main() 52 | { 53 | srand(time(0)); 54 | read_config(&conf); 55 | 56 | work_dir = conf.root_dir; 57 | 58 | if (conf.log_enable) { 59 | log_init(conf.log_time_out, conf.log_level); 60 | } 61 | 62 | //for dir 63 | block_read(DIR_FIRST_PART, dir_first_part, sizeof(dir_first_part)); 64 | block_read(DIR_SECOND_PART, dir_second_part, sizeof(dir_second_part)); 65 | 66 | //mime type 67 | qsort(mime_type, sizeof(mime_type) / sizeof(mime_type_t), sizeof(mime_type_t), cmp); 68 | 69 | 70 | worker_threads_init(conf.worker_thread_num); 71 | 72 | signal(SIGPIPE, SIG_IGN); 73 | listen_sock = tcp_server(conf.listen_port); 74 | 75 | if (listen_sock == -1) { 76 | if (conf.log_enable) { 77 | log_error("listen err\n"); 78 | } 79 | else { 80 | fprintf(stderr, "listen error\n"); 81 | } 82 | return -1; 83 | } 84 | /* TODO: ET model will raise a fatal problem. */ 85 | /* connection exceeds max connection, listen_sock will not be triggerd */ 86 | listen_loop = ev_create_loop(conf.max_conn, 0); 87 | 88 | int ret = ev_register(listen_loop, listen_sock, EV_READ, accept_sock); 89 | if (ret == -1) { 90 | if (conf.log_enable) { 91 | log_error("register err\n"); 92 | } 93 | else { 94 | fprintf(stderr, "register error\n"); 95 | } 96 | ev_clear(listen_sock); 97 | return -1; 98 | } 99 | 100 | if (conf.log_enable) { 101 | log_info("sparrow started successfully!\n"); 102 | } 103 | else { 104 | fprintf(stdout, "sparrow started successfully!\n"); 105 | } 106 | ev_run_loop(listen_loop); 107 | 108 | int i; 109 | for (i = 0; i < conf.worker_thread_num; i++)//等待线程全部执行完 110 | pthread_join(worker_threads_queue[i], NULL); 111 | 112 | return 0; 113 | } 114 | 115 | 116 | //for debug 117 | void dbg_printf(const char *str) { 118 | #ifdef _DEBUG 119 | printf("[%s:%d] %s\n", __FILE__, __LINE__, str); 120 | #endif 121 | } 122 | 123 | static 124 | void process_timeout(ev_loop_t *loop, ev_timer_t *timer) { 125 | if (fd_records[timer->fd].active) { 126 | ev_unregister(loop, timer->fd); 127 | } 128 | close(timer->fd); 129 | } 130 | 131 | static 132 | void safe_close(ev_loop_t *loop, int sockfd) { 133 | delete_timer(loop, sockfd); 134 | ev_unregister(loop, sockfd); 135 | close(sockfd); 136 | } 137 | 138 | 139 | void *accept_sock(ev_loop_t *loop, int sock, EV_TYPE events) { 140 | struct sockaddr_in client_sock; 141 | socklen_t len = sizeof(client_sock); 142 | int conn_fd; 143 | while ((conn_fd = accept(sock, (struct sockaddr *)&client_sock, &len)) > 0) { 144 | /*limit the connection*/ 145 | if (conn_fd >= conf.max_conn) { 146 | if (conf.log_enable) { 147 | log_warn("Too many connections come, exceeds the maximum num of the configuration!\n"); 148 | } 149 | else { 150 | fprintf(stderr, "Warn: too many connections come, exceeds the maximum num of the configuration!\n"); 151 | } 152 | close(conn_fd); 153 | return NULL; 154 | } 155 | 156 | setnonblocking(conn_fd); 157 | 158 | if (conf.log_enable) { 159 | log_info("Got connection from ip:%s, port:%d, conn_fd:%d\n", inet_ntoa(client_sock.sin_addr), ntohs(client_sock.sin_port), conn_fd); 160 | } 161 | else { 162 | //printf("ip:%s, conn_fd:%d\n", inet_ntoa(client_sock.sin_addr),conn_fd); 163 | } 164 | int reuse = 1; 165 | setsockopt(conn_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); 166 | 167 | // 接收缓冲区 168 | int nRecvBuf = TCP_RECV_BUF; 169 | setsockopt(conn_fd, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int)); 170 | //发送缓冲区 171 | int nSendBuf = TCP_SEND_BUF; 172 | setsockopt(conn_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&nSendBuf, sizeof(int)); 173 | 174 | // if(conf.use_tcp_cork) { 175 | // int on = 1; 176 | // setsockopt(sock, SOL_TCP, TCP_CORK, &on, sizeof(on)); 177 | // } 178 | int ret = ev_register(ev_loop_queue[(round_robin_num++) % conf.worker_thread_num/*rand()%conf.worker_thread_num*/], conn_fd, EV_READ, read_http); 179 | if (ret == -1) { 180 | if (conf.log_enable) { 181 | log_error("register err\n"); 182 | } 183 | else { 184 | fprintf(stderr, "ev register err in accept_sock()\n"); 185 | } 186 | //ev_unregister(loop, conn_fd); 187 | //close(conn_fd); 188 | return NULL; 189 | } 190 | } 191 | if (-1 == conn_fd) { 192 | if (errno != EAGAIN && errno != ECONNABORTED \ 193 | && errno != EPROTO && errno != EINTR) {//排除accpet到队列完这种返回,这只是读完了,并不是错误 194 | if (conf.log_enable) { 195 | log_error("accpet err\n"); 196 | } 197 | else { 198 | fprintf(stderr, "1. accpet err: %s\n", strerror(errno)); 199 | } 200 | return NULL; 201 | } 202 | //fprintf(stderr, "2. accpet err: %s\n", strerror(errno)); 203 | } 204 | 205 | return NULL; 206 | } 207 | void *read_http(ev_loop_t *loop, int sock, EV_TYPE events) { 208 | if (sock > conf.max_conn) { 209 | safe_close(loop, sock); 210 | return NULL; 211 | } 212 | 213 | char *buf = fd_records[sock].buf; 214 | int read_complete = 0; /*判断是否读取完 \r\n\r\n*/ 215 | 216 | //////////////////////////////////////////////////////////////////// 217 | const char *method, *path; 218 | int pret, minor_version; 219 | struct phr_header headers[100]; 220 | size_t method_len, path_len, num_headers; 221 | ssize_t nread; 222 | //////////////////////////////////////////////////////////////////// 223 | struct timeval curtv; 224 | gettimeofday(&curtv, NULL); 225 | unsigned int last_sec = curtv.tv_sec; 226 | while (1) { 227 | nread = read(sock, buf + fd_records[sock].read_pos, MAXBUFSIZE - fd_records[sock].read_pos); 228 | if (nread > 0) { 229 | read_complete = (strstr(buf + fd_records[sock].read_pos, "\n\n") != 0) 230 | || (strstr(buf + fd_records[sock].read_pos, "\r\n\r\n") != 0); 231 | 232 | fd_records[sock].read_pos += nread; 233 | //判断是否读取完 \r\n\r\n 234 | if (read_complete) { 235 | break; 236 | } 237 | //问题又来了,如果对方迟迟都没有发\r\n\r\n那么岂不是要一直等下去? 238 | //加了一个时间判断,如果8s还没有处理完,就关掉 239 | gettimeofday(&curtv, NULL); 240 | if (curtv.tv_sec - last_sec >= 8) { 241 | safe_close(loop, sock); 242 | if (conf.log_enable) { 243 | log_error("read http header timeout\n"); 244 | } 245 | else { 246 | fprintf(stderr, "read http header timeout\n"); 247 | } 248 | return NULL; 249 | } 250 | } 251 | else if (nread == -1) { 252 | if (errno != EAGAIN) { 253 | if (conf.log_enable) { 254 | log_error("read http err, %s\n", strerror(errno)); 255 | } 256 | else { 257 | fprintf(stderr, "read http err, %s\n", strerror(errno)); 258 | } 259 | //是否需要处理timer呢???? 260 | safe_close(loop, sock); 261 | return NULL; 262 | } 263 | else { 264 | //这个地方应该是返回,等下一次触发继续读 265 | return NULL; 266 | //break;//read complete 267 | } 268 | } 269 | else if (nread == 0) { 270 | dbg_printf("client quit!"); 271 | safe_close(loop, sock); 272 | return NULL; 273 | } 274 | } 275 | 276 | 277 | int header_length = fd_records[sock].read_pos; 278 | fd_records[sock].buf[header_length] = '\0'; 279 | 280 | num_headers = sizeof(headers) / sizeof(headers[0]); 281 | pret = phr_parse_request(buf, header_length, &method, &method_len, &path, &path_len, 282 | &minor_version, headers, &num_headers, 0); 283 | if (pret < 0) { 284 | safe_close(loop, sock); 285 | return NULL; 286 | } 287 | int i; 288 | #ifdef _DEBUG 289 | printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); 290 | printf("request is %d bytes long\n", pret); 291 | printf("method is %.*s\n", (int)method_len, method); 292 | printf("path is %.*s\n", (int)path_len, path); 293 | printf("HTTP version is 1.%d\n", minor_version); 294 | printf("headers:\n"); 295 | for (i = 0; i != (int)num_headers; ++i) { 296 | printf("%.*s: %.*s\n", (int)headers[i].name_len, headers[i].name, 297 | (int)headers[i].value_len, headers[i].value); 298 | } 299 | printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); 300 | #endif 301 | 302 | if (conf.log_enable) { 303 | //log the info 304 | log_info("+++++++++++++++++++++++ REQUEST START +++++++++++++++++++++\n"); 305 | log_info("request is %d bytes long\n", pret); 306 | log_info("method is %.*s\n", (int)method_len, method); 307 | log_info("path is %.*s\n", (int)path_len, path); 308 | log_info("HTTP version is 1.%d\n", minor_version); 309 | log_info("headers:\n"); 310 | for (i = 0; i != (int)num_headers; ++i) { 311 | log_info("%.*s: %.*s\n", (int)headers[i].name_len, headers[i].name, 312 | (int)headers[i].value_len, headers[i].value); 313 | } 314 | log_info("++++++++++++++++++++++++ REQUEST END ++++++++++++++++++++++\n"); 315 | } 316 | 317 | if (read_complete) { 318 | //目前暂时只支持Get,排除非GET外的其他请求 319 | if (!str_equal((char *)method, method_len, "get")) { 320 | safe_close(loop, sock); 321 | return NULL; 322 | } 323 | 324 | //the last modified time of file cached in browser side 325 | const char *last_mtime = NULL; 326 | size_t last_mtime_len = 0; 327 | 328 | //处理Keep-alive和modified time 329 | for (i = 0; i != (int)num_headers; ++i) { 330 | if (str_equal((char *)headers[i].name, headers[i].name_len, "connection") && 331 | str_equal((char *)headers[i].value, headers[i].value_len, "keep-alive")) { 332 | 333 | fd_records[sock].keep_alive = 1; 334 | dbg_printf("keep_alive connection!"); 335 | } 336 | if (str_equal((char *)headers[i].name, headers[i].name_len, "if-modified-since")) { 337 | last_mtime = headers[i].value; 338 | last_mtime_len = headers[i].value_len; 339 | dbg_printf("find last_modified_time!"); 340 | } 341 | } 342 | 343 | const char *action; 344 | int action_len; 345 | KV kvs[0]; // not used 346 | int kvs_num = sizeof(kvs) / sizeof(kvs[0]); 347 | 348 | int p_ret = parse_get_path(path, path_len, &action, &action_len, kvs, &kvs_num); 349 | if (p_ret == -1) { 350 | safe_close(loop, sock); 351 | return NULL; 352 | } 353 | 354 | char *prefix = work_dir; 355 | char filename[1024 + 1 + strlen(work_dir)];//full path 356 | memset(filename, 0, sizeof(filename)); 357 | 358 | if (memcmp(action, "/", action_len) == 0) { 359 | snprintf(filename, 512, "%s/%s", prefix, conf.def_home_page); 360 | } 361 | else { 362 | /*limit the filename len*/ 363 | snprintf(filename, 512, "%s%.*s", prefix, action_len, action); 364 | } 365 | #ifdef _DEBUG 366 | char dbg_msg[1024 + 1 + strlen(work_dir)]; 367 | memset(dbg_msg, 0, sizeof(dbg_msg)); 368 | snprintf(dbg_msg, 512, "prefix:%s", prefix); 369 | 370 | dbg_printf(dbg_msg); 371 | 372 | memset(dbg_msg, 0, sizeof(dbg_msg)); 373 | snprintf(dbg_msg, 512, "fileFullPath:%s", filename); 374 | dbg_printf(dbg_msg); 375 | #endif 376 | /*********************************************************************** 377 | *decode, 解决url中包含中文/特殊字符"&%.."被转码的问题 378 | *这一步可以加到decode特定path的内容的时候用到 379 | * 直接加到http_parse_path()中去 380 | **********************************************************************/ 381 | //url_decode(path, path_len); 382 | 383 | 384 | struct stat filestat; 385 | time_t last_modified_time; 386 | 387 | /*********************************************************************** 388 | *decode, 解决url中包含中文/特殊字符"&%.."被转码的问题 389 | *这一步可以加到decode特定path的内容的时候用到 390 | * 直接加到http_parse_path()中去 391 | **********************************************************************/ 392 | url_decode(filename, strlen(filename)); 393 | int s = lstat(filename, &filestat); 394 | if (-1 == s) { 395 | fd_records[sock].http_code = 404; 396 | } 397 | else if (S_ISDIR(filestat.st_mode)) { 398 | fd_records[sock].http_code = DIR_CODE; 399 | } 400 | 401 | 402 | if (fd_records[sock].http_code == 404) { 403 | memset(filename, 0, sizeof(filename)); 404 | snprintf(filename, 512, "%s/%s", prefix, "404.html"); 405 | lstat(filename, &filestat); 406 | } 407 | 408 | int fd = -1; 409 | if (fd_records[sock].http_code != DIR_CODE) { 410 | 411 | last_modified_time = filestat.st_mtime; 412 | //process 304 not modified 413 | if (last_mtime != NULL) { 414 | /*先转lower case*/ 415 | char *file_last_mtime = str_2_lower(ctime(&last_modified_time), strlen(ctime(&last_modified_time))); 416 | #ifdef _DEBUG 417 | //ctime() end with '\n\0'; 418 | printf("file_last_mtime::%.*s::\n", last_mtime_len, file_last_mtime); 419 | printf("reqt_last_mtime::%.*s::\n", last_mtime_len, last_mtime); 420 | #endif 421 | if (str_equal((char *)last_mtime, last_mtime_len, file_last_mtime)) { 422 | fd_records[sock].http_code = 304; 423 | dbg_printf("304 not modified!"); 424 | } 425 | } 426 | 427 | /* 428 | * 确定304之前不需要打开文件,如果不是304,那么这时才打开文件。[bug:304的时候导致ffd没能关闭] 429 | */ 430 | if (fd_records[sock].http_code != 304) { 431 | fd = open(filename, O_RDONLY); 432 | 433 | if (fd == -1) { 434 | if (conf.log_enable) { 435 | log_error("can not open file:%s\n", filename); 436 | } 437 | else { 438 | fprintf(stderr, "can not open file:%s, because:%s\n", filename, strerror(errno)); 439 | } 440 | safe_close(loop, sock); 441 | return NULL; 442 | } 443 | 444 | fd_records[sock].ffd = fd; 445 | } 446 | else { 447 | fd_records[sock].ffd = NO_FILE_FD; 448 | } 449 | } 450 | 451 | char content_type[64]; 452 | memset(content_type, 0, sizeof(content_type)); 453 | 454 | if (fd_records[sock].http_code != 304) { 455 | 456 | //fd_records[sock].ffd = fd; 457 | fd_records[sock].read_pos = 0; 458 | 459 | if (fd_records[sock].http_code != DIR_CODE) { 460 | fd_records[sock].total_len = (int)filestat.st_size; 461 | setnonblocking(fd); 462 | } 463 | strcpy(fd_records[sock].path, filename); 464 | 465 | 466 | char *suffix = strrchr(filename + 1, '.'); 467 | /*the type of dir must be "text/html"*/ 468 | if (fd_records[sock].http_code != DIR_CODE) { 469 | if (suffix == NULL) { 470 | if (fd_records[sock].http_code == DIR_CODE) 471 | strcpy(content_type, "text/html"); 472 | else 473 | strcpy(content_type, "text/plain"); 474 | } 475 | else { 476 | int index = mime_type_binary_search(mime_type, sizeof(mime_type) / sizeof(mime_type_t), suffix + 1); 477 | if (index == -1) { 478 | strcpy(content_type, "text/plain"); 479 | } 480 | else { 481 | strcpy(content_type, mime_type[index].l_type); 482 | } 483 | } 484 | } 485 | else { 486 | strcpy(content_type, "text/html"); 487 | } 488 | } 489 | 490 | int header_length = 0; 491 | 492 | if (fd_records[sock].http_code == 200 || fd_records[sock].http_code == DIR_CODE) { 493 | 494 | if (fd_records[sock].http_code == 200) { 495 | /*because ctime() retuan a string end with '\n', so no more '\n' is add below*/ 496 | header_length = sprintf(fd_records[sock].buf, \ 497 | "%sContent-Type: %s\r\nContent-Length: %d\r\nLast-Modified:%sCache-Control: max-age=%d\r\n", \ 498 | header_200_ok, content_type, (int)filestat.st_size, ctime(&last_modified_time), conf.cache_control_max_age); 499 | 500 | } 501 | else { /*folder*/ 502 | header_length = sprintf(fd_records[sock].buf, \ 503 | "%sContent-Type: %s\r\nCache-Control: max-age=%d\r\n", \ 504 | header_200_ok, content_type, conf.cache_control_max_age); 505 | } 506 | } 507 | else if (fd_records[sock].http_code == 404) { 508 | header_length = sprintf(fd_records[sock].buf, \ 509 | "%sContent-Type: %s\r\nContent-Length: %d\r\n", \ 510 | header_404_not_found, content_type, (int)filestat.st_size); 511 | } 512 | else if (fd_records[sock].http_code == 304) { 513 | header_length = sprintf(fd_records[sock].buf, "%s\r\n", header_304_not_modified); 514 | } 515 | if (fd_records[sock].keep_alive && fd_records[sock].http_code != 304) { 516 | header_length += sprintf(fd_records[sock].buf + header_length, "%s\r\n\r\n", "Connection: Keep-Alive"); 517 | } 518 | else { 519 | header_length += sprintf(fd_records[sock].buf + header_length, "%s\r\n\r\n", "Connection: Close"); 520 | } 521 | fd_records[sock].buf[header_length] = '\0'; 522 | 523 | int ret; 524 | ////////////////////////////////////////////////////////////// 525 | // stop the read 526 | // thus, not support the http pipeline... 527 | ///////////////////////////////////////////////////////////// 528 | ret = ev_stop(loop, sock, EV_READ); 529 | if (ret == -1) { 530 | safe_close(loop, sock); 531 | return NULL; 532 | } 533 | 534 | ret = ev_register(loop, sock, EV_WRITE, write_http_header); 535 | if (ret == -1) { 536 | if (conf.log_enable) { 537 | log_error("ev register err in read_http()\n"); 538 | } 539 | else { 540 | fprintf(stderr, "ev register err in read_http()\n"); 541 | } 542 | delete_timer(loop, sock); 543 | return NULL; 544 | } 545 | } 546 | else { 547 | safe_close(loop, sock); 548 | return NULL; 549 | } 550 | return NULL; 551 | } 552 | 553 | 554 | 555 | void *write_http_header(ev_loop_t *loop, int sockfd, EV_TYPE events){ 556 | if (sockfd > conf.max_conn) { 557 | safe_close(loop, sockfd); 558 | return NULL; 559 | } 560 | 561 | // if(conf.use_tcp_cork) { 562 | // int on = 1; 563 | // setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); 564 | // } 565 | while (1) { 566 | int nwrite; 567 | nwrite = write(sockfd, fd_records[sockfd].buf + fd_records[sockfd].write_pos, strlen(fd_records[sockfd].buf) - fd_records[sockfd].write_pos); 568 | if (nwrite > 0) { 569 | fd_records[sockfd].write_pos += nwrite; 570 | } 571 | if (nwrite == -1) { 572 | if (errno != EAGAIN) 573 | { 574 | if (conf.log_enable) { 575 | log_error("%s\n", strerror(errno)); 576 | } 577 | else { 578 | fprintf(stderr, "%s\n", strerror(errno)); 579 | } 580 | safe_close(loop, sockfd); 581 | return NULL; 582 | } 583 | break; 584 | } 585 | 586 | if (fd_records[sockfd].write_pos == strlen(fd_records[sockfd].buf)) { 587 | fd_records[sockfd].write_pos = 0; 588 | 589 | if (fd_records[sockfd].http_code == 304) { 590 | safe_close(loop, sockfd); 591 | return NULL; 592 | } 593 | if (fd_records[sockfd].http_code == 2048) { 594 | safe_close(loop, sockfd); 595 | dbg_printf("==============2048===============\n"); 596 | return NULL; 597 | } 598 | 599 | ev_stop(loop, sockfd, EV_WRITE); 600 | if (fd_records[sockfd].http_code != DIR_CODE) { 601 | int ret = ev_register(loop, sockfd, EV_WRITE, write_http_body); 602 | if (ret == -1) { 603 | if (conf.log_enable) { 604 | log_error("ev register err\n"); 605 | } 606 | else { 607 | fprintf(stderr, "ev register err in write_http_header1()\n"); 608 | } 609 | delete_timer(loop, sockfd); 610 | return NULL; 611 | } 612 | } 613 | else if (fd_records[sockfd].http_code == DIR_CODE) { 614 | 615 | int r = process_dir_html(fd_records[sockfd].path, sockfd); 616 | if (r == -1) { 617 | if (conf.log_enable) { 618 | log_error("err when making dir html\n"); 619 | } 620 | else { 621 | fprintf(stderr, "err when making dir html\n"); 622 | } 623 | safe_close(loop, sockfd); 624 | return NULL; 625 | } 626 | int ret = ev_register(loop, sockfd, EV_WRITE, write_dir_html); 627 | if (ret == -1) { 628 | if (conf.log_enable) { 629 | log_error("ev register err in write_http_header2()\n"); 630 | } 631 | else { 632 | fprintf(stderr, "ev register err in write_http_header2()\n"); 633 | } 634 | delete_timer(loop, sockfd); 635 | return NULL; 636 | } 637 | } 638 | return NULL; 639 | } 640 | } 641 | return NULL; 642 | } 643 | 644 | void *write_dir_html(ev_loop_t *loop, int sockfd, EV_TYPE events) { 645 | if (sockfd > conf.max_conn) { 646 | safe_close(loop, sockfd); 647 | return NULL; 648 | } 649 | 650 | // if(conf.use_tcp_cork) { 651 | // int on = 1; 652 | // setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); 653 | // } 654 | while (1) { 655 | int nwrite; 656 | nwrite = write(sockfd, fd_records[sockfd].buf + fd_records[sockfd].write_pos, strlen(fd_records[sockfd].buf) - fd_records[sockfd].write_pos); 657 | fd_records[sockfd].write_pos += nwrite; 658 | if (nwrite == -1) { 659 | if (errno != EAGAIN) 660 | { 661 | if (conf.log_enable) { 662 | log_error("write dir html%s\n", strerror(errno)); 663 | } 664 | else { 665 | fprintf(stderr, "write dir html%s\n", strerror(errno)); 666 | } 667 | safe_close(loop, sockfd); 668 | return NULL; 669 | } 670 | break; 671 | } 672 | 673 | if (fd_records[sockfd].write_pos == strlen(fd_records[sockfd].buf)) { 674 | fd_records[sockfd].write_pos = 0; 675 | safe_close(loop, sockfd); 676 | return NULL; 677 | } 678 | } 679 | 680 | return NULL; 681 | } 682 | 683 | /** 684 | * return content_length 685 | */ 686 | int process_dir_html(char *path, int sockfd) { 687 | memset(fd_records[sockfd].buf, 0, sizeof(fd_records[sockfd].buf)); 688 | 689 | int n = sprintf(fd_records[sockfd].buf, "%s", dir_first_part); 690 | int ret = dir_html_maker(fd_records[sockfd].buf + n, path); 691 | if (ret == -1) 692 | return -1; 693 | sprintf(fd_records[sockfd].buf + strlen(fd_records[sockfd].buf), "%s", dir_second_part); 694 | 695 | return strlen(fd_records[sockfd].buf); 696 | } 697 | 698 | 699 | void *write_http_body(ev_loop_t *loop, int sockfd, EV_TYPE events) { 700 | if (sockfd > conf.max_conn) { 701 | safe_close(loop, sockfd); 702 | return NULL; 703 | } 704 | int ffd = fd_records[sockfd].ffd; 705 | while (1) { 706 | off_t offset = fd_records[sockfd].read_pos; 707 | /* 708 | * bug: sendfile is a block operation, file-->network, non-blocking on network, but blocking on file(read io) 709 | * if read() is blocked, sendfile() cannot return. 710 | */ 711 | int s = sendfile(sockfd, ffd, &offset, fd_records[sockfd].total_len - fd_records[sockfd].read_pos); 712 | fd_records[sockfd].read_pos = offset; 713 | if (s == -1) { 714 | if (errno != EAGAIN) { 715 | if (conf.log_enable) { 716 | log_error("%s\n", strerror(errno)); 717 | } 718 | else { 719 | fprintf(stderr, "sendfile:%s\n", strerror(errno)); 720 | } 721 | safe_close(loop, sockfd); 722 | return NULL; 723 | } 724 | else { 725 | // 写入到缓冲区已满了 726 | break; 727 | } 728 | } 729 | if (fd_records[sockfd].read_pos == fd_records[sockfd].total_len) { 730 | int keep_alive = fd_records[sockfd].keep_alive; 731 | ev_timer_t *timer = (ev_timer_t *)fd_records[sockfd].timer_ptr; 732 | int flag = 0; 733 | if (timer != NULL) { 734 | timer->cb = NULL; 735 | flag = 1; 736 | } 737 | /***************************************************************** 738 | * 明显的一个需要改进的地方,不需要每次都先unregister 然后在重新注册, 739 | * 不过改动容易出问题,先保留 740 | *****************************************************************/ 741 | ev_unregister(loop, sockfd); 742 | if (keep_alive) { 743 | ev_register(loop, sockfd, EV_READ, read_http); 744 | if (!flag) { 745 | add_timer(loop, 40, process_timeout, 0, 0, (void*)sockfd); 746 | } 747 | else { 748 | dbg_printf("reuse sockfd!"); 749 | add_timer(loop, 40, process_timeout, 0, 0, (void*)sockfd); 750 | } 751 | } 752 | else { 753 | close(sockfd); 754 | } 755 | 756 | return NULL; 757 | } 758 | } 759 | return NULL; 760 | } 761 | 762 | -------------------------------------------------------------------------------- /sparrow.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPARROW_H 2 | #define _SPARROW_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" 6 | { 7 | #endif 8 | 9 | 10 | #include "ev_loop.h" 11 | 12 | #define DIR_CODE 1024 13 | 14 | void *accept_sock(ev_loop_t *loop, int sock, EV_TYPE events); 15 | void *read_http(ev_loop_t *loop, int sock, EV_TYPE events); 16 | void *write_http_header(ev_loop_t *loop, int sockfd, EV_TYPE events); 17 | void *write_http_body(ev_loop_t *loop, int sockfd, EV_TYPE events); 18 | void *process_dir(ev_loop_t *loop, int sockfd, EV_TYPE events); 19 | int process_dir_html(char *path, int sockfd); 20 | void *write_dir_html(ev_loop_t *loop, int sockfd, EV_TYPE events); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif -------------------------------------------------------------------------------- /sparrow_main_r: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/sparrow_main_r -------------------------------------------------------------------------------- /task.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE task ( 2 | task_id BIGINT NOT NULL AUTO_INCREMENT, 3 | task_user_name VARCHAR(20) NOT NULL, 4 | task_create_time VARCHAR(16) NOT NULL, 5 | task_delete_time VARCHAR(16), 6 | task_finish_time VARCHAR(16), 7 | task_status INT NOT NULL, 8 | task_content TEXT NOT NULL, 9 | 10 | PRIMARY KEY (task_id) 11 | ); 12 | 13 | //task_status 14 | //1-->not finish 15 | //2-->finish 16 | //3-->delete 17 | 18 | CREATE INDEX IDX_task_create_time on task(task_create_time); 19 | 20 | 21 | insert into task values(NULL, 'simon', '12345678900', '12345678900', 22 | '12345678900', 1, 'for test'); 23 | 24 | -------------------------------------------------------------------------------- /thread_manage.c: -------------------------------------------------------------------------------- 1 | #include "ev_loop.h" 2 | #include "async_log.h" 3 | #include "thread_manage.h" 4 | #include "global.h" 5 | #include "sparrow.h" 6 | #include "config.h" 7 | 8 | #include "min_heap.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | 16 | static 17 | void * worker_threads_entrance(void *arg) { 18 | ev_loop_t *loop = (ev_loop_t *)arg; 19 | if (loop == NULL) { 20 | if (conf.log_enable) { 21 | log_error("loop is empty\n"); 22 | } 23 | else { 24 | fprintf(stderr, "loop is empty\n"); 25 | } 26 | } 27 | ev_run_loop(loop); 28 | return NULL; 29 | } 30 | void worker_threads_destroy() { 31 | 32 | } 33 | 34 | int worker_threads_init(int thread_num) { 35 | //log_info("enter worker_init..."); 36 | worker_threads_queue = (pthread_t *)malloc(thread_num * sizeof(pthread_t)); 37 | ev_loop_queue = (ev_loop_t **)malloc(thread_num * sizeof(ev_loop_t*)); 38 | 39 | int i, ret; 40 | for (i = 0; i < thread_num; i++) { 41 | 42 | ev_loop_queue[i] = ev_create_loop(conf.max_conn, conf.use_epoll_et); 43 | timer_heap_init(ev_loop_queue[i], conf.max_conn); 44 | 45 | ret = pthread_create(&(worker_threads_queue[i]), NULL, worker_threads_entrance, (void *)ev_loop_queue[i]); 46 | if (ret < 0) { 47 | if (conf.log_enable) { 48 | log_error("thread init create err\n"); 49 | } 50 | else { 51 | fprintf(stderr, "thread init create err\n"); 52 | } 53 | worker_threads_destroy(); 54 | return -1; 55 | } 56 | } 57 | return 0; 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /thread_manage.h: -------------------------------------------------------------------------------- 1 | #ifndef _THREAD_MANAGE_H 2 | #define _THREAD_MANAGE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" 6 | { 7 | #endif 8 | 9 | #include "ev_loop.h" 10 | #include 11 | 12 | pthread_t *worker_threads_queue; 13 | ev_loop_t **ev_loop_queue; 14 | 15 | 16 | 17 | int worker_threads_init(int thread_num); 18 | /**/ 19 | void worker_threads_destroy(); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /tool.sh: -------------------------------------------------------------------------------- 1 | # the following are some useful shell cmd to analyse the log or the network 2 | 3 | echo "-------------------------------------\n" 4 | # for log, find the different ip addresses and the numbers it visited the website 5 | cat log/$(date +%F).log | awk 'BEGIN{pv=0;uv=0} /ip:/{++s[$10];}END {for(a in s) {print a, s[a];uv++; pv=s[a]+pv;} printf "\nUV:%d, PV:%d\n", uv, pv;}' 6 | 7 | # for network 8 | echo "------------------------------------\n" 9 | netstat -n | awk '/^tcp/ {++s[$NF];} END {for(a in s) print a, s[a];}' 10 | echo "------------------------------------\n" 11 | -------------------------------------------------------------------------------- /url.c: -------------------------------------------------------------------------------- 1 | /******************************************************************* 2 | * url编码和解码,解决url中包含中文的问题. 3 | * 参考博客:http://blog.csdn.net/langeldep/article/details/6264058 4 | * 本人做了少量的修改. 5 | *******************************************************************/ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "url.h" 13 | 14 | static unsigned char hexchars[] = "0123456789ABCDEF"; 15 | 16 | static 17 | int htoi(char *s) { 18 | int value; 19 | int c; 20 | 21 | c = ((unsigned char *)s)[0]; 22 | if (isupper(c)) 23 | c = tolower(c); 24 | value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; 25 | 26 | c = ((unsigned char *)s)[1]; 27 | if (isupper(c)) 28 | c = tolower(c); 29 | value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10; 30 | 31 | return (value); 32 | } 33 | 34 | 35 | char *url_encode(char const *s, int len, int *new_length) { 36 | register unsigned char c; 37 | unsigned char *to, *start; 38 | unsigned char const *from, *end; 39 | 40 | from = (unsigned char *)s; 41 | end = (unsigned char *)s + len; 42 | start = to = (unsigned char *)calloc(1, 3 * len + 1); 43 | 44 | while (from < end) { 45 | c = *from++; 46 | 47 | if (c == ' ') { 48 | *to++ = '+'; 49 | } 50 | else if ((c < '0' && c != '-' && c != '.') || 51 | (c < 'A' && c > '9') || 52 | (c > 'Z' && c < 'a' && c != '_') || 53 | (c > 'z')) { 54 | to[0] = '%'; 55 | to[1] = hexchars[c >> 4]; 56 | to[2] = hexchars[c & 15]; 57 | to += 3; 58 | } 59 | else { 60 | *to++ = c; 61 | } 62 | } 63 | *to = 0; 64 | if (new_length) { 65 | *new_length = to - start; 66 | } 67 | return (char *)start; 68 | } 69 | 70 | 71 | int url_decode(char *str, int len) { 72 | char *dest = str; 73 | char *data = str; 74 | 75 | while (len--) { 76 | if (*data == '+') { 77 | *dest = ' '; 78 | } 79 | else if (*data == '%' && len >= 2 && isxdigit((int)*(data + 1)) && isxdigit((int)*(data + 2))) { 80 | *dest = (char)htoi(data + 1); 81 | data += 2; 82 | len -= 2; 83 | } 84 | else { 85 | *dest = *data; 86 | } 87 | data++; 88 | dest++; 89 | } 90 | *dest = '\0'; 91 | return dest - str; 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /url.h: -------------------------------------------------------------------------------- 1 | /******************************************************************* 2 | * url编码和解码,解决url中包含中文的问题. 3 | * 参考博客:http://blog.csdn.net/langeldep/article/details/6264058 4 | * 本人做了少量的修改. 5 | *******************************************************************/ 6 | 7 | #ifndef _URL_H 8 | #define _URL_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | int url_decode(char *str, int len); 15 | char *url_encode(char const *s, int len, int *new_length); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif /* URL_H */ 22 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct { 5 | const char *key; 6 | int key_len; 7 | const char *value; 8 | int value_len; 9 | } KV; 10 | 11 | static 12 | const char * strnchr(const char *str, const char target, int n) { 13 | if (str == NULL || n == 0) { 14 | return NULL; 15 | } 16 | 17 | int i; 18 | for (i = 0; i < n; i++) { 19 | if (*(str + i) == target) { 20 | return str + i; 21 | } 22 | } 23 | return NULL; 24 | } 25 | 26 | 27 | int parse_get_path(const char *path, int path_len, /*input*/ 28 | const char **action, int *action_len, KV *kvs, int *kvs_num) {/*output*/ 29 | const char *p = strnchr(path, '?', path_len); 30 | /*not find the '?'*/ 31 | if (p == NULL) { 32 | *action = path; 33 | *action_len = path_len; 34 | *kvs_num = 0; 35 | } 36 | else { 37 | *action = path; 38 | *action_len = p - path; 39 | 40 | int cnt = 0; /*kv count*/ 41 | const char *p1 = p; 42 | const char *p2; 43 | const char *p3; 44 | int end = 0; 45 | 46 | while (1) { 47 | if (*kvs_num <= cnt) {/*tha array kvs' len is less than b*/ 48 | break; 49 | } 50 | p2 = strnchr(p1 + 1, '=', path_len - (p1 + 1 - path)); 51 | if (p2 == NULL) { 52 | *kvs_num = cnt; 53 | return -1; 54 | } 55 | p3 = strnchr(p2 + 1, '&', path_len - (p2 + 1 - path)); 56 | if (p3 == NULL) { 57 | p3 = path + path_len; 58 | end = 1; 59 | } 60 | kvs[cnt].key = p1 + 1; 61 | kvs[cnt].key_len = p2 - p1 - 1; 62 | kvs[cnt].value = p2 + 1; 63 | kvs[cnt].value_len = p3 - p2 - 1; 64 | cnt++; 65 | 66 | if (!end) { 67 | p1 = p3; 68 | } 69 | else { 70 | break; 71 | } 72 | 73 | } 74 | *kvs_num = cnt; 75 | } 76 | return 0; 77 | } 78 | 79 | char *str_2_lower(char *str, int len) { 80 | int i; 81 | for (i = 0; i < len; i++) { 82 | str[i] = tolower(str[i]); 83 | } 84 | return str; 85 | } -------------------------------------------------------------------------------- /www/.res/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/404.jpg -------------------------------------------------------------------------------- /www/.res/back.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/back.ico -------------------------------------------------------------------------------- /www/.res/dir.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family:Verdana, Arial, Helvetica, sans-serif; 3 | font-size:9pt; 4 | background: #ffffff; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | img { border: none; } 10 | 11 | a { 12 | color: navy; 13 | } 14 | 15 | .footer { 16 | text-align: right; 17 | border-radius: 10px; 18 | margin-top: 0.5em; 19 | padding: 0.5em 1em 0.5em; 20 | font-size: 16px; 21 | background:#8B8378; 22 | } 23 | .header{ 24 | text-align: right; 25 | border-radius: 10px; 26 | margin-top: 1em; 27 | padding: 0.5em 1em 0.5em; 28 | font-size: 90%; 29 | background: #8B8378; 30 | } 31 | 32 | .mainframe { 33 | margin-left: 0.5em; 34 | margin-right: 0.5em; 35 | } 36 | 37 | .rev { 38 | margin-right: 3px; 39 | padding-left: 3px; 40 | text-align: left; 41 | font-size: 120%; 42 | } 43 | 44 | .dir a { 45 | text-decoration: none; 46 | color: black; 47 | display: block; 48 | 49 | } 50 | 51 | .dir img { vertical-align: middle } 52 | 53 | .file a { 54 | text-decoration: none; 55 | color: black; 56 | display: block; 57 | } 58 | 59 | .file { 60 | margin: 3px; 61 | padding: 3px; 62 | background: #F8F8FF; 63 | border-radius: 10px; 64 | 65 | } 66 | 67 | .file img { vertical-align: middle } 68 | 69 | .file:hover { 70 | margin: 3px; 71 | padding: 3px; 72 | background: #DBDBDB; 73 | } 74 | 75 | .dir { 76 | margin: 3px; 77 | padding: 3px; 78 | background: rgb(90%,90%,90%); 79 | border-radius: 10px; 80 | } 81 | 82 | .dir:hover { 83 | margin: 3px; 84 | padding: 3px; 85 | background: #DBDBDB; 86 | } -------------------------------------------------------------------------------- /www/.res/dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/dir.png -------------------------------------------------------------------------------- /www/.res/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/favicon.ico -------------------------------------------------------------------------------- /www/.res/file.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/file.ico -------------------------------------------------------------------------------- /www/.res/github24.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/github24.ico -------------------------------------------------------------------------------- /www/.res/home.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/home.ico -------------------------------------------------------------------------------- /www/.res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/logo.png -------------------------------------------------------------------------------- /www/.res/weibo24.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codercheng/sparrow/885939b991b8acb196351d1127568958bd609d1c/www/.res/weibo24.ico -------------------------------------------------------------------------------- /www/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Welcome to Sparrow !

4 |

.. A new && full version one is coming soon ..

5 | 6 | --------------------------------------------------------------------------------