From f64bbb94d742ae63d2926956ce2e7eb81c699ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 24 Apr 2016 22:54:29 +0200 Subject: [PATCH] working xively report Former-commit-id: eea29fc09155b1be61b49b02e319554f1040991a --- esphttpclient/httpclient.c | 145 +++++++++++++++++++++--------------- esphttpclient/httpclient.h | 34 +++++---- libesphttpd/core/httpd.c | 24 ++++-- libesphttpd/include/httpd.h | 17 +++-- user/reporting.c | 24 +++++- 5 files changed, 153 insertions(+), 91 deletions(-) diff --git a/esphttpclient/httpclient.c b/esphttpclient/httpclient.c index 9a7d1ce..995b3b0 100644 --- a/esphttpclient/httpclient.c +++ b/esphttpclient/httpclient.c @@ -10,6 +10,7 @@ // FIXME: sprintf->snprintf everywhere. #include +#include #include "httpclient.h" #include @@ -27,6 +28,7 @@ typedef struct { httpclient_cb user_callback; int timeout; os_timer_t timeout_timer; + http_method method; } request_args; static char *FLASH_FN esp_strdup(const char *str) @@ -247,14 +249,18 @@ static void FLASH_FN connect_callback(void * arg) espconn_regist_recvcb(conn, receive_callback); espconn_regist_sentcb(conn, sent_callback); - const char *method = "GET"; char post_headers[32] = ""; if (req->post_data != NULL) { // If there is data this is a POST request. - method = "POST"; sprintf(post_headers, "Content-Length: %d\r\n", strlen(req->post_data)); + + if (req->method == HTTP_GET) { + req->method = HTTP_POST; + } } + const char* method = http_method_str(req->method); + if (req->headers == NULL) { /* Avoid NULL pointer, it may cause exception */ req->headers = (char *)malloc(sizeof(char)); req->headers[0] = '\0'; @@ -438,68 +444,30 @@ static void FLASH_FN dns_callback(const char *hostname, ip_addr_t *addr, void *a } } -void FLASH_FN http_raw_request(const char *hostname, int port, bool secure, const char *path, const char *post_data, const char *headers, httpclient_cb user_callback) +bool FLASH_FN http_request( + const char *url, + http_method method, + const char *body, + const char *headers, + httpclient_cb user_callback) { - info("HTTP request: %s:%d%s", hostname, port, path); - - request_args * req = (request_args *)malloc(sizeof(request_args)); - req->hostname = esp_strdup(hostname); - req->path = esp_strdup(path); - req->port = port; - req->secure = secure; - req->headers = esp_strdup(headers); - req->post_data = esp_strdup(post_data); - req->buffer_size = 1; - req->buffer = (char *)malloc(1); - req->buffer[0] = '\0'; // Empty string. - req->user_callback = user_callback; - req->timeout = HTTP_REQUEST_TIMEOUT_MS; + // --- prepare port, secure... --- - ip_addr_t addr; - err_t error = espconn_gethostbyname((struct espconn *)req, // It seems we don't need a real espconn pointer here. - hostname, &addr, dns_callback); - - if (error == ESPCONN_INPROGRESS) { - dbg("DNS pending"); - } else if (error == ESPCONN_OK) { - // Already in the local names table (or hostname was an IP address), execute the callback ourselves. - dns_callback(hostname, &addr, req); - } else { - if (error == ESPCONN_ARG) { - error("DNS arg error %s", hostname); - } else { - error("DNS error code %d", error); - } - dns_callback(hostname, NULL, req); // Handle all DNS errors the same way. - } -} - -/* - * Parse an URL of the form http://host:port/path - * can be a hostname or an IP address - * is optional - */ -void FLASH_FN http_post(const char *url, const char *post_data, const char *headers, httpclient_cb user_callback) -{ // FIXME: handle HTTP auth with http://user:pass@host/ - // FIXME: get rid of the #anchor part if present. char hostname[128] = ""; int port = 80; bool secure = false; - bool is_http = strstarts(url, "http://"); - bool is_https = strstarts(url, "https://"); - - if (is_http) + if (strstarts(url, "http://")) url += strlen("http://"); // Get rid of the protocol. - else if (is_https) { + else if (strstarts(url, "https://")) { port = 443; secure = true; url += strlen("https://"); // Get rid of the protocol. } else { - error("URL is not HTTP or HTTPS %s", url); - return; + error("Invalid URL protocol: %s", url); + return false; } char *path = strchr(url, '/'); @@ -513,38 +481,91 @@ void FLASH_FN http_post(const char *url, const char *post_data, const char *head } if (colon == NULL) { // The port is not present. - memcpy(hostname, url, path - url); + memcpy(hostname, url, (size_t)(path - url)); hostname[path - url] = '\0'; } else { port = atoi(colon + 1); if (port == 0) { error("Port error %s\n", url); - return; + return false; } - memcpy(hostname, url, colon - url); + memcpy(hostname, url, (size_t)(colon - url)); hostname[colon - url] = '\0'; } - if (path[0] == '\0') { // Empty path is not allowed. path = "/"; } - http_raw_request(hostname, port, secure, path, post_data, headers, user_callback); + // --- + + info("HTTP request: %s:%d%s", hostname, port, path); + + request_args * req = (request_args *)malloc(sizeof(request_args)); + req->hostname = esp_strdup(hostname); + req->path = esp_strdup(path); + + // remove #anchor + char *hash = strchr(req->path, '#'); + if (hash != NULL) *hash = '\0'; // remove the hash part + + req->port = port; + req->secure = secure; + req->headers = esp_strdup(headers); + req->post_data = esp_strdup(body); + req->buffer_size = 1; + req->buffer = (char *)malloc(1); + req->buffer[0] = '\0'; // Empty string. + req->user_callback = user_callback; + req->timeout = HTTP_REQUEST_TIMEOUT_MS; + req->method = method; + + ip_addr_t addr; + err_t error = espconn_gethostbyname((struct espconn *)req, // It seems we don't need a real espconn pointer here. + hostname, &addr, dns_callback); + + if (error == ESPCONN_INPROGRESS) { + dbg("DNS pending"); + } else if (error == ESPCONN_OK) { + // Already in the local names table (or hostname was an IP address), execute the callback ourselves. + dns_callback(hostname, &addr, req); + } else { + if (error == ESPCONN_ARG) { + error("DNS arg error %s", hostname); + } else { + error("DNS error code %d", error); + } + dns_callback(hostname, NULL, req); // Handle all DNS errors the same way. + } + + return true; } -void FLASH_FN http_get(const char *url, const char *headers, httpclient_cb user_callback) + +bool FLASH_FN http_post(const char *url, const char *body, const char *headers, httpclient_cb user_callback) +{ + return http_request(url, HTTP_POST, body, headers, user_callback); +} + + +bool http_get(const char *url, const char *headers, httpclient_cb user_callback) { - http_post(url, NULL, headers, user_callback); + return http_request(url, HTTP_GET, NULL, headers, user_callback); } + +bool http_put(const char *url, const char *body, const char *headers, httpclient_cb user_callback) +{ + return http_request(url, HTTP_PUT, body, headers, user_callback); +} + + void FLASH_FN http_callback_example(char *response_body, int http_status, char *response_headers, int body_size) { - dbg("http_status=%d", http_status); + dbg("Response: code %d", http_status); if (http_status != HTTP_STATUS_GENERIC_ERROR) { - dbg("strlen(headers)=%d", (int)strlen(response_headers)); - dbg("body_size=%d", body_size); - dbg("body=%s", response_body); // FIXME: this does not handle binary data. + dbg("len(headers) = %d, len(body) = %d", (int)strlen(response_headers), body_size); + dbg("body: %s", response_body); // FIXME: this does not handle binary data. } } diff --git a/esphttpclient/httpclient.h b/esphttpclient/httpclient.h index 4871656..70041f3 100644 --- a/esphttpclient/httpclient.h +++ b/esphttpclient/httpclient.h @@ -11,6 +11,7 @@ #define HTTPCLIENT_H #include +#include #define HTTP_STATUS_GENERIC_ERROR -1 // In case of TCP or DNS error the callback is called with this status. #define BUFFER_SIZE_MAX 5000 // Size of http responses that will cause an error. @@ -27,34 +28,39 @@ * A successful request corresponds to an HTTP status code of 200 (OK). * More info at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes */ -typedef void (* httpclient_cb)(char * response_body, int http_status, char * response_headers, int body_size); +typedef void (* httpclient_cb)(char *response_body, int http_status, char *response_headers, int body_size); /** - * Download a web page from its URL. + * @brief Download a web page from its URL. + * * Try: * http_get("http://wtfismyip.com/text", http_callback_example); */ -void http_get(const char * url, const char *headers, httpclient_cb user_callback); +bool http_get(const char * url, const char *headers, httpclient_cb user_callback); /** - * Post data to a web form. + * @brief Post data to a web form. + * * The data should be encoded as application/x-www-form-urlencoded. + * * Try: * http_post("http://httpbin.org/post", "first_word=hello&second_word=world", http_callback_example); */ -void http_post(const char *url, const char *post_data, const char *headers, httpclient_cb user_callback); +bool http_post(const char *url, const char *post_data, const char *headers, httpclient_cb user_callback); + +/** Like POST, but with the PUT method. */ +bool http_put(const char *url, const char *body, const char *headers, httpclient_cb user_callback); /** - * Call this function to skip URL parsing if the arguments are already in separate variables. + * @brief Send a HTTP request + * @param url : protocol://host[:port][/path] + * @param method : get, post, ... + * @param body : request body. If GET & body != NULL, method changes to POST. + * @param headers : additional headers string. Must end with \r\n + * @param user_callback : callback for parsing the response + * @return success (in sending) */ -void http_raw_request( - const char * hostname, - int port, - bool secure, - const char *path, - const char *post_data, - const char *headers, - httpclient_cb user_callback); +bool http_request(const char *url, http_method method, const char *body, const char *headers, httpclient_cb user_callback); /** * Output on the UART. diff --git a/libesphttpd/core/httpd.c b/libesphttpd/core/httpd.c index a2adf4e..2131476 100644 --- a/libesphttpd/core/httpd.c +++ b/libesphttpd/core/httpd.c @@ -115,6 +115,20 @@ const char ICACHE_FLASH_ATTR *httpdGetMimetype(const char *filepath) { return mimeTypes[i].mimetype; } +const char* ICACHE_FLASH_ATTR http_method_str(http_method m) +{ + switch (m) { + default: + case HTTP_GET: return "GET"; + case HTTP_POST: return "POST"; + case HTTP_OPTIONS: return "OPTIONS"; + case HTTP_PUT: return "PUT"; + case HTTP_DELETE: return "DELETE"; + case HTTP_PATCH: return "PATCH"; + case HTTP_HEAD: return "HEAD"; + } +} + //Looks up the connData info for a specific connection static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(ConnTypePtr conn, const char *remIp, int remPort) { for (int i=0; irequestType == HTTPD_METHOD_OPTIONS /*&& conn->priv->chunkHdr[0] != 0*/) { + if (conn->requestType == HTTP_OPTIONS /*&& conn->priv->chunkHdr[0] != 0*/) { // we have a CORS preflight httpdStartResponse(conn, 200); httpdHeader(conn, "Access-Control-Allow-Headers", conn->priv->corsToken); @@ -589,16 +603,16 @@ static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) { char firstLine=0; if (strstarts(h, "GET ")) { - conn->requestType = HTTPD_METHOD_GET; + conn->requestType = HTTP_GET; firstLine=1; } else if (strstarts(h, "POST ")) { - conn->requestType = HTTPD_METHOD_POST; + conn->requestType = HTTP_POST; firstLine=1; } else if (strstarts(h, "PUT ")) { - conn->requestType = HTTPD_METHOD_PUT; + conn->requestType = HTTP_PUT; firstLine=1; } else if (strstarts(h, "OPTIONS ")) { - conn->requestType = HTTPD_METHOD_OPTIONS; + conn->requestType = HTTP_OPTIONS; firstLine=1; } else if (strstarts(h, "Host:")) { i=5; diff --git a/libesphttpd/include/httpd.h b/libesphttpd/include/httpd.h index bd4f7cd..0daecc7 100644 --- a/libesphttpd/include/httpd.h +++ b/libesphttpd/include/httpd.h @@ -13,15 +13,16 @@ typedef enum { } httpd_cgi_state; typedef enum { - HTTPD_METHOD_GET = 1, - HTTPD_METHOD_POST = 2, - HTTPD_METHOD_OPTIONS = 3, - HTTPD_METHOD_PUT = 4, - HTTPD_METHOD_DELETE = 5, - HTTPD_METHOD_PATCH = 6, - HTTPD_METHOD_HEAD = 7, + HTTP_GET = 1, + HTTP_POST = 2, + HTTP_OPTIONS = 3, + HTTP_PUT = 4, + HTTP_DELETE = 5, + HTTP_PATCH = 6, + HTTP_HEAD = 7, } http_method; + typedef struct HttpdPriv HttpdPriv; typedef struct HttpdConnData HttpdConnData; typedef struct HttpdPostData HttpdPostData; @@ -95,7 +96,7 @@ typedef struct { #define ROUTE_END() {NULL, NULL, NULL, NULL} - +const char *http_method_str(http_method m); httpd_cgi_state cgiRedirect(HttpdConnData *connData); httpd_cgi_state cgiRedirectToHostname(HttpdConnData *connData); diff --git a/user/reporting.c b/user/reporting.c index 20d1fdf..12e19d9 100644 --- a/user/reporting.c +++ b/user/reporting.c @@ -2,6 +2,7 @@ #include "datalink.h" #include "serial.h" #include "httpclient.h" +#include "ftoa.h" #define RPT_CONF_MAGIC 0x24C595D5 @@ -114,9 +115,28 @@ static void FLASH_FN compare_ref_cb(SBMP_Endpoint *ep, SBMP_Datagram *dg, void * static void FLASH_FN do_send_report(void) { info("Sending report..."); + + char buf[100]; + char *bb = buf; + + char url_buf[200]; + char hdrs_buf[100]; + switch (rpt_conf.service) { - case RPT_XIVELY: - warn("------- TODO: REPORT TO XIVELY -------"); + case RPT_XIVELY:; + bb += sprintf(bb, "deviation,"); + bb += my_ftoa(bb, rpt_result.deviation, 2); + bb += sprintf(bb, "\nI_rms,"); + bb += my_ftoa(bb, rpt_result.i_rms, 2); + + // URL + sprintf(url_buf, "http://api.xively.com/v2/feeds/%s.csv", rpt_conf.feed); + + // Key + sprintf(hdrs_buf, "X-ApiKey: %s\r\n", rpt_conf.key); + + http_put(url_buf, buf, hdrs_buf, http_callback_example); + break; case RPT_THINGSPEAK: