diff options
Diffstat (limited to 'src/uvh.c')
| -rw-r--r-- | src/uvh.c | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/src/uvh.c b/src/uvh.c new file mode 100644 index 0000000..f5d85f5 --- /dev/null +++ b/src/uvh.c @@ -0,0 +1,473 @@ +#include "uvh.h" +#include "sds.h" +#include "http_parser.h" + +#ifndef offsetof +#ifdef __GNUC__ +#define offsetof(type, member) __builtin_offsetof (type, member) +#endif +#endif + +#ifndef container_of +#ifdef __GNUC__ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif +#endif + +#define LOG(LEVEL, FMT, args...) \ + fprintf(stderr, FMT "\n" , ##args) + +#define LOG_DEBUG(FMT, args...) LOG(0, "DEBUG " FMT , ##args) +#define LOG_WARNING(FMT, args...) LOG(0, "WARNING " FMT , ##args) +#define LOG_ERROR(FMT, args...) LOG(0, "ERROR " FMT , ##args) +#define LOG_INFO(FMT, args...) LOG(0, "INFO " FMT , ##args) + +struct http_status_code_def +{ + int code; + const char *str; +}; + +static struct http_status_code_def http_status_code_defs[] = +{ +#define XX(CODE, NAME, STR) { CODE, STR }, + HTTP_STATUS_CODE_MAP(XX) +#undef XX + { -1, NULL } +}; + +static void on_connection(uv_stream_t *stream, int status); +static uv_buf_t alloc_cb(uv_handle_t *, size_t size); +static void read_cb(uv_stream_t *stream, ssize_t nread, uv_buf_t buf); + +static int on_message_begin(http_parser *parser); +static int on_url(http_parser *parser, const char *at, size_t len); +static int on_status(http_parser *parser, const char *at, size_t len); +static int on_header_field(http_parser *parser, const char *at, size_t len); +static int on_header_value(http_parser *parser, const char *at, size_t len); +static int on_headers_complete(http_parser *parser); +static int on_body(http_parser *parser, const char *at, size_t len); +static int on_message_complete(http_parser *parser); + +struct uvh_server_private +{ + struct uvh_server server; + struct sockaddr_storage addr; + socklen_t addr_len; + uv_loop_t *loop; + struct http_parser_settings http_parser_settings; + uv_tcp_t stream; +}; + +struct uvh_request_private +{ + struct uvh_request req; + struct http_parser parser; + uv_tcp_t stream; + sds header_name; + sds header_value; + int header_state; +}; + +struct uvh_server *uvh_server_init(uv_loop_t *loop, void *data, + uvh_request_handler_cb request_handler) +{ + struct uvh_server_private *server; + int rc; + + server = calloc(1, sizeof(*server)); + + if (!server) + goto error; + + server->loop = loop; + server->server.data = data; + server->server.request_handler = request_handler; + + rc = uv_tcp_init(loop, &server->stream); + + if (rc < 0) + goto error; + + server->stream.data = server; + + server->http_parser_settings.on_message_begin = on_message_begin; + server->http_parser_settings.on_url = on_url; + server->http_parser_settings.on_status = on_status; + server->http_parser_settings.on_header_field = on_header_field; + server->http_parser_settings.on_header_value = on_header_value; + server->http_parser_settings.on_headers_complete = on_headers_complete; + server->http_parser_settings.on_body = on_body; + server->http_parser_settings.on_message_complete = on_message_complete; + + return &server->server; + +error: + + if (server) + { + free(server); + } + + return NULL; +} + +int uvh_server_listen(struct uvh_server *server, const char *address, + short port) +{ + struct uvh_server_private *serverp = container_of(server, struct uvh_server_private, server); + struct sockaddr_in addr = uv_ip4_addr(address, port); + + memcpy(&serverp->addr, &addr, sizeof(addr)); + serverp->addr_len = sizeof(addr); + + uv_tcp_bind(&serverp->stream, addr); + + int r = uv_listen((uv_stream_t *) &serverp->stream, 128, + on_connection); + + if (r) + return r; + + return 0; +} + +static void on_connection(uv_stream_t *stream, int status) +{ + struct uvh_server_private *priv = container_of((uv_tcp_t *) stream, struct uvh_server_private, stream); + + LOG_DEBUG("%s", __FUNCTION__); + + if (status == -1) + { + LOG_WARNING("on_connection: status = -1"); + return; + } + + struct uvh_request_private *req = calloc(1, sizeof(*req)); + req->req.server = &priv->server; + req->header_state = 0; + + http_parser_init(&req->parser, HTTP_REQUEST); + req->parser.data = req; + + if (uv_tcp_init(priv->loop, &req->stream)) + { + LOG_WARNING("failed to initialize uv_tcp_t"); + goto error; + } + + req->stream.data = req; + + if (uv_accept(stream, (uv_stream_t *) &req->stream) == 0) + { + uv_read_start((uv_stream_t *) &req->stream, alloc_cb, + read_cb); + return; + } + else + { + uv_close((uv_handle_t *) &req->stream, NULL); + LOG_WARNING("failed to accept"); + } + +error: + + if (req) + { + free(req); + } +} + +static uv_buf_t alloc_cb(uv_handle_t *handle, size_t size) +{ + (void) handle; + return uv_buf_init(calloc(1, size), size); +} + +static void read_cb(uv_stream_t *stream, ssize_t nread, uv_buf_t buf) +{ + struct uvh_request_private *req = (struct uvh_request_private *) stream->data; + struct uvh_server_private *serverp = container_of(req->req.server, struct uvh_server_private, server); + + LOG_DEBUG("read_cb: nread: %zd, buf.len: %zd", nread, buf.len); + + if (nread < 0) + { + uv_err_t err = uv_last_error(stream->loop); + + if (buf.base) + free(buf.base); + + if (err.code == UV_EOF) + { + LOG_DEBUG("EOF"); + http_parser_execute(&req->parser, + &serverp->http_parser_settings, NULL, 0); + } + + uv_close((uv_handle_t *) stream, NULL); + + return; + } + + if (nread == 0) + { + free(buf.base); + return; + } + + int nparsed = http_parser_execute(&req->parser, + &serverp->http_parser_settings, + buf.base, nread); + + LOG_DEBUG("nparsed:%d", nparsed); + + if (nparsed != nread) + { + LOG_ERROR("http parse error, closing connection"); + uv_close((uv_handle_t *) stream, NULL); + } + + free(buf.base); +} + +static int on_message_begin(http_parser *parser) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + priv->req.content = (const char *) sdsempty(); + return 0; +} + +static int on_url(http_parser *parser, const char *at, size_t len) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + + LOG_DEBUG("on_url: <%.*s>", (int) len, at); + + if (!priv->req.url.full) + { + priv->req.url.full = sdsnewlen(at, len); + } + else + { + priv->req.url.full = sdscatlen((sds) priv->req.url.full, + at, len); + } + + return 0; +} + +static int on_status(http_parser *parser, const char *at, size_t len) +{ + LOG_DEBUG("on_status: <%.*s>", (int) len, at); + return 0; +} + +static int on_header_field(http_parser *parser, const char *at, size_t len) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + struct uvh_request *req = &priv->req; + + if (priv->header_state == 0) + { + priv->header_name = sdsnewlen(at, len); + } + else if (priv->header_state == 1) + { + priv->header_name = sdscatlen(priv->header_name, at, len); + } + else if (priv->header_state == 2) + { + req->headers[req->header_count].name = priv->header_name; + req->headers[req->header_count].value = priv->header_value; + req->header_count += 1; + + priv->header_name = sdsnewlen(at, len); + priv->header_value = NULL; + } + + priv->header_state = 1; + + return 0; +} + +static int on_header_value(http_parser *parser, const char *at, size_t len) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + + if (priv->header_state == 1) + { + priv->header_value = sdsnewlen(at, len); + } + else if (priv->header_state == 2) + { + priv->header_value = sdscatlen(priv->header_value, at, len); + } + + priv->header_state = 2; + + return 0; +} + +static int on_headers_complete(http_parser *parser) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + struct uvh_request *req = &priv->req; + struct http_parser_url url; + const char *full = req->url.full; + + if (priv->header_state == 2) + { + req->headers[req->header_count].name = priv->header_name; + req->headers[req->header_count].value = priv->header_value; + req->header_count += 1; + } + + LOG_DEBUG("on_headers_complete"); + + http_parser_parse_url(req->url.full, sdslen((sds) req->url.full), + 1, &url); + +#define UF_OFFSET(X) url.field_data[X].off +#define UF_LEN(X) url.field_data[X].len +#define UF_SET(X) (url.field_set & (1 << (X))) +#define UF_CHECK_AND_SET(X, DST) \ + if (UF_SET(X)) \ + (DST) = sdsnewlen(full + UF_OFFSET(X), UF_LEN(X)) + + UF_CHECK_AND_SET(UF_SCHEMA, req->url.schema); + UF_CHECK_AND_SET(UF_HOST, req->url.host); + UF_CHECK_AND_SET(UF_PORT, req->url.port); + UF_CHECK_AND_SET(UF_PATH, req->url.path); + UF_CHECK_AND_SET(UF_QUERY, req->url.query); + UF_CHECK_AND_SET(UF_FRAGMENT, req->url.fragment); + UF_CHECK_AND_SET(UF_USERINFO, req->url.userinfo); + +#undef UF_CHECK_AND_SET +#undef UF_SET +#undef UF_LEN +#undef UF_OFFSET + + return 0; +} + +static int on_body(http_parser *parser, const char *at, size_t len) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + priv->req.content = (const char *) sdscatlen((sds) priv->req.content, + at, len); + return 0; +} + +static int on_message_complete(http_parser *parser) +{ + struct uvh_request_private *priv = (struct uvh_request_private *) parser->data; + + LOG_DEBUG("on_message_complete"); + + if (priv->req.content) + priv->req.content_length = sdslen((sds) priv->req.content); + else + priv->req.content_length = 0; + + priv->req.method = sdsnew(http_method_str(parser->method)); + + priv->req.version = sdsempty(); + priv->req.version = sdscatprintf((sds) priv->req.version, + "HTTP/%d.%d", parser->http_major, parser->http_minor); + + if (priv->req.server->request_handler) + priv->req.server->request_handler(&priv->req); + + return 0; +} + +struct uvh_write_request +{ + uv_buf_t buf; + uv_write_t wreq; + struct uvh_request_private *req; +}; + +static void after_request_write(uv_write_t *req, int status) +{ + struct uvh_write_request *wreq = container_of(req, struct uvh_write_request, wreq); + sdsfree((sds) wreq->buf.base); + free(wreq); +} + +static void uvh_request_write_sds(struct uvh_request *req, sds data) +{ + struct uvh_request_private *p = container_of(req, struct uvh_request_private, req); + struct uvh_write_request *wreq = calloc(1, sizeof(*wreq)); + + wreq->buf.base = (char *) data; + wreq->buf.len = sdslen(data); + + wreq->req = p; + + uv_write(&wreq->wreq, (uv_stream_t *) &p->stream, &wreq->buf, 1, + &after_request_write); +} + +void uvh_request_write(struct uvh_request *req, + const char *data, size_t len) +{ + uvh_request_write_sds(req, sdsnewlen(data, len)); +} + +void uvh_request_writef(struct uvh_request *req, const char *fmt, ...) +{ + va_list ap; + sds result; + + va_start(ap, fmt); + result = sdscatvprintf(sdsempty(), fmt, ap); + va_end(ap); + + uvh_request_write_sds(req, result); +} + +void uvh_request_write_status(struct uvh_request *req, int status) +{ + uvh_request_writef(req, "%s %d %s\r\n", req->version, status, + http_status_code_str(status)); +} + +void uvh_request_write_header(struct uvh_request *req, + const char *name, const char *value) +{ + uvh_request_writef(req, "%s: %s\r\n", name, value); +} + +const char *http_status_code_str(int code) +{ + struct http_status_code_def *def = http_status_code_defs; + while (def->code != -1) + { + if (def->code == code) + { + return def->str; + } + ++def; + } + + return NULL; +} + +const char *uvh_request_get_header(struct uvh_request *req, + const char *name) +{ + int i; + + for (i = 0; i < req->header_count; ++i) + { + if (strcasecmp(name, req->headers[i].name) == 0) + return req->headers[i].value; + } + + return NULL; +} |
