Web API#
Oasis supports HTTP, HTTPS servers and clients.
Header File#
OasisWeb.h
HttpUpStreamDelegate Interface#
Oasis Web servers and clients process HTTP requests and responses through user-defined objects derived from the HttpUpStreamDelegate interface.
class HttpUpStreamDelegate : public std::enable_shared_from_this<HttpUpStreamDelegate>
{
public:
HttpUpStreamDelegate();
virtual ~HttpUpStreamDelegate();
public:
virtual void onRequest(const NetMessageRef &msg);
virtual void onEarlyResponse(const NetMessageRef &msg); //when the response header is ready
virtual void onResponse(const NetMessageRef &msg); //when the response header and contents ready
virtual void onGatewayResponse(const struct sockaddr_in &local_addr, const struct sockaddr_in &remote_addr, int32_t status_code, const char *status_reason, const char *content_type, const char *content, size_t content_length, key_value_map_t ¶meters);
};
Web Server Functions#
To enable HTTPS, the following key-value map is required:
tls-certificate-request-authority-names key.createHttpServer.
- 0: Success
- -1: Failure
createHttpServer.
- 0: Success
- -1: Failure
createHttpServer.
- 0: Success
- -1: Failure
createHttpServer.
- 0: Success
- -1: Failure
Below is the list of key-value maps required to create a websocket service.
HttpUpStreamDelegate::onRequest API, which is the request message handler of the web server. If there is a server script tagging like <?=<key_name>?> within a web page file, a user function defined as http_interpret_callback_t is called to obtain the value of <key_name>. If the server_page_path file does not exist, it transmits a "400 Bad Request" response. On success, it transmits the content of the file along with a "200 OK" response code.
- 0: Success
- -1: Failure
The code below is an example of executing a web server script when the web document extension is osp.
static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
// Obtains the value corresponding to key_name and returns it to output.
return 0;
}
void MyHttpConfigUpStreamDelegate::onRequest(const NetMessageRef &msg)
{
bool reply_now = true;
std::string url;
netMessageUriGetPath(msg, url);
ASSERT(netMessageIsRequest(msg));
if(!netMessageIsRequest(msg)) {
return;
}
NetMessageRef res = netMessageCreate(msg, false);
if(res == nullptr) {
netMessageAbortConnection(msg);
return;
}
netMessageSetStatusCodeAndReasonString(res, 200, "OK");
netMessageSetHeaderField(res, "Connection", "close");
if(url == "/") {
std::string index_path = app().httpRootPath();
index_path += "/index.osp";
err = httpInterpretServerPage(res, url.c_str(), index_path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
if(err < 0) {
//do nothing
netMessageAbortConnection(msg);
return;
}
} else {
std::string path = app().httpRootPath();
path += url;
if(access(path.c_str(), F_OK) == 0) {
//get mime type based on extension
const char *mime_type = get_file_mime_type(path.c_str());
if(mime_type == nullptr) {
//not supported
netMessageSetStatusCodeAndReasonString(res, 403, "Forbidden");
} else {
std::string ext = get_file_ext(path);
if(ext == "osp") {
err = httpInterpretServerPage(res, url.c_str(), path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
if(err < 0) {
//do nothing
netMessageAbortConnection(msg);
return;
}
} else {
ssize_t ret = netMessageSendFile(res, path.c_str(), mime_type);
reply_now = false;
}
}
} else {
netMessageSetStatusCodeAndReasonString(res, 400, "Bad Request");
}
}
done:
if(reply_now) {
netMessagePost(res);
}
}
Script Processing Callback#
The Oasis web server can define elements for server scripts in the format of <?=<key_name>?> within web documents.
The httpInterpretServerPage function parses this element from the web document and obtains the actual value corresponding to <key_name> by calling the http_interpret_callback_t callback function. It delivers the dynamically modified web document content to the client in this manner.
httpInterpretCallback.
httpInterpretCallback.
- 0: Success
- -1: Failure
Below is an implementation example of http_interpret_callback_t.
<p>
The product name is <?=printProductName?>.
</p>
<p>
The IP address of the connected server is <?=printIPAddress?>.
</p>
static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
std::string _url(url);
char value[256];
int32_t err;
output.clear();
if(strcmp(key_name, "printProductName") == 0) {
output = "Oasis";
} else if(strcmp(key_name, "printIPAddress") == 0) {
output = "127.0.0.1";
}
return 0;
}
Web Client Functions#
The key-value map required for the connection is as follows:
httpConnectionIsClosed, and if it is already in a connected state, call httpCloseConnection before using httpOpenConnection.
- 0: Success
- -1: Failure
true.
- 0: Success
- -1: Failure
- 1: The connection is closed.
- 0: Connecting.
Example#
Server#
The server in the example below reads the index.osp file when accessing the home page, calls the Script callback function to substitute variable values inside index.osp with actual application values, and transmits it to the client. When accessing other files, it transmits the file content if allowed.
Additionally, it connects a websocket server to /ws/echo, so websocket communication takes place when a websocket client accesses the /ws/echo URL.
The application name is webserver and the usage is as follows:
USAGE: ./webserver [-p <port number>] [-s] <documnet root dir>
$ ./webserver -p 8080 -s /mnt/sd
- The p option specifies the port number.
- The s option runs the secure server (HTTPS). It is assumed that the certificate and private key are located at /mnt/sd/webserver-cert.pem and /mnt/sd/webserver-key.pem, respectively.
- document root dir is a required argument representing the root directory path of the HTTP document files.
Explaining the code.
Define the script callback function in the server.
static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
std::string _url(url);
char value[256];
int32_t err;
output.clear();
if(strcmp(key_name, "printModelName") == 0) {
output = "OASIS";
}
return 0;
}
Define a user-defined Delegate class derived from HttpUpStreamDelegate for the HTTP server.
class MyHttpUpStreamDelegate : public HttpUpStreamDelegate
{
public:
MyHttpUpStreamDelegate() { }
virtual ~MyHttpUpStreamDelegate() { }
public:
virtual void onRequest(const NetMessageRef &msg) {
std::string url;
netMessageUriGetPath(msg, url);
ASSERT(netMessageIsRequest(msg));
if(!netMessageIsRequest(msg)) {
return;
}
NetMessageRef res = netMessageCreate(msg, false);
if(res == nullptr) {
netMessageAbortConnection(msg);
return;
}
fprintf(stdout, "onRequest: %s\n", url.c_str());
netMessageSetStatusCodeAndReasonString(res, 200, "OK");
//netMessageSetHeaderField(res, "Connection", "close");
if(url == "/") {
// If it is the HOME url, it reads the index.osp file from the root directory of HTTP documents, executes the script, and transmits it.
std::string index_path = http_root_dir_path + "/index.osp";
int32_t err = httpInterpretServerPage(res, url.c_str(), index_path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
if(err < 0) {
netMessageAbortConnection(msg);
return;
} else {
netMessagePost(res);
}
} else {
std::string path = http_root_dir_path + url;
if(access(path.c_str(), F_OK) == 0) {
//get mime type based on extension
const char *mime_type = get_file_mime_type(path.c_str());
if(mime_type == nullptr) {
//not supported
netMessageSetStatusCodeAndReasonString(res, 403, "Forbidden");
netMessagePost(res);
} else {
std::string ext = get_file_ext(path);
if(ext == "osp") {
//oasis server script file extension
int32_t err = httpInterpretServerPage(res, url.c_str(), path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
if(err < 0) {
netMessageAbortConnection(msg);
return;
}
} else {
netMessageSendFile(res, path.c_str(), mime_type);
}
}
} else {
netMessageSetStatusCodeAndReasonString(res, 400, "Bad Request");
netMessagePost(res);
}
}
}
virtual void onResponse(const NetMessageRef &msg) {
}
};
The index.osp document is as follows:
<html>
<head>
<meta charset="utf-8">
<title>Oasis Web Server</title>
</head>
<body>
<h1> Hello, World!</h1>
<h2> My model is <?=printModelName?>.</h2>
</body>
</html>
Define a user-defined Delegate class derived from WsEventDelegate for the websocket server.
It is a simple function that responds with the received message as is.
class EchoWsEventDelegate : public WsEventDelegate
{
public:
EchoWsEventDelegate() {}
virtual ~EchoWsEventDelegate() {}
public:
virtual void onOpen(const WsEventRef &evt) {
fprintf(stdout, "Echo: Open\n");
}
virtual void onMessage(const WsEventRef &evt) {
fprintf(stdout, "Echo: Message\n");
if(wsEventMessageType(evt) == kWsMessageText) {
std::vector<uint8_t> data;
if(wsEventData(evt, data) > 0) {
std::string str = "Echo: ";
str += std::string(data.data(), data.data()+data.size());
wsSendText(evt, str.c_str());
}
}
}
virtual void onError(const WsEventRef &evt) {
fprintf(stdout, "Echo: Error\n");
}
virtual void onClose(const WsEventRef &evt) {
fprintf(stdout, "Echo: Close\n");
}
};
Initialize Oasis. The Oasis file system is not used.
key_value_map_t parameters;
parameters["offs-disable"] = "1";
if(oasis::initialize(parameters) < 0) {
fprintf(stderr, "Oasis init failed\n");
return -1;
}
Create and start the HTTP server object.
parameters.clear();
parameters["port"] = std::to_string(http_port);
parameters["root-dir"] = http_root_dir_path.c_str();
parameters["content-length-max"] = std::to_string(3*1024*1024);
parameters["tls-disable-certificate-validation"] = "1";
if(is_secure) {
parameters["tls-enabled"] = "1";
parameters["tls-version"] = "1.2";
parameters["cert-file-path"] = "/mnt/sd/webserver-cert.pem";
parameters["pkey-file-path"] = "/mnt/sd/webserver-key.pem";
}
http_delegate = std::make_shared<MyHttpUpStreamDelegate>();
http_server = oasis::createHttpServer(parameters, http_delegate);
if (http_server) {
err = oasis::startHttpServer(http_server);
ASSERT(err == 0);
if(err < 0) {
fprintf(stderr, "HTTPS server failed\n");
goto done;
}
fprintf(stdout, "HTTP%s running at %d...\n", is_secure?"S":"", http_port);
// Allow the websocket service to be accessed via the /ws/echo URL.
parameters.clear();
std::shared_ptr<EchoWsEventDelegate> echo_delegate = std::make_shared<EchoWsEventDelegate>();
if(createHttpWsService(http_server, "/ws/echo", parameters, echo_delegate) >= 0) {
}
}
Releases the HTTP server.
if(http_server) {
oasis::destroyHttpServer(http_server);
}
The complete code is as follows:
#include "OasisAPI.h"
#include "OasisWeb.h"
#include "OasisLog.h"
#include "OasisUtil.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <signal.h>
#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>
#define DLOG_THIS 0x00080000
#undef DLOG_FLAGS
#define DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)
#undef DLOG_TAG
#define DLOG_TAG "HTTP-SERVER"
using namespace oasis;
static bool isDirectory(const char *path)
{
struct stat path_stat;
stat(path, &path_stat);
return S_ISDIR(path_stat.st_mode);
}
static std::string http_root_dir_path;
static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
std::string _url(url);
char value[256];
int32_t err;
output.clear();
if(strcmp(key_name, "printModelName") == 0) {
output = "OASIS";
}
return 0;
}
class MyHttpUpStreamDelegate : public HttpUpStreamDelegate
{
public:
MyHttpUpStreamDelegate() { }
virtual ~MyHttpUpStreamDelegate() { }
public:
virtual void onRequest(const NetMessageRef &msg) {
std::string url;
netMessageUriGetPath(msg, url);
ASSERT(netMessageIsRequest(msg));
if(!netMessageIsRequest(msg)) {
return;
}
NetMessageRef res = netMessageCreate(msg, false);
if(res == nullptr) {
netMessageAbortConnection(msg);
return;
}
fprintf(stdout, "onRequest: %s\n", url.c_str());
netMessageSetStatusCodeAndReasonString(res, 200, "OK");
//netMessageSetHeaderField(res, "Connection", "close");
if(url == "/") {
std::string index_path = http_root_dir_path + "/index.osp";
int32_t err = httpInterpretServerPage(res, url.c_str(), index_path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
if(err < 0) {
netMessageAbortConnection(msg);
return;
} else {
netMessagePost(res);
}
} else {
std::string path = http_root_dir_path + url;
if(access(path.c_str(), F_OK) == 0) {
//get mime type based on extension
const char *mime_type = get_file_mime_type(path.c_str());
if(mime_type == nullptr) {
//not supported
netMessageSetStatusCodeAndReasonString(res, 403, "Forbidden");
netMessagePost(res);
} else {
std::string ext = get_file_ext(path);
if(ext == "osp") {
//oasis server script file extension
int32_t err = httpInterpretServerPage(res, url.c_str(), path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
if(err < 0) {
netMessageAbortConnection(msg);
return;
}
} else {
netMessageSendFile(res, path.c_str(), mime_type);
}
}
} else {
netMessageSetStatusCodeAndReasonString(res, 400, "Bad Request");
netMessagePost(res);
}
}
}
virtual void onResponse(const NetMessageRef &msg) {
}
};
class EchoWsEventDelegate : public WsEventDelegate
{
public:
EchoWsEventDelegate() {}
virtual ~EchoWsEventDelegate() {}
public:
virtual void onOpen(const WsEventRef &evt) {
fprintf(stdout, "Echo: Open\n");
}
virtual void onMessage(const WsEventRef &evt) {
fprintf(stdout, "Echo: Message\n");
if(wsEventMessageType(evt) == kWsMessageText) {
std::vector<uint8_t> data;
if(wsEventData(evt, data) > 0) {
std::string str = "Echo: ";
str += std::string(data.data(), data.data()+data.size());
wsSendText(evt, str.c_str());
}
}
}
virtual void onError(const WsEventRef &evt) {
fprintf(stdout, "Echo: Error\n");
}
virtual void onClose(const WsEventRef &evt) {
fprintf(stdout, "Echo: Close\n");
}
};
static bool continue_running = true;
void cancel_handler(int signum)
{
continue_running = false;
}
void printUsage(const char *pgname)
{
fprintf(stderr, "USAGE: %s [-p <port number>] [-s] <documnet root dir>\n", pgname);
}
using namespace oasis;
int main(int argc, char* argv[])
{
int32_t err;
int c;
int32_t http_port = 80;
bool is_secure = false;
std::shared_ptr<MyHttpUpStreamDelegate> http_delegate;
HttpServerRef http_server;
opterr = 0;
while ((c = getopt(argc, argv, "p:sh")) != -1) {
switch (c) {
case 'p':
if(optarg == nullptr) {
fprintf(stderr, "error: bad option argument '%c'\n", optopt);
return -1;
}
http_port = atoi(optarg);
if(http_port <= 0 || http_port > 65535) {
fprintf(stderr, "bad port number: %d\n", http_port);
return -1;
}
break;
case 's':
is_secure = true;
break;
case 'h':
default:
printUsage(argv[0]);
return -1;
}
}
if(argc-optind < 1) {
fprintf(stderr, "error: invalid or insufficient arguments (%d, %d)\n", argc, optind);
printUsage(argv[0]);
return -1;
}
http_root_dir_path = argv[optind];
if(!isDirectory(http_root_dir_path.c_str())) {
fprintf(stderr, "error: invalid root directory: %s\n", http_root_dir_path.c_str());
printUsage(argv[0]);
return -1;
}
key_value_map_t parameters;
parameters["offs-disable"] = "1";
if(oasis::initialize(parameters) < 0) {
fprintf(stderr, "Oasis init failed\n");
return -1;
}
parameters.clear();
parameters["port"] = std::to_string(http_port);
parameters["root-dir"] = http_root_dir_path.c_str();
parameters["content-length-max"] = std::to_string(3*1024*1024);
parameters["tls-disable-certificate-validation"] = "1";
if(is_secure) {
parameters["tls-enabled"] = "1";
parameters["tls-version"] = "1.2";
parameters["cert-file-path"] = "/mnt/sd/webserver-cert.pem";
parameters["pkey-file-path"] = "/mnt/sd/webserver-key.pem";
}
http_delegate = std::make_shared<MyHttpUpStreamDelegate>();
http_server = oasis::createHttpServer(parameters, http_delegate);
if (http_server) {
err = oasis::startHttpServer(http_server);
ASSERT(err == 0);
if(err < 0) {
fprintf(stderr, "HTTPS server failed\n");
goto done;
}
fprintf(stdout, "HTTP%s running at %d...\n", is_secure?"S":"", http_port);
//serverless websocket handler
parameters.clear();
std::shared_ptr<EchoWsEventDelegate> echo_delegate = std::make_shared<EchoWsEventDelegate>();
if(createHttpWsService(http_server, "/ws/echo", parameters, echo_delegate) >= 0) {
}
}
do {
usleep(100000);
} while(continue_running);
done:
if(http_server) {
oasis::destroyHttpServer(http_server);
}
oasis::finalize();
return 0;
}
Client#
The following is an example of a client that connects to a website and downloads the homepage HTML document.
#include "OasisAPI.h"
#include "OasisWeb.h"
#include "OasisLog.h"
#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>
#define DLOG_THIS 0x00080000
#undef DLOG_FLAGS
#define DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)
#undef DLOG_TAG
#define DLOG_TAG "HTTPCLI"
using namespace oasis;
int main(int argc, char* argv[])
{
int32_t err;
int32_t status_code;
std::string reason_string;
std::string content_type;
std::vector<char> res_content;
size_t content_length;
const char *url = "https://google.com/";
HttpConnectionRef conn;
NetMessageRef msg, res_msg;
int32_t wait_count = 0;
key_value_map_t parameters, fields;
parameters["offs-disable"] = "1";
parameters["media-cache-size"] = "4096";
if(oasis::initialize(parameters) < 0) {
DLOG(DLOG_ERROR, "Oasis init failed\n");
return -1;
}
parameters.clear();
// Set if client certificate is required.
// parameters["ca-certs-file-path"] = "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem";
// parameters["cert-file-path"] = "/data/certs/client_fullchain.pem";
// parameters["pkey-file-path"] = "/data/certs/client_privkey.pem";
parameters["tls-disable-certificate-validation"] = "1";
conn = httpOpenConnectionWithParameters(url, 30000, parameters);
if(!conn) {
DLOG(DLOG_THIS, "connection failed!\n");
goto done;
}
msg = httpConnectionGetRequestMessage(conn);
ASSERT(msg);
if(!msg) {
return -1;
}
netMessageSetMethod(msg, "GET");
if(netMessagePost(msg) < 0) {
DLOG(DLOG_ERROR, "post error\n");
goto done;
}
wait_count = 0;
do {
res_msg = httpConnectionGetResponseMessage(conn, 1000);
wait_count++;
} while(res_msg == nullptr && wait_count*1000 < 30000); //30seconds
if(res_msg == nullptr) {
//either timeout or aborted
DLOG(DLOG_ERROR, "res timeout\n");
goto done;
}
status_code = netMessageGetStatusCodeAndReasonString(res_msg, reason_string);
DLOG(DLOG_THIS, "Response: %d %s\n", status_code, reason_string.c_str());
parameters.clear();
netMessageUriGetQueryParameters(res_msg, parameters);
if(parameters.empty() == false) {
DLOG(DLOG_THIS, "Parameters:\n");
for(auto p : parameters) {
DLOG(DLOG_THIS, " [%s] = %s\n", p.first.c_str(), p.second.c_str());
}
}
DLOG(DLOG_THIS, "Headers:\n");
fields.clear();
netMessageGetHeaderFields(res_msg, fields);
for(auto p : fields) {
DLOG(DLOG_THIS, " [%s] = %s\n", p.first.c_str(), p.second.c_str());
}
content_length = netMessageGetContentLength(res_msg);
DLOG(DLOG_THIS, "Content length: %d\n", content_length);
netMessageGetContentType(res_msg, content_type);
DLOG(DLOG_THIS, "Content type: %s\n", content_type.c_str());
netMessageGetContent(res_msg, res_content);
//DUMP("REG_RES", res_content.data(), res_content.size());
if(content_length > 0) {
char path[PATH_MAX];
sprintf(path, "/tmp/%u.html", (uint32_t)time(NULL));
FILE *fp = fopen(path, "w");
if(fp) {
fwrite(res_content.data(), 1, res_content.size(), fp);
fclose(fp);
DLOG(DLOG_THIS, "saved to %s, %d bytes\n", path, content_length);
}
}
httpCloseConnection(conn);
conn = nullptr;
done:
oasis::finalize();
return 0;
}