Split Web portal off of CivetWeb interface file

This is in preparation for adding a Redfish protocol
server.
parent 13cbede0
......@@ -34,6 +34,7 @@ target_sources(app PRIVATE
${KESTREL_SOURCE_DIR}/src/fsi.c
${KESTREL_SOURCE_DIR}/src/kestrel.c
${KESTREL_SOURCE_DIR}/src/webservice.c
${KESTREL_SOURCE_DIR}/src/webportal.c
${KESTREL_SOURCE_DIR}/src/direct_uart.c
${CIVETWEB_SOURCE_DIR}/src/libc_extensions.c
${CIVETWEB_SOURCE_DIR}/src/raptor_extensions.c
......
/*
* Copyright (c) 2021 Raptor Engineering, LLC <sales@raptorengineering.com>
*
* SPDX-License-Identifier: GPL-3.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(webportal, LOG_LEVEL_DBG);
#include <zephyr.h>
#include <posix/pthread.h>
#include <data/json.h>
#include "civetweb.h"
#include "raptor_extensions.h"
#include "static_files.h"
#define WITH_ZEPHYR 1
#include "kestrel.h"
#define STANDARD_HEADER(title) \
"<head>" \
"<link rel=\"stylesheet\" href=\"style.css\"><meta charset=\"UTF-8\">" \
"<title>" title "</title>" \
"</head>"
#define SEND_STANDARD_TITLE_HEADER(conn, title) \
civetweb_send_file_data(conn, STANDARD_HEADER(title), sizeof(STANDARD_HEADER(title)));
#define SEND_STANDARD_HEADER(conn, title) \
SEND_STANDARD_TITLE_HEADER(conn, title) \
civetweb_send_file_data(conn, embedded_file_header_inc, sizeof(embedded_file_header_inc)-1);
#define SEND_STANDARD_FOOTER(conn) \
civetweb_send_file_data(conn, embedded_file_footer_inc, sizeof(embedded_file_footer_inc)-1);
#define KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(name, type) \
static int static_file_##name##_handler(struct mg_connection *conn, void *cbdata) \
{ \
civetweb_send_file(conn, type, embedded_file_##name, sizeof(embedded_file_##name)-1); \
return 200; \
}
#define KESTREL_WEBSERVICE_DEFINE_PAGE_HANDLER(name, type, title) \
static int static_file_##name##_handler(struct mg_connection *conn, void *cbdata) \
{ \
mg_printf(conn, \
"HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"Connection: close\r\n\r\n"); \
SEND_STANDARD_HEADER(conn, title) \
civetweb_send_file_data(conn, embedded_file_##name, sizeof(embedded_file_##name)-1); \
SEND_STANDARD_FOOTER(conn) \
return 200; \
}
#define KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(name, path) \
mg_set_request_handler(ctx, path, static_file_##name##_handler, (void *)0);
// Set this as small as possible
#define MAX_REQUEST_SIZE_BYTES 1024
// Define ports
#define HTTP_PORT 8080
#define HTTPS_PORT 4443
KESTREL_WEBSERVICE_DEFINE_PAGE_HANDLER(index_html, "text/html", "Kestrel SoftBMC")
KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(style_css, "text/css")
KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(raptor_logo_jpg, "image/jpeg")
KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(kestrel_avatar_small_png, "image/png")
struct civetweb_info {
const char *version;
const char *os;
const char *compiler;
};
#define FIELD(struct_, member_, type_) { \
.field_name = #member_, \
.field_name_len = sizeof(#member_) - 1, \
.offset = offsetof(struct_, member_), \
.type = type_ \
}
void send_ok(struct mg_connection *conn)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n");
}
int system_info_handler(struct mg_connection *conn, void *cbdata)
{
static const struct json_obj_descr descr[] = {
FIELD(struct civetweb_info, version, JSON_TOK_STRING),
FIELD(struct civetweb_info, os, JSON_TOK_STRING),
FIELD(struct civetweb_info, compiler, JSON_TOK_STRING),
};
struct civetweb_info info = {};
char info_str[1024] = {};
int ret;
int size;
size = mg_get_system_info(info_str, sizeof(info_str));
ret = json_obj_parse(info_str, size, descr, ARRAY_SIZE(descr), &info);
send_ok(conn);
if (ret < 0) {
mg_printf(conn, "Could not retrieve: %d\n", ret);
return 500;
}
SEND_STANDARD_HEADER(conn, "Kestrel Information")
mg_printf(conn, "<h3>BMC information</h3>");
mg_printf(conn, "<ul>\n");
mg_printf(conn, "<li>BMC OS - %s</li>\n", info.os);
mg_printf(conn, "<li>Server - civetweb %s</li>\n", info.version);
mg_printf(conn, "<li>Compiler - %s</li>\n", info.compiler);
mg_printf(conn, "<li>Board - %s</li>\n", CONFIG_BOARD);
mg_printf(conn, "<li>Architecture - %s</li>\n", CONFIG_ARCH);
mg_printf(conn, "</ul>\n");
SEND_STANDARD_FOOTER(conn);
return 200;
}
int firmware_upload_form_handler(struct mg_connection *conn, void *cbdata)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
"close\r\n\r\n");
SEND_STANDARD_HEADER(conn, "Firmware Upload")
mg_printf(conn, "<h3>Firmware Upload</h3>");
mg_printf(conn,
"<form action=\"%s\" method=\"POST\" "
"enctype=\"multipart/form-data\">\n",
(const char *)cbdata);
mg_printf(conn, "<input type=\"file\" name=\"firmwarefile\" multiple>\n");
mg_printf(conn, "<input type=\"submit\" value=\"Upload\">\n");
mg_printf(conn, "</form>\n");
SEND_STANDARD_FOOTER(conn);
return 200;
}
int fw_field_received(const char *key, const char *filename, char *path, size_t pathlen, void *user_data)
{
(void)key;
(void)path;
(void)pathlen;
if (strcmp(key, "firmwarefile") == 0) {
return MG_FORM_FIELD_STORAGE_GET;
}
return MG_FORM_FIELD_STORAGE_ABORT;
}
int fw_data_received(const char *key, const char *value, size_t valuelen, void *user_data)
{
struct firmware_buffer_region *fw_data = (struct firmware_buffer_region *)user_data;
if ((fw_data->current_write_offset + valuelen) > fw_data->buffer_length) {
// Buffer overflow!
// Discard the extra data...
valuelen = fw_data->buffer_length - fw_data->current_write_offset;
fw_data->overflow = 1;
}
memcpy(fw_data->buffer_address + fw_data->current_write_offset, value, valuelen);
fw_data->current_write_offset += valuelen;
fw_data->valid_bytes = fw_data->current_write_offset;
return 0;
}
int firmware_upload_handler(struct mg_connection *conn, void *cbdata)
{
const struct mg_request_info *req_info = mg_get_request_info(conn);
int ret;
struct mg_form_data_handler fdh = {fw_field_received,
fw_data_received,
0,
(void *)&main_firmware_buffer};
(void)req_info;
main_firmware_buffer.locked = 1;
main_firmware_buffer.overflow = 0;
main_firmware_buffer.current_write_offset = 0;
main_firmware_buffer.valid_bytes = 0;
// Call the form handler
ret = mg_handle_form_request(conn, &fdh);
// Clear out the remainder of the buffer
memset(main_firmware_buffer.buffer_address + main_firmware_buffer.valid_bytes, 0xff, main_firmware_buffer.buffer_length - main_firmware_buffer.valid_bytes);
main_firmware_buffer.locked = 0;
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n");
SEND_STANDARD_HEADER(conn, "Firmware Uploaded")
mg_printf(conn, "<h3>Firmware Uploaded</h3>");
mg_printf(conn, "<p>");
mg_printf(conn, "File(s) uploaded:<br>");
mg_printf(conn, "%i files<br>", ret);
mg_printf(conn, "%lld bytes<br>", main_firmware_buffer.valid_bytes);
if (main_firmware_buffer.overflow) {
mg_printf(conn, "<p>WARNING: Data was discarded due to buffer overflow!");
SEND_STANDARD_FOOTER(conn);
return 500;
}
SEND_STANDARD_FOOTER(conn);
return 200;
}
void *web_portal_pthread(void *arg)
{
static const char * const options[] = {
"listening_ports",
STRINGIFY(HTTP_PORT),
"num_threads",
"1",
"max_request_size",
STRINGIFY(MAX_REQUEST_SIZE_BYTES),
NULL
};
struct mg_callbacks callbacks;
struct mg_context *ctx;
(void)arg;
memset(&callbacks, 0, sizeof(callbacks));
ctx = mg_start(&callbacks, 0, (const char **)options);
if (ctx == NULL) {
LOG_INF("Unable to start the server.");
return 0;
}
// Static pages
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(index_html, "/$")
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(style_css, "/style.css$")
// Static files
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(raptor_logo_jpg, "/raptor_logo.jpg$")
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(kestrel_avatar_small_png, "/kestrel_avatar_small.png$")
// Dynamic pages
mg_set_request_handler(ctx, "/info$", system_info_handler, (void *)0);
mg_set_request_handler(ctx, "/firmwareupload$", firmware_upload_form_handler, (void *)"/firmwareupload.callback");
mg_set_request_handler(ctx, "/firmwareupload.callback$", firmware_upload_handler, (void *)0);
return 0;
}
// © 2021 Raptor Engineering, LLC
//
// Released under the terms of the GPL v3
// See the LICENSE file for full details
#ifndef _WEBPORTAL_H
#define _WEBPORTAL_H
void *web_portal_pthread(void *arg);
#endif // _WEBPORTAL_H
\ No newline at end of file
......@@ -9,280 +9,12 @@
#include <posix/pthread.h>
#include <data/json.h>
#include "civetweb.h"
#define WITH_ZEPHYR 1
#include "kestrel.h"
#include "raptor_extensions.h"
#include "static_files.h"
#define HTTP_PORT 8080
#define HTTPS_PORT 4443
#include "webportal.h"
#define CIVETWEB_MAIN_THREAD_STACK_SIZE CONFIG_MAIN_STACK_SIZE
/* Use samllest possible value of 1024 (see the line 18619 of civetweb.c) */
#define MAX_REQUEST_SIZE_BYTES 1024
#define STANDARD_HEADER(title) \
"<head>" \
"<link rel=\"stylesheet\" href=\"style.css\"><meta charset=\"UTF-8\">" \
"<title>" title "</title>" \
"</head>"
#define SEND_STANDARD_TITLE_HEADER(conn, title) \
civetweb_send_file_data(conn, STANDARD_HEADER(title), sizeof(STANDARD_HEADER(title)));
#define SEND_STANDARD_HEADER(conn, title) \
SEND_STANDARD_TITLE_HEADER(conn, title) \
civetweb_send_file_data(conn, embedded_file_header_inc, sizeof(embedded_file_header_inc)-1);
#define SEND_STANDARD_FOOTER(conn) \
civetweb_send_file_data(conn, embedded_file_footer_inc, sizeof(embedded_file_footer_inc)-1);
#define KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(name, type) \
static int static_file_##name##_handler(struct mg_connection *conn, void *cbdata) \
{ \
civetweb_send_file(conn, type, embedded_file_##name, sizeof(embedded_file_##name)-1); \
return 200; \
}
#define KESTREL_WEBSERVICE_DEFINE_PAGE_HANDLER(name, type, title) \
static int static_file_##name##_handler(struct mg_connection *conn, void *cbdata) \
{ \
mg_printf(conn, \
"HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"Connection: close\r\n\r\n"); \
SEND_STANDARD_HEADER(conn, title) \
civetweb_send_file_data(conn, embedded_file_##name, sizeof(embedded_file_##name)-1); \
SEND_STANDARD_FOOTER(conn) \
return 200; \
}
#define KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(name, path) \
mg_set_request_handler(ctx, path, static_file_##name##_handler, (void *)0);
K_THREAD_STACK_DEFINE(civetweb_stack, CIVETWEB_MAIN_THREAD_STACK_SIZE);
KESTREL_WEBSERVICE_DEFINE_PAGE_HANDLER(index_html, "text/html", "Kestrel SoftBMC")
KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(style_css, "text/css")
KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(raptor_logo_jpg, "image/jpeg")
KESTREL_WEBSERVICE_DEFINE_FILE_HANDLER(kestrel_avatar_small_png, "image/png")
struct civetweb_info {
const char *version;
const char *os;
uint32_t features;
const char *feature_list;
const char *build;
const char *compiler;
const char *data_model;
};
#define FIELD(struct_, member_, type_) { \
.field_name = #member_, \
.field_name_len = sizeof(#member_) - 1, \
.offset = offsetof(struct_, member_), \
.type = type_ \
}
void send_ok(struct mg_connection *conn)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n");
}
int system_info_handler(struct mg_connection *conn, void *cbdata)
{
static const struct json_obj_descr descr[] = {
FIELD(struct civetweb_info, version, JSON_TOK_STRING),
FIELD(struct civetweb_info, os, JSON_TOK_STRING),
FIELD(struct civetweb_info, feature_list, JSON_TOK_STRING),
FIELD(struct civetweb_info, build, JSON_TOK_STRING),
FIELD(struct civetweb_info, compiler, JSON_TOK_STRING),
FIELD(struct civetweb_info, data_model, JSON_TOK_STRING),
};
struct civetweb_info info = {};
char info_str[1024] = {};
int ret;
int size;
size = mg_get_system_info(info_str, sizeof(info_str));
ret = json_obj_parse(info_str, size, descr, ARRAY_SIZE(descr), &info);
send_ok(conn);
if (ret < 0) {
mg_printf(conn, "Could not retrieve: %d\n", ret);
return 500;
}
SEND_STANDARD_HEADER(conn, "Kestrel Information")
mg_printf(conn, "<h3>BMC information</h3>");
mg_printf(conn, "<ul>\n");
mg_printf(conn, "<li>BMC OS - %s</li>\n", info.os);
mg_printf(conn, "<li>Server - civetweb %s</li>\n", info.version);
mg_printf(conn, "<li>Compiler - %s</li>\n", info.compiler);
mg_printf(conn, "<li>Board - %s</li>\n", CONFIG_BOARD);
mg_printf(conn, "</ul>\n");
SEND_STANDARD_FOOTER(conn);
return 200;
}
int firmware_upload_form_handler(struct mg_connection *conn, void *cbdata)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
"close\r\n\r\n");
SEND_STANDARD_HEADER(conn, "Firmware Upload")
mg_printf(conn, "<h3>Firmware Upload</h3>");
mg_printf(conn,
"<form action=\"%s\" method=\"POST\" "
"enctype=\"multipart/form-data\">\n",
(const char *)cbdata);
mg_printf(conn, "<input type=\"file\" name=\"firmwarefile\" multiple>\n");
mg_printf(conn, "<input type=\"submit\" value=\"Upload\">\n");
mg_printf(conn, "</form>\n");
SEND_STANDARD_FOOTER(conn);
return 200;
}
int fw_field_received(const char *key, const char *filename, char *path, size_t pathlen, void *user_data)
{
(void)key;
(void)path;
(void)pathlen;
if (strcmp(key, "firmwarefile") == 0) {
return MG_FORM_FIELD_STORAGE_GET;
}
return MG_FORM_FIELD_STORAGE_ABORT;
}
int fw_data_received(const char *key, const char *value, size_t valuelen, void *user_data)
{
struct firmware_buffer_region *fw_data = (struct firmware_buffer_region *)user_data;
if ((fw_data->current_write_offset + valuelen) > fw_data->buffer_length) {
// Buffer overflow!
// Discard the extra data...
valuelen = fw_data->buffer_length - fw_data->current_write_offset;
fw_data->overflow = 1;
}
memcpy(fw_data->buffer_address + fw_data->current_write_offset, value, valuelen);
fw_data->current_write_offset += valuelen;
fw_data->valid_bytes = fw_data->current_write_offset;
return 0;
}
int firmware_upload_handler(struct mg_connection *conn, void *cbdata)
{
/* Handler may access the request info using mg_get_request_info */
const struct mg_request_info *req_info = mg_get_request_info(conn);
int ret;
struct mg_form_data_handler fdh = {fw_field_received,
fw_data_received,
0,
(void *)&main_firmware_buffer};
/* It would be possible to check the request info here before calling
* mg_handle_form_request. */
(void)req_info;
main_firmware_buffer.locked = 1;
main_firmware_buffer.overflow = 0;
main_firmware_buffer.current_write_offset = 0;
main_firmware_buffer.valid_bytes = 0;
/* Call the form handler */
ret = mg_handle_form_request(conn, &fdh);
// Clear out the remainder of the buffer
memset(main_firmware_buffer.buffer_address + main_firmware_buffer.valid_bytes, 0xff, main_firmware_buffer.buffer_length - main_firmware_buffer.valid_bytes);
main_firmware_buffer.locked = 0;
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n");
SEND_STANDARD_HEADER(conn, "Firmware Uploaded")
mg_printf(conn, "<h3>Firmware Uploaded</h3>");
mg_printf(conn, "<p>");
mg_printf(conn, "File(s) uploaded:<br>");
mg_printf(conn, "%i files<br>", ret);
mg_printf(conn, "%lld bytes<br>", main_firmware_buffer.valid_bytes);
if (main_firmware_buffer.overflow) {
mg_printf(conn, "<p>WARNING: Data was discarded due to buffer overflow!");
SEND_STANDARD_FOOTER(conn);
return 500;
}
SEND_STANDARD_FOOTER(conn);
return 200;
}
void *main_pthread(void *arg)
{
static const char * const options[] = {
"listening_ports",
STRINGIFY(HTTP_PORT),
"num_threads",
"1",
"max_request_size",
STRINGIFY(MAX_REQUEST_SIZE_BYTES),
NULL
};
struct mg_callbacks callbacks;
struct mg_context *ctx;
(void)arg;
memset(&callbacks, 0, sizeof(callbacks));
ctx = mg_start(&callbacks, 0, (const char **)options);
if (ctx == NULL) {
printf("Unable to start the server.");
return 0;
}
// Static pages
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(index_html, "/$")
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(style_css, "/style.css$")
// Static files
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(raptor_logo_jpg, "/raptor_logo.jpg$")
KESTREL_WEBSERVICE_REGISTER_FILE_HANDLER(kestrel_avatar_small_png, "/kestrel_avatar_small.png$")
// Dynamic pages
mg_set_request_handler(ctx, "/info$", system_info_handler, (void *)0);
mg_set_request_handler(ctx, "/firmwareupload$", firmware_upload_form_handler, (void *)"/firmwareupload.callback");
mg_set_request_handler(ctx, "/firmwareupload.callback$", firmware_upload_handler, (void *)0);
return 0;
}
void start_webservice(void)
{
pthread_attr_t civetweb_attr;
......@@ -293,5 +25,5 @@ void start_webservice(void)
CIVETWEB_MAIN_THREAD_STACK_SIZE);
(void)pthread_create(&civetweb_thread, &civetweb_attr,
&main_pthread, 0);
&web_portal_pthread, 0);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment