Rizky Ardi Maulana / mbed-os
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers HTTPClient.cpp Source File

HTTPClient.cpp

00001 /* HTTPClient.cpp */
00002 /* Copyright (C) 2012 mbed.org, MIT License
00003  *
00004  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
00005  * and associated documentation files (the "Software"), to deal in the Software without restriction,
00006  * including without limitation the rights to use, copy, modify, merge, publish, distribute,
00007  * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
00008  * furnished to do so, subject to the following conditions:
00009  *
00010  * The above copyright notice and this permission notice shall be included in all copies or
00011  * substantial portions of the Software.
00012  *
00013  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
00014  * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00015  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
00016  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00017  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00018  */
00019 
00020 //Debug is disabled by default
00021 #if 1
00022 //Enable debug
00023 #include <cstdio>
00024 //#define DBG(x, ...) std::printf("[HTTPClient : DBG]"x"\r\n", ##__VA_ARGS__);
00025 #define DBG(x, ...)
00026 #define WARN(x, ...) std::printf("[HTTPClient : WARN]"x"\r\n", ##__VA_ARGS__);
00027 #define ERR(x, ...) std::printf("[HTTPClient : ERR]"x"\r\n", ##__VA_ARGS__);
00028 
00029 #else
00030 //Disable debug
00031 #define DBG(x, ...)
00032 #define WARN(x, ...)
00033 #define ERR(x, ...)
00034 
00035 #endif
00036 
00037 #define HTTP_PORT 80
00038 
00039 #define OK 0
00040 
00041 #define MIN(x,y) (((x)<(y))?(x):(y))
00042 #define MAX(x,y) (((x)>(y))?(x):(y))
00043 
00044 #define CHUNK_SIZE 256
00045 
00046 #include <cstring>
00047 
00048 #include "HTTPClient.h"
00049 
00050 HTTPClient::HTTPClient() :
00051 m_sock(), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0)
00052 {
00053 
00054 }
00055 
00056 HTTPClient::~HTTPClient()
00057 {
00058 
00059 }
00060 
00061 #if 0
00062 void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification
00063 {
00064   m_basicAuthUser = user;
00065   m_basicAuthPassword = password;
00066 }
00067 #endif
00068 
00069 HTTPResult HTTPClient::get(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
00070 {
00071   return connect(url, HTTP_GET, NULL, pDataIn, timeout);
00072 }
00073 
00074 HTTPResult HTTPClient::get(const char* url, char* result, size_t maxResultLen, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
00075 {
00076   HTTPText str(result, maxResultLen);
00077   return get(url, &str, timeout);
00078 }
00079 
00080 HTTPResult HTTPClient::post(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
00081 {
00082   return connect(url, HTTP_POST, (IHTTPDataOut*)&dataOut, pDataIn, timeout);
00083 }
00084 
00085 int HTTPClient::getHTTPResponseCode()
00086 {
00087   return m_httpResponseCode;
00088 }
00089 
00090 #define CHECK_CONN_ERR(ret) \
00091   do{ \
00092     if(ret) { \
00093       m_sock.close(); \
00094       ERR("Connection error (%d)", ret); \
00095       return HTTP_CONN; \
00096     } \
00097   } while(0)
00098 
00099 #define PRTCL_ERR() \
00100   do{ \
00101     m_sock.close(); \
00102     ERR("Protocol error"); \
00103     return HTTP_PRTCL; \
00104   } while(0)
00105 
00106 HTTPResult HTTPClient::connect(const char* url, HTTP_METH method, IHTTPDataOut* pDataOut, IHTTPDataIn* pDataIn, int timeout) //Execute request
00107 {
00108   m_httpResponseCode = 0; //Invalidate code
00109   m_timeout = timeout;
00110 
00111   char scheme[8];
00112   uint16_t port;
00113   char host[32];
00114   char path[64];
00115   //First we need to parse the url (http[s]://host[:port][/[path]]) -- HTTPS not supported (yet?)
00116   HTTPResult res = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
00117   if(res != HTTP_OK)
00118   {
00119     ERR("parseURL returned %d", res);
00120     return res;
00121   }
00122 
00123   if(port == 0) //TODO do handle HTTPS->443
00124   {
00125     port = 80;
00126   }
00127 
00128   DBG("Scheme: %s", scheme);
00129   DBG("Host: %s", host);
00130   DBG("Port: %d", port);
00131   DBG("Path: %s", path);
00132 
00133   //Connect
00134   DBG("Connecting socket to server");
00135   int ret = m_sock.connect(host, port);
00136   if (ret < 0)
00137   {
00138     m_sock.close();
00139     ERR("Could not connect");
00140     return HTTP_CONN;
00141   }
00142 
00143   //Send request
00144   DBG("Sending request");
00145   char buf[CHUNK_SIZE];
00146   const char* meth = (method==HTTP_GET)?"GET":(method==HTTP_POST)?"POST":"";
00147   snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\nHost: %s\r\n", meth, path, host); //Write request
00148   ret = send(buf);
00149   if(ret)
00150   {
00151     m_sock.close();
00152     ERR("Could not write request");
00153     return HTTP_CONN;
00154   }
00155 
00156   //Send all headers
00157 
00158   //Send default headers
00159   DBG("Sending headers");
00160   if( (method == HTTP_POST) && (pDataOut != NULL) )
00161   {
00162     if( pDataOut->getIsChunked() )
00163     {
00164       ret = send("Transfer-Encoding: chunked\r\n");
00165       CHECK_CONN_ERR(ret);
00166     }
00167     else
00168     {
00169       snprintf(buf, sizeof(buf), "Content-Length: %d\r\n", pDataOut->getDataLen());
00170       ret = send(buf);
00171       CHECK_CONN_ERR(ret);
00172     }
00173     char type[48];
00174     if( pDataOut->getDataType(type, 48) == HTTP_OK )
00175     {
00176       snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", type);
00177       ret = send(buf);
00178       CHECK_CONN_ERR(ret);
00179     }
00180   }
00181 
00182   //Close headers
00183   DBG("Headers sent");
00184   ret = send("\r\n");
00185   CHECK_CONN_ERR(ret);
00186 
00187   size_t trfLen;
00188 
00189   //Send data (if POST)
00190   if( (method == HTTP_POST) && (pDataOut != NULL) )
00191   {
00192     DBG("Sending data");
00193     while(true)
00194     {
00195       size_t writtenLen = 0;
00196       pDataOut->read(buf, CHUNK_SIZE, &trfLen);
00197       if( pDataOut->getIsChunked() )
00198       {
00199         //Write chunk header
00200         char chunkHeader[16];
00201         snprintf(chunkHeader, sizeof(chunkHeader), "%X\r\n", trfLen); //In hex encoding
00202         ret = send(chunkHeader);
00203         CHECK_CONN_ERR(ret);
00204       }
00205       else if( trfLen == 0 )
00206       {
00207         break;
00208       }
00209       if( trfLen != 0 )
00210       {
00211         ret = send(buf, trfLen);
00212         CHECK_CONN_ERR(ret);
00213       }
00214 
00215       if( pDataOut->getIsChunked()  )
00216       {
00217         ret = send("\r\n"); //Chunk-terminating CRLF
00218         CHECK_CONN_ERR(ret);
00219       }
00220       else
00221       {
00222         writtenLen += trfLen;
00223         if( writtenLen >= pDataOut->getDataLen() )
00224         {
00225           break;
00226         }
00227       }
00228 
00229       if( trfLen == 0 )
00230       {
00231         break;
00232       }
00233     }
00234 
00235   }
00236 
00237   //Receive response
00238   DBG("Receiving response");
00239   ret = recv(buf, CHUNK_SIZE - 1, CHUNK_SIZE - 1, &trfLen); //Read n bytes
00240   CHECK_CONN_ERR(ret);
00241 
00242   buf[trfLen] = '\0';
00243 
00244   char* crlfPtr = strstr(buf, "\r\n");
00245   if(crlfPtr == NULL)
00246   {
00247     PRTCL_ERR();
00248   }
00249 
00250   int crlfPos = crlfPtr - buf;
00251   buf[crlfPos] = '\0';
00252 
00253   //Parse HTTP response
00254   if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 )
00255   {
00256     //Cannot match string, error
00257     ERR("Not a correct HTTP answer : %s\n", buf);
00258     PRTCL_ERR();
00259   }
00260 
00261   if(m_httpResponseCode != 200)
00262   {
00263     //Cannot match string, error
00264     WARN("Response code %d", m_httpResponseCode);
00265     PRTCL_ERR();
00266   }
00267 
00268   DBG("Reading headers");
00269 
00270   memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
00271   trfLen -= (crlfPos + 2);
00272 
00273   size_t recvContentLength = 0;
00274   bool recvChunked = false;
00275   //Now get headers
00276   while( true )
00277   {
00278     crlfPtr = strstr(buf, "\r\n");
00279     if(crlfPtr == NULL)
00280     {
00281       if( trfLen < CHUNK_SIZE - 1 )
00282       {
00283         size_t newTrfLen;
00284         ret = recv(buf + trfLen, 1, CHUNK_SIZE - trfLen - 1, &newTrfLen);
00285         trfLen += newTrfLen;
00286         buf[trfLen] = '\0';
00287         DBG("Read %d chars; In buf: [%s]", newTrfLen, buf);
00288         CHECK_CONN_ERR(ret);
00289         continue;
00290       }
00291       else
00292       {
00293         PRTCL_ERR();
00294       }
00295     }
00296 
00297     crlfPos = crlfPtr - buf;
00298 
00299     if(crlfPos == 0) //End of headers
00300     {
00301       DBG("Headers read");
00302       memmove(buf, &buf[2], trfLen - 2 + 1); //Be sure to move NULL-terminating char as well
00303       trfLen -= 2;
00304       break;
00305     }
00306 
00307     buf[crlfPos] = '\0';
00308 
00309     char key[64] = {0};
00310     char value[32] = {0};
00311 
00312     int n = sscanf(buf, "%63[^:]: %31[^\r\n]", key, value);
00313 
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) + 1); //Be sure to move NULL-terminating char as well
00336       trfLen -= (crlfPos + 2);
00337 
00338     }
00339     else
00340     {
00341       ERR("Could not parse header");
00342       PRTCL_ERR();
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           CHECK_CONN_ERR(ret);
00372           continue;
00373         }
00374         else
00375         {
00376           PRTCL_ERR();
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         PRTCL_ERR();
00385       }
00386 
00387       memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2)); //Not need to move NULL-terminating char any more
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         CHECK_CONN_ERR(ret);
00421       }
00422     } while(readLen);
00423 
00424     if( recvChunked )
00425     {
00426       if(trfLen < 2)
00427       {
00428         size_t newTrfLen;
00429         //Read missing chars to find end of chunk
00430         ret = recv(buf, 2 - trfLen, CHUNK_SIZE, &newTrfLen);
00431         CHECK_CONN_ERR(ret);
00432         trfLen += newTrfLen;
00433       }
00434       if( (buf[0] != '\r') || (buf[1] != '\n') )
00435       {
00436         ERR("Format error");
00437         PRTCL_ERR();
00438       }
00439       memmove(buf, &buf[2], trfLen - 2);
00440       trfLen -= 2;
00441     }
00442     else
00443     {
00444       break;
00445     }
00446 
00447   }
00448 
00449   m_sock.close();
00450   DBG("Completed HTTP transaction");
00451 
00452   return HTTP_OK;
00453 }
00454 
00455 HTTPResult HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure
00456 {
00457   DBG("Trying to read between %d and %d bytes", minLen, maxLen);
00458   size_t readLen = 0;
00459 
00460   if(!m_sock.is_connected())
00461   {
00462     WARN("Connection was closed by server");
00463     return HTTP_CLOSED; //Connection was closed by server
00464   }
00465 
00466   int ret;
00467   while(readLen < maxLen)
00468   {
00469     if(readLen < minLen)
00470     {
00471       DBG("Trying to read at most %d bytes [Blocking]", minLen - readLen);
00472       m_sock.set_blocking(false, m_timeout);
00473       ret = m_sock.receive_all(buf + readLen, minLen - readLen);
00474     }
00475     else
00476     {
00477       DBG("Trying to read at most %d bytes [Not blocking]", maxLen - readLen);
00478       m_sock.set_blocking(false, 0);
00479       ret = m_sock.receive(buf + readLen, maxLen - readLen);
00480     }
00481 
00482     if( ret > 0)
00483     {
00484       readLen += ret;
00485     }
00486     else if( ret == 0 )
00487     {
00488       break;
00489     }
00490     else
00491     {
00492       if(!m_sock.is_connected())
00493       {
00494         ERR("Connection error (recv returned %d)", ret);
00495         *pReadLen = readLen;
00496         return HTTP_CONN;
00497       }
00498       else
00499       {
00500         break;
00501       }
00502     }
00503 
00504     if(!m_sock.is_connected())
00505     {
00506       break;
00507     }
00508   }
00509   DBG("Read %d bytes", readLen);
00510   *pReadLen = readLen;
00511   return HTTP_OK;
00512 }
00513 
00514 HTTPResult HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure
00515 {
00516   if(len == 0)
00517   {
00518     len = strlen(buf);
00519   }
00520   DBG("Trying to write %d bytes", len);
00521   size_t writtenLen = 0;
00522 
00523   if(!m_sock.is_connected())
00524   {
00525     WARN("Connection was closed by server");
00526     return HTTP_CLOSED; //Connection was closed by server
00527   }
00528 
00529   m_sock.set_blocking(false, m_timeout);
00530   int ret = m_sock.send_all(buf, len);
00531   if(ret > 0)
00532   {
00533     writtenLen += ret;
00534   }
00535   else if( ret == 0 )
00536   {
00537     WARN("Connection was closed by server");
00538     return HTTP_CLOSED; //Connection was closed by server
00539   }
00540   else
00541   {
00542     ERR("Connection error (send returned %d)", ret);
00543     return HTTP_CONN;
00544   }
00545 
00546   DBG("Written %d bytes", writtenLen);
00547   return HTTP_OK;
00548 }
00549 
00550 HTTPResult 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
00551 {
00552   char* schemePtr = (char*) url;
00553   char* hostPtr = (char*) strstr(url, "://");
00554   if(hostPtr == NULL)
00555   {
00556     WARN("Could not find host");
00557     return HTTP_PARSE; //URL is invalid
00558   }
00559 
00560   if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
00561   {
00562     WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
00563     return HTTP_PARSE;
00564   }
00565   memcpy(scheme, schemePtr, hostPtr - schemePtr);
00566   scheme[hostPtr - schemePtr] = '\0';
00567 
00568   hostPtr+=3;
00569 
00570   size_t hostLen = 0;
00571 
00572   char* portPtr = strchr(hostPtr, ':');
00573   if( portPtr != NULL )
00574   {
00575     hostLen = portPtr - hostPtr;
00576     portPtr++;
00577     if( sscanf(portPtr, "%hu", port) != 1)
00578     {
00579       WARN("Could not find port");
00580       return HTTP_PARSE;
00581     }
00582   }
00583   else
00584   {
00585     *port=0;
00586   }
00587   char* pathPtr = strchr(hostPtr, '/');
00588   if( hostLen == 0 )
00589   {
00590     hostLen = pathPtr - hostPtr;
00591   }
00592 
00593   if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
00594   {
00595     WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
00596     return HTTP_PARSE;
00597   }
00598   memcpy(host, hostPtr, hostLen);
00599   host[hostLen] = '\0';
00600 
00601   size_t pathLen;
00602   char* fragmentPtr = strchr(hostPtr, '#');
00603   if(fragmentPtr != NULL)
00604   {
00605     pathLen = fragmentPtr - pathPtr;
00606   }
00607   else
00608   {
00609     pathLen = strlen(pathPtr);
00610   }
00611 
00612   if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
00613   {
00614     WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
00615     return HTTP_PARSE;
00616   }
00617   memcpy(path, pathPtr, pathLen);
00618   path[pathLen] = '\0';
00619 
00620   return HTTP_OK;
00621 }