Simple httpd solution for embedded systems based on lwip(Raw/TCP) and mbedtls (本项目是基于 lwip 和 mbedtls 开发的 http server 原型).
The goal of this project is to build a prototype system to simulate web services on an embedded system. The simulator has the following benefits (本项目的目标是搭建一个原型系统来模拟嵌入式系统上的 web 服务,利用该模拟器有如下好处):
- The Windows platform is far more powerful than any MCU, and
Microsoft Visual Studio
has powerful debugging tools, so debugging will be much more efficient (Windows平台远比任何MCU强大,加上 Visual Studio 丰富的调试手段,让调试效率大大提高). - When working with a team, if only limited hardware or
J-TAG emulators
are available, some features that do not depend on the hardware can be developed simultaneously using this prototype (团队开发时,如果硬件环境不足、或J-TAG仿真器数量有限时,不依赖硬件环境的某些应用可以进行同步开发). lwip
andmbedtls
have a lot of optional parameters, which need to be optimized for different embedded systems. To compare parameters, extensive experimentation is required, and this prototype can help speed up this process (lwip
和mbedtls
有非常多的可选参数,如何优化参数以适应不同的嵌入式系统,需要大量的比较实验,该模拟环境可以大大缩减实验周期).
In a Windows environment, create a Virtual Device (VD)
using pcap
, lwip
and mbedtls
, and implement a simple but typical web server.
This project creates a Virtual Device
on Computer B
. Since the browser on Computer B
cannot access this local Virtual Device
, another Computer A
should be used to access this device remotely.
-
Computer B
MUST be a Windows device withVS2010
or later installed. Use VS2010 to open the projectstraight-httpd/straight-httpd.sln
and run it in debug. -
Computer A
could be aWindows
,Linux
, or amobile
device. For Windows, openFile Explorer
and search fornetwork neighbors
. There should be a device namedVirtual Device
. Right click on it and selectProperties
, and you will see the device information, including its IP address and URL. Use default useradmin
and passwordpassword
to sign into the device home page.
- Support
SSDP
protocol: neighbours can detect this virtual device in the network. - Support
HTTPS
: all HTTP requests will be redirected to HTTPS (except SSDP XML). BothGET
andPOST
are supported. - Support
HTTP/1.1 pipelining
andkeep-alive
. It is better for HTTPS to reuse connections because creating new connections is time consuming. - Support
chunked
response: Because the device memory is limited, generally it is not possible to wait for all the data to be ready before replying. When the device begins to return data, it does not know the total length.Transfer-Encoding: chunked
allows you to split the data into chunks of various lengths, so that every chunk is within the device’s memory limit. - Support
range
request:Range: bytes=0-
allows you to specify a single range. This feature is suitable for paged data, multi-threaded downloading or video fast forwarding. - Support request header
If-Modified-Since
for browsers to cache static files. - Support simple SSI (“Server Side Includes”) with simple syntax:
<!--#TAG_NAME-->
. Note that the syntax does not allow spaces between the "<" and ">". Every tag is a placeholder, which corresponds to a variable. The variable can be HTML content or the initial value of a javascript variable. When the device replies to a request, it will first convert the variable into a string. This string then replaces the corresponding tag. Based on the tag content, the js code can then complete HTML/DOM operations, such as select, checkbox, and radio control etc. - Demo pages include:
- Sign in/out: verify user's username/password, if successful, redirect to "Home", otherwise stay in the login page, the session has 3 minutes timeout after login.
- Home page: show some device information and the current status of the device, and scan the QR code to access the device web.
- Upload page: multiple files can be selected and queued to be uploaded to a specific device directory. The concurrency is 1, and every upload process can be terminated separately. Support large files.
- Files page: you can browse all the files in the specified device directory, enter subdirectories or return to the parent directory. In addition, all files could be downloaded. Support large files.
- Form page: demonstrates parameter modification and saving.
- Spport MQTT protocol.
- Port httpd to a real embedded system, such as Cortex-M4 LPC40xx (Please refer to another project LPC407x-NoOS-LWIP-MBEDTLS-HTTPD-KEIL).
Computer A
: only need a browser installed. If using the latest npcap driver (v0.9995), the browser onComputer B
can access theVirtual Device
right now, soComputer A
may not be needed.Computer B
: need to installwpcap driver
, or simply installWireshark
that includes wpcap driver.
- folder
pcap
:wpcap
C library copied from the original location: pcap - folder
lwip
: copied from the original location: git://git.savannah.nongnu.org/lwip.git - folder
mbedtls
: copied from the original location: mbedtls. The file config.h has been modified. QR-Code-generator
by nayuki, copied from the original location: source
-
folder
lwip-port
: lwip porting files on Windows platform- Receive ethernet frame from
wpcap
and send it tolwip stack
. - Send ethernet frame from
lwip
stack viawpcap
. - Also provide features:
Mutex
,Semaphore
,Mailbox
,System Tick
, andFile API
.
- Receive ethernet frame from
-
folder
straight-httpd
: Microsoft Visual Studio 2010 project:straight-httpd.sln
, and the projectstraight-buildfs
can package all web pages and convert them into source code.
- The simplest way is to use the tool EmbedTools, which can generate the pem file named
lwip_cert.c
. - If you want to create your customized certificate, run
makecert.bat
. Both the certificate and the private key will be saved in the fileserver.pfx
. - Use OpenSSL or other tools to convert PFX file to PEM format.
openssl pkcs12 -in server.pfx -out server.pem
- Copy the following content in the pem file to
http_api.c
const char *privkey_pass = "straight";
const char *privkey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n"\
"MIIJjjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIh7VHf699+v0CAggA\n"\
。。。。
"-----END ENCRYPTED PRIVATE KEY-----\n";
const char *cert = "-----BEGIN CERTIFICATE-----\n"\
"MIIFEDCCAvigAwIBAgIQ+UjKCIcDNLRKcHf+9tlCFjANBgkqhkiG9w0BAQ0FADAR\n"\
。。。。
"-----END CERTIFICATE-----\n";
-
LWIP task can be thought as a black-box that is asynchronously driven by the network events.
-
Immediate response is very important for high throughput.
-
This task need access various data in local database or variables which should be protected for concurrency.
/src/include/lwip/priv/tcpip_priv.h
ETHNET_INPUT //2020-03-29 added by straight coder
/src/include/lwip/arch.h
define LWIP_NO_STDINT_H 1 //2020-03-29 set to 1 by straight coder
/src/core/tcp.c
void tcp_kill_all(void) //2020-03-29 new function by straight coder
/src/core/ipv4/dhcp.c
OnDhcpFinished(); //2020-03-29 notify when IP is ready by straight coder
/src/api/tcpip.c
sys_mbox_t tcpip_mbox; //2020-03-29 removed static by straight coder
extern struct netif main_netif; //2020-03-29 added by straight coder
extern err_t ethernetif_input(struct netif *netif); //2020-03-29 added by straight coder
void tcpip_thread_handle_msg(struct tcpip_msg *msg); //2020-03-29 removed static by straight coder
case ETHNET_INPUT: //2020-03-29 added by straight coder
ethernetif_input(&main_netif);
break;
- for pcap
#define ETH_PAD_SIZE 0
struct member alignment 1 byte(/Zp1)
struct CGI_Mapping
{
char path[MAX_CGI_PATH]; //string, request full path
unsigned long optionsAllowed; //defined by CGI_OPT_xxxxxx
void (*OnCancel)(REQUEST_CONTEXT* context);
//request callbacks
int (*OnHeaderReceived)(REQUEST_CONTEXT* context, char* header_line); //return 1 if eaten
void (*OnHeadersReceived)(REQUEST_CONTEXT* context);
int (*OnContentReceived)(REQUEST_CONTEXT* context, char* buffer, int size);
void (*OnRequestReceived)(REQUEST_CONTEXT* context);
//response callbacks
void (*SetResponseHeaders)(REQUEST_CONTEXT* context, char* HttpCode);
int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize);
void (*OnAllSent)(REQUEST_CONTEXT* context);
void (*OnFinished)(REQUEST_CONTEXT* context);
struct CGI_Mapping* next;
};
//其中的回调函数,如果不需要可以设置为 NULL,如:静态网页、SSI等已被http_core.c处理,就不需要额外处理
//像 POST 等需要特殊处理的,就需要增加相应的回调函数
- Functions for CGI processing
//setup mapping when initializing httpd context
//建立所有的路径、事件和函数的绑定关系
void CGI_SetupMapping(void);
//cancel notification to app layer because of any HTTP fatal errors
// including timeout, format errors, sending failures, and stack keneral errors
//如果错误源自系统内部,如处理http时遇到内存不足、或连接错误等无法恢复的错误时,通过回调通知上层做相应处理(可选)
void CGI_Cancel(REQUEST_CONTEXT* context);
//called by FreeHttpContext()
//单次请求结束之前,回调通知上层做相应处理(可选)
void CGI_Finish(REQUEST_CONTEXT* context);
//called when the first line of the HTTP request is completely received
//收到http请求路径时,立即查找相关的绑定设置,只有CGI_SetupMapping定义过的绑定请求才会被处理
void CGI_SetCgiHandler(REQUEST_CONTEXT* context);
//if any HTTP request header/line is skipped by http-core.c, the following function will be called, return 1 if accepted
//如果请求头没有被http-core.c吃掉,就要看上层有没有定义回调函数以进行特殊处理
int CGI_HeaderReceived(REQUEST_CONTEXT* context, char* header_line);
//called when all HTTP request headers/lines are received
//所有请求头接收完毕,在此回调上层函数可以进行一些额外准备,以便消化紧接着的请求内容(如POST,或上载文件)
void CGI_HeadersReceived(REQUEST_CONTEXT* context);
//called when any bytes of the HTTP request body are received (may be partial), and return the accepted count, 0 for blocking, or negative for error.
//上传内容收到后,回调上层函数,告知数据块位置和大小,经上层处理后,返回实际消化掉的字节数,0表示阻塞,负数表示接收遇错会导致连接关闭。
//如果回调只是消化了部分数据,那残余数据会被后续的数据继续触发、或被定时触发
int CGI_ContentReceived(REQUEST_CONTEXT* context, char* buffer, int size);
//called when the HTTP request body is completely received
//所有请求内容完全收到,且缓存内容被完全处理,下一步应该进入应答阶段
void CGI_RequestReceived(REQUEST_CONTEXT* context);
//set response headers: content-type, content-length, connection, and http code and status info
//响应请求的第一步是准备响应头,如填写响应数据类型、长度、错误码、或连接保持参数
void CGI_SetResponseHeaders(REQUEST_CONTEXT* context, char* HttpCodeInfo);
//load response body chunk by chunk
// there are two-level progresses initialized as 0:
// context->ctxResponse._dwOperStage = 0; //major progress, set _dwOperStage=STAGE_END if it is the last data chunk
// context->ctxResponse._dwOperIndex = 0; //sub-progress for each stage, for app layer internal use
// caller: called from HTTP_PROC_CALLER_RECV (event) / HTTP_PROC_CALLER_POLL (timer) / HTTP_PROC_CALLER_SENT (event)
// int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize); //need to be implemented by upper level
// return 1 if data is ready to send
// data MUST be put in context->ctxResponse._sendBuffer,
// and the buffer size set to context->ctxResponse._bytesLeft before return
//来自lwip的OnReceive和OnPoll会触发该接口的调用,上层需要提供绑定函数ReadContent(REQUEST_CONTEXT* context, char* buffer, int maxSize)
//该函数从上层获取最多maxSize字节数据用于发送(返回值为实际可发送数量),具体发送进度控制需要在回调ReadContent中实现:
//请求上下文中有两个变量可用于进度控制:
// 主进度变量_dwOperStage:如果是最后一块数据,调用结束时置为STAGE_END,其他值由用户控制
// 辅进度变量_dwOperIndex:如果需要用到两级进度、或仅仅是做个判断标记,该变量也用得上
int CGI_LoadContentToSend(REQUEST_CONTEXT* context, int caller);
Device HTTP Events | CGI Adapter - Actions | Description |
---|---|---|
GET /app/index.shtml HTTP/1.1 | CGI_SetCgiHandler(context) 查找绑定设置,找到处理函数 |
First request line |
Connection: keep-alive Cookie: SID=0123ABCD X-Auth-Token: SID=0123ABCD Range: bytes=0- If-Modified-Since: |
processed in http_core.c 默认处理一些解析需要的请求头 |
Request headers |
other headers | CGI_HeaderReceived(context,line) 不关心的请求头留给回调处理 |
Request headers |
all headers received | Check session: GetSession(char* token) 找到已登录的会话 CGI_HeadersReceived(context) |
End of the request headers |
request completely received | CGI_RequestReceived(context) | Ready to response |
send response headers | CGI_SetResponseHeaders(context,codeInfo) | Response headers |
response with content | CGI_LoadContentToSend(context, caller) | Response body |
Response completely sent out | OnAllSent(context) | Response finished |
When connection disconnected or -500 | CGI_Finish(context) | For additional cleanup |
lwip error, request error, timeout, <=-400 | CGI_Cancel(context) | For additional cleanup |
Device HTTP Events | CGI Adapter - Actions | Description |
---|---|---|
POST /auth/login.html HTTP/1.1 | CGI_SetCgiHandler(context) | First request line |
Content-Type: application/x-form-urlencoded Content-Length: xxx Cookie: SID=0123ABCD X-Auth-Token: SID=0123ABCD Connection: keep-alive |
processed in http_core.c | Request headers |
other headers | CGI_HeaderReceived(context,line) | Request headers |
all headers received | Check session: GetSession(char* token) CGI_HeadersReceived(context) |
End of the request headers |
request body | CGI_ContentReceived(context, buffer, size) | Request body |
post body received | CGI_RequestReceived(context) | Ready to response |
send response headers | CGI_SetResponseHeaders(context,codeInfo) | Response headers |
response with content | CGI_LoadContentToSend(context, caller) | Response body |
Response completely sent out | OnAllSent(context) | Response finished |
When connection disconnected or -500 | CGI_Finish(context) | For additional cleanup |
lwip error, request error, timeout, <=-400 | CGI_Cancel(context) | For additional cleanup |
MEM_SIZE
in filelwipopts.h
, large memory for more connections.
#if (ENABLE_HTTPS > 0)
#define MEM_SIZE (130*1024) //for Chrome, at least 130kB
#else
#define MEM_SIZE (24*1024) //for HTTP only
#endif
TCP_MSS
in filelwipopts.h
, the bigger the TCP_MSS, the higher the throughput, but the more the memory.
#define TCP_MSS 800 //for HTTPS, at least 800; for HTTP can be smaller
TCP_WND
in filelwipopts.h
#if (ENABLE_HTTPS > 0)
#define TCP_WND (22 * TCP_MSS) //for HTTPS, should be greater than MBEDTLS_SSL_MAX_CONTENT_LEN
#else
#define TCP_WND (4 * TCP_MSS) //for HTTP, default value is OK
#endif
MAX_CONNECTIONS
inhttp_core.h
#define MAX_CONNECTIONS 4 //max concurrent tcp connections, some browsers may use up to 4 connections for one session.
MAX_REQ_BUF_SIZE
inhttp_core.h
, the larger the receiving buffer, the faster the uploading speed.
#define MAX_REQ_BUF_SIZE TCP_MSS //max. length of the header that can be recognized, and it must be bigger than the max.length of path.
MAX_SEND_BUF_SIZE
inhttp_core.h
, the larger sending buffer, the faster the downloading speed.
#define MAX_SEND_BUF_SIZE 2*TCP_MSS
- cgi_ssdp.c processes SSDP requests.
- All information tags can be modified using API functions. All tags are defined and parsed by
cgi_ssi.c
.
e.g. GetDeviceName(), GetVendor(), GetModel(), GetDeviceUUID().
- This example applies to devices that require login or exclusive use.
/auth/login.html
is the default page before authentication. The user must provide the username and password. All web pages are physically located in the folder defined byLOCAL_WEBROOT
.
#define LOCAL_WEBROOT "D:/straight/straight-httpd/straight-httpd/straight-httpd/httpd/cncweb/"
cgi_auth.c
processes the user’s sign-in/sign-out. This module responds to all requests with the prefix/auth/*
./auth/session_check.cgi
checks if the current session has timed out./auth/logout.cgi
signs out the user and frees the session context.- Default user:
admin
, password:password
- CGI mapping:
struct CGI_Mapping g_cgiAuth = {
"/auth/*", //char* path;
CGI_OPT_GET_ENABLED | CGI_OPT_POST_ENABLED,//Options ===> GET login page, then POST username/password;
NULL, //void (*OnCancel)(REQUEST_CONTEXT* context);
NULL, //int (*OnHeaderReceived)(REQUEST_CONTEXT* context, char* header_line);
Auth_OnHeaders, //void (*OnHeadersReceived)(REQUEST_CONTEXT* context); ===> prepare buffer to receive username/password
Auth_OnPostData, //int (*OnContentReceived)(REQUEST_CONTEXT* context, char* buffer, int size); ===> receive posted data (username/password)
Auth_OnRequestReceived, //void (*OnRequestReceived)(REQUEST_CONTEXT* context); ===> POST: check username and password, create session and cookie;
===> GET: check session timeout;
===> GET: logout and kill session;
Auth_SetResponseHeaders, //void (*SetResponseHeader)(REQUEST_CONTEXT* context, char* HttpCode); ===> response with session cookie
WEB_ReadContent, //int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize); ===> default processing
WEB_AllSent, //int (*OnAllSent)(REQUEST_CONTEXT* context); ===> default processing
WEB_Finished, //void (*OnFinished)(REQUEST_CONTEXT* context); ===> default processing
NULL //struct CGI_Mapping* next;
};
int CheckUser(char* u, char* p);
- This example is applicable to all static or
SSI
(with extensions:.shtml;.shtm;.ssi
) web pages. /app/index.shtml
is the home page after authentication.cgi_web.c
responds to all requests with prefix/app/*
after authentication.- CGI mapping:
struct CGI_Mapping g_cgiWebApp = {
"/app/*", //char* path;
CGI_OPT_AUTH_REQUIRED | CGI_OPT_GET_ENABLED,// unsigned long options; ===> Login to access; GET only
NULL, //void (*OnCancel)(REQUEST_CONTEXT* context);
NULL, //int (*OnHeaderReceived)(REQUEST_CONTEXT* context, char* header_line);
NULL, //void (*OnHeadersReceived)(REQUEST_CONTEXT* context);
NULL, //int (*OnContentReceived)(REQUEST_CONTEXT* context, char* buffer, int size);
WEB_RequestReceived, //void (*OnRequestReceived)(REQUEST_CONTEXT* context); ===> check request (SSI? gzip? chunked? modified?) and prepare to response
WEB_AppendHeaders, //void (*SetResponseHeader)(REQUEST_CONTEXT* context, char* HttpCode); ===> set response headers (gzip? chunked? Last-Modified?)
WEB_ReadContent, //int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize); ===> replace SSI and send response block by block or chunk by chunk
WEB_AllSent, //int (*OnAllSent)(REQUEST_CONTEXT* context); ===> close file
WEB_Finished, //void (*OnFinished)(REQUEST_CONTEXT* context); ===> close file
NULL //struct CGI_Mapping* next;
};
cgi_ssi.c
includes all infomation getters.
char* GetVendor(void);
char* GetVendorURL(void);
char* GetModel(void);
char* GetModelURL(void);
char* GetDeviceName(void);
char* GetDeviceSN(void);
char* GetDeviceUUID(void);
char* GetDeviceVersion(void);
int FillMAC(void* context, char* buffer, int maxSize);
static SSI_Tag g_Getters[] = {
{ "DEV_VENDOR", TAG_GETTER, GetVendor },
{ "DEV_VENDOR_URL", TAG_GETTER, GetVendorURL },
{ "DEV_MODEL", TAG_GETTER, GetModel },
{ "DEV_MODEL_URL", TAG_GETTER, GetModelURL },
{ "DEV_MAC", TAG_PROVIDER, FillMAC },
{ "DEV_NAME", TAG_GETTER, GetDeviceName },
{ "DEV_SN", TAG_GETTER, GetDeviceSN },
{ "DEV_UUID", TAG_GETTER, GetDeviceUUID },
{ "DEV_VERSION", TAG_GETTER, GetDeviceVersion },
{ NULL, NULL, NULL }
};
- This example is applicable to device firmware upgrade.
/app/upload.shtml
and/app/plugin/fileTransfer/fileTransfer.js
provide a demo for uploading files. The destination folder is defined byUPLOAD_TO_FOLDER
.
#define UPLOAD_TO_FOLDER "D:/straight/straight-httpd/straight-httpd/straight-httpd/httpd/cncweb/app/cache/"
cgi_upload.c
responds to the request URL/api/upload.cgi
.- CGI mapping
struct CGI_Mapping g_cgiUpload = {
"/api/upload.cgi",
CGI_OPT_AUTH_REQUIRED | CGI_OPT_POST_ENABLED,// unsigned long options; ===> Login to access; POST only
Upload_OnCancel, //void (*OnCancel)(REQUEST_CONTEXT* context); ===> callback when connection is down, clean the uploaded data or file
Upload_OnHeaderReceived, //int (*OnHeaderReceived)(REQUEST_CONTEXT* context, char* header_line); ===> special header processing
Upload_OnHeadersReceived, //void (*OnHeadersReceived)(REQUEST_CONTEXT* context); ===> request received, and check condition: busy? oversized?
Upload_OnContentReceived, //int (*OnContentReceived)(REQUEST_CONTEXT* context, char* buffer, int size); ===> receive and save data block by block with flow control
Upload_AllReceived, //void (*OnRequestReceived)(REQUEST_CONTEXT* context); ===> all post data received, close file
NULL, //void (*SetResponseHeader)(REQUEST_CONTEXT* context, char* HttpCode);
NULL, //int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize);
NULL, //int (*OnAllSent)(REQUEST_CONTEXT* context);
NULL, //void (*OnFinished)(REQUEST_CONTEXT* context);
NULL //struct CGI_Mapping* next;
};
long Upload_Start(void* context, char* szFileName, long nFileSize);
long Upload_GetFreeSize(unsigned long askForSize);
long Upload_Received(REQUEST_CONTEXT* context, unsigned char* pData, unsigned long dwLen);
void Upload_AllReceived(REQUEST_CONTEXT* context);
- This is a good example for
RESTful API
usingchunked
header. /app/files.shtml
and/app/plugin/fileList/fileList.js
provide a demo for file browsing. The directory is defined byFOLDER_TO_LIST
.
#define FOLDER_TO_LIST "D:/straight/straight-httpd/straight-httpd/straight-httpd/httpd/cncweb/app/cache/"
cgi_files.c
responds to the request with URL/api/files.cgi
.- CGI mapping
struct CGI_Mapping g_cgiFiles = {
"/api/files.cgi",
CGI_OPT_AUTH_REQUIRED | CGI_OPT_GET_ENABLED,// unsigned long options; ===> Login to access; GET only
NULL, //void (*OnCancel)(REQUEST_CONTEXT* context);
NULL, //int (*OnHeaderReceived)(REQUEST_CONTEXT* context, char* header_line);
NULL, //void (*OnHeadersReceived)(REQUEST_CONTEXT* context);
NULL, //int (*OnContentReceived)(REQUEST_CONTEXT* context, char* buffer, int size);
Files_OnRequestReceived, //void (*OnRequestReceived)(REQUEST_CONTEXT* context);
Files_SetResponseHeader, //void (*SetResponseHeader)(REQUEST_CONTEXT* context, char* HttpCode);
Files_ReadOneFileInfo, //int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize);
NULL, //int (*OnAllSent)(REQUEST_CONTEXT* context);
NULL, //void (*OnFinished)(REQUEST_CONTEXT* context);
NULL //struct CGI_Mapping* next;
};
- Make preparations before response.
static void Files_OnRequestReceived(REQUEST_CONTEXT* context);
- Generate response header with
chunked
header.
static void Files_SetResponseHeader(REQUEST_CONTEXT* context, char* HttpCodeInfo);
- Generate one small
chunk
on each call.static int Files_ReadOneFileInfo(REQUEST_CONTEXT* context, char* buffer, int maxSize);
- This function is a callback from lwip stack when it is ready to send the next
chunk
. - There are two variables in the context that could be used for the progress control.
context->ctxResponse._dwOperStage
: It is the first level progress (0-based), andSTAGE_END
stands for the lastchunk
.context->ctxResponse._dwOperIndex
: Optional, it is the second level progress (0-based), and the max value iscontext->ctxResponse._dwTotal
.
request:
http://192.168.5.58/api/files.cgi?path=/
response with JSON including 9 chunks:
{
"result":1,
"data":[
{"folder":1,"name":"..","size":0,"date":1593903275},
{"folder":0,"name":"00108.MTS","size":147062784,"date":1596318548},
{"folder":0,"name":"Building a 3d Printer From Cd Drives -- Only $45.mp4","size":90868943,"date":1595116269},
{"folder":1,"name":"cfg","size":0,"date":1593903275},
{"folder":1,"name":"include","size":0,"date":1593903276},
{"folder":1,"name":"lib","size":0,"date":1593903276},
{"folder":0,"name":"videoplayback.mp4","size":25542302,"date":1593903614}
]
}
- This is a typical form example: GET the existing settings then POST the modifications to device.
/app/form.shtml
is a form page for modifying parameters. All parameters are processed bycgi_ssi.c
.cgi_form.c
provides general processing for all forms. All parameters and types are defined incgi_ssi.c
.- CGI mapping
struct CGI_Mapping g_cgiForm = {
"/app/form.shtml", //char* path;
CGI_OPT_AUTH_REQUIRED | CGI_OPT_GET_ENABLED | CGI_OPT_POST_ENABLED,// unsigned long options; ===> enable GET and POST, login required
NULL, //void (*OnCancel)(REQUEST_CONTEXT* context);
NULL, //int (*OnHeaderReceived)(REQUEST_CONTEXT* context, char* header_line);
Form_OnHeadersReceived, //void (*OnHeadersReceived)(REQUEST_CONTEXT* context); ===> prepare for the following getting or setting
WEB_OnFormReceived, //int (*OnContentReceived)(REQUEST_CONTEXT* context, char* buffer, int size); ===> default processing for POSTed data
Form_OnRequestReceived, //void (*OnRequestReceived)(REQUEST_CONTEXT* context); ===> apply the POSTed modification
Form_SetResponseHeaders, //void (*SetResponseHeader)(REQUEST_CONTEXT* context, char* HttpCode); ===> just call default processing if no further processing
WEB_ReadContent, //int (*ReadContent)(REQUEST_CONTEXT* context, char* buffer, int maxSize); ===> default processing
Form_AllSent, //int (*OnAllSent)(REQUEST_CONTEXT* context); ===> just call default processing WEB_AllSent() if no further processing
NULL, //void (*OnFinished)(REQUEST_CONTEXT* context);
NULL //struct CGI_Mapping* next;
};
cgi_ssi.c
includes all infomation getters and setters.
static SSI_Tag g_Getters[] = {
{ "DEV_DHCP", TAG_PROVIDER, FillDhcp },
{ "DEV_IP", TAG_PROVIDER, FillIP },
{ "DEV_GATEWAY", TAG_PROVIDER, FillGateway },
{ "DEV_SUBNET", TAG_PROVIDER, FillSubnet },
{ "VAR_SESSION_TIMEOUT",TAG_PROVIDER, FillSessionTimeout },
{ "VAR_LOCATION", TAG_GETTER, GetLocation },
{ "VAR_COLOR", TAG_GETTER, GetColor },
{ "VAR_DATE", TAG_GETTER, GetDate },
{ "VAR_FONT", TAG_GETTER, GetFont },
{ "VAR_LOG", TAG_PROVIDER, FillLog },
{ NULL, NULL, NULL }
};
static SSI_Tag g_Setters[] = {
{ "DEV_DHCP", TAG_SETTER, SetDhcpEnabled },
{ "DEV_IP", TAG_SETTER, SetMyIP },
{ "DEV_GATEWAY", TAG_SETTER, SetGateway },
{ "DEV_SUBNET", TAG_SETTER, SetSubnet },
{ "VAR_SESSION_TIMEOUT",TAG_SETTER, SetSessionTimeout },
{ "VAR_LOCATION", TAG_SETTER, SetLocation },
{ "VAR_COLOR", TAG_SETTER, SetColor },
{ "VAR_DATE", TAG_SETTER, SetDate },
{ "VAR_FONT", TAG_SETTER, SetFont },
{ "VAR_LOG", TAG_SETTER, SetLog },
{ NULL, NULL, NULL }
};
void LoadConfig4Edit();
void ApplyConfig();