MM32搭建Web服务器——POST指令

Foreword

这周最开心的事情应该就是周二一边吃小龙虾一边把剩下一半的《玫瑰岛不可思议的历史》看完了,Rose Island博主名字的由来。一个活在自己世界里的工程师造了一个自己的国家。玫瑰岛象征了乌托邦,也是真实存在过的历史,被笑称为意大利唯一打赢了的战争。

2017年,罗萨去世了。去世之前,意大利的潜水员们从海底捞上来一块砖头,送还给他。

上面还写着一行字:里米尼的潜水员们很荣幸把梦的碎片还给做梦的人。

上一期说好这周来讲get和post指令的区别。

GET & POST的区别

如果你对服务器和浏览器的交互还没有概念的话呢,建议先看一下「Rose Island」山外多功能调试助手用作虚拟服务器

可以看到,通过域名或是IP地址加载网页内容是默认通过GET请求,之后服务器与客户端的交互可以通过GET,也可以通过POST。

GET和POST的区别大致如下:

GETPOST
可传递数据类型ASCII文本(汉字有专门的方法转换)不限,支持二进制文件
可传递的数据量有限制(2048字节减去URL长度)无限制
内容编码类型application/x-www-form-urlencodedapplication/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>&nbsp;&nbsp;号:<input type="text" placeholder="请输入账号名" name="username" required><br>&nbsp;&nbsp;码:<input type="password" placeholder="请输入密码" name="password" required><br><br>
                <input id="submit" type="submit" name="checkinSubmit" value="login">
        </form>
    </body>
    </html>
    

    点击submit之后可以看到:

    tz_get

    表单数据以param1=param1_value&param2=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>&nbsp;&nbsp;号:<input type="text" placeholder="请输入账号名" name="username" required><br>&nbsp;&nbsp;码:<input type="password" placeholder="请输入密码" name="password" required><br><br>
                <input id="submit" type="submit" name="checkinSubmit" value="login">
        </form>
    </body>
    </html>
    

    点击submit之后可以看到:

    tz_post

    表单数据封装在body中,url中只显示对应的action。

POST请求在CGI中的实现

介绍一下整个项目使用的软硬件环境:

硬件环境:MM32F3277、网线

软件环境:IAR、FreeRTOS、LWIP、makefsfile.exe

如果对CGI和SSI接口不熟悉的话,可以看一下「Rose Island」MM32搭建Web服务器——SSI和CGI接口,里面包括了GET请求在CGI中的实现方法。

先说一下浏览器和MM32服务器通过POST指令请求的过程:

  1. 浏览器和TCP建立连接后,函数http_recv()接收浏览器的请求数据
  2. MM32调用http_parse_request()函数提取接收到的字符串中是GET指令还是POST指令
  3. 调用http_post_request处理POST请求
  4. 调用httpd_post_begin处理header部分的uri
  5. 调用httpd_post_rxpbuf处理接收数据长度
  6. 调用httpd_post_receive_data处理body部分的数据
  7. 调用httpd_handle_post_finished,在这里调用http_find_file
  8. 之后的过程就和上一篇写的一样啦

在LWIP中,默认使用GET方法,所以使用POST指令需要自己写几个函数。

  • 首先得先将这个POST定义修改为1

    #define LWIP_HTTPD_SUPPORT_POST  1
    

    也需要关心一下可传递数据的大小,文件数据一般是1514字节一帧数据地发送给板端

    #define PBUF_POOL_BUFSIZE 1524
    
  • 虽然LWIP中没有写POST的相关函数,但在httpd.h中定义了三个函数,分别是httpd_post_beginhttpd_post_receive_datahttpd_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天班,充实到爆炸,以至于我还有点儿停不下来哈哈哈。

我最爱的五月到了,有西瓜、有冰激凌、有蛋糕,所以让所有的情绪和脾气都滚蛋吧!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值