因为以前用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 中发送的:
/test/demo_form.asp?name1=value1&name2=value2
有关 GET 请求的其他一些注释:
- GET 请求可被缓存
- GET 请求保留在浏览器历史记录中
- GET 请求可被收藏为书签
- GET 请求不应在处理敏感数据时使用
- GET 请求有长度限制
- GET 请求只应当用于取回数据
Post方法
请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的:
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需要包含的头文件
#include <netdb.h> #include <sys/socket.h>
函数原型:
struct hostent *gethostbyname(const char *name);
hostent结构体的定义如下:
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()。
下面通过一个代码讲解一下这个函数的使用:
#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; }
最后运行打印的结果如下:
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头文件如下:
#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
源码文件如下:
#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查询基金相关的信息,大家也可以使用这个来试一试上面的代码:
#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_ptrhttp_request(new HttpGetPostMethod()); shared_ptr http_request = make_shared (); // 现在使用的是自己的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