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 < timhardist...@gmail.com>: > 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 > >