CDC/ECM driver for mbed, based on USBDevice by mbed-official. Uses PicoTCP to access Ethernet USB device. License: GPLv2

Dependents:   USBEthernet_TEST

Fork of USB_Ethernet by Daniele Lacamera

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers pico_http_client.c Source File

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