Free (GPLv2) TCP/IP stack developed by TASS Belgium
Fork of PicoTCP by
modules/pico_http_server.c
- Committer:
- tass
- Date:
- 2013-05-17
- Revision:
- 1:cfe8984a32b4
- Parent:
- libraries/picotcp/modules/pico_http_server.c@ 0:d7f2341ab245
File content as of revision 1:cfe8984a32b4:
/********************************************************************* PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. See LICENSE and COPYING for usage. Author: Andrei Carp <andrei.carp@tass.be> *********************************************************************/ #include "pico_stack.h" #include "pico_http_server.h" #include "pico_tcp.h" #include "pico_tree.h" #include "pico_socket.h" #ifdef PICO_SUPPORT_HTTP_SERVER #define BACKLOG 10 #define HTTP_SERVER_CLOSED 0 #define HTTP_SERVER_LISTEN 1 #define HTTP_HEADER_MAX_LINE 256u #define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) static char returnOkHeader[] = "HTTP/1.1 200 OK\r\n\ Host: localhost\r\n\ Transfer-Encoding: chunked\r\n\ Connection: close\r\n\ \r\n"; static char returnFailHeader[] = "HTTP/1.1 404 Not Found\r\n\ Host: localhost\r\n\ Connection: close\r\n\ \r\n\ <html><body>The resource you requested cannot be found !</body></html>"; static char errorHeader[] = "HTTP/1.1 400 Bad Request\r\n\ Host: localhost\r\n\ Connection: close\r\n\ \r\n\ <html><body>There was a problem with your request !</body></html>"; struct httpServer { uint16_t state; struct pico_socket * sck; uint16_t port; void (*wakeup)(uint16_t ev, uint16_t param); uint8_t accepted; }; struct httpClient { uint16_t connectionID; struct pico_socket * sck; void * buffer; uint16_t bufferSize; uint16_t bufferSent; char * resource; uint16_t state; }; /* Local states for clients */ #define HTTP_WAIT_HDR 0 #define HTTP_WAIT_EOF_HDR 1 #define HTTP_EOF_HDR 2 #define HTTP_WAIT_RESPONSE 3 #define HTTP_WAIT_DATA 4 #define HTTP_SENDING_DATA 5 #define HTTP_ERROR 6 #define HTTP_CLOSED 7 static struct httpServer server = {}; /* * Private functions */ static int parseRequest(struct httpClient * client); static int readRemainingHeader(struct httpClient * client); static void sendData(struct httpClient * client); static inline int readData(struct httpClient * client); // used only in a place static inline struct httpClient * findClient(uint16_t conn); static int compareClients(void * ka, void * kb) { return ((struct httpClient *)ka)->connectionID - ((struct httpClient *)kb)->connectionID; } PICO_TREE_DECLARE(pico_http_clients,compareClients); void httpServerCbk(uint16_t ev, struct pico_socket *s) { struct pico_tree_node * index; struct httpClient * client = NULL; uint8_t serverEvent = FALSE; // determine the client for the socket if( s == server.sck) { serverEvent = TRUE; } else { pico_tree_foreach(index,&pico_http_clients) { client = index->keyValue; if(client->sck == s) break; client = NULL; } } if(!client && !serverEvent) { return; } if (ev & PICO_SOCK_EV_RD) { if(readData(client) == HTTP_RETURN_ERROR) { // send out error client->state = HTTP_ERROR; pico_socket_write(client->sck,errorHeader,sizeof(errorHeader)-1); server.wakeup(EV_HTTP_ERROR,client->connectionID); } } if(ev & PICO_SOCK_EV_WR) { if(client->state == HTTP_SENDING_DATA) { sendData(client); } } if(ev & PICO_SOCK_EV_CONN) { server.accepted = FALSE; server.wakeup(EV_HTTP_CON,HTTP_SERVER_ID); if(!server.accepted) { pico_socket_close(s); // reject socket } } if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) { server.wakeup(EV_HTTP_CLOSE,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); } if(ev & PICO_SOCK_EV_ERR) { server.wakeup(EV_HTTP_ERROR,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); } } /* * API for starting the server. If 0 is passed as a port, the port 80 * will be used. */ int pico_http_server_start(uint16_t port, void (*wakeup)(uint16_t ev, uint16_t conn)) { struct pico_ip4 anything = {}; server.port = port ? short_be(port) : short_be(80u); if(!wakeup) { pico_err = PICO_ERR_EINVAL; return HTTP_RETURN_ERROR; } server.sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &httpServerCbk); if(!server.sck) { pico_err = PICO_ERR_EFAULT; return HTTP_RETURN_ERROR; } if(pico_socket_bind(server.sck , &anything, &server.port)!=0) { pico_err = PICO_ERR_EADDRNOTAVAIL; return HTTP_RETURN_ERROR; } if (pico_socket_listen(server.sck, BACKLOG) != 0) { pico_err = PICO_ERR_EADDRINUSE; return HTTP_RETURN_ERROR; } server.wakeup = wakeup; server.state = HTTP_SERVER_LISTEN; return HTTP_RETURN_OK; } /* * API for accepting new connections. This function should be * called when the event EV_HTTP_CON is triggered, if not called * when noticed the connection will be considered rejected and the * socket will be dropped. * * Returns the ID of the new connection or a negative value if error. */ int pico_http_server_accept(void) { struct pico_ip4 orig; struct httpClient * client; uint16_t port; client = pico_zalloc(sizeof(struct httpClient)); if(!client) { pico_err = PICO_ERR_ENOMEM; return HTTP_RETURN_ERROR; } client->sck = pico_socket_accept(server.sck,&orig,&port); if(!client->sck) { pico_err = PICO_ERR_ENOMEM; pico_free(client); return HTTP_RETURN_ERROR; } server.accepted = TRUE; // buffer used for async sending client->state = HTTP_WAIT_HDR; client->buffer = NULL; client->bufferSize = 0; client->connectionID = pico_rand() & 0x7FFF; //add element to the tree, if duplicate because the rand //regenerate while(pico_tree_insert(&pico_http_clients,client)!=NULL) client->connectionID = pico_rand() & 0x7FFF; return client->connectionID; } /* * Function used for getting the resource asked by the * client. It is useful after the request header (EV_HTTP_REQ) * from client was received, otherwise NULL is returned. */ char * pico_http_getResource(uint16_t conn) { struct httpClient * client = findClient(conn); if(!client) return NULL; else return client->resource; } /* * After the resource was asked by the client (EV_HTTP_REQ) * before doing anything else, the server has to let know * the client if the resource can be provided or not. * * This is controlled via the code parameter which can * have two values : * * HTTP_RESOURCE_FOUND or HTTP_RESOURCE_NOT_FOUND * * If a resource is reported not found the 404 header will be sent and the connection * will be closed , otherwise the 200 header is sent and the user should * immediately submit data. * */ int pico_http_respond(uint16_t conn, uint16_t code) { struct httpClient * client = findClient(conn); if(!client) { dbg("Client not found !\n"); return HTTP_RETURN_ERROR; } if(client->state == HTTP_WAIT_RESPONSE) { if(code == HTTP_RESOURCE_FOUND) { client->state = HTTP_WAIT_DATA; return pico_socket_write(client->sck,returnOkHeader,sizeof(returnOkHeader)-1);//remove \0 } else { int length; length = pico_socket_write(client->sck,returnFailHeader,sizeof(returnFailHeader)-1);//remove \0 pico_socket_close(client->sck); client->state = HTTP_CLOSED; return length; } } else { dbg("Bad state for the client \n"); return HTTP_RETURN_ERROR; } } /* * API used to submit data to the client. * Server sends data only using Transfer-Encoding: chunked. * * With this function the user will submit a data chunk to * be sent. * The function will send the chunk size in hex and the rest will * be sent using WR event from sockets. * After each transmision EV_HTTP_PROGRESS is called and at the * end of the chunk EV_HTTP_SENT is called. * * To let the client know this is the last chunk, the user * should pass a NULL buffer. */ int pico_http_submitData(uint16_t conn, void * buffer, int len) { struct httpClient * client = findClient(conn); char chunkStr[10]; int chunkCount; if(client->state != HTTP_WAIT_DATA) { dbg("Client is in a different state than accepted\n"); return HTTP_RETURN_ERROR; } if(client->buffer) { dbg("Already a buffer submited\n"); return HTTP_RETURN_ERROR; } if(!client) { dbg("Wrong connection ID\n"); return HTTP_RETURN_ERROR; } if(!buffer) { len = 0; } if(len > 0) { client->buffer = pico_zalloc(len); if(!client->buffer) { pico_err = PICO_ERR_ENOMEM; return HTTP_RETURN_ERROR; } // taking over the buffer memcpy(client->buffer,buffer,len); } else client->buffer = NULL; client->bufferSize = len; client->bufferSent = 0; // create the chunk size and send it if(len > 0) { client->state = HTTP_SENDING_DATA; chunkCount = pico_itoaHex(client->bufferSize,chunkStr); chunkStr[chunkCount++] = '\r'; chunkStr[chunkCount++] = '\n'; pico_socket_write(client->sck,chunkStr,chunkCount); } else if(len == 0) { dbg("->\n"); // end of transmision pico_socket_write(client->sck,"0\r\n\r\n",5u); // nothing left, close the client pico_socket_close(client->sck); client->state = HTTP_CLOSED; } return HTTP_RETURN_OK; } /* * When EV_HTTP_PROGRESS is triggered you can use this * function to check the state of the chunk. */ int pico_http_getProgress(uint16_t conn, uint16_t * sent, uint16_t *total) { struct httpClient * client = findClient(conn); if(!client) { dbg("Wrong connection id !\n"); return HTTP_RETURN_ERROR; } *sent = client->bufferSent; *total = client->bufferSize; return HTTP_RETURN_OK; } /* * This API can be used to close either a client * or the server ( if you pass HTTP_SERVER_ID as a connection ID). */ int pico_http_close(uint16_t conn) { // close the server if(conn == HTTP_SERVER_ID) { if(server.state == HTTP_SERVER_LISTEN) { struct pico_tree_node * index, * tmp; // close the server pico_socket_close(server.sck); server.sck = NULL; // destroy the tree pico_tree_foreach_safe(index,&pico_http_clients,tmp) { struct httpClient * client = index->keyValue; if(client->resource) pico_free(client->resource); pico_socket_close(client->sck); pico_tree_delete(&pico_http_clients,client); } server.state = HTTP_SERVER_CLOSED; return HTTP_RETURN_OK; } else // nothing to close return HTTP_RETURN_ERROR; } // close a connection in this case else { struct httpClient * client = findClient(conn); if(!client) { dbg("Client not found..\n"); return HTTP_RETURN_ERROR; } pico_tree_delete(&pico_http_clients,client); if(client->resource) pico_free(client->resource); if(client->buffer) pico_free(client->buffer); if(client->state != HTTP_CLOSED || !client->sck) pico_socket_close(client->sck); pico_free(client); return HTTP_RETURN_OK; } } // check the integrity of the request int parseRequest(struct httpClient * client) { char c; //read first line consumeChar(c); if(c == 'G') { // possible GET char line[HTTP_HEADER_MAX_LINE]; int index = 0; line[index] = c; // consume the full line while(consumeChar(c)>0) // read char by char only the first line { line[++index] = c; if(c == '\n') break; if(index >= HTTP_HEADER_MAX_LINE) { dbg("Size exceeded \n"); return HTTP_RETURN_ERROR; } } // extract the function and the resource if(memcmp(line,"GET",3u) || line[3u]!=' ' || index < 10u || line[index] !='\n') { dbg("Wrong command or wrong ending\n"); return HTTP_RETURN_ERROR; } // start reading the resource index = 4u; // go after ' ' while(line[index]!=' ') { if(line[index]=='\n') // no terminator ' ' { dbg("No terminator...\n"); return HTTP_RETURN_ERROR; } index++; } client->resource = pico_zalloc(index - 3u);// allocate without the GET in front + 1 which is \0 if(!client) { pico_err = PICO_ERR_ENOMEM; return HTTP_RETURN_ERROR; } // copy the resource memcpy(client->resource,line+4u,index-4u);// copy without the \0 which was already set by pico_zalloc client->state = HTTP_WAIT_EOF_HDR; return HTTP_RETURN_OK; } return HTTP_RETURN_ERROR; } int readRemainingHeader(struct httpClient * client) { char line[100]; int count = 0; int len; while( (len = pico_socket_read(client->sck,line,100u)) > 0) { char c; int index = 0; // parse the response while(index < len) { c = line[index++]; if(c!='\r' && c!='\n') count++; if(c=='\n') { if(!count) { client->state = HTTP_EOF_HDR; dbg("End of header !\n"); break; } count = 0; } } } return HTTP_RETURN_OK; } void sendData(struct httpClient * client) { int length; while( client->bufferSent < client->bufferSize && (length = pico_socket_write(client->sck,client->buffer+client->bufferSent,client->bufferSize-client->bufferSent)) > 0 ) { client->bufferSent += length; server.wakeup(EV_HTTP_PROGRESS,client->connectionID); } if(client->bufferSent == client->bufferSize && client->bufferSize) { //send chunk trail if(pico_socket_write(client->sck,"\r\n",2) > 0) { client->state = HTTP_WAIT_DATA; //free the buffer pico_free(client->buffer); client->buffer = NULL; server.wakeup(EV_HTTP_SENT,client->connectionID); } } } int readData(struct httpClient * client) { if(client->state == HTTP_WAIT_HDR) { if(parseRequest(client)<0 || readRemainingHeader(client)<0) { return HTTP_RETURN_ERROR; } } // continue with this in case the header comes line by line not a big chunk else if(client->state == HTTP_WAIT_EOF_HDR) { if(readRemainingHeader(client)<0 ) return HTTP_RETURN_ERROR; } if(client->state == HTTP_EOF_HDR) { client->state = HTTP_WAIT_RESPONSE; pico_socket_shutdown(client->sck,PICO_SHUT_RD); server.wakeup(EV_HTTP_REQ,client->connectionID); } return HTTP_RETURN_OK; } struct httpClient * findClient(uint16_t conn) { struct httpClient dummy = {.connectionID = conn}; return pico_tree_findKey(&pico_http_clients,&dummy); } #endif