lighttpd源代码分析1--http请求处理流程

lighttpd通过状态机处理HTTP请求。从CON_STATE_REQUEST_START开始,进行初始化工作,接着进入CON_STATE_READ状态,读取HTTP请求头。如果启用SSL,调用connection_handle_read_ssl处理。非SSL情况下,根据系统调用读取数据。如果读取到数据,检查文件是否存在,处理目录重定向和权限问题。lighttpd使用插件模式处理各种请求,如mod_staticfile处理静态文件传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理

lighttpd的事件处理是由一个状态机完成的。                                                                                   

  • connect
    waiting for a connection
  • reqstart
    init the read-idle timer
  • read
    read http-request-header from network
  • reqend
    parse request
  • readpost
    read http-request-content from network
  • handlereq
    handle the request internally (might result in sub-requests)
  • respstart
    prepare response header
  • write
    write response-header + content to network
  • respend
    cleanup environment, log request
  • error
    reset connection (incl. close())
  • close
    close connection (handle lingering close)

状态机的状态转变图为:internal-http-states 

其中的绿线代表的是一个简单的GET请求的状态变化线路;而灰线表示的是POST请求。图中的状态并不都是稳定的状态,有的是个瞬时状态;也就是说一个外部事件会触发若干状态的转换。

数据处理流程

当客户端发起一个请求时,lighttpd为它建立一个新的连接,同时置该连接的状态为CON_STATE_REQUEST_START。

connection *connection_accept(server *srv, server_socket *srv_socket) {
    /* accept everything */

    /* search an empty place */
    int cnt;
    sock_addr cnt_addr;
    socklen_t cnt_len;
    /* accept it and register the fd */

    /**
     * check if we can still open a new connections
     *
     * see #1216
     */

    if (srv->conns->used >= srv->max_conns) {
        return NULL;
    }

    cnt_len = sizeof(cnt_addr);

    if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {
        switch (errno) {
        case EAGAIN:
#if EWOULDBLOCK != EAGAIN
        case EWOULDBLOCK:
#endif
        case EINTR:
            /* we were stopped _before_ we had a connection */
        case ECONNABORTED: /* this is a FreeBSD thingy */
            /* we were stopped _after_ we had a connection */
            break;
        case EMFILE:
            /* out of fds */
            break;
        default:
            log_error_write(srv, __FILE__, __LINE__, "ssd", "accept failed:", strerror(errno), errno);
        }
        return NULL;
    } else {
        connection *con;

        srv->cur_fds++;

        /* ok, we have the connection, register it */
#if 0
        log_error_write(srv, __FILE__, __LINE__, "sd",
                "appected()", cnt);
#endif
        srv->con_opened++;

        con = connections_get_new_connection(srv);

        con->fd = cnt;
        con->fde_ndx = -1;
#if 0
        gettimeofday(&(con->start_tv), NULL);
#endif
        fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);

        connection_set_state(srv, con, CON_STATE_REQUEST_START);

        con->connection_start = srv->cur_ts;
        con->dst_addr = cnt_addr;
        buffer_copy_string(con->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
        con->srv_socket = srv_socket;

        if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
            log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
            return NULL;
        }
#ifdef USE_OPENSSL
        /* connect FD to SSL */
        if (srv_socket->is_ssl) {
            if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) {
                log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
                        ERR_error_string(ERR_get_error(), NULL));

                return NULL;
            }

            SSL_set_accept_state(con->ssl);
            con->conf.is_ssl=1;

            if (1 != (SSL_set_fd(con->ssl, cnt))) {
                log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
                        ERR_error_string(ERR_get_error(), NULL));
                return NULL;
            }
        }
#endif
        return con;
    }
}

connection_accept新建一个连接后流程回到函数network_server_handle_fdevent(network.c文件)

for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
        handler_t r;

        connection_state_machine(srv, con); /*新建连接后马上进入状态机处理*/

        switch(r = plugins_call_handle_joblist(srv, con)) {
        case HANDLER_FINISHED:
        case HANDLER_GO_ON:
            break;
        default:
            log_error_write(srv, __FILE__, __LINE__, "d", r);
            break;
        }
    }

状态机处理

对于新的连接,初始状态是CON_STATE_REQUEST_START

状态reqstart

  • case CON_STATE_REQUEST_START: /* transient */
                if (srv->srvconf.log_state_handling) {
                    log_error_write(srv, __FILE__, __LINE__, "sds",
                            "state for fd", con->fd, connection_get_state(con->state));
                }
  •             con->request_start = srv->cur_ts;
                con->read_idle_ts = srv->cur_ts;
  •             con->request_count++;
                con->loops_per_request = 0;
  •             connection_set_state(srv, con, CON_STATE_READ);
  •             /* patch con->conf.is_ssl if the connection is a ssl-socket already */
  • #ifdef USE_OPENSSL
                con->conf.is_ssl = srv_sock->is_ssl;
    #endif
  •             break;

该状态下主要做些初始化的准备工作。

需要注意的是这里将connetion中的两个字段保存为当前时间,request_start和read_idle_ts, 前者存放的是接收连接的时间, 后者用于超时判断,lighttpd设置了一个每一秒一次的定时器, 每次定时器到时就依次轮询所有的连接, 判断是否超时, 而判断的依据就是拿当前的时间 - connection的read_idle_ts字段, 看看是否超时:

// 如果当前时间与read_idle_ts之差大于max_read_idle, 超时
if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) {
/* time - out */
                                connection_set_state(srv, con, CON_STATE_ERROR);
                                changed = 1;
                            }

这些该保存的数据都保存完毕之后, 状态机进入下一个状态,CON_STATE_READ, 也就是开始接收数据。

状态read

case CON_STATE_READ:
            if (srv->srvconf.log_state_handling) {
                log_error_write(srv, __FILE__, __LINE__, "sds",
                        "state for fd", con->fd, connection_get_state(con->state));
            }

            connection_handle_read_state(srv, con);
            break;

看看connection_handle_read_state都做了些什么。

static int connection_handle_read_state(server *srv, connection *con)  {
    connection_state_t ostate = con->state;
    chunk *c, *last_chunk;
    off_t last_offset;
    chunkqueue *cq = con->read_queue;
    chunkqueue *dst_cq = con->request_content_queue;
    int is_closed = 0; /* the connection got closed, if we don't have a complete header, -> error */

    if (con->is_readable) {
        con->read_idle_ts = srv->cur_ts;

        switch(connection_handle_read(srv, con)) {
        case -1:
            return -1;
        case -2:
            is_closed = 1;
            break;
        default:
            break;
        }
    }

第一步是从网络中读取请求数据——connection_handle_read

static int connection_handle_read(server *srv, connection *con) {
    int len;
    buffer *b;
    int toread;

    if (con->conf.is_ssl) {                  /*启用了SSL*/
        return connection_handle_read_ssl(srv, con);
    }

#if defined(__WIN32)
    b = chunkqueue_get_append_buffer(con->read_queue);
    buffer_prepare_copy(b, 4 * 1024);
    len = recv(con->fd, b->ptr, b->size - 1, 0);
#else
    if (ioctl(con->fd, FIONREAD, &toread) || toread == 0) { /* 有多少数据待读*/
        b = chunkqueue_get_append_buffer(con->read_queue);
        buffer_prepare_copy(b, 4 * 1024);
    } else {
        if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT;
        b = chunkqueue_get_append_buffer(con->read_queue);
        buffer_prepare_copy(b, toread + 1);
    }
    len = read(con->fd, b->ptr, b->size - 1);
#endif

    if (len < 0) {
        con->is_readable = 0;

        if (errno == EAGAIN) return 0;/*当前没有数据可读*/
        if (errno == EINTR) {     /* 被信号中断*/
            /* we have been interrupted before we could read */
            con->is_readable = 1;
            return 0;
        }

        if (errno != ECONNRESET) {
            /* expected for keep-alive */
            log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
        }

        connection_set_state(srv, con, CON_STATE_ERROR);

        return -1;
    } else if (len == 0) {
        con->is_readable = 0;
        /* the other end close the connection -> KEEP-ALIVE */

        /* pipelining */

        return -2;  /* 连接被对端关闭,返回-2*/
    } else if ((size_t)len < b->size - 1) {
        /* we got less then expected, wait for the next fd-event */

        con->is_readable = 0;
    }

    b->used = len;
    b->ptr[b->used++] = '/0';

    con->bytes_read += len;
#if 0
    dump_packet(b->ptr, len);
#endif

    return 0;
}

read返回-1并不一定是没有数据可读;函数最后在读到的数据末尾添加一个字符串结束符('/0')。

继续回到函数connection_handle_read_state中, 看接下来的代码:

// 这一段循环代码用于更新read chunk队列,没有使用的chunk都归入未使用chunk链中
/* the last chunk might be empty */
for (c = cq->first; c;) {
if (cq->first == c && c->mem->used == 0) {
// 如果第一个chunk是空的并且没有使用过
/* the first node is empty */
/* and it is empty, move it to unused */
// 则chunk队列的第一个chunk为下一个chunk
            cq->first = c->next;
// 第一个chunk为NULL, 那么最后一个chunk为NULL
if (cq->first == NULL) 
                cq->last = NULL;
// 更新chunk队列中对于未使用chunk的记录
            c->next = cq->unused;
            cq->unused = c;
            cq->unused_chunks++;
// 重新指向第一个chunk
            c = cq->first;
        } else if (c->next && c->next->mem->used == 0) {
            chunk *fc;
// 如果下一个chunk存在而且未使用过
/* next node is the last one */
/* and it is empty, move it to unused */
// 将这个chunk从队列中分离出去, 同时fc指向这个未使用的chunk
            fc = c->next;
            c->next = fc->next;
// 将这个未使用的chunk(fc所指)保存到未使用chunk链中
            fc->next = cq->unused;
            cq->unused = fc;
            cq->unused_chunks++;
/* the last node was empty */
// 如果c的下一个chunk是空的, 那么chunk队列的最后一个chunk就是c了
if (c->next == NULL) {
                cq->last = c;
            }
// 继续往下走
            c = c->next;
        } else {
// 继续往下走
            c = c->next;
        }
    }

每个connection结构体中, 有一个read_queue成员, 该成员是chunkqueue类型的, 一个connection读入的数据都会保存在这个成员中。

switch(ostate) {
    case CON_STATE_READ:
        /* if there is a /r/n/r/n in the chunkqueue
         *  /r/n/r/n用来判断一个请求数据包的结束
         * scan the chunk-queue twice
         * 1. to find the /r/n/r/n
         * 2. to copy the header-packet
         *
         */

        last_chunk = NULL;
        last_offset = 0;

for循环用来寻找/r/n/r/n,有可能在一个chunk里,也有可能跨越chunk。

        for (c = cq->first; !last_chunk && c; c = c->next) {
            buffer b;
            size_t i;

            b.ptr = c->mem->ptr + c->offset;
            b.used = c->mem->used - c->offset;

            for (i = 0; !last_chunk && i < b.used; i++) {
                char ch = b.ptr[i];
                size_t have_chars = 0;

                switch (ch) {
                case '/r':
                    /* we have to do a 4 char lookup */
                    have_chars = b.used - i - 1;

                    if (have_chars >= 4) {
                        /* all chars are in this buffer */

                        if (0 == strncmp(b.ptr + i, "/r/n/r/n", 4)) {
                            /* found */
                            last_chunk = c;
                            last_offset = i + 4;

                            break;
                        }
                    } else {
                        chunk *lookahead_chunk = c->next;
                        size_t missing_chars;
                        /* looks like the following chars are not in the same chunk */

                        missing_chars = 4 - have_chars;

                        if (lookahead_chunk && lookahead_chunk->type == MEM_CHUNK) {
                            /* is the chunk long enough to contain the other chars ? */

                            if (lookahead_chunk->mem->used > missing_chars) {
                                if (0 == strncmp(b.ptr + i, "/r/n/r/n", have_chars) &&
                                    0 == strncmp(lookahead_chunk->mem->ptr, "/r/n/r/n" + have_chars, missing_chars)) {

                                    last_chunk = lookahead_chunk;
                                    last_offset = missing_chars;

                                    break;
                                }
                            } else {
                                /* a splited /r /n */
                                break;
                            }
                        }
                    }

                    break;
                }
            }
        }

从first开始到last之间的chunk里的数据都是该次请求的头部(对于POST等可能有其它数据,这里先提取头部出来保存到con->request.request)。

        /* found */
        if (last_chunk) {
            buffer_reset(con->request.request);

            for (c = cq->first; c; c = c->next) {
                buffer b;

                b.ptr = c->mem->ptr + c->offset;
                b.used = c->mem->used - c->offset;

                if (c == last_chunk) {
                    b.used = last_offset + 1;
                }

                buffer_append_string_buffer(con->request.request, &b);

                if (c == last_chunk) {
                    c->offset += last_offset;

                    break;
                } else {
                    /* the whole packet was copied */
                    c->offset = c->mem->used - 1;
                }
            }

            connection_set_state(srv, con, CON_STATE_REQUEST_END);
        } else if (chunkqueue_length(cq) > 64 * 1024) {
            log_error_write(srv, __FILE__, __LINE__, "s", "oversized request-header -> sending Status 414");

            con->http_status = 414; /* Request-URI too large */
            con->keep_alive = 0;
            connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
        }
        break;

/* the connection got closed and we didn't got enough data to leave one of the READ states
     * the only way is to leave here */
    if (is_closed && ostate == con->state) {
        connection_set_state(srv, con, CON_STATE_ERROR);
    }

    chunkqueue_remove_finished_chunks(cq); /*该请求数据已经读取,从队列中删除*/

    return 0;

状态reqend

case CON_STATE_REQUEST_END: /* transient */
            if (srv->srvconf.log_state_handling) {
                log_error_write(srv, __FILE__, __LINE__, "sds",
                        "state for fd", con->fd, connection_get_state(con->state));
            }

            if (http_request_parse(srv, con)) {
                /* we have to read some data from the POST request */

                connection_set_state(srv, con, CON_STATE_READ_POST);

                break;
            }

            connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);

            break;

这个状态的工作是对读到的request头部解析,然后进入请求处理状态CON_STATE_HANDLE_REQUEST;当然对于POST请求的话,http_request_parse会返回1,从而进入CON_STATE_READ_POST。

看看http_request_parse函数:

首先解析的是请求行(Request-Line)

Request-Line =Method SP Request-URL SP HTTP-Version CRLF

int http_request_parse(server *srv, connection *con) {
    char *uri = NULL, *proto = NULL, *method = NULL, con_length_set;
    int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding;
    char *value = NULL, *key = NULL;

    enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET;

    int line = 0;

    int request_line_stage = 0;
    size_t i, first;

    int done = 0;

    /*
     * Request: "^(GET|POST|HEAD) ([^ ]+(//?[^ ]+|)) (HTTP/1//.[01])$"
     * Option : "^([-a-zA-Z]+): (.+)$"
     * End    : "^$"
     */

    if (con->conf.log_request_header) {
        log_error_write(srv, __FILE__, __LINE__, "sdsdSb",
                "fd:", con->fd,
                "request-len:", con->request.request->used,
                "/n", con->request.request);
    }

    if (con->request_count > 1 &&
        con->request.request->ptr[0] == '/r' &&
        con->request.request->ptr[1] == '/n') {
        /* we are in keep-alive and might get /r/n after a previous POST request.*/

        buffer_copy_string_len(con->parse_request, con->request.request->ptr + 2, con->request.request->used - 1 - 2);
    } else {
        /* fill the local request buffer */
        buffer_copy_string_buffer(con->parse_request, con->request.request);
    }

    keep_alive_set = 0;
    con_length_set = 0;

    /* parse the first line of the request
     *
     * should be:
     *
     * /r/n
     * */
    for (i = 0, first = 0; i < con->parse_request->used && line == 0; i++) {
        char *cur = con->parse_request->ptr + i;

        switch(*cur) {
        case '/r': /*到达request-line的结束标记处*/
            if (con->parse_request->ptr[i+1] == '/n') {
                http_method_t r;
                char *nuri = NULL;
                size_t j;

               /* /r/n -> /0/0 */
                con->parse_request->ptr[i] = '/0';
                con->parse_request->ptr[i+1] = '/0';

                buffer_copy_string_len(con->request.request_line, con->parse_request->ptr, i);

                if (request_line_stage != 2) {   /*解析结果不完整*/
                    con->http_status = 400;
                    con->response.keep_alive = 0;
                    con->keep_alive = 0;

                    if (srv->srvconf.log_request_header_on_error) {
                        log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400");
                        log_error_write(srv, __FILE__, __LINE__, "Sb",
                                "request-header:/n",
                                con->request.request);
                    }
                    return 0;
                }

                得到正确的解析结果

                proto = con->parse_request->ptr + first;

               把这3个字段用字符串结束符分割开来

                *(uri - 1) = '/0';       
                *(proto - 1) = '/0';

                /* we got the first one :)  方法解析*/
                if (-1 == (r = get_http_method_key(method))) {
                    con->http_status = 501;
                    con->response.keep_alive = 0;
                    con->keep_alive = 0;

           

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值