This is how I implemented REST API with uIP in NuttX some time ago.
http.c
int format_header(char* buff, int status, int body_len) { if
(body_len > 0) { return snprintf(buff, HTTPD_MAX_HEADERLEN,
"HTTP/1.0 %d %s\r\n" "Connection: %s\r\n" "Content-Length:
%d\r\n" "%s" "\r\n", status, status >= 400 ? "Error" :
"OK", "close", body_len, status < 400 ? "Content-type:
application/json\r\n" : "" ); } else { return snprintf(buff,
HTTPD_MAX_HEADERLEN, "HTTP/1.0 %d %s\r\n" "Connection:
%s\r\n" "%s" "\r\n", status, status >= 400 ? "Error" :
"OK", "close", status < 400 ? "Content-type:
application/json\r\n" : "" ); }}
void send_header(struct httpd_state* pstate, int status, int body_len)
{ char header[HTTPD_MAX_HEADERLEN]; int header_len;
header_len = format_header(header, status, body_len);
send(pstate->ht_sockfd, header, header_len, 0);}
void send_body(struct httpd_state* pstate, char* body, int body_len) {
send(pstate->ht_sockfd, body, body_len, 0);}
void send_reply(struct httpd_state* pstate, int status, char* body,
int body_len) { send_header(pstate, status, body_len);
if (body_len > 0) { send_body(pstate, body, body_len); }}
void send_code(struct httpd_state* pstate, int status) {
send_reply(pstate, status, nullptr, 0);}
static uint8_t already_initialized_http = false;
void http_serve(struct httpd_cgi_call* routes, int n) { if
(!already_initialized_http) { httpd_init();
for (int i = 0; i < n; i++) { httpd_cgi_register(&routes[i]); }
already_initialized_http = true; }
httpd_listen(settings_webserver_port.get());}
rest.c
static void sensors_route(struct httpd_state* pstate, char* ptr) {
static const int sensors_answer_max_size = 524;
char body[sensors_answer_max_size];
int body_len = snprintf(body, sensors_answer_max_size, ""\"{"\
"\"battery\": \"%s\","\ "\"inputs_count\": %d,"\ "\"inputs\": ["\
"{\"name\": \"%s\", \"state\": \"%s\", \"reading\": \"%s\", \"text\":
\"%s\"},"\ "{\"name\": \"%s\", \"state\": \"%s\", \"reading\":
\"%s\", \"text\": \"%s\"},"\ "{\"name\": \"%s\", \"state\": \"%s\",
\"reading\": \"%s\", \"text\": \"%s\"},"\ "{\"name\": \"%s\",
\"state\": \"%s\", \"reading\": \"%s\", \"text\": \"%s\"},"\
"{\"name\": \"%s\", \"state\": \"%s\", \"reading\": \"%s\", \"text\":
\"%s\"},"\ "{\"name\": \"%s\", \"state\": \"%s\", \"reading\":
\"%s\", \"text\": \"%s\"}"\ "]"\"}\n",
power_state_stringify((power_state_t)global_battery_level.get()),
(unsigned char)settings_inputs_count.get(),
sensors_stringify_gas_type((settings_gas_type_t)settings_inputs_gas[0].get()),
sensors_stringify_state((global_sensors_state_t)global_sensors_state[0].get()),
formatted_readings[0], global_inputs_label[0],
... );
send_reply(pstate, 200, body, body_len);}
static struct httpd_cgi_call routes[] = { { nullptr, "/api/info",
info_route }, { nullptr, "/api/channel", set_channel_label }, {
nullptr, "/api/sensors", sensors_route }, { nullptr,
"/api/history_list", history_list_route }, { nullptr, "/api/auth",
auth_route }, { nullptr, "/api/change-password",
change_password_route }
...};
static void backend_worker(void *arg) {
pthread_setname_np(pthread_self(), "backend");
while (true) { http_serve(routes, (sizeof routes / sizeof *routes));
sleep(1);
printf("backend.cxx : restarting backend\n"); }}
void backend_start(void) { pthread_attr_t attr;
pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, 5 * 512);
int ret = pthread_create(&backend_thread, &attr,
(pthread_startroutine_t)backend_worker, nullptr);
ASSERT(ret >= 0);}
And then you call backend_start, for example in your application entrypoint.
I am not sure if it is how it should be done, but it was working for me.
Am Di., 3. Juni 2025 um 22:57 Uhr schrieb Tim Hardisty <
[email protected]>:
> To try and answer my own question...
>
> It seems (and probably obvious, doh, to all but me) the uIP server only
> serves pre-formatted html. No matter what "send" option you choose
> (pre-formatted/sendfile/mmap) or the URL/CGI mapping option) the html is
> pre-rendered. So NO chance of a form action that calls a CGI function.
> CGI functions are not called on-the-fly. It just does not work.
>
> I have now moved to THTTP...and although it serves my pages nicely, I
> can't get ssi or other cgi functions to work: so yet more day after day
> after day of trying to reverse engineer stuff to find that magic
> incantation. Grrr.
>
> FYI thttp example doesn't work either (using BINFS not NXFLAT)
>
> On 31/05/2025 11:16, Tim Hardisty wrote:
> >
> > I'm using the netutils uIP webserver to provide a simple interface,
> > served by my board, for configuration, log downloads, firmware
> > updates, etc.
> >
> > Forgive me if the terminology is wrong here, but I am trying to find
> > documentation - NuttX or elsewhere - about the %! "tag" that denotes a
> > call to a CGI function. Specifically, I am trying to add a form that
> > calls a script from a button, where do-firmware-update is my CGI
> function:
> >
> > <form action="%! do-firmware-update" method="post"
> > enctype="multipart/form-data" accept-charset="UTF-8">
> >
> > This gets served with nothing after the first opening quote character
> > on this line so i am assuming I am "calling" the script incorrectly
> > but can't find anything anywhere to tell me how to do this.
> >
> > Can anyone point me in the right direction?
> >
> > Thanks!
> >
> > TimH
> >