2 /* Copyright (C) 2012 mbed.org, MIT License
4 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5 * and associated documentation files (the "Software"), to deal in the Software without restriction,
6 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
7 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
10 * The above copyright notice and this permission notice shall be included in all copies or
11 * substantial portions of the Software.
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 //Debug is disabled by default
24 #define DBG(x, ...) std::printf("[HTTPClient : DBG]"x"\r\n", ##__VA_ARGS__);
25 #define WARN(x, ...) std::printf("[HTTPClient : WARN]"x"\r\n", ##__VA_ARGS__);
26 #define ERR(x, ...) std::printf("[HTTPClient : ERR]"x"\r\n", ##__VA_ARGS__);
40 #define MIN(x,y) (((x)<(y))?(x):(y))
41 #define MAX(x,y) (((x)>(y))?(x):(y))
43 #define CHUNK_SIZE 256
47 #include "HTTPClient.h"
49 HTTPClient::HTTPClient() :
50 m_sock(), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0)
55 HTTPClient::~HTTPClient()
61 void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification
63 m_basicAuthUser = user;
64 m_basicAuthPassword = password;
68 HTTPResult HTTPClient::get(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
70 return connect(url, HTTP_GET, NULL, pDataIn, timeout);
73 HTTPResult HTTPClient::get(const char* url, char* result, size_t maxResultLen, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
75 HTTPText str(result, maxResultLen);
76 return get(url, &str, timeout);
79 HTTPResult HTTPClient::post(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
81 return connect(url, HTTP_POST, (IHTTPDataOut*)&dataOut, pDataIn, timeout);
84 HTTPResult HTTPClient::put(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
86 return connect(url, HTTP_PUT, (IHTTPDataOut*)&dataOut, pDataIn, timeout);
89 HTTPResult HTTPClient::del(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
91 return connect(url, HTTP_DELETE, NULL, pDataIn, timeout);
95 int HTTPClient::getHTTPResponseCode()
97 return m_httpResponseCode;
100 #define CHECK_CONN_ERR(ret) \
104 ERR("Connection error (%d)", ret); \
109 #define PRTCL_ERR() \
112 ERR("Protocol error"); \
116 HTTPResult HTTPClient::connect(const char* url, HTTP_METH method, IHTTPDataOut* pDataOut, IHTTPDataIn* pDataIn, int timeout) //Execute request
118 m_httpResponseCode = 0; //Invalidate code
121 pDataIn->writeReset();
124 pDataOut->readReset();
131 //First we need to parse the url (http[s]://host[:port][/[path]]) -- HTTPS not supported (yet?)
132 HTTPResult res = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
135 ERR("parseURL returned %d", res);
139 if(port == 0) //TODO do handle HTTPS->443
144 DBG("Scheme: %s", scheme);
145 DBG("Host: %s", host);
146 DBG("Port: %d", port);
147 DBG("Path: %s", path);
150 DBG("Connecting socket to server");
151 int ret = m_sock.connect(host, port);
155 ERR("Could not connect");
160 DBG("Sending request");
161 char buf[CHUNK_SIZE];
162 const char* meth = (method==HTTP_GET)?"GET":(method==HTTP_POST)?"POST":(method==HTTP_PUT)?"PUT":(method==HTTP_DELETE)?"DELETE":"";
163 snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\nHost: %s\r\n", meth, path, host); //Write request
168 ERR("Could not write request");
174 //Send default headers
175 DBG("Sending headers");
176 if( pDataOut != NULL )
178 if( pDataOut->getIsChunked() )
180 ret = send("Transfer-Encoding: chunked\r\n");
185 snprintf(buf, sizeof(buf), "Content-Length: %d\r\n", pDataOut->getDataLen());
190 if( pDataOut->getDataType(type, 48) == HTTP_OK )
192 snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", type);
205 //Send data (if available)
206 if( pDataOut != NULL )
211 size_t writtenLen = 0;
212 pDataOut->read(buf, CHUNK_SIZE, &trfLen);
213 if( pDataOut->getIsChunked() )
216 char chunkHeader[16];
217 snprintf(chunkHeader, sizeof(chunkHeader), "%X\r\n", trfLen); //In hex encoding
218 ret = send(chunkHeader);
221 else if( trfLen == 0 )
227 ret = send(buf, trfLen);
231 if( pDataOut->getIsChunked() )
233 ret = send("\r\n"); //Chunk-terminating CRLF
238 writtenLen += trfLen;
239 if( writtenLen >= pDataOut->getDataLen() )
254 DBG("Receiving response");
255 ret = recv(buf, CHUNK_SIZE - 1, CHUNK_SIZE - 1, &trfLen); //Read n bytes
260 char* crlfPtr = strstr(buf, "\r\n");
266 int crlfPos = crlfPtr - buf;
269 //Parse HTTP response
270 if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 )
272 //Cannot match string, error
273 ERR("Not a correct HTTP answer : %s\n", buf);
277 if( (m_httpResponseCode < 200) || (m_httpResponseCode >= 300) )
279 //Did not return a 2xx code; TODO fetch headers/(&data?) anyway and implement a mean of writing/reading headers
280 WARN("Response code %d", m_httpResponseCode);
284 DBG("Reading headers");
286 memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
287 trfLen -= (crlfPos + 2);
289 size_t recvContentLength = 0;
290 bool recvChunked = false;
294 crlfPtr = strstr(buf, "\r\n");
297 if( trfLen < CHUNK_SIZE - 1 )
300 ret = recv(buf + trfLen, 1, CHUNK_SIZE - trfLen - 1, &newTrfLen);
303 DBG("Read %d chars; In buf: [%s]", newTrfLen, buf);
313 crlfPos = crlfPtr - buf;
315 if(crlfPos == 0) //End of headers
318 memmove(buf, &buf[2], trfLen - 2 + 1); //Be sure to move NULL-terminating char as well
331 int n = sscanf(buf, "%31[^:]: %31[^\r\n]", key, value);
334 DBG("Read header : %s: %s\n", key, value);
335 if( !strcmp(key, "Content-Length") )
337 sscanf(value, "%d", &recvContentLength);
338 pDataIn->setDataLen(recvContentLength);
340 else if( !strcmp(key, "Transfer-Encoding") )
342 if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") )
345 pDataIn->setIsChunked(true);
348 else if( !strcmp(key, "Content-Type") )
350 pDataIn->setDataType(value);
353 memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
354 trfLen -= (crlfPos + 2);
359 ERR("Could not parse header");
366 DBG("Receiving data");
382 for(; crlfPos < trfLen - 2; crlfPos++)
384 if( buf[crlfPos] == '\r' && buf[crlfPos + 1] == '\n' )
391 if(!foundCrlf) //Try to read more
393 if( trfLen < CHUNK_SIZE )
396 ret = recv(buf + trfLen, 0, CHUNK_SIZE - trfLen - 1, &newTrfLen);
408 int n = sscanf(buf, "%x", &readLen);
411 ERR("Could not read chunk length");
415 memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2)); //Not need to move NULL-terminating char any more
416 trfLen -= (crlfPos + 2);
426 readLen = recvContentLength;
429 DBG("Retrieving %d bytes", readLen);
433 pDataIn->write(buf, MIN(trfLen, readLen));
434 if( trfLen > readLen )
436 memmove(buf, &buf[readLen], trfLen - readLen);
447 ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen);
457 //Read missing chars to find end of chunk
458 ret = recv(buf + trfLen, 2 - trfLen, CHUNK_SIZE - trfLen - 1, &newTrfLen);
462 if( (buf[0] != '\r') || (buf[1] != '\n') )
467 memmove(buf, &buf[2], trfLen - 2);
478 DBG("Completed HTTP transaction");
483 HTTPResult HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure
485 DBG("Trying to read between %d and %d bytes", minLen, maxLen);
488 if(!m_sock.is_connected())
490 WARN("Connection was closed by server");
491 return HTTP_CLOSED; //Connection was closed by server
495 while(readLen < maxLen)
499 DBG("Trying to read at most %d bytes [Blocking]", minLen - readLen);
500 m_sock.set_blocking(false, m_timeout);
501 ret = m_sock.receive_all(buf + readLen, minLen - readLen);
505 DBG("Trying to read at most %d bytes [Not blocking]", maxLen - readLen);
506 m_sock.set_blocking(false, 0);
507 ret = m_sock.receive(buf + readLen, maxLen - readLen);
520 if(!m_sock.is_connected())
522 ERR("Connection error (recv returned %d)", ret);
532 if(!m_sock.is_connected())
537 DBG("Read %d bytes", readLen);
542 HTTPResult HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure
548 DBG("Trying to write %d bytes", len);
549 size_t writtenLen = 0;
551 if(!m_sock.is_connected())
553 WARN("Connection was closed by server");
554 return HTTP_CLOSED; //Connection was closed by server
557 m_sock.set_blocking(false, m_timeout);
558 int ret = m_sock.send_all(buf, len);
565 WARN("Connection was closed by server");
566 return HTTP_CLOSED; //Connection was closed by server
570 ERR("Connection error (send returned %d)", ret);
574 DBG("Written %d bytes", writtenLen);
578 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
580 char* schemePtr = (char*) url;
581 char* hostPtr = (char*) strstr(url, "://");
584 WARN("Could not find host");
585 return HTTP_PARSE; //URL is invalid
588 if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
590 WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
593 memcpy(scheme, schemePtr, hostPtr - schemePtr);
594 scheme[hostPtr - schemePtr] = '\0';
600 char* portPtr = strchr(hostPtr, ':');
601 if( portPtr != NULL )
603 hostLen = portPtr - hostPtr;
605 if( sscanf(portPtr, "%hu", port) != 1)
607 WARN("Could not find port");
615 char* pathPtr = strchr(hostPtr, '/');
618 hostLen = pathPtr - hostPtr;
621 if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
623 WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
626 memcpy(host, hostPtr, hostLen);
627 host[hostLen] = '\0';
630 char* fragmentPtr = strchr(hostPtr, '#');
631 if(fragmentPtr != NULL)
633 pathLen = fragmentPtr - pathPtr;
637 pathLen = strlen(pathPtr);
640 if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
642 WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
645 memcpy(path, pathPtr, pathLen);
646 path[pathLen] = '\0';