📄 libhttp.c
字号:
#include <sys/socket.h>#include <sys/types.h>#include <sys/time.h>#include <event.h>#include <unistd.h>#include <fcntl.h>#include <time.h>#include <strings.h>#include <string.h>#include <stdlib.h>#include <stdio.h>#include <stdarg.h>#include <glib/glist.h>#include "libhttp.h"//#define DEBUG#ifdef DEBUG#define Dprintf printf#else#define Dprintf( a... )#endifint http_get_cmd(char *cmd) { if (strcasecmp("GET", cmd) == 0) return HC_CMD_GET; if (strcasecmp("POST", cmd) == 0) return HC_CMD_POST; if (strcasecmp("HEAD", cmd) == 0) return HC_CMD_HEAD; return HC_CMD_UNKNOWN;}/* Split line into string elements seperated by space or tab */static int split_string_by_space(char *input, char **elem, int max) { int no; char *i=input; for(no=0;no<max;) { /* Skip space or tab */ while(*i == ' ' || *i == '\t' || *i == '\r' || *i == '\n') *i++=0x0; if(*i == 0x0) break; /* Store pointer */ elem[no++]=i; /* Skip non space */ while(*i != ' ' && *i != '\t' && *i != 0x0) i++; /* End of String ? */ if(*i == 0x0) break; } return no;}/* * Parse a string like HTTP/1.0 or HTTP/1.1 and return * the protocol and version * * * HP_UNKNOWN - Unknown protocol * HP_HTTP - Seems to be HTTP without/with broken/with unknown version * HP_HTTP10 - HTTP/1.0 * HP_HTTP11 - HTTP/1.1 * */static int http_parse_proto(char *pstr) { char *pver, *dotptr, *endptr; int major, minor; /* Check if we are dealing with HTTP */ if (strncasecmp(pstr, "HTTP", 4) != 0) return HP_UNKNOWN; pver=strchr(pstr, '/'); if (!pver) return HP_HTTP; major=strtol(++pver, &dotptr, 10); /* 1.0 or 1.1 */ if (*dotptr != '.') return HP_HTTP; minor=strtol(++dotptr, &endptr, 10); /* String should end after [major].[minor] */ if (*endptr != 0x0) return HP_HTTP; Dprintf("%s: major %d minor %d\n", __FUNCTION__, major, minor); if (major == 1 && minor == 0) return HP_HTTP10; if (major == 1 && minor == 1) return HP_HTTP11; return HP_HTTP;}/* * Read the first line - PUT/GET/POST etc ... * * GET <url> HTTP/<version> * */static int http_parse_command_url(struct http_connection *hc, struct evbuffer *input) { char *p=(char *) EVBUFFER_DATA(input); char *elem[10]; int no; no=split_string_by_space(p, elem, 10); /* Need "CMD URL PROTO/VERSION" which makes it 3 parms */ if (no != 3) return 0; if ((hc->cmd=http_get_cmd(elem[0])) == HC_CMD_UNKNOWN) return 0; hc->url=strdup(elem[1]); hc->proto=http_parse_proto(elem[2]); if (hc->proto == HP_HTTP11) hc->keepalive=1; else hc->keepalive=0; return 1;}static int http_parse_attrib(struct http_connection *hc, struct evbuffer *input) { char *p=(char *) EVBUFFER_DATA(input); struct http_attrib *ha; char *token, *value; token=p; while(*p != ':' && *p != ' ' && *p != 0x0) p++; /* Did we find the colon as delimiter ? * If not something went really wrong - terminate the connection */ if (*p == ':') *p++=0x0; else return 0; while(*p == ' ' || *p == '\t') p++; value=p; ha=malloc(sizeof(struct http_attrib)); ha->token=strdup(token); ha->value=strdup(value); hc->attrib=g_list_append(hc->attrib, ha); Dprintf("%s: %s %s\n", __FUNCTION__, token, value); return 1;}/* * HTTP/1.1 404 Not Found * Date: Wed, 17 May 2006 17:33:00 GMT * Server: Apache/2.0.54 (Debian GNU/Linux) * Content-Length: 310 * Connection: close * Content-Type: text/html; charset=iso-8859-1 * */static char *http_static_msg_404 ="<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\"> \<html><head> \<title>404 Not Found</title> \</head><body> \<h1>404 Not Found</h1> \</body> \</html>";int http_url_handler_404(struct http_connection *hc, int cbtype, void *arg) { int err; Dprintf("%s: cbtype %d\n", __FUNCTION__, cbtype); /* Answer with 404 page on QUERY */ if (cbtype == HCB_QUERY) { err=http_return_simple(hc, "404 Not found", "text/html", http_static_msg_404, strlen(http_static_msg_404)); return 1; } /* * Drop connection if socket gets writeable again * or we have an error */ http_request_end(hc); return 1;}size_t http_get_queue(struct http_connection *hc) { return EVBUFFER_LENGTH(hc->bev->output);}/* * Function to return an http chunk with previous unknown size. HTTP/1.1 allows * for chunked transfers which HTTP/1.0 doesnt know about. There is no legal way * to do this so we hope our best. * */int http_return_stream(struct http_connection *hc, void *data, size_t datalen) { /* We are chunked and done sending - end session */ if (!data) { /* Chunked transfer - send a 0 chunk */ if (hc->proto == HP_HTTP11) { evbuffer_add_printf(hc->evb,"0\r\n\r\n"); bufferevent_write_buffer(hc->bev, hc->evb); } hc->status=HC_STATUS_END; return 1; } /* In case of Transfer-Encoding: chunked send a chunk - otherwise just data */ if (hc->proto == HP_HTTP11) { evbuffer_add_printf(hc->evb,"%x\r\n", datalen); evbuffer_add(hc->evb, data, datalen); evbuffer_add_printf(hc->evb, "\r\n"); } else { evbuffer_add(hc->evb, data, datalen); } bufferevent_write_buffer(hc->bev, hc->evb); Dprintf("%s: output buffer len %d\n", __FUNCTION__, EVBUFFER_LENGTH(hc->bev->output)); return 1;}int http_header_add(struct http_connection *hc, char *fmt, ...) { va_list ap; va_start(ap, fmt); hc->hsize+=vsprintf(&hc->hdr[hc->hsize], fmt, ap); hc->hdr[hc->hsize++]=0x0d; hc->hdr[hc->hsize++]=0x0a; hc->hdr[hc->hsize]=0x00; va_end(ap); return 0;}int http_header_end(struct http_connection *hc) { if (hc->keepalive) http_header_add(hc, "Connection: keep-alive"); else http_header_add(hc, "Connection: close"); hc->hsize+=sprintf(&hc->hdr[hc->hsize], "\r\n"); bufferevent_write(hc->bev, hc->hdr, hc->hsize); hc->hsize=0; return 0;}int http_header_clength(struct http_connection *hc, ssize_t length) { if (length >= 0) http_header_add(hc, "Content-Length: %d", length); else { if (hc->proto == HP_HTTP11) http_header_add(hc, "Transfer-Encoding: chunked"); } return 0;}int http_header_nocache(struct http_connection *hc) { http_header_add(hc, "Pragma: no-cache"); http_header_add(hc, "Cache-Control: no-cache"); return 0;}int http_header_start(struct http_connection *hc, char *result, char *type) { struct tm *tm; time_t t; /* Wed, 17 May 2006 17:33:00 GMT */ t=time(NULL); tm=gmtime(&t); hc->hsize=0; hc->hsize+=sprintf(&hc->hdr[hc->hsize], "HTTP/1.1 %s\r\n", result); hc->hsize+=sprintf(&hc->hdr[hc->hsize], "Date: "); hc->hsize+=strftime(&hc->hdr[hc->hsize], MAX_HEADER_SIZE-hc->hsize, "%a, %d %b %Y %H:%M:%S %Z\r\n", tm); http_header_add(hc, "Content-Type: %s", type); return 0;}/* * Send a simple request answer: * Content-Length: is set - No chunked, partial transfer, * After requests gets answered - connection will be closed * */int http_return_simple(struct http_connection *hc, char *result, char *type, void *data, size_t datalen) { http_header_start(hc, result, type); http_header_clength(hc, datalen); http_header_end(hc); bufferevent_write(hc->bev, data, datalen); hc->status=HC_STATUS_END; return 1;}static int http_process_query(struct http_connection *hc) { struct http_url *hu; /* * FIXME Need to parse url to parameters, unescaping * characters etc ... * */ hu=g_hash_table_lookup(hc->server->urls, hc->url); Dprintf("%s: hu %p\n", __FUNCTION__, hu); if (hu) { hc->url_handler=hu->cb; return hc->url_handler(hc, HCB_QUERY, hu->arg); } return hc->url_handler(hc, HCB_QUERY, NULL);}static int http_read_head(struct http_connection *hc, struct bufferevent *bev) { struct evbuffer *input = EVBUFFER_INPUT(bev); u_char *eol, *p; int err, drainlen; while((eol=evbuffer_find(input, (u_char *) "\n", 1)) != NULL) { /* get begin of line */ p=EVBUFFER_DATA(input); /* Line length */ drainlen=eol-p+1; /* * Remove \n and if exists the \r * FIXME Is this robust enough ? */ *eol=0x0; if (eol > p && *(eol-1) == '\r') *(--eol) = 0x0; if (!hc->url) { /* Did we have the first line with command and url ? */ err=http_parse_command_url(hc, input); } else if (eol == p) { err=http_process_query(hc); } else { err=http_parse_attrib(hc, input); } /* tell buffer to remove leading bytes */ evbuffer_drain(input, drainlen); if (!err) return 0; } return 1;}/* * Free Request based resources from the http_connection * */static void http_request_free(struct http_connection *hc) { GList *item; struct http_attrib *ha; if (hc->url) free(hc->url); hc->url=NULL; /* Free http attributes */ while((item=g_list_first(hc->attrib))) { hc->attrib=g_list_remove_link(hc->attrib, item); ha=item->data; free(ha->token); free(ha->value); free(ha); g_list_free(item); }}/* * Drop the connection in case of error of non keepalive */void http_drop_connection(struct http_connection *hc) { struct http_server *hs=hc->server; Dprintf("%s:%d\n", __FUNCTION__, __LINE__); hs->conn=g_list_remove(hs->conn, hc); bufferevent_disable(hc->bev, EV_READ|EV_WRITE); bufferevent_free(hc->bev); http_request_free(hc); close(hc->fd); evbuffer_free(hc->evb); free(hc);}/* * Application signals http request end - either * drop the connection or simply free the request * resources. * */void http_request_end(struct http_connection *hc) { if (!hc->keepalive) { http_drop_connection(hc); } else { http_request_free(hc); hc->status=HC_STATUS_HEAD; hc->url_handler=&http_url_handler_404; hc->hsize=0; }}/* * Callback handler for EV_READ of the client connection. If data * arrives for our handler */static void http_cb_read(struct bufferevent *bev, void *arg) { struct http_connection *hc=arg; Dprintf("%s:%d\n", __FUNCTION__, __LINE__); switch(hc->status) { case(HC_STATUS_END): hc->url_handler(hc, HCB_END, hc->arg); break; case(HC_STATUS_HEAD): if (!http_read_head(hc, bev)) hc->url_handler(hc, HCB_ERROR, hc->arg); break; case(HC_STATUS_BODY): hc->url_handler(hc, HCB_READ, hc->arg); break; }}static void http_cb_write(struct bufferevent *bev, void *arg) { struct http_connection *hc=arg; Dprintf("%s:%d\n", __FUNCTION__, __LINE__); if (hc->status != HC_STATUS_END) hc->url_handler(hc, HCB_WRITE, hc->arg); if (hc->status == HC_STATUS_END) hc->url_handler(hc, HCB_END, hc->arg);}static void http_cb_error(struct bufferevent *bev, short what, void *arg) { struct http_connection *hc=arg; Dprintf("%s:%d\n", __FUNCTION__, __LINE__); hc->url_handler(hc, HCB_ERROR, hc->arg);}static void http_connect(int fd, short ev, void *arg) { struct http_server *hs=arg; struct http_connection *hc; struct sockaddr_in sin; socklen_t slen=sizeof(struct sockaddr_in); int nfd; unsigned long flags; nfd=accept(fd, (struct sockaddr *) &sin, &slen); if (nfd < 0) return; Dprintf("%s:%d New connection\n", __FUNCTION__, __LINE__); /* Set new socket O_NONBLOCK */ flags=fcntl(nfd, F_GETFL); fcntl(nfd, F_SETFL, flags | O_NONBLOCK); hc=calloc(1, sizeof(struct http_connection)); hc->fd=nfd; hc->server=hs; hc->status=HC_STATUS_HEAD; hc->cid=hs->cid++; hc->evb=evbuffer_new(); /* Copy remote endpoint sockaddr to http_connection structure */ memcpy(&hc->sin, &sin, sizeof(struct sockaddr_in)); hc->bev=bufferevent_new(hc->fd, http_cb_read, http_cb_write, http_cb_error, hc); bufferevent_enable(hc->bev, EV_READ); hc->url_handler=&http_url_handler_404; hs->conn=g_list_append(hs->conn, hc);}int http_register_url(struct http_server *hs, struct http_url *hu) { while(hu->url) { g_hash_table_insert(hs->urls, hu->url, hu); hu++; } return 1;}struct http_server *http_init(int port) { struct http_server *hs; unsigned long flags; int one=1; hs=calloc(1, sizeof(struct http_server)); hs->port=port; hs->urls=g_hash_table_new(g_str_hash, g_str_equal); hs->fd=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); setsockopt(hs->fd, SOL_SOCKET, SO_REUSEADDR, (void *) &one, sizeof(int)); hs->sin.sin_family=AF_INET; hs->sin.sin_port=htons(port); hs->sin.sin_addr.s_addr=INADDR_ANY; if (bind(hs->fd, (struct sockaddr *) &hs->sin, sizeof(struct sockaddr_in))) return NULL; if (listen(hs->fd, 3)) return NULL; flags=fcntl(hs->fd, F_GETFL); fcntl(hs->fd, F_GETFL, flags | O_NONBLOCK); event_set(&hs->ev, hs->fd, EV_READ|EV_PERSIST, http_connect, (void *) hs); event_add(&hs->ev, NULL); return hs;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -