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 - 1, CHUNK_SIZE - 1, &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) + 1); //Be sure to move NULL-terminating char as well
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 - 1 )
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("Read %d chars; In buf: [%s]", newTrfLen, 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 + 1); //Be sure to move NULL-terminating char as well
00304       trfLen -= 2;
00305       break;
00306     }
00307 
00308     buf[crlfPos] = '\0';
00309 
00310     char key[32];
00311     char value[32];
00312 
00313     key[31] = '\0';
00314     value[31] = '\0';
00315 
00316     int n = sscanf(buf, "%31[^:]: %31[^\r\n]", key, value);
00317     if ( n == 2 )
00318     {
00319       DBG("Read header : %s: %s\n", key, value);
00320       if( !strcmp(key, "Content-Length") )
00321       {
00322         sscanf(value, "%d", &recvContentLength);
00323         pDataIn->setDataLen(recvContentLength);
00324       }
00325       else if( !strcmp(key, "Transfer-Encoding") )
00326       {
00327         if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") )
00328         {
00329           recvChunked = true;
00330           pDataIn->setIsChunked(true);
00331         }
00332       }
00333       else if( !strcmp(key, "Content-Type") )
00334       {
00335         pDataIn->setDataType(value);
00336       }
00337 
00338       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
00339       trfLen -= (crlfPos + 2);
00340 
00341     }
00342     else
00343     {
00344       ERR("Could not parse header");
00345       goto prtclerr;
00346     }
00347 
00348   }
00349 
00350   //Receive data
00351   DBG("Receiving data");
00352   while(true)
00353   {
00354     size_t readLen = 0;
00355 
00356     if( recvChunked )
00357     {
00358       //Read chunk header
00359       crlfPos=0;
00360       for(crlfPos++; crlfPos < trfLen - 2; crlfPos++)
00361       {
00362         if( buf[crlfPos] == '\r' && buf[crlfPos + 1] == '\n' )
00363         {
00364           break;
00365         }
00366       }
00367       if(crlfPos >= trfLen - 2) //Try to read more
00368       {
00369         if( trfLen < CHUNK_SIZE )
00370         {
00371           size_t newTrfLen;
00372           ret = recv(buf + trfLen, 0, CHUNK_SIZE - trfLen - 1, &newTrfLen);
00373           trfLen += newTrfLen;
00374           if(ret != OK) goto connerr;
00375           continue;
00376         }
00377         else
00378         {
00379           goto prtclerr;
00380         }
00381       }
00382       buf[crlfPos] = '\0';
00383       int n = sscanf(buf, "%x", &readLen);
00384       if(n!=1)
00385       {
00386         ERR("Could not read chunk length");
00387         goto prtclerr;
00388       }
00389 
00390       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2)); //Not need to move NULL-terminating char any more
00391       trfLen -= (crlfPos + 2);
00392 
00393       if( readLen == 0 )
00394       {
00395         //Last chunk
00396         break;
00397       }
00398     }
00399     else
00400     {
00401       readLen = recvContentLength;
00402     }
00403 
00404     DBG("Retrieving %d bytes", readLen);
00405 
00406     do
00407     {
00408       pDataIn->write(buf, MIN(trfLen, readLen));
00409       if( trfLen > readLen )
00410       {
00411         memmove(buf, &buf[readLen], trfLen - readLen);
00412         trfLen -= readLen;
00413         readLen = 0;
00414       }
00415       else
00416       {
00417         readLen -= trfLen;
00418       }
00419 
00420       if(readLen)
00421       {
00422         ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen);
00423         if(ret != OK) goto connerr;
00424       }
00425     } while(readLen);
00426 
00427     if( recvChunked )
00428     {
00429       if(trfLen < 2)
00430       {
00431         size_t newTrfLen;
00432         //Read missing chars to find end of chunk
00433         ret = recv(buf, 2 - trfLen, CHUNK_SIZE, &newTrfLen);
00434         if(ret != OK) goto connerr;
00435         trfLen += newTrfLen;
00436       }
00437       if( (buf[0] != '\r') || (buf[1] != '\n') )
00438       {
00439         ERR("Format error");
00440         goto prtclerr;
00441       }
00442       memmove(buf, &buf[2], trfLen - 2);
00443       trfLen -= 2;
00444     }
00445     else
00446     {
00447       break;
00448     }
00449 
00450   }
00451 
00452   socket::close(m_sock);
00453   DBG("Completed HTTP transaction");
00454 
00455   return OK;
00456 
00457   connerr:
00458     socket::close(m_sock);
00459     ERR("Connection error (%d)", ret);
00460   return NET_CONN;
00461 
00462   prtclerr:
00463     socket::close(m_sock);
00464     ERR("Protocol error");
00465   return NET_PROTOCOL;
00466 
00467 }
00468 
00469 int HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure
00470 {
00471   DBG("Trying to read between %d and %d bytes", minLen, maxLen);
00472   size_t readLen = 0;
00473   while(readLen < minLen)
00474   {
00475     //Wait for socket to be readable
00476     //Creating FS set
00477     fd_set socksSet;
00478     FD_ZERO(&socksSet);
00479     FD_SET(m_sock, &socksSet);
00480     struct timeval t_val;
00481     t_val.tv_sec = m_timeout / 1000;
00482     t_val.tv_usec = (m_timeout - (t_val.tv_sec * 1000)) * 1000;
00483     int ret = socket::select(FD_SETSIZE, &socksSet, NULL, NULL, &t_val);
00484     if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
00485     {
00486       WARN("Timeout");
00487       return NET_TIMEOUT; //Timeout
00488     }
00489 
00490     ret = socket::recv(m_sock, buf + readLen, maxLen - readLen, 0);
00491     if( ret > 0)
00492     {
00493       readLen += ret;
00494       continue;
00495     }
00496     else if( ret == 0 )
00497     {
00498       WARN("Connection was closed by server");
00499       return NET_CLOSED; //Connection was closed by server
00500     }
00501     else
00502     {
00503       ERR("Connection error (recv returned %d)", ret);
00504       return NET_CONN;
00505     }
00506   }
00507   *pReadLen = readLen;
00508   DBG("Read %d bytes", readLen);
00509   return OK;
00510 }
00511 
00512 int HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure
00513 {
00514   if(len == 0)
00515   {
00516     len = strlen(buf);
00517   }
00518   DBG("Trying to write %d bytes", len);
00519   size_t writtenLen = 0;
00520   while(writtenLen < len)
00521   {
00522     //Wait for socket to be writeable
00523     //Creating FS set
00524     fd_set socksSet;
00525     FD_ZERO(&socksSet);
00526     FD_SET(m_sock, &socksSet);
00527     struct timeval t_val;
00528     t_val.tv_sec = m_timeout / 1000;
00529     t_val.tv_usec = (m_timeout - (t_val.tv_sec * 1000)) * 1000;
00530     int ret = socket::select(FD_SETSIZE, NULL, &socksSet, NULL, &t_val);
00531     if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
00532     {
00533       WARN("Timeout");
00534       return NET_TIMEOUT; //Timeout
00535     }
00536 
00537     ret = socket::send(m_sock, buf + writtenLen, len - writtenLen, 0);
00538     if( ret > 0)
00539     {
00540       writtenLen += ret;
00541       continue;
00542     }
00543     else if( ret == 0 )
00544     {
00545       WARN("Connection was closed by server");
00546       return NET_CLOSED; //Connection was closed by server
00547     }
00548     else
00549     {
00550       ERR("Connection error (recv returned %d)", ret);
00551       return NET_CONN;
00552     }
00553   }
00554   DBG("Written %d bytes", writtenLen);
00555   return OK;
00556 }
00557 
00558 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
00559 {
00560   char* schemePtr = (char*) url;
00561   char* hostPtr = (char*) strstr(url, "://");
00562   if(hostPtr == NULL)
00563   {
00564     WARN("Could not find host");
00565     return NET_INVALID; //URL is invalid
00566   }
00567 
00568   if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
00569   {
00570     WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
00571     return NET_TOOSMALL;
00572   }
00573   memcpy(scheme, schemePtr, hostPtr - schemePtr);
00574   scheme[hostPtr - schemePtr] = '\0';
00575 
00576   hostPtr+=3;
00577 
00578   size_t hostLen = 0;
00579 
00580   char* portPtr = strchr(hostPtr, ':');
00581   if( portPtr != NULL )
00582   {
00583     hostLen = portPtr - hostPtr;
00584     portPtr++;
00585     if( sscanf(portPtr, "%hu", port) != 1)
00586     {
00587       WARN("Could not find port");
00588       return NET_INVALID;
00589     }
00590   }
00591   else
00592   {
00593     *port=0;
00594   }
00595   char* pathPtr = strchr(hostPtr, '/');
00596   if( hostLen == 0 )
00597   {
00598     hostLen = pathPtr - hostPtr;
00599   }
00600 
00601   if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
00602   {
00603     WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
00604     return NET_TOOSMALL;
00605   }
00606   memcpy(host, hostPtr, hostLen);
00607   host[hostLen] = '\0';
00608 
00609   size_t pathLen;
00610   char* fragmentPtr = strchr(hostPtr, '#');
00611   if(fragmentPtr != NULL)
00612   {
00613     pathLen = fragmentPtr - pathPtr;
00614   }
00615   else
00616   {
00617     pathLen = strlen(pathPtr);
00618   }
00619 
00620   if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
00621   {
00622     WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
00623     return NET_TOOSMALL;
00624   }
00625   memcpy(path, pathPtr, pathLen);
00626   path[pathLen] = '\0';
00627 
00628   return OK;
00629 }