首页 > 代码库 > libevent源码分析:http-server例子
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‘t189 * 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=http://www.mamicode.com/‘%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=http://www.mamicode.com/"%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 else258 {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" cb353 * 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 else392 {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 else404 {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服务器是多么的简单。
libevent源码分析:http-server例子
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。