http-parser解析http报文详解

2023-05-16

说明

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

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

其使用说明很详细。

开源用例

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

<span xmlns="http://www.w3.org/1999/xhtml" style="">/* -*- 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 <sys/wait.h>
#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 <zlib.h>
#  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 <sys/types.h>
#include <iostream>
#include <algorithm>
#include <map>
#include <iomanip>

#define HTTP_CMD "http_cmd"
#define HTTP_ALERT_FD "http_alert_fd"

/* options */
std::string http_cmd;                   // command to run on each http object
int http_subproc_max = 10;              // how many subprocesses are we allowed?
int http_subproc = 0;                   // how many do we currently have?
int http_alert_fd = -1;                 // where should we send alerts?


/* define a callback object for sharing state between scan_http() and its callbacks
 */
class scan_http_cbo {
private:
    typedef enum {NOTHING,FIELD,VALUE} last_on_header_t;
    scan_http_cbo(const scan_http_cbo& c); // not implemented
    scan_http_cbo &operator=(const scan_http_cbo &c); // not implemented

public:
    virtual ~scan_http_cbo(){
        on_message_complete();          // make sure message was ended
    }
    scan_http_cbo(const std::string& path_,const char *base_,std::stringstream *xmlstream_) :
        path(path_), base(base_),xmlstream(xmlstream_),xml_fo(),request_no(0),
        headers(), last_on_header(NOTHING), header_value(), header_field(),
        output_path(), fd(-1), first_body(true),bytes_written(0),unzip(false),zs(),zinit(false),zfail(false){};
private:        
        
    const std::string path;             // where data gets written
    const char *base;                   // where data started in memory
    std::stringstream *xmlstream;       // if present, where to put the fileobject annotations
    std::stringstream xml_fo;           // xml stream for this file object
    int request_no;                     // request number
        
    /* parsed headers */
    std::map<std::string, std::string> headers;
        
    /* placeholders for possibly-incomplete header data */
    last_on_header_t last_on_header;
    std::string header_value, header_field;
    std::string output_path;
    int         fd;                         // fd for writing
    bool        first_body;                 // first call to on_body after headers
    uint64_t    bytes_written;

    /* decompression for gzip-encoded streams. */
    bool     unzip;           // should we be decompressing?
    z_stream zs;              // zstream (avoids casting and memory allocation)
    bool     zinit;           // we have initialized the zstream 
    bool     zfail;           // zstream failed in some manner, so ignore the rest of this stream

    /* The static functions are callbacks; they wrap the method calls */
#define CBO (reinterpret_cast<scan_http_cbo*>(parser->data))
public:
    static int scan_http_cb_on_message_begin(http_parser * parser) { return CBO->on_message_begin();}
    static int scan_http_cb_on_url(http_parser * parser, const char *at, size_t length) { return 0;}
    static int scan_http_cb_on_header_field(http_parser * parser, const char *at, size_t length) { return CBO->on_header_field(at,length);}
    static int scan_http_cb_on_header_value(http_parser * parser, const char *at, size_t length) { return CBO->on_header_value(at,length); }
    static int scan_http_cb_on_headers_complete(http_parser * parser) { return CBO->on_headers_complete();}
    static int scan_http_cb_on_body(http_parser * parser, const char *at, size_t length) { return CBO->on_body(at,length);}
    static int scan_http_cb_on_message_complete(http_parser * parser) {return CBO->on_message_complete();}
#undef CBO
private:
    int on_message_begin();
    int on_url(const char *at, size_t length);
    int on_header_field(const char *at, size_t length);
    int on_header_value(const char *at, size_t length);
    int on_headers_complete();
    int on_body(const char *at, size_t length);
    int on_message_complete();          
};
    

/**
 * on_message_begin:
 * Increment request nubmer. Note that the first request is request_no = 1
 */

int scan_http_cbo::on_message_begin()
{
    request_no ++;
    return 0;
}

/**
 * on_url currently not implemented.
 */

int scan_http_cbo::on_url(const char *at, size_t length)
{
    return 0;
}


/* Note 1: The state machine is defined in http-parser/README.md
 * Note 2: All header field names are converted to lowercase.
 *         This is consistent with the RFC.
 */

int scan_http_cbo::on_header_field(const char *at,size_t length)
{
    std::string field(at,length);
    std::transform(field.begin(), field.end(), field.begin(), ::tolower);
    
    switch(last_on_header){
    case NOTHING:                       
        // Allocate new buffer and copy callback data into it
        header_field = field;
        break;
    case VALUE:
        // New header started.
        // Copy current name,value buffers to headers
        // list and allocate new buffer for new name
        headers[header_field] = header_value;
        header_field = field;
        break;
    case FIELD:
        // Previous name continues. Reallocate name
        // buffer and append callback data to it
        header_field.append(field);
        break;
    }
    last_on_header = FIELD;
    return 0;
}

int scan_http_cbo::on_header_value(const char *at, size_t length)
{
    const std::string value(at,length);
    switch(last_on_header){
    case FIELD:
        //Value for current header started. Allocate
        //new buffer and copy callback data to it
        header_value = value;
        break;
    case VALUE:
        //Value continues. Reallocate value buffer
        //and append callback data to it
        header_value.append(value);
        break;
    case NOTHING:
        // this shouldn't happen
        DEBUG(10)("Internal error in http-parser");
        break;
    }
    last_on_header = VALUE;

    return 0;
}

/**
 * called when last header is read.
 * Determine the filename based on request_no and extension.
 * Also see if decompressing is happening...
 */

int scan_http_cbo::on_headers_complete()
{
    tcpdemux *demux = tcpdemux::getInstance();

    /* Add the most recently read header to the map, if any */
    if (last_on_header==VALUE) {
        headers[header_field] = header_value;
        header_field="";
    }
        
    /* Set output path to <path>-HTTPBODY-nnn.ext for each part.
     * This is not consistent with tcpflow <= 1.3.0, which supported only one HTTPBODY,
     * but it's correct...
     */
    
    std::stringstream os;
    os << path << "-HTTPBODY-" << std::setw(3) << std::setfill('0') << request_no << std::setw(0);

    /* See if we can guess a file extension */
    std::string extension = get_extension_for_mime_type(headers["content-type"]);
    if (extension.size()) {
        os << "." << extension;
    }
        
    output_path = os.str();
        
    /* Choose an output function based on the content encoding */
    std::string content_encoding(headers["content-encoding"]);

    if ((content_encoding == "gzip" || content_encoding == "deflate") && (demux->opt.gzip_decompress)){
#ifdef HAVE_LIBZ
        DEBUG(10) ( "%s: detected zlib content, decompressing", output_path.c_str());
        unzip = true;
#else
        /* We can't decompress, so just give it a .gz */
        output_path.append(".gz");
        DEBUG(5) ( "%s: refusing to decompress since zlib is unavailable", output_path.c_str() );
#endif
    } 
        
    /* Open the output path */
    fd = demux->retrying_open(output_path.c_str(), O_WRONLY|O_CREAT|O_BINARY|O_TRUNC, 0644);
    if (fd < 0) {
        DEBUG(1) ("unable to open HTTP body file %s", output_path.c_str());
    }
    if(http_alert_fd>=0){
        std::stringstream ss;
        ss << "open\t" << output_path << "\n";
        const std::string &sso = ss.str();
        if(write(http_alert_fd,sso.c_str(),sso.size())!=(int)sso.size()){
            perror("write");
        }
    }

    first_body = true;                  // next call to on_body will be the first one
        
    /* We can do something smart with the headers here.
     *
     * For example, we could:
     *  - Record all headers into the report.xml
     *  - Pick the intended filename if we see Content-Disposition: attachment; name="..."
     *  - Record headers into filesystem extended attributes on the body file
     */
    return 0;
}

/* Write to fd, optionally decompressing as we go */
int scan_http_cbo::on_body(const char *at,size_t length)
{
    if (fd < 0)    return -1;              // no open fd? (internal error)x
    if (length==0) return 0;               // nothing to write

    if(first_body){                      // stuff for first time on_body is called
        xml_fo << "     <byte_run file_offset='" << (at-base) << "'><fileobject><filename>" << output_path << "</filename>";
        first_body = false;
    }

    /* If not decompressing, just write the data and return. */
    if(unzip==false){
        int rv = write(fd,at,length);
        if(rv<0) return -1;             // write error; that's bad
        bytes_written += rv;
        return 0;
    }

#ifndef HAVE_LIBZ
    assert(0);                          // shoudln't have gotten here
#endif    
    if(zfail) return 0;                 // stream was corrupt; ignore rest
    /* set up this round of decompression, using a small local buffer */

    /* Call init if we are not initialized */
    char decompressed[65536];           // where decompressed data goes
    if (!zinit) {
        memset(&zs,0,sizeof(zs));
        zs.next_in = (Bytef*)at;
        zs.avail_in = length;
        zs.next_out = (Bytef*)decompressed;
        zs.avail_out = sizeof(decompressed);
        
        int rv = inflateInit2(&zs, 32 + MAX_WBITS);      /* 32 auto-detects gzip or deflate */
        if (rv != Z_OK) {
            /* fail! */
            DEBUG(3) ("decompression failed at stream initialization; rv=%d bad Content-Encoding?",rv);
            zfail = true;
            return 0;
        }
        zinit = true;                   // successfully initted
    } else {
        zs.next_in = (Bytef*)at;
        zs.avail_in = length;
        zs.next_out = (Bytef*)decompressed;
        zs.avail_out = sizeof(decompressed);
    }
        
    /* iteratively decompress, writing each time */
    while (zs.avail_in > 0) {
        /* decompress as much as possible */
        int rv = inflate(&zs, Z_SYNC_FLUSH);
                
        if (rv == Z_STREAM_END) {
            /* are we done with the stream? */
            if (zs.avail_in > 0) {
                /* ...no. */
                DEBUG(3) ("decompression completed, but with trailing garbage");
                return 0;
            }
        } else if (rv != Z_OK) {
            /* some other error */
            DEBUG(3) ("decompression failed (corrupted stream?)");
            zfail = true;               // ignore the rest of this stream
            return 0;
        }
                
        /* successful decompression, at least partly */
        /* write the result */
        int bytes_decompressed = sizeof(decompressed) - zs.avail_out;
        ssize_t written = write(fd, decompressed, bytes_decompressed);

        if (written < bytes_decompressed) {
            DEBUG(3) ("writing decompressed data failed");
            zfail= true;
            return 0;
        }
        bytes_written += written;
                
        /* reset the buffer for the next iteration */
        zs.next_out = (Bytef*)decompressed;
        zs.avail_out = sizeof(decompressed);
    }
    return 0;
}


/**
 * called at the conclusion of each HTTP body.
 * Clean out all of the state for this HTTP header/body pair.
 */

int scan_http_cbo::on_message_complete()
{
    /* Close the file */
    headers.clear();
    header_field = "";
    header_value = "";
    last_on_header = NOTHING;
    if(fd >= 0) {
        if (::close(fd) != 0) {
            perror("close() of http body");
        }
        fd = -1;
    }

    /* Erase zero-length files and update the DFXML */
    if(bytes_written>0){
        /* Update DFXML */
        if(xmlstream){
            xml_fo << "<filesize>" << bytes_written << "</filesize></fileobject></byte_run>\n";
            if(xmlstream) *xmlstream << xml_fo.str();
        }
        if(http_alert_fd>=0){
            std::stringstream ss;
            ss << "close\t" << output_path << "\n";
            const std::string &sso = ss.str();
            if(write(http_alert_fd,sso.c_str(),sso.size()) != (int)sso.size()){
                perror("write");
            }
        }
        if(http_cmd.size()>0 && output_path.size()>0){
            /* If we are at maximum number of subprocesses, wait for one to exit */
            std::string cmd = http_cmd + " " + output_path;
#ifdef HAVE_FORK
            int status=0;
            pid_t pid = 0;
            while(http_subproc >= http_subproc_max){
                pid = wait(&status);
                http_subproc--;
            }
            /* Fork off a child */
            pid = fork();
            if(pid<0) die("Cannot fork child");
            if(pid==0){
                /* We are the child */
                exit(system(cmd.c_str()));
            }
            http_subproc++;
#else
            system(cmd.c_str());
#endif            
        }
    } else {
        /* Nothing written; erase the file */
        if(output_path.size() > 0){
            ::unlink(output_path.c_str());
        }
    }

    /* Erase the state variables for this part */
    xml_fo.str("");
    output_path = "";
    bytes_written=0;
    unzip = false;
    if(zinit){
        inflateEnd(&zs);
        zinit = false;
    }
    zfail = false;
    return 0;
}


/***
 * the HTTP scanner plugin itself
 */

extern "C"
void  scan_http(const class scanner_params &sp,const recursion_control_block &rcb)
{
    if(sp.sp_version!=scanner_params::CURRENT_SP_VERSION){
        std::cerr << "scan_http requires sp version " << scanner_params::CURRENT_SP_VERSION << "; "
                  << "got version " << sp.sp_version << "\n";
        exit(1);
    }

    if(sp.phase==scanner_params::PHASE_STARTUP){
        sp.info->name  = "http";
        sp.info->flags = scanner_info::SCANNER_DISABLED; // default disabled
        sp.info->get_config(HTTP_CMD,&http_cmd,"Command to execute on each HTTP attachment");
        sp.info->get_config(HTTP_ALERT_FD,&http_alert_fd,"File descriptor to send information about completed HTTP attachments");
        return;         /* No feature files created */
    }

    if(sp.phase==scanner_params::PHASE_SCAN){
        /* See if there is an HTTP response */
        if(sp.sbuf.bufsize>=MIN_HTTP_BUFSIZE && sp.sbuf.memcmp(reinterpret_cast<const uint8_t *>("HTTP/1."),0,7)==0){
            /* Smells enough like HTTP to try parsing */
            /* Set up callbacks */
            http_parser_settings scan_http_parser_settings;
            memset(&scan_http_parser_settings,0,sizeof(scan_http_parser_settings)); // in the event that new callbacks get created
            scan_http_parser_settings.on_message_begin          = scan_http_cbo::scan_http_cb_on_message_begin;
            scan_http_parser_settings.on_url                    = scan_http_cbo::scan_http_cb_on_url;
            scan_http_parser_settings.on_header_field           = scan_http_cbo::scan_http_cb_on_header_field;
            scan_http_parser_settings.on_header_value           = scan_http_cbo::scan_http_cb_on_header_value;
            scan_http_parser_settings.on_headers_complete       = scan_http_cbo::scan_http_cb_on_headers_complete;
            scan_http_parser_settings.on_body                   = scan_http_cbo::scan_http_cb_on_body;
            scan_http_parser_settings.on_message_complete       = scan_http_cbo::scan_http_cb_on_message_complete;
                        
            if(sp.sxml) (*sp.sxml) << "\n    <byte_runs>\n";
            for(size_t offset=0;;){
                /* Set up a parser instance for the next chunk of HTTP responses and data.
                 * This might be repeated several times due to connection re-use and multiple requests.
                 * Note that the parser is not a C++ library but it can pass a "data" to the
                 * callback. We put the address for the scan_http_cbo object in the data and
                 * recover it with a cast in each of the callbacks.
                 */
                
                /* Make an sbuf for the remaining data.
                 * Note that this may not be necessary, because in our test runs the parser
                 * processed all of the data the first time through...
                 */
                sbuf_t sub_buf(sp.sbuf, offset);
                                
                const char *base = reinterpret_cast<const char*>(sub_buf.buf);
                http_parser parser;
                http_parser_init(&parser, HTTP_RESPONSE);

                scan_http_cbo cbo(sp.sbuf.pos0.path,base,sp.sxml);
                parser.data = &cbo;

                /* Parse */
                size_t parsed = http_parser_execute(&parser, &scan_http_parser_settings,
                                                    base, sub_buf.size());
                assert(parsed <= sub_buf.size());
                                
                /* Indicate EOF (flushing callbacks) and terminate if we parsed the entire buffer.
                 */
                if (parsed == sub_buf.size()) {
                    http_parser_execute(&parser, &scan_http_parser_settings, NULL, 0);
                    break;
                }
                                
                /* Stop parsing if we parsed nothing, as that indicates something header! */
                if (parsed == 0) {
                    break;
                }
                                
                /* Stop parsing if we're a connection upgrade (e.g. WebSockets) */
                if (parser.upgrade) {
                    DEBUG(9) ("upgrade connection detected (WebSockets?); cowardly refusing to dump further");
                    break;
                }
                                
                /* Bump the offset for next iteration */
                offset += parsed;
            }
            if(sp.sxml) (*sp.sxml) << "    </byte_runs>";
        }
    }
}</span>

其中使用    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 onUrl(struct http_parser*, const char*, size_t);
        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::onUrl(struct http_parser* parser, const char* at, size_t length)
    {
        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 onUrl(at, length);
            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编码结束。


是不是很简单,那就用到你项目中吧!


参考:

https://github.com/joyent/http-parser

https://github.com/simsong/tcpflow

https://github.com/siddontang/libtnet

http://rootk.com/post/tutorial-for-http-parser.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

http-parser解析http报文详解 的相关文章

  • 如何发送http basic auth post?

    我的任务是使用基本身份验证创建 http 帖子 我正在 asp net MVC 应用程序中使用 C 进行开发 我也得到过这个例子 POST v2 token endpoint HTTP 1 1 Authorization Basic Y2x
  • 将照片上传到 Google Photos API 不返回上传令牌

    我正在使用 2018 版 Google Photos API 来上传图像和媒体 如下所述 上传字节 https developers google com photos library guides upload media uploadi
  • 在 Flash AS3 中捕获未处理的 IOErrorEvent

    错误 2044 未处理的 IOErrorEvent text 错误 2036 从不加载 完全的 这就是我每次尝试使用加载器加载不存在的图像时看到的情况 我正在获取 URL 列表 但无法验证它们是否指向任何有用的内容 每当遇到 404 时 它
  • SPDY 与保持活动连接上的 http 多路复用有何不同

    HTTP 1 1 支持保持活动连接 在发送 Connection close 之前连接不会关闭 那么 如果浏览器 本例中是firefox 启用了network http pipelined 并且增加了network http pipelin
  • node.js http 服务器,检测客户端何时断开连接

    我使用express 和node js 作为http 服务器 我存储响应对象 以便可以将事件流式传输到该通道上的客户端 有没有办法检测客户端何时断开连接 当我杀死我的客户端时 我仍然可以写入响应对象 而不会收到任何类型的异常 错误 看起来只
  • Django:如何在ajax中返回模型表单集并在模板中使用

    我需要在运行时使用ajax动态地将表单添加到我的表单集中 我指的是使用 Ajax 将表单动态添加到 Django 表单集 https stackoverflow com questions 501719 dynamically adding
  • 如何从 apache 中删除日期标头?

    我需要最小化 Apache HTTP 响应标头 现在我将它们减少如下 HTTP 1 1 200 OK Date Thu 25 Mar 2010 21 57 41 GMT Server Apache Content Type text htm
  • 多个资源的 REST 接口使用

    我目前正在通过 http 添加 REST API 到在线服务 我遇到了一个非常简单的问题 我找不到令我满意的答案 我主要有 2 个资源 用户 和 报告 正如您所猜测的那样 报告与用户相关联 与一个且仅一个 我的数据库中的外键 不管怎样 我有
  • Http PUT 请求到 jpeg

    我收到了如下 HTTP PUT PUT photo HTTP 1 1 X Apple AssetKey F92F9B91 954E 4D63 BB9A EEC771ADE6E8 X Apple Transition Dissolve Con
  • 如何使用 Jade 和 Node.js 迭代 JSON 数组

    所以我有这个 JSON 数组apiData被传递到视图作为data Backend router get function req res var data JSON stringify apiData res render gallery
  • IIS 中特定资源的自定义 http 状态代码

    我有一个带有单个 app offline htm 文件的网站 我如何配置 IIS 以使用特定状态代码 例如 209 而不是默认状态代码 200 返回它 使用 ASP Net 的 app offline htm 功能的替代方法是使用IIS U
  • 如何设置http请求的源IP?

    在发送 http 请求之前 我需要设置源 IP 地址 用于 IP 欺骗等 用于建立http连接的类是HTTPURLConnection 我在 stackoverflow 上找到了下面的链接 这非常有用 注册和使用自定义 java net U
  • IIS7 和 HTTP 状态代码处理

    我因试图对 IIS7 集成模式 中的错误呈现进行完整的编程控制而感到非常头疼 我想要做的是给出一个错误 找不到页面 内部服务器错误 未经过身份验证等 将整个请求传输到自定义 ASPX 或 HTML 我更喜欢后者 并使用正确的 HTTP 状态
  • (Flutter) HTTPClient 参数无效:URI 中未指定主机

    目前正在开发一个小应用程序 允许用户查看存储在 Heroku 上的数据库 但是在使用数据库的 URL herokuapp com api 时 我遇到了上述问题 var client createHttpClient var response
  • 角度 $q.all() 是否有第二次成功,如 jQuery $.get()

    查看 jQuery 文档 我发现了以下内容 get example php function alert success done function alert second success lt fail function alert e
  • Perl:读取网页文本文件并“打开”它

    我正在尝试创建一个脚本来读取文本文件 然后分析它们 无论文本文件是在线还是离线 离线部分完成 使用 open FILENAME anyfilename txt analyze file sub analyze file while
  • 如何在 suave webpart 中设置 Json 响应

    我从 Suave 和 F 开始 我正在尝试在我的 web 部件中传递一个 json 序列化对象以在我的响应中获取它 在 php 中我有这个 player1Key hdegftzj25 gameKey aegfhzkfszl
  • 即使禁用缓存,Safari 也会缓存 GET 请求

    我已经将我所知道的所有标头设置为在我的服务器上禁用缓存 甚至禁用 ETAG 但 Safari 仍然偶尔 大约 50 次 缓存我的请求 Workflow 我正在实施 oauth 1 所以 浏览器使GET api user request 服务
  • gradle - 从 url 下载并解压文件

    从 url 下载和解压文件的正确 gradle 方法是什么 http 如果可能的话 我想防止每次运行任务时重新下载 在ant get可以通过以下方式实现skipexisting true 我当前的解决方案是 task foo ant get
  • 使用 php 和 jquery 的简单彗星示例

    谁能给我一个使用 PHP 的彗星技术的简单好例子 我只需要一个使用持久 HTTP 连接或类似连接的示例 我不想使用轮询技术 因为我已经设置了类似的东西 并且不仅难以使用和管理它的大量资源 另外我使用的是 IIS7 而不是 Apache 一个

随机推荐

  • SIP 注册过程

    SIP协议包含两种类型的消息 xff0c 一种是请求行用于发出邀请 xff0c 而另一种则是状态行 xff0c 用于标明当前通信的状态 请求行和状态行军包含三部分 xff0c 其中每一部分以空格隔开 xff0c 不论是请求行还是状态行均以C
  • UUID原理,以及JAVA生成短8位UUID

    最近需要生成短uuid xff0c 网上查了查资料 xff0c 这里整理记录一下 xff0c 供大家参考 1 前言 UUID xff0c 全名叫做 Universally Unique Identifier xff0c 也就是通用唯一标识符
  • user agent查询(iPhone/ Android/ iPad/ Windows Phone/ Macintosh)

    这里分享一个查询user agent的网站 xff0c 里面可以搜索各个平台的user agent 1 网页 例如 xff1a iPhone的user agent https www plus a net tools user agent
  • 跨源资源共享(CORS)

    转自 https developer mozilla org zh CN docs Web HTTP CORS 跨源资源共享 CORS xff08 或通俗地译为跨域资源共享 xff09 是一种基于HTTP 头的机制 xff0c 该机制通过允
  • 工业软件CAD、CAE、CAM介绍

    最近看了一篇文章介绍工业软件CAD CAE CAM xff0c 这里记录分享一下 自从上世纪八十年代工业软件出现后 xff0c 设计师们终于不用通过手绘来完成图纸的设计了 xff0c 转而在电脑上完成 xff0c 设计效率极大提高 那么工业
  • 502 bad gateway原因、解决方法

    nbsp 网上查了查资料 这里记录一下 nbsp nbsp nbsp 在当今时代 每个人都使用互联网 通常 在使用 Internet 和访问网页时 计算机和网站之间可能会出现连接问题 这些连接问题会产生某些错误代码 称为 nbsp HTTP
  • Lombok详解

    网上看到这篇文章 xff0c 这里记录学习一下 用 x1f336 Lombok xff0c 让 Java 更简洁 ENCODE the WORLD 零 历史 一个标准的 Java bean 一个典型的 Java bean 一般具有几个属性
  • cookie setSecure详解

    1 前言 最近项目用Sparrow Health System检测漏洞 xff0c 发现存在一个setSecure安全漏洞问题 xff0c 于是网上搜索了一下 xff0c 这里记录一下 2 问题 在cas中或其他web开发中 xff0c 会
  • cookie和localStorage详解

    网上看到这篇文章 xff0c 这里记录学习一下 一文带你看懂cookie xff0c 面试前端不用愁 知乎 前言 在前端面试中 xff0c 有一个必问的问题 xff1a 请你谈谈cookie和localStorage有什么区别啊 xff1f
  • Referrer和Referrer-Policy简介

    1 什么是Referer referer参数是http请求头header里的一个关键参数 xff0c 表示的意思是链接的来源地址 xff0c 比如在页面引入图片 JS 等资源 xff0c 或者跳转链接 xff0c 一般不修改策略 xff0c
  • Filebeat 日志采集利器

    网上看到这篇文章 xff0c 觉得很不错 xff0c 这里转载记录一下 目录 Filebeat简介 Filebeat和Beats的关系 目前Beats包含六种工具 Filebeat 是什么 Filebeat 工作的流程图 Filebeat和
  • 无刷电机工作原理介绍

    一 有刷马达的原理 要讲清这一问题 xff0c 那就应粗略地了解一下有刷马达的工作原理 接下来用一个三电极 二磁极内转子有刷马达作为演示 二 无刷电机工作原理 首先 xff0c 无刷电机不是直流电机 xff0c 模型虽然是直流电池供电 xf
  • 通过filebeat、logstash、rsyslog 几种方式采集 nginx 日志

    网上看到这篇文章 xff0c 觉得很不错 xff0c 这里转载记录一下 目录 前言 一 直接通过filebeat采集日志到ES 二 通过filebeat采集日志到logstash再送到ES 接下来配置filebeat xff1a 具体配置如
  • DNS域名解析,以及A、AAAA、CNAME、MX、NS、TXT、SRV、SOA、PTR说明

    温故知新 xff0c 最近网上开到相关文章 xff0c 这里终结记录一下 xff0c 供大家参考 目录 1 A记录 2 CNAME xff1a 两种域名解析方式 4 NS记录 5 TXT记录 xff1a 6 AAAA记录 xff1a 7 S
  • Transfer-Encoding:chunked 说明

    参考 xff1a http blog csdn net wy5761 article details 17568851 先说解决方法 xff1a xff1a xff1a 不让服务器返回Transfer Encoding chunked xf
  • HTTP状态码大全,Nginx 408/499错误

    不错的一个笔记 xff01 状态码太多 xff0c 网上查了下 xff0c 在这里记录学习 状态错误码 1 信息类 xff1a 表示接收到请求并且继续处理 100 xff08 continue xff09 xff1a 说明收到了请求的初始部
  • 浏览器多标签,Http协议和底层socket的情况

    2个浏览器标签同时访问同1个url xff08 即相同ip xff09 xff0c 来get数据 xff0c 判断http的chunked数据包会不会交叉 1 发现chrome是2个标签使用同一个链接 但是第2个get是在第一个get数据收
  • 端口状态 LISTENING、ESTABLISHED、TIME_WAIT及CLOSE_WAIT详解,以及三次握手,滑动窗口

    本文根据众多互联网博客内容整理后形成 xff0c 引用内容的版权归原始作者所有 xff0c 仅限于学习研究使用 网上查了一下端口状态的资料 xff0c 我下面总结了一下 xff0c 自己学习学习 xff1a TCP状态转移要点 TCP协议规
  • http协议以及chunked编码分析

    Http协议 Http协议 格式 HTTP消息包括浏览器向服务器的请求消息和服务器向浏览器的响应消息 这两种类型的消息都由一个起始行 xff0c 一个或者多个头域 xff0c 一个头域结束的空行和可选的消息体组成 HTTP头域一般包括通用头
  • http-parser解析http报文详解

    说明 项目里用到力http parser xff0c 在这里简单说明一下其用法吧 下载地址 xff1a https github com joyent http parser 其使用说明很详细 开源用例 开源tcpflow 1 4 4中使用