Amazingly, nobody came up with anything here, on the ST forum (which is mostly useless anyway due to vast traffic and zero mfg participation, but has great SEO which reliably yields hundreds of hits from desperate people getting no help), on the LWIP mailing list (from which I was privately told to not bother), and on freelancer.com where a few claimed to know LWIP and netconn and even Cube but (as is common there) no meaningful comms could be established.
In the end, a friend with a lot of unix expertise, and a good analytical brain, worked it out, and it works. I will post the details here in case somebody is googling and looking for the solution.
You start with something like
void initialise(void)
{
struct netconn *conn, *newconn;
err_t err;
debug_thread_printf("Starting HTTP server");
/* Create a new TCP connection handle */
conn = netconn_new(NETCONN_TCP);
if (conn!= NULL)
{
/* Bind to port 80 with default IP address */
err = netconn_bind(conn, NULL, 80);
if (err == ERR_OK)
{
// Put the connection into LISTEN state
netconn_listen(conn);
while(1)
{
// Accept the connection after netconn_listen
// This function reports if there is some rx data (non blocking)
err_t res = netconn_accept(conn, &newconn);
if (res == ERR_OK)
{
// Something arrived from the client
debug_thread("http incoming connection");
// Respond to the client request
http_server_serve(newconn);
// Delete the connection
netconn_delete(newconn);
}
}
}
else
{
debug_thread_printf("cannot bind netconn");
}
}
else
{
debug_thread_printf("cannot create netconn");
}
then you have
// Some data has arrived.
void http_server_serve(struct netconn *conn)
{
struct netbuf *inbuf = NULL;
char* buf;
u16_t buflen;
// Read the data from the port. BLOCKING if nothing there (should never happen
// because we get here only if netconn_accept() reported there is something).
// We assume the request (the part we care about) is in one netbuf.
err_t res = netconn_recv(conn, &inbuf);
if ((res == ERR_OK) && (inbuf != NULL))
{
if (netconn_err(conn) == ERR_OK)
{
// this function returns one buffer, out of a possible number of them, according to lwipopts.h settings
// #define PBUF_POOL_SIZE 4
// #define PBUF_POOL_BUFSIZE 1500 + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN
netbuf_data(inbuf, (void**)&buf, &buflen);
/* Is this an HTTP GET command? (only check the first 5 chars, since
there are other formats for GET, and we're keeping it very simple )*/
if ((buflen >=5) && (strncmp(buf, "GET /", 5) == 0))
{
// Check if this is a return string from the login page
// in the form of GET /xxxxxx.html?un=username&pw=password some-junk.....
if( (strncmp(buf, "GET /xxxxxx.html?un=", 20) == 0) && (!logged_in) )
{
// this function does more digging around buf, and in this case picks up the client IP too for checking
LoginCheck(conn, buf, conn->pcb.ip->remote_ip.addr);
}
// and so on for other client requests
}
}
}
/* Close the connection (server closes in HTTP) */
netconn_close(conn);
/* Delete the buffer (netconn_recv gives us ownership,
so we have to make sure to deallocate the buffer) */
netbuf_delete(inbuf);
}
The above has picked up ONE buffer only and we have been parsing it for some client requests. This is ok for one-packet usage, and most examples online cover only that, but is no good if you want to receive loads of data. For that, you do the following, which shows reception of data and writing it to a file. This function contains code irrelevant to netconn; it deals with a PUT... request from the client (which has to be generated by a JS script) and with the fact that the first packet we got contains not only the PUT... header (with filename and filesize) which is CRLFCRLF terminated as standard, but also with some file data.
/*
*
* This function receives the PUT data from a JS-submitted textarea box.
* The PUT etc is generated by a JS script EDIT_HEADER_JS.
* We get here only if data has arrived from client browser i.e. from netconn_recv
* Buffer buf already contains first packet and starts with:
*
* PUT /ufile=BOOT.TXT HTTP/1.1..Host: 192.168.3.73:81..User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64;
* x64; rv:103.0) Gecko/20100101 Firefox/103.0..Accept: .....Accept-Language: en-US,en;q=0.5..Accept-En
* coding: gzip, deflate..Content-Type: text/plain;charset=UTF-8..Content-Length: 198..Origin: http * p
* ://192.168.3.73:81..Connection: keep-alive..Referer: [url]http://192.168.3.73:81/efile?BOOT.TXT....boot[/url]
* time: 2022-08-02 15:39:28.app name: appname_1.1
*
* The likelihood of the 1st packet containing the entire header including the CRLFCRLF
* is dependent on the value PBUF_POOL_BUFSIZE on lwipopts.h, and on the program sending
* the data (the browser).
*
* We get here with the filename at buf[11] (buf = "PUT /ufile=BOOT.TXT ...)
* To get data size, search for Content-Length:
* To find the start of the data, search for CRLFCRLF, and extract Content-Length of it.
*
* The actual file data is after a CRLFCRLF and is quite likely in this buffer!
*
* This function writes data directly from LWIP's buffers to the filesystem, so there
* is no 512 byte etc limitation there.
*
*/
static void EditGetData(struct netconn *conn, char *buf, uint16_t buflen)
{
struct netbuf *nbuf = NULL; // address of a netbuf
err_t err = ERR_RST; // some initial value ("connection closed")
char filename[20];
char filesizebuf[20];
uint32_t filesize=0; // size extracted from client header
bool found=true; // false if any of various items not found in header
char * ptr = NULL;
FIL fp;
UINT actual_length=0; // #bytes actually written to file
uint32_t total_written=0; // accumulation of above
bool wr_fail = false;
char TEMPFILE[] = "$$$$TEMP.TMP";
// Buf already holds the first load of data. Length is buflen. Parsing this assumes that
// - the relevant part of the client's header is in buf
// - enough data has actually arrived for the above to be true (might need a short delay)
// The lwipopts.h PBUF_POOL_BUFSIZE parameter has a direct bearing on this and needs to be >500
// Extract filename
strncpy(filename,&buf[11],sizeof(filename)); // copy over filename, ' ' terminated
ptr = strnstr(filename," ",sizeof(filename));
if (ptr != NULL)
{
*(uint32_t*) ptr = 0; // null-terminate filename (replace '=' with 0)
}
else
{
found=false;
}
// Extract file size; limit search for "Content-Length:" to some plausible value (MTU)
ptr = strnstr(buf,"Content-Length:",1500);
if ( (ptr != NULL) && found )
{
strncpy(filesizebuf, 15+ptr, sizeof(filesizebuf));
filesize=atoi(filesizebuf);
}
else
{
found=false;
}
// Extract the portion of the file in buf. Typically this is at buf+390 or so
uint32_t foffset=0; // offset of where file data starts in buf
uint32_t flen=0; // size of file data
ptr = strnstr(buf,CRLFCRLF,1500);
if ( (ptr != NULL) && found )
{
foffset = 4+(uint32_t)(ptr-buf); // 4 to skip CRLFCRLF
flen = buflen-foffset;
}
else
{
found=false;
}
//debug_thread_printf("flen=%d offset=%d, found=%d", flen, foffset, found);
// If we got a duff data size then filename is probably duff too, so do nothing
if ( found && (filesize!=0) && (filesize<(2048*1024)) )
{
// First we need to write to the file the data in the above buffer, from
// buf[offset], len=flen
// Write to a temp file first
// Delete any file of same name
f_chmod(TEMPFILE, 0, AM_RDO|AM_SYS|AM_HID); // This is in case file exists and is R/O etc
f_unlink(TEMPFILE); // Erase it
// Open the file
if ( f_open(&fp, TEMPFILE, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK )
{
wr_fail=true;
}
else
{
// Write this initial piece of the data
if (f_write(&fp, &buf[foffset], flen, &actual_length) != FR_OK )
{
wr_fail=true;
}
else
{
total_written+=flen;
// Now write the rest of the data to the file
while((err = netconn_recv(conn, &nbuf)) == ERR_OK)
{
char *data = NULL;
uint16_t len = 0;
netbuf_first(nbuf);
do
{
netbuf_data(nbuf, (void**)&data, &len);
if ( f_write(&fp, data, len, &actual_length) != FR_OK )
{
wr_fail=true;
break;
}
total_written+=len;
}
while ( (netbuf_next(nbuf) >= 0) && (len==actual_length) );
netbuf_delete(nbuf);
}
}
f_close(&fp);
}
// Return status to client
if ( !found || wr_fail || (total_written!=filesize) )
{
netconn_write(conn, EDIT_WRITE_BAD, strlen((char*)EDIT_WRITE_BAD), NETCONN_COPY);
}
else
{
netconn_write(conn, EDIT_WRITE_GOOD, strlen((char*)EDIT_WRITE_GOOD), NETCONN_COPY);
}
// Close connection
netconn_close(conn);
}
}
The last code above does not show the renaming of the temp file to the real file, etc. It also does not show checking if the temp file is the same size as the filesize extracted from the header, but these are irrelevant to the topic.
So actual netconn code is simple. What it is not showing is that the netconn API is clearly (though I have not seen this spelt out anywhere) tightly connected to the above mention two values in lwipopts.h. This loop
// Now write the rest of the data to the file
while((err = netconn_recv(conn, &nbuf)) == ERR_OK)
{
char *data = NULL;
uint16_t len = 0;
netbuf_first(nbuf);
do
{
netbuf_data(nbuf, (void**)&data, &len);
if ( f_write(&fp, data, len, &actual_length) != FR_OK )
{
wr_fail=true;
break;
}
total_written+=len;
}
while ( (netbuf_next(nbuf) >= 0) && (len==actual_length) );
netbuf_delete(nbuf);
}
picks the next buffer out of the possibly many rx buffers specified in lwipopts, and then netbuf_next advances to the next buffer, until you reach the last one. Then netbuf_delete tells lwip that you have extracted data out of that buffer and it can be refilled.
I have posted a lot more code above than was needed but I wanted to show the whole sequence of netconn calls and don't have the time to cut out the other stuff.
One interesting aspect in the above code is that all the rx data gets copied straight out of lwip buffers (which in this case are 1 MTU in size) and you don't need another buffer of your own, for which memory would need to be allocated.