wifidog认证自带http服务器Lighttpd1.4.20源码分析之状态机返回response

前一篇介绍完了请求的处理,先面lighttpd将会把处理的结果返回给客户端。状态机进入CON_STATE_RESPONST_START。在这个状态中,服务器主要的工作在函数connection_handle_write_prepare。这个函数不算复杂,主要是根据客户端请求的method来设置response的headers,其实就是设置“Content-Length”的值。下面是函数代码,做了一些删减:

static int connection_handle_write_prepare(server * srv, connection * con)
{
   if (con->mode == DIRECT)
   {
       /*
        * static files
        */
       switch (con->request.http_method)
       {
       case HTTP_METHOD_GET:
       case HTTP_METHOD_POST:
       case HTTP_METHOD_HEAD:
       case HTTP_METHOD_PUT:
       case HTTP_METHOD_MKCOL:
       case HTTP_METHOD_DELETE:
       case HTTP_METHOD_COPY:
       case HTTP_METHOD_MOVE:
       case HTTP_METHOD_PROPFIND:
       case HTTP_METHOD_PROPPATCH:
       case HTTP_METHOD_LOCK:
       case HTTP_METHOD_UNLOCK:
           break;
       case HTTP_METHOD_OPTIONS:
           /*
            * 400 is coming from the request-parser BEFORE uri.path is set
            * 403 is from the response handler when noone else catched it
            * */
           if ((!con->http_status || con->http_status == 200)
               && con->uri.path->used && con->uri.path->ptr[0] != ‘*‘)
           {
               response_header_insert(srv, con, CONST_STR_LEN("Allow"),
                  CONST_STR_LEN("OPTIONS, GET, HEAD, POST"));

               con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
               con->parsed_response &= ~HTTP_CONTENT_LENGTH;

               con->http_status = 200;
               con->file_finished = 1;

               chunkqueue_reset(con->write_queue);
           }
           break;
       default:
           switch (con->http_status)
           {
           case 400:            /* bad request */
           case 414:            /* overload request header */
           case 505:            /* unknown protocol */
           case 207:            /* this was webdav */
               break;
           default:
               con->http_status = 501;
               break;
           }
           break;
       }
   }

   if (con->http_status == 0)
   {
       con->http_status = 403;
   }

   switch (con->http_status)
   {
   case 204:                    /* class: header only */
   case 205:
   case 304:
       /*
        * disable chunked encoding again as we have no body
        */
       con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
       con->parsed_response &= ~HTTP_CONTENT_LENGTH;
       chunkqueue_reset(con->write_queue);

       con->file_finished = 1;
       break;
   default:                    /* class: header + body */
       if (con->mode != DIRECT)
           break;

       /*
        * only custom body for 4xx and 5xx
        */
       if (con->http_status < 400 || con->http_status >= 600)
           break;

       con->file_finished = 0;
       buffer_reset(con->physical.path);

       /*
        * try to send static errorfile
        */
       if (!buffer_is_empty(con->conf.errorfile_prefix))
       {
        //设置对应的错误提示文件
       }

       if (!con->file_finished)
       {
           //没有对应的错误提示文件,设置默认错误提示。
       }
       break;
   }

   if (con->file_finished)
   {
       /*
        * we have all the content and chunked encoding is not used, set a
        * content-length
        */
       if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) &&
           (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)
       {
           off_t qlen = chunkqueue_length(con->write_queue);
           /**
            * The Content-Length header only can be sent if we have content:
            * - HEAD doesn‘t have a content-body (but have a content-length)
            * - 1xx, 204 and 304 don‘t have a content-body (RFC 2616 Section 4.3)
            *
            * Otherwise generate a Content-Length header as chunked encoding is not
            * available
            */
           if ((con->http_status >= 100 && con->http_status < 200) ||
               con->http_status == 204 || con->http_status == 304)
           {
               data_string *ds;
               /*
                * no Content-Body, no Content-Length
                */
               if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "Content-Length")))
               {
                   buffer_reset(ds->value);    /* Headers with empty values
                                                * are ignored for output */
               }
           }
           else if (qlen > 0 || con->request.http_method != HTTP_METHOD_HEAD)
           {
               /*
                * qlen = 0 is important for Redirects (301, ...) as they MAY
                * have a content. Browsers are waiting for a Content otherwise
                */
               buffer_copy_off_t(srv->tmp_buf, qlen);

               response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf));
           }
       }
   }
   else
   {
       /**
        * the file isn‘t finished yet, but we have all headers
        *
        * to get keep-alive we either need:
        * - Content-Length: ... (HTTP/1.0 and HTTP/1.0) or
        * - Transfer-Encoding: chunked (HTTP/1.1)
        */

       if (((con->parsed_response & HTTP_CONTENT_LENGTH) == 0) &&
           ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0))
       {
           con->keep_alive = 0;
       }

       /**
        * if the backend sent a Connection: close, follow the wish
        *
        * NOTE: if the backend sent Connection: Keep-Alive, but no Content-Length, we
        * will close the connection. That‘s fine. We can always decide the close
        * the connection
        *
        * FIXME: to be nice we should remove the Connection: ...
        */
       if (con->parsed_response & HTTP_CONNECTION)
       {
           /*
            * a subrequest disable keep-alive although the client wanted it
            */
           if (con->keep_alive && !con->response.keep_alive)
           {
               con->keep_alive = 0;
           }
       }
   }

   if (con->request.http_method == HTTP_METHOD_HEAD)
   {
       /**
        * a HEAD request has the same as a GET
        * without the content
        */
       con->file_finished = 1;

       chunkqueue_reset(con->write_queue);
       con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
   }

   http_response_write_header(srv, con);

   return 0;
}

首先,该函数判断连接的模式(mode)是否是DIRECT,如果是,说明连接没有经过插件处理,是由服务器自身处理的。这里判断连接的请求method,如果是OPTION,则设置Allow的值。同时清空write_queue,因为没有数据需要返回。 接着,在下面这个switch语句中,比较http_status的值,如果为204,205,304,说明服务器不需要给客户端返回文件,仅仅返回 response中headers及其之前的部分。这里和前面处理OPTION方法都设置con->file_finished为1。 file_finished用来标记客户端请求的静态文件是否已经发送完了(这个file_finished的含义比较模糊,目前还没搞清楚是表示文件发 送完毕,还是要发送的文件设置完毕可以发送。。。也有可能是个bug。。。如果各位读者有什么高见,还望不吝赐教!)。这两处都不需要给客户端发送文件, 因此将其设置为1,发送程序将直接跳过文件的发送。switch的default分支处理4xx错误,返回相应的错误提示文件。
  出了switch语句之后,接着是一个if判断file_finished的值。如果值为1,说明不需要给客户端返回文件数据。对于1xx,204和 304状态,将Content-Length设置为空值。如果method是HEAD,那么服务器可能需要返回一些数据,这时候要设置对应的 Content-Length。如果file_finished的值为0,那么要设置一下keep_alive的值。在 connection_handle_write_prepare函数的最后,调用http_response_write_header将 headers写入write_queue,等待返回给客户端。如果一切顺利,那么状态机进入CON_STATE_WRITE。
  由于数据可能不会一次写完,尤其是发送大文件的时候。因此,在CON_STATE_WRITE状态中首先判断write_queue时候为空,也就是有没 有数据还没有发送。同时判断连接是否可写。如果有数据且可写,那么调用connection_handle_write发送数据。如果没有数据可写或者连 接不可写,那么会退出switch(con->state)这个语句。同时,由于状态机状态没有发生改变,switch后面的if语句使得服务器退 出了大while循环,进入循环后面的小switch(con->state)语句。这个switch语句在前面已经介绍过。在这里,进入 CON_STATE_WRITE分支,如果有数据可写且连接可写且没有达到流量限制,那么在fdevent中注册这个连接,否则,删除这个连接。
  下面我们进入connection_handle_write函数,看看有数据可写且连接可写的情况。connection_handle_write函 数会首先在switch语句中调用network_write_chunkqueue函数。顾名思义,这个函数就是将write_queue中的数据写回 给客户端的。函数network_write_chunkqueue首先判断当前连接的流量是否超过了限制,如果是,则不发送任何数据,直接将连接加到作 业列表(joblist)中,让其他连接发送数据。如果没有超限,那么首先设置TCP_CORK选项。这个选项可以将多个write调用的数据一起发送, 提高发送效率。有关TCP_CORK的内容读者可自行google之。接下来就是调用srv->network_backend_wirte()函 数进行真正的写数据了。这个函数的真正定义有多个,在network_*.c文件中。服务器在network.c的network_init函数中会根据 当前的运行环境设置不同的值。为了提高服务器的效率,不同的OS会提供一些不同的方法,提高发送文件的速度。通过传统的先read在write的方法,需 要4次数据拷贝(从磁盘到内核缓冲区,从内核缓冲区到用户缓冲区,从用户缓冲区到网络接口的内核缓冲区,最后,从网络接口的内核缓冲区到网络设备缓冲 区。),OS会提供一些特定的方法来减少拷贝的次数,具体读者可以google“直接IO”,有不少介绍这个的文章。为了充分利用这些方法来提高服务器的 性能,lighttpd在不同的OS上都会去使用这些特定的接口,因此就需要多个network_backend_wirte函数的实现。这些实现基本上 大同小异,因此这里只介绍network_write.c中的实现。函数的主体是个大循环,遍历所有的chunk。如果chunk的类型是 MEM_CHUNK,那么这个chunk中的数据是在内存中的,直接调用write或者windows下的send函数发送数据。如果是 FILE_CHUNK类型,说明这个chunk表示的是一个文件,那么如果运行环境有mmap函数,就使用mmap映射文件并发送,否则就read在 write。如果这个chunk发送完了,那么继续发送下一个chunk。如果没有发送完(chunk_finished=0),那么退出循环,接着也就 退出了这个函数。服务器这时返回到network_write_chunkqueue中,下面做一些统计工作,再一次检查该连接的流量是否超限。最后服务 器返回到connection_handle_write中。如果network_write_chunkqueue返回-1,表示服务器出错。状态机进 入CON_STATE_ERROR。如果返回-2,则客户端关闭连接,状态机也进入CON_STATE_ERROR。返回0表示发送完毕,进入下一个状 态。返回1说明数据没有发送完,标记is_wirtable为0。
  从connection_handler_write函数返回后,如果数据没有发送完毕,那么状态机还在
CON_STATE_WRITE状态,接着连接被加到fdevent系统中,等待下一次数据发送。重复上述过程知道发送完毕或出错。
  如果数据发送完毕,状态机进入CON_STATE_RESPONSE_END状态。在这个状态中,服务器首先调用 plugins_call_handle_request_done通知所有插件连接处理完毕。然后判断是否保持连接,如果保持,将状态机设置为 CON_STATE_REQUEST_START。如果不保持,那么先调用plugins_call_handle_connection_close通 知所有插件连接关闭。然后关闭连接。最后,重置con,清除前一次请求的数据。
  至此,请求处理结束。总的来说,返回response的过程还算直接,没有多少难懂的地方。比较不好懂的地方都是关于http协议中一些情况的细节处理,读者可以参照rfc理解。
下面一片文章将会分析一些错误处理过程。

本文章由http://www.wifidog.pro/2015/04/10/wifidog%E8%AE%A4%E8%AF%81lighttpd-1.html 整理编辑,转载请注明出处

时间: 2024-11-20 14:01:41

wifidog认证自带http服务器Lighttpd1.4.20源码分析之状态机返回response的相关文章

Lighttpd1.4.20源码分析 笔记 状态机之response

在CON_STATE_RESPONSE_START状态中,服务器开始准备给客户端的response: case CON_STATE_RESPONSE_START: /* * the decision is done * - create the HTTP-Response-Header * */ if (srv->srvconf.log_state_handling) { log_error_write(srv, __FILE__, __LINE__, "sds", "

Lighttpd1.4.20源码分析 笔记 状态机之错误处理和连接关闭

这里所说的错误有两种: 1.http协议规定的错误,如404错误: 2.服务器运行过程中的错误,如write错误. 对于http协议规定的错误,这里的"错误"是针对客户端的.lighttpd返回相应的错误提示文件之后,相当于顺利的完成了一次请求,只是结果和客户端想要的不一样而已. 对于服务器运行中的错误,状态机进入CON_STATE_ERROR状态.常见的错误原因:客户端提前断开连接.比如你不停的刷新页面,在你刷新的时候,前一次的连接没有完成,但被浏览器强行断开.对于服务器而言,刷新前

wifidog 认证Lighttpd1.4.20源码分析之bitset.c(h) -------位集合的使用

使用一个比特位来表示一个事件的两种状态,即节省内存,又可以提高运行速度.在Lighttpd中,提供了一个bitset数据结构,用来管理使用一个比特位集合. 在bitset.h中,比特位集合的数据结构定义如下: typedef struct { size_t *bits; size_t nbits; } bitset; bits指向一个size_t类型的数组,存放bit集合.size_t类型通常被定义成一个无符号的整型(int或long),其长度和具体的机器有关,这个读者可以查阅相关的资料.nbi

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(1)---fdevents结构体和fdevent系统对外接口

前面讲了lighttpd的插件系统,这一篇将看一看lighttpd中的fdevent系统.fdevent系统主要是处理各种IO事件,在web服务器中,主要就是向socket写数据和从socket读数据.通常,web服务器是IO密集型程序,这就要求在数据的读写上,web服务器必须能够具有很好的性能,不会因为某个socket的阻塞而致使其他socket也被阻塞,否则会大大降低服务器的性能.因此,大部分的web服务器都采用非阻塞IO进行数据的读写.lighttpd通过fdevent系统,采用类似OO中

wifidog源码分析Lighttpd1.4.20源码分析之插件系统(3)---PLUGIN_TO_SLOT宏

前面讲了lighttpd插件系统的加载和初始化,这一篇中,将介绍一下plugin.c中的宏PLUGIN_TO_SLOT.在将PLUGIN_TO_SLOT宏之前,我们先来看看lighttpd中插件系统的对外接口.这个接口所对的“外”指的是lighttpd服务器.前面已经提到,在运行的过程中,lighttpd不知道所加载的插件都是干什么用的,只知道这些插件所实现的接口,也就是在plugin结构体中那些函数指针有哪些对于某个插件是NULL,哪些是具体的函数地址.既然lighttpd只知道这些,那么它又

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(3) -----使用

接着上文介绍的函数fdevent_linux_sysepoll_event_add 讲解,首先看函数的第三个参数events,他是一个整型,其没以为对应一种IO事件.上面fdevent_event_add()函数的额第三个参数是FDEVENT_IN,这是一个宏 /* * 用于标记文件描述符的状态 */ #define FDEVENT_IN BV(0) //文件描述符是否可写 #define FDEVENT_PRI BV(1) //不阻塞的可读高优先级的数据 poll #define FDEVEN

【.NET Core项目实战-统一认证平台】第八章 授权篇-IdentityServer4源码分析

[.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何在网关上实现客户端自定义限流功能,基本完成了关于网关的一些自定义扩展需求,后面几篇将介绍基于IdentityServer4(后面简称Ids4)的认证相关知识,在具体介绍ids4实现我们统一认证的相关功能前,我们首先需要分析下Ids4源码,便于我们彻底掌握认证的原理以及后续的扩展需求. .netcore项目实战交流群(637326624),有兴趣的朋友可以在群里交流讨论. 一.Ids4文档及源码 文档地址 http:/

HTTP服务器的本质:tinyhttpd源码分析及拓展

已经有一个月没有更新博客了,一方面是因为平时太忙了,另一方面是想积攒一些干货进行分享.最近主要是做了一些开源项目的源码分析工作,有c项目也有python项目,想提升一下内功,今天分享一下tinyhttpd源码分析的成果.tinyhttpd是一个非常轻量型的http服务器,c代码500行左右,可以帮助我们了解http服务器运行的实质.在分析之前,我们先说一下http报文. 一.http请求 http请求由三部分组成,分别是:起始行.消息报头.请求正文 Request Line<CRLF> Hea

Titanic (带波纹效果的TextView)源码分析

前一段时间用LinearGradient(线性渐变) 写了一个颜色变化闪动的文字控件和文字进度条http://blog.csdn.net/huweigoodboy/article/details/43088953, 最近在github上看到一个开源控件Titanic,特拿来分析一下,效果如下图: 一,实现原理 这里需要用到BitmapShader,用一张图片为图像渲染. 上面的图片是一张一半透明一半呈白色的图片,而且随x坐标起伏,能够形成波纹状.那么仅仅靠上面一张图片如何得到效果图中的效果呢?