【Web】C++ Http -- 記一次使用第三方http請求的問題解決

使用一個第三方的http請求,在VS裏打印如下,而作者在處理時,以\r\n\r\n處理獲取頭部,剩餘部分全部作爲了body,害的我追了幾個小時,wireShark抓包如下。
http分四部分:頭部、頭部分隔符、body長度,body
(頭部)
\r\n\r\n
(bodylength)\r\n
body

HTTP/1.1 200 OK\r\n
Date: Tue, 14 Jun 2016 17:42:00 GMT\r\n
Server: Apache/2.4.7 (Ubuntu)\r\n
Vary: Accept-Encoding\r\n
Connection: close\r\n
Transfer-Encoding: chunked\r\n
Content-Type: text/plain;charset=UTF-8\r\n
\r\n\r\n
71\r\n
00rtmp://video-center.alivecdn.com/show/3-1?vhost=live.abcdedo.com\r\n
閫夋墜鐜嬪皬涓洿鎾?\r\n
admin\r\n
2eCuw0F\r\n
S0000007qv\r\n

看到上述 71\r\n,該被處理掉,此爲body的長度(後面帶了一個\r\n作爲結束符)
附上wireShark的抓包圖:
這裏寫圖片描述
122bytes的數據段和VS打印的相符

細心的讀者獲取會看到:圖中出現了(gzip):132 bytes -> 122 bytes

還沒有搞懂,在 HTTP chunked response 裏爲什麼兩個 Data chunk,,分別爲122 + 10,
而gizp又是怎麼使用的呢??有機會繼續追蹤。。

附HTTP:找不到原作者是誰了,和下載地址了,非常感謝原作者的貢獻:
Http.h

#pragma once
#include <string>

typedef bool (*httpResponseCB)(const char* data, int len, int offset, int totalsize, bool bFin, void* param) ;
struct HttpResponse
{
public:
    explicit HttpResponse(){ clear();}
    std::string http_version;       // 版本
    unsigned int status_code;       // 狀態碼
    std::string status_message; // 狀態
    std::string header;                 // HTTP包頭
    std::string body;           // HTTP返回的內容
    std::string content_type;
    std::string modify_time;
    unsigned int content_length;
    unsigned int total_length;
    unsigned int offset;
    httpResponseCB funcResponseCB;
    void* param;
    void clear()
    {
        http_version.clear();
        status_code = -1;
        status_message.clear();
        header.clear();
        content_type.clear();
        modify_time.clear();
        content_length = 0;
        total_length = 0;
        offset = 0;
        body.clear();
        funcResponseCB = NULL;
        param = NULL;
    }
};

class CHttpPrivate; // 封裝ASIO操作

/*
* @brief 發送http請求並接收結果, 單線程, 同步
*/
class  CHttp
{
public:

    CHttp(void);
    virtual ~CHttp(void);

    //************************************
    // Method:    PostRequest
    // FullName:  CHttp::PostRequest
    // Access:    public 
    // Returns:   bool
    // Qualifier:
    // Parameter: const std::string & szUrl 
    // Parameter: CHttpResponse & result        HTTP請求返回的結果
    // Parameter: const std::string & data  默認爲空, 使用GET http請求, 若不爲空, 則POST HTTP請求和data
    //************************************
    bool PostRequest(const std::string &szUrl, HttpResponse &result, const std::string &data = std::string(), int rangeStart = 0);  // post

    bool PostRequest(const std::string &szUrl, std::string &szResult, const std::string &data = std::string()); // post

    //************************************
    // Method:    ParseUrl
    // FullName:  CHttp::ParseUrl
    // Access:    public 
    // Returns:   bool return false indicate invalid URL
    // Qualifier:
    // Parameter: std::string szUrl         傳入URL
    // Parameter: std::string & szHost  解析後的host
    // Parameter: std::string & szParam 解析後的host參數
    //************************************
    bool ParseUrl(std::string szUrl, std::string &szHost, std::string &szParam);

    enum{
        SUCCESS_OK = 0,
        ERR_BADURL,
        ERR_NETWORK,
        ERR_HTTPRESP,
        ERR_UNKNOWN
    };

    int GetLastErr() { return m_lasterr;};
private:    
    int m_lasterr;
    CHttpPrivate *p; 
    friend class CHttpPrivate;
};

Http.cpp

#include "stdafx.h"

#include "Http.h"
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>  
#include <boost/asio.hpp>
#include <boost/algorithm/string.hpp>
#include "CStringUtilUnicode.h"
using boost::asio::ip::tcp;

class CHttpPrivate
{
private:
    bool ParseHeader(const std::string &str, HttpResponse &result);

    CHttp* _d;
public:
    CHttpPrivate(CHttp* d); 
    enum HTTP_METHOD{ GET, POST};
    bool RecvResponse(HttpResponse &result);
    bool connect(const std::string &szHost);

    void  FillRequest(
        boost::asio::streambuf &request, 
        HTTP_METHOD method , 
        const std::string &szHost, 
        const std::string &szParam, 
        const std::string &data = std::string(),
        const int rangeStart = 0);


    boost::asio::io_service m_io_service;
    tcp::resolver m_resolver;
    tcp::socket m_socket; 
};

CHttpPrivate::CHttpPrivate( CHttp* d )
    : m_resolver(m_io_service)
    , m_socket(m_io_service)
{
    _d = d;
}

bool CHttpPrivate::ParseHeader(const std::string &strHeader, HttpResponse &result)
{
    // Check that response is OK.
    std::istringstream response_stream(strHeader);
    response_stream >> result.http_version;
    response_stream >> result.status_code;

    std::string strLine;
    std::getline(response_stream, strLine);
    while (!strLine.empty())
    {
        if (strLine.find("Content-Type:") != std::string::npos)
        {
            result.content_type = strLine.substr(strlen("Content-Type:"));
            result.content_type.erase(0, result.content_type.find_first_not_of(" "));
        }
        if (strLine.find("Content-Length:") != std::string::npos)
        {
            result.content_length = atoi(strLine.substr(strlen("Content-Length:")).c_str());
            result.total_length = result.content_length;
        }
        if (strLine.find("Last-Modified:") != std::string::npos)
        {
            result.modify_time = strLine.substr(strlen("Last-Modified:"));
            result.modify_time.erase(0, result.modify_time.find_first_not_of(" "));
        }   
        if (strLine.find("Content-Range: bytes") != std::string::npos)
        {
            std::string tmp = strLine.substr(strlen("Content-Range: bytes"));
            result.offset = atoi(tmp.substr(0, tmp.find('-')).c_str());
            int ipos = tmp.find('/');
            int ivalue = 0;
            if (ipos != std::string::npos)
            {
                ivalue = atoi(tmp.substr(ipos+1).c_str());
            }
            if (ivalue) 
                result.total_length = ivalue;
        }
        strLine.clear();
        std::getline(response_stream, strLine);
    }
/*
    if (result.offset < 0 && result.content_length+result.offset>0 )
        result.offset += result.content_length;
*/
    if ( result.http_version.substr(0, 5) != "HTTP/")
    {
        _d->m_lasterr = CHttp::ERR_HTTPRESP;
        std::cout << "Invalid response\n";
        return false;
    }
    if (result.status_code != 200 && result.status_code != 206)
    {
        _d->m_lasterr = CHttp::ERR_HTTPRESP;
        std::cout << "Response returned with status code " 
            << result.status_code << "\n";
        return false;
    }

    return true;
}

bool CHttpPrivate::RecvResponse( HttpResponse &result )
{
    boost::asio::streambuf response;
    std::ostringstream packetStream;
    try
    {
        _d->m_lasterr = CHttp::SUCCESS_OK;
        // Read until EOF, writing data to output as we go.
        bool hasReadHeader = false;

        boost::system::error_code error;

        result.body.clear();
        while (boost::asio::read(m_socket, response, boost::asio::transfer_at_least(1), error))
        {
            packetStream.str("");
            packetStream << &response;

            std::string packetString = packetStream.str(); 
            std::wstring log = CUtility::Ansi2Wchar(packetString);
            OutputDebugString(log.c_str());


            if (!hasReadHeader)
            {
                // 取出http header
                size_t nEndHeader = packetString.find("\r\n\r\n");
                if(nEndHeader == std::string::npos)
                    continue;

                hasReadHeader = true;

                result.header = packetString.substr(0, nEndHeader);
                if (!ParseHeader(result.header, result))
                    return false;
                packetString.erase(0, nEndHeader + 4);
                int bodyLenPos = packetString.find("\r\n");
                packetString.erase(0, bodyLenPos + 2);

                int bodyEndPos = packetString.find("\r\n");
                packetString = packetString.substr(0, bodyEndPos);

                std::wstring log = CUtility::Ansi2Wchar(packetString);
                OutputDebugString(log.c_str());
            }

//          response.consume(response.size());

            if (result.funcResponseCB)
            {
                if (!result.param) 
                {
                    _d->m_lasterr = CHttp::ERR_UNKNOWN;
                    return false;
                }
                if (!result.funcResponseCB(packetString.c_str(), packetString.length(), result.offset, result.total_length, false, result.param))
                {
                    _d->m_lasterr = CHttp::ERR_UNKNOWN;
                    return false;
                }
            }
            else
            {
                result.body.append(packetString);
            }
        }

        if (result.funcResponseCB)
        {
            result.funcResponseCB(NULL, 0, 0, result.content_length, true, result.param);
        }

        if (error != boost::asio::error::eof)
        {
            _d->m_lasterr = CHttp::ERR_UNKNOWN;
            throw boost::system::system_error(error);
        }
    }
    catch (std::exception& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
        if (_d->m_lasterr == CHttp::SUCCESS_OK)
            _d->m_lasterr = CHttp::ERR_NETWORK;
        return false;
    }

    return true;
}

void CHttpPrivate::FillRequest( boost::asio::streambuf &request, HTTP_METHOD method , const std::string &szHost, const std::string &szParam, const std::string &data /*= std::string()*/, const int rangeStart)
{
    // Form the request. We specify the "Connection: close" header so that the
    // server will close the socket after transmitting the response. This will
    // allow us to treat all data up until the EOF as the content.

    std::ostream request_stream(&request);      

    switch(method)
    {
    case GET:
        request_stream << "GET " ;
        request_stream << szParam << " HTTP/1.1\r\n";
        request_stream << "Host: " << szHost << "\r\n";
        break;
    case POST:
        request_stream << "POST ";
        request_stream << szParam << " HTTP/1.1\r\n";
        request_stream << "Host: " << szHost << "\r\n";
        request_stream << "Content-Length:" << data.size();
        break;
    }

    request_stream << "Accept: */*\r\n";
    request_stream << "Pragma: no-cache\r\n";
    request_stream << "Cache-Control: no-cache\r\n";
    request_stream << "Connection: close\r\n";
    if (rangeStart)
    {
        request_stream << "Range: bytes=" << rangeStart << "- \r\n";    
    }
    request_stream << "\r\n";
}

bool CHttpPrivate::connect( const std::string &szHost )
{

    // Get a list of endpoints corresponding to the server name.
    std::string szService ("http");
    std::string szIp = szHost;
    int i = szHost.find(":") ;
    if (i != -1)
    {
        szService = szHost.substr(i+1);
        szIp = szHost.substr(0, i);
    }
    tcp::resolver::query query(szIp, szService);
    tcp::resolver::iterator endpoint_iterator = m_resolver.resolve(query), end_it;

    // Try each endpoint until we successfully establish a connection.
    tcp::resolver::iterator it = boost::asio::connect(m_socket, endpoint_iterator);

    if(it == end_it)
        return false;
    return true;
}

CHttp::CHttp(void)
{
    p = new CHttpPrivate(this);
    m_lasterr = SUCCESS_OK;
}


CHttp::~CHttp(void)
{
    delete p; 
}

bool CHttp::ParseUrl(std::string szUrl, std::string &szHost, std::string &szParam)
{
    do 
    {
        if(szUrl.empty())
            break;  

        boost::trim(szUrl);

        std::string szProtocol = szUrl.substr(0, 7);
        boost::to_upper(szProtocol);

        size_t pos;

        if(szProtocol.compare("HTTP://") != 0 )
            break;

        pos = szUrl.find_first_of("/", 7);
        if(std::string::npos == pos)
            break;

        szHost = szUrl.substr(7, pos - 7);

        szParam = szUrl.substr(pos, std::string::npos);

        return true;
    } while (0);

    return false;
}

bool CHttp::PostRequest( const std::string &szUrl, HttpResponse &result, const std::string &data /*= std::string()*/ , int rangeStart/* =0 */)
{
    try
    {
        std::string szHost;
        std::string szParam;

        if(!ParseUrl(szUrl, szHost, szParam))
        {
            m_lasterr = ERR_BADURL;
            std::cerr << "Fail in parsing url " << std::endl;
            return false;
        }

        if(!p->connect(szHost))
        {
            m_lasterr = ERR_NETWORK;
            return false;
        }
        boost::asio::streambuf request;
        if(data.empty())
        {
            p->FillRequest(request, CHttpPrivate::GET, szHost, szParam, std::string(), rangeStart);
            boost::asio::write(p->m_socket, request);
        } 
        else
        {
            p->FillRequest(request, CHttpPrivate::POST, szHost, szParam, std::string(), rangeStart);
            boost::asio::write(p->m_socket, request);
            boost::asio::write(p->m_socket, boost::asio::buffer(data));
        }

        if(!p->RecvResponse(result))
            return false;
    }
    catch (std::exception& e)
    {
        m_lasterr = ERR_UNKNOWN;
        std::cout << "Exception: " << e.what() << "\n";
        return false;
    }
    return true;
}

bool CHttp::PostRequest( const std::string &szUrl, std::string &szResult, const std::string &data /*= std::string()*/ )
{
    HttpResponse result;
    if(!this->PostRequest(szUrl, result))
        return false; 
    szResult = result.body; 
    return true; 
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章