1. 程式人生 > >基於C++實現HTTP的封裝

基於C++實現HTTP的封裝

因為在專案中有和java後臺對接的http介面,所以在此學習和總結了一下,c++如何實現Http協議的post\get\put等請求方式,通過蒐集一些資料發現,有現成的封裝庫可以實現,如curl、boost、libcurl等.大家可以直接做http應用開發。而本文是基於c/c++ socket 封裝的Http介面,直接程式碼

以上傳檔案介面為例:

HttpRequest.cpp

#include "HttpRequest.h"
#include "TimerOut.h"
#include "WriLog.h"
#include "GlobleMsg.h"
HttpRequest::HttpRequest()
{

}

HttpRequest::~HttpRequest()
{

}

std::string HttpRequest::get_file_contents(const char* filename){
    std::FILE *fp = std::fopen(filename,"rb");
    std::string contents = "";
    if(fp){
        std::fseek(fp,0,SEEK_END);
        contents.resize(std::ftell(fp));
        std::rewind(fp);
        std::fread(&contents[0],1,contents.size(),fp);
        std::fclose(fp);
    }
        return contents;
}
string HttpRequest::HttpPostFile(char* hostname, char* strUrl,string & content,int port)
{
    Common *com = Common::get_instance();
    struct hostent* host_addr = gethostbyname(hostname);
    if (host_addr == NULL)
    {
        EasyLog::Inst()->Log("Unvalible host!");
        return "";
    }

    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons((unsigned short)port);
    sin.sin_addr=  *((struct in_addr *)host_addr->h_addr);
    bzero(&(sin.sin_zero),8);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
    {
        EasyLog::Inst()->Log("create sock error\r\n!");
        return "";
    }

    if (connect(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr) ) == -1)
    {
        EasyLog::Inst()->Log("connect sock error\r\n!");
        return "";
    }

//http head
    char send_str[2048] = {0};
    strcat(send_str, "POST ");
    strcat(send_str, strUrl);
    strcat(send_str, " HTTP/1.1\r\n");
    strcat(send_str, "Host: ");
    strcat(send_str, hostname);
    strcat(send_str, "\r\n");
    strcat(send_str, "Connection: keep-alive\r\n");
    char endsss[512] = {0};
    strcat(endsss,"------WebKitFormBoundarybZRmyZBq9p09AotO\r\n");
    strcat(endsss,"Content-Disposition: form-data; name=\"image\";                       filename=\"*.jpg\"\r\n");
    strcat(endsss,"Content-Type: application/octet-stream\r\n\r\n");


    char ends[190] = {0};
    strcat(ends, "\r\n------WebKitFormBoundarybZRmyZBq9p09AotO--\r\n");


    char content_header[512];

    sprintf(content_header,"Content-Length: %d\r\n",content.size()+strlen(endsss)+strlen(ends));

    strcat(send_str, content_header);
    strcat(send_str, "Cache-Control: no-cache\r\n");
    strcat(send_str, "Content-Type: multipart/form-data; boundary=----WebKitFormBoundarybZRmyZBq9p09AotO\r\n");
    strcat(send_str, "Accept: */*\r\n");
    strcat(send_str, "Accept-Encoding: gzip,deflate,sdch\r\n");
    strcat(send_str, "Accept-Language: zh-CN,zh;q=0.8\r\n");




    strcat(send_str, "\r\n\r\n");

    if (write(sock, send_str, strlen(send_str)) == -1)
    {
        cout<<"send failed"<<endl;
        return "";
    }
    if (write(sock, endsss, strlen(endsss)) == -1)
     {
        cout<<"send failed"<<endl;
        return "";


     }
    if (write(sock, content.c_str(), content.size()) == -1)
    {


       cout<<"send failed"<<endl;
       return "";
    }

    if (write(sock, ends, strlen(ends)) == -1)
    {
        EasyLog::Inst()->Log("sock send error!");
        return "";


    }
     TimeWatcher<std::chrono::seconds> watcher(dev_info.NewTimeout);
    char recv_str[4096] = {0};
    	while(!watcher())
	{
		int ret = recv(sock, (void *)recv_str, sizeof(recv_str),0);
		if (ret == 0) //
		{
			close(sock);
			return "";
		}
		else if(ret > 0) {
            string result = recv_str;
  
            if(!result.empty()){
        if(result.find("{") < 0)
            return "";
        Json::Reader reader;
        Json::Value jsonobject;
        result = com->StringExtract(recv_str,'{');
        if(!reader.parse(result,jsonobject))
        return "";
        result = jsonobject["path"].asString();
}
else
    result = "";
    return result;
		}
		else if(ret < 0) //
		{
			if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
				continue;
			}
			else {
				close(sock);
				return "";
			}
		}
	}
}



HttpRequest.h

#ifndef __HTTP__
#define __HTTP__

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <iosfwd>
#include <iostream>
#include <fstream>
#include <netdb.h>
#include <cerrno>
#include <string>
#include <cstring>
#include "string.h"
#include "json/json.h"
#include "json/reader.h"
#include <ios>
#include "Common.h"
#define BUFSIZE 41000   //41000 1024*100
#define URLSIZE 1024
using namespace std;
class HttpRequest
{
	public:
		HttpRequest();
		~HttpRequest();
		static HttpRequest*  get_instance(){
            static HttpRequest* instance = NULL;
            if(instance == NULL)
            {
                instance = new HttpRequest;
                   return instance;
            }

		}

	string HttpPostFile(char* hostname, char* strUrl,string & content,int port);
        std::string get_file_contents(const char* filename);
	private:

};

#endif

HTTP規範中的幾種請求方式

我們知道http是基於TCP/IP的應用層協議,其規範了幾種與伺服器互動的不同方式,在我看來這幾種方式 ,類似於操作資料庫的增、刪、改、查。

GET:用來請求訪問已被URI識別的資源,指定的資源經伺服器解析後返回響應內容;

POST:傳輸實體主體;

PUT:傳輸檔案,鑑於HTTP/1.1的PUT方法自身不帶驗證機制,任何人都可以上傳檔案,存在安全性問題,因此一般的網站不建議使用該方法。若配合Web應用程式的驗證機制,或架構設計採用REST標準的同類Web網站,就可能會開放使用PUT方法。

HEAD:獲得報文首部,和GET方法一樣,只是不返回報文主體部分,用於確認URI的有效性及資源更新的日期時間等。

DELETE:刪除檔案,和PUT方法一樣不帶驗證機制。

OPTIONS:用來查詢針對請求URI指定的資源支援的方法。

TRACE:追蹤路徑,客戶端通過TRACE方法可以查詢傳送出去的請求是怎樣被加工修改/篡改的。這是因為,想要連線到源目標伺服器可能會通過代理中轉,TRACE方法就是用來確認連線過程中發生的一系列操作。

CONNECT: 要求用隧道協議連線代理。CONNECT方法在與代理伺服器通訊時建立隧道,實現用隧道協議進行TCP通訊。主要使用SSL和TLS協議把通訊內容經網路隧道傳輸。

HTTP資料提交格式

服務端通常是根據請求頭(headers)中的 Content-Type 欄位來獲知請求中的訊息主體是用何種方式編碼,再對主體進行解析。所以說到 POST 提交資料方案,包含了 Content-Type 和訊息主體編碼方式兩部分。

1.表單格式application/x-www-form-urlencoded

這是通過表單傳送資料時預設的編碼型別。我們沒有在form標籤中設定enctype屬性時預設就是application/x-www-form-urlencoded型別的。

application/x-www-form-urlencoded編碼型別會把表單中傳送的資料編碼為“名稱/值”對。 這是標準的編碼格式。當表單的ACTION為POST的時候,

瀏覽器把form資料封裝到http body中,然後傳送到伺服器。當表單的ACTION 為GET的時候,application/x-www-form-urlencoded編碼型別會

把表單中傳送的資料轉換成一個字串 (name=coderbolg&key=php),然後把這個字串附加到URL後面,並用?分割,接著就請求這個新的URL。

當我們通過 POST方式向伺服器傳送AJAX請求時最好要通過設定請求頭來指定為application/x-www-form-urlencoded編碼型別。方法是在xmlobject.open()

方法之後新增xmlobject.setRequestHeader("Content- Type","application/x-www-form-urlencoded") 不然伺服器會接收不到POST過來的資料。

2.multipart/form-data

在向伺服器傳送大量的文字、包含非ASCII字元的文字或二進位制資料時“application/x-www-form-urlencoded”這種編碼方式效率很低。因此在檔案上傳下載時,所使用的編碼型別應當是“multipart/form-data”,它既可以傳送文字資料,也支援二進位制資料上載。Browser 端<form>表單的ENCTYPE屬性值為multipart/form-data,它告訴我們傳輸的資料要用到多媒體傳輸協議,由於多媒體傳輸的都是大量的資料,所以規定上傳檔案必須是post方法,<input>的type屬性必須是file。(表單裡有圖片上傳用 ENCTYPE="multipart/form-data")<form name="userInfo" method="post" action="first_submit.jsp"    ENCTYPE="multipart/form-data">表單標籤中設定enctype="multipart/form-data"來確保匿名上載檔案的正確編碼

3. application/json

application/json 這個 Content-Type 作為響應頭。這是我最喜歡的一種的資料提交方式,用來告訴服務端訊息主體是序列化後的 JSON 字串。由於 JSON 規範的流行,除了低版本 IE 之外的各大瀏覽器都原生支援 JSON.stringify,服務端語言也都有處理 JSON 的函式,因此,使用json是很方便的。

4. text/xml

 這個我真不太熟,也不太喜歡,所以我就不多做說明了,上網查都有。