339 lines
9.8 KiB
C++
339 lines
9.8 KiB
C++
#include "microhttpd_server.h"
|
|
#include "piliterals_string.h"
|
|
#include "piliterals_time.h"
|
|
|
|
#include <microhttpd.h>
|
|
// clang-format off
|
|
#ifdef QNX
|
|
# include <arpa/inet.h>
|
|
# include <sys/socket.h>
|
|
# include <sys/types.h>
|
|
# ifdef BLACKBERRY
|
|
# include <netinet/in.h>
|
|
# else
|
|
# include <sys/dcmd_io-net.h>
|
|
# endif
|
|
#else
|
|
# ifdef WINDOWS
|
|
# include <io.h>
|
|
# include <winsock2.h>
|
|
# include <ws2tcpip.h>
|
|
# else
|
|
# include <netinet/in.h>
|
|
# include <sys/socket.h>
|
|
# ifdef LWIP
|
|
# include <lwip/sockets.h>
|
|
# endif
|
|
# endif
|
|
#endif
|
|
// clang-format on
|
|
|
|
|
|
struct MicrohttpdServerConnection {
|
|
bool ready();
|
|
int send_reply(const MicrohttpdServer::Reply & r);
|
|
int send_error();
|
|
|
|
bool done = false;
|
|
MicrohttpdServer::Method method = MicrohttpdServer::Method::Unknown;
|
|
PIString path;
|
|
PIByteArray body;
|
|
PIMap<PIString, PIString> headers, args, post;
|
|
MHD_Connection * connection = nullptr;
|
|
MicrohttpdServer * server = nullptr;
|
|
MHD_PostProcessor * postprocessor = nullptr;
|
|
};
|
|
|
|
|
|
bool MicrohttpdServerConnection::ready() {
|
|
if (!server) return false;
|
|
if (done) return true;
|
|
done = true;
|
|
MicrohttpdServer::Reply rep;
|
|
if (method == MicrohttpdServer::Method::Get) {
|
|
if (path == "/favicon.ico"_a) {
|
|
// piCout << "send favicon" << server->favicon.size() << "bytes";
|
|
rep.setBody(server->favicon);
|
|
send_reply(rep);
|
|
return true;
|
|
}
|
|
}
|
|
// piCout << "ready" << (int)method << path;
|
|
MicrohttpdServer::Request req;
|
|
req.method = method;
|
|
req.path = path;
|
|
req.body = body;
|
|
req.headers = headers;
|
|
req.args = args;
|
|
rep.setCode(MHD_HTTP_BAD_REQUEST);
|
|
if (server->callback) rep = server->callback(req);
|
|
rep.addFixedHeaders();
|
|
send_reply(rep);
|
|
return true;
|
|
}
|
|
|
|
|
|
int MicrohttpdServerConnection::send_reply(const MicrohttpdServer::Reply & r) {
|
|
MHD_Response * response = MHD_create_response_from_buffer(r.body.size(), (void *)r.body.data(), MHD_RESPMEM_MUST_COPY);
|
|
if (!response) {
|
|
// piCout << "null response" << r.body.size() << (void *)r.body.data();
|
|
return MHD_NO;
|
|
}
|
|
auto it = r.headers.makeIterator();
|
|
while (it.next())
|
|
MHD_add_response_header(response, it.key().dataAscii(), it.value().dataUTF8());
|
|
// piCout << "status" << r.code;
|
|
int ret = MHD_queue_response(connection, r.code, response);
|
|
MHD_destroy_response(response);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int MicrohttpdServerConnection::send_error() {
|
|
return MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY));
|
|
}
|
|
|
|
|
|
void log_callback(void * cls, const char * fmt, va_list ap) {
|
|
MicrohttpdServer * server = (MicrohttpdServer *)cls;
|
|
piCout << "log" << server;
|
|
if (!server) return;
|
|
char buffer[1024];
|
|
memset(buffer, 0, 1024);
|
|
std::vsnprintf(buffer, 1024, fmt, ap);
|
|
piCout << buffer;
|
|
}
|
|
|
|
|
|
int iterate_post(void * conn_cls,
|
|
MHD_ValueKind kind,
|
|
const char * key,
|
|
const char * filename,
|
|
const char * content_type,
|
|
const char * transfer_encoding,
|
|
const char * data,
|
|
uint64_t off,
|
|
size_t size) {
|
|
MicrohttpdServerConnection * conn = (MicrohttpdServerConnection *)conn_cls;
|
|
if (!conn) return MHD_NO;
|
|
conn->post[PIString::fromUTF8(key)] = PIString::fromUTF8(data);
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
void request_completed(void * cls, MHD_Connection * connection, void ** con_cls, MHD_RequestTerminationCode toe) {
|
|
MicrohttpdServerConnection *& conn((MicrohttpdServerConnection *&)(*con_cls));
|
|
// piCout << "request_completed" << conn << conn->headers << conn->post << '"' << conn->body << '"';
|
|
if (!conn) return;
|
|
if (conn->postprocessor) {
|
|
MHD_destroy_post_processor(conn->postprocessor);
|
|
conn->postprocessor = nullptr;
|
|
}
|
|
conn->ready();
|
|
piDeleteSafety(conn);
|
|
}
|
|
|
|
|
|
int header_iterate(void * cls, MHD_ValueKind kind, const char * key, const char * value) {
|
|
MicrohttpdServerConnection * conn = (MicrohttpdServerConnection *)cls;
|
|
if (!conn) return MHD_NO;
|
|
conn->headers[PIString::fromUTF8(key)] = PIString::fromUTF8(value);
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
int args_iterate(void * cls, MHD_ValueKind kind, const char * key, const char * value) {
|
|
MicrohttpdServerConnection * conn = (MicrohttpdServerConnection *)cls;
|
|
if (!conn) return MHD_NO;
|
|
conn->args[PIString::fromUTF8(key)] = PIString::fromUTF8(value);
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
int answer_to_connection(void * cls,
|
|
MHD_Connection * connection,
|
|
const char * url,
|
|
const char * method,
|
|
const char * version,
|
|
const char * upload_data,
|
|
size_t * upload_data_size,
|
|
void ** con_cls) {
|
|
MicrohttpdServer * server = (MicrohttpdServer *)cls;
|
|
|
|
MicrohttpdServer::Method m = MicrohttpdServer::Method::Unknown;
|
|
|
|
if (0 == strcmp(method, "GET"))
|
|
m = MicrohttpdServer::Method::Get;
|
|
else if (0 == strcmp(method, "POST"))
|
|
m = MicrohttpdServer::Method::Post;
|
|
else if (0 == strcmp(method, "HEAD"))
|
|
m = MicrohttpdServer::Method::Head;
|
|
else if (0 == strcmp(method, "PUT"))
|
|
m = MicrohttpdServer::Method::Put;
|
|
else if (0 == strcmp(method, "DELETE"))
|
|
m = MicrohttpdServer::Method::Delete;
|
|
else if (0 == strcmp(method, "CONNECT"))
|
|
m = MicrohttpdServer::Method::Connect;
|
|
else if (0 == strcmp(method, "OPTIONS"))
|
|
m = MicrohttpdServer::Method::Options;
|
|
else if (0 == strcmp(method, "TRACE"))
|
|
m = MicrohttpdServer::Method::Trace;
|
|
else if (0 == strcmp(method, "PATCH"))
|
|
m = MicrohttpdServer::Method::Patch;
|
|
|
|
if (m == MicrohttpdServer::Method::Unknown) {
|
|
piCout << "[MicrohttpdServer]"
|
|
<< "Warning:"
|
|
<< "Unknown method!";
|
|
return MHD_NO;
|
|
}
|
|
|
|
piCout << "answer" << url << method << (int)m << server;
|
|
MicrohttpdServerConnection *& conn((MicrohttpdServerConnection *&)(*con_cls));
|
|
if (!conn) {
|
|
conn = new MicrohttpdServerConnection();
|
|
conn->connection = connection;
|
|
conn->server = server;
|
|
conn->path = PIString::fromUTF8(url);
|
|
conn->method = m;
|
|
MHD_get_connection_values(connection, MHD_HEADER_KIND, (MHD_KeyValueIterator)header_iterate, *con_cls);
|
|
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, (MHD_KeyValueIterator)args_iterate, *con_cls);
|
|
return MHD_YES;
|
|
}
|
|
|
|
if (m == MicrohttpdServer::Method::Unknown) {
|
|
return conn->send_error();
|
|
}
|
|
|
|
if (*upload_data_size) {
|
|
if (!conn->postprocessor) {
|
|
conn->postprocessor = MHD_create_post_processor(connection, 65536, (MHD_PostDataIterator)iterate_post, (void *)conn);
|
|
}
|
|
conn->body.append(upload_data, *upload_data_size);
|
|
MHD_post_process(conn->postprocessor, upload_data, *upload_data_size);
|
|
*upload_data_size = 0;
|
|
} else {
|
|
// qDebug() << "answer ok";
|
|
if (!conn->ready()) return conn->send_error();
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
PRIVATE_DEFINITION_START(MicrohttpdServer)
|
|
MHD_Daemon * daemon;
|
|
PRIVATE_DEFINITION_END(MicrohttpdServer)
|
|
|
|
|
|
MicrohttpdServer::MicrohttpdServer() {
|
|
PRIVATE->daemon = nullptr;
|
|
opts[Option::ConnectionLimit] = FD_SETSIZE - 4;
|
|
opts[Option::ConnectionTimeout] = 0_s;
|
|
}
|
|
|
|
|
|
MicrohttpdServer::~MicrohttpdServer() {
|
|
stop();
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::setOption(Option o, PIVariant v) {
|
|
opts[o] = std::move(v);
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::setFavicon(const PIByteArray & im) {
|
|
favicon = im;
|
|
}
|
|
|
|
|
|
bool MicrohttpdServer::listen(PINetworkAddress addr) {
|
|
stop();
|
|
uint flags = 0;
|
|
#if MHD_VERSION <= 0x00095100
|
|
flags |= MHD_USE_POLL_INTERNALLY;
|
|
#else
|
|
flags |= MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD;
|
|
#endif
|
|
if (opts.value(Option::HTTPSEnabled).toBool()) flags |= MHD_USE_TLS;
|
|
mem_key = opts.value(Option::HTTPSMemKey).toByteArray();
|
|
if (mem_key.isNotEmpty()) mem_key.append(0);
|
|
mem_cert = opts.value(Option::HTTPSMemCert).toByteArray();
|
|
if (mem_cert.isNotEmpty()) mem_cert.append(0);
|
|
key_pass = opts.value(Option::HTTPSKeyPassword).toByteArray();
|
|
if (key_pass.isNotEmpty()) key_pass.append(0);
|
|
sockaddr_in sa_addr;
|
|
memset(&sa_addr, 0, sizeof(sa_addr));
|
|
sa_addr.sin_port = htons(addr.port());
|
|
sa_addr.sin_addr.s_addr = addr.ip();
|
|
sa_addr.sin_family = AF_INET;
|
|
PIVector<MHD_OptionItem> options;
|
|
options.append({MHD_OPTION_EXTERNAL_LOGGER, (intptr_t)log_callback, this});
|
|
options.append({MHD_OPTION_NOTIFY_COMPLETED, (intptr_t)request_completed, nullptr});
|
|
options.append({MHD_OPTION_CONNECTION_LIMIT, opts.value(Option::ConnectionLimit).toInt(), nullptr});
|
|
options.append({MHD_OPTION_CONNECTION_TIMEOUT, piRound(opts.value(Option::ConnectionTimeout).toSystemTime().toSeconds()), nullptr});
|
|
options.append({MHD_OPTION_SOCK_ADDR, 0, &sa_addr});
|
|
if (opts.value(Option::HTTPSEnabled).toBool()) {
|
|
options.append({MHD_OPTION_HTTPS_MEM_KEY, 0, mem_key.data()});
|
|
options.append({MHD_OPTION_HTTPS_MEM_CERT, 0, mem_cert.data()});
|
|
options.append({MHD_OPTION_HTTPS_KEY_PASSWORD, 0, key_pass.data()});
|
|
}
|
|
options.append({MHD_OPTION_END, 0, nullptr});
|
|
PRIVATE->daemon = MHD_start_daemon(flags,
|
|
addr.port(),
|
|
nullptr,
|
|
nullptr,
|
|
(MHD_AccessHandlerCallback)answer_to_connection,
|
|
this,
|
|
MHD_OPTION_ARRAY,
|
|
options.data(),
|
|
MHD_OPTION_END);
|
|
return isListen();
|
|
}
|
|
|
|
|
|
bool MicrohttpdServer::isListen() const {
|
|
return PRIVATE->daemon;
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::stop() {
|
|
if (PRIVATE->daemon) {
|
|
MHD_stop_daemon(PRIVATE->daemon);
|
|
PRIVATE->daemon = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::Reply::addHeader(const PIString & header, const PIString & value) {
|
|
headers[header] = value;
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::Reply::removeHeader(const PIString & header) {
|
|
headers.remove(header);
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::Reply::setBody(const PIByteArray & b) {
|
|
body = b;
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::Reply::setCode(int c) {
|
|
code = c;
|
|
}
|
|
|
|
|
|
void MicrohttpdServer::Reply::addFixedHeaders() {
|
|
if (!headers.contains(MHD_HTTP_HEADER_CONTENT_TYPE)) {
|
|
if (body.isNotEmpty()) {
|
|
if (body.startsWith(PIByteArray::fromAscii("<!DOCTYPE html>")))
|
|
addHeader(MHD_HTTP_HEADER_CONTENT_TYPE, "text/html; charset=utf-8");
|
|
else if (body[0] == '[' || body[0] == '{')
|
|
addHeader(MHD_HTTP_HEADER_CONTENT_TYPE, "application/json; charset=utf-8");
|
|
}
|
|
}
|
|
addHeader(MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
|
}
|