266 lines
11 KiB
C
266 lines
11 KiB
C
#include <vessel/conf.h>
|
|
|
|
#include <vessel/def.h>
|
|
#include <vessel/log.h>
|
|
#include <vessel/vessel.h>
|
|
#include <vessel/server.h>
|
|
|
|
#include <vessel-web/srv.h>
|
|
#include <vessel-web/web.h>
|
|
#include <vessel-web/form.h>
|
|
#include <vessel-web/http.h>
|
|
#include <vessel-web/request.h>
|
|
#include <vessel-web/httpbody.h>
|
|
|
|
static volatile uint64_t vs_request_count = 0;
|
|
static vs_Logger vs_logger = VS_LOG_ALL;
|
|
|
|
#define VS_ERROR_AND_LOG(code, info) \
|
|
vs_flog_error(&vs_logger, "%s: %s", __func__, (info)); \
|
|
vw_report_error(&proxy->req, (code), "/", (info)); \
|
|
return false
|
|
|
|
static bool vs_get_index(vw_Proxy *proxy) {
|
|
if (vw_HTTPRequest_res_begin(&proxy->req, 200) == VS_STREAM_ERROR) {
|
|
VS_ERROR_AND_LOG(500, "Failed to begin an HTTPRequest response");
|
|
}
|
|
|
|
if (vw_HTTPRequest_res_header(&proxy->req, "content-type", "text/html") == VS_STREAM_ERROR) {
|
|
VS_ERROR_AND_LOG(500, "Failed to set content-type header");
|
|
}
|
|
|
|
/* clang-format off */
|
|
if (vs_Stream_ignore_err(vw_HTTPRequest_writef(&proxy->req,
|
|
"<!DOCTYPE html>" VW_HTTP_CRLF
|
|
"<html lang=\"en\">" VW_HTTP_CRLF
|
|
" <head>" VW_HTTP_CRLF
|
|
" <meta charset=\"UTF-8\">" VW_HTTP_CRLF
|
|
" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">" VW_HTTP_CRLF
|
|
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" VW_HTTP_CRLF
|
|
" <title>Home Page</title>" VW_HTTP_CRLF
|
|
" </head>" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <body>" VW_HTTP_CRLF
|
|
" <h1>Hello!</h1>" VW_HTTP_CRLF
|
|
" <p>This is a sample website. Your request number: %ju. Submit form here:</p>" VW_HTTP_CRLF
|
|
"<form action=\"/save\" method=\"post\" enctype=\"multipart/form-data\">" VW_HTTP_CRLF
|
|
" <!-- Text input -->" VW_HTTP_CRLF
|
|
" <label for=\"username\">Username:</label>" VW_HTTP_CRLF
|
|
" <input type=\"text\" id=\"username\" name=\"username\" placeholder=\"Enter username\" required />" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Password input -->" VW_HTTP_CRLF
|
|
" <label for=\"password\">Password:</label>" VW_HTTP_CRLF
|
|
" <input type=\"password\" id=\"password\" name=\"password\" required />" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Email input with multiple attribute -->" VW_HTTP_CRLF
|
|
" <label for=\"emails\">Email(s):</label>" VW_HTTP_CRLF
|
|
" <input type=\"email\" id=\"emails\" name=\"emails\" multiple placeholder=\"Enter one or more emails separated by commas\" />" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Number input -->" VW_HTTP_CRLF
|
|
" <label for=\"age\">Age:</label>" VW_HTTP_CRLF
|
|
" <input type=\"number\" id=\"age\" name=\"age\" min=\"1\" max=\"120\" />" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Checkbox inputs -->" VW_HTTP_CRLF
|
|
" <fieldset>" VW_HTTP_CRLF
|
|
" <legend>Choose your hobbies:</legend>" VW_HTTP_CRLF
|
|
" <input type=\"checkbox\" id=\"hobby1\" name=\"hobbies\" value=\"reading\" />" VW_HTTP_CRLF
|
|
" <label for=\"hobby1\">Reading</label><br />" VW_HTTP_CRLF
|
|
" <input type=\"checkbox\" id=\"hobby2\" name=\"hobbies\" value=\"sports\" />" VW_HTTP_CRLF
|
|
" <label for=\"hobby2\">Sports</label><br />" VW_HTTP_CRLF
|
|
" <input type=\"checkbox\" id=\"hobby3\" name=\"hobbies\" value=\"music\" />" VW_HTTP_CRLF
|
|
" <label for=\"hobby3\">Music</label>" VW_HTTP_CRLF
|
|
" </fieldset>" VW_HTTP_CRLF
|
|
" <br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Radio buttons -->" VW_HTTP_CRLF
|
|
" <fieldset>" VW_HTTP_CRLF
|
|
" <legend>Gender:</legend>" VW_HTTP_CRLF
|
|
" <input type=\"radio\" id=\"gender_m\" name=\"gender\" value=\"male\" />" VW_HTTP_CRLF
|
|
" <label for=\"gender_m\">Male</label><br />" VW_HTTP_CRLF
|
|
" <input type=\"radio\" id=\"gender_f\" name=\"gender\" value=\"female\" />" VW_HTTP_CRLF
|
|
" <label for=\"gender_f\">Female</label><br />" VW_HTTP_CRLF
|
|
" <input type=\"radio\" id=\"gender_o\" name=\"gender\" value=\"other\" />" VW_HTTP_CRLF
|
|
" <label for=\"gender_o\">Other</label>" VW_HTTP_CRLF
|
|
" </fieldset>" VW_HTTP_CRLF
|
|
" <br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Single select dropdown -->" VW_HTTP_CRLF
|
|
" <label for=\"country\">Country:</label>" VW_HTTP_CRLF
|
|
" <select id=\"country\" name=\"country\">" VW_HTTP_CRLF
|
|
" <option value=\"\">--Select a country--</option>" VW_HTTP_CRLF
|
|
" <option value=\"us\">United States</option>" VW_HTTP_CRLF
|
|
" <option value=\"ca\">Canada</option>" VW_HTTP_CRLF
|
|
" <option value=\"uk\">United Kingdom</option>" VW_HTTP_CRLF
|
|
" </select>" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Multiple select dropdown -->" VW_HTTP_CRLF
|
|
" <label for=\"languages\">Languages Spoken:</label>" VW_HTTP_CRLF
|
|
" <select id=\"languages\" name=\"languages\" multiple size=\"4\">" VW_HTTP_CRLF
|
|
" <option value=\"english\">English</option>" VW_HTTP_CRLF
|
|
" <option value=\"spanish\">Spanish</option>" VW_HTTP_CRLF
|
|
" <option value=\"french\">French</option>" VW_HTTP_CRLF
|
|
" <option value=\"german\">German</option>" VW_HTTP_CRLF
|
|
" </select>" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- File upload with multiple files -->" VW_HTTP_CRLF
|
|
" <label for=\"files\">Upload files:</label>" VW_HTTP_CRLF
|
|
" <input type=\"file\" id=\"files\" name=\"files\" multiple />" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Textarea -->" VW_HTTP_CRLF
|
|
" <label for=\"comments\">Comments:</label><br />" VW_HTTP_CRLF
|
|
" <textarea id=\"comments\" name=\"comments\" rows=\"4\" cols=\"50\" placeholder=\"Enter your comments here\"></textarea>" VW_HTTP_CRLF
|
|
" <br /><br />" VW_HTTP_CRLF
|
|
"" VW_HTTP_CRLF
|
|
" <!-- Submit and reset buttons -->" VW_HTTP_CRLF
|
|
" <button type=\"submit\">Submit</button>" VW_HTTP_CRLF
|
|
" <button type=\"reset\">Reset</button>" VW_HTTP_CRLF
|
|
"</form>" VW_HTTP_CRLF
|
|
" <p>Header count: %zu (%zu bytes)</p>" VW_HTTP_CRLF
|
|
" </body>" VW_HTTP_CRLF
|
|
"</html>" VW_HTTP_CRLF,
|
|
|
|
++vs_request_count, proxy->req.headers.size, proxy->req.header_size
|
|
)) == 0) {
|
|
VS_ERROR_AND_LOG(500, "Failed to send response HTML");
|
|
}
|
|
/* clang-format on */
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool vs_get_index_html(vw_Proxy *proxy) {
|
|
vw_srv_redirect(&proxy->req, 307, "/");
|
|
return true;
|
|
}
|
|
|
|
static bool vs_post_save(vw_Proxy *proxy) {
|
|
vs_HMap form = { 0 };
|
|
|
|
if (!vs_HMap_init(&form)) {
|
|
VS_ERROR_AND_LOG(500, "Failed to initialise the form hashmap");
|
|
}
|
|
|
|
if (!vw_Form_read(&proxy->req, &form)) {
|
|
VS_ERROR_AND_LOG(400, "Failed to read and save the POSTed form");
|
|
}
|
|
|
|
if (!vw_HTTPRequest_res_begin(&proxy->req, 200)) {
|
|
VS_ERROR_AND_LOG(500, "Failed to begin HTTP response");
|
|
}
|
|
|
|
if (!vw_HTTPRequest_res_header(&proxy->req, "content-type", "text/plain")) {
|
|
VS_ERROR_AND_LOG(500, "Failed to set content-type header");
|
|
}
|
|
|
|
vs_ByteStream stream = { 0 };
|
|
|
|
if (!vs_ByteStream_init(&stream)) {
|
|
VS_ERROR_AND_LOG(500, "Failed to initialise byte stream");
|
|
}
|
|
|
|
for (size_t idx = 0; idx < form.size; ++idx) {
|
|
const char *key = form.occupied_ents[idx]->key;
|
|
const vw_FormField *field = (const vw_FormField *)form.occupied_ents[idx]->value;
|
|
|
|
switch (field->type) {
|
|
case vw_FormFieldType_plain:
|
|
if (!vs_ByteStream_ignore_err(vs_ByteStream_writef(
|
|
&stream, "%s (plain): %s" VW_HTTP_CRLF, key, (const char *)field->value))) {
|
|
vs_ByteStream_destroy(&stream);
|
|
VS_ERROR_AND_LOG(500, "Failed to write plain form format in byte stream");
|
|
}
|
|
break;
|
|
|
|
case vw_FormFieldType_file: {
|
|
const vw_FormFile *file = (const vw_FormFile *)field->value;
|
|
if (!vs_ByteStream_ignore_err(vs_ByteStream_writef(
|
|
&stream, "%s (file): %s" VW_HTTP_CRLF, key, file->filename))) {
|
|
vs_ByteStream_destroy(&stream);
|
|
VS_ERROR_AND_LOG(500, "Failed to write file form format in byte stream");
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
if (!vw_HTTPRequest_write(
|
|
&proxy->req, vs_ByteStream_buf(&stream), vs_ByteStream_len(&stream))) {
|
|
vs_ByteStream_destroy(&stream);
|
|
VS_ERROR_AND_LOG(500, "Failed to write final response");
|
|
}
|
|
|
|
vs_ByteStream_destroy(&stream);
|
|
vw_Form_destroy(&form);
|
|
return true;
|
|
}
|
|
|
|
static bool vs_any_not_found(vw_Proxy *proxy) {
|
|
return 0 != vw_report_error(&proxy->req, 404, "/", "Request handler vs_any_not_found");
|
|
}
|
|
|
|
int main(void) {
|
|
vs_vessel_init(VS_VESSEL_HEADER_VERSION);
|
|
|
|
/* Set up socket server */
|
|
|
|
vs_SockServer server = { 0 };
|
|
|
|
server.host = "127.0.0.1";
|
|
server.port = 8080;
|
|
server.logger = &vs_logger;
|
|
server.threads = 128;
|
|
|
|
if (!vs_SockServer_create_generic(&server)) {
|
|
vs_log_error(&vs_logger, "Failed to create a generic TCP server.");
|
|
return 1;
|
|
}
|
|
|
|
if (!vs_SockServer_init(&server, VS_SEC2TO(30))) {
|
|
vs_log_error(&vs_logger, "Failed to create a generic TCP server.");
|
|
vs_SockServer_destroy(&server);
|
|
return 1;
|
|
}
|
|
|
|
if (!vs_SockServer_setup_signals(&server)) {
|
|
vs_log_error(&vs_logger, "Failed to handle signals for the server.");
|
|
vs_SockServer_destroy(&server);
|
|
return 1;
|
|
}
|
|
|
|
/* Set up web server */
|
|
|
|
vs_SockWorkerHook sock_server_hooks[] = VS_HOOKS(VW_WEBSERVER_HOOKS);
|
|
|
|
static vw_Route routes[] =
|
|
VW_ROUTES(VW_NEW_ROUTE(GET, "/", normal, vs_get_index),
|
|
VW_NEW_ROUTE(GET, "/<int(10:100/2)>", normal, vs_get_index),
|
|
VW_NEW_ROUTE(GET, "/index.html", normal, vs_get_index_html),
|
|
VW_NEW_ROUTE(POST, "/save", normal, vs_post_save),
|
|
VW_NEW_ROUTE(ANY, "<path>", not_found, vs_any_not_found));
|
|
|
|
vw_Router router = { 0 };
|
|
router.routes = routes;
|
|
|
|
if (!vw_WebServer_init(&server, &router)) {
|
|
vs_log_error(&vs_logger, "Failed to init the web server.");
|
|
vs_SockServer_destroy(&server);
|
|
return 1;
|
|
}
|
|
|
|
if (!vs_SockServer_start(&server, sock_server_hooks, NULL)) {
|
|
vs_log_error(&vs_logger, "Failed to start the server.");
|
|
vs_SockServer_destroy(&server);
|
|
return 1;
|
|
}
|
|
|
|
vw_WebServer_destroy(&router);
|
|
vs_SockServer_destroy(&server);
|
|
|
|
return 0;
|
|
}
|