HTTP Client Library

Dependents:   EthernetHTTPClientTest

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 #define CHECK_CONN_ERR(ret) \
00081   do{ \
00082     if(ret != OK) { \
00083       ::close(m_sock); \
00084       ERR("Connection error (%d)", ret); \
00085       return NET_CONN; \
00086     } \
00087   } while(0)
00088 
00089 #define PRTCL_ERR() \
00090   do{ \
00091     ::close(m_sock); \
00092     ERR("Protocol error"); \
00093     return NET_PROTOCOL; \
00094   } while(0)
00095 
00096 int HTTPClient::connect(const char* url, HTTP_METH method, IHTTPDataOut* pDataOut, IHTTPDataIn* pDataIn, uint32_t timeout) //Execute request
00097 {
00098   m_httpResponseCode = 0; //Invalidate code
00099   m_timeout = timeout;
00100 
00101   char scheme[8];
00102   uint16_t port;
00103   char host[32];
00104   char path[64];
00105   //First we need to parse the url (http[s]://host[:port][/[path]]) -- HTTPS not supported (yet?)
00106   int ret = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
00107   if(ret != OK)
00108   {
00109     ERR("parseURL returned %d", ret);
00110     return ret;
00111   }
00112 
00113   if(port == 0) //TODO do handle HTTPS->443
00114   {
00115     port = 80;
00116   }
00117 
00118   DBG("Scheme: %s", scheme);
00119   DBG("Host: %s", host);
00120   DBG("Port: %d", port);
00121   DBG("Path: %s", path);
00122 
00123   //Now populate structure
00124   std::memset(&m_serverAddr, 0, sizeof(struct sockaddr_in));
00125 
00126   //Resolve DNS if needed
00127 
00128   DBG("Resolving DNS address or populate hard-coded IP address");
00129   struct hostent *server = ::gethostbyname(host);
00130   if(server == NULL)
00131   {
00132     return NET_NOTFOUND; //Fail
00133   }
00134   memcpy((char*)&m_serverAddr.sin_addr.s_addr, (char*)server->h_addr_list[0], server->h_length);
00135 
00136   m_serverAddr.sin_family = AF_INET;
00137   m_serverAddr.sin_port = htons(port);
00138 
00139   //Create socket
00140   DBG("Creating socket");
00141   m_sock = ::socket(AF_INET, SOCK_STREAM, 0); //UDP socket
00142   if (m_sock < 0)
00143   {
00144     ERR("Could not create socket");
00145     return NET_OOM;
00146   }
00147   DBG("Handle is %d", m_sock);
00148 
00149   //Connect it
00150   DBG("Connecting socket to %s:%d", inet_ntoa(m_serverAddr.sin_addr), ntohs(m_serverAddr.sin_port));
00151   ret = ::connect(m_sock, (const struct sockaddr *)&m_serverAddr, sizeof(m_serverAddr));
00152   if (ret < 0)
00153   {
00154     ::close(m_sock);
00155     ERR("Could not connect");
00156     return NET_CONN;
00157   }
00158 
00159   //Send request
00160   DBG("Sending request");
00161   char line[128];
00162   const char* meth = (method==HTTP_GET)?"GET":(method==HTTP_POST)?"POST":"";
00163   snprintf(line, sizeof(line), "%s %s HTTP/1.1\r\nHost: %s\r\n", meth, path, host); //Write request
00164   ret = send(line);
00165   if(ret)
00166   {
00167     ::close(m_sock);
00168     ERR("Could not write request");
00169     return NET_CONN;
00170   }
00171 
00172   //Send all headers
00173 
00174   //Send default headers
00175   DBG("Sending headers");
00176   if( (method == HTTP_POST) && (pDataOut != NULL) )
00177   {
00178     if( pDataOut->getIsChunked() )
00179     {
00180       ret = send("Transfer-Encoding: chunked\r\n");
00181       CHECK_CONN_ERR(ret);
00182     }
00183     else
00184     {
00185       snprintf(line, sizeof(line), "Content-Length: %d\r\n", pDataOut->getDataLen());
00186       ret = send(line);
00187       CHECK_CONN_ERR(ret);
00188     }
00189     char type[48];
00190     if( pDataOut->getDataType(type, 48) == OK )
00191     {
00192       snprintf(line, sizeof(line), "Content-Type: %s\r\n", type);
00193       ret = send(line);
00194       CHECK_CONN_ERR(ret);
00195     }
00196   }
00197 
00198   //Close headers
00199   DBG("Headers sent");
00200   ret = send("\r\n");
00201   CHECK_CONN_ERR(ret);
00202 
00203   char buf[CHUNK_SIZE];
00204   size_t trfLen;
00205 
00206   //Send data (if POST)
00207   if( (method == HTTP_POST) && (pDataOut != NULL) )
00208   {
00209     DBG("Sending data");
00210     while(true)
00211     {
00212       size_t writtenLen = 0;
00213       pDataOut->read(buf, CHUNK_SIZE, &trfLen);
00214       if( pDataOut->getIsChunked() )
00215       {
00216         //Write chunk header
00217         snprintf(line, sizeof(line), "%X\r\n", trfLen); //In hex encoding
00218         ret = send(line);
00219         CHECK_CONN_ERR(ret);
00220       }
00221       else if( trfLen == 0 )
00222       {
00223         break;
00224       }
00225       if( trfLen != 0 )
00226       {
00227         ret = send(buf, trfLen);
00228         CHECK_CONN_ERR(ret);
00229       }
00230 
00231       if( pDataOut->getIsChunked()  )
00232       {
00233         ret = send("\r\n"); //Chunk-terminating CRLF
00234         CHECK_CONN_ERR(ret);
00235       }
00236       else
00237       {
00238         writtenLen += trfLen;
00239         if( writtenLen >= pDataOut->getDataLen() )
00240         {
00241           break;
00242         }
00243       }
00244 
00245       if( trfLen == 0 )
00246       {
00247         break;
00248       }
00249     }
00250 
00251   }
00252 
00253   //Receive response
00254   DBG("Receiving response");
00255   ret = recv(buf, CHUNK_SIZE - 1, CHUNK_SIZE - 1, &trfLen); //Read n bytes
00256   CHECK_CONN_ERR(ret);
00257 
00258   buf[trfLen] = '\0';
00259 
00260   char* crlfPtr = strstr(buf, "\r\n");
00261   if(crlfPtr == NULL)
00262   {
00263     PRTCL_ERR();
00264   }
00265 
00266   int crlfPos = crlfPtr - buf;
00267   buf[crlfPos] = '\0';
00268 
00269   //Parse HTTP response
00270   if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 )
00271   {
00272     //Cannot match string, error
00273     ERR("Not a correct HTTP answer : %s\n", buf);
00274     PRTCL_ERR();
00275   }
00276 
00277   if(m_httpResponseCode != 200)
00278   {
00279     //Cannot match string, error
00280     WARN("Response code %d", m_httpResponseCode);
00281     PRTCL_ERR();
00282   }
00283 
00284   DBG("Reading headers");
00285 
00286   memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
00287   trfLen -= (crlfPos + 2);
00288 
00289   size_t recvContentLength = 0;
00290   bool recvChunked = false;
00291   //Now get headers
00292   while( true )
00293   {
00294     crlfPtr = strstr(buf, "\r\n");
00295     if(crlfPtr == NULL)
00296     {
00297       if( trfLen < CHUNK_SIZE - 1 )
00298       {
00299         size_t newTrfLen;
00300         ret = recv(buf + trfLen, 1, CHUNK_SIZE - trfLen - 1, &newTrfLen);
00301         trfLen += newTrfLen;
00302         buf[trfLen] = '\0';
00303         DBG("Read %d chars; In buf: [%s]", newTrfLen, buf);
00304         CHECK_CONN_ERR(ret);
00305         continue;
00306       }
00307       else
00308       {
00309         PRTCL_ERR();
00310       }
00311     }
00312 
00313     crlfPos = crlfPtr - buf;
00314 
00315     if(crlfPos == 0) //End of headers
00316     {
00317       DBG("Headers read");
00318       memmove(buf, &buf[2], trfLen - 2 + 1); //Be sure to move NULL-terminating char as well
00319       trfLen -= 2;
00320       break;
00321     }
00322 
00323     buf[crlfPos] = '\0';
00324 
00325     char key[32];
00326     char value[32];
00327 
00328     key[31] = '\0';
00329     value[31] = '\0';
00330 
00331     int n = sscanf(buf, "%31[^:]: %31[^\r\n]", key, value);
00332     if ( n == 2 )
00333     {
00334       DBG("Read header : %s: %s\n", key, value);
00335       if( !strcmp(key, "Content-Length") )
00336       {
00337         sscanf(value, "%d", &recvContentLength);
00338         pDataIn->setDataLen(recvContentLength);
00339       }
00340       else if( !strcmp(key, "Transfer-Encoding") )
00341       {
00342         if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") )
00343         {
00344           recvChunked = true;
00345           pDataIn->setIsChunked(true);
00346         }
00347       }
00348       else if( !strcmp(key, "Content-Type") )
00349       {
00350         pDataIn->setDataType(value);
00351       }
00352 
00353       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
00354       trfLen -= (crlfPos + 2);
00355 
00356     }
00357     else
00358     {
00359       ERR("Could not parse header");
00360       PRTCL_ERR();
00361     }
00362 
00363   }
00364 
00365   //Receive data
00366   DBG("Receiving data");
00367   while(true)
00368   {
00369     size_t readLen = 0;
00370 
00371     if( recvChunked )
00372     {
00373       //Read chunk header
00374       crlfPos=0;
00375       for(crlfPos++; crlfPos < trfLen - 2; crlfPos++)
00376       {
00377         if( buf[crlfPos] == '\r' && buf[crlfPos + 1] == '\n' )
00378         {
00379           break;
00380         }
00381       }
00382       if(crlfPos >= trfLen - 2) //Try to read more
00383       {
00384         if( trfLen < CHUNK_SIZE )
00385         {
00386           size_t newTrfLen;
00387           ret = recv(buf + trfLen, 0, CHUNK_SIZE - trfLen - 1, &newTrfLen);
00388           trfLen += newTrfLen;
00389           CHECK_CONN_ERR(ret);
00390           continue;
00391         }
00392         else
00393         {
00394           PRTCL_ERR();
00395         }
00396       }
00397       buf[crlfPos] = '\0';
00398       int n = sscanf(buf, "%x", &readLen);
00399       if(n!=1)
00400       {
00401         ERR("Could not read chunk length");
00402         PRTCL_ERR();
00403       }
00404 
00405       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2)); //Not need to move NULL-terminating char any more
00406       trfLen -= (crlfPos + 2);
00407 
00408       if( readLen == 0 )
00409       {
00410         //Last chunk
00411         break;
00412       }
00413     }
00414     else
00415     {
00416       readLen = recvContentLength;
00417     }
00418 
00419     DBG("Retrieving %d bytes", readLen);
00420 
00421     do
00422     {
00423       pDataIn->write(buf, MIN(trfLen, readLen));
00424       if( trfLen > readLen )
00425       {
00426         memmove(buf, &buf[readLen], trfLen - readLen);
00427         trfLen -= readLen;
00428         readLen = 0;
00429       }
00430       else
00431       {
00432         readLen -= trfLen;
00433       }
00434 
00435       if(readLen)
00436       {
00437         ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen);
00438         CHECK_CONN_ERR(ret);
00439       }
00440     } while(readLen);
00441 
00442     if( recvChunked )
00443     {
00444       if(trfLen < 2)
00445       {
00446         size_t newTrfLen;
00447         //Read missing chars to find end of chunk
00448         ret = recv(buf, 2 - trfLen, CHUNK_SIZE, &newTrfLen);
00449         CHECK_CONN_ERR(ret);
00450         trfLen += newTrfLen;
00451       }
00452       if( (buf[0] != '\r') || (buf[1] != '\n') )
00453       {
00454         ERR("Format error");
00455         PRTCL_ERR();
00456       }
00457       memmove(buf, &buf[2], trfLen - 2);
00458       trfLen -= 2;
00459     }
00460     else
00461     {
00462       break;
00463     }
00464 
00465   }
00466 
00467   ::close(m_sock);
00468   DBG("Completed HTTP transaction");
00469 
00470   return OK;
00471 }
00472 
00473 int HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure
00474 {
00475   DBG("Trying to read between %d and %d bytes", minLen, maxLen);
00476   size_t readLen = 0;
00477   while(readLen < minLen)
00478   {
00479     //Wait for socket to be readable
00480     //Creating FS set
00481     fd_set socksSet;
00482     FD_ZERO(&socksSet);
00483     FD_SET(m_sock, &socksSet);
00484     struct timeval t_val;
00485     t_val.tv_sec = m_timeout / 1000;
00486     t_val.tv_usec = (m_timeout - (t_val.tv_sec * 1000)) * 1000;
00487     int ret = ::select(FD_SETSIZE, &socksSet, NULL, NULL, &t_val);
00488     if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
00489     {
00490       WARN("Timeout");
00491       return NET_TIMEOUT; //Timeout
00492     }
00493 
00494     ret = ::recv(m_sock, buf + readLen, maxLen - readLen, 0);
00495     if( ret > 0)
00496     {
00497       readLen += ret;
00498       continue;
00499     }
00500     else if( ret == 0 )
00501     {
00502       WARN("Connection was closed by server");
00503       return NET_CLOSED; //Connection was closed by server
00504     }
00505     else
00506     {
00507       ERR("Connection error (recv returned %d)", ret);
00508       return NET_CONN;
00509     }
00510   }
00511   *pReadLen = readLen;
00512   DBG("Read %d bytes", readLen);
00513   return OK;
00514 }
00515 
00516 int HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure
00517 {
00518   if(len == 0)
00519   {
00520     len = strlen(buf);
00521   }
00522   DBG("Trying to write %d bytes", len);
00523   size_t writtenLen = 0;
00524   while(writtenLen < len)
00525   {
00526     //Wait for socket to be writeable
00527     //Creating FS set
00528     fd_set socksSet;
00529     FD_ZERO(&socksSet);
00530     FD_SET(m_sock, &socksSet);
00531     struct timeval t_val;
00532     t_val.tv_sec = m_timeout / 1000;
00533     t_val.tv_usec = (m_timeout - (t_val.tv_sec * 1000)) * 1000;
00534     int ret = ::select(FD_SETSIZE, NULL, &socksSet, NULL, &t_val);
00535     if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
00536     {
00537       WARN("Timeout");
00538       return NET_TIMEOUT; //Timeout
00539     }
00540 
00541     ret = ::send(m_sock, buf + writtenLen, len - writtenLen, 0);
00542     if( ret > 0)
00543     {
00544       writtenLen += ret;
00545       continue;
00546     }
00547     else if( ret == 0 )
00548     {
00549       WARN("Connection was closed by server");
00550       return NET_CLOSED; //Connection was closed by server
00551     }
00552     else
00553     {
00554       ERR("Connection error (recv returned %d)", ret);
00555       return NET_CONN;
00556     }
00557   }
00558   DBG("Written %d bytes", writtenLen);
00559   return OK;
00560 }
00561 
00562 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
00563 {
00564   char* schemePtr = (char*) url;
00565   char* hostPtr = (char*) strstr(url, "://");
00566   if(hostPtr == NULL)
00567   {
00568     WARN("Could not find host");
00569     return NET_INVALID; //URL is invalid
00570   }
00571 
00572   if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
00573   {
00574     WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
00575     return NET_TOOSMALL;
00576   }
00577   memcpy(scheme, schemePtr, hostPtr - schemePtr);
00578   scheme[hostPtr - schemePtr] = '\0';
00579 
00580   hostPtr+=3;
00581 
00582   size_t hostLen = 0;
00583 
00584   char* portPtr = strchr(hostPtr, ':');
00585   if( portPtr != NULL )
00586   {
00587     hostLen = portPtr - hostPtr;
00588     portPtr++;
00589     if( sscanf(portPtr, "%d", &port) != 1)
00590     {
00591       WARN("Could not find port");
00592       return NET_INVALID;
00593     }
00594   }
00595   else
00596   {
00597     *port=0;
00598   }
00599   char* pathPtr = strchr(hostPtr, '/');
00600   if( hostLen == 0 )
00601   {
00602     hostLen = pathPtr - hostPtr;
00603   }
00604 
00605   if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
00606   {
00607     WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
00608     return NET_TOOSMALL;
00609   }
00610   memcpy(host, hostPtr, hostLen);
00611   host[hostLen] = '\0';
00612 
00613   size_t pathLen;
00614   char* fragmentPtr = strchr(hostPtr, '#');
00615   if(fragmentPtr != NULL)
00616   {
00617     pathLen = fragmentPtr - pathPtr;
00618   }
00619   else
00620   {
00621     pathLen = strlen(pathPtr);
00622   }
00623 
00624   if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
00625   {
00626     WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
00627     return NET_TOOSMALL;
00628   }
00629   memcpy(path, pathPtr, pathLen);
00630   path[pathLen] = '\0';
00631 
00632   return OK;
00633 }