#include "http_client/http_client.h" #include "http_client/http_client_rx_callback.h" #include "http_client/http_client_headers.h" #include "http_client_configuration.h" #include "gsm/sim800c_tcpip.h" #include #include #define HTTP_PREFIX_LN 7 typedef enum http_client_state { HTTP_CLIENT_UNITIALIZED, HTTP_CLIENT_READY, HTTP_CLIENT_CONNECTED_IDLE, HTTP_CLIENT_WAITING_POST, HTTP_CLIENT_WAITING_GET } http_client_state_t; http_client_state_t http_client_state = HTTP_CLIENT_UNITIALIZED; http_client_response_available_t http_client_on_response_callback = 0; /** * Content lenght received from HTTP response headers or chunked encoding */ uint16_t http_client_content_lenght = 0; /** * Maximum content lenght which should be received by the client. Please bear in mind that THIS NOT include * HTTP headers lenght */ uint16_t http_client_max_content_ln = 0; /** * HTTP code returned by the latest query. It is zeroed after each successful call to async * function. This indicate that a request is currently in progress. Negative values means some * non HTTP error, like communication timeout or response longer than expected */ int16_t http_client_http_code = 0; /** * SIM800 state and serial context used to communication with gsm module. */ gsm_sim800_state_t * http_client_deticated_sim800_state; srl_context_t * http_client_deticated_serial_context; /** * If this is set to non zero, the library will stop response processing after HTTP code different from <200, 299> */ uint8_t http_client_ignore_content_on_http_error; /** * Default port for http */ const char * http_client_default_port = "80"; /** * Local, static buffers to fetch domain name or IP address from URI */ #define HOSTNAME_LN 32 #define PORT_LN 5 static char http_client_hostname[HOSTNAME_LN]; static char http_client_port[PORT_LN]; /** * Callback used when response has been received from HTTP server (or timeout occured) */ static void http_client_response_done_callback(srl_context_t* context) { gsm_sim800_tcpip_close(context, http_client_deticated_sim800_state, 1); if (http_client_on_response_callback != 0) { // execute a callback. addition '+1' is requires because 'http_client_content_end_index' points to the last character of response http_client_on_response_callback(http_client_http_code, (char *)(context->srl_rx_buf_pointer + http_client_content_start_index), http_client_content_end_index - http_client_content_start_index + 1); } } /** * This functions splits the URL string into hostname and path * * It return a split point index which in case of * http://pogoda.cc:8080/meteo_backend/station/z_gss_zar/summary * will return an index of '/' after 8080 * * */ static uint16_t http_client_split_hostname_and_path(char * input, uint16_t input_ln) { uint16_t out = 0xFFFF; uint16_t iterator = 7; // check if URL starts correctly if (*input == 'h' && *(input + 1) == 't' && *(input + 2) == 't' && *(input + 3) == 'p' && *(input + 4) == ':' && *(input + 5) == '/' && *(input + 6) == '/') { for (; iterator < input_ln; iterator++) { if (*(input + iterator) == '/') { out = iterator; break; } } } return out; } static uint16_t http_client_get_port_from_url(char * input, uint16_t input_ln, char * port, uint16_t port_ln) { char temp[5] = {0, 0, 0, 0, 0}; short i, j = 0; // get split point uint16_t split_point = http_client_split_hostname_and_path(input, input_ln); if (split_point != 0xFFFF) { // check last character before split point char last_character = *(input + split_point - 1); // clear target buffer memset (port, 0x00, port_ln); // if any port has been provided by a user if (last_character >= '0' && last_character <= '9' ) { // copy maximum of 5 characters until ':' for (i = 1; i < 6; i++) { // get current character last_character = *(input + split_point - i); // break on any non digit character if (last_character < '0' || last_character > '9') { break; } // copy to temporary buffer temp[i - 1] = last_character; } // copy port number into target buffer //memcpy(port, temp, 5); for (; i > 0 ; i--) { if (temp[i - 1] == 0) { continue; } port[j++] = temp[i - 1]; } } else { // copy default port memcpy(port, http_client_default_port, 2); } } return split_point; } static uint16_t http_client_get_address_from_url(char * input, uint16_t input_ln, char * address, uint16_t address_ln) { int first_index = 0, last_index = 0; // get split point uint16_t split_point = http_client_split_hostname_and_path(input, input_ln); // if split point is valid it also means that URL starts from 'http://' if (split_point != 0xFFFF) { // first position of an URL first_index = 7; // rewind to either ':' or split point for (last_index = first_index; last_index <= split_point; last_index ++ ) { // fetch current character char current_ = *(input + last_index); // check if if (current_ == ':' || current_ == '/') { // rewind one character current_--; break; } } // check if output fits data if (last_index - first_index < address_ln) { // if yes copy it memcpy(address, input + first_index, last_index - first_index); } } return split_point; } void http_client_init(gsm_sim800_state_t * state, srl_context_t * serial_context, uint8_t ignore_content_on_http_error) { http_client_deticated_sim800_state = state; http_client_deticated_serial_context = serial_context; http_client_state = HTTP_CLIENT_READY; } uint8_t http_client_async_get(char * url, uint8_t url_ln, uint16_t response_ln_limit, uint8_t force_disconnect_on_busy, http_client_response_available_t callback_on_response) { uint16_t split_point = http_client_split_hostname_and_path(url, url_ln); uint8_t out = 0; uint8_t connect_result = -1; uint16_t current_request_ln = 0; // simple check if url seems to be corrected or not if (split_point != 0xFFFF && http_client_state == HTTP_CLIENT_READY ) { // clear local buffers memset(http_client_hostname, 0x00, HOSTNAME_LN); memset(http_client_port, 0x00, PORT_LN); // check if module is busy on other TCP/IP connection if (*http_client_deticated_sim800_state == SIM800_TCP_CONNECTED && force_disconnect_on_busy != 0) { // if client is connected end a user wants to force disconnect gsm_sim800_tcpip_close(http_client_deticated_serial_context, http_client_deticated_sim800_state, 0); } else if (*http_client_deticated_sim800_state == SIM800_TCP_CONNECTED && force_disconnect_on_busy == 0) { out = HTTP_CLIENT_RET_TCPIP_BSY; } http_client_on_response_callback = callback_on_response; // get hotsname from URL (URI) http_client_get_address_from_url(url, url_ln, http_client_hostname, HOSTNAME_LN); http_client_get_port_from_url(url, url_ln, http_client_port, PORT_LN); // establish TCP connection to HTTP server connect_result = gsm_sim800_tcpip_connect(http_client_hostname, HOSTNAME_LN, http_client_port, PORT_LN, http_client_deticated_serial_context, http_client_deticated_sim800_state); // if connection has been established if (connect_result == 0) { // set appropriate state http_client_state = HTTP_CLIENT_CONNECTED_IDLE; // wait for any serial transmission to finish while (http_client_deticated_serial_context->srl_tx_state == SRL_TXING); // clear a buffer to make a room for http request memset(http_client_deticated_serial_context->srl_tx_buf_pointer, 0x00, http_client_deticated_serial_context->srl_tx_buf_ln); // assemble headers current_request_ln = http_client_headers_preamble(HTTP_GET, url + split_point, url_ln - split_point, (char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln);\ current_request_ln = http_client_headers_host(url + HTTP_PREFIX_LN, split_point - HTTP_PREFIX_LN, (char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_accept((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_user_agent((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_terminate((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); // send data through TCP/IP connection connect_result = gsm_sim800_tcpip_write(http_client_deticated_serial_context->srl_tx_buf_pointer, current_request_ln, http_client_deticated_serial_context, http_client_deticated_sim800_state); // check if data has been sent succesfully if (connect_result == 0) { // configure timeout for reception srl_switch_timeout(http_client_deticated_serial_context, 1, HTTP_CLIENT_DEFAULT_TIMEOUT_MSEC); // reset callback to initial state http_client_rx_done_callback_init(); // wait for GET response http_client_state = HTTP_CLIENT_WAITING_GET; gsm_sim800_tcpip_async_receive(http_client_deticated_serial_context, http_client_deticated_sim800_state, http_client_rx_done_callback, 5000u, http_client_response_done_callback /** FIXME */); } } } else if (split_point == 0xFFFF) { out = HTTP_CLIENT_RET_WRONG_URL; } else if (http_client_state != HTTP_CLIENT_READY) { out = HTTP_CLIENT_RET_UNITIALIZED; } return out; } uint8_t http_client_async_post(char * url, uint8_t url_ln, char * data_to_post, uint16_t data_ln, uint8_t force_disconnect_on_busy, http_client_response_available_t callback_on_response) { uint8_t out = 0; uint16_t split_point = http_client_split_hostname_and_path(url, url_ln); uint8_t connect_result = -1; uint16_t current_request_ln = 0; // simple check if url seems to be corrected or not if (split_point != 0xFFFF && http_client_state == HTTP_CLIENT_READY ) { // clear local buffers memset(http_client_hostname, 0x00, HOSTNAME_LN); memset(http_client_port, 0x00, PORT_LN); } else if (split_point == 0xFFFF) { out = HTTP_CLIENT_RET_WRONG_URL; } else if (http_client_state != HTTP_CLIENT_READY) { out = HTTP_CLIENT_RET_UNITIALIZED; } // check if module is busy on other TCP/IP connection if (*http_client_deticated_sim800_state == SIM800_TCP_CONNECTED && force_disconnect_on_busy != 0) { // if client is connected end a user wants to force disconnect gsm_sim800_tcpip_close(http_client_deticated_serial_context, http_client_deticated_sim800_state, 0); } else if (*http_client_deticated_sim800_state == SIM800_TCP_CONNECTED && force_disconnect_on_busy == 0) { out = HTTP_CLIENT_RET_TCPIP_BSY; } http_client_on_response_callback = callback_on_response; // get hotsname from URL (URI) http_client_get_address_from_url(url, url_ln, http_client_hostname, HOSTNAME_LN); http_client_get_port_from_url(url, url_ln, http_client_port, PORT_LN); // establish TCP connection to HTTP server connect_result = gsm_sim800_tcpip_connect(http_client_hostname, HOSTNAME_LN, http_client_port, PORT_LN, http_client_deticated_serial_context, http_client_deticated_sim800_state); // if connection has been established if (connect_result == 0) { // set appropriate state http_client_state = HTTP_CLIENT_CONNECTED_IDLE; // wait for any serial transmission to finish while (http_client_deticated_serial_context->srl_tx_state == SRL_TXING); // clear a buffer to make a room for http request memset(http_client_deticated_serial_context->srl_tx_buf_pointer, 0x00, http_client_deticated_serial_context->srl_tx_buf_ln); // assemble headers current_request_ln = http_client_headers_preamble(HTTP_POST, url + split_point, url_ln - split_point, (char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln);\ current_request_ln = http_client_headers_host(url + HTTP_PREFIX_LN, split_point - HTTP_PREFIX_LN, (char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_content_type_json((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_user_agent((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_accept((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); current_request_ln = http_client_headers_content_ln((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln, data_ln); current_request_ln = http_client_headers_terminate((char * )http_client_deticated_serial_context->srl_tx_buf_pointer, http_client_deticated_serial_context->srl_tx_buf_ln, current_request_ln); // check if there is a room for the content if (http_client_deticated_serial_context->srl_tx_buf_ln > current_request_ln + data_ln) { // if yes append HTTP content sprintf((char * )http_client_deticated_serial_context->srl_tx_buf_pointer + current_request_ln, "%s", data_to_post); // and calculate total request ln current_request_ln = strlen((char * )http_client_deticated_serial_context->srl_tx_buf_pointer); // send data through TCP/IP connection connect_result = gsm_sim800_tcpip_write(http_client_deticated_serial_context->srl_tx_buf_pointer, current_request_ln, http_client_deticated_serial_context, http_client_deticated_sim800_state); // check if data has been sent succesfully if (connect_result == 0) { // configure timeout for reception srl_switch_timeout(http_client_deticated_serial_context, 1, HTTP_CLIENT_DEFAULT_TIMEOUT_MSEC); // reset callback to initial state http_client_rx_done_callback_init(); // wait for POST response http_client_state = HTTP_CLIENT_WAITING_POST; gsm_sim800_tcpip_async_receive(http_client_deticated_serial_context, http_client_deticated_sim800_state, http_client_rx_done_callback, 5000u, http_client_response_done_callback /** FIXME */); } } } return out; } void http_client_close(void) { gsm_sim800_tcpip_close(http_client_deticated_serial_context, http_client_deticated_sim800_state, 1); } char * http_client_get_server_response() { return 0; } uint16_t http_client_get_latest_http_code() { return http_client_http_code; }