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
> >

Reply via email to