LWIP官方httpd使用之GET
前言
httpd的移植可以参考上篇文章LWIP官方DEMO使用之httpd服务 - USTHzhanglu - 博客园 (cnblogs.com)
此博文为学习笔记,仅介绍如何使用官方demo,无更深入分析。
此博文介绍了如何通过GET返回各种数据。
关键词:LWIP, HTTP, HTTPD, GET
| LWIP版本 | lwip-STABLE-2_2_0_RC1 |
GET
GET
最常见的一种请求方式,当客户端要从服务器中读取文档时,当点击网页上的链接或者通过在浏览器的地址栏输入网址来浏览网页的,使用的都是GET方式。GET方法要求服务器将URL定位的资源放在响应报文的数据部分,回送给客户端。使用GET方法时,请求参数和对应的值附加在URL后面,利用一个问号(“?”)代表URL的结尾与请求参数的开始,传递参数长度受限制。例如,/index.jsp?id=100&op=bind,这样通过GET方式传递的数据直接表示在地址中。
访问网页本身就是一个GET请求(毕竟本质上是从server获取html网页数据后展示),在访问lwip.local
时,debug时信息如下:
ttp_accept 1ffead78 / 00000000
http_recv: pcb=1ffead78 pbuf=1ffedd84 err=Ok.
Received 487 bytes
First pbuf
CRLF received, parsing request
Received GET request
Received GET request for URI: /
Looking for /index.shtml...
Looking for /index.ssi...
Looking for /index.shtm...
Looking for /index.html...
Opened.
尝试用GET
获取图片数据:
可以看到,返回了一个图片数据。
通过makefsdata
,将需要的资源提前烧录到flash中,可以很方便的通过GET url
的方式获取。
问题来了,假如想动态读取MCU里的某种数据呢?
类似于访问https://dog.ceo/api/breeds/image/random
会得到一个json数据,其中包含了一个随机的dog图片url
{
message: https://images.dog.ceo/breeds/coonhound/n02089078_3191.jpg,
status: success
}
这种是没法通过提前烧录资源实现的。
一般情况下,我们会想到解析到对应url后,手动拼接字符串并返回来实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main() {
char* url = http://example.com;
char* method = GET;
char* headers[] = {
Content-Type: application/json,
Accept: application/json
};
int header_count = sizeof(headers) / sizeof(char*);
char* body = { \current_time\: \;
time_t t = time(NULL);
char* time_str = ctime(&t);
body += time_str;
body += }\;
int len = strlen(url) + strlen(method) + header_count * sizeof(char*) + strlen(body);
char* full_url = malloc(len + 1);
strcpy(full_url, url);
strcat(full_url, );
strcat(full_url, method);
for (int i = 0; i < header_count; i++) {
strcat(full_url, \r);
strcat(full_url, headers[i]);
}
strcat(full_url, \r\r);
strcat(full_url, body);
printf(Full URL: %s, full_url);
free(full_url);
return 0;
}
运行后输出的结果如下:
Full URL: http://example.com GET
Content-Type: application/json
Accept: application/json
{ current_time: Thu Jan 26 16:14:03 2023 }
以上方法太繁琐了,而且既然用LWIP了,在自己拼接字符串,实在是太过愚蠢了。
下面将介绍如何通过自带的API实现GET动态数据功能
genfiles_example
在官方示例中,有一个文件生成的示例genfiles_example
:
lwip-STABLE-2_2_0_RC1/contrib/examples/httpd
$ tree -L 1
.
|-- cgi_example
|-- examples_fs
|-- examples_fsdata.c
|-- fs_example
|-- genfiles_example
|-- https_example
|-- post_example
`-- ssi_example
查看介绍如下:
/** * @file * HTTPD custom file system example for runtime generated files * * This file demonstrates how to add support for generated files to httpd. */
可以看到,该示例展示了如何在运行时实时生成文件,也即是实时生成返回数据。
导入
要使用该示例,首先要开启如下几个选项:
#define LWIP_HTTPD_CUSTOM_FILES 1
#define LWIP_HTTPD_FILE_EXTENSION 1
#define LWIP_HTTPD_DYNAMIC_HEADERS 1
#define LWIP_HTTPD_EXAMPLE_GENERATEDFILES 1
然后在工程中加入genfiles_example.c
即可。
编译烧录,然后访问 <lwip.local/generated.html>
自定义
官方示例仅展示了如何生成一个html文件,如果需要生成其他文件,我们还需要修改一部分代码。
官方贴心的给了提示:
/* * Generating custom things instead of memcpy is left to your imagination :-) */ /* instead of doing memcpy, you would generate e.g. a JSON here */ memcpy(file->pextension, generated_html, sizeof(generated_html));
还是先用memcpy
,把html数据替换成json看看:
static const char JSON_STRING[] =
"{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n "
"\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
int
fs_open_custom(struct fs_file *file, const char *name)
{
/* this example only provides one file */
if (!strcmp(name, "/generated.html")) {
/* initialize fs_file correctly */
memset(file, 0, sizeof(struct fs_file));
file->pextension = mem_malloc(sizeof(JSON_STRING));
if (file->pextension != NULL) {
/* instead of doing memcpy, you would generate e.g. a JSON here */
memcpy(file->pextension, JSON_STRING, sizeof(JSON_STRING));
file->data = (const char *)file->pextension;
file->len = sizeof(JSON_STRING) - 1; /* don't send the trailing 0 */
file->index = file->len;
/* allow persisteng connections */
file->flags = FS_FILE_FLAGS_HEADER_PERSISTENT;
return 1;
}
}
return 0;
}
返回值
GET /generated.html HTTP/1.1
Host: lwip.local
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
HTTP/1.0 200 OK
Server: lwIP/2.2.0rc1 (http://savannah.nongnu.org/projects/lwip)
Content-Length: 98
Content-Type: text/html
{"user": "johndoe", "admin": false, "uid": 1000,
"groups": ["users", "wheel", "audio", "video"]}
可以看到返回结果符合我们的预期。不过Content-Type: text/html
和我们想返回的数据格式不符合。
在httpd_structs.h
中,存在以下定义:
#define HTTP_HDR_HTML HTTP_CONTENT_TYPE("text/html")
#define HTTP_HDR_SSI HTTP_CONTENT_TYPE("text/html\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache")
#define HTTP_HDR_GIF HTTP_CONTENT_TYPE("image/gif")
#define HTTP_HDR_PNG HTTP_CONTENT_TYPE("image/png")
#define HTTP_HDR_JPG HTTP_CONTENT_TYPE("image/jpeg")
#define HTTP_HDR_BMP HTTP_CONTENT_TYPE("image/bmp")
#define HTTP_HDR_ICO HTTP_CONTENT_TYPE("image/x-icon")
#define HTTP_HDR_APP HTTP_CONTENT_TYPE("application/octet-stream")
#define HTTP_HDR_JS HTTP_CONTENT_TYPE("application/javascript")
#define HTTP_HDR_RA HTTP_CONTENT_TYPE("application/javascript")
#define HTTP_HDR_CSS HTTP_CONTENT_TYPE("text/css")
#define HTTP_HDR_SWF HTTP_CONTENT_TYPE("application/x-shockwave-flash")
#define HTTP_HDR_XML HTTP_CONTENT_TYPE("text/xml")
#define HTTP_HDR_PDF HTTP_CONTENT_TYPE("application/pdf")
#define HTTP_HDR_JSON HTTP_CONTENT_TYPE("application/json")
#define HTTP_HDR_CSV HTTP_CONTENT_TYPE("text/csv")
#define HTTP_HDR_TSV HTTP_CONTENT_TYPE("text/tsv")
#define HTTP_HDR_SVG HTTP_CONTENT_TYPE("image/svg+xml")
#define HTTP_HDR_SVGZ HTTP_CONTENT_TYPE_ENCODING("image/svg+xml", "gzip")
#define HTTP_HDR_DEFAULT_TYPE HTTP_CONTENT_TYPE("text/plain")
要使用这些定义,需要开启
#define LWIP_HTTPD_DYNAMIC_HEADERS 1
以启动动态头功能。
启用该功能后,httpd
会在获取到返回数据后,根据访问的uri来动态变更头
#if LWIP_HTTPD_DYNAMIC_HEADERS
/* Determine the HTTP headers to send based on the file extension of
* the requested URI. */
if ((hs->handle == NULL) || ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0)) {
get_http_headers(hs, uri);
}
将访问uri变更为json
/* this example only provides one file */
if (!strcmp(name, "/generated.json")) {
可以看到返回数据
HTTP/1.0 200 OK
Server: lwIP/2.2.0rc1 (http://savannah.nongnu.org/projects/lwip)
Content-Length: 98
Content-Type: application/json
{"user": "johndoe", "admin": false, "uid": 1000,
"groups": ["users", "wheel", "audio", "video"]}
但是这种方式有个限制,即GET
时必须确定uri后缀,也算是LWIP的一种限制,在不改动lwip
的前提下,暂时无更好的方法。
如果不加任何后缀,将返回content-type: application/octet-stream
,这时候需要和前端约定好解数据的方式。