基於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
這個我真不太熟,也不太喜歡,所以我就不多做說明了,上網查都有。