HTTP Server upon new mbed Ethernet Interface. Based on original code by Henry Leinen.
Dependencies: EthernetInterface mbed-rtos mbed
Fork of HTTP_server by
Revision 0:fcceff3299be, committed 2013-07-26
- Comitter:
- pabloxid
- Date:
- Fri Jul 26 22:05:19 2013 +0000
- Child:
- 1:f0c641cd9bad
- Commit message:
- HTTP Server ; Copyright (c) 2013 Pablo Gindel (palmer@pablogindel.com); Based on original code by Henry Leinen.
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EthernetInterface.lib Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/EthernetInterface/#40640efbfcae
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/HTTPServer.cpp Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,417 @@ +#include "mbed.h" +#include "HTTPServer.h" + +#define DEBUG +#include "debug.h" + +#define TIMEOUT 500 +#define OK 0 +#define ERROR -1 +#define EMPTY -2 +#define MIN_LONG 3 + + +RequestConfig rq_conf[] = { + { "GET", HTTP_RT_GET }, + { "POST", HTTP_RT_POST} +}; + +HTTPServer::HTTPServer (int port, const char* _path) { + + INFO("Binding to port %d...", port); + if (socketServer.bind (port) < 0) { + ERR("Failed to bind to port !\n"); + error("Binding"); + } + + INFO("Listening ..."); + if (socketServer.listen(1) < 0) { + ERR("Failed to listen !\n"); + error("Listening"); + } + + INFO("Connected !"); + // set into blocking operation + socketServer.set_blocking (true, TIMEOUT); + + path = _path; + +} + +HTTPServer::~HTTPServer() { }; + +int HTTPServer::poll () { + + cliente = new TCPSocketConnection; + cliente->set_blocking (false, TIMEOUT); + + if (socketServer.accept(*cliente) < 0) { + INFO("No connection\n"); + return ERROR; + } + + // a new connection was received + INFO("Client (IP=%s) is connected !\n", cliente->get_address()); + + msg = new HTTPMsg; // estructura para decodificar y alojar el mensaje + + int c = pollConnection (); // esto parsea y llena las cosas contenidas en msg + + if (c == OK) { + // Handle the request + INFO("Handling request !"); + //cliente->set_blocking (true, TIMEOUT); + handleRequest (); + } + + delete msg; + delete cliente; + + INFO("Leaving polling thread"); + return c; +} + +int HTTPServer::pollConnection () { + + int received = 0; + INFO("Waiting for new data in connection"); + // Try receiving request line + received = receiveLine (); + + if (received == ERROR) { + // there was an error, probably the connection was closed, so close this connection as well + INFO("No more data available. Will close this connection now."); + return ERROR; + } + + // The Request has not yet been received so try it + received = parse (); + + if (received == ERROR) { + // Invalid content received, so close the connection + INFO("Invalid message received, so sending negative response and closing connection !"); + sprintf (buffer,"HTTP/1.1 400 BadRequest\n\rContent-Length: %d\n\rContent-Type: text\n\r\n\r\n\r",0); + + cliente->send (buffer, strlen (buffer)); + + return ERROR; + } + + /* The request has been received, try receive the body + do { + received = receiveLine (); + if (received == ERROR) {return ERROR;} + // First check if we received an empty line. This would indicate the end of the message or message body. + if (received == EMPTY) { + // there was an empty line, so we can start with performing the request + INFO("Request Header was received completely. Performing request."); + received = 0; + break; + } else { + // add message body + if (parseHeader () != 0) { + WARN("Invalid message header received !"); + } + } + } while (received > 0); */ + + INFO("Leaving poll function!"); + return received; +} + +int HTTPServer::receiveLine () { + + buffer[0] = 0; + + if (!cliente->is_connected()) { + error("NOT Connected anymore"); + return ERROR; + } + + Timer tm; + int i; + + // Try to receive up to the max number of characters + for (i=0; i<BUFFER_SIZE-1; i++) { + int c = cliente->receive (buffer+i, 1); + // Check that - if no character was currently received - the timeout period is reached. + if (c == 0 || c == -1) { + // no character was read, so check if operation timed out + if (tm.read_ms() > TIMEOUT) { + // Operation timed out + INFO("Timeout occured in function 'receiveLine'."); + return ERROR; + } + } + // Check if line terminating character was received + if (buffer[i] == '\n') {break;} + } + // Terminate with \0 + buffer[i] = 0; + + // Trim for '\r' linefeed at the end + if (i>0 && buffer[i-1] == '\r') { + i--; + buffer[i] = 0; + } + + // return number of characters received in the line or return -2 if an empty line was received + if (i==0 || (i==1 && buffer[0]=='\r')) { + // empty line received, so return -2 + return EMPTY; + } + // retorna número de caracteres leidos + return i; +} + +int HTTPServer::parse () { + + // Check if buffer content is not long enough. + if (strlen(buffer) < MIN_LONG) { + ERR("Buffer content is invalid or too short."); + return ERROR; + } + + std::vector<std::string> args; + + int argno = 0; + // decompose string into a list of arguments + int start = 0; // current starting char + int nLen = strlen(buffer)+1; + for (int i=0; i<nLen; i++) { + if ((buffer[i] == ' ') || (buffer[i] == '\n') || (buffer[i] == 0)) { + // new arg found + buffer[i] = 0; + if (argno++ == 1) { + // it's the uri + // parse the uri args + parseUriArgs (&buffer[start]); + } + INFO("Found argument \"%s\"", &buffer[start]); + args.push_back(&buffer[start]); + start = i+1; + } + } + + // store the uri and the HTTP version + msg->uri = args[1]; + msg->version = args[2]; + + // Find matching request type + for (int i=0; i<sizeof(rq_conf)/sizeof(RequestConfig) ; i++) { + if (args.at(0) == rq_conf[i].request_string) { + msg->request = rq_conf[i].request_type; + } + } + + return OK; +} + +/* esta rutina no se usa */ + +int HTTPServer::parseHeader () { + + // Check if the buffer content is too short to be meaningful + if (strlen(buffer) < MIN_LONG) {return ERROR;} + + // decompose string into a touple of <field name> : <field value> + int value_start = 0; + int buflen = strlen(buffer)+1; + for (int i=0; i<buflen; i++) { + if (buffer[i] == ':') { + // touple found + buffer[i] = 0; + value_start = i+1; + msg->headers[buffer] = &buffer[value_start]; + INFO("Header name=\"%s\" : value=\"%s\".", buffer, &buffer[value_start]); + return OK; + } + } + + ERR("Did not recieve a valid header : \"%s\".", buffer); + return ERROR; +} + +int HTTPServer::parseUriArgs (char *uri_buffer) { + + // Check if the buffer content is too short to be meaningful + if (strlen(uri_buffer) < MIN_LONG) {return ERROR;} + + int args_start = -1; + int value_start = -1; + int buflen = strlen(uri_buffer) + 1; + char* argname = NULL; + char* valuename = NULL; + for (int i=0; i<buflen; i++) { + if (args_start == -1) { // args section not yet found + if (uri_buffer[i] == '?') { // starts with a question mark, so got it + uri_buffer[i] = 0; + args_start = i; // set the start of the args section + INFO("Argument section found !"); + } + } else { // search arg-value touples + if (argname == NULL) { // arg-name found ? + if (uri_buffer[i] == '=') { + // yes, separate the arg-name + uri_buffer[i] = 0; + argname = &uri_buffer[args_start]; + value_start = i+1; + INFO("Argument name %s", argname); + } + } else { // search for end of value + if ((uri_buffer[i] == '&') || (uri_buffer[i] == 0) || (uri_buffer[i] == '\r') || (uri_buffer[i] == '\n')) { + buffer[i] = 0; + valuename = &uri_buffer[value_start]; + INFO("Argument value %s", valuename); + msg->args[argname] = valuename; + // reset all indicators + argname = NULL; + valuename = NULL; + } + } + } + } + + return OK; +} + +/* verificar qué parte de msg realmente se usa, + eliminar el resto, incluyendo los procesos asociados */ + +void HTTPServer::handleRequest () { + + int err_; + + switch (msg->request) { + case HTTP_RT_GET: + INFO("Dispatching GET Request."); + err_ = handleGetRequest(); + break; + case HTTP_RT_POST: + INFO("Dispatching POST request."); + err_ = handlePostRequest(); + break; + default: + INFO("Error in handleRequest, unhandled request type."); + err_ = 501; // HTTP_NotImplemented + break; + } + + // if any of these functions returns a negative number, call the error handler + if (err_ > 0) { + handleError (err_); + } + +} + +int HTTPServer::handleGetRequest() { + + INFO("Handling Get Request."); + + int retval = OK; //success + + // maping to root path + std::string reqPath = path + msg->uri.substr(1); + + // Check if we received a directory with the local path + if (reqPath.substr(reqPath.length()-1, 1) == "/") { + // yes, we shall append the default page name + reqPath += "index.htm"; + } + + INFO("Mapping \"%s\" to \"%s\"", msg->uri.c_str(), reqPath.c_str()); + + FILE *fp = fopen(reqPath.c_str(), "r"); + if (fp != NULL) { + + char * pBuffer = NULL; + int sz = 8192; // fixme harcode + while( pBuffer == NULL) { + sz /= 2; + pBuffer = (char*)malloc(sz); + if (sz < 128) // fixme harcode + error ("OutOfMemory"); + } + + // File was found and can be returned + + // first determine the size + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + startResponse (200, size); // response: 200 + while (!feof(fp) && !ferror(fp)) { + int cnt = fread (pBuffer, 1, sz , fp); + if (cnt < 0) + cnt = 0; + processResponse (cnt, pBuffer); + } + + INFO("Ending Response !"); + + free (pBuffer); + fclose (fp); + + } else { + retval = 404; + ERR("Requested file was not found !"); + } + + return retval; + +} + +int HTTPServer::handlePostRequest() { + + return 404; +} + +static const char hdrStandard[] = "DNT: 1\r\n" + "MaxAge: 0\r\n" + "Connection: Keep-Alive\r\n" + "Content-Type: text/html\r\n" + "Server: mbed embedded\r\n" + "Accessible: 1\r\n" + "\r\n"; + +void HTTPServer::startResponse (int returnCode, long nLen) { + + INFO("Starting response (%ld bytes in total)!", nLen); + + sprintf (buffer, "HTTP/1.1 %d OK\r\n", returnCode); + cliente->send(buffer, strlen(buffer)); + sprintf (buffer, "Content-Length: %ld\r\n", nLen); // Add 2 chars for the terminating CR+LF + cliente->send(buffer, strlen(buffer)); + INFO("Sending standard headers !"); + cliente->send_all((char*)hdrStandard, strlen(hdrStandard)); + + INFO("Proceeding !"); + // other content must be sent using the 'processResponse' function +} + +void HTTPServer::processResponse (int nLen, char* body) { + + INFO("Processing Response (%d bytes)!\n", nLen); + cliente->send_all (body, nLen); + +} + +static const char* errorPage = "<HTML><HEAD><META content=\"text/html\" http-equiv=Content-Type></HEAD><BODY><h1>Error</h1><P>HTTPServer Error<P></BODY></HTML>\r\n\r\n"; + +void HTTPServer::handleError (int errorCode) { + + INFO("Handling error !"); + + sprintf (buffer,"HTTP/1.1 %d Error\r\n", errorCode); + cliente->send (buffer, strlen(buffer)); + sprintf (buffer, "Content-Length: %ld\r\n", strlen(errorPage)); + cliente->send (buffer, strlen(buffer)); + sprintf(buffer, "Content-Type: text/html\r\nServer: mbed embedded\r\n\r\n"); + cliente->send(buffer, strlen(buffer)); + cliente->send_all((char*)errorPage, strlen(errorPage)); + cliente->send("\r\n", 3); + + INFO("Done !"); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/HTTPServer.h Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,91 @@ +/* +Copyright (c) 2013 Pablo Gindel (palmer@pablogindel.com) +Based on original code by Henry Leinen (henry[dot]leinen [at] online [dot] de) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + + +#ifndef __HTTPSERVER_H__ +#define __HTTPSERVER_H__ + +#include "mbed.h" +#include "EthernetInterface.h" +#include <vector> +#include <string> +#include <map> + +enum RequestType { + HTTP_RT_GET, /*!< GET request */ + HTTP_RT_POST, /*!< POST request */ +}; + +/** HTTPMessage contains all the details of the request received by external HTTP client. */ + struct HTTPMsg { + // Specifies the request type received + RequestType request; + // The uri associated with the request. + std::string uri; + // Contains the HTTP/1.1 or HTTP/1.0 version requested by client. + std::string version; + // Map of headers provided by the client in the form <HeaderName>:<HeaderValue> + std::map<std::string, std::string> headers; + // Map of arguments that came with the uri string + std::map<std::string, std::string> args; +}; + +struct RequestConfig { + const char* request_string; + RequestType request_type; +}; + +#define BUFFER_SIZE 256 + +class HTTPServer { + +private: + TCPSocketServer socketServer; + TCPSocketConnection *cliente; + HTTPMsg *msg; + std::string path; + char buffer [BUFFER_SIZE]; + int pollConnection (); + int receiveLine (); + int parse (); + int parseHeader (); + int parseUriArgs (char *uri_buffer); + void handleRequest (); + int handleGetRequest(); + int handlePostRequest(); + void startResponse (int returnCode, long nLen); + void processResponse (int nLen, char* body); + void handleError (int errorCode); + +public: + /** Constructor for HTTPServer objects. */ + HTTPServer (int port, const char* _path); + + /** Destructor for HTTPServer objects. */ + ~HTTPServer(); + + int poll(); +}; + + +#endif //__HTTPSERVER_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/debug.h Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,17 @@ + +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + + +#ifdef DEBUG +#define INFO(x, ...) std::printf("[INFO: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__); +#define WARN(x, ...) std::printf("[WARN: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__); +#define ERR(x, ...) std::printf("[ERR: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__); +#else +#define INFO(x, ...) +#define WARN(x, ...) +#define ERR(x, ...) +#endif + + +#endif // __DEBUG_H__#ifndef __DEBUG_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,50 @@ + +#include "mbed.h" +#include "EthernetInterface.h" +#include "HTTPServer.h" + +LocalFileSystem local("local"); + +DigitalOut led1(LED1); + +void http_thread (void const* arg); /////// + +int main() { + + EthernetInterface eth; + eth.init(); //Use DHCP + eth.connect(); + printf("IP Address is %s\n", eth.getIPAddress()); + + Timer onesec; + onesec.start(); + + Thread httpsvr( &http_thread ); + + while (true) { + + if (onesec.read() > 1) { + onesec.reset(); + led1 = 1-led1; + } + + } + +} + +////////////////////////////////////////////////////////////////////////////////////////////////////// +// HTTP THREAD // +////////////////////////////////////////////////////////////////////////////////////////////////////// + +void http_thread (void const* arg) { + + HTTPServer svr (80, "/local/"); // esto incluye el init + + // osThreadSetPriority( Thread::gettid() , osPriorityBelowNormal ); + + while (1) { + + svr.poll(); + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-rtos.lib Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed-rtos/#58b30ac3f00e
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Fri Jul 26 22:05:19 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed/builds/b3110cd2dd17 \ No newline at end of file