Skip to content

利用asio开发的轻量级高性能http服务器

waruqi edited this page Sep 19, 2014 · 5 revisions

这个只是个demo程序,仅做参考。功能做到了最简化,只支持get请求,但是是性能还是很不错的,支持大量并发操作,资源使用量低,非常适合在嵌入式平台使用。

使用当前目录作为根目录并运行:

./httpd

指定根目录并运行:

./httpd /home/xxxx/root

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "tbox/tbox.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * macros
 */

// the httpd port
#define TB_DEMO_HTTPD_PORT                              (8080)

// the httpd session maximum count
#define TB_DEMO_HTTPD_SESSION_MAXN                      (100000)

// the httpd session timeout: 15s
#define TB_DEMO_HTTPD_SESSION_TIMEOUT                   (15000)

// the httpd session buffer maximum size
#define TB_DEMO_HTTPD_SESSION_BUFF_MAXN                 (8192)

/* //////////////////////////////////////////////////////////////////////////////////////
 * types
 */

// the httpd type
typedef struct __tb_demo_httpd_t
{
    // the root directory
    tb_char_t           root[TB_PATH_MAXN];

    // the listen port
    tb_uint16_t         port;

    // the listen aico
    tb_aico_ref_t       aico;

    // the aicp
    tb_aicp_ref_t       aicp;

}tb_demo_httpd_t;

// the httpd session type
typedef struct __tb_demo_httpd_session_t
{
    // the aico
    tb_aico_ref_t       aico;

    // the code
    tb_size_t           code;

    // the file
    tb_file_ref_t       file;

    // the path
    tb_string_t         path;

    // the line
    tb_string_t         line;

    // the index
    tb_size_t           index;

    // the cache
    tb_buffer_t         cache;

    // the httpd
    tb_demo_httpd_t*    httpd;

    // the method
    tb_size_t           method;

    // the file offset
    tb_hize_t           file_offset;

    // the content size
    tb_hize_t           content_size;

    // the version
    tb_uint32_t         version : 1;

    // keep-alive?
    tb_uint32_t         balived : 1;

    // the recv and send buffer
    tb_byte_t           buffer[TB_DEMO_HTTPD_SESSION_BUFF_MAXN];

}tb_demo_httpd_session_t;

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
static tb_char_t const* tb_demo_httpd_code_cstr(tb_size_t code)
{
    // done
    tb_char_t const* cstr = tb_null;
    switch (code)
    {
    case TB_HTTP_CODE_OK:                       cstr = "OK"; break;
    case TB_HTTP_CODE_BAD_REQUEST:              cstr = "Bad Request"; break;
    case TB_HTTP_CODE_NOT_FOUND:                cstr = "Not Found"; break;
    case TB_HTTP_CODE_NOT_IMPLEMENTED:          cstr = "Not Implemented"; break;
    case TB_HTTP_CODE_NOT_MODIFIED:             cstr = "Not Modified"; break;
    case TB_HTTP_CODE_INTERNAL_SERVER_ERROR:    cstr = "Internal Server Error"; break;
    default: break;
    }

    // check
    tb_assert_abort(cstr);

    // ok?
    return cstr;
}
static tb_bool_t tb_demo_httpd_aico_clos(tb_aice_ref_t aice)
{
    // check
    tb_assert_and_check_return_val(aice && aice->aico && aice->code == TB_AICE_CODE_CLOS, tb_false);

    // trace
    tb_trace_d("aico[%p]: clos: %s", aice->aico, tb_state_cstr(aice->state));

    // exit aico
    tb_aico_exit(aice->aico);

    // ok
    return tb_true;
}
static tb_void_t tb_demo_httpd_session_exit(tb_demo_httpd_session_t* session)
{
    // check
    tb_assert_and_check_return(session);

    // clos aico
    if (session->aico) tb_aico_clos(session->aico, tb_demo_httpd_aico_clos, tb_null);
    session->aico = tb_null;

    // exit file
    if (session->file) tb_file_exit(session->file);
    session->file = tb_null;

    // exit cache
    tb_buffer_exit(&session->cache);
    
    // exit line
    tb_string_exit(&session->line);
 
    // exit path
    tb_string_exit(&session->path);

    // clear status
    session->code           = TB_HTTP_CODE_OK;
    session->index          = 0;
    session->httpd          = tb_null;
    session->method         = TB_HTTP_METHOD_GET;
    session->version        = 1;
    session->balived        = 0;
    session->file_offset    = 0;
    session->content_size   = 0;
    
    // exit it
    tb_free(session);
}
static tb_void_t tb_demo_httpd_session_keep(tb_demo_httpd_session_t* session)
{
    // check
    tb_assert_and_check_return(session);

    // exit file
    if (session->file) tb_file_exit(session->file);
    session->file = tb_null;

    // clear cache
    tb_buffer_clear(&session->cache);
    
    // clear line
    tb_string_clear(&session->line);
 
    // clear path
    tb_string_clear(&session->path);

    // clear some status
    session->code           = TB_HTTP_CODE_OK;
    session->index          = 0;
    session->method         = TB_HTTP_METHOD_GET;
    session->file_offset    = 0;
    session->content_size   = 0;
}
static tb_demo_httpd_session_t* tb_demo_httpd_session_init(tb_demo_httpd_t* httpd, tb_aico_ref_t aico)
{
    // check
    tb_assert_and_check_return_val(httpd && httpd->aicp && aico, tb_null);

    // done
    tb_bool_t                   ok = tb_false;
    tb_demo_httpd_session_t*    session = tb_null;
    do
    {
        // make session
        session = tb_malloc0_type(tb_demo_httpd_session_t);
        tb_assert_and_check_break(session);

        // init session
        session->httpd          = httpd;
        session->aico           = aico;
        session->code           = TB_HTTP_CODE_OK;
        session->version        = 1;
        session->balived        = 0;
        session->method         = TB_HTTP_METHOD_GET;
        session->file_offset    = 0;
        session->content_size   = 0;

        // init path
        if (!tb_string_init(&session->path)) break;

        // init line
        if (!tb_string_init(&session->line)) break;

        // init cache
        if (!tb_buffer_init(&session->cache)) break;

        // init timeout
        tb_aico_timeout_set(session->aico, TB_AICO_TIMEOUT_SEND, TB_DEMO_HTTPD_SESSION_TIMEOUT);
        tb_aico_timeout_set(session->aico, TB_AICO_TIMEOUT_RECV, TB_DEMO_HTTPD_SESSION_TIMEOUT);

        // ok
        ok = tb_true;

    } while (0);

    // failed?
    if (!ok)
    {
        // exit session
        if (session) tb_demo_httpd_session_exit(session);
        session = tb_null;
    }

    // ok?
    return session;
}
static tb_bool_t tb_demo_httpd_session_head_recv(tb_aice_ref_t aice);
static tb_void_t tb_demo_httpd_session_resp_exit(tb_demo_httpd_session_t* session)
{
    // keep-alived?
    tb_bool_t ok = tb_false;
    if (session->balived && session->aico)
    {
        // keep session
        tb_demo_httpd_session_keep(session);

        // recv the header
        ok = tb_aico_recv(session->aico, session->buffer, sizeof(session->buffer), tb_demo_httpd_session_head_recv, session);
    }

    // exit session
    if (!ok) tb_demo_httpd_session_exit(session);
}
static tb_bool_t tb_demo_httpd_session_resp_send_file(tb_aice_ref_t aice)
{
    // check
    tb_assert_and_check_return_val(aice && aice->code == TB_AICE_CODE_SENDF, tb_false);

    // the session
    tb_demo_httpd_session_t* session = (tb_demo_httpd_session_t*)aice->priv;
    tb_assert_and_check_return_val(session, tb_false);

    // done
    tb_bool_t ok = tb_false;
    do
    {
        // ok?
        tb_check_break(aice->state == TB_STATE_OK);

        // trace
        tb_trace_d("resp_send_file[%p]: real: %lu, size: %llu", aice->aico, aice->u.sendf.real, aice->u.sendf.size);

        // save offset
        session->file_offset += aice->u.sendf.real;

        // continue to send it?
        if (aice->u.sendf.real < aice->u.sendf.size)
        {
            // send file
            ok = tb_aico_sendf(aice->aico, session->file, session->file_offset, aice->u.sendf.size - aice->u.sendf.real, tb_demo_httpd_session_resp_send_file, session);
        }

    } while (0);

    // finished or closed or failed?
    if (!ok)
    {
        // trace
        tb_trace_d("resp_send_file[%p]: state: %s", aice->aico, tb_state_cstr(aice->state));

        // exit response
        tb_demo_httpd_session_resp_exit(session);
    }

    // ok
    return tb_true;
}
static tb_bool_t tb_demo_httpd_session_resp_send_head(tb_aice_ref_t aice)
{
    // check
    tb_assert_and_check_return_val(aice && aice->aico && aice->code == TB_AICE_CODE_SEND, tb_false);

    // the session
    tb_demo_httpd_session_t* session = (tb_demo_httpd_session_t*)aice->priv;
    tb_assert_and_check_return_val(session, tb_false);

    // done
    tb_bool_t ok = tb_false;
    do
    {
        // ok?
        tb_check_break(aice->state == TB_STATE_OK);

        // trace
        tb_trace_d("resp_send_head[%p]: real: %lu, size: %lu", aice->aico, aice->u.send.real, aice->u.send.size);

        // check data
        tb_assert_and_check_break(aice->u.send.data && aice->u.send.size);

        // not finished? 
        if (aice->u.send.real < aice->u.send.size)
        {
            // continue to send it
            ok = tb_aico_send(aice->aico, aice->u.send.data, aice->u.send.size - aice->u.send.real, tb_demo_httpd_session_resp_send_head, session);
        }
        // send file if exists
        else if (session->file)
        {
            // send file
            ok = tb_aico_sendf(aice->aico, session->file, session->file_offset, tb_file_size(session->file), tb_demo_httpd_session_resp_send_file, session);
        }

    } while (0);

    // not continue?
    if (!ok)
    {
        // trace
        tb_trace_d("resp_send_head[%p]: state: %s", aice->aico, tb_state_cstr(aice->state));

        // exit response
        tb_demo_httpd_session_resp_exit(session);
    }

    // ok
    return tb_true;
}
static tb_bool_t tb_demo_httpd_session_resp_send_done(tb_demo_httpd_session_t* session)
{
    // check
    tb_assert_and_check_return_val(session, tb_false);
 
    // format the error info
    tb_long_t size = tb_snprintf(   (tb_char_t*)session->buffer
                                ,   sizeof(session->buffer) - 1
                                ,   "HTTP/1.%u %lu %s\r\n"
                                    "Server: %s\r\n"
                                    "Content-Type: text/html\r\n"
                                    "Content-Length: %llu\r\n"
                                    "Connection: %s\r\n"
                                    "\r\n"
                                ,   session->version
                                ,   session->code
                                ,   tb_demo_httpd_code_cstr(session->code)
                                ,   TB_VERSION_SHORT_STRING
                                ,   session->file? tb_file_size(session->file) : 0
                                ,   session->balived? "keep-alive" : "close");

    tb_assert_and_check_return_val(size > 0, tb_false);

    // end
    session->buffer[size] = '\0';

    // trace
    tb_trace_d("response[%p]: %s", session->aico, session->buffer);

    // send the error info
    return tb_aico_send(session->aico, session->buffer, size, tb_demo_httpd_session_resp_send_head, session);
}
static tb_bool_t tb_demo_httpd_session_reqt_done(tb_demo_httpd_session_t* session)
{
    // check
    tb_assert_and_check_return_val(session && session->aico, tb_false);

    // done
    tb_bool_t ok = tb_false;
    do
    {
        // check 
        tb_check_break(session->code == TB_HTTP_CODE_OK);
        tb_assert_and_check_break(session->httpd);

        // get?
        if (session->method == TB_HTTP_METHOD_GET)
        {
            // check path
            tb_check_break_state(tb_string_size(&session->path), session->code, TB_HTTP_CODE_BAD_REQUEST);

            // the path
            tb_char_t const* path = tb_string_cstr(&session->path);
            tb_check_break_state(!session->file && path, session->code, TB_HTTP_CODE_INTERNAL_SERVER_ERROR);

            // the full path
            tb_char_t full[TB_PATH_MAXN] = {0};
            tb_long_t size = tb_snprintf(full, sizeof(full) - 1, "%s%s%s", session->httpd->root, path[0] != '/'? "/" : "", path);
            tb_assert_abort(size > 0);

            // end
            full[size] = '\0';

            // trace
            tb_trace_d("reqt_done[%p]: full path: %s", session->aico, full);
            
            // init file
            session->file = tb_file_init(full, TB_FILE_MODE_RO | TB_FILE_MODE_BINARY | TB_FILE_MODE_ASIO);
            tb_check_break_state(session->file, session->code, TB_HTTP_CODE_NOT_FOUND);

            // send the file
            if (!tb_demo_httpd_session_resp_send_done(session)) return tb_false;

            // ok
            ok = tb_true;
        }
        else
        {
            // not implemented
            session->code = TB_HTTP_CODE_NOT_IMPLEMENTED;
            break;
        }

    } while (0);

    // error?
    if (!ok)
    {
        // save code
        if (session->code == TB_HTTP_CODE_OK) session->code = TB_HTTP_CODE_INTERNAL_SERVER_ERROR;

        // send the error info
        if (!tb_demo_httpd_session_resp_send_done(session)) return tb_false;
    }

    // ok
    return tb_true;
}
static tb_bool_t tb_demo_httpd_session_head_done(tb_demo_httpd_session_t* session)
{
    // check
    tb_assert_and_check_return_val(session, tb_false);

    // the line and size
    tb_char_t const*    line = tb_string_cstr(&session->line);
    tb_size_t           size = tb_string_size(&session->line);
    tb_assert_and_check_return_val(line && size, tb_false);

    // the first line? 
    tb_char_t const* p = line;
    if (!session->index)
    {
        // check protocol
        if (tb_stristr(line, "HTTP/1.1")) session->version = 1;
        else if (tb_stristr(line, "HTTP/1.0")) session->version = 0;
        // bad request?
        else
        {
            // save code
            session->code = TB_HTTP_CODE_BAD_REQUEST;
            return tb_false;
        }

        // parse get
        if (!tb_strnicmp(line, "GET", 3))
        {
            // save the method
            session->method = TB_HTTP_METHOD_GET;

            // skip the method
            p += 3;
        }
        // parse post
        else if (!tb_strnicmp(line, "POST", 4))
        {
            // save the method
            session->method = TB_HTTP_METHOD_POST;

            // save code
            session->code = TB_HTTP_CODE_NOT_IMPLEMENTED;

            // skip the method
            p += 4;
        }
        else
        {
            // trace
            tb_trace_e("the method: %s is not supported now!", line);

            // save code
            session->code = TB_HTTP_CODE_NOT_IMPLEMENTED;
        }

        // get or post? parse the path
        if (    session->method == TB_HTTP_METHOD_GET
            ||  session->method == TB_HTTP_METHOD_POST)
        {
            // skip space
            while (*p && tb_isspace(*p)) p++;

            // append path
            while (*p && !tb_isspace(*p)) tb_string_chrcat(&session->path, *p++);
        }
    }
    // key: value?
    else
    {
        // seek to value
        while (*p && *p != ':') p++;
        tb_assert_and_check_return_val(*p, tb_false);
        p++; while (*p && tb_isspace(*p)) p++;

        // no value
        tb_check_return_val(*p, tb_true);

        // parse content-length
        if (!tb_strnicmp(line, "Content-Length", 14))
        {
            // the content size
            session->content_size = tb_stou64(p);
        }
        // parse connection
        else if (!tb_strnicmp(line, "Connection", 10))
        {
            // keep-alive?
            session->balived = !tb_stricmp(p, "keep-alive")? 1 : 0;
        }
        // parse accept-encoding
        else if (!tb_strnicmp(line, "Accept-Encoding", 15))
        {
        }
        // parse accept
        else if (!tb_strnicmp(line, "Accept", 6))
        {
        }
        // parse cookie
        else if (!tb_strnicmp(line, "Cookie", 6))
        {
        }
        // parse range
        else if (!tb_strnicmp(line, "Range", 5))
        {
            // save code
            session->code = TB_HTTP_CODE_NOT_IMPLEMENTED;
        }
        // parse host
        else if (!tb_strnicmp(line, "Host", 4))
        {
        }
    }

    // ok
    return tb_true;
}
static tb_bool_t tb_demo_httpd_session_head_recv(tb_aice_ref_t aice)
{
    // check
    tb_assert_and_check_return_val(aice && aice->aico && aice->code == TB_AICE_CODE_RECV, tb_false);

    // the session
    tb_demo_httpd_session_t* session = (tb_demo_httpd_session_t*)aice->priv;
    tb_assert_and_check_return_val(session, tb_false);

    // done
    tb_bool_t state = tb_false;
    do
    {
        // ok?
        tb_check_break(aice->state == TB_STATE_OK);

        // trace
        tb_trace_d("head_recv[%p]: real: %lu, size: %lu", aice->aico, aice->u.recv.real, aice->u.recv.size);

        // check
        tb_assert_and_check_break(aice->u.recv.data);
            
        // done
        tb_long_t           ok = 0;
        tb_char_t           ch = '\0';
        tb_char_t const*    p = (tb_char_t const*)aice->u.recv.data;
        tb_char_t const*    e = p + aice->u.recv.size;
        while (p < e)
        {
            // the char
            ch = *p++;

            // error end?
            if (!ch)
            {
                ok = -1;
                break;
            }

            // append char to line
            if (ch != '\n') tb_string_chrcat(&session->line, ch);
            // is line end?
            else
            {
                // strip '\r' if exists
                tb_char_t const*    pb = tb_string_cstr(&session->line);
                tb_size_t           pn = tb_string_size(&session->line);
                if (!pb || !pn)
                {
                    ok = -1;
                    tb_assert(0);
                    break;
                }

                // line end? strip '\r\n'
                if (pb[pn - 1] == '\r') tb_string_strip(&session->line, pn - 1);

                // trace
                tb_trace_d("head_recv[%p]: %s", aice->aico, pb);
    
                // end?
                if (!tb_string_size(&session->line)) 
                {
                    // ok
                    ok = 1;
                    break;
                }

                // done the head request
                if (!tb_demo_httpd_session_head_done(session)) 
                {   
                    // error
                    ok = -1;
                    break;
                }

                // clear line 
                tb_string_clear(&session->line);

                // update index
                session->index++;
            }
        }

        // continue?
        if (!ok) 
        {
            // recv the header
            if (!tb_aico_recv(aice->aico, session->buffer, sizeof(session->buffer), tb_demo_httpd_session_head_recv, session)) break;
        }
        // end?
        else if (ok > 0) 
        {
            // trace
            tb_trace_d("head_recv[%p]: end, left: %lu", aice->aico, e - p);

            // save the left data to the cache
            tb_buffer_memncpy(&session->cache, (tb_byte_t const*)p, e - p);
 
            // trace
            tb_trace_d("head_recv[%p]: ok", aice->aico);
 
            // done request
            if (!tb_demo_httpd_session_reqt_done(session)) break;
        }
        // failed?
        else 
        {
            // trace
            tb_trace_e("head_recv[%p]: failed", aice->aico);
            break;
        }

        // ok
        state = tb_true;

    } while (0);

    // closed or failed?
    if (!state)
    {
        // trace
        tb_trace_d("head_recv[%p]: state: %s", aice->aico, tb_state_cstr(aice->state));

        // exit session
        tb_demo_httpd_session_exit(session);
    }

    // ok
    return tb_true;
}
static tb_void_t tb_demo_httpd_exit(tb_demo_httpd_t* httpd)
{
    // check
    tb_assert_and_check_return(httpd);

    // trace
    tb_trace_d("exit");

    // clear aico
    httpd->aico = tb_null;

    // exit it
    tb_free(httpd);
}
static tb_demo_httpd_t* tb_demo_httpd_init(tb_char_t const* root)
{
    // done
    tb_bool_t           ok = tb_false;
    tb_demo_httpd_t*    httpd = tb_null;
    do
    {
        // make httpd
        httpd = tb_malloc0_type(tb_demo_httpd_t);
        tb_assert_and_check_break(httpd);

        // init root
        if (root) tb_strlcpy(httpd->root, root, sizeof(httpd->root));
        else tb_directory_curt(httpd->root, sizeof(httpd->root));
        httpd->root[sizeof(httpd->root) - 1] = '\0';
        tb_assert_and_check_break(tb_file_info(httpd->root, tb_null));

        // init port
        httpd->port = TB_DEMO_HTTPD_PORT;

        // trace
        tb_trace_d("init: %s: %u", httpd->root, httpd->port);

        // init aicp
        httpd->aicp = tb_aicp();
        tb_assert_and_check_break(httpd->aicp);

        // init aico
        httpd->aico = tb_aico_init(httpd->aicp);
        tb_assert_and_check_break(httpd->aico);

        // open aico
        if (!tb_aico_open_sock_from_type(httpd->aico, TB_SOCKET_TYPE_TCP)) break;

        // bind port
        if (!tb_socket_bind(tb_aico_sock(httpd->aico), tb_null, httpd->port)) break;

        // listen sock
        if (!tb_socket_listen(tb_aico_sock(httpd->aico), TB_DEMO_HTTPD_SESSION_MAXN >> 2)) break;

        // ok
        ok = tb_true;

    } while (0);

    // failed?
    if (!ok)
    {
        // exit httpd
        if (httpd) tb_demo_httpd_exit(httpd);
        httpd = tb_null;
    }

    // ok?
    return httpd;
}
static tb_bool_t tb_demo_httpd_acpt(tb_aice_ref_t aice)
{
    // check
    tb_assert_and_check_return_val(aice && aice->aico && aice->code == TB_AICE_CODE_ACPT, tb_false);

    // the httpd
    tb_demo_httpd_t* httpd = (tb_demo_httpd_t*)aice->priv;
    tb_assert_and_check_return_val(httpd && httpd->aicp, tb_false);

    // done
    tb_bool_t                   ok = tb_false;
    tb_demo_httpd_session_t*    session = tb_null;
    do
    {
        // ok?
        tb_check_break(aice->state == TB_STATE_OK);
    
        // check
        tb_assert_and_check_break(aice->u.acpt.aico);

        // trace
        tb_trace_d("acpt[%p]: aico: %p, addr: %u.%u.%u.%u, port: %u", aice->aico, aice->u.acpt.aico, tb_ipv4_u8x4(aice->u.acpt.addr), aice->u.acpt.port);

        // init the session
        session = tb_demo_httpd_session_init(httpd, aice->u.acpt.aico);
        tb_assert_and_check_break(session && session->aico);

        // recv the header
        if (!tb_aico_recv(session->aico, session->buffer, sizeof(session->buffer), tb_demo_httpd_session_head_recv, session)) break;

        // ok
        ok = tb_true;

    } while (0);

    // failed?
    if (!ok)
    {
        // trace
        tb_trace_d("acpt[%p]: state: %s", aice->aico, tb_state_cstr(aice->state));

        // exit session
        if (session) tb_demo_httpd_session_exit(session);
        session = tb_null;

        // clos aico
        if (aice->aico) tb_aico_clos(aice->aico, tb_demo_httpd_aico_clos, tb_null);
    }

    // ok
    return tb_true;
}
static tb_void_t tb_demo_httpd_done(tb_demo_httpd_t* httpd)
{
    // check
    tb_assert_and_check_return(httpd && httpd->aicp && httpd->aico);

    // done listen
    if (!tb_aico_acpt(httpd->aico, tb_demo_httpd_acpt, httpd)) return ;

    // wait some time
    getchar();
}


/* //////////////////////////////////////////////////////////////////////////////////////
 * main
 */
tb_int_t main(tb_int_t argc, tb_char_t** argv)
{
    // init tbox
    if (!tb_init(tb_null, tb_null, 0)) return 0;

    // init httpd
    tb_demo_httpd_t* httpd = tb_demo_httpd_init(argv[1]);
    if (httpd)
    {
        // done httpd
        tb_demo_httpd_done(httpd);

        // exit httpd
        tb_demo_httpd_exit(httpd);
    }

    // exit tbox
    tb_exit();
    return 0;
}
Clone this wiki locally