669 lines
18 KiB
C
669 lines
18 KiB
C
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2019 Jianhui Zhao <zhaojh329@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <sys/sendfile.h>
|
|
|
|
#include "connection.h"
|
|
#include "uhttpd.h"
|
|
#include "utils.h"
|
|
#include "file.h"
|
|
#include "ssl.h"
|
|
|
|
static void conn_done(struct uh_connection *conn)
|
|
{
|
|
buffer_pull(&conn->rb, NULL, buffer_length(&conn->rb));
|
|
|
|
if (!http_should_keep_alive(&conn->parser))
|
|
conn->flags |= CONN_F_SEND_AND_CLOSE;
|
|
|
|
if (conn->flags & CONN_F_SEND_AND_CLOSE)
|
|
ev_io_stop(conn->srv->loop, &conn->ior);
|
|
|
|
ev_io_start(conn->srv->loop, &conn->iow);
|
|
}
|
|
|
|
static void conn_send(struct uh_connection *conn, const void *data, ssize_t len)
|
|
{
|
|
buffer_put_data(&conn->wb, data, len);
|
|
ev_io_start(conn->srv->loop, &conn->iow);
|
|
}
|
|
|
|
static void conn_send_file(struct uh_connection *conn, const char *path)
|
|
{
|
|
struct stat st;
|
|
|
|
conn->file.fd = open(path, O_RDONLY);
|
|
|
|
fstat(conn->file.fd, &st);
|
|
|
|
conn->file.size = st.st_size;
|
|
|
|
ev_io_start(conn->srv->loop, &conn->iow);
|
|
}
|
|
|
|
static void conn_printf(struct uh_connection *conn, const char *format, ...)
|
|
{
|
|
struct buffer *wb = &conn->wb;
|
|
va_list arg;
|
|
|
|
va_start(arg, format);
|
|
buffer_put_vprintf(wb, format, arg);
|
|
va_end(arg);
|
|
|
|
ev_io_start(conn->srv->loop, &conn->iow);
|
|
}
|
|
|
|
static void conn_vprintf(struct uh_connection *conn, const char *format, va_list arg)
|
|
{
|
|
buffer_put_vprintf(&conn->wb, format, arg);
|
|
ev_io_start(conn->srv->loop, &conn->iow);
|
|
}
|
|
|
|
static void conn_chunk_send(struct uh_connection *conn, const void *data, ssize_t len)
|
|
{
|
|
conn_printf(conn, "%X\r\n", len);
|
|
conn_send(conn, data, len);
|
|
conn_printf(conn, "\r\n", len);
|
|
}
|
|
|
|
static void conn_chunk_vprintf(struct uh_connection *conn, const char *format, va_list arg)
|
|
{
|
|
char buf[256];
|
|
va_list arg2;
|
|
int len;
|
|
|
|
va_copy(arg2, arg);
|
|
len = vsnprintf(buf, sizeof(buf), format, arg2);
|
|
va_end(arg2);
|
|
|
|
conn_printf(conn, "%X\r\n", len);
|
|
if (len < sizeof(buf))
|
|
conn_send(conn, buf, len);
|
|
else
|
|
conn_vprintf(conn, format, arg);
|
|
conn_printf(conn, "\r\n", len);
|
|
}
|
|
|
|
static void conn_chunk_printf(struct uh_connection *conn, const char *format, ...)
|
|
{
|
|
va_list arg;
|
|
|
|
va_start(arg, format);
|
|
conn_chunk_vprintf(conn, format, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
static inline void conn_chunk_end(struct uh_connection *conn)
|
|
{
|
|
conn_chunk_send(conn, NULL, 0);
|
|
}
|
|
|
|
static void conn_send_status_line(struct uh_connection *conn, int code, const char *extra_headers)
|
|
{
|
|
conn_printf(conn, "HTTP/1.1 %d %s\r\nServer: Libuhttpd/%s\r\n", code, http_status_str(code), UHTTPD_VERSION_STRING);
|
|
if (extra_headers)
|
|
conn_send(conn, extra_headers, strlen(extra_headers));
|
|
}
|
|
|
|
static void conn_send_head(struct uh_connection *conn, int code, int content_length, const char *extra_headers)
|
|
{
|
|
conn_send_status_line(conn, code, extra_headers);
|
|
if (content_length < 0)
|
|
conn_printf(conn, "%s", "Transfer-Encoding: chunked\r\n");
|
|
else
|
|
conn_printf(conn, "Content-Length: %d\r\n", content_length);
|
|
|
|
if (!http_should_keep_alive(&conn->parser))
|
|
conn_printf(conn, "%s", "Connection: close\r\n");
|
|
|
|
conn_send(conn, "\r\n", 2);
|
|
}
|
|
|
|
static void conn_error(struct uh_connection *conn, int code, const char *reason)
|
|
{
|
|
if (!reason)
|
|
reason = http_status_str(code);
|
|
conn_send_head(conn, code, strlen(reason), "Content-Type: text/plain\r\nConnection: close\r\n");
|
|
conn_send(conn, reason, strlen(reason));
|
|
|
|
conn->flags |= CONN_F_SEND_AND_CLOSE;
|
|
|
|
conn_done(conn);
|
|
}
|
|
|
|
static void conn_redirect(struct uh_connection *conn, int code, const char *location, ...)
|
|
{
|
|
struct buffer *wb = &conn->wb;
|
|
va_list arg;
|
|
|
|
assert((code == HTTP_STATUS_MOVED_PERMANENTLY || code == HTTP_STATUS_FOUND) && location);
|
|
|
|
conn_send_status_line(conn, code, NULL);
|
|
|
|
conn_printf(conn, "Location: ");
|
|
va_start(arg, location);
|
|
buffer_put_vprintf(wb, location, arg);
|
|
va_end(arg);
|
|
conn_send(conn, "\r\n", 2);
|
|
|
|
conn_printf(conn, "Content-Length: 0\r\n");
|
|
conn_send(conn, "\r\n", 2);
|
|
|
|
conn_done(conn);
|
|
}
|
|
|
|
static uint32_t conn_get_addr(struct uh_connection *conn)
|
|
{
|
|
return ntohl(conn->addr.sin_addr.s_addr);
|
|
}
|
|
|
|
static enum http_method conn_get_method(struct uh_connection *conn)
|
|
{
|
|
return conn->parser.method;
|
|
}
|
|
|
|
static const char *conn_get_method_str(struct uh_connection *conn)
|
|
{
|
|
return http_method_str(conn->parser.method);
|
|
}
|
|
|
|
/* offset of the request field */
|
|
#define ROF(c, a) (a - (const char *)c->rb.data)
|
|
|
|
/* data of the request field */
|
|
#define O2D(c, o) ((const char *)c->rb.data + o)
|
|
|
|
static struct uh_str conn_get_path(struct uh_connection *conn)
|
|
{
|
|
struct http_parser_url *u = &conn->url_parser;
|
|
struct uh_request *req = &conn->req;
|
|
struct uh_str path;
|
|
|
|
path.p = O2D(conn, u->field_data[UF_PATH].off) + req->url.offset;
|
|
path.len = u->field_data[UF_PATH].len;
|
|
|
|
return path;
|
|
}
|
|
|
|
static struct uh_str conn_get_query(struct uh_connection *conn)
|
|
{
|
|
struct http_parser_url *u = &conn->url_parser;
|
|
struct uh_request *req = &conn->req;
|
|
struct uh_str query = {};
|
|
|
|
if (!(u->field_set & (1 << UF_QUERY)))
|
|
return query;
|
|
|
|
query.p = O2D(conn, u->field_data[UF_QUERY].off) + req->url.offset;
|
|
query.len = u->field_data[UF_QUERY].len;
|
|
|
|
return query;
|
|
}
|
|
|
|
static struct uh_str conn_get_header(struct uh_connection *conn, const char *name)
|
|
{
|
|
struct uh_request *req = &conn->req;
|
|
int name_len = strlen(name);
|
|
struct uh_str value = {};
|
|
int i;
|
|
|
|
for (i = 0; i < UHTTPD_MAX_HEADER_NUM; i++) {
|
|
if (req->headers[i].field.offset == 0)
|
|
return value;
|
|
|
|
if (req->headers[i].field.length != name_len)
|
|
continue;
|
|
|
|
if (!strncasecmp(O2D(conn, req->headers[i].field.offset), name, name_len)) {
|
|
value.p = O2D(conn, req->headers[i].value.offset);
|
|
value.len = req->headers[i].value.length;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static struct uh_str conn_get_body(struct uh_connection *conn)
|
|
{
|
|
struct uh_request *req = &conn->req;
|
|
struct uh_str body;
|
|
|
|
body.p = O2D(conn, req->body.offset);
|
|
body.len = req->body.length;
|
|
|
|
return body;
|
|
}
|
|
|
|
static struct uh_str conn_extract_body(struct uh_connection *conn)
|
|
{
|
|
struct uh_request *req = &conn->req;
|
|
struct uh_str body;
|
|
|
|
body.p = O2D(conn, req->body.offset);
|
|
body.len = req->body.length;
|
|
|
|
req->body.length = 0;
|
|
|
|
buffer_discard(&conn->rb, body.len);
|
|
|
|
return body;
|
|
}
|
|
|
|
static int on_message_begin_cb(struct http_parser *parser)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_request *req = &conn->req;
|
|
|
|
memset(req, 0, sizeof(struct uh_request));
|
|
|
|
req->last_was_header_value = true;
|
|
|
|
http_parser_url_init(&conn->url_parser);
|
|
|
|
ev_timer_start(conn->srv->loop, &conn->timer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_url_cb(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_request *req = &conn->req;
|
|
|
|
if (req->url.offset == 0)
|
|
req->url.offset = ROF(conn, at);
|
|
req->url.length += length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_header_field_cb(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_request *req = &conn->req;
|
|
|
|
if (req->last_was_header_value) {
|
|
req->last_was_header_value = false;
|
|
req->header_num++;
|
|
|
|
if (req->header_num == UHTTPD_MAX_HEADER_NUM) {
|
|
uh_log_err("Header too more\n");
|
|
return 1;
|
|
}
|
|
|
|
req->headers[req->header_num - 1].field.offset = ROF(conn, at);
|
|
}
|
|
|
|
req->headers[req->header_num - 1].field.length += length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_header_value_cb(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_request *req = &conn->req;
|
|
|
|
if (!req->last_was_header_value) {
|
|
req->last_was_header_value = true;
|
|
req->headers[req->header_num - 1].value.offset = ROF(conn, at);
|
|
}
|
|
|
|
req->headers[req->header_num - 1].value.length += length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_headers_complete(struct http_parser *parser)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_server *srv = conn->srv;
|
|
struct uh_request *req = &conn->req;
|
|
struct uh_path_handler *h = srv->handlers;
|
|
struct uh_plugin *p = srv->plugins;
|
|
struct uh_str path;
|
|
|
|
http_parser_parse_url(O2D(conn, req->url.offset), req->url.length, false, &conn->url_parser);
|
|
|
|
if (conn->handler)
|
|
return 0;
|
|
|
|
path = conn->get_path(conn);
|
|
|
|
while (h) {
|
|
if (strlen(h->path) == path.len && !strncmp(path.p, h->path, path.len)) {
|
|
conn->handler = h->handler;
|
|
return 0;
|
|
}
|
|
h = h->next;
|
|
}
|
|
|
|
while (p) {
|
|
if (strlen(p->h->path) == path.len && !strncmp(path.p, p->h->path, path.len)) {
|
|
conn->handler = p->h->handler;
|
|
return 0;
|
|
}
|
|
p = p->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_body_cb(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_request *req = &conn->req;
|
|
struct uh_server *srv = conn->srv;
|
|
int event = UH_EV_BODY;
|
|
|
|
if (req->body.offset == 0)
|
|
req->body.offset = ROF(conn, at);
|
|
req->body.length += length;
|
|
|
|
if (conn->handler)
|
|
conn->handler(conn, event);
|
|
else if (srv->default_handler)
|
|
srv->default_handler(conn, event);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_message_complete_cb(struct http_parser *parser)
|
|
{
|
|
struct uh_connection *conn = (struct uh_connection *)parser->data;
|
|
struct uh_server *srv = conn->srv;
|
|
int event = UH_EV_COMPLETE;
|
|
|
|
ev_timer_stop(srv->loop, &conn->timer);
|
|
|
|
if (conn->handler)
|
|
conn->handler(conn, event);
|
|
else if (srv->default_handler)
|
|
srv->default_handler(conn, event);
|
|
else
|
|
conn_error(conn, HTTP_STATUS_NOT_FOUND, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct http_parser_settings settings = {
|
|
.on_message_begin = on_message_begin_cb,
|
|
.on_url = on_url_cb,
|
|
.on_header_field = on_header_field_cb,
|
|
.on_header_value = on_header_value_cb,
|
|
.on_headers_complete = on_headers_complete,
|
|
.on_body = on_body_cb,
|
|
.on_message_complete = on_message_complete_cb
|
|
};
|
|
|
|
void conn_free(struct uh_connection *conn)
|
|
{
|
|
struct ev_loop *loop = conn->srv->loop;
|
|
struct sockaddr_in *addr = &conn->addr;
|
|
|
|
ev_timer_stop(loop, &conn->timer);
|
|
ev_io_stop(loop, &conn->ior);
|
|
ev_io_stop(loop, &conn->iow);
|
|
|
|
buffer_free(&conn->rb);
|
|
buffer_free(&conn->wb);
|
|
|
|
if (conn->file.fd > 0)
|
|
close(conn->file.fd);
|
|
|
|
if (conn->prev)
|
|
conn->prev->next = conn->next;
|
|
else
|
|
conn->srv->conns = conn->next;
|
|
|
|
if (conn->next)
|
|
conn->next->prev = conn->prev;
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
uh_ssl_free(conn->ssl);
|
|
#endif
|
|
|
|
if (conn->sock > 0)
|
|
close(conn->sock);
|
|
|
|
uh_log_debug("Connection(%s:%d) closed\n", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
|
|
|
|
free(conn);
|
|
}
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
static int conn_ssl_write(int fd, void *buf, size_t count, void *ssl)
|
|
{
|
|
int ret = uh_ssl_write(ssl, buf, count);
|
|
if (ret < 0) {
|
|
if (ret == UH_SSL_ERROR_AGAIN)
|
|
return P_FD_PENDING;
|
|
return P_FD_ERR;
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
#endif
|
|
|
|
static void conn_write_cb(struct ev_loop *loop, struct ev_io *w, int revents)
|
|
{
|
|
struct uh_connection *conn = container_of(w, struct uh_connection, iow);
|
|
int ret;
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
if (conn->ssl)
|
|
ret = buffer_pull_to_fd_ex(&conn->wb, w->fd, buffer_length(&conn->wb), conn_ssl_write, conn->ssl);
|
|
else
|
|
#endif
|
|
ret = buffer_pull_to_fd(&conn->wb, w->fd, buffer_length(&conn->wb));
|
|
|
|
if (ret < 0) {
|
|
uh_log_err("write error: %s\n", strerror(errno));
|
|
conn_free(conn);
|
|
return;
|
|
}
|
|
|
|
if (buffer_length(&conn->wb) == 0) {
|
|
if (conn->file.fd > 0) {
|
|
ret = sendfile(w->fd, conn->file.fd, NULL, conn->file.size);
|
|
if (ret < 0) {
|
|
if (errno != EAGAIN) {
|
|
uh_log_err("write error: %s\n", strerror(errno));
|
|
conn_free(conn);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ret < conn->file.size) {
|
|
conn->file.size -= ret;
|
|
return;
|
|
}
|
|
|
|
close(conn->file.fd);
|
|
conn->file.fd = -1;
|
|
}
|
|
|
|
if (conn->flags & CONN_F_SEND_AND_CLOSE)
|
|
conn_free(conn);
|
|
else
|
|
ev_io_stop(loop, w);
|
|
}
|
|
}
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
static int conn_ssl_read(int fd, void *buf, size_t count, void *ssl)
|
|
{
|
|
int ret = uh_ssl_read(ssl, buf, count);
|
|
if (ret < 0) {
|
|
if (ret == UH_SSL_ERROR_AGAIN)
|
|
return P_FD_PENDING;
|
|
return P_FD_ERR;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static void conn_read_cb(struct ev_loop *loop, struct ev_io *w, int revents)
|
|
{
|
|
struct uh_connection *conn = container_of(w, struct uh_connection, ior);
|
|
struct http_parser *parser = &conn->parser;
|
|
struct buffer *rb = &conn->rb;
|
|
int ret, nread, length, nparsed;
|
|
bool eof;
|
|
|
|
if (conn->flags & CONN_F_SEND_AND_CLOSE) {
|
|
ev_io_stop(loop, w);
|
|
return;
|
|
}
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
if (conn->ssl && !(conn->flags & CONN_F_SSL_HANDSHAKE_DONE)) {
|
|
ret = uh_ssl_handshake(conn->ssl);
|
|
if (ret == UH_SSL_ERROR_AGAIN)
|
|
return;
|
|
if (ret == UH_SSL_ERROR_UNKNOWN) {
|
|
conn_free(conn);
|
|
return;
|
|
}
|
|
conn->flags |= CONN_F_SSL_HANDSHAKE_DONE;
|
|
}
|
|
#endif
|
|
|
|
conn->activity = ev_now(loop);
|
|
|
|
length = buffer_length(rb);
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
if (conn->ssl)
|
|
ret = buffer_put_fd_ex(rb, w->fd, -1, &eof, conn_ssl_read, conn->ssl);
|
|
else
|
|
#endif
|
|
ret = buffer_put_fd(rb, w->fd, -1, &eof);
|
|
|
|
if (ret < 0) {
|
|
conn_error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
|
|
uh_log_err("read error: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (eof) {
|
|
conn_free(conn);
|
|
return;
|
|
}
|
|
|
|
nread = buffer_length(rb) - length;
|
|
|
|
nparsed = http_parser_execute(parser, &settings, (const char *)rb->data + length, nread);
|
|
if (parser->upgrade)
|
|
conn_error(conn, HTTP_STATUS_NOT_IMPLEMENTED, NULL);
|
|
else if (nparsed != nread)
|
|
conn_error(conn, HTTP_STATUS_BAD_REQUEST, http_errno_description(parser->http_errno));
|
|
}
|
|
|
|
static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
|
|
{
|
|
struct uh_connection *conn = container_of(w, struct uh_connection, timer);
|
|
ev_tstamp after = conn->activity + UHTTPD_CONNECTION_TIMEOUT - ev_now(loop);
|
|
|
|
if (conn->flags & CONN_F_SEND_AND_CLOSE) {
|
|
ev_timer_stop(loop, w);
|
|
return;
|
|
}
|
|
|
|
if (after > 0) {
|
|
ev_timer_set(w, after, 0.0);
|
|
ev_timer_start(loop, w);
|
|
return;
|
|
}
|
|
|
|
conn_error(conn, HTTP_STATUS_REQUEST_TIMEOUT, NULL);
|
|
}
|
|
|
|
struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct sockaddr_in *addr)
|
|
{
|
|
struct uh_connection *conn;
|
|
|
|
conn = calloc(1, sizeof(struct uh_connection));
|
|
if (!conn) {
|
|
uh_log_err("malloc: %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
conn->srv = srv;
|
|
conn->sock = sock;
|
|
conn->activity = ev_now(srv->loop);
|
|
|
|
memcpy(&conn->addr, addr, sizeof(struct sockaddr_in));
|
|
|
|
ev_io_init(&conn->iow, conn_write_cb, sock, EV_WRITE);
|
|
|
|
ev_io_init(&conn->ior, conn_read_cb, sock, EV_READ);
|
|
ev_io_start(srv->loop, &conn->ior);
|
|
|
|
ev_timer_init(&conn->timer, keepalive_cb, UHTTPD_CONNECTION_TIMEOUT, 0.0);
|
|
ev_timer_start(srv->loop, &conn->timer);
|
|
|
|
#if UHTTPD_SSL_SUPPORT
|
|
if (srv->ssl_ctx)
|
|
conn->ssl = uh_ssl_new(srv->ssl_ctx, sock);
|
|
#endif
|
|
|
|
http_parser_init(&conn->parser, HTTP_REQUEST);
|
|
|
|
conn->parser.data = conn;
|
|
|
|
conn->done = conn_done;
|
|
conn->send = conn_send;
|
|
conn->send_file = conn_send_file;
|
|
conn->printf = conn_printf;
|
|
conn->vprintf = conn_vprintf;
|
|
conn->send_status_line = conn_send_status_line;
|
|
conn->send_head = conn_send_head;
|
|
conn->error = conn_error;
|
|
conn->redirect = conn_redirect;
|
|
conn->serve_file = serve_file;
|
|
|
|
conn->chunk_send = conn_chunk_send;
|
|
conn->chunk_printf = conn_chunk_printf;
|
|
conn->chunk_vprintf = conn_chunk_vprintf;
|
|
conn->chunk_end = conn_chunk_end;
|
|
|
|
conn->get_addr = conn_get_addr;
|
|
conn->get_method = conn_get_method;
|
|
conn->get_method_str = conn_get_method_str;
|
|
conn->get_path = conn_get_path;
|
|
conn->get_query = conn_get_query;
|
|
conn->get_header = conn_get_header;
|
|
conn->get_body = conn_get_body;
|
|
conn->extract_body = conn_extract_body;
|
|
|
|
return conn;
|
|
}
|