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_client.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 #include <string.h> 00008 #include <stdint.h> 00009 #include "pico_tree.h" 00010 #include "pico_config.h" 00011 #include "pico_socket.h" 00012 #include "pico_tcp.h" 00013 #include "pico_dns_client.h" 00014 #include "pico_http_client.h" 00015 #include "pico_ipv4.h" 00016 #include "pico_stack.h" 00017 00018 /* 00019 * This is the size of the following header 00020 * 00021 * GET <resource> HTTP/1.1<CRLF> 00022 * Host: <host>:<port><CRLF> 00023 * User-Agent: picoTCP<CRLF> 00024 * Connection: close<CRLF> 00025 * <CRLF> 00026 * 00027 * where <resource>,<host> and <port> will be added later. 00028 */ 00029 00030 #ifdef PICO_SUPPORT_HTTP_CLIENT 00031 00032 #define HTTP_GET_BASIC_SIZE 63u 00033 #define HTTP_HEADER_LINE_SIZE 50u 00034 #define RESPONSE_INDEX 9u 00035 00036 #define HTTP_CHUNK_ERROR 0xFFFFFFFFu 00037 00038 #ifdef dbg 00039 #undef dbg 00040 #define dbg(...) do{}while(0); 00041 #endif 00042 00043 #define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) 00044 #define isLocation(line) (memcmp(line,"Location",8u) == 0) 00045 #define isContentLength(line) (memcmp(line,"Content-Length",14u) == 0u) 00046 #define isTransferEncoding(line) (memcmp(line,"Transfer-Encoding",17u) == 0u) 00047 #define isChunked(line) (memcmp(line," chunked",8u) == 0u) 00048 #define isNotHTTPv1(line) (memcmp(line,"HTTP/1.",7u)) 00049 #define is_hex_digit(x) ( ('0' <= x && x <= '9') || ('a' <= x && x <= 'f') ) 00050 #define hex_digit_to_dec(x) ( ('0' <= x && x <= '9') ? x-'0' : ( ('a' <= x && x <= 'f') ? x-'a' + 10 : -1) ) 00051 00052 struct pico_http_client 00053 { 00054 uint16_t connectionID; 00055 uint8_t state; 00056 struct pico_socket * sck; 00057 void (*wakeup)(uint16_t ev, uint16_t conn); 00058 struct pico_ip4 ip; 00059 struct pico_http_uri * uriKey; 00060 struct pico_http_header * header; 00061 }; 00062 00063 // HTTP Client internal states 00064 #define HTTP_READING_HEADER 0 00065 #define HTTP_READING_BODY 1 00066 #define HTTP_READING_CHUNK_VALUE 2 00067 #define HTTP_READING_CHUNK_TRAIL 3 00068 00069 00070 static int compareClients(void * ka, void * kb) 00071 { 00072 return ((struct pico_http_client *)ka)->connectionID - ((struct pico_http_client *)kb)->connectionID; 00073 } 00074 00075 PICO_TREE_DECLARE(pico_client_list,compareClients); 00076 00077 // Local functions 00078 int parseHeaderFromServer(struct pico_http_client * client, struct pico_http_header * header); 00079 int readChunkLine(struct pico_http_client * client); 00080 00081 void tcpCallback(uint16_t ev, struct pico_socket *s) 00082 { 00083 00084 struct pico_http_client * client = NULL; 00085 struct pico_tree_node * index; 00086 00087 // find httpClient 00088 pico_tree_foreach(index,&pico_client_list) 00089 { 00090 if( ((struct pico_http_client *)index->keyValue)->sck == s ) 00091 { 00092 client = (struct pico_http_client *)index->keyValue; 00093 break; 00094 } 00095 } 00096 00097 if(!client) 00098 { 00099 dbg("Client not found...Something went wrong !\n"); 00100 return; 00101 } 00102 00103 if(ev & PICO_SOCK_EV_CONN) 00104 client->wakeup(EV_HTTP_CON,client->connectionID); 00105 00106 if(ev & PICO_SOCK_EV_RD) 00107 { 00108 00109 // read the header, if not read 00110 if(client->state == HTTP_READING_HEADER) 00111 { 00112 // wait for header 00113 client->header = pico_zalloc(sizeof(struct pico_http_header)); 00114 if(!client->header) 00115 { 00116 pico_err = PICO_ERR_ENOMEM; 00117 return; 00118 } 00119 00120 // wait for header 00121 if(parseHeaderFromServer(client,client->header) < 0) 00122 { 00123 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00124 } 00125 else 00126 { 00127 // call wakeup 00128 if(client->header->responseCode != HTTP_CONTINUE) 00129 { 00130 client->wakeup( 00131 client->header->responseCode == HTTP_OK ? 00132 EV_HTTP_REQ | EV_HTTP_BODY : // data comes for sure only when 200 is received 00133 EV_HTTP_REQ 00134 ,client->connectionID); 00135 } 00136 } 00137 } 00138 else 00139 { 00140 // just let the user know that data has arrived, if chunked data comes, will be treated in the 00141 // read api. 00142 client->wakeup(EV_HTTP_BODY,client->connectionID); 00143 } 00144 } 00145 00146 if(ev & PICO_SOCK_EV_ERR) 00147 { 00148 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00149 } 00150 00151 if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) 00152 { 00153 client->wakeup(EV_HTTP_CLOSE,client->connectionID); 00154 } 00155 00156 } 00157 00158 // used for getting a response from DNS servers 00159 static void dnsCallback(char *ip, void * ptr) 00160 { 00161 struct pico_http_client * client = (struct pico_http_client *)ptr; 00162 00163 if(!client) 00164 { 00165 dbg("Who made the request ?!\n"); 00166 return; 00167 } 00168 00169 if(ip) 00170 { 00171 client->wakeup(EV_HTTP_DNS,client->connectionID); 00172 00173 // add the ip address to the client, and start a tcp connection socket 00174 pico_string_to_ipv4(ip,&client->ip.addr); 00175 pico_free(ip); 00176 client->sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcpCallback); 00177 if(!client->sck) 00178 { 00179 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00180 return; 00181 } 00182 00183 if(pico_socket_connect(client->sck,&client->ip,short_be(client->uriKey->port)) < 0) 00184 { 00185 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00186 return; 00187 } 00188 00189 } 00190 else 00191 { 00192 // wakeup client and let know error occured 00193 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00194 00195 // close the client (free used heap) 00196 pico_http_client_close(client->connectionID); 00197 } 00198 } 00199 00200 /* 00201 * API used for opening a new HTTP Client. 00202 * 00203 * The accepted uri's are [http://]hostname[:port]/resource 00204 * no relative uri's are accepted. 00205 * 00206 * The function returns a connection ID >= 0 if successful 00207 * -1 if an error occured. 00208 */ 00209 int pico_http_client_open(char * uri, void (*wakeup)(uint16_t ev, uint16_t conn)) 00210 { 00211 struct pico_http_client * client; 00212 00213 if(!wakeup) 00214 { 00215 pico_err = PICO_ERR_EINVAL; 00216 return HTTP_RETURN_ERROR; 00217 } 00218 00219 client = pico_zalloc(sizeof(struct pico_http_client)); 00220 if(!client) 00221 { 00222 // memory error 00223 pico_err = PICO_ERR_ENOMEM; 00224 return HTTP_RETURN_ERROR; 00225 } 00226 00227 client->wakeup = wakeup; 00228 client->connectionID = (uint16_t)pico_rand() & 0x7FFFu; // negative values mean error, still not good generation 00229 00230 client->uriKey = pico_zalloc(sizeof(struct pico_http_uri)); 00231 00232 if(!client->uriKey) 00233 { 00234 pico_err = PICO_ERR_ENOMEM; 00235 pico_free(client); 00236 return HTTP_RETURN_ERROR; 00237 } 00238 00239 pico_processURI(uri,client->uriKey); 00240 00241 if(pico_tree_insert(&pico_client_list,client)) 00242 { 00243 // already in 00244 pico_err = PICO_ERR_EEXIST; 00245 pico_free(client->uriKey); 00246 pico_free(client); 00247 return HTTP_RETURN_ERROR; 00248 } 00249 00250 // dns query 00251 dbg("Querying : %s \n",client->uriKey->host); 00252 pico_dns_client_getaddr(client->uriKey->host, dnsCallback,client); 00253 00254 // return the connection ID 00255 return client->connectionID; 00256 } 00257 00258 /* 00259 * API for sending a header to the client. 00260 * 00261 * if hdr == HTTP_HEADER_RAW , then the parameter header 00262 * is sent as it is to client. 00263 * 00264 * if hdr == HTTP_HEADER_DEFAULT, then the parameter header 00265 * is ignored and the library will build the response header 00266 * based on the uri passed when opening the client. 00267 * 00268 */ 00269 int pico_http_client_sendHeader(uint16_t conn, char * header, int hdr) 00270 { 00271 struct pico_http_client search = {.connectionID = conn}; 00272 struct pico_http_client * http = pico_tree_findKey(&pico_client_list,&search); 00273 int length ; 00274 if(!http) 00275 { 00276 dbg("Client not found !\n"); 00277 return HTTP_RETURN_ERROR; 00278 } 00279 00280 // the api gives the possibility to the user to build the GET header 00281 // based on the uri passed when opening the client, less headache for the user 00282 if(hdr == HTTP_HEADER_DEFAULT) 00283 { 00284 header = pico_http_client_buildHeader(http->uriKey); 00285 00286 if(!header) 00287 { 00288 pico_err = PICO_ERR_ENOMEM; 00289 return HTTP_RETURN_ERROR; 00290 } 00291 } 00292 00293 length = pico_socket_write(http->sck,(void *)header,strlen(header)+1); 00294 00295 if(hdr == HTTP_HEADER_DEFAULT) 00296 pico_free(header); 00297 00298 return length; 00299 } 00300 00301 /* 00302 * API for reading received data. 00303 * 00304 * This api hides from the user if the transfer-encoding 00305 * was chunked or a full length was provided, in case of 00306 * a chunked transfer encoding will "de-chunk" the data 00307 * and pass it to the user. 00308 */ 00309 int pico_http_client_readData(uint16_t conn, char * data, uint16_t size) 00310 { 00311 struct pico_http_client dummy = {.connectionID = conn}; 00312 struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); 00313 00314 if(!client) 00315 { 00316 dbg("Wrong connection id !\n"); 00317 pico_err = PICO_ERR_EINVAL; 00318 return HTTP_RETURN_ERROR; 00319 } 00320 00321 // for the moment just read the data, do not care if it's chunked or not 00322 if(client->header->transferCoding == HTTP_TRANSFER_FULL) 00323 return pico_socket_read(client->sck,(void *)data,size); 00324 else 00325 { 00326 int lenRead = 0; 00327 00328 // read the chunk line 00329 if(readChunkLine(client) == HTTP_RETURN_ERROR) 00330 { 00331 dbg("Probably the chunk is malformed or parsed wrong...\n"); 00332 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00333 return HTTP_RETURN_ERROR; 00334 } 00335 00336 // nothing to read, no use to try 00337 if(client->state != HTTP_READING_BODY) 00338 { 00339 pico_err = PICO_ERR_EAGAIN; 00340 return HTTP_RETURN_OK; 00341 } 00342 00343 // check if we need more than one chunk 00344 if(size >= client->header->contentLengthOrChunk) 00345 { 00346 // read the rest of the chunk, if chunk is done, proceed to the next chunk 00347 while(lenRead <= size) 00348 { 00349 int tmpLenRead = 0; 00350 00351 if(client->state == HTTP_READING_BODY) 00352 { 00353 00354 // if needed truncate the data 00355 tmpLenRead = pico_socket_read(client->sck,data + lenRead, 00356 client->header->contentLengthOrChunk < size-lenRead ? client->header->contentLengthOrChunk : size-lenRead); 00357 00358 if(tmpLenRead > 0) 00359 { 00360 client->header->contentLengthOrChunk -= tmpLenRead; 00361 } 00362 else if(tmpLenRead < 0) 00363 { 00364 // error on reading 00365 dbg(">>> Error returned pico_socket_read\n"); 00366 pico_err = PICO_ERR_EBUSY; 00367 // return how much data was read until now 00368 return lenRead; 00369 } 00370 } 00371 00372 lenRead += tmpLenRead; 00373 if(readChunkLine(client) == HTTP_RETURN_ERROR) 00374 { 00375 dbg("Probably the chunk is malformed or parsed wrong...\n"); 00376 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00377 return HTTP_RETURN_ERROR; 00378 } 00379 00380 if(client->state != HTTP_READING_BODY || !tmpLenRead) break; 00381 00382 } 00383 } 00384 else 00385 { 00386 // read the data from the chunk 00387 lenRead = pico_socket_read(client->sck,(void *)data,size); 00388 00389 if(lenRead) 00390 client->header->contentLengthOrChunk -= lenRead; 00391 } 00392 00393 return lenRead; 00394 } 00395 } 00396 00397 /* 00398 * API for reading received data. 00399 * 00400 * Reads out the header struct received from server. 00401 */ 00402 struct pico_http_header * pico_http_client_readHeader(uint16_t conn) 00403 { 00404 struct pico_http_client dummy = {.connectionID = conn}; 00405 struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); 00406 00407 if(client) 00408 { 00409 return client->header; 00410 } 00411 else 00412 { 00413 // not found 00414 dbg("Wrong connection id !\n"); 00415 pico_err = PICO_ERR_EINVAL; 00416 return NULL; 00417 } 00418 } 00419 00420 /* 00421 * API for reading received data. 00422 * 00423 * Reads out the uri struct after was processed. 00424 */ 00425 struct pico_http_uri * pico_http_client_readUriData(uint16_t conn) 00426 { 00427 struct pico_http_client dummy = {.connectionID = conn}; 00428 struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); 00429 // 00430 if(client) 00431 return client->uriKey; 00432 else 00433 { 00434 // not found 00435 dbg("Wrong connection id !\n"); 00436 pico_err = PICO_ERR_EINVAL; 00437 return NULL; 00438 } 00439 } 00440 00441 /* 00442 * API for reading received data. 00443 * 00444 * Close the client. 00445 */ 00446 int pico_http_client_close(uint16_t conn) 00447 { 00448 struct pico_http_client * toBeRemoved = NULL; 00449 struct pico_http_client dummy = {}; 00450 dummy.connectionID = conn; 00451 00452 dbg("Closing the client...\n"); 00453 toBeRemoved = pico_tree_delete(&pico_client_list,&dummy); 00454 if(!toBeRemoved) 00455 { 00456 dbg("Warning ! Element not found ..."); 00457 return HTTP_RETURN_ERROR; 00458 } 00459 00460 // close socket 00461 if(toBeRemoved->sck) 00462 pico_socket_close(toBeRemoved->sck); 00463 00464 00465 if(toBeRemoved->header) 00466 { 00467 // free space used 00468 if(toBeRemoved->header->location) 00469 pico_free(toBeRemoved->header->location); 00470 00471 pico_free(toBeRemoved->header); 00472 } 00473 00474 if(toBeRemoved->uriKey) 00475 { 00476 if(toBeRemoved->uriKey->host) 00477 pico_free(toBeRemoved->uriKey->host); 00478 00479 if(toBeRemoved->uriKey->resource) 00480 pico_free(toBeRemoved->uriKey->resource); 00481 pico_free(toBeRemoved->uriKey); 00482 } 00483 pico_free(toBeRemoved); 00484 00485 return 0; 00486 } 00487 00488 /* 00489 * API for reading received data. 00490 * 00491 * Builds a GET header based on the fields on the uri. 00492 */ 00493 char * pico_http_client_buildHeader(const struct pico_http_uri * uriData) 00494 { 00495 char * header; 00496 char port[6u]; // 6 = max length of a uint16 + \0 00497 00498 uint16_t headerSize = HTTP_GET_BASIC_SIZE; 00499 00500 if(!uriData->host || !uriData->resource || !uriData->port) 00501 { 00502 pico_err = PICO_ERR_EINVAL; 00503 return NULL; 00504 } 00505 00506 // 00507 headerSize += strlen(uriData->host) + strlen(uriData->resource) + pico_itoa(uriData->port,port) + 4u; // 3 = size(CRLF + \0) 00508 header = pico_zalloc(headerSize); 00509 00510 if(!header) 00511 { 00512 // not enought memory 00513 pico_err = PICO_ERR_ENOMEM; 00514 return NULL; 00515 } 00516 00517 // build the actual header 00518 strcpy(header,"GET "); 00519 strcat(header,uriData->resource); 00520 strcat(header," HTTP/1.1\r\n"); 00521 strcat(header,"Host: "); 00522 strcat(header,uriData->host); 00523 strcat(header,":"); 00524 strcat(header,port); 00525 strcat(header,"\r\n"); 00526 strcat(header,"User-Agent: picoTCP\r\nConnection: close\r\n\r\n"); //? 00527 00528 return header; 00529 } 00530 00531 int parseHeaderFromServer(struct pico_http_client * client, struct pico_http_header * header) 00532 { 00533 char line[HTTP_HEADER_LINE_SIZE]; 00534 char c; 00535 int index = 0; 00536 00537 // read the first line of the header 00538 while(consumeChar(c)>0 && c!='\r') 00539 { 00540 if(index < HTTP_HEADER_LINE_SIZE) // truncate if too long 00541 line[index++] = c; 00542 } 00543 00544 consumeChar(c); // consume \n 00545 00546 // check the integrity of the response 00547 // make sure we have enough characters to include the response code 00548 // make sure the server response starts with HTTP/1. 00549 if(index < RESPONSE_INDEX+2 || isNotHTTPv1(line)) 00550 { 00551 // wrong format of the the response 00552 pico_err = PICO_ERR_EINVAL; 00553 return HTTP_RETURN_ERROR; 00554 } 00555 00556 // extract response code 00557 header->responseCode = (line[RESPONSE_INDEX] - '0') * 100u + 00558 (line[RESPONSE_INDEX+1] - '0') * 10u + 00559 (line[RESPONSE_INDEX+2] - '0'); 00560 00561 00562 if(header->responseCode/100u > 5u) 00563 { 00564 // invalid response type 00565 header->responseCode = 0; 00566 return HTTP_RETURN_ERROR; 00567 } 00568 00569 dbg("Server response : %d \n",header->responseCode); 00570 00571 // parse the rest of the header 00572 while(consumeChar(c)>0) 00573 { 00574 if(c==':') 00575 { 00576 // check for interesting fields 00577 00578 // Location: 00579 if(isLocation(line)) 00580 { 00581 index = 0; 00582 while(consumeChar(c)>0 && c!='\r') 00583 { 00584 line[index++] = c; 00585 } 00586 00587 // allocate space for the field 00588 header->location = pico_zalloc(index+1u); 00589 if(!header->location) 00590 { 00591 pico_err = PICO_ERR_ENOMEM; 00592 return HTTP_RETURN_ERROR; 00593 } 00594 00595 memcpy(header->location,line,index); 00596 00597 }// Content-Length: 00598 else if(isContentLength(line)) 00599 { 00600 header->contentLengthOrChunk = 0u; 00601 header->transferCoding = HTTP_TRANSFER_FULL; 00602 // consume the first space 00603 consumeChar(c); 00604 while(consumeChar(c)>0 && c!='\r') 00605 { 00606 header->contentLengthOrChunk = header->contentLengthOrChunk*10u + (c-'0'); 00607 } 00608 00609 }// Transfer-Encoding: chunked 00610 else if(isTransferEncoding(line)) 00611 { 00612 index = 0; 00613 while(consumeChar(c)>0 && c!='\r') 00614 { 00615 line[index++] = c; 00616 } 00617 00618 if(isChunked(line)) 00619 { 00620 header->contentLengthOrChunk = 0u; 00621 header->transferCoding = HTTP_TRANSFER_CHUNKED; 00622 } 00623 00624 }// just ignore the line 00625 else 00626 { 00627 while(consumeChar(c)>0 && c!='\r'); 00628 } 00629 00630 // consume the next one 00631 consumeChar(c); 00632 // reset the index 00633 index = 0u; 00634 } 00635 else if(c=='\r' && !index) 00636 { 00637 // consume the \n 00638 consumeChar(c); 00639 break; 00640 } 00641 else 00642 { 00643 line[index++] = c; 00644 } 00645 } 00646 00647 if(header->transferCoding == HTTP_TRANSFER_CHUNKED) 00648 { 00649 // read the first chunk 00650 header->contentLengthOrChunk = 0; 00651 00652 client->state = HTTP_READING_CHUNK_VALUE; 00653 readChunkLine(client); 00654 00655 } 00656 else 00657 client->state = HTTP_READING_BODY; 00658 00659 dbg("End of header\n"); 00660 return HTTP_RETURN_OK; 00661 00662 } 00663 00664 // an async read of the chunk part, since in theory a chunk can be split in 2 packets 00665 int readChunkLine(struct pico_http_client * client) 00666 { 00667 char c = 0; 00668 00669 if(client->header->contentLengthOrChunk==0 && client->state == HTTP_READING_BODY) 00670 { 00671 client->state = HTTP_READING_CHUNK_VALUE; 00672 } 00673 00674 if(client->state == HTTP_READING_CHUNK_VALUE) 00675 { 00676 while(consumeChar(c)>0 && c!='\r' && c!=';') 00677 { 00678 if(is_hex_digit(c)) 00679 client->header->contentLengthOrChunk = (client->header->contentLengthOrChunk << 4u) + hex_digit_to_dec(c); 00680 else 00681 { 00682 pico_err = PICO_ERR_EINVAL; 00683 // something went wrong 00684 return HTTP_RETURN_ERROR; 00685 } 00686 } 00687 00688 if(c=='\r' || c==';') client->state = HTTP_READING_CHUNK_TRAIL; 00689 } 00690 00691 if(client->state == HTTP_READING_CHUNK_TRAIL) 00692 { 00693 00694 while(consumeChar(c)>0 && c!='\n'); 00695 00696 if(c=='\n') client->state = HTTP_READING_BODY; 00697 } 00698 00699 return HTTP_RETURN_OK; 00700 } 00701 #endif
Generated on Thu Jul 14 2022 08:24:58 by
1.7.2
