Files
maze32/components/maze32_web/maze32_web.c
T

191 lines
6.4 KiB
C

#include "maze32_web.h"
#include <stdio.h>
#include "esp_log.h"
#define MAZE32_STATIC_CHUNK_SIZE 1024
static const char* TAG = "maze32_web";
typedef struct {
const char* path;
const char* content_type;
} maze32_static_asset_t;
static const maze32_static_asset_t INDEX_ASSET = {"/littlefs/index.html.gz",
"text/html; charset=utf-8"};
static const maze32_static_asset_t PLAYER_STYLE_ASSET = {"/littlefs/player.css.gz", "text/css"};
static const maze32_static_asset_t PLAYER_SCRIPT_ASSET = {"/littlefs/player.js.gz",
"text/javascript; charset=utf-8"};
static const maze32_static_asset_t ICON_ASSET = {"/littlefs/icons.svg.gz", "image/svg+xml"};
static void preserve_static_context(void* context) {
(void)context;
}
static esp_err_t send_static_asset(httpd_req_t* request) {
const maze32_static_asset_t* asset = request->user_ctx;
if (asset == NULL) {
return ESP_ERR_INVALID_ARG;
}
FILE* file = fopen(asset->path, "rb");
if (file == NULL) {
ESP_LOGE(TAG, "Static asset is unavailable: %s", asset->path);
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR,
"Приложение временно недоступно");
return ESP_FAIL;
}
httpd_resp_set_type(request, asset->content_type);
httpd_resp_set_hdr(request, "Content-Encoding", "gzip");
httpd_resp_set_hdr(request, "Cache-Control", "no-cache");
char buffer[MAZE32_STATIC_CHUNK_SIZE];
size_t bytes_read;
esp_err_t result = ESP_OK;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
result = httpd_resp_send_chunk(request, buffer, bytes_read);
if (result != ESP_OK) {
break;
}
}
if (ferror(file) != 0) {
ESP_LOGE(TAG, "Failed while reading static asset: %s", asset->path);
result = ESP_FAIL;
}
fclose(file);
if (result == ESP_OK) {
result = httpd_resp_send_chunk(request, NULL, 0);
}
return result;
}
static esp_err_t send_health(httpd_req_t* request) {
const maze32_health_t* health = request->user_ctx;
char response[192];
int length = snprintf(response, sizeof(response),
"{\"status\":\"%s\",\"healthy\":%s,\"version\":\"%s\",\"errorCode\":%d}",
maze32_health_state_name(health->state),
maze32_health_is_healthy(health) ? "true" : "false", health->version,
health->error_code);
if (length < 0 || (size_t)length >= sizeof(response)) {
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR,
"Диагностический ответ слишком большой");
return ESP_FAIL;
}
httpd_resp_set_type(request, "application/json");
httpd_resp_set_hdr(request, "Cache-Control", "no-store");
return httpd_resp_send(request, response, length);
}
static esp_err_t send_diagnostics(httpd_req_t* request) {
maze32_gateway_t* gateway = request->user_ctx;
char response[1024];
size_t length = maze32_gateway_format_diagnostics(gateway, response, sizeof(response));
if (length == 0U) {
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR,
"Диагностика временно недоступна");
return ESP_FAIL;
}
httpd_resp_set_type(request, "application/json");
httpd_resp_set_hdr(request, "Cache-Control", "no-store");
return httpd_resp_send(request, response, (ssize_t)length);
}
esp_err_t maze32_web_start(httpd_handle_t* server, maze32_health_t* health,
maze32_gateway_t* gateway) {
if (server == NULL || health == NULL || gateway == NULL) {
return ESP_ERR_INVALID_ARG;
}
httpd_config_t configuration = HTTPD_DEFAULT_CONFIG();
configuration.max_uri_handlers = 13;
configuration.max_open_sockets = 9;
configuration.lru_purge_enable = false;
configuration.recv_wait_timeout = 5;
configuration.send_wait_timeout = 1;
configuration.global_user_ctx = gateway;
configuration.global_user_ctx_free_fn = preserve_static_context;
configuration.close_fn = maze32_gateway_close_socket;
esp_err_t result = httpd_start(server, &configuration);
if (result != ESP_OK) {
return result;
}
const httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = send_static_asset,
.user_ctx = (void*)&INDEX_ASSET,
};
const httpd_uri_t index = {
.uri = "/index.html",
.method = HTTP_GET,
.handler = send_static_asset,
.user_ctx = (void*)&INDEX_ASSET,
};
const httpd_uri_t player_style = {
.uri = "/player.css",
.method = HTTP_GET,
.handler = send_static_asset,
.user_ctx = (void*)&PLAYER_STYLE_ASSET,
};
const httpd_uri_t player_script = {
.uri = "/player.js",
.method = HTTP_GET,
.handler = send_static_asset,
.user_ctx = (void*)&PLAYER_SCRIPT_ASSET,
};
const httpd_uri_t icons = {
.uri = "/icons.svg",
.method = HTTP_GET,
.handler = send_static_asset,
.user_ctx = (void*)&ICON_ASSET,
};
const httpd_uri_t health_uri = {
.uri = "/health",
.method = HTTP_GET,
.handler = send_health,
.user_ctx = health,
};
const httpd_uri_t diagnostics_uri = {
.uri = "/diagnostics",
.method = HTTP_GET,
.handler = send_diagnostics,
.user_ctx = gateway,
};
result = httpd_register_uri_handler(*server, &root);
if (result == ESP_OK) {
result = httpd_register_uri_handler(*server, &index);
}
if (result == ESP_OK) {
result = httpd_register_uri_handler(*server, &player_style);
}
if (result == ESP_OK) {
result = httpd_register_uri_handler(*server, &player_script);
}
if (result == ESP_OK) {
result = httpd_register_uri_handler(*server, &icons);
}
if (result == ESP_OK) {
result = httpd_register_uri_handler(*server, &health_uri);
}
if (result == ESP_OK) {
result = httpd_register_uri_handler(*server, &diagnostics_uri);
}
if (result != ESP_OK) {
httpd_stop(*server);
*server = NULL;
return result;
}
ESP_LOGI(TAG, "HTTP server ready with static and health endpoints");
return ESP_OK;
}