菜鸟笔记
提升您的技术认知

http-ag真人游戏

项目里用到力http-parser,在这里简单说明一下其用法吧

下载地址:https://github.com/joyent/http-parser

其使用说明很详细。

开源tcpflow 1.4.4中使用http-parser的源代码

/* -*- mode: c  ; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
 *
 * scan_http:
 * decodes http responses
 */
#include "config.h"
#include "tcpflow.h"
#include "tcpip.h"
#include "tcpdemux.h"
#include "http-parser/http_parser.h"
#include "mime_map.h"
#ifdef have_sys_wait_h
#include 
#endif
#ifdef have_libz
#  define zlib_const
#  ifdef gnuc_has_diagnostic_pragma
#    pragma gcc diagnostic ignored "-wundef"
#    pragma gcc diagnostic ignored "-wcast-qual"
#  endif
#  ifdef have_zlib_h
#    include 
#  endif
#else
#  define z_stream void *               // prevents z_stream from generating an error
#endif
#define min_http_bufsize 80             // don't bother parsing smaller than this
#include 
#include 
#include 
#include 

其中使用    struct http_parser_settings 设置回调,使用http_parser 来解析。

开源libtnet-master中的使用情况

#include "httpparser.h"
#include "httputil.h"
#include "log.h"
using namespace std;
namespace tnet
{
    struct http_parser_settings ms_settings;
    class httpparsersettings
    {
    public:
        httpparsersettings();
        static int onmessagebegin(struct http_parser*);
        static int on;
        static int onstatuscomplete(struct http_parser*);
        static int onheaderfield(struct http_parser*, const char*, size_t);
        static int onheadervalue(struct http_parser*, const char*, size_t);
        static int onheaderscomplete(struct http_parser*);
        static int onbody(struct http_parser*, const char*, size_t);
        static int onmessagecomplete(struct http_parser*); 
    };
    httpparsersettings::httpparsersettings()
    {
        ms_settings.on_message_begin = &httpparsersettings::onmessagebegin;
        ms_settings.on_url = &httpparsersettings::onurl;
        ms_settings.on_status_complete = &httpparsersettings::onstatuscomplete;
        ms_settings.on_header_field = &httpparsersettings::onheaderfield;
        ms_settings.on_header_value = &httpparsersettings::onheadervalue;
        ms_settings.on_headers_complete = &httpparsersettings::onheaderscomplete;
        ms_settings.on_body = &httpparsersettings::onbody;
        ms_settings.on_message_complete = &httpparsersettings::onmessagecomplete;    
    }    
    static httpparsersettings initobj;
    int httpparsersettings::onmessagebegin(struct http_parser* parser)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_messagebegin, 0, 0);
    }
    int httpparsersettings::on
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_url, at, length);
    }
    int httpparsersettings::onstatuscomplete(struct http_parser* parser)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_statuscomplete, 0, 0);
    }
    int httpparsersettings::onheaderfield(struct http_parser* parser, const char* at, size_t length)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_headerfield, at, length);
    }
    int httpparsersettings::onheadervalue(struct http_parser* parser, const char* at, size_t length)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_headervalue, at, length);
    }
    int httpparsersettings::onheaderscomplete(struct http_parser* parser)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_headerscomplete, 0, 0);
    }
    int httpparsersettings::onbody(struct http_parser* parser, const char* at, size_t length)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_body, at, length);
    }
    int httpparsersettings::onmessagecomplete(struct http_parser* parser)
    {
        httpparser* p = (httpparser*)parser->data;
        return p->onparser(httpparser::parser_messagecomplete, 0, 0);
    }
    httpparser::httpparser(enum http_parser_type type)
    {
        http_parser_init(&m_parser, type);
        m_parser.data = this;
   
        m_lastwasvalue = true;
    }
   
    httpparser::~httpparser()
    {
        
    }
    int httpparser::onparser(event event, const char* at, size_t length)
    {
        switch(event)
        {
            case parser_messagebegin:
                return handlemessagebegin();
            case parser_url:
                return on;
            case parser_statuscomplete:
                return 0;
            case parser_headerfield:
                return handleheaderfield(at, length);
            case parser_headervalue:
                return handleheadervalue(at, length);
            case parser_headerscomplete:
                return handleheaderscomplete();
            case parser_body:
                return onbody(at, length);
            case parser_messagecomplete:
                return onmessagecomplete();
            default:
                break;
        }
        return 0;
    }
    int httpparser::handlemessagebegin()
    {
        m_curfield.clear();
        m_curvalue.clear();
        m_lastwasvalue = true;
        
        m_errorcode = 0;
        return onmessagebegin();
    }        
        
    int httpparser::handleheaderfield(const char* at, size_t length)
    {
        if(m_lastwasvalue)
        {
            if(!m_curfield.empty())
            {  
                onheader(httputil::normalizeheader(m_curfield), m_curvalue);
            }
            
            m_curfield.clear();    
            m_curvalue.clear();
        }
        m_curfield.append(at, length);
        m_lastwasvalue = 0;
        return 0;
    }
        
    int httpparser::handleheadervalue(const char* at, size_t length)
    {
        m_curvalue.append(at, length);
        m_lastwasvalue = 1;
        return 0;
    }
        
    int httpparser::handleheaderscomplete()
    {
        if(!m_curfield.empty())
        {
            string field = httputil::normalizeheader(m_curfield); 
            onheader(field, m_curvalue);    
        }    
        return onheaderscomplete();
    }
    int httpparser::execute(const char* buffer, size_t count)
    {
        int n = http_parser_execute(&m_parser, &ms_settings, buffer, count);
        if(m_parser.upgrade)
        {
            onupgrade(buffer   n, count - n); 
            return 0;
        }
        else if(n != count)
        {
            int code = (m_errorcode != 0 ? m_errorcode : 400);
            
            httperror error(code, http_errno_description((http_errno)m_parser.http_errno));
            log_error("parser error %s", error.message.c_str());
            
            onerror(error);
            return code;
        }     
        return 0;
    }
}

概括

http-parser是一个用c代码编写的http消息解析器。可以解析http请求或者回应消息。这个解析器常常在高性能的http应用中使用。在解析的过程中,它不会调用任何系统调用,不会在heap上申请内存,不会缓存数据,并且可以在任意时刻打断解析过程,而不会产生任何影响。对于每个http消息(在web服务器中就是每个请求),它只需要40字节的内存占用(解析器本身的基本数据结构),不过最终的要看你实际的代码架构。

特性:

  • 无第三方依赖
  • 可以处理持久消息(keep-alive)
  • 支持解码chunk编码的消息
  • 支持upgrade协议升级(如无例外就是websocket)
  • 可以防御缓冲区溢出攻击

解析器可以处理以下类型的http消息:

  • 头部的字段和值
  • content-length
  • 请求方法
  • 返回的http代码
  • transfer-encoding
  • http版本
  • 请求的url
  • http消息主体

简单使用:

每个http请求使用一个http_parser对象。使用http_parser_init来初始化结构体,并且设置解析时的回调。下面的代码可能看起来像是解析http请求:

// 设置回调
http_parser_settings settings;
settings.on_url = my_url_callback;
settings.on_header_field = my_header_field_callback;
/* ... */
// 为结构体申请内存
http_parser *parser = malloc(sizeof(http_parser));
// 初始化解析器
http_parser_init(parser, http_request);
// 设置保存调用者的数据,用于在callback内使用
parser->data = my_socket;

当接收到数据后,解析器开始执行,并检查错误:

size_t len = 80*1024;   // 需要接受的数据大小80k
size_t nparsed;         // 已经解析完成的数据大小
char buf[len];          // 接收缓存
ssize_t recved;         // 实际接收到的数据大小
// 接受数据
recved = recv(fd, buf, len, 0);
// 如果接收到的字节数小于0,说明从socket读取出错
if (recved < 0) {
  /* handle error. */
}
/* start up / continue the parser.
 * note we pass recved==0 to signal that eof has been recieved.
 */
// 开始解析
// @parser 解析器对象
// @&settings 解析时的回调函数
// @buf 要解析的数据
// @receved 要解析的数据大小
nparsed = http_parser_execute(parser, &settings, buf, recved);
// 如果解析到websocket请求
if (parser->upgrade) {
  /* handle new protocol */
// 如果解析出错,即解析完成的数据大小不等于传递给http_parser_execute的大小
} else if (nparsed != recved) {
  /* handle error. usually just close the connection. */
}

http需要知道数据流在那里结束。

举个例子,一些服务器发送响应数据的时候,http头部不带有content-length字段,希望客户端持续从socket中读取数据,知道遇到eof为止。在调用http_parser_execute时,传递最后一个参数为0,用来通知http_parser,解析已经结束。在http_parser遇到eof并处理的过程中,仍然可能会遇到错误,所以应该在callback中处理这些错误。

注意: 上面的意思是说,如果需要多次调用http_parser_execute的时候,就是因为无法一次完成对http服务器/客户端数据的接收。所以需要在每次接收到一些数据之后,调用一次http_parser_execute,当从socket接收到eof时,应该结束解析,同时通知http_parser解析结束。

一些可扩展的信息字段,例如status_codemethodhttp版本号,它们都存储在解析器的数据结构中。这些数据被临时的存储在http_parser中,并且会在每个连接到来后被重置(当多个连接的http数据使用同一个解析器时);如果需要保留这些数据,必须要在on_headers_complete返回之前保存它門。

注意: 应该为每个http连接的数据,单独初始化一个解析器的时候,不会存在上述问题.

解析器会解析http请求和相应中的transfer-encoding字段。就是说,chunked编码会在调用on_body之前被解析。

关于upgrade协议的问题

http支持将连接升级为不同的协议. 例如目前日益普遍的websocket协议的请求数据:

get /demo http/1.1
upgrade: websocket
connection: upgrade
host: example.com
origin: http://example.com
websocket-protocol: sample

在websocket请求头部传输完毕后,就下来传输的数据是非http协议的数据了。

关于websocket协议的详细内容见: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75

要支持这种类似与websocket的协议,解析器会把它当作一个不带http主体数据的包(只含有头部).然后调用on_headers_completeon_message_complete回调。所以不论怎样,当检测到http头部的数据结束时,http_parser_execute会停止解析,并且返回。

建议用户在http_parser_execute函数返回后,检查parset->upgrade字段,是否被设置为1.在http_parset_execute的返回值中,非http类型的数据(除去http头部的数据)的范围,会被设置为从一个offset参数处开始。

回调函数

当调用http_parser_execute时,在http_parset_settings中设置的回调会执行。解析器维护了自身状态数据,并且这些数据不会被保存,所以没有必要将这些状态数据缓存。如果你真需要保存这些状态数据,可以在回调中保存。

有两种类型的回调:

  • 通知 typedef int (*http_cb) (http_parser *);包括:on_message_begin,on_headers_complete, on_message_complete

  • 数据 typedef int (*http_data_cb) (http_parser *, const char at, size_t length);包括;(只限与请求)on_uri, (通用) on_header_field, on_header_value,on_body

用户的回调函数应该返回0表示成功。返回非0的值,会告诉解析器发生了错误,解析器会立刻退出。

如果你解析chunks编码的http消息(例如:从socket中读read()http请求行,解析,然后再次读到一半的头部消息后,再次解析,等等),你的数据类型的回调就会被调用不止一次。http解析器保证,参数中传递的数据指针,只在回调函数内有效(即回调调用结束,数据指针无效).因为http-parser返回解析结果的方式为:在需要解析的数据中,依靠指针和数据长度来供用户代码读取 如果可以的话,你也可以将read()到的数据,保存到在heap上申请的内存中,以避免非必要的数据拷贝。

比较笨的方法是:每读取一次将读取到的数据传递给http_parset_execute函数.

注意:对于将一个完整的http报文分开多次解析,应该使用同一个parser对象!

但是实际上的情况更复杂:

  • 首先根据http协议头部的规则,应该持续从socket读取数据,直到读到了\r\n\r\n,表示头部报文结束。这时可以传递给http_parser解析,或者根据下面的规则,继续读取实体部分的数据。

  • 如果报文中使用content-length指定传输实体的大小,接下来不论http客户/服务器都因该根据读取到content-length指定的实体大小

  • 对于分块传输的实体,传输编码为chunked。即transfer-encoding: chunked。分快传输的编码,一般只适用于http内容响应(http请求也可以指定传输编码为chunked,但不是所有http服务器都支持)。这时可以读取定量的数据(如4096字节) ,交给parser解析。然后重复此过程,直到chunk编码结束。

网站地图