4.4.4客户端和服务器端交互
HTTP1.0定义了3种请求方法:GET、POST、HEAD;HTTP1.1新增5种请求方法:PUT、DELETE、CONNECT、OPTIONS、TRACE。
GET:请求指定的页面信息,并返回实体主体。
POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头。
PUT:从客户端向服务器传送的数据取代指定的文档的内容。
DELETE:请求服务器删除指定的页面。
CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS:允许客户端查看服务器的性能。
TRACE:回显服务器收到的请求,主要用于测试或诊断。
HTTP响应(Reponce)的状态码:
1xx:指示信息--表示请求已接收,继续处理。
2xx:成功—表示请求已被成功接收、理解、接受。
3xx:重定向—要完成请求必须进行更进一步的操作。
4xx:客户端错误—请求有语法错误或请求无法实现。
5xx:服务器端错误—服务器未能实现合法的请求。
常见状态码:
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
使用WireShark或fiddler抓HTTP协议包,如下所示:
上图红色部分是HTTP客户端请求(GET),蓝色部分是HTTP服务器端响应(200 ok表示请求成功)。
常用的请求有GET和POST,区别如下:
GET请求:
GET /books/?name=Professional%20Ajax HTTP/1.1
Host: www.wrox.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive
POST请求:
POST / HTTP/1.1 //请求行
Host: www.wrox.com //2-7行是请求头部
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
(----此处空一行----) //空行
name=Professional%20Ajax&publisher=Wiley //请求数据,即HTTP消息主体
这是我从网上找的2个例子。当我们发送一些数据请求时,如用户名、密码,GET请求是放入URL中发送的;而且GET请求是可被缓存的,参数保留在浏览器历史中。因此会被爬虫爬走一些关键性的数据。
若使用POST请求,数据是存放在HTTP消息主体中发送的;且POST请求不会被缓存,参数不会保存在浏览器中。因此使用POST请求更加安全。
参考博客:https://www.cnblogs.com/ranyonsue/p/5984001.html
4.4.5 CURL编程
在win10平台下,使用vs2017编译CURL。
(1)在https://curl.haxx.se/download.html下载源码。
(2)解压后,放入D盘,进入D:\curl-7.62.0\winbuild,并复制其文件夹路径。
(3)打开开始菜单,找到vs2017,若编译64位,则选择”适用于VS 2017的x64本机工具命令提示”;若编译32位,则选择”适用于VS 2017的x86本机工具命令提示”。在这里,我们选择编译64位。
(4)进入命令行后,输入cd D:\curl-7.62.0\winbuild,进入winbuild目录;然后输入命令nmake /f Makefile.vc mode=static VC=15 MACHINE=x64 ENABLE_IDN=no DEBUG=no,等待即可。注:这里一定要加上ENABLE_IDN=no,不然它会按照ENABLE_IDN=yes来编译,代码运行后会出现”无法解析的外部符号 __imp__IdnToAscii@20,该符号在函数 _curl_win32_idn_to_ascii 中被引用”和”无法解析的外部符号 __imp__IdnToUnicode@20,该符号在函数 _curl_win32_ascii_to_idn 中被引用”这样的错误。
(5)若使用动态编译,则将”mode=static”改为”mode=dll”;若使用x86平台,将“MACHINE=x64”改为“MACHINE=x86”; 若需要debug版,将“DEBUG=no”改为“DEBUG=yes”;若使用其它版本的Visual Studio,则”VC”后的数字需要改为对应Visual Studio的版本。
(6)编译完成后,打开curl-7.62.0\builds文件夹,libcurl-vc15-x64-release-static-ipv6-sspi-winssl存放的就是编译结果(名称最短的那个文件夹)。我们可以将编译完成的文件夹放入其他路径,以便长期使用,如下图所示。
编译完成CURL,该如何使用呢?
(1)在vs2017中,新建一个项目。右击:项目->属性;将配置改为”所有配置”,即包括Debug调试版本和Release发行版本;平台选择”x64”。
(2)选择VC++目录,在包含目录处,添加”C:\CURL_WIN_X64\include”;在库目录处,添加”C:\CURL_WIN_X64\lib”。因为我们将编译好的头文件、库文件放在C盘CURL_WIN_X64目录下。
(3)选择目录:编译器->输入,在附加依赖项处,添加”libcurl_a.lib”、”ws2_32.lib”、”Crypt32.lib”、”wldap32.lib”,如下图所示。
(4)若使用静态编译的CURL版本,则需要加入预处理定义:CURL_STATICLIB。在C/C++->预处理器->预处理器定义,添加CURL_STATICLIB。
(5)若使用的是Release版本,则进入目录:C/C++->代码生成,在运行库处,选择MD。官方不建议使用MT和MTd,即静态链接多线程库,加d表示是Debug版本。
(6)在解决方案那里,解决方案配置选择Release或Debug,解决方案平台选择x64。
//http发送GET请求
#include <curl/curl.h>
static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
return written;
}
void curl_get_req(const char * url,const char * filename)
{
CURL *curl_handle;
FILE *pagefile;
//static const char *pagefilename = "page.out";
if (url==NULL || filename==NULL)
{
return;
}
curl_global_init(CURL_GLOBAL_ALL);
/* init the curl session */
curl_handle = curl_easy_init();
/* set URL to get here */
curl_easy_setopt(curl_handle, CURLOPT_URL,url);
/* Switch on full protocol/debug output while testing */
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
/* disable progress meter, set to 0L to enable and disable debug output */
curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L);
/* send all data to this function */
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);
/* open the file */
//pagefile = fopen(pagefilename, "wb");
errno_t err = fopen_s(&pagefile,filename,"wb");
if (err==0) {
//if (pagefile) {
/* write the page body to this file handle */
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, pagefile);
/* get it! */
curl_easy_perform(curl_handle);
/* close the header file */
fclose(pagefile);
}
/* cleanup curl stuff */
curl_easy_cleanup(curl_handle);
curl_global_cleanup();
}
int main(int argc, char ** argv)
{
static const char * load_png = "baidu.PNG";
const char * url = "https://www.baidu.com/img/bd_logo1.png?where=super";
curl_get_req(url, load_png);
return 0;
}
运行结果:
* Trying 112.80.248.75...
* TCP_NODELAY set
* Connected to www.baidu.com (112.80.248.75) port 443 (#0)
* schannel: SSL/TLS connection with www.baidu.com port 443 (step 1/3)
* schannel: checking server certificate revocation
* schannel: sending initial handshake data: sending 178 bytes...
* schannel: sent initial handshake data: sent 178 bytes
* schannel: SSL/TLS connection with www.baidu.com port 443 (step 2/3)
* schannel: failed to receive handshake, need more data
* schannel: SSL/TLS connection with www.baidu.com port 443 (step 2/3)
* schannel: encrypted data got 3559
* schannel: encrypted data buffer: offset 3559 length 4096
* schannel: SSL/TLS connection with www.baidu.com port 443 (step 2/3)
* schannel: encrypted data got 347
* schannel: encrypted data buffer: offset 347 length 4096
* schannel: sending next handshake data: sending 126 bytes...
* schannel: SSL/TLS connection with www.baidu.com port 443 (step 2/3)
* schannel: encrypted data got 226
* schannel: encrypted data buffer: offset 226 length 4096
* schannel: SSL/TLS handshake complete
* schannel: SSL/TLS connection with www.baidu.com port 443 (step 3/3)
* schannel: stored credential handle in session cache
> GET /img/bd_logo1.png?where=super HTTP/1.1
Host: www.baidu.com
Accept: */*
* schannel: client wants to read 16384 bytes
* schannel: encdata_buffer resized 17408
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: encrypted data got 7074
* schannel: encrypted data buffer: offset 7074 length 17408
* schannel: decrypted data length: 4000
* schannel: decrypted data added: 4000
* schannel: decrypted data cached: offset 4000 length 16384
* schannel: encrypted data length: 3045
* schannel: encrypted data cached: offset 3045 length 17408
* schannel: decrypted data length: 96
* schannel: decrypted data added: 96
* schannel: decrypted data cached: offset 4096 length 16384
* schannel: encrypted data length: 2920
* schannel: encrypted data cached: offset 2920 length 17408
* schannel: failed to decrypt data, need more data
* schannel: schannel_recv cleanup
* schannel: decrypted data returned 4096
* schannel: decrypted data buffer: offset 0 length 16384
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: max-age=315360000
< Connection: Keep-Alive
< Content-Length: 7877
< Content-Type: image/png
< Date: Mon, 12 Nov 2018 13:53:40 GMT
< Etag: "1ec5-502264e2ae4c0"
< Expires: Thu, 09 Nov 2028 13:53:40 GMT
< Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT
< P3p: CP=" OTI DSP COR IVA OUR IND COM "
< Server: Apache
< Set-Cookie: BAIDUID=DA921C00BF8A31532A27D2D6BFB1F355:FG=1; expires=Tue, 12-Nov-19 13:53:40 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
<
* schannel: client wants to read 4287 bytes
* schannel: encrypted data buffer: offset 2920 length 17408
* schannel: encrypted data got 1425
* schannel: encrypted data buffer: offset 4345 length 17408
* schannel: decrypted data length: 4000
* schannel: decrypted data added: 4000
* schannel: decrypted data cached: offset 4000 length 16384
* schannel: encrypted data length: 316
* schannel: encrypted data cached: offset 316 length 17408
* schannel: decrypted data length: 287
* schannel: decrypted data added: 287
* schannel: decrypted data cached: offset 4287 length 16384
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: decrypted data buffer: offset 4287 length 16384
* schannel: schannel_recv cleanup
* schannel: decrypted data returned 4287
* schannel: decrypted data buffer: offset 0 length 16384
* Connection #0 to host www.baidu.com left intact
图片如下所示:
POST请求中,有四种常见的提交数据方式:application/x-www-form-urlencoded、multipart/form-data、application/json、text/xml。将application/json作为请求头,用来告诉服务器消息主体是序列化后的JSON字符串。大家可以自己用JSON方式来实现POST请求。