SP8EBC-ParaTNC/system/src/http_client/http_client.c

404 wiersze
14 KiB
C

#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 <string.h>
#include <stdio.h>
#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;
}