Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
lwip_http_client.c
00001 /** 00002 * @file 00003 * HTTP client 00004 */ 00005 00006 /* 00007 * Copyright (c) 2018 Simon Goldschmidt <goldsimon@gmx.de> 00008 * All rights reserved. 00009 * 00010 * Redistribution and use in source and binary forms, with or without modification, 00011 * are permitted provided that the following conditions are met: 00012 * 00013 * 1. Redistributions of source code must retain the above copyright notice, 00014 * this list of conditions and the following disclaimer. 00015 * 2. Redistributions in binary form must reproduce the above copyright notice, 00016 * this list of conditions and the following disclaimer in the documentation 00017 * and/or other materials provided with the distribution. 00018 * 3. The name of the author may not be used to endorse or promote products 00019 * derived from this software without specific prior written permission. 00020 * 00021 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 00022 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 00023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 00024 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 00025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 00026 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00027 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00028 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 00029 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 00030 * OF SUCH DAMAGE. 00031 * 00032 * This file is part of the lwIP TCP/IP stack. 00033 * 00034 * Author: Simon Goldschmidt <goldsimon@gmx.de> 00035 */ 00036 00037 /** 00038 * @defgroup httpc HTTP client 00039 * @ingroup apps 00040 * @todo: 00041 * - persistent connections 00042 * - select outgoing http version 00043 * - optionally follow redirect 00044 * - check request uri for invalid characters? (e.g. encode spaces) 00045 * - IPv6 support 00046 */ 00047 00048 #include "lwip/apps/http_client.h" 00049 00050 #include "lwip/altcp_tcp.h" 00051 #include "lwip/dns.h" 00052 #include "lwip/debug.h" 00053 #include "lwip/mem.h" 00054 #include "lwip/altcp_tls.h" 00055 #include "lwip/init.h" 00056 00057 #include <stdio.h> 00058 #include <stdlib.h> 00059 #include <string.h> 00060 00061 #if LWIP_TCP && LWIP_CALLBACK_API 00062 00063 /** 00064 * HTTPC_DEBUG: Enable debugging for HTTP client. 00065 */ 00066 #ifndef HTTPC_DEBUG 00067 #define HTTPC_DEBUG LWIP_DBG_OFF 00068 #endif 00069 00070 /** Set this to 1 to keep server name and uri in request state */ 00071 #ifndef HTTPC_DEBUG_REQUEST 00072 #define HTTPC_DEBUG_REQUEST 0 00073 #endif 00074 00075 /** This string is passed in the HTTP header as "User-Agent: " */ 00076 #ifndef HTTPC_CLIENT_AGENT 00077 #define HTTPC_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)" 00078 #endif 00079 00080 /* the various debug levels for this file */ 00081 #define HTTPC_DEBUG_TRACE (HTTPC_DEBUG | LWIP_DBG_TRACE) 00082 #define HTTPC_DEBUG_STATE (HTTPC_DEBUG | LWIP_DBG_STATE) 00083 #define HTTPC_DEBUG_WARN (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING) 00084 #define HTTPC_DEBUG_WARN_STATE (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE) 00085 #define HTTPC_DEBUG_SERIOUS (HTTPC_DEBUG | LWIP_DBG_LEVEL_SERIOUS) 00086 00087 #define HTTPC_POLL_INTERVAL 1 00088 #define HTTPC_POLL_TIMEOUT 30 /* 15 seconds */ 00089 00090 #define HTTPC_CONTENT_LEN_INVALID 0xFFFFFFFF 00091 00092 /* GET request basic */ 00093 #define HTTPC_REQ_11 "GET %s HTTP/1.1\r\n" /* URI */\ 00094 "User-Agent: %s\r\n" /* User-Agent */ \ 00095 "Accept: */*\r\n" \ 00096 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 00097 "\r\n" 00098 #define HTTPC_REQ_11_FORMAT(uri) HTTPC_REQ_11, uri, HTTPC_CLIENT_AGENT 00099 00100 /* GET request with host */ 00101 #define HTTPC_REQ_11_HOST "GET %s HTTP/1.1\r\n" /* URI */\ 00102 "User-Agent: %s\r\n" /* User-Agent */ \ 00103 "Accept: */*\r\n" \ 00104 "Host: %s\r\n" /* server name */ \ 00105 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 00106 "\r\n" 00107 #define HTTPC_REQ_11_HOST_FORMAT(uri, srv_name) HTTPC_REQ_11_HOST, uri, HTTPC_CLIENT_AGENT, srv_name 00108 00109 /* GET request with proxy */ 00110 #define HTTPC_REQ_11_PROXY "GET http://%s%s HTTP/1.1\r\n" /* HOST, URI */\ 00111 "User-Agent: %s\r\n" /* User-Agent */ \ 00112 "Accept: */*\r\n" \ 00113 "Host: %s\r\n" /* server name */ \ 00114 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 00115 "\r\n" 00116 #define HTTPC_REQ_11_PROXY_FORMAT(host, uri, srv_name) HTTPC_REQ_11_PROXY, host, uri, HTTPC_CLIENT_AGENT, srv_name 00117 00118 /* GET request with proxy (non-default server port) */ 00119 #define HTTPC_REQ_11_PROXY_PORT "GET http://%s:%d%s HTTP/1.1\r\n" /* HOST, host-port, URI */\ 00120 "User-Agent: %s\r\n" /* User-Agent */ \ 00121 "Accept: */*\r\n" \ 00122 "Host: %s\r\n" /* server name */ \ 00123 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 00124 "\r\n" 00125 #define HTTPC_REQ_11_PROXY_PORT_FORMAT(host, host_port, uri, srv_name) HTTPC_REQ_11_PROXY_PORT, host, host_port, uri, HTTPC_CLIENT_AGENT, srv_name 00126 00127 typedef enum ehttpc_parse_state { 00128 HTTPC_PARSE_WAIT_FIRST_LINE = 0, 00129 HTTPC_PARSE_WAIT_HEADERS, 00130 HTTPC_PARSE_RX_DATA 00131 } httpc_parse_state_t; 00132 00133 typedef struct _httpc_state 00134 { 00135 struct altcp_pcb* pcb; 00136 ip_addr_t remote_addr; 00137 u16_t remote_port; 00138 int timeout_ticks; 00139 struct pbuf *request; 00140 struct pbuf *rx_hdrs; 00141 u16_t rx_http_version; 00142 u16_t rx_status; 00143 altcp_recv_fn recv_fn; 00144 const httpc_connection_t *conn_settings; 00145 void* callback_arg; 00146 u32_t rx_content_len; 00147 u32_t hdr_content_len; 00148 httpc_parse_state_t parse_state; 00149 #if HTTPC_DEBUG_REQUEST 00150 char* server_name; 00151 char* uri; 00152 #endif 00153 } httpc_state_t; 00154 00155 /** Free http client state and deallocate all resources within */ 00156 static err_t 00157 httpc_free_state(httpc_state_t* req) 00158 { 00159 struct altcp_pcb* tpcb; 00160 00161 if (req->request != NULL) { 00162 pbuf_free(req->request); 00163 req->request = NULL; 00164 } 00165 if (req->rx_hdrs != NULL) { 00166 pbuf_free(req->rx_hdrs); 00167 req->rx_hdrs = NULL; 00168 } 00169 00170 tpcb = req->pcb; 00171 mem_free(req); 00172 req = NULL; 00173 00174 if (tpcb != NULL) { 00175 err_t r; 00176 altcp_arg (tpcb, NULL); 00177 altcp_recv (tpcb, NULL); 00178 altcp_err (tpcb, NULL); 00179 altcp_poll (tpcb, NULL, 0); 00180 altcp_sent (tpcb, NULL); 00181 r = altcp_close (tpcb); 00182 if (r != ERR_OK) { 00183 altcp_abort (tpcb); 00184 return ERR_ABRT; 00185 } 00186 } 00187 return ERR_OK; 00188 } 00189 00190 /** Close the connection: call finished callback and free the state */ 00191 static err_t 00192 httpc_close(httpc_state_t* req, httpc_result_t result, u32_t server_response, err_t err) 00193 { 00194 if (req != NULL) { 00195 if (req->conn_settings != NULL) { 00196 if (req->conn_settings->result_fn != NULL) { 00197 req->conn_settings->result_fn(req->callback_arg, result, req->rx_content_len, server_response, err); 00198 } 00199 } 00200 return httpc_free_state(req); 00201 } 00202 return ERR_OK; 00203 } 00204 00205 /** Parse http header response line 1 */ 00206 static err_t 00207 http_parse_response_status(struct pbuf *p, u16_t *http_version, u16_t *http_status, u16_t *http_status_str_offset) 00208 { 00209 u16_t end1 = pbuf_memfind(p, "\r\n", 2, 0); 00210 if (end1 != 0xFFFF) { 00211 /* get parts of first line */ 00212 u16_t space1, space2; 00213 space1 = pbuf_memfind(p, " ", 1, 0); 00214 if (space1 != 0xFFFF) { 00215 if ((pbuf_memcmp(p, 0, "HTTP/", 5) == 0) && (pbuf_get_at(p, 6) == '.')) { 00216 char status_num[10]; 00217 size_t status_num_len; 00218 /* parse http version */ 00219 u16_t version = pbuf_get_at(p, 5) - '0'; 00220 version <<= 8; 00221 version |= pbuf_get_at(p, 7) - '0'; 00222 *http_version = version; 00223 00224 /* parse http status number */ 00225 space2 = pbuf_memfind(p, " ", 1, space1 + 1); 00226 if (space2 != 0xFFFF) { 00227 *http_status_str_offset = space2 + 1; 00228 status_num_len = space2 - space1 - 1; 00229 } else { 00230 status_num_len = end1 - space1 - 1; 00231 } 00232 memset(status_num, 0, sizeof(status_num)); 00233 if (pbuf_copy_partial(p, status_num, (u16_t)status_num_len, space1 + 1) == status_num_len) { 00234 int status = atoi(status_num); 00235 if ((status > 0) && (status <= 0xFFFF)) { 00236 *http_status = (u16_t)status; 00237 return ERR_OK; 00238 } 00239 } 00240 } 00241 } 00242 } 00243 return ERR_VAL; 00244 } 00245 00246 /** Wait for all headers to be received, return its length and content-length (if available) */ 00247 static err_t 00248 http_wait_headers(struct pbuf *p, u32_t *content_length, u16_t *total_header_len) 00249 { 00250 u16_t end1 = pbuf_memfind(p, "\r\n\r\n", 4, 0); 00251 if (end1 < (0xFFFF - 2)) { 00252 /* all headers received */ 00253 /* check if we have a content length (@todo: case insensitive?) */ 00254 u16_t content_len_hdr; 00255 *content_length = HTTPC_CONTENT_LEN_INVALID; 00256 *total_header_len = end1 + 4; 00257 00258 content_len_hdr = pbuf_memfind(p, "Content-Length: ", 16, 0); 00259 if (content_len_hdr != 0xFFFF) { 00260 u16_t content_len_line_end = pbuf_memfind(p, "\r\n", 2, content_len_hdr); 00261 if (content_len_line_end != 0xFFFF) { 00262 char content_len_num[16]; 00263 u16_t content_len_num_len = (u16_t)(content_len_line_end - content_len_hdr - 16); 00264 memset(content_len_num, 0, sizeof(content_len_num)); 00265 if (pbuf_copy_partial(p, content_len_num, content_len_num_len, content_len_hdr + 16) == content_len_num_len) { 00266 int len = atoi(content_len_num); 00267 if ((len >= 0) && ((u32_t)len < HTTPC_CONTENT_LEN_INVALID)) { 00268 *content_length = (u32_t)len; 00269 } 00270 } 00271 } 00272 } 00273 return ERR_OK; 00274 } 00275 return ERR_VAL; 00276 } 00277 00278 /** http client tcp recv callback */ 00279 static err_t 00280 httpc_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t r) 00281 { 00282 httpc_state_t* req = (httpc_state_t*)arg; 00283 LWIP_UNUSED_ARG(r); 00284 00285 if (p == NULL) { 00286 httpc_result_t result; 00287 if (req->parse_state != HTTPC_PARSE_RX_DATA) { 00288 /* did not get RX data yet */ 00289 result = HTTPC_RESULT_ERR_CLOSED; 00290 } else if ((req->hdr_content_len != HTTPC_CONTENT_LEN_INVALID) && 00291 (req->hdr_content_len != req->rx_content_len)) { 00292 /* header has been received with content length but not all data received */ 00293 result = HTTPC_RESULT_ERR_CONTENT_LEN; 00294 } else { 00295 /* receiving data and either all data received or no content length header */ 00296 result = HTTPC_RESULT_OK; 00297 } 00298 return httpc_close(req, result, req->rx_status, ERR_OK); 00299 } 00300 if (req->parse_state != HTTPC_PARSE_RX_DATA) { 00301 if (req->rx_hdrs == NULL) { 00302 req->rx_hdrs = p; 00303 } else { 00304 pbuf_cat(req->rx_hdrs, p); 00305 } 00306 if (req->parse_state == HTTPC_PARSE_WAIT_FIRST_LINE) { 00307 u16_t status_str_off; 00308 err_t err = http_parse_response_status(req->rx_hdrs, &req->rx_http_version, &req->rx_status, &status_str_off); 00309 if (err == ERR_OK) { 00310 /* don't care status string */ 00311 req->parse_state = HTTPC_PARSE_WAIT_HEADERS; 00312 } 00313 } 00314 if (req->parse_state == HTTPC_PARSE_WAIT_HEADERS) { 00315 u16_t total_header_len; 00316 err_t err = http_wait_headers(req->rx_hdrs, &req->hdr_content_len, &total_header_len); 00317 if (err == ERR_OK) { 00318 struct pbuf *q; 00319 /* full header received, send window update for header bytes and call into client callback */ 00320 altcp_recved (pcb, total_header_len); 00321 if (req->conn_settings) { 00322 if (req->conn_settings->headers_done_fn) { 00323 err = req->conn_settings->headers_done_fn(req, req->callback_arg, req->rx_hdrs, total_header_len, req->hdr_content_len); 00324 if (err != ERR_OK) { 00325 return httpc_close(req, HTTPC_RESULT_LOCAL_ABORT, req->rx_status, err); 00326 } 00327 } 00328 } 00329 /* hide header bytes in pbuf */ 00330 q = pbuf_free_header(req->rx_hdrs, total_header_len); 00331 p = q; 00332 req->rx_hdrs = NULL; 00333 /* go on with data */ 00334 req->parse_state = HTTPC_PARSE_RX_DATA; 00335 } 00336 } 00337 } 00338 if ((p != NULL) && (req->parse_state == HTTPC_PARSE_RX_DATA)) { 00339 req->rx_content_len += p->tot_len; 00340 if (req->recv_fn != NULL) { 00341 /* directly return here: the connection migth already be aborted from the callback! */ 00342 return req->recv_fn(req->callback_arg, pcb, p, r); 00343 } else { 00344 altcp_recved (pcb, p->tot_len); 00345 pbuf_free(p); 00346 } 00347 } 00348 return ERR_OK; 00349 } 00350 00351 /** http client tcp err callback */ 00352 static void 00353 httpc_tcp_err(void *arg, err_t err) 00354 { 00355 httpc_state_t* req = (httpc_state_t*)arg; 00356 if (req != NULL) { 00357 /* pcb has already been deallocated */ 00358 req->pcb = NULL; 00359 httpc_close(req, HTTPC_RESULT_ERR_CLOSED, 0, err); 00360 } 00361 } 00362 00363 /** http client tcp poll callback */ 00364 static err_t 00365 httpc_tcp_poll(void *arg, struct altcp_pcb *pcb) 00366 { 00367 /* implement timeout */ 00368 httpc_state_t* req = (httpc_state_t*)arg; 00369 LWIP_UNUSED_ARG(pcb); 00370 if (req != NULL) { 00371 if (req->timeout_ticks) { 00372 req->timeout_ticks--; 00373 } 00374 if (!req->timeout_ticks) { 00375 return httpc_close(req, HTTPC_RESULT_ERR_TIMEOUT, 0, ERR_OK); 00376 } 00377 } 00378 return ERR_OK; 00379 } 00380 00381 /** http client tcp sent callback */ 00382 static err_t 00383 httpc_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len) 00384 { 00385 /* nothing to do here for now */ 00386 LWIP_UNUSED_ARG(arg); 00387 LWIP_UNUSED_ARG(pcb); 00388 LWIP_UNUSED_ARG(len); 00389 return ERR_OK; 00390 } 00391 00392 /** http client tcp connected callback */ 00393 static err_t 00394 httpc_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err) 00395 { 00396 err_t r; 00397 httpc_state_t* req = (httpc_state_t*)arg; 00398 LWIP_UNUSED_ARG(pcb); 00399 LWIP_UNUSED_ARG(err); 00400 00401 /* send request; last char is zero termination */ 00402 r = altcp_write (req->pcb, req->request->payload, req->request->len - 1, TCP_WRITE_FLAG_COPY); 00403 if (r != ERR_OK) { 00404 /* could not write the single small request -> fail, don't retry */ 00405 return httpc_close(req, HTTPC_RESULT_ERR_MEM, 0, r); 00406 } 00407 /* everything written, we can free the request */ 00408 pbuf_free(req->request); 00409 req->request = NULL; 00410 00411 altcp_output (req->pcb); 00412 return ERR_OK; 00413 } 00414 00415 /** Start the http request when the server IP addr is known */ 00416 static err_t 00417 httpc_get_internal_addr(httpc_state_t* req, const ip_addr_t *ipaddr) 00418 { 00419 err_t err; 00420 LWIP_ASSERT("req != NULL", req != NULL); 00421 00422 if (&req->remote_addr != ipaddr) { 00423 /* fill in remote addr if called externally */ 00424 req->remote_addr = *ipaddr; 00425 } 00426 00427 err = altcp_connect (req->pcb, &req->remote_addr, req->remote_port, httpc_tcp_connected); 00428 if (err == ERR_OK) { 00429 return ERR_OK; 00430 } 00431 LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err)); 00432 return err; 00433 } 00434 00435 #if LWIP_DNS 00436 /** DNS callback 00437 * If ipaddr is non-NULL, resolving succeeded and the request can be sent, otherwise it failed. 00438 */ 00439 static void 00440 httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg) 00441 { 00442 httpc_state_t* req = (httpc_state_t*)arg; 00443 err_t err; 00444 httpc_result_t result; 00445 00446 LWIP_UNUSED_ARG(hostname); 00447 00448 if (ipaddr != NULL) { 00449 err = httpc_get_internal_addr(req, ipaddr); 00450 if (err == ERR_OK) { 00451 return; 00452 } 00453 result = HTTPC_RESULT_ERR_CONNECT; 00454 } else { 00455 LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("httpc_dns_found: failed to resolve hostname: %s\n", 00456 hostname)); 00457 result = HTTPC_RESULT_ERR_HOSTNAME; 00458 err = ERR_ARG; 00459 } 00460 httpc_close(req, result, 0, err); 00461 } 00462 #endif /* LWIP_DNS */ 00463 00464 /** Start the http request after converting 'server_name' to ip address (DNS or address string) */ 00465 static err_t 00466 httpc_get_internal_dns(httpc_state_t* req, const char* server_name) 00467 { 00468 err_t err; 00469 LWIP_ASSERT("req != NULL", req != NULL); 00470 00471 #if LWIP_DNS 00472 err = dns_gethostbyname(server_name, &req->remote_addr, httpc_dns_found, req); 00473 #else 00474 err = ipaddr_aton(server_name, &req->remote_addr) ? ERR_OK : ERR_ARG; 00475 #endif 00476 00477 if (err == ERR_OK) { 00478 /* cached or IP-string */ 00479 err = httpc_get_internal_addr(req, &req->remote_addr); 00480 } else if (err == ERR_INPROGRESS) { 00481 return ERR_OK; 00482 } 00483 return err; 00484 } 00485 00486 static int 00487 httpc_create_request_string(const httpc_connection_t *settings, const char* server_name, int server_port, const char* uri, 00488 int use_host, char *buffer, size_t buffer_size) 00489 { 00490 if (settings->use_proxy) { 00491 LWIP_ASSERT("server_name != NULL", server_name != NULL); 00492 if (server_port != HTTP_DEFAULT_PORT) { 00493 return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_PORT_FORMAT(server_name, server_port, uri, server_name)); 00494 } else { 00495 return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_FORMAT(server_name, uri, server_name)); 00496 } 00497 } else if (use_host) { 00498 LWIP_ASSERT("server_name != NULL", server_name != NULL); 00499 return snprintf(buffer, buffer_size, HTTPC_REQ_11_HOST_FORMAT(uri, server_name)); 00500 } else { 00501 return snprintf(buffer, buffer_size, HTTPC_REQ_11_FORMAT(uri)); 00502 } 00503 } 00504 00505 /** Initialize the connection struct */ 00506 static err_t 00507 httpc_init_connection_common(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name, 00508 u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg, int use_host) 00509 { 00510 size_t alloc_len; 00511 mem_size_t mem_alloc_len; 00512 int req_len, req_len2; 00513 httpc_state_t *req; 00514 #if HTTPC_DEBUG_REQUEST 00515 size_t server_name_len, uri_len; 00516 #endif 00517 00518 LWIP_ASSERT("uri != NULL", uri != NULL); 00519 00520 /* get request len */ 00521 req_len = httpc_create_request_string(settings, server_name, server_port, uri, use_host, NULL, 0); 00522 if ((req_len < 0) || (req_len > 0xFFFF)) { 00523 return ERR_VAL; 00524 } 00525 /* alloc state and request in one block */ 00526 alloc_len = sizeof(httpc_state_t); 00527 #if HTTPC_DEBUG_REQUEST 00528 server_name_len = server_name ? strlen(server_name) : 0; 00529 uri_len = strlen(uri); 00530 alloc_len += server_name_len + 1 + uri_len + 1; 00531 #endif 00532 mem_alloc_len = (mem_size_t)alloc_len; 00533 if ((mem_alloc_len < alloc_len) || (req_len + 1 > 0xFFFF)) { 00534 return ERR_VAL; 00535 } 00536 00537 req = (httpc_state_t*)mem_malloc((mem_size_t)alloc_len); 00538 if(req == NULL) { 00539 return ERR_MEM; 00540 } 00541 memset(req, 0, sizeof(httpc_state_t)); 00542 req->timeout_ticks = HTTPC_POLL_TIMEOUT; 00543 req->request = pbuf_alloc(PBUF_RAW, (u16_t)(req_len + 1), PBUF_RAM); 00544 if (req->request == NULL) { 00545 httpc_free_state(req); 00546 return ERR_MEM; 00547 } 00548 if (req->request->next != NULL) { 00549 /* need a pbuf in one piece */ 00550 httpc_free_state(req); 00551 return ERR_MEM; 00552 } 00553 req->hdr_content_len = HTTPC_CONTENT_LEN_INVALID; 00554 #if HTTPC_DEBUG_REQUEST 00555 req->server_name = (char*)(req + 1); 00556 if (server_name) { 00557 memcpy(req->server_name, server_name, server_name_len + 1); 00558 } 00559 req->uri = req->server_name + server_name_len + 1; 00560 memcpy(req->uri, uri, uri_len + 1); 00561 #endif 00562 req->pcb = altcp_new(settings->altcp_allocator); 00563 if(req->pcb == NULL) { 00564 httpc_free_state(req); 00565 return ERR_MEM; 00566 } 00567 req->remote_port = settings->use_proxy ? settings->proxy_port : server_port; 00568 altcp_arg (req->pcb, req); 00569 altcp_recv (req->pcb, httpc_tcp_recv); 00570 altcp_err (req->pcb, httpc_tcp_err); 00571 altcp_poll (req->pcb, httpc_tcp_poll, HTTPC_POLL_INTERVAL); 00572 altcp_sent (req->pcb, httpc_tcp_sent); 00573 00574 /* set up request buffer */ 00575 req_len2 = httpc_create_request_string(settings, server_name, server_port, uri, use_host, 00576 (char *)req->request->payload, req_len + 1); 00577 if (req_len2 != req_len) { 00578 httpc_free_state(req); 00579 return ERR_VAL; 00580 } 00581 00582 req->recv_fn = recv_fn; 00583 req->conn_settings = settings; 00584 req->callback_arg = callback_arg; 00585 00586 *connection = req; 00587 return ERR_OK; 00588 } 00589 00590 /** 00591 * Initialize the connection struct 00592 */ 00593 static err_t 00594 httpc_init_connection(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name, 00595 u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg) 00596 { 00597 return httpc_init_connection_common(connection, settings, server_name, server_port, uri, recv_fn, callback_arg, 1); 00598 } 00599 00600 00601 /** 00602 * Initialize the connection struct (from IP address) 00603 */ 00604 static err_t 00605 httpc_init_connection_addr(httpc_state_t **connection, const httpc_connection_t *settings, 00606 const ip_addr_t* server_addr, u16_t server_port, const char* uri, 00607 altcp_recv_fn recv_fn, void* callback_arg) 00608 { 00609 char *server_addr_str = ipaddr_ntoa(server_addr); 00610 if (server_addr_str == NULL) { 00611 return ERR_VAL; 00612 } 00613 return httpc_init_connection_common(connection, settings, server_addr_str, server_port, uri, 00614 recv_fn, callback_arg, 1); 00615 } 00616 00617 /** 00618 * @ingroup httpc 00619 * HTTP client API: get a file by passing server IP address 00620 * 00621 * @param server_addr IP address of the server to connect 00622 * @param port tcp port of the server 00623 * @param uri uri to get from the server, remember leading "/"! 00624 * @param settings connection settings (callbacks, proxy, etc.) 00625 * @param recv_fn the http body (not the headers) are passed to this callback 00626 * @param callback_arg argument passed to all the callbacks 00627 * @param connection retreives the connection handle (to match in callbacks) 00628 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 00629 * or an error code 00630 */ 00631 err_t 00632 httpc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings, 00633 altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection) 00634 { 00635 err_t err; 00636 httpc_state_t* req; 00637 00638 LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;); 00639 00640 err = httpc_init_connection_addr(&req, settings, server_addr, port, 00641 uri, recv_fn, callback_arg); 00642 if (err != ERR_OK) { 00643 return err; 00644 } 00645 00646 if (settings->use_proxy) { 00647 err = httpc_get_internal_addr(req, &settings->proxy_addr); 00648 } else { 00649 err = httpc_get_internal_addr(req, server_addr); 00650 } 00651 if(err != ERR_OK) { 00652 httpc_free_state(req); 00653 return err; 00654 } 00655 00656 if (connection != NULL) { 00657 *connection = req; 00658 } 00659 return ERR_OK; 00660 } 00661 00662 /** 00663 * @ingroup httpc 00664 * HTTP client API: get a file by passing server name as string (DNS name or IP address string) 00665 * 00666 * @param server_name server name as string (DNS name or IP address string) 00667 * @param port tcp port of the server 00668 * @param uri uri to get from the server, remember leading "/"! 00669 * @param settings connection settings (callbacks, proxy, etc.) 00670 * @param recv_fn the http body (not the headers) are passed to this callback 00671 * @param callback_arg argument passed to all the callbacks 00672 * @param connection retreives the connection handle (to match in callbacks) 00673 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 00674 * or an error code 00675 */ 00676 err_t 00677 httpc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings, 00678 altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection) 00679 { 00680 err_t err; 00681 httpc_state_t* req; 00682 00683 LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;); 00684 00685 err = httpc_init_connection(&req, settings, server_name, port, uri, recv_fn, callback_arg); 00686 if (err != ERR_OK) { 00687 return err; 00688 } 00689 00690 if (settings->use_proxy) { 00691 err = httpc_get_internal_addr(req, &settings->proxy_addr); 00692 } else { 00693 err = httpc_get_internal_dns(req, server_name); 00694 } 00695 if(err != ERR_OK) { 00696 httpc_free_state(req); 00697 return err; 00698 } 00699 00700 if (connection != NULL) { 00701 *connection = req; 00702 } 00703 return ERR_OK; 00704 } 00705 00706 #if LWIP_HTTPC_HAVE_FILE_IO 00707 /* Implementation to disk via fopen/fwrite/fclose follows */ 00708 00709 typedef struct _httpc_filestate 00710 { 00711 const char* local_file_name; 00712 FILE *file; 00713 httpc_connection_t settings; 00714 const httpc_connection_t *client_settings; 00715 void *callback_arg; 00716 } httpc_filestate_t; 00717 00718 static void httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, 00719 u32_t srv_res, err_t err); 00720 00721 /** Initalize http client state for download to file system */ 00722 static err_t 00723 httpc_fs_init(httpc_filestate_t **filestate_out, const char* local_file_name, 00724 const httpc_connection_t *settings, void* callback_arg) 00725 { 00726 httpc_filestate_t *filestate; 00727 size_t file_len, alloc_len; 00728 FILE *f; 00729 00730 file_len = strlen(local_file_name); 00731 alloc_len = sizeof(httpc_filestate_t) + file_len + 1; 00732 00733 filestate = (httpc_filestate_t *)mem_malloc((mem_size_t)alloc_len); 00734 if (filestate == NULL) { 00735 return ERR_MEM; 00736 } 00737 memset(filestate, 0, sizeof(httpc_filestate_t)); 00738 filestate->local_file_name = (const char *)(filestate + 1); 00739 memcpy((char *)(filestate + 1), local_file_name, file_len + 1); 00740 filestate->file = NULL; 00741 filestate->client_settings = settings; 00742 filestate->callback_arg = callback_arg; 00743 /* copy client settings but override result callback */ 00744 memcpy(&filestate->settings, settings, sizeof(httpc_connection_t)); 00745 filestate->settings.result_fn = httpc_fs_result; 00746 00747 f = fopen(local_file_name, "wb"); 00748 if(f == NULL) { 00749 /* could not open file */ 00750 mem_free(filestate); 00751 return ERR_VAL; 00752 } 00753 filestate->file = f; 00754 *filestate_out = filestate; 00755 return ERR_OK; 00756 } 00757 00758 /** Free http client state for download to file system */ 00759 static void 00760 httpc_fs_free(httpc_filestate_t *filestate) 00761 { 00762 if (filestate != NULL) { 00763 if (filestate->file != NULL) { 00764 fclose(filestate->file); 00765 filestate->file = NULL; 00766 } 00767 mem_free(filestate); 00768 } 00769 } 00770 00771 /** Connection closed (success or error) */ 00772 static void 00773 httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, 00774 u32_t srv_res, err_t err) 00775 { 00776 httpc_filestate_t *filestate = (httpc_filestate_t *)arg; 00777 if (filestate != NULL) { 00778 if (filestate->client_settings->result_fn != NULL) { 00779 filestate->client_settings->result_fn(filestate->callback_arg, httpc_result, rx_content_len, 00780 srv_res, err); 00781 } 00782 httpc_fs_free(filestate); 00783 } 00784 } 00785 00786 /** tcp recv callback */ 00787 static err_t 00788 httpc_fs_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) 00789 { 00790 httpc_filestate_t *filestate = (httpc_filestate_t*)arg; 00791 struct pbuf* q; 00792 LWIP_UNUSED_ARG(err); 00793 00794 LWIP_ASSERT("p != NULL", p != NULL); 00795 00796 for (q = p; q != NULL; q = q->next) { 00797 fwrite(q->payload, 1, q->len, filestate->file); 00798 } 00799 altcp_recved (pcb, p->tot_len); 00800 pbuf_free(p); 00801 return ERR_OK; 00802 } 00803 00804 /** 00805 * @ingroup httpc 00806 * HTTP client API: get a file to disk by passing server IP address 00807 * 00808 * @param server_addr IP address of the server to connect 00809 * @param port tcp port of the server 00810 * @param uri uri to get from the server, remember leading "/"! 00811 * @param settings connection settings (callbacks, proxy, etc.) 00812 * @param callback_arg argument passed to all the callbacks 00813 * @param connection retreives the connection handle (to match in callbacks) 00814 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 00815 * or an error code 00816 */ 00817 err_t 00818 httpc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings, 00819 void* callback_arg, const char* local_file_name, httpc_state_t **connection) 00820 { 00821 err_t err; 00822 httpc_state_t* req; 00823 httpc_filestate_t *filestate; 00824 00825 LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;); 00826 00827 err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg); 00828 if (err != ERR_OK) { 00829 return err; 00830 } 00831 00832 err = httpc_init_connection_addr(&req, &filestate->settings, server_addr, port, 00833 uri, httpc_fs_tcp_recv, filestate); 00834 if (err != ERR_OK) { 00835 httpc_fs_free(filestate); 00836 return err; 00837 } 00838 00839 if (settings->use_proxy) { 00840 err = httpc_get_internal_addr(req, &settings->proxy_addr); 00841 } else { 00842 err = httpc_get_internal_addr(req, server_addr); 00843 } 00844 if(err != ERR_OK) { 00845 httpc_fs_free(filestate); 00846 httpc_free_state(req); 00847 return err; 00848 } 00849 00850 if (connection != NULL) { 00851 *connection = req; 00852 } 00853 return ERR_OK; 00854 } 00855 00856 /** 00857 * @ingroup httpc 00858 * HTTP client API: get a file to disk by passing server name as string (DNS name or IP address string) 00859 * 00860 * @param server_name server name as string (DNS name or IP address string) 00861 * @param port tcp port of the server 00862 * @param uri uri to get from the server, remember leading "/"! 00863 * @param settings connection settings (callbacks, proxy, etc.) 00864 * @param callback_arg argument passed to all the callbacks 00865 * @param connection retreives the connection handle (to match in callbacks) 00866 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 00867 * or an error code 00868 */ 00869 err_t 00870 httpc_get_file_dns_to_disk(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings, 00871 void* callback_arg, const char* local_file_name, httpc_state_t **connection) 00872 { 00873 err_t err; 00874 httpc_state_t* req; 00875 httpc_filestate_t *filestate; 00876 00877 LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;); 00878 00879 err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg); 00880 if (err != ERR_OK) { 00881 return err; 00882 } 00883 00884 err = httpc_init_connection(&req, &filestate->settings, server_name, port, 00885 uri, httpc_fs_tcp_recv, filestate); 00886 if (err != ERR_OK) { 00887 httpc_fs_free(filestate); 00888 return err; 00889 } 00890 00891 if (settings->use_proxy) { 00892 err = httpc_get_internal_addr(req, &settings->proxy_addr); 00893 } else { 00894 err = httpc_get_internal_dns(req, server_name); 00895 } 00896 if(err != ERR_OK) { 00897 httpc_fs_free(filestate); 00898 httpc_free_state(req); 00899 return err; 00900 } 00901 00902 if (connection != NULL) { 00903 *connection = req; 00904 } 00905 return ERR_OK; 00906 } 00907 #endif /* LWIP_HTTPC_HAVE_FILE_IO */ 00908 00909 #endif /* LWIP_TCP && LWIP_CALLBACK_API */
Generated on Tue Jul 12 2022 13:54:28 by
1.7.2