u-blox modem HTTP client test
Dependencies: UbloxUSBModem mbed
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 0 00022 //Enable debug 00023 #include <cstdio> 00024 #define DBG(x, ...) std::printf("[HTTPClient : DBG]"x"\r\n", ##__VA_ARGS__); 00025 #define WARN(x, ...) std::printf("[HTTPClient : WARN]"x"\r\n", ##__VA_ARGS__); 00026 #define ERR(x, ...) std::printf("[HTTPClient : ERR]"x"\r\n", ##__VA_ARGS__); 00027 00028 #else 00029 //Disable debug 00030 #define DBG(x, ...) 00031 #define WARN(x, ...) 00032 #define ERR(x, ...) 00033 00034 #endif 00035 00036 #define HTTP_PORT 80 00037 00038 #define OK 0 00039 00040 #define MIN(x,y) (((x)<(y))?(x):(y)) 00041 #define MAX(x,y) (((x)>(y))?(x):(y)) 00042 00043 #define CHUNK_SIZE 256 00044 00045 #include <cstring> 00046 00047 #include "HTTPClient.h" 00048 00049 HTTPClient::HTTPClient() : 00050 m_sock(), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0) 00051 { 00052 00053 } 00054 00055 HTTPClient::~HTTPClient() 00056 { 00057 00058 } 00059 00060 #if 0 00061 void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification 00062 { 00063 m_basicAuthUser = user; 00064 m_basicAuthPassword = password; 00065 } 00066 #endif 00067 00068 HTTPResult HTTPClient::get(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking 00069 { 00070 return connect(url, HTTP_GET, NULL, pDataIn, timeout); 00071 } 00072 00073 HTTPResult HTTPClient::get(const char* url, char* result, size_t maxResultLen, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking 00074 { 00075 HTTPText str(result, maxResultLen); 00076 return get(url, &str, timeout); 00077 } 00078 00079 HTTPResult HTTPClient::post(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking 00080 { 00081 return connect(url, HTTP_POST, (IHTTPDataOut*)&dataOut, pDataIn, timeout); 00082 } 00083 00084 HTTPResult HTTPClient::put(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking 00085 { 00086 return connect(url, HTTP_PUT, (IHTTPDataOut*)&dataOut, pDataIn, timeout); 00087 } 00088 00089 HTTPResult HTTPClient::del(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking 00090 { 00091 return connect(url, HTTP_DELETE, NULL, pDataIn, timeout); 00092 } 00093 00094 00095 int HTTPClient::getHTTPResponseCode() 00096 { 00097 return m_httpResponseCode; 00098 } 00099 00100 #define CHECK_CONN_ERR(ret) \ 00101 do{ \ 00102 if(ret) { \ 00103 m_sock.close(); \ 00104 ERR("Connection error (%d)", ret); \ 00105 return HTTP_CONN; \ 00106 } \ 00107 } while(0) 00108 00109 #define PRTCL_ERR() \ 00110 do{ \ 00111 m_sock.close(); \ 00112 ERR("Protocol error"); \ 00113 return HTTP_PRTCL; \ 00114 } while(0) 00115 00116 HTTPResult HTTPClient::connect(const char* url, HTTP_METH method, IHTTPDataOut* pDataOut, IHTTPDataIn* pDataIn, int timeout) //Execute request 00117 { 00118 m_httpResponseCode = 0; //Invalidate code 00119 m_timeout = timeout; 00120 00121 pDataIn->writeReset(); 00122 if( pDataOut ) 00123 { 00124 pDataOut->readReset(); 00125 } 00126 00127 char scheme[8]; 00128 uint16_t port; 00129 char host[32]; 00130 char path[64]; 00131 //First we need to parse the url (http[s]://host[:port][/[path]]) -- HTTPS not supported (yet?) 00132 HTTPResult res = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path)); 00133 if(res != HTTP_OK) 00134 { 00135 ERR("parseURL returned %d", res); 00136 return res; 00137 } 00138 00139 if(port == 0) //TODO do handle HTTPS->443 00140 { 00141 port = 80; 00142 } 00143 00144 DBG("Scheme: %s", scheme); 00145 DBG("Host: %s", host); 00146 DBG("Port: %d", port); 00147 DBG("Path: %s", path); 00148 00149 //Connect 00150 DBG("Connecting socket to server"); 00151 int ret = m_sock.connect(host, port); 00152 if (ret < 0) 00153 { 00154 m_sock.close(); 00155 ERR("Could not connect"); 00156 return HTTP_CONN; 00157 } 00158 00159 //Send request 00160 DBG("Sending request"); 00161 char buf[CHUNK_SIZE]; 00162 const char* meth = (method==HTTP_GET)?"GET":(method==HTTP_POST)?"POST":(method==HTTP_PUT)?"PUT":(method==HTTP_DELETE)?"DELETE":""; 00163 snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\nHost: %s\r\n", meth, path, host); //Write request 00164 ret = send(buf); 00165 if(ret) 00166 { 00167 m_sock.close(); 00168 ERR("Could not write request"); 00169 return HTTP_CONN; 00170 } 00171 00172 //Send all headers 00173 00174 //Send default headers 00175 DBG("Sending headers"); 00176 if( 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(buf, sizeof(buf), "Content-Length: %d\r\n", pDataOut->getDataLen()); 00186 ret = send(buf); 00187 CHECK_CONN_ERR(ret); 00188 } 00189 char type[48]; 00190 if( pDataOut->getDataType(type, 48) == HTTP_OK ) 00191 { 00192 snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", type); 00193 ret = send(buf); 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 size_t trfLen; 00204 00205 //Send data (if available) 00206 if( pDataOut != NULL ) 00207 { 00208 DBG("Sending data"); 00209 while(true) 00210 { 00211 size_t writtenLen = 0; 00212 pDataOut->read(buf, CHUNK_SIZE, &trfLen); 00213 if( pDataOut->getIsChunked() ) 00214 { 00215 //Write chunk header 00216 char chunkHeader[16]; 00217 snprintf(chunkHeader, sizeof(chunkHeader), "%X\r\n", trfLen); //In hex encoding 00218 ret = send(chunkHeader); 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) || (m_httpResponseCode >= 300) ) 00278 { 00279 //Did not return a 2xx code; TODO fetch headers/(&data?) anyway and implement a mean of writing/reading headers 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 bool foundCrlf; 00375 do 00376 { 00377 foundCrlf = false; 00378 crlfPos=0; 00379 buf[trfLen]=0; 00380 if(trfLen >= 2) 00381 { 00382 for(; crlfPos < trfLen - 2; crlfPos++) 00383 { 00384 if( buf[crlfPos] == '\r' && buf[crlfPos + 1] == '\n' ) 00385 { 00386 foundCrlf = true; 00387 break; 00388 } 00389 } 00390 } 00391 if(!foundCrlf) //Try to read more 00392 { 00393 if( trfLen < CHUNK_SIZE ) 00394 { 00395 size_t newTrfLen; 00396 ret = recv(buf + trfLen, 0, CHUNK_SIZE - trfLen - 1, &newTrfLen); 00397 trfLen += newTrfLen; 00398 CHECK_CONN_ERR(ret); 00399 continue; 00400 } 00401 else 00402 { 00403 PRTCL_ERR(); 00404 } 00405 } 00406 } while(!foundCrlf); 00407 buf[crlfPos] = '\0'; 00408 int n = sscanf(buf, "%x", &readLen); 00409 if(n!=1) 00410 { 00411 ERR("Could not read chunk length"); 00412 PRTCL_ERR(); 00413 } 00414 00415 memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2)); //Not need to move NULL-terminating char any more 00416 trfLen -= (crlfPos + 2); 00417 00418 if( readLen == 0 ) 00419 { 00420 //Last chunk 00421 break; 00422 } 00423 } 00424 else 00425 { 00426 readLen = recvContentLength; 00427 } 00428 00429 DBG("Retrieving %d bytes", readLen); 00430 00431 do 00432 { 00433 pDataIn->write(buf, MIN(trfLen, readLen)); 00434 if( trfLen > readLen ) 00435 { 00436 memmove(buf, &buf[readLen], trfLen - readLen); 00437 trfLen -= readLen; 00438 readLen = 0; 00439 } 00440 else 00441 { 00442 readLen -= trfLen; 00443 } 00444 00445 if(readLen) 00446 { 00447 ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen); 00448 CHECK_CONN_ERR(ret); 00449 } 00450 } while(readLen); 00451 00452 if( recvChunked ) 00453 { 00454 if(trfLen < 2) 00455 { 00456 size_t newTrfLen; 00457 //Read missing chars to find end of chunk 00458 ret = recv(buf + trfLen, 2 - trfLen, CHUNK_SIZE - trfLen - 1, &newTrfLen); 00459 CHECK_CONN_ERR(ret); 00460 trfLen += newTrfLen; 00461 } 00462 if( (buf[0] != '\r') || (buf[1] != '\n') ) 00463 { 00464 ERR("Format error"); 00465 PRTCL_ERR(); 00466 } 00467 memmove(buf, &buf[2], trfLen - 2); 00468 trfLen -= 2; 00469 } 00470 else 00471 { 00472 break; 00473 } 00474 00475 } 00476 00477 m_sock.close(); 00478 DBG("Completed HTTP transaction"); 00479 00480 return HTTP_OK; 00481 } 00482 00483 HTTPResult HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure 00484 { 00485 DBG("Trying to read between %d and %d bytes", minLen, maxLen); 00486 size_t readLen = 0; 00487 00488 if(!m_sock.is_connected()) 00489 { 00490 WARN("Connection was closed by server"); 00491 return HTTP_CLOSED; //Connection was closed by server 00492 } 00493 00494 int ret; 00495 while(readLen < maxLen) 00496 { 00497 if(readLen < minLen) 00498 { 00499 DBG("Trying to read at most %d bytes [Blocking]", minLen - readLen); 00500 m_sock.set_blocking(false, m_timeout); 00501 ret = m_sock.receive_all(buf + readLen, minLen - readLen); 00502 } 00503 else 00504 { 00505 DBG("Trying to read at most %d bytes [Not blocking]", maxLen - readLen); 00506 m_sock.set_blocking(false, 0); 00507 ret = m_sock.receive(buf + readLen, maxLen - readLen); 00508 } 00509 00510 if( ret > 0) 00511 { 00512 readLen += ret; 00513 } 00514 else if( ret == 0 ) 00515 { 00516 break; 00517 } 00518 else 00519 { 00520 if(!m_sock.is_connected()) 00521 { 00522 ERR("Connection error (recv returned %d)", ret); 00523 *pReadLen = readLen; 00524 return HTTP_CONN; 00525 } 00526 else 00527 { 00528 break; 00529 } 00530 } 00531 00532 if(!m_sock.is_connected()) 00533 { 00534 break; 00535 } 00536 } 00537 DBG("Read %d bytes", readLen); 00538 *pReadLen = readLen; 00539 return HTTP_OK; 00540 } 00541 00542 HTTPResult HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure 00543 { 00544 if(len == 0) 00545 { 00546 len = strlen(buf); 00547 } 00548 DBG("Trying to write %d bytes", len); 00549 size_t writtenLen = 0; 00550 00551 if(!m_sock.is_connected()) 00552 { 00553 WARN("Connection was closed by server"); 00554 return HTTP_CLOSED; //Connection was closed by server 00555 } 00556 00557 m_sock.set_blocking(false, m_timeout); 00558 int ret = m_sock.send_all(buf, len); 00559 if(ret > 0) 00560 { 00561 writtenLen += ret; 00562 } 00563 else if( ret == 0 ) 00564 { 00565 WARN("Connection was closed by server"); 00566 return HTTP_CLOSED; //Connection was closed by server 00567 } 00568 else 00569 { 00570 ERR("Connection error (send returned %d)", ret); 00571 return HTTP_CONN; 00572 } 00573 00574 DBG("Written %d bytes", writtenLen); 00575 return HTTP_OK; 00576 } 00577 00578 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 00579 { 00580 char* schemePtr = (char*) url; 00581 char* hostPtr = (char*) strstr(url, "://"); 00582 if(hostPtr == NULL) 00583 { 00584 WARN("Could not find host"); 00585 return HTTP_PARSE; //URL is invalid 00586 } 00587 00588 if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char 00589 { 00590 WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1); 00591 return HTTP_PARSE; 00592 } 00593 memcpy(scheme, schemePtr, hostPtr - schemePtr); 00594 scheme[hostPtr - schemePtr] = '\0'; 00595 00596 hostPtr+=3; 00597 00598 size_t hostLen = 0; 00599 00600 char* portPtr = strchr(hostPtr, ':'); 00601 if( portPtr != NULL ) 00602 { 00603 hostLen = portPtr - hostPtr; 00604 portPtr++; 00605 if( sscanf(portPtr, "%hu", port) != 1) 00606 { 00607 WARN("Could not find port"); 00608 return HTTP_PARSE; 00609 } 00610 } 00611 else 00612 { 00613 *port=0; 00614 } 00615 char* pathPtr = strchr(hostPtr, '/'); 00616 if( hostLen == 0 ) 00617 { 00618 hostLen = pathPtr - hostPtr; 00619 } 00620 00621 if( maxHostLen < hostLen + 1 ) //including NULL-terminating char 00622 { 00623 WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1); 00624 return HTTP_PARSE; 00625 } 00626 memcpy(host, hostPtr, hostLen); 00627 host[hostLen] = '\0'; 00628 00629 size_t pathLen; 00630 char* fragmentPtr = strchr(hostPtr, '#'); 00631 if(fragmentPtr != NULL) 00632 { 00633 pathLen = fragmentPtr - pathPtr; 00634 } 00635 else 00636 { 00637 pathLen = strlen(pathPtr); 00638 } 00639 00640 if( maxPathLen < pathLen + 1 ) //including NULL-terminating char 00641 { 00642 WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1); 00643 return HTTP_PARSE; 00644 } 00645 memcpy(path, pathPtr, pathLen); 00646 path[pathLen] = '\0'; 00647 00648 return HTTP_OK; 00649 }
Generated on Tue Jul 12 2022 14:33:52 by 1.7.2