From 6686e23f4ded56642901fe5a95fb31eb2fc51d2b Mon Sep 17 00:00:00 2001 From: Adrian Bowyer Date: Wed, 2 Oct 2013 12:31:38 +0100 Subject: [PATCH] Webserver coming along. Some tidying. For some reason there is a big delay on closing the connection when there is a second partly-full buffer of data sent to the browser after a completely full one. --- Platform.cpp | 128 ++++++++++++++++++++------------- Platform.h | 6 +- Webserver.cpp | 4 +- network/httpd.c | 183 ++++++++++++++++++++++++++---------------------- 4 files changed, 186 insertions(+), 135 deletions(-) diff --git a/Platform.cpp b/Platform.cpp index d323334..e8dc672 100644 --- a/Platform.cpp +++ b/Platform.cpp @@ -681,6 +681,10 @@ void Platform::Spin() // } } +//************************************************************************************************* + +// Serial/USB class + Line::Line() { } @@ -693,47 +697,70 @@ void Line::Init() while (!SerialUSB.available()); } +//*************************************************************************************************** + +// Network/Ethernet class + +// C calls to interface with LWIP (http://savannah.nongnu.org/projects/lwip/) +// These are implemented in, and called from, a modified version of httpd.c +// in the network directory. + extern "C" { -static char* hdat = 0; -static int hlen = 0; -//static float nwtime = 100000.0; +// Transmit data to the Network void SetNetworkDataToSend(char* data, int length); + +// Close the connection + void CloseConnection(); -void NWSetNetworkDataToSend(char* data, int length) -{ - hdat = data; - hlen = length; -// nwtime = reprap.GetPlatform()->Time() + 3.0; -} +// Called to put out a message via the RepRap firmware. void RepRapNetworkMessage(char* s) { reprap.GetPlatform()->Message(HOST_MESSAGE, s); } +// Called to push data into the RepRap firmware. + void RepRapNetworkReceiveInput(char* ip, int length) { reprap.GetPlatform()->GetNetwork()->ReceiveInput(ip, length); } +// Called when transmission of outgoing data is complete to allow +// the RepRap firmware to write more. + void RepRapNetworkAllowWriting() { reprap.GetPlatform()->GetNetwork()->SetWriteEnable(true); } +// Called by the RepRap firmware to transmit data, if there is +// any to send. + void SendDataFromRepRapNetwork() { if(!reprap.GetPlatform()->GetNetwork()->DataToSendAvailable()) return; + // Stop the generation of more data. + reprap.GetPlatform()->GetNetwork()->SetWriteEnable(false); + + // Find where the data is. + char* data = reprap.GetPlatform()->GetNetwork()->OutputBuffer(); int length = reprap.GetPlatform()->GetNetwork()->OutputBufferLength(); + + // Send it. + SetNetworkDataToSend(data, length); + + // Prepare to write more, when writing is re-enabled. + reprap.GetPlatform()->GetNetwork()->ClearWriteBuffer(); } @@ -746,36 +773,47 @@ Network::Network() ethPinsInit(); } -void Network::Init() +// Reset the network to its disconnected and ready state. + +void Network::Reset() { - alternateInput = NULL; - alternateOutput = NULL; - init_ethernet(); inputPointer = 0; inputLength = -1; outputPointer = 0; outputLength = -1; writeEnabled = true; + status = nothing; } -// Look for CloseConnectionAndFreeBuffer - -//static void FreeBuffer(); +void Network::Init() +{ + alternateInput = NULL; + alternateOutput = NULL; + init_ethernet(); + Reset(); +} void Network::Spin() { // Finish reading any data that's been received. if(inputPointer < inputLength) - { - //reprap.GetWebserver()->Spin(); // Is this sensible? return; - } + + ethernet_task(); + + // Send any data that's available SendDataFromRepRapNetwork(); + // Poll the network, and update its timers. + ethernet_task(); + // If we've finished generating data, queue up the + // last bytes recorded (which may not fill the + // buffer) to send. + if(!reprap.GetWebserver()->WebserverIsWriting()) { if(outputPointer > 0) @@ -784,33 +822,15 @@ void Network::Spin() outputPointer = 0; } } - -// if(reprap.GetPlatform()->Time() > nwtime && hdat != 0) -// { -// SetNetworkDataToSend(hdat, hlen); -// hdat = 0; -// } } void Network::ReceiveInput(char* ip, int length) { - if(length > STRING_LENGTH) - { - reprap.GetPlatform()->Message(HOST_MESSAGE, "Network input buffer overflow.\n"); - inputLength = STRING_LENGTH; - } else - inputLength = length; - for(int i = 0; i < inputLength; i++) - { - inputBuffer[i] = ip[i]; - //SerialUSB.print(inputBuffer[i]); - } -// inputBuffer = ip; -// inputLength = length; + status = clientLive; + inputBuffer = ip; + inputLength = length; inputPointer = 0; - //while(Status() != nothing) - // reprap.GetWebserver()->Spin(); // Nasty... } @@ -834,6 +854,8 @@ void Network::ClearWriteBuffer() void Network::Write(char b) { + // Check for horrible things... + if(!writeEnabled) { reprap.GetPlatform()->Message(HOST_MESSAGE, "Network::Write(char b) - Attempt to write when disabled.\n"); @@ -855,9 +877,13 @@ void Network::Write(char b) return; } + // Add the byte to the buffer + outputBuffer[outputPointer] = b; outputPointer++; + // Buffer full? If so, flag it to send. + if(outputPointer >= STRING_LENGTH - 5) // 5 is for safety { outputLength = outputPointer; @@ -866,6 +892,9 @@ void Network::Write(char b) outputLength = -1; } +// If outputLength has been set, there's some data ready +// to send. + bool Network::DataToSendAvailable() { return (outputLength > 0); @@ -879,6 +908,9 @@ bool Network::CanWrite() void Network::SetWriteEnable(bool enable) { writeEnabled = enable; + + // Reset the write buffer if needs be. + if(writeEnabled && outputLength > 0) { outputLength = -1; @@ -886,6 +918,11 @@ void Network::SetWriteEnable(bool enable) } } +// This is not called for data, only for internally- +// generated short strings at the start of a transmission, +// so it should never overflow the buffer (which is checked +// anyway). + void Network::Write(char* s) { int i = 0; @@ -909,19 +946,14 @@ bool Network::Read(char& b) void Network::Close() { CloseConnection(); -// if (client) -// { -// client.stop(); -// //Serial.println("client disconnected"); -// } else -// reprap.GetPlatform()->Message(HOST_MESSAGE, "Attempt to disconnect non-existent client."); + Reset(); } int8_t Network::Status() { if(inputPointer >= inputLength) - return nothing; - return clientConnected | byteAvailable; + return status; + return status | clientConnected | byteAvailable; } diff --git a/Platform.h b/Platform.h index 2ecd0e9..c1024df 100644 --- a/Platform.h +++ b/Platform.h @@ -252,12 +252,16 @@ protected: Network(); void Init(); void Spin(); + private: - char inputBuffer[STRING_LENGTH]; + + void Reset(); + char* inputBuffer; char outputBuffer[STRING_LENGTH]; int inputPointer, inputLength; int outputPointer, outputLength; bool writeEnabled; + int8_t status; }; // This class handles serial I/O - typically via USB diff --git a/Webserver.cpp b/Webserver.cpp index fb40319..9d9295c 100644 --- a/Webserver.cpp +++ b/Webserver.cpp @@ -636,12 +636,14 @@ void Webserver::Spin() } } -// if (platform->GetNetwork()->Status() & clientLive) + if (platform->GetNetwork()->Status() & clientLive) { if(needToCloseClient) { if(platform->Time() - clientCloseTime < CLIENT_CLOSE_DELAY || !platform->GetNetwork()->CanWrite()) return; + //if(!platform->GetNetwork()->CanWrite()) + // return; needToCloseClient = false; platform->GetNetwork()->Close(); } diff --git a/network/httpd.c b/network/httpd.c index 4b477f3..ea5726a 100644 --- a/network/httpd.c +++ b/network/httpd.c @@ -30,6 +30,17 @@ * */ + +/* + * Heavily modified by Adrian + * + * RepRapPro Ltd + * http://reprappro.com + * + * 2 October 2013 + * + */ + //#include "lwipopts.h" //#if defined(HTTP_RAW_USED) // @@ -50,20 +61,31 @@ #include "lwip/src/include/lwip/tcp.h" #include "fs.h" -void RepRapNetworkReceiveInput(char* ip, int length); -void RepRapNetworkMessage(char* s); -void RepRapNetworkAllowWriting(); -//void NWSetNetworkDataToSend(char* data, int length); - struct http_state { char *file; u16_t left; u8_t retries; }; +// Prototypes for the RepRap functions in Platform.cpp that we +// need to call. + +void RepRapNetworkReceiveInput(char* ip, int length); +void RepRapNetworkMessage(char* s); +void RepRapNetworkAllowWriting(); + +// Static storage for pointers that need to be saved when we go +// out to the RepRap firmware for when it calls back in again. +// Note that this means that the code is not reentrant, but in +// our context it doesn't need to be. + static struct tcp_pcb* activePcb; static struct tcp_pcb* pcbToClose = 0; static struct http_state* activeHttpState; +static struct pbuf* pbufToFree = 0; +static struct tcp_pcb* sendingPcb = 0; +static int initCount = 0; +bool alreadySending = false; /*-----------------------------------------------------------------------------------*/ static void @@ -78,33 +100,38 @@ conn_err(void *arg, err_t err) } /*-----------------------------------------------------------------------------------*/ +// Added to allow RepRap to close the connection. + void CloseConnection() { - RepRapNetworkMessage("CloseConnection() called.\n"); if(pcbToClose == 0) return; - RepRapNetworkMessage("Got a pcb.\n"); - - tcp_arg(pcbToClose, NULL); - tcp_sent(pcbToClose, NULL); - tcp_recv(pcbToClose, NULL); - //mem_free(hs); - tcp_close(pcbToClose); - pcbToClose = 0; + RepRapNetworkMessage("CloseConnection() called.\n"); + tcp_arg(pcbToClose, NULL); + tcp_sent(pcbToClose, NULL); + tcp_recv(pcbToClose, NULL); + //mem_free(hs); + tcp_close(pcbToClose); + pcbToClose = 0; + alreadySending = false; } +// httpd.c's close function, slightly mashed... + static void close_conn(struct tcp_pcb *pcb, struct http_state *hs) { - RepRapNetworkMessage("Internal close_conn called.\n"); -// CloseConnection(); + RepRapNetworkMessage("Internal close_conn called.\n"); tcp_arg(pcb, NULL); tcp_sent(pcb, NULL); tcp_recv(pcb, NULL); //mem_free(hs); tcp_close(pcb); + alreadySending = false; } +char scratch[40]; + /*-----------------------------------------------------------------------------------*/ static void send_data(struct tcp_pcb *pcb, struct http_state *hs) @@ -120,6 +147,11 @@ send_data(struct tcp_pcb *pcb, struct http_state *hs) len = hs->left; } + RepRapNetworkMessage("Sending "); + sprintf(scratch, "%d", len); + RepRapNetworkMessage(scratch); + RepRapNetworkMessage(".."); + do { err = tcp_write(pcb, hs->file, len, 0); if (err == ERR_MEM) { @@ -158,25 +190,33 @@ http_poll(void *arg, struct tcp_pcb *pcb) return ERR_OK; } + /*-----------------------------------------------------------------------------------*/ static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len) { struct http_state *hs; - //RepRapNetworkAllowWriting(); - LWIP_UNUSED_ARG(len); hs = arg; hs->retries = 0; - if (hs->left > 0) { + RepRapNetworkMessage("..sent\n"); + + if (hs->left > 0) + { send_data(pcb, hs); - } else { + } else + { + // See if there is more to send, and remember the + // pcb for when the connection is closed. + // TODO - possible memory leak? RepRapNetworkAllowWriting(); + sendingPcb = pcb; pcbToClose = pcb; + tcp_sent(pcb, http_sent); //close_conn(pcb, hs); } @@ -184,8 +224,10 @@ http_sent(void *arg, struct tcp_pcb *pcb, u16_t len) } /*-----------------------------------------------------------------------------------*/ -static struct pbuf* pbufToFree = 0; -static struct tcp_pcb* sendingPcb = 0; +// ReoRap calls this with data to send. +// It has the side effect of freeing the input buffer +// that prompted the transmission, as that must now have been fully read. +// If RepRap ignores input, is this another potential memory leak? void SetNetworkDataToSend(char* data, int length) { @@ -202,76 +244,45 @@ void SetNetworkDataToSend(char* data, int length) send_data(sendingPcb, activeHttpState); + if(alreadySending) + return; + /* Tell TCP that we wish be to informed of data that has been successfully sent by a call to the http_sent() function. */ + + tcp_sent(sendingPcb, http_sent); + + alreadySending = true; } static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { - int i; - char *data; - //struct fs_file file; - //struct http_state *hs; + int i; + char *data; - //hs = arg; + if (err == ERR_OK && p != NULL) + { + /* Inform TCP that we have taken the data. */ + tcp_recved(pcb, p->tot_len); - if (err == ERR_OK && p != NULL) { + if (activeHttpState->file == NULL) + { + data = p->payload; + RepRapNetworkReceiveInput(data, p->len); + pbufToFree = p; + sendingPcb = pcb; + } else + { + pbuf_free(p); + } + } - /* Inform TCP that we have taken the data. */ - tcp_recved(pcb, p->tot_len); - - if (activeHttpState->file == NULL) { - data = p->payload; - - RepRapNetworkReceiveInput(data, p->len); - -// if (strncmp(data, "GET ", 4) == 0) { -// for(i = 0; i < 40; i++) { -// if (((char *)data + 4)[i] == ' ' || -// ((char *)data + 4)[i] == '\r' || -// ((char *)data + 4)[i] == '\n') { -// ((char *)data + 4)[i] = 0; -// } -// } -// -// if (*(char *)(data + 4) == '/' && -// *(char *)(data + 5) == 0) { -// fs_open("/index.html", &file); -// } else if (!fs_open((char *)data + 4, &file)) { -// fs_open("/404.html", &file); -// } - - pbufToFree = p; - sendingPcb = pcb; - - //NWSetNetworkDataToSend(file.data, file.len); - -// activeHttpState->file = file.data; -// activeHttpState->left = file.len; -// /* printf("data %p len %ld\n", hs->file, hs->left);*/ -// -// pbuf_free(pbufToFree); -// -// send_data(sendingPcb, activeHttpState); -// -// /* Tell TCP that we wish be to informed of data that has been -// successfully sent by a call to the http_sent() function. */ -// tcp_sent(sendingPcb, http_sent); -// } else { -// pbuf_free(p); -// close_conn(pcb, activeHttpState); -// } - } else { - pbuf_free(p); - } - } - - if (err == ERR_OK && p == NULL) { - close_conn(pcb, activeHttpState); - } - return ERR_OK; + if (err == ERR_OK && p == NULL) { + close_conn(pcb, activeHttpState); + } + return ERR_OK; } /*-----------------------------------------------------------------------------------*/ static err_t @@ -284,9 +295,7 @@ http_accept(void *arg, struct tcp_pcb *pcb, err_t err) tcp_setprio(pcb, TCP_PRIO_MIN); - /* Allocate memory for the structure that holds the state of the - connection. */ - //hs = (struct http_state *)mem_malloc(sizeof(struct http_state)); + // Ignore arg; we know it must be our static activeHttpState hs = activeHttpState; @@ -315,11 +324,15 @@ http_accept(void *arg, struct tcp_pcb *pcb, err_t err) } /*-----------------------------------------------------------------------------------*/ -// This function is called once at the start. +// This function (is)x should be called only once at the start. void httpd_init(void) { + initCount++; + if(initCount > 1) + RepRapNetworkMessage("httpd_init() called more than once.\n"); + activeHttpState = (struct http_state *)mem_malloc(sizeof(struct http_state)); activePcb = tcp_new(); tcp_bind(activePcb, IP_ADDR_ANY, 80);