Foreword
这周最开心的事情应该就是周二一边吃小龙虾一边把剩下一半的《玫瑰岛不可思议的历史》看完了,Rose Island博主名字的由来。一个活在自己世界里的工程师造了一个自己的国家。玫瑰岛象征了乌托邦,也是真实存在过的历史,被笑称为意大利唯一打赢了的战争。
2017年,罗萨去世了。去世之前,意大利的潜水员们从海底捞上来一块砖头,送还给他。
上面还写着一行字:里米尼的潜水员们很荣幸把梦的碎片还给做梦的人。
上一期说好这周来讲get和post指令的区别。
GET & POST的区别
如果你对服务器和浏览器的交互还没有概念的话呢,建议先看一下「Rose Island」山外多功能调试助手用作虚拟服务器。
可以看到,通过域名或是IP地址加载网页内容是默认通过GET请求,之后服务器与客户端的交互可以通过GET,也可以通过POST。
GET和POST的区别大致如下:
GET | POST | |
---|---|---|
可传递数据类型 | ASCII文本(汉字有专门的方法转换) | 不限,支持二进制文件 |
可传递的数据量 | 有限制(2048字节减去URL长度) | 无限制 |
内容编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded 或multipart/form-data |
后退/刷新 | 回退后再次前进或刷新不会通知用户 | 数据会被再次提交 |
历史记录 | 浏览器会记录全部内容 | 浏览器只记录接收POST内容的URL但不记录POST的具体内容 |
典型应用 | 获取服务器上的资源,如url地址栏请求,下载 | 向服务器添加资源,如上传附件,提交表单 |
GET和POST还有一个区别就是TCP数据包的个数,GET产生一个数据包,POST产生两个数据包,分别放header和data。两者请求的过程如下:
- GET请求过程:
- 浏览器请求tcp连接(第一次握手)
- 服务器答应进行tcp连接(第二次握手)
- 浏览器确认,并发送GET请求header和data(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
- 服务器返回200 OK响应
- POST请求过程:
- 浏览器请求tcp连接(第一次握手)
- 服务器答应进行tcp连接(第二次握手)
- 浏览器确认,并发送POST请求header(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
- 服务器返回100 Continue响应
- 浏览器发送body中的数据
- 服务器返回200 OK响应
不过并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
我对GET和POST区别的理解呢,最重要的是上传表单的时候GET会将内容都显示到浏览器url地址上,安全性很差,而POST会将参数名和参数值放到body中,不会在浏览器url地址上显示出来。
举个栗子看一下:
-
GET请求
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> <title>用户登录 - MindMotion</title> </head> <body> <form action="login.cgi" method="GET" name="login"> <label> <h1>用户账号登录</h1> <hr><br> </label> 账 号:<input type="text" placeholder="请输入账号名" name="username" required><br> 密 码:<input type="password" placeholder="请输入密码" name="password" required><br><br> <input id="submit" type="submit" name="checkinSubmit" value="login"> </form> </body> </html>
点击submit之后可以看到:
表单数据以
param1=param1_value¶m2=param2_vlaue
的形式添加在url后面,以?
分割,每个Param之间用&
分割。 -
POST请求
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> <title>用户登录 - MindMotion</title> </head> <body> <form action="login.cgi" method="POST" name="login"> <label> <h1>用户账号登录</h1> <hr><br> </label> 账 号:<input type="text" placeholder="请输入账号名" name="username" required><br> 密 码:<input type="password" placeholder="请输入密码" name="password" required><br><br> <input id="submit" type="submit" name="checkinSubmit" value="login"> </form> </body> </html>
点击submit之后可以看到:
表单数据封装在body中,url中只显示对应的action。
POST请求在CGI中的实现
介绍一下整个项目使用的软硬件环境:
硬件环境:MM32F3277、网线
软件环境:IAR、FreeRTOS、LWIP、makefsfile.exe
如果对CGI和SSI接口不熟悉的话,可以看一下「Rose Island」MM32搭建Web服务器——SSI和CGI接口,里面包括了GET请求在CGI中的实现方法。
先说一下浏览器和MM32服务器通过POST指令请求的过程:
- 浏览器和TCP建立连接后,函数
http_recv()
接收浏览器的请求数据 - MM32调用
http_parse_request()
函数提取接收到的字符串中是GET指令还是POST指令 - 调用
http_post_request
处理POST请求 - 调用
httpd_post_begin
处理header部分的uri - 调用
httpd_post_rxpbuf
处理接收数据长度 - 调用
httpd_post_receive_data
处理body部分的数据 - 调用
httpd_handle_post_finished
,在这里调用http_find_file
- 之后的过程就和上一篇写的一样啦
在LWIP中,默认使用GET方法,所以使用POST指令需要自己写几个函数。
-
首先得先将这个POST定义修改为1
#define LWIP_HTTPD_SUPPORT_POST 1
也需要关心一下可传递数据的大小,文件数据一般是1514字节一帧数据地发送给板端
#define PBUF_POOL_BUFSIZE 1524
-
虽然LWIP中没有写POST的相关函数,但在
httpd.h
中定义了三个函数,分别是httpd_post_begin
,httpd_post_receive_data
,httpd_post_finished
-
httpd_post_begin
用于初始化POST数据接收功能,获取uri,就是header部分err_t httpd_post_begin(void *connection, const char *uri, const char *http_request,u16_t http_request_len, int content_len, char *response_uri, u16_t response_uri_len, u8_t *post_auto_wnd) { memset(http_uri_buf,0,sizeof(http_uri_buf)); strcpy(response_uri,uri); return ERR_OK; }
-
httpd_post_receive_data
用于接收数据内容,就是body部分,分为params和param_vals-
方法一:
在uri后面加上
?
然后加上*p中的param1=param1_val & param2=param2_val
,在这里人为地把POST转成GET指令的样式,传到http_find_file
中(这个函数默认处理GET指令),用GET指令的方法去调用CGI接口。如果用这种方法的话,还需要修改一下uri的最大长度,不然很容易超。err_t httpd_post_receive_data(void *connection, struct pbuf *p) { *(strrchr(p->payload,'&')) = 0; sprintf(http_uri_buf +strlen(http_uri_buf),"?%s",(char*)p->payload); return ERR_MEM; }
#define LWIP_HTTPD_MAX_REQUEST_URI_LEN 256
-
方法二:
通过形参connection,调用
extract_uri_parameters
将*p中的body数据分别赋给hs的params和param_vals,因为http_find_file
是针对GET指令写的,如果用这种方法的话,就要修改http_find_file
函数,将http_cgi_paramcount = extract_uri_parameters(hs, params);
这句话删掉,不然的话会将hs中的params和param_vals清空。err_t httpd_post_receive_data(void *connection, struct pbuf *p) { struct http_state *hs = (struct http_state *)connection; *(strrchr(p->payload,'&')) = 0; extract_uri_parameters(hs, (char*)p->payload); return ERR_MEM; }
-
-
httpd_post_finished
在数据接收完成后执行,由httpd_handle_post_finished
调用,之后就进入http_find_file
去根据uri查找对应的内容,因为header和body的内容我们在前面两个函数中已经提取出来了,这个函数就先空着。但是需要改一下httpd.c
中的httpd_handle_post_finished
void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len) { }
static err_t http_handle_post_finished(struct http_state *hs) { #if LWIP_HTTPD_POST_MANUAL_WND /* Prevent multiple calls to httpd_post_finished, since it might have already been called before from httpd_post_data_recved(). */ if (hs->post_finished) { return ERR_OK; } hs->post_finished = 1; #endif /* LWIP_HTTPD_POST_MANUAL_WND */ /* application error or POST finished */ /* NULL-terminate the buffer */ // http_uri_buf[0] = 0; // httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN); return http_find_file(hs, http_uri_buf, 0); }
-
这三个函数有很多种写法,可以根据你的功能逻辑进行修改,也可以参考LWIP中的post_example.c
中的写法。
The End
时间过得真的好快,今天打开冰箱发现之前买的保质期21天的酸奶过期了。这周上了6天班,充实到爆炸,以至于我还有点儿停不下来哈哈哈。
我最爱的五月到了,有西瓜、有冰激凌、有蛋糕,所以让所有的情绪和脾气都滚蛋吧!