在前三篇文章中自己介绍了如何配置freeRTOS以及如何配置LWIP,并使用lwip实现一个httpd服务器,使浏览器可以访问。在本章中我们利用CGI功能,实现通过网页来控制单片机的一个LED灯的电平翻转。
自己写的另外五篇文章
从零开始Cubemx配置STM32搭载freeRTOS实现多路ADC(一)
从零开始Cubemx配置STM32搭载freeRTOS以及lwip实现tcp网络通信(二)
从零开始使用CubeMX配置STM32使用lwip实现httpd服务器以及使用vscode编辑阅读keil代码(三)
CubeMX配置STM32实现FatFS文件系统(五)
CUBEMX配置STM32实现FTP文件传输以及使用SNTP获取网络时间并写入RTC
在这里把CGI功能开启。
CGI的介绍部分参考这位老哥的文章 https://blog.csdn.net/weixin_39517902/article/details/111205216
CGI是common gateway interface的缩写,大家都译作通用网关接口,但很不幸,我们无法见名知意。
我们知道,web服务器所处理的内容都是静态的,要想处理动态内容,需要依赖于web应用程序,如php、jsp、python、perl等。但是web
server如何将动态的请求传递给这些应用程序?它所依赖的就是cgi协议。没错,是协议,也就是web
server和web应用程序交流时的规范。换句话说,通过cgi协议,再结合已搭建好的web应用程序,就可以让web
server也能"处理"动态请求(或者说,当用户访问某个特定资源时,可以触发执行某个web应用程序来实现特定功能),你肯定知道处理两字为什么要加上双引号。
简单版的cgi工作方式如下:
例如,在谷歌搜索栏中搜索一个关键词"http",对应的URL为:https://www.google.com/search?q=http&oq=http&aqs=chrome…69i57j69i60l4j0.1136j0j8&sourceid=chrome&ie=UTF-8
当谷歌的web
server收到该请求后,先分析该url,从中知道了要执行search程序,并且还知道了一系列要传递给search的参数及其对应的value。web
server会将这些程序参数和其它一些环境变量根据cgi协议通过TCP或套接字等方式传递给已启动的cgi程序(可能是cgi进程,或者是已加载的模块cgi模块)。当cgi进程接收到web
server的请求后,调用search程序并执行,同时还会传递参数给search程序。search执行结束后,cgi进程/线程将处理结果返回给web
server,web server再返回给浏览器。
有多种方式可以执行cgi程序,但对http的请求方法来说,只有get和post两种方法允许执行cgi脚本(即上面的search程序)。实际上post方法的内部本质还是get方法,只不过在发送http请求时,get和post方法对url中的参数处理方式不一样而已。
在这里我新建一个文件命名为apimain.c
#include "apimain.h"
#include "httpd.h"
#include
static const char *api_operation_req(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);
//核心就是根据URL的参数来执行不同的处理函数
static const tCGI g_psConfigCGIURIs[] = {
{"/img/sics.gif", api_operation_req},
};
const char *api_operation_req(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
// HAL_GPIO_WritePin(GPIOC, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_3);
printf("cgi success!!!!\n");
}
void api_server_init(void)
{
httpd_init();
//这是CGI的初始化
http_set_cgi_handlers(g_psConfigCGIURIs, sizeof(g_psConfigCGIURIs)/sizeof(tCGI));
}
应用CGI的核心就是根据URL的参数来执行不同的处理函数,比如请求的URL是http://192.168.0.205/img/sics.gif ,我就是利用了这个GET请求,来实现一个LED灯的电平翻转,刷新网页的时候会执行这个请求。
CGI具体实现在httpd.c的http_find_file( )函数中,主要是下面几句,可以看到与URL的字符串进行对比,如果一样就进入CGI的这个句柄分支。
#if LWIP_HTTPD_CGI
http_cgi_paramcount = -1;
/* Does the base URI we have isolated correspond to a CGI handler? */
if (httpd_num_cgis && httpd_cgis) {
for (i = 0; i < httpd_num_cgis; i++) {
if (strcmp(uri, httpd_cgis[i].pcCGIName) == 0) {
/*
* We found a CGI that handles this URI so extract the
* parameters and call the handler.
*/
http_cgi_paramcount = extract_uri_parameters(hs, params);
uri = httpd_cgis[i].pfnCGIHandler(i, http_cgi_paramcount, hs->params,
hs->param_vals);
break;
}
}
}
刷新网页请求的url是/img/sics.gif,因此我的apimain.c写的就是,这样执行这个请求的时候就会进api_operation_req()这个函数。这样我们就实现了使用网页的请求来控制单片机和其他设备。
static const tCGI g_psConfigCGIURIs[] = {
{"/img/sics.gif", api_operation_req},
};
LWIP的HTTPD服务没有实现POST的相关处理,但是httpd.h中已经进行了声明,但是在httpd.c中没有进行具体的实现,这里坑我了一下,我没仔细看,开启LWIP_HTTPD_SUPPORT_POST的宏后,编译不过去,如果想要支持POST操作的话,要自己写函数的实现,主要是这三个函数,网上有具体的实现。
#if LWIP_HTTPD_SUPPORT_POST
/* These functions must be implemented by the application */
/**
* @ingroup httpd
* Called when a POST request has been received. The application can decide
* whether to accept it or not.
*
* @param connection Unique connection identifier, valid until httpd_post_end
* is called.
* @param uri The HTTP header URI receiving the POST request.
* @param http_request The raw HTTP request (the first packet, normally).
* @param http_request_len Size of 'http_request'.
* @param content_len Content-Length from HTTP header.
* @param response_uri Filename of response file, to be filled when denying the
* request
* @param response_uri_len Size of the 'response_uri' buffer.
* @param post_auto_wnd Set this to 0 to let the callback code handle window
* updates by calling 'httpd_post_data_recved' (to throttle rx speed)
* default is 1 (httpd handles window updates automatically)
* @return ERR_OK: Accept the POST request, data may be passed in
* another err_t: Deny the POST request, send back 'bad request'.
*/
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);
/**
* @ingroup httpd
* Called for each pbuf of data that has been received for a POST.
* ATTENTION: The application is responsible for freeing the pbufs passed in!
*
* @param connection Unique connection identifier.
* @param p Received data.
* @return ERR_OK: Data accepted.
* another err_t: Data denied, http_post_get_response_uri will be called.
*/
err_t httpd_post_receive_data(void *connection, struct pbuf *p);
/**
* @ingroup httpd
* Called when all data is received or when the connection is closed.
* The application must return the filename/URI of a file to send in response
* to this POST request. If the response_uri buffer is untouched, a 404
* response is returned.
*
* @param connection Unique connection identifier.
* @param response_uri Filename of response file, to be filled when denying the request
* @param response_uri_len Size of the 'response_uri' buffer.
*/
void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len);
#if LWIP_HTTPD_POST_MANUAL_WND
void httpd_post_data_recved(void *connection, u16_t recved_len);
#endif /* LWIP_HTTPD_POST_MANUAL_WND */
#endif /* LWIP_HTTPD_SUPPORT_POST */
到此,通过这四篇文章,我实现了从零使用CUBEMX配置了一个带有FREERTOS以及LWIP和带有CGI功能的HTTPD服务器,以后如果想使用物联网设备开启局域网服务器,就可以使用这个框架。在这里我已经把框架搭好,可以根据需求进行下一步完善。
下一步我完善主要是需要与前端进行沟通,设计网页的API,如一个开门操作。通过这样的请求,利用CGI,完成比较复杂的操作。
本文提供的更多是一种思路,因为加入了操作系统,不只适用于单片机,只有是嵌入式设备都可以用,因为操作系统,lwip协议栈都是相同的。这个项目到此为止,希望有帮助的话多多点赞,收藏和评论。