因为以前用Java写Android的时候感觉Java已经封装得很好了,HTTP实现的东西都不是很了解。然后就买了本HTTP入门书籍《图解HTTP》来读,快速读完了,那就用C++实现简单的HTTP Get和Post请求吧。
在编写代码前,还是需要将HTTP Get和Post请求的区别做下说明的,《图解HTTP》这本书毕竟是入门书籍,这两个的区别都没有说得很详细,所以这里借助互联网,参考下W3School对Get和Post方法的总结。
Get和Post请求的区别
在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
- GET – 从指定的资源请求数据。
- POST – 向指定的资源提交要被处理的数据
Get方法
请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:
1 |
/test/demo_form.asp?name1=value1&name2=value2 |
有关 GET 请求的其他一些注释:
- GET 请求可被缓存
- GET 请求保留在浏览器历史记录中
- GET 请求可被收藏为书签
- GET 请求不应在处理敏感数据时使用
- GET 请求有长度限制
- GET 请求只应当用于取回数据
Post方法
请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的:
1 2 3 |
POST /test/demo_form.asp HTTP/1.1 Host: w3schools.com name1=value1&name2=value2 |
有关 POST 请求的其他一些注释:
- POST 请求不会被缓存
- POST 请求不会保留在浏览器历史记录中
- POST 不能被收藏为书签
- POST 请求对数据长度没有要求
比较 GET 与 POST
下面的表格比较了两种 HTTP 方法:GET 和 POST。
GET | POST | |
---|---|---|
后退按钮/刷新 | 无害 | 数据会被重新提交(浏览器应该告知用户数据会被重新提交) |
书签 | 可收藏为书签 | 不可收藏为书签 |
缓存 | 可被缓存 | 不能缓存 |
历史 | 参数保留在浏览器历史中 | 参数不会保存在浏览器历史中 |
对数据长度限制 | 是的。当发送数据时,GET方法向URL添加数据;URL的长度是受限制的(URL的最大长度是2048个字符) | 无限制 |
对数据类型的限制 | 只允许ASCII字符 | 没有限制,也允许二进制数据 |
安全性 | 和POST相比,GET的安全性较差,因为所发送的数据是URL的一部分。在发送密码或其他,敏感信息时决不能使用GET | POST比GET更安全,因为参数不会被保存在浏览器历史或web服务器日志中 |
可见性 | 数据在URL中所有人都是可见的 | 数据不会显示在URL中 |
下面做一些额外的补充:
- 在数据类型限制这一块,如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。
- 对于数据长度的限制一部分找到其他的说法,首先是”GET方式提交的数据最多只能是2048字节”,因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。
OK,概念性的东西就介绍到这里了,下面还要多介绍一个Linux函数的使用:gethostbyname
gethostbyname()函数说明
想要使用gethostbyname需要包含的头文件
1 2 |
#include <netdb.h> #include <sys/socket.h> |
函数原型:
1 |
struct hostent *gethostbyname(const char *name); |
hostent结构体的定义如下:
1 2 3 4 5 6 7 8 9 |
struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; #define h_addr h_addr_list[0] }; |
下面是结构体各个字段的解析:
- hostent->h_name:表示的是主机的规范名。例如www.google.com的规范名其实是www.l.google.com。
-
hostent->h_aliases:表示的是主机的别名.www.google.com就是google他自己的别名。有的时候,有的主机可能有好几个别名,这些,其实都是为了易于用户记忆而为自己的网站多取的名字。
-
hostent->h_addrtype:表示的是主机ip地址的类型,到底是ipv4(AF_INET),还是pv6(AF_INET6)
-
hostent->h_length:表示的是主机ip地址的长度
-
hostent->h_addr_lisst:表示的是主机的ip地址,注意,这个是以网络字节序存储的。千万不要直接用printf带%s参数来打这个东西,会有问题的哇。所以到真正需要打印出这个IP的话,需要调用inet_ntop()。
下面通过一个代码讲解一下这个函数的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
#include <iostream> #include <netdb.h> #include <arpa/inet.h> using namespace std; // 该示例需要转换的Host name const string kTestHostName1 = "www.blog4jimmy.com"; const string kTestHostName2 = "www.baidu.com"; int main() { struct hostent *hostent1, *hostent2; char **pptr; char str[32]; hostent1 = gethostbyname(kTestHostName1.c_str()); if(hostent1 == nullptr) { cout << "gethostbyname error for host:" << kTestHostName1 << endl; } else { cout << "For " << kTestHostName1 << ": " << endl; // 打印主机的规范名 cout << "h_name: " << hostent1->h_name << endl; // 打印主机的别名 pptr = hostent1->h_aliases; for(;*pptr != nullptr; pptr++) cout << "h_aliases: " << *pptr << endl; // 打印主机的IP地址 switch(hostent1->h_addrtype) { case AF_INET: case AF_INET6: pptr = hostent1->h_addr_list; for(; *pptr != nullptr; pptr++) { cout << "IP Addr: " << inet_ntop(hostent1->h_addrtype, *pptr, str, sizeof(str)) << endl; } } // 打印主机的首先IP地址 cout << "First address: " << inet_ntop(hostent1->h_addrtype, hostent1->h_addr, str, sizeof(str)) << endl; } // 打印的内容和上面的一样,这里不多做讲解 hostent2 = gethostbyname(kTestHostName2.c_str()); if(hostent2 == nullptr) { cout << "gethostbyname error for host:" << kTestHostName1 << endl; } else { cout << endl; cout << "For " << kTestHostName2 << ": " << endl; cout << "h_name: " << hostent2->h_name << endl; pptr = hostent2->h_aliases; for(;*pptr != nullptr; pptr++) cout << "h_aliases: " << *pptr << endl; switch(hostent1->h_addrtype) { case AF_INET: case AF_INET6: pptr = hostent1->h_addr_list; for(; *pptr != nullptr; pptr++) { cout << "IP Addr: " << inet_ntop(hostent1->h_addrtype, *pptr, str, sizeof(str)) << endl; } } cout << "First address: " << inet_ntop(hostent1->h_addrtype, hostent1->h_addr, str, sizeof(str)) << endl; } return 0; } |
最后运行打印的结果如下:
1 2 3 4 5 6 7 8 9 10 11 |
For www.blog4jimmy.com: h_name: www.blog4jimmy.com IP Addr: 166.62.28.146 First address: 166.62.28.146 For www.baidu.com: h_name: www.a.shifen.com h_aliases: www.baidu.com IP Addr: 14.215.177.39 IP Addr: 14.215.177.38 First address: 14.215.177.39 |
获取到了IP地址后,其实可以直接通过IP地址来访问baidu,但是因为我的博客使用共享主机,所以直接通过IP地址访问好像有问题,具体原因博主也不是很清楚。但是访问通过IP地址来访问baidu是没问题的,大家可以试一试。
C++实现GET和POST请求
HttpGetPostMethod头文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#ifndef HTTPGETPOSTMETHOD_H #define HTTPGETPOSTMETHOD_H #include <iostream> class HttpGetPostMethod { public: HttpGetPostMethod(); virtual ~HttpGetPostMethod(); // Http GET 请求 int HttpGet(std::string host, std::string path, std::string get_content); // Http POST 请求 int HttpPost(std::string host, std::string path, std::string post_content); std::string get_request_return(); std::string get_main_text(); int get_return_status_code(); protected: private: // 记录请求返回的状态码 int return_status_code_; // 记录请求返回所有数据 std::string request_return_; // 记录请求返回的报文部分 std::string main_text_; // HTTP请求过程中使用到的Socket通信部分 std::string HttpSocket(std::string host, std::string request_str); // 将HTTP请求返回的数据分解 void AnalyzeReturn(void); }; #endif // HTTPGETPOSTMETHOD_H |
源码文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
#include "../include/HttpGetPostMethod.h" #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/types.h> #include <netinet/in.h> #include <stdlib.h> #include <string> #include <cstring> #include <netdb.h> #include <sstream> #include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> using namespace std; HttpGetPostMethod::HttpGetPostMethod() : return_status_code_(0), request_return_(""), main_text_("") { } HttpGetPostMethod::~HttpGetPostMethod() { } // 分解HTTP请求返回的数据 void HttpGetPostMethod::AnalyzeReturn(void) { size_t position1 = request_return_.find_first_of(" ", 0); size_t position2 = request_return_.find(" ", position1+1); return_status_code_ = atoi(request_return_.substr(position1+1, position2).c_str()); position1 = request_return_.find("\r\n\r\n", position2); main_text_ = request_return_.substr(position1+4); } // HTTP GET请求 int HttpGetPostMethod::HttpGet(std::string host, std::string path, std::string get_content) { stringstream request_str; request_str << "GET " << path << (path=="/" ? "" : "?") << get_content << " HTTP/1.1" << "\r\n"; request_str << "Host: " << host << "\r\n"; // request_str << "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " // << "Ubuntu Chromium/62.0.3202.75 Chrome/62.0.3202.75 Safari/537.36 " << "\r\n"; request_str << "Content-Type: text/html\r\n"; request_str << "Content-Length: 0\r\n"; // 对于现在写的方法,这里必须设置为close,以配合HttpSocket中使用的select request_str << "Connection: close\r\n"; request_str << "\r\n"; #if 0 cout << endl; cout << "dump request string: " << endl; cout << request_str.str() << endl; #endif request_return_ = HttpSocket(host, request_str.str()); if(request_return_ == "") { cout << "Http Socket error!" << endl; return -1; } AnalyzeReturn(); return 0; } // HTTP POST请求 int HttpGetPostMethod::HttpPost(std::string host, std::string path, std::string post_content) { stringstream request_str; request_str << "POST " << path << " HTTP/1.1" << "\r\n"; request_str << "Host: " << host << "\r\n"; // request_str << "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " // << "Ubuntu Chromium/62.0.3202.75 Chrome/62.0.3202.75 Safari/537.36 " << endl; request_str << "Content-Type: application/x-www-form-urlencoded" << "\r\n"; request_str << "Content-Length: " << post_content.length() << "\r\n"; // 对于现在写的方法,这里必须设置为close,以配合HttpSocket中使用的select request_str << "Connection:close" << "\r\n"; request_str << "\r\n"; request_str << post_content; #if 0 cout << endl; cout << "dump request string: " << endl; cout << request_str.str() << endl; #endif request_return_ = HttpSocket(host, request_str.str()); if(request_return_ == "") { cout << "Http Post error!" << endl; return -1; } AnalyzeReturn(); return 0; } std::string HttpGetPostMethod::HttpSocket(std::string host, std::string request_str) { const unsigned int kBufferSize = 1024*1024; struct sockaddr_in server_addr; hostent *server_hostent = nullptr; int client_fd; char recv_buf[kBufferSize]; char ip_str[32]; bzero(recv_buf, kBufferSize); bzero(&server_addr, sizeof(sockaddr_in)); bzero(ip_str, 32); int ret = 0; fd_set client_fd_set; stringstream result_string; int h = 0; // 根据host name获取host的ip地址 server_hostent = gethostbyname(host.c_str()); if(server_hostent == nullptr) { cout << "get host ip address error!" << endl; return ""; } client_fd = socket(AF_INET, SOCK_STREAM, 0); if(client_fd < 0) { cout << "create socket fd error!" << endl; return ""; } server_addr.sin_family = AF_INET; memcpy((char *)&server_addr.sin_addr.s_addr, (char *)*server_hostent->h_addr_list, server_hostent->h_length); server_addr.sin_port = htons(80); ret = connect(client_fd, (struct sockaddr *) &server_addr, sizeof(sockaddr_in)); if(ret < 0) { cout << "connect to server error!" << endl; return ""; } // 将POST或者GET请求发送出去 ret = send(client_fd, request_str.c_str(), request_str.length(), 0); if(ret != request_str.length()) { cout << "Send request unfinish!" << endl; return ""; } FD_ZERO(&client_fd_set); FD_SET(client_fd, &client_fd_set); while(1) { // HTTP请求返回的内容可能会分成几个Socket包,所以这里使用select方法,将返回的内容合并起来 h = select(client_fd + 1, &client_fd_set, NULL, NULL, NULL); if(h == -1) { cout << "read select failed!" << endl; break; } if(FD_ISSET(client_fd, &client_fd_set)) { bzero(recv_buf, kBufferSize); ret = read(client_fd, recv_buf, kBufferSize); // 因为已经在请求中设置了‘connection:close’,所以这里肯定会返回的。 if(ret == 0) { cout << "server has closed this connect!" << endl; break; } result_string << recv_buf; } } shutdown(client_fd, SHUT_RDWR); close(client_fd); return result_string.str(); } string HttpGetPostMethod::get_request_return() { return request_return_; } string HttpGetPostMethod::get_main_text() { return main_text_; } int HttpGetPostMethod::get_return_status_code() { return return_status_code_; } |
上面的代码暂时就这样子处理了,使用方法也很简单,如下是使用一个易源数据的开放API查询基金相关的信息,大家也可以使用这个来试一试上面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#include <iostream> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/types.h> #include <netinet/in.h> #include <stdlib.h> #include <string> #include <cstring> #include <memory> #include "include/HttpGetPostMethod.h" int main() { //shared_ptr<HttpGetPostMethod> http_request(new HttpGetPostMethod()); shared_ptr<HttpGetPostMethod> http_request = make_shared<HttpGetPostMethod>(); // 现在使用的是自己的API key,免费的所以频率只能是一秒钟一次,大家可以测试下 int ret = http_request->HttpGet("route.showapi.com", "/902-1", "showapi_appid=52875&showapi_sign=59c54e39583740bf9708c645c389c9ec&fundCode=519185&needDetails=1"); if(ret == -1) { cout << "Http Socket error!" << endl; } if(http_request->get_return_status_code() != 200) { cout << "Http get status code was: " << http_request->get_return_status_code() << endl; } string request_return = http_request->get_request_return(); string main_text = http_request->get_main_text(); cout << "Return Status Code: " << http_request->get_return_status_code() << endl; cout << "Request Return: " << endl << request_return << endl; cout << "Main Text: " << endl << main_text << endl; sleep(5); ret = http_request->HttpGet("route.showapi.com", "/902-1", "showapi_appid=52875&showapi_sign=59c54e39583740bf9708c645c389c9ec&fundCode=000478&needDetails=1"); if(ret == -1) { cout << "Http Socket error!" << endl; } if(http_request->get_return_status_code() != 200) { cout << "Http get status code was: " << http_request->get_return_status_code() << endl; } request_return = http_request->get_request_return(); main_text = http_request->get_main_text(); cout << "Return Status Code: " << http_request->get_return_status_code() << endl; cout << "Request return: " << endl << request_return << endl; cout << "Main Text: " << endl << main_text << endl; } |
十分感谢你的文章
有个小瑕疵,gethostbyname用法介绍,关于kTestHostName2的代码中,有些地方hostent2写成了hostent1