LWIP协议栈实现POST方法
(转自我的新浪博客http://blog.sina.com.cn/s/blog_b315f69b0102w534.html)
一、废话
这段时间日子过得很平淡,平淡里面有很多心心念念的小幸福。前段时间一起进公司的哥们儿被开除后找到了一份新的实习期就能拿到更高薪资的工作,临走前小聚了一餐,一起谈谈理想和以后的一些小打算。保持着一颗积极乐观的心态,试着换种理解方式面对生活,那些过不去的小烦恼才不容易被情绪扩大化,不管遇到繁杂的工作,折腾的老板,还是生活琐事也都会慢慢烟消云散。不过分追求,毕竟有得就有失,鱼和熊掌不可兼得。——《穿普达拉的女王》
二、正文
不罗嗦了,接着上期博客继续,这里再谈谈笔者项目中接触的LWIP协议栈如何实现POST功能的,很多不正确的地方希望读者指出,及时改正。现有的LWIP协议栈都只实现GET,未实现POST功能。这里先说说POST的特点和功能:
前文提到,GET和POST是两种最常用的HTTP请求方法。我们日常生活中“打开网页”的操作相当于“使用GET方法获取服务器资源”,而“上传附件”、“提交表格”等操作相当于“使用POST将本地资源提交给服务器”。似乎GET方法和POST方法的区别就是一个用于“索取”,一个用于“递交”。这么说也对也不对,实际使用中确实有部分程序这样使用了,但是HTTP协议设计时可不是这样考虑的,下面的表格简单对比了两者的一些差异。
GET 方法 : POST 方法:
可传递数据类型 ASCII文本(汉字有专门的方法转换) 不限,支持二进制文件
可传送的数据量 有限制(2048字节减去URL长度) 无限制
内容编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或multipart/form-data
后退/刷新 回退后再次前进或刷新不会通知用户 数据会被再次提交
历史记录 浏览器会记录全部内容 浏览器只记录接收POST内容的URL但不 记录POST的具体内容
典型应用 获取服务器上的资源,如下载 向服务器添加资源,如上传附件
至于POST支持上传的内容编码就不详说了,首先我们还得从浏览器客户端说起,浏览器中提交的表单时方式采用POST,如图:
此处提交的表单是一个用户自己选择的二进制文件,在与WEB服务器通信时,点击上传新固件后,浏览器会发出请求,并接着随着请求上传文件数据。这里有个细节的问题,不同的浏览器和不同的提交方式,会导致向服务器提交时发送的第一个和第二个数据包不同:(第二种情况是另一个同事遇到的情况,没具体查看)
第一种情况:第一个包只包含数据包包头,第二个包包含文件名和文件数据,以后的包全是上传的文件数据。
第二种情况:第一个包包含数据包包头和文件名,以及文件数据,以后的包全是文件数据。
笔者是第一种情况,但两种情况的处理方式可根据后面的论述同样处理,接着来看用wireshark抓包工具抓到的第一二个数据包和最后一个数据包:(以下是原始数据和复制到记事本后的数据)
最后一个数据包:
从第一个包中可以看出接着要继续接受的文件的大小(437116字节),接受的数据编码方式,以及后文提到的"boundary=......."等。
在服务器中要使用到POST功能时,首先要打开功能开关,httpd.h中提到一个可以打开POST功能的宏开关需要我们在合适的位置打开它。服务器端的POST实现的流程如下:
服务器端在接收到数据后LWIP中已经实现了每个数据包包头(这里仅指数据包最前面包含src address和dest address等信息的前54个字节)的处理,其后的数据(第一个包的POST处开始后面的数据,以及第二个包中----webki......处开始后面的数据)都存放在一个存放数据的pbuf的结构体中,而且在每次接收一个数据包并处理后,需要对pbuf进行释放,后面的数据才能继续正常接收。对这些数据做出处理时,直接从pbuf中提取出来用即可。
由于第一个包包含了内容是是GET还是POST方法提交的请求,lwip已经实现了对第一个包的解析接口函数http_post_request (),当然也可以加入一些自己需要的功能代码(如后文将要提到的提取第一个包中的boundary)。从第二个包开始后的数据的接收,提取,处理,LWIP协议栈提供了三个标准的POST处理接口:
- httpd_post_begin();
- httpd_post_receive_data();
- httpd_post_finished();
这三个接口用于接收前的准备,接收数据,接收后的处理。但LWIP协议栈并未实现这三个接口,下面我们就可以根据我们抓到的数据包的具体内容来实现这三个标准接口了。
有一个注意的地方,要获取上传的文件数据,需要用到第一个包中的一段标志字符串(每次上传同样的一个文件,这个字符串会不一样),即图中的“boundary=----WebKitFormBoundaryWi9cGoLBSzBxZqzi”,这个标志字符串用于标记接受的数据中文件的开始和结束位置,这串字符串的获取需要在LWIP协议栈中已经实现的接口http_post_request ()中加入一段获取这段字符串的代码,并把这段标记性的字符串保存起来。如下为截取的一段获取这段标志字符串的一些代码:
获得标志字符串后,就可以从pbuf中提取文件数据了。下面就直接贴出这三个接口的源码了:(在贴在这里之前,查了很多网上资料都简单的实现几行代码,没有针对实际通信过程时的处理和数据解析的具体过程)
此接口中记录了后期需要处理的CGI函数,在httpd_post_finished()中进行调用和处理。
2、 httpd_post_receive_data();
在这个接口中,可以将收到的数据用于自己的需求。这里笔者需要实现的功能是将读取的数据写入flash中,自己编写了一个些数据到flash的函数,因为需要八字节对齐才能写入flash,而每次接收的数据大小不满足八字节的倍数,前后两个包需要进行融合,用了一个下午和一个晚上的时间才调试正确出来。
3、 httpd_post_finished();
这个接口函数里面,可以调用前期记录的一些CGI。
到此实现了POST的功能和数据接收以及处理了。