libevent源码分析:http-server例子
http-server例子是libevent提供的一个简单web服务器,实现了对静态网页的处理功能。
1 /* 2 * gcc -g -o http-server http-server.c -levent 3 */ 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 8 #include <sys/types.h> 9 #include <sys/stat.h> 10 #include <sys/socket.h> 11 #include <signal.h> 12 #include <fcntl.h> 13 #include <unistd.h> 14 #include <dirent.h> 15 #include <errno.h> 16 17 #include <event2/event.h> 18 #include <event2/http.h> 19 #include <event2/buffer.h> 20 #include <event2/util.h> 21 #include <event2/keyvalq_struct.h> 22 23 #include <netinet/in.h> 24 #include <arpa/inet.h> 25 26 char uri_root[512]; 27 28 static const struct table_entry { 29 const char *extension; 30 const char *content_type; 31 } content_type_table[] = { 32 { "txt", "text/plain" }, 33 { "c", "text/plain" }, 34 { "h", "text/plain" }, 35 { "html", "text/html" }, 36 { "htm", "text/html" }, 37 { "css", "text/css" }, 38 { "gif", "image/gif" }, 39 { "jpg", "image/jpg" }, 40 { "jpeg", "image/jpeg" }, 41 { "png", "image/png" }, 42 { "pdf", "application/pdf" }, 43 { "ps", "application/postscript" }, 44 { NULL, NULL }, 45 }; 46 47 /* Try to guess a good content-type for 'path' */ 48 const char* guess_content_type(const char *path) 49 { 50 const char *last_period, *extension; 51 const struct table_entry *ent; 52 last_period = strrchr(path, '.'); 53 if (!last_period || strchr(last_period, '/')) 54 { 55 goto not_found; 56 } 57 58 extension = last_period + 1; 59 for (ent = &content_type_table[0]; ent->extension; ++ent) 60 { 61 if (!evutil_ascii_strcasecmp(ent->extension, extension)) 62 { 63 return ent->content_type; 64 } 65 } 66 67 not_found: 68 return "application/misc"; 69 } 70 71 /* Callbase used for the /dump URI, and for every non-get request: 72 ** dumps all information to stdout and gives base a trivial 200 ok */ 73 void dump_request_cb(struct evhttp_request *req, void *arg) 74 { 75 const char *cmdtype; 76 struct evkeyvalq *headers; 77 struct evkeyval *header; 78 struct evbuffer *buf; 79 80 switch (evhttp_request_get_command(req)) 81 { 82 case EVHTTP_REQ_GET: 83 cmdtype = "GET"; 84 break; 85 case EVHTTP_REQ_POST: 86 cmdtype = "POST"; 87 break; 88 case EVHTTP_REQ_HEAD: 89 cmdtype = "HEAD"; 90 break; 91 case EVHTTP_REQ_PUT: 92 cmdtype = "PUT"; 93 break; 94 case EVHTTP_REQ_DELETE: 95 cmdtype = "DELETE"; 96 break; 97 case EVHTTP_REQ_OPTIONS: 98 cmdtype = "OPTIONS"; 99 break; 100 case EVHTTP_REQ_TRACE: 101 cmdtype = "TRACE"; 102 break; 103 case EVHTTP_REQ_CONNECT: 104 break; 105 case EVHTTP_REQ_PATCH: 106 cmdtype = "PATCH"; 107 break; 108 default: 109 cmdtype = "unknown"; 110 break; 111 } 112 113 printf("Received a %s request for %s\nHeader:\n", cmdtype, evhttp_request_get_uri(req)); 114 115 headers = evhttp_request_get_input_headers(req); 116 for (header = headers->tqh_first; header; header = header->next.tqe_next) 117 { 118 printf(" %s: %s\n", header->key, header->value); 119 } 120 121 buf = evhttp_request_get_input_buffer(req); 122 puts("Input data: <<<<"); 123 while (evbuffer_get_length(buf)) 124 { 125 int n; 126 char cbuf[128]; 127 n = evbuffer_remove(buf, cbuf, sizeof(cbuf)); 128 if (n > 0) 129 { 130 (void)fwrite(cbuf, 1, n, stdout); 131 } 132 } 133 puts(">>>"); 134 135 evhttp_send_reply(req, 200, "ok", NULL); 136 } 137 138 /* This callback gets invoked when we get and http request than doesn't match 139 * any other callback. Like any evhttp server callback, it has a simple job: 140 * it must eventually call evhttp_send_error() or evhttp_send_reply(). 141 */ 142 void send_document_cb(struct evhttp_request *req, void *arg) 143 { 144 struct evbuffer *evb = NULL; 145 const char *docroot = arg; 146 const char *uri = evhttp_request_get_uri(req); 147 struct evhttp_uri *decoded = NULL; 148 const char *path; 149 char *decoded_path; 150 char *whole_path = NULL; 151 size_t len; 152 int fd = -1; 153 struct stat st; 154 155 if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) 156 { 157 dump_request_cb(req, arg); 158 return; 159 } 160 161 printf("Got a GET request for <%s>\n", uri); 162 163 /* Decode the URI */ 164 decoded = evhttp_uri_parse(uri); 165 if (!decoded) 166 { 167 printf("It's not a good URI, Sneding BADREQUEST\n"); 168 evhttp_send_error(req, HTTP_BADREQUEST, 0); 169 return; 170 } 171 172 /* Let's see what path the user asked for. */ 173 path = evhttp_uri_get_path(decoded); 174 if (!path) 175 { 176 path = "/"; 177 } 178 179 /* We need to decode it, to see what path the user really wanted */ 180 decoded_path = evhttp_uridecode(path, 0, NULL); 181 if (decoded_path == NULL) 182 { 183 goto err; 184 } 185 186 /* Don't allow any ".."'s in the path, to avoid exposing stuff outside 187 * of the docroot. This test is both overzealous and underzealous: 188 * it forbids aceptable paths like "/this/one..here", but it doesn't 189 * do anything to prevent symlink following. */ 190 if (strstr(decoded_path, "..")) 191 { 192 goto err; 193 } 194 195 len = strlen(decoded_path) + strlen(docroot) + 2; 196 if (!(whole_path = malloc(len))) 197 { 198 perror("malloc"); 199 goto err; 200 } 201 evutil_snprintf(whole_path, len, "%s/%s", docroot, decoded_path); 202 203 if (stat(whole_path, &st) < 0) 204 { 205 goto err; 206 } 207 208 /* This holds the content we're sending */ 209 evb = evbuffer_new(); 210 211 if (S_ISDIR(st.st_mode)) 212 { 213 /* If it's a directory, read the comments and make a little index page */ 214 DIR *d; 215 struct dirent *ent; 216 const char *trailing_slash = ""; 217 218 if (!strlen(path) || path[strlen(path) - 1] != '/') 219 { 220 trailing_slash = "/"; 221 } 222 223 if (!(d = opendir(whole_path))) 224 { 225 goto err; 226 } 227 228 evbuffer_add_printf(evb, 229 "<!DOCTYPE html>\n" 230 "<html>\n " 231 " <head>\n" 232 " <meta charset='utf-8'>\n" 233 " <title>%s</title>\n" 234 " <base href='%s%s'>\n" 235 " </head>\n" 236 " <body>\n" 237 " <h1>%s</h1>\n" 238 " <ul>\n", 239 decoded_path, /* xxx html-escape this. */ 240 path, /* xxx html-escape this? */ 241 trailing_slash, 242 decoded_path /* xx html-escape this */ 243 ); 244 245 while ((ent = readdir(d))) 246 { 247 const char *name = ent->d_name; 248 evbuffer_add_printf(evb, 249 " <li><a href=\"%s\">%s</a>\n", name, name); 250 } 251 252 evbuffer_add_printf(evb, "</ul></body></html>\n"); 253 closedir(d); 254 evhttp_add_header(evhttp_request_get_output_headers(req), 255 "Content-Type", "text/html"); 256 } 257 else 258 { 259 /* Otherwise it's a file; and it to the buffer to get send via sendfile */ 260 const char *type = guess_content_type(decoded_path); 261 if ((fd = open(whole_path, O_RDONLY)) < 0) 262 { 263 perror("open"); 264 goto err; 265 } 266 267 if (fstat(fd, &st) < 0) 268 { 269 /* Make sure the length still matches, now that we opened the file :/ */ 270 perror("fstat"); 271 goto err; 272 } 273 evhttp_add_header(evhttp_request_get_output_headers(req), 274 "Content-Type", type); 275 evbuffer_add_file(evb, fd, 0, st.st_size); 276 } 277 278 evhttp_send_reply(req, 200, "OK", evb); 279 goto done; 280 281 err: 282 evhttp_send_error(req, 404, "Document was not found"); 283 if (fd >= 0) 284 { 285 close(fd); 286 } 287 288 done: 289 if (decoded) 290 { 291 evhttp_uri_free(decoded); 292 } 293 294 if (decoded_path) 295 { 296 free(decoded_path); 297 } 298 299 if (whole_path) 300 { 301 free(whole_path); 302 } 303 304 if (evb) 305 { 306 evbuffer_free(evb); 307 } 308 } 309 310 void syntax(void) 311 { 312 fprintf(stdout, "Syntax: http-server <docroot>\n"); 313 } 314 315 int main(int argc, char **argv) 316 { 317 struct event_base *base; 318 struct evhttp *http; 319 struct evhttp_bound_socket *handle; 320 int port = 0; 321 322 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) 323 { 324 printf("signal error, errno[%d], error[%s]", errno, strerror(errno)); 325 return -1; 326 } 327 328 if (argc < 2) 329 { 330 syntax(); 331 return -1; 332 } 333 334 base = event_base_new(); 335 if (!base) 336 { 337 printf("Couldn't create an event_base:exiting\n"); 338 return -1; 339 } 340 341 /* Create a new http oject to handle request */ 342 http = evhttp_new(base); 343 if (!http) 344 { 345 printf("Couldn't create evhttp.Exiting\n"); 346 return -1; 347 } 348 349 /* The /dump URI will dump all requests to stdout and say 200 ok */ 350 evhttp_set_cb(http, "/dump", dump_request_cb, NULL); 351 352 /* We want to accept arbitrary requests, so we need to set a "generic" cb 353 * We can also add callbacks for specific paths */ 354 evhttp_set_gencb(http, send_document_cb, argv[1]); 355 356 /* Now we teel the evhttp what port to listen on */ 357 handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port); 358 if (!handle) 359 { 360 printf("Couldn't bind to port[%d], exiting\n", port); 361 return -1; 362 } 363 364 { 365 /* Extract and display the address we're listening on. */ 366 struct sockaddr_storage ss; 367 evutil_socket_t fd; 368 ev_socklen_t socklen = sizeof(ss); 369 char addrbuf[128]; 370 void *inaddr; 371 const char *addr; 372 int got_port = -1; 373 fd = evhttp_bound_socket_get_fd(handle); 374 memset(&ss, 0, sizeof(ss)); 375 if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) 376 { 377 perror("getsockname() failed"); 378 return -1; 379 } 380 381 if (ss.ss_family == AF_INET) 382 { 383 got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port); 384 inaddr = &((struct sockaddr_in*)&ss)->sin_addr; 385 } 386 else if (ss.ss_family == AF_INET6) 387 { 388 got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port); 389 inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; 390 } 391 else 392 { 393 printf("Weird address family\n"); 394 return 1; 395 } 396 397 addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, sizeof(addrbuf)); 398 if (addr) 399 { 400 printf("Listening on %s:%d\n", addr, got_port); 401 evutil_snprintf(uri_root, sizeof(uri_root), "http://%s:%d", addr, got_port); 402 } 403 else 404 { 405 printf("evutil_inet_ntop failed\n"); 406 return -1; 407 } 408 } 409 event_base_dispatch(base); 410 411 return 0; 412 }
下面就通过分析这个例子来理解evhttp对象的使用与实现:
1、首先介绍一个这段代码里面的几个函数及其作用:
1)guess_content_type:传入请求的路径,返回文件类型(根据请求资源的后缀名返回响应的MIME类型)
2)dump_requese_cb:这个函数是当uri为/dump时的回调,操作是打印全部的请求信息。
3)send_document_cb:这个函数是通用uri的回调函数,就是将请求的资源发送给客户端(浏览器)。
4)syntax:打印用法的函数
5)main:主函数
2、调用event_base_new函数得到一个event base对象。
3、调用evhttp_new函数得到一个evhttp对象。
4、调用evhttp_set_cb、evthttp_set_gencb设置回调函数和通用回调函数。
5、调用evhttp_bind_socket_with_handle函数设置监听端口。
6、打印监听端口信息。
7、调用event_base_dispatch进入事件循环。
这里使用了一个新的类evhttp,这个也是对基本函数更高层次的封装,方便编写http相关的程序,关于这个类会在后面详细的分析,这里略过。
到这里就分析完http-server了,可以发现使用libevent提供的函数来编写一个http-server服务器是多么的简单。