HTTP Client library

Dependents:   weather_LCD_display News_LCD_display TwitterExample_1 GeoLocation_LCD_Display ... more

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers HTTPClient.cpp Source File

HTTPClient.cpp

00001 /* HTTPClient.cpp */
00002 /*
00003 Copyright (C) 2012 ARM Limited.
00004 
00005 Permission is hereby granted, free of charge, to any person obtaining a copy of
00006 this software and associated documentation files (the "Software"), to deal in
00007 the Software without restriction, including without limitation the rights to
00008 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
00009 of the Software, and to permit persons to whom the Software is furnished to do
00010 so, subject to the following conditions:
00011 
00012 The above copyright notice and this permission notice shall be included in all
00013 copies or substantial portions of the Software.
00014 
00015 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00016 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00017 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00018 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00019 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00020 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00021 SOFTWARE.
00022 */
00023 
00024 #define __DEBUG__ 4 //Maximum verbosity
00025 #ifndef __MODULE__
00026 #define __MODULE__ "HTTPClient.cpp"
00027 #endif
00028 
00029 #include "core/fwk.h"
00030 
00031 #include "HTTPClient.h"
00032 
00033 #define HTTP_REQUEST_TIMEOUT 30000
00034 #define HTTP_PORT 80
00035 
00036 #define CHUNK_SIZE 256
00037 
00038 #include <cstring>
00039 
00040 HTTPClient::HTTPClient() :
00041 m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0)
00042 {
00043 
00044 }
00045 
00046 HTTPClient::~HTTPClient()
00047 {
00048 
00049 }
00050 
00051 #if 0
00052 void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification
00053 {
00054   m_basicAuthUser = user;
00055   m_basicAuthPassword = password;
00056 }
00057 #endif
00058 
00059 int HTTPClient::get(const char* url, IHTTPDataIn* pDataIn, uint32_t timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
00060 {
00061   return connect(url, HTTP_GET, NULL, pDataIn, timeout);
00062 }
00063 
00064 int HTTPClient::get(const char* url, char* result, size_t maxResultLen, uint32_t timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
00065 {
00066   HTTPText str(result, maxResultLen);
00067   return get(url, &str, timeout);
00068 }
00069 
00070 int HTTPClient::post(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, uint32_t timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
00071 {
00072   return connect(url, HTTP_POST, (IHTTPDataOut*)&dataOut, pDataIn, timeout);
00073 }
00074 
00075 int HTTPClient::getHTTPResponseCode()
00076 {
00077   return m_httpResponseCode;
00078 }
00079 
00080 
00081 int HTTPClient::connect(const char* url, HTTP_METH method, IHTTPDataOut* pDataOut, IHTTPDataIn* pDataIn, uint32_t timeout) //Execute request
00082 {
00083   m_httpResponseCode = 0; //Invalidate code
00084   m_timeout = timeout;
00085 
00086   char scheme[8];
00087   uint16_t port;
00088   char host[32];
00089   char path[64];
00090   //First we need to parse the url (http[s]://host[:port][/[path]]) -- HTTPS not supported (yet?)
00091   int ret = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
00092   if(ret != OK)
00093   {
00094     ERR("parseURL returned %d", ret);
00095     return ret;
00096   }
00097 
00098   if(port == 0) //TODO do handle HTTPS->443
00099   {
00100     port = 80;
00101   }
00102 
00103   DBG("Scheme: %s", scheme);
00104   DBG("Host: %s", host);
00105   DBG("Port: %d", port);
00106   DBG("Path: %s", path);
00107 
00108   //Now populate structure
00109   std::memset(&m_serverAddr, 0, sizeof(struct sockaddr_in));
00110 
00111   //Resolve DNS if needed
00112 
00113   DBG("Resolving DNS address or populate hard-coded IP address");
00114   struct hostent *server = socket::gethostbyname(host);
00115   if(server == NULL)
00116   {
00117     return NET_NOTFOUND; //Fail
00118   }
00119   memcpy((char*)&m_serverAddr.sin_addr.s_addr, (char*)server->h_addr_list[0], server->h_length);
00120 
00121   m_serverAddr.sin_family = AF_INET;
00122   m_serverAddr.sin_port = htons(port);
00123 
00124   //Create socket
00125   DBG("Creating socket");
00126   m_sock = socket::socket(AF_INET, SOCK_STREAM, 0); //UDP socket
00127   if (m_sock < 0)
00128   {
00129     ERR("Could not create socket");
00130     return NET_OOM;
00131   }
00132   DBG("Handle is %d", m_sock);
00133 
00134   //Connect it
00135   DBG("Connecting socket to %s:%d", inet_ntoa(m_serverAddr.sin_addr), ntohs(m_serverAddr.sin_port));
00136   ret = socket::connect(m_sock, (const struct sockaddr *)&m_serverAddr, sizeof(m_serverAddr));
00137   if (ret < 0)
00138   {
00139     socket::close(m_sock);
00140     ERR("Could not connect");
00141     return NET_CONN;
00142   }
00143 
00144   //Send request
00145   DBG("Sending request");
00146   char line[128];
00147   const char* meth = (method==HTTP_GET)?"GET":(method==HTTP_POST)?"POST":"";
00148   snprintf(line, sizeof(line), "%s %s HTTP/1.1\r\nHost: %s\r\n", meth, path, host); //Write request
00149   ret = send(line);
00150   if(ret)
00151   {
00152     socket::close(m_sock);
00153     ERR("Could not write request");
00154     return NET_CONN;
00155   }
00156 
00157   //Send all headers
00158 
00159   //Send default headers
00160   DBG("Sending headers");
00161   if( (method == HTTP_POST) && (pDataOut != NULL) )
00162   {
00163     if( pDataOut->getIsChunked() )
00164     {
00165       ret = send("Transfer-Encoding: chunked\r\n");
00166       if(ret != OK) goto connerr;
00167     }
00168     else
00169     {
00170       snprintf(line, sizeof(line), "Content-Length: %d\r\n", pDataOut->getDataLen());
00171       ret = send(line);
00172       if(ret != OK) goto connerr;
00173     }
00174     char type[48];
00175     if( pDataOut->getDataType(type, 48) == OK )
00176     {
00177       snprintf(line, sizeof(line), "Content-Type: %s\r\n", type);
00178       ret = send(line);
00179       if(ret != OK) goto connerr;
00180     }
00181   }
00182 
00183   //Close headers
00184   DBG("Headers sent");
00185   ret = send("\r\n");
00186   if(ret != OK) goto connerr;
00187 
00188   char buf[CHUNK_SIZE];
00189   size_t trfLen;
00190 
00191   //Send data (if POST)
00192   if( (method == HTTP_POST) && (pDataOut != NULL) )
00193   {
00194     DBG("Sending data");
00195     while(true)
00196     {
00197       size_t writtenLen = 0;
00198       pDataOut->read(buf, CHUNK_SIZE, &trfLen);
00199       if( pDataOut->getIsChunked() )
00200       {
00201         //Write chunk header
00202         snprintf(line, sizeof(line), "%X\r\n", trfLen); //In hex encoding
00203         ret = send(line);
00204         if(ret != OK) goto connerr;
00205       }
00206       else if( trfLen == 0 )
00207       {
00208         break;
00209       }
00210       if( trfLen != 0 )
00211       {
00212         ret = send(buf, trfLen);
00213         if(ret != OK) goto connerr;
00214       }
00215 
00216       if( pDataOut->getIsChunked()  )
00217       {
00218         ret = send("\r\n"); //Chunk-terminating CRLF
00219         if(ret != OK) goto connerr;
00220       }
00221       else
00222       {
00223         writtenLen += trfLen;
00224         if( writtenLen >= pDataOut->getDataLen() )
00225         {
00226           break;
00227         }
00228       }
00229 
00230       if( trfLen == 0 )
00231       {
00232         break;
00233       }
00234     }
00235 
00236   }
00237 
00238   //Receive response
00239   DBG("Receiving response");
00240   ret = recv(buf, CHUNK_SIZE, CHUNK_SIZE, &trfLen); //Read n bytes
00241   if(ret != OK) goto connerr;
00242 
00243   buf[trfLen] = '\0';
00244 
00245   char* crlfPtr = strstr(buf, "\r\n");
00246   if(crlfPtr == NULL)
00247   {
00248     goto prtclerr;
00249   }
00250 
00251   int crlfPos = crlfPtr - buf;
00252   buf[crlfPos] = '\0';
00253 
00254   //Parse HTTP response
00255   if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 )
00256   {
00257     //Cannot match string, error
00258     ERR("Not a correct HTTP answer : %s\n", buf);
00259     goto prtclerr;
00260   }
00261 
00262   if(m_httpResponseCode != 200)
00263   {
00264     //Cannot match string, error
00265     WARN("Response code %d", m_httpResponseCode);
00266     goto prtclerr;
00267   }
00268 
00269   DBG("Reading headers");
00270 
00271   memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2));
00272   trfLen -= (crlfPos + 2);
00273 
00274   size_t recvContentLength = 0;
00275   bool recvChunked = false;
00276   //Now get headers
00277   while( true )
00278   {
00279     crlfPtr = strstr(buf, "\r\n");
00280     if(crlfPtr == NULL)
00281     {
00282       if( trfLen < CHUNK_SIZE )
00283       {
00284         size_t newTrfLen;
00285         ret = recv(buf + trfLen, 1, CHUNK_SIZE - trfLen - 1, &newTrfLen);
00286         trfLen += newTrfLen;
00287         buf[trfLen] = '\0';
00288         DBG("In buf: [%s]", buf);
00289         if(ret != OK) goto connerr;
00290         continue;
00291       }
00292       else
00293       {
00294         goto prtclerr;
00295       }
00296     }
00297 
00298     crlfPos = crlfPtr - buf;
00299 
00300     if(crlfPos == 0) //End of headers
00301     {
00302       DBG("Headers read");
00303       memmove(buf, &buf[2], trfLen - 2);
00304       trfLen -= 2;
00305       break;
00306     }
00307 
00308     buf[crlfPos] = '\0';
00309 
00310     char key[16];
00311     char value[16];
00312 
00313     int n = sscanf(buf, "%16[^:]: %16[^\r\n]", key, value);
00314     if ( n == 2 )
00315     {
00316       DBG("Read header : %s: %s\n", key, value);
00317       if( !strcmp(key, "Content-Length") )
00318       {
00319         sscanf(value, "%d", &recvContentLength);
00320         pDataIn->setDataLen(recvContentLength);
00321       }
00322       else if( !strcmp(key, "Transfer-Encoding") )
00323       {
00324         if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") )
00325         {
00326           recvChunked = true;
00327           pDataIn->setIsChunked(true);
00328         }
00329       }
00330       else if( !strcmp(key, "Content-Type") )
00331       {
00332         pDataIn->setDataType(value);
00333       }
00334 
00335       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2));
00336       trfLen -= (crlfPos + 2);
00337 
00338     }
00339     else
00340     {
00341       ERR("Could not parse header");
00342       goto prtclerr;
00343     }
00344 
00345   }
00346 
00347   //Receive data
00348   DBG("Receiving data");
00349   while(true)
00350   {
00351     size_t readLen = 0;
00352 
00353     if( recvChunked )
00354     {
00355       //Read chunk header
00356       crlfPos=0;
00357       for(crlfPos++; crlfPos < trfLen - 2; crlfPos++)
00358       {
00359         if( buf[crlfPos] == '\r' && buf[crlfPos + 1] == '\n' )
00360         {
00361           break;
00362         }
00363       }
00364       if(crlfPos >= trfLen - 2) //Try to read more
00365       {
00366         if( trfLen < CHUNK_SIZE )
00367         {
00368           size_t newTrfLen;
00369           ret = recv(buf + trfLen, 0, CHUNK_SIZE - trfLen - 1, &newTrfLen);
00370           trfLen += newTrfLen;
00371           if(ret != OK) goto connerr;
00372           continue;
00373         }
00374         else
00375         {
00376           goto prtclerr;
00377         }
00378       }
00379       buf[crlfPos] = '\0';
00380       int n = sscanf(buf, "%x", &readLen);
00381       if(n!=1)
00382       {
00383         ERR("Could not read chunk length");
00384         goto prtclerr;
00385       }
00386 
00387       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2));
00388       trfLen -= (crlfPos + 2);
00389 
00390       if( readLen == 0 )
00391       {
00392         //Last chunk
00393         break;
00394       }
00395     }
00396     else
00397     {
00398       readLen = recvContentLength;
00399     }
00400 
00401     DBG("Retrieving %d bytes", readLen);
00402 
00403     do
00404     {
00405       pDataIn->write(buf, MIN(trfLen, readLen));
00406       if( trfLen > readLen )
00407       {
00408         memmove(buf, &buf[readLen], trfLen - readLen);
00409         trfLen -= readLen;
00410         readLen = 0;
00411       }
00412       else
00413       {
00414         readLen -= trfLen;
00415       }
00416 
00417       if(readLen)
00418       {
00419         ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen);
00420         if(ret != OK) goto connerr;
00421 
00422       }
00423     } while(readLen);
00424 
00425     if( recvChunked )
00426     {
00427       if(trfLen < 2)
00428       {
00429         size_t newTrfLen;
00430         //Read missing chars to find end of chunk
00431         ret = recv(buf, 2 - trfLen, CHUNK_SIZE, &newTrfLen);
00432         if(ret != OK) goto connerr;
00433         trfLen += newTrfLen;
00434       }
00435       if( (buf[0] != '\r') || (buf[1] != '\n') )
00436       {
00437         ERR("Format error");
00438         goto prtclerr;
00439       }
00440       memmove(buf, &buf[2], trfLen - 2);
00441       trfLen -= 2;
00442     }
00443     else
00444     {
00445       break;
00446     }
00447 
00448   }
00449 
00450   socket::close(m_sock);
00451   DBG("Completed HTTP transaction");
00452 
00453   return OK;
00454 
00455   connerr:
00456     socket::close(m_sock);
00457     ERR("Connection error (%d)", ret);
00458   return NET_CONN;
00459 
00460   prtclerr:
00461     socket::close(m_sock);
00462     ERR("Protocol error");
00463   return NET_PROTOCOL;
00464 
00465 }
00466 
00467 int HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure
00468 {
00469   DBG("Trying to read between %d and %d bytes", minLen, maxLen);
00470   size_t readLen = 0;
00471   while(readLen < minLen)
00472   {
00473     //Wait for socket to be readable
00474     //Creating FS set
00475     fd_set socksSet;
00476     FD_ZERO(&socksSet);
00477     FD_SET(m_sock, &socksSet);
00478     struct timeval t_val;
00479     t_val.tv_sec = m_timeout / 1000;
00480     t_val.tv_usec = (m_timeout - (t_val.tv_sec * 1000)) * 1000;
00481     int ret = socket::select(FD_SETSIZE, &socksSet, NULL, NULL, &t_val);
00482     if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
00483     {
00484       WARN("Timeout");
00485       return NET_TIMEOUT; //Timeout
00486     }
00487 
00488     ret = socket::recv(m_sock, buf + readLen, maxLen - readLen, 0);
00489     if( ret > 0)
00490     {
00491       readLen += ret;
00492       continue;
00493     }
00494     else if( ret == 0 )
00495     {
00496       WARN("Connection was closed by server");
00497       return NET_CLOSED; //Connection was closed by server
00498     }
00499     else
00500     {
00501       ERR("Connection error (recv returned %d)", ret);
00502       return NET_CONN;
00503     }
00504   }
00505   *pReadLen = readLen;
00506   DBG("Read %d bytes", readLen);
00507   return OK;
00508 }
00509 
00510 int HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure
00511 {
00512   if(len == 0)
00513   {
00514     len = strlen(buf);
00515   }
00516   DBG("Trying to write %d bytes", len);
00517   size_t writtenLen = 0;
00518   while(writtenLen < len)
00519   {
00520     //Wait for socket to be writeable
00521     //Creating FS set
00522     fd_set socksSet;
00523     FD_ZERO(&socksSet);
00524     FD_SET(m_sock, &socksSet);
00525     struct timeval t_val;
00526     t_val.tv_sec = m_timeout / 1000;
00527     t_val.tv_usec = (m_timeout - (t_val.tv_sec * 1000)) * 1000;
00528     int ret = socket::select(FD_SETSIZE, NULL, &socksSet, NULL, &t_val);
00529     if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
00530     {
00531       WARN("Timeout");
00532       return NET_TIMEOUT; //Timeout
00533     }
00534 
00535     ret = socket::send(m_sock, buf + writtenLen, len - writtenLen, 0);
00536     if( ret > 0)
00537     {
00538       writtenLen += ret;
00539       continue;
00540     }
00541     else if( ret == 0 )
00542     {
00543       WARN("Connection was closed by server");
00544       return NET_CLOSED; //Connection was closed by server
00545     }
00546     else
00547     {
00548       ERR("Connection error (recv returned %d)", ret);
00549       return NET_CONN;
00550     }
00551   }
00552   DBG("Written %d bytes", writtenLen);
00553   return OK;
00554 }
00555 
00556 int HTTPClient::parseURL(const char* url, char* scheme, size_t maxSchemeLen, char* host, size_t maxHostLen, uint16_t* port, char* path, size_t maxPathLen) //Parse URL
00557 {
00558   char* schemePtr = (char*) url;
00559   char* hostPtr = (char*) strstr(url, "://");
00560   if(hostPtr == NULL)
00561   {
00562     WARN("Could not find host");
00563     return NET_INVALID; //URL is invalid
00564   }
00565 
00566   if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
00567   {
00568     WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
00569     return NET_TOOSMALL;
00570   }
00571   memcpy(scheme, schemePtr, hostPtr - schemePtr);
00572   scheme[hostPtr - schemePtr] = '\0';
00573 
00574   hostPtr+=3;
00575 
00576   size_t hostLen = 0;
00577 
00578   char* portPtr = strchr(hostPtr, ':');
00579   if( portPtr != NULL )
00580   {
00581     hostLen = portPtr - hostPtr;
00582     portPtr++;
00583     if( sscanf(portPtr, "%d", &port) != 1)
00584     {
00585       WARN("Could not find port");
00586       return NET_INVALID;
00587     }
00588   }
00589   else
00590   {
00591     *port=0;
00592   }
00593   char* pathPtr = strchr(hostPtr, '/');
00594   if( hostLen == 0 )
00595   {
00596     hostLen = pathPtr - hostPtr;
00597   }
00598 
00599   if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
00600   {
00601     WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
00602     return NET_TOOSMALL;
00603   }
00604   memcpy(host, hostPtr, hostLen);
00605   host[hostLen] = '\0';
00606 
00607   size_t pathLen;
00608   char* fragmentPtr = strchr(hostPtr, '#');
00609   if(fragmentPtr != NULL)
00610   {
00611     pathLen = fragmentPtr - pathPtr;
00612   }
00613   else
00614   {
00615     pathLen = strlen(pathPtr);
00616   }
00617 
00618   if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
00619   {
00620     WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
00621     return NET_TOOSMALL;
00622   }
00623   memcpy(path, pathPtr, pathLen);
00624   path[pathLen] = '\0';
00625 
00626   return OK;
00627 }
00628