Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of PicoTCP by
pico_http_server.c
00001 /********************************************************************* 00002 PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. 00003 See LICENSE and COPYING for usage. 00004 00005 Author: Andrei Carp <andrei.carp@tass.be> 00006 *********************************************************************/ 00007 00008 #include "pico_stack.h" 00009 #include "pico_http_server.h" 00010 #include "pico_tcp.h" 00011 #include "pico_tree.h" 00012 #include "pico_socket.h" 00013 00014 #ifdef PICO_SUPPORT_HTTP_SERVER 00015 00016 #define BACKLOG 10 00017 00018 #define HTTP_SERVER_CLOSED 0 00019 #define HTTP_SERVER_LISTEN 1 00020 00021 #define HTTP_HEADER_MAX_LINE 256u 00022 00023 #define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) 00024 00025 static char returnOkHeader[] = 00026 "HTTP/1.1 200 OK\r\n\ 00027 Host: localhost\r\n\ 00028 Transfer-Encoding: chunked\r\n\ 00029 Connection: close\r\n\ 00030 \r\n"; 00031 00032 static char returnFailHeader[] = 00033 "HTTP/1.1 404 Not Found\r\n\ 00034 Host: localhost\r\n\ 00035 Connection: close\r\n\ 00036 \r\n\ 00037 <html><body>The resource you requested cannot be found !</body></html>"; 00038 00039 static char errorHeader[] = 00040 "HTTP/1.1 400 Bad Request\r\n\ 00041 Host: localhost\r\n\ 00042 Connection: close\r\n\ 00043 \r\n\ 00044 <html><body>There was a problem with your request !</body></html>"; 00045 00046 struct httpServer 00047 { 00048 uint16_t state; 00049 struct pico_socket * sck; 00050 uint16_t port; 00051 void (*wakeup)(uint16_t ev, uint16_t param); 00052 uint8_t accepted; 00053 }; 00054 00055 struct httpClient 00056 { 00057 uint16_t connectionID; 00058 struct pico_socket * sck; 00059 void * buffer; 00060 uint16_t bufferSize; 00061 uint16_t bufferSent; 00062 char * resource; 00063 uint16_t state; 00064 }; 00065 00066 /* Local states for clients */ 00067 #define HTTP_WAIT_HDR 0 00068 #define HTTP_WAIT_EOF_HDR 1 00069 #define HTTP_EOF_HDR 2 00070 #define HTTP_WAIT_RESPONSE 3 00071 #define HTTP_WAIT_DATA 4 00072 #define HTTP_SENDING_DATA 5 00073 #define HTTP_ERROR 6 00074 #define HTTP_CLOSED 7 00075 00076 static struct httpServer server = {}; 00077 00078 /* 00079 * Private functions 00080 */ 00081 static int parseRequest(struct httpClient * client); 00082 static int readRemainingHeader(struct httpClient * client); 00083 static void sendData(struct httpClient * client); 00084 static inline int readData(struct httpClient * client); // used only in a place 00085 static inline struct httpClient * findClient(uint16_t conn); 00086 00087 static int compareClients(void * ka, void * kb) 00088 { 00089 return ((struct httpClient *)ka)->connectionID - ((struct httpClient *)kb)->connectionID; 00090 } 00091 00092 PICO_TREE_DECLARE(pico_http_clients,compareClients); 00093 00094 void httpServerCbk(uint16_t ev, struct pico_socket *s) 00095 { 00096 struct pico_tree_node * index; 00097 struct httpClient * client = NULL; 00098 uint8_t serverEvent = FALSE; 00099 00100 // determine the client for the socket 00101 if( s == server.sck) 00102 { 00103 serverEvent = TRUE; 00104 } 00105 else 00106 { 00107 pico_tree_foreach(index,&pico_http_clients) 00108 { 00109 client = index->keyValue; 00110 if(client->sck == s) break; 00111 client = NULL; 00112 } 00113 } 00114 00115 if(!client && !serverEvent) 00116 { 00117 return; 00118 } 00119 00120 if (ev & PICO_SOCK_EV_RD) 00121 { 00122 00123 if(readData(client) == HTTP_RETURN_ERROR) 00124 { 00125 // send out error 00126 client->state = HTTP_ERROR; 00127 pico_socket_write(client->sck,errorHeader,sizeof(errorHeader)-1); 00128 server.wakeup(EV_HTTP_ERROR,client->connectionID); 00129 } 00130 } 00131 00132 if(ev & PICO_SOCK_EV_WR) 00133 { 00134 if(client->state == HTTP_SENDING_DATA) 00135 { 00136 sendData(client); 00137 } 00138 } 00139 00140 if(ev & PICO_SOCK_EV_CONN) 00141 { 00142 server.accepted = FALSE; 00143 server.wakeup(EV_HTTP_CON,HTTP_SERVER_ID); 00144 if(!server.accepted) 00145 { 00146 pico_socket_close(s); // reject socket 00147 } 00148 } 00149 00150 if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) 00151 { 00152 server.wakeup(EV_HTTP_CLOSE,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); 00153 } 00154 00155 if(ev & PICO_SOCK_EV_ERR) 00156 { 00157 server.wakeup(EV_HTTP_ERROR,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); 00158 } 00159 } 00160 00161 /* 00162 * API for starting the server. If 0 is passed as a port, the port 80 00163 * will be used. 00164 */ 00165 int pico_http_server_start(uint16_t port, void (*wakeup)(uint16_t ev, uint16_t conn)) 00166 { 00167 struct pico_ip4 anything = {}; 00168 00169 server.port = port ? short_be(port) : short_be(80u); 00170 00171 if(!wakeup) 00172 { 00173 pico_err = PICO_ERR_EINVAL; 00174 return HTTP_RETURN_ERROR; 00175 } 00176 00177 server.sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &httpServerCbk); 00178 00179 if(!server.sck) 00180 { 00181 pico_err = PICO_ERR_EFAULT; 00182 return HTTP_RETURN_ERROR; 00183 } 00184 00185 if(pico_socket_bind(server.sck , &anything, &server.port)!=0) 00186 { 00187 pico_err = PICO_ERR_EADDRNOTAVAIL; 00188 return HTTP_RETURN_ERROR; 00189 } 00190 00191 if (pico_socket_listen(server.sck, BACKLOG) != 0) 00192 { 00193 pico_err = PICO_ERR_EADDRINUSE; 00194 return HTTP_RETURN_ERROR; 00195 } 00196 server.wakeup = wakeup; 00197 server.state = HTTP_SERVER_LISTEN; 00198 return HTTP_RETURN_OK; 00199 } 00200 00201 /* 00202 * API for accepting new connections. This function should be 00203 * called when the event EV_HTTP_CON is triggered, if not called 00204 * when noticed the connection will be considered rejected and the 00205 * socket will be dropped. 00206 * 00207 * Returns the ID of the new connection or a negative value if error. 00208 */ 00209 int pico_http_server_accept(void) 00210 { 00211 struct pico_ip4 orig; 00212 struct httpClient * client; 00213 uint16_t port; 00214 00215 client = pico_zalloc(sizeof(struct httpClient)); 00216 if(!client) 00217 { 00218 pico_err = PICO_ERR_ENOMEM; 00219 return HTTP_RETURN_ERROR; 00220 } 00221 00222 client->sck = pico_socket_accept(server.sck,&orig,&port); 00223 00224 if(!client->sck) 00225 { 00226 pico_err = PICO_ERR_ENOMEM; 00227 pico_free(client); 00228 return HTTP_RETURN_ERROR; 00229 } 00230 00231 server.accepted = TRUE; 00232 // buffer used for async sending 00233 client->state = HTTP_WAIT_HDR; 00234 client->buffer = NULL; 00235 client->bufferSize = 0; 00236 client->connectionID = pico_rand() & 0x7FFF; 00237 00238 //add element to the tree, if duplicate because the rand 00239 //regenerate 00240 while(pico_tree_insert(&pico_http_clients,client)!=NULL) 00241 client->connectionID = pico_rand() & 0x7FFF; 00242 00243 return client->connectionID; 00244 } 00245 00246 /* 00247 * Function used for getting the resource asked by the 00248 * client. It is useful after the request header (EV_HTTP_REQ) 00249 * from client was received, otherwise NULL is returned. 00250 */ 00251 char * pico_http_getResource(uint16_t conn) 00252 { 00253 struct httpClient * client = findClient(conn); 00254 00255 if(!client) 00256 return NULL; 00257 else 00258 return client->resource; 00259 } 00260 00261 /* 00262 * After the resource was asked by the client (EV_HTTP_REQ) 00263 * before doing anything else, the server has to let know 00264 * the client if the resource can be provided or not. 00265 * 00266 * This is controlled via the code parameter which can 00267 * have two values : 00268 * 00269 * HTTP_RESOURCE_FOUND or HTTP_RESOURCE_NOT_FOUND 00270 * 00271 * If a resource is reported not found the 404 header will be sent and the connection 00272 * will be closed , otherwise the 200 header is sent and the user should 00273 * immediately submit data. 00274 * 00275 */ 00276 int pico_http_respond(uint16_t conn, uint16_t code) 00277 { 00278 struct httpClient * client = findClient(conn); 00279 00280 if(!client) 00281 { 00282 dbg("Client not found !\n"); 00283 return HTTP_RETURN_ERROR; 00284 } 00285 00286 if(client->state == HTTP_WAIT_RESPONSE) 00287 { 00288 if(code == HTTP_RESOURCE_FOUND) 00289 { 00290 client->state = HTTP_WAIT_DATA; 00291 return pico_socket_write(client->sck,returnOkHeader,sizeof(returnOkHeader)-1);//remove \0 00292 } 00293 else 00294 { 00295 int length; 00296 00297 length = pico_socket_write(client->sck,returnFailHeader,sizeof(returnFailHeader)-1);//remove \0 00298 pico_socket_close(client->sck); 00299 client->state = HTTP_CLOSED; 00300 return length; 00301 00302 } 00303 } 00304 else 00305 { 00306 dbg("Bad state for the client \n"); 00307 return HTTP_RETURN_ERROR; 00308 } 00309 00310 } 00311 00312 /* 00313 * API used to submit data to the client. 00314 * Server sends data only using Transfer-Encoding: chunked. 00315 * 00316 * With this function the user will submit a data chunk to 00317 * be sent. 00318 * The function will send the chunk size in hex and the rest will 00319 * be sent using WR event from sockets. 00320 * After each transmision EV_HTTP_PROGRESS is called and at the 00321 * end of the chunk EV_HTTP_SENT is called. 00322 * 00323 * To let the client know this is the last chunk, the user 00324 * should pass a NULL buffer. 00325 */ 00326 int pico_http_submitData(uint16_t conn, void * buffer, int len) 00327 { 00328 00329 struct httpClient * client = findClient(conn); 00330 char chunkStr[10]; 00331 int chunkCount; 00332 00333 if(client->state != HTTP_WAIT_DATA) 00334 { 00335 dbg("Client is in a different state than accepted\n"); 00336 return HTTP_RETURN_ERROR; 00337 } 00338 00339 if(client->buffer) 00340 { 00341 dbg("Already a buffer submited\n"); 00342 return HTTP_RETURN_ERROR; 00343 } 00344 00345 if(!client) 00346 { 00347 dbg("Wrong connection ID\n"); 00348 return HTTP_RETURN_ERROR; 00349 } 00350 00351 if(!buffer) 00352 { 00353 len = 0; 00354 } 00355 00356 if(len > 0) 00357 { 00358 client->buffer = pico_zalloc(len); 00359 if(!client->buffer) 00360 { 00361 pico_err = PICO_ERR_ENOMEM; 00362 return HTTP_RETURN_ERROR; 00363 } 00364 // taking over the buffer 00365 memcpy(client->buffer,buffer,len); 00366 } 00367 else 00368 client->buffer = NULL; 00369 00370 00371 client->bufferSize = len; 00372 client->bufferSent = 0; 00373 00374 // create the chunk size and send it 00375 if(len > 0) 00376 { 00377 client->state = HTTP_SENDING_DATA; 00378 chunkCount = pico_itoaHex(client->bufferSize,chunkStr); 00379 chunkStr[chunkCount++] = '\r'; 00380 chunkStr[chunkCount++] = '\n'; 00381 pico_socket_write(client->sck,chunkStr,chunkCount); 00382 } 00383 else if(len == 0) 00384 { 00385 dbg("->\n"); 00386 // end of transmision 00387 pico_socket_write(client->sck,"0\r\n\r\n",5u); 00388 // nothing left, close the client 00389 pico_socket_close(client->sck); 00390 client->state = HTTP_CLOSED; 00391 } 00392 00393 return HTTP_RETURN_OK; 00394 } 00395 00396 /* 00397 * When EV_HTTP_PROGRESS is triggered you can use this 00398 * function to check the state of the chunk. 00399 */ 00400 00401 int pico_http_getProgress(uint16_t conn, uint16_t * sent, uint16_t *total) 00402 { 00403 struct httpClient * client = findClient(conn); 00404 00405 if(!client) 00406 { 00407 dbg("Wrong connection id !\n"); 00408 return HTTP_RETURN_ERROR; 00409 } 00410 00411 *sent = client->bufferSent; 00412 *total = client->bufferSize; 00413 00414 return HTTP_RETURN_OK; 00415 } 00416 00417 /* 00418 * This API can be used to close either a client 00419 * or the server ( if you pass HTTP_SERVER_ID as a connection ID). 00420 */ 00421 int pico_http_close(uint16_t conn) 00422 { 00423 // close the server 00424 if(conn == HTTP_SERVER_ID) 00425 { 00426 if(server.state == HTTP_SERVER_LISTEN) 00427 { 00428 struct pico_tree_node * index, * tmp; 00429 // close the server 00430 pico_socket_close(server.sck); 00431 server.sck = NULL; 00432 00433 // destroy the tree 00434 pico_tree_foreach_safe(index,&pico_http_clients,tmp) 00435 { 00436 struct httpClient * client = index->keyValue; 00437 00438 if(client->resource) 00439 pico_free(client->resource); 00440 00441 pico_socket_close(client->sck); 00442 pico_tree_delete(&pico_http_clients,client); 00443 } 00444 00445 server.state = HTTP_SERVER_CLOSED; 00446 return HTTP_RETURN_OK; 00447 } 00448 else // nothing to close 00449 return HTTP_RETURN_ERROR; 00450 } // close a connection in this case 00451 else 00452 { 00453 00454 struct httpClient * client = findClient(conn); 00455 00456 if(!client) 00457 { 00458 dbg("Client not found..\n"); 00459 return HTTP_RETURN_ERROR; 00460 } 00461 00462 pico_tree_delete(&pico_http_clients,client); 00463 00464 if(client->resource) 00465 pico_free(client->resource); 00466 00467 if(client->buffer) 00468 pico_free(client->buffer); 00469 00470 if(client->state != HTTP_CLOSED || !client->sck) 00471 pico_socket_close(client->sck); 00472 00473 pico_free(client); 00474 return HTTP_RETURN_OK; 00475 } 00476 } 00477 00478 // check the integrity of the request 00479 int parseRequest(struct httpClient * client) 00480 { 00481 char c; 00482 //read first line 00483 consumeChar(c); 00484 if(c == 'G') 00485 { // possible GET 00486 00487 char line[HTTP_HEADER_MAX_LINE]; 00488 int index = 0; 00489 00490 line[index] = c; 00491 00492 // consume the full line 00493 while(consumeChar(c)>0) // read char by char only the first line 00494 { 00495 line[++index] = c; 00496 if(c == '\n') 00497 break; 00498 00499 if(index >= HTTP_HEADER_MAX_LINE) 00500 { 00501 dbg("Size exceeded \n"); 00502 return HTTP_RETURN_ERROR; 00503 } 00504 } 00505 00506 // extract the function and the resource 00507 if(memcmp(line,"GET",3u) || line[3u]!=' ' || index < 10u || line[index] !='\n') 00508 { 00509 dbg("Wrong command or wrong ending\n"); 00510 return HTTP_RETURN_ERROR; 00511 } 00512 00513 // start reading the resource 00514 index = 4u; // go after ' ' 00515 while(line[index]!=' ') 00516 { 00517 if(line[index]=='\n') // no terminator ' ' 00518 { 00519 dbg("No terminator...\n"); 00520 return HTTP_RETURN_ERROR; 00521 } 00522 00523 index++; 00524 } 00525 00526 client->resource = pico_zalloc(index - 3u);// allocate without the GET in front + 1 which is \0 00527 00528 if(!client) 00529 { 00530 pico_err = PICO_ERR_ENOMEM; 00531 return HTTP_RETURN_ERROR; 00532 } 00533 00534 // copy the resource 00535 memcpy(client->resource,line+4u,index-4u);// copy without the \0 which was already set by pico_zalloc 00536 00537 client->state = HTTP_WAIT_EOF_HDR; 00538 return HTTP_RETURN_OK; 00539 00540 } 00541 00542 return HTTP_RETURN_ERROR; 00543 } 00544 00545 00546 00547 int readRemainingHeader(struct httpClient * client) 00548 { 00549 char line[100]; 00550 int count = 0; 00551 int len; 00552 00553 while( (len = pico_socket_read(client->sck,line,100u)) > 0) 00554 { 00555 char c; 00556 int index = 0; 00557 // parse the response 00558 while(index < len) 00559 { 00560 c = line[index++]; 00561 if(c!='\r' && c!='\n') 00562 count++; 00563 if(c=='\n') 00564 { 00565 if(!count) 00566 { 00567 client->state = HTTP_EOF_HDR; 00568 dbg("End of header !\n"); 00569 break; 00570 } 00571 count = 0; 00572 00573 } 00574 } 00575 } 00576 00577 return HTTP_RETURN_OK; 00578 } 00579 00580 void sendData(struct httpClient * client) 00581 { 00582 int length; 00583 while( client->bufferSent < client->bufferSize && 00584 (length = pico_socket_write(client->sck,client->buffer+client->bufferSent,client->bufferSize-client->bufferSent)) > 0 ) 00585 { 00586 client->bufferSent += length; 00587 server.wakeup(EV_HTTP_PROGRESS,client->connectionID); 00588 } 00589 00590 if(client->bufferSent == client->bufferSize && client->bufferSize) 00591 { 00592 //send chunk trail 00593 if(pico_socket_write(client->sck,"\r\n",2) > 0) 00594 { 00595 client->state = HTTP_WAIT_DATA; 00596 //free the buffer 00597 pico_free(client->buffer); 00598 client->buffer = NULL; 00599 server.wakeup(EV_HTTP_SENT,client->connectionID); 00600 } 00601 } 00602 00603 } 00604 00605 int readData(struct httpClient * client) 00606 { 00607 if(client->state == HTTP_WAIT_HDR) 00608 { 00609 if(parseRequest(client)<0 || readRemainingHeader(client)<0) 00610 { 00611 return HTTP_RETURN_ERROR; 00612 } 00613 } // continue with this in case the header comes line by line not a big chunk 00614 else if(client->state == HTTP_WAIT_EOF_HDR) 00615 { 00616 if(readRemainingHeader(client)<0 ) 00617 return HTTP_RETURN_ERROR; 00618 } 00619 00620 if(client->state == HTTP_EOF_HDR) 00621 { 00622 client->state = HTTP_WAIT_RESPONSE; 00623 pico_socket_shutdown(client->sck,PICO_SHUT_RD); 00624 server.wakeup(EV_HTTP_REQ,client->connectionID); 00625 } 00626 00627 return HTTP_RETURN_OK; 00628 } 00629 00630 struct httpClient * findClient(uint16_t conn) 00631 { 00632 struct httpClient dummy = {.connectionID = conn}; 00633 00634 return pico_tree_findKey(&pico_http_clients,&dummy); 00635 } 00636 #endif
Generated on Thu Jul 14 2022 08:24:58 by
1.7.2
