Serving Static Files and Single Page Applications on Oat++ (OatPP)

Oat++(OatPP) is a lightweight C++ Web framework. Out of the box, it provides REST API with built-in JSON serialization/deserialization features, which could be interfaced with your DTOs.

It also offers various modules that could be easily added and used in your application in a plug-in manner.

Since the main concept of the framework is RestAPI, it does not provide static file serving as default. It means that you must implement your solution to serve your static resources over the Oat++ Web server.

Despite sounding a little scary, it is not difficult at all. With the help of the frameworks Oatpp::String::loadFromFile method, we can easily implement our solution.

Before getting started we can list the steps that we will follow below;

  1. Create a public static folder and files
  2. File Mime Content Type Mapping
  3. Define Endpoints

1- Create a public static folder and files

This folder will be used to serve static sources and will be accessible over the HTTP Protocol. So we can store our HTML, JS, CSS files, images, etc.

I will create my folder directly under the project root folder and name it as public in my example. Create a simple HTML file, a simple JS file, a simple CSS file, a favicon, and an image to simulate the widely used content types in order the following folder tree.

Folder tree

ProjectRoot/
├─ public/
│ ├─ favicon.ico
│ ├─ index.html
│ ├─ index.js
│ ├─ style.css
│ ├─ images/
│ │ ├─ logo.svg
Enter fullscreen mode
Exit fullscreen mode

index.html

<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" href="favicon.ico"> http://index.js <link rel="stylesheet" href="style.css"> <title>Oat++ Static File Serving Example</title>
</head>
<body> <div class="container"> <div class="header"> <img src="images/logo.png" alt="Logo"> <h1> Oat++ </h1> <p id="counter"> </p> </div> <div class="main"> <button id="clicker" onclick="onCount()">Count</button> </div> <div class="footer"></div> </div>
</body>
</html>
Enter fullscreen mode
Exit fullscreen mode

index.js

var iterator = 0;
var counter = document.getElementById("counter");
function onCount() { counter.innerText = iterator++; console.log("Counter Increased: ", iterator);
};
Enter fullscreen mode
Exit fullscreen mode

style.css

body { background-color: bisque; color: black;
} .header {
display: block;
} 
Enter fullscreen mode
Exit fullscreen mode

Images

An .ico file as a favicon, and an image as a logo.

favicon would be stored in the static folder according to my HTML file and logo under the images folder which is a sub-folder of the public.

2. File Mime Content Type Mapping

Web servers usually provide a public folder, like www where you can store your static resources via S/FTP, WebUI, SSH, etc.
Or you can set your dedicated folder as a static resources folder accessible over HTTP.

So, if you have an index.html file in your static folder, you can access it over HTTP via http://localhost:port/index.html. All the other files located in the static folder would be accessible with the same path structure as well. For example, we can access the logo.png file under the images folder via this URL: http://localhost:port/images/logo.png

This is what we are looking for, right? We are almost there.
We also need a content-type mapper to respond correctly with files regarding its extension.

I will shortly create a sample function that expects the path and finds the extension by easily looking after the last. in the path and return the correct content type.

To create a full mime content type, you can reference NGINX’s map linked below.
https://github.com/nginx/nginx/blob/master/conf/mime.types

std::string* getContentType(const std::string &path) { const size_t i = path.find_last_of("."); if (i != std::string::npos) { const std::string extension = path.substr(i+1); if(extension == "html" || extension == "htm" || extension == "shtml") return "text/html"; if (extension == "js") return "application/javascript"; if (extension == "css") return "text/css"; if (extension == "jpg" || extension == "jpg") return "image/jpeg"; if (extension == "gif") return "image/gif"; if (extension == "ico") return "image/x-icon"; if (extension == "png") return "image/png"; if (extension == "svg" || extension == "svgz") return "image/svg+xml"; } return nullptr; // if the path has no "." or has unknow extension we can not find the correct mime as well.
};
Enter fullscreen mode
Exit fullscreen mode

3. Defining Endpoints

The best way of serving static files is by defining a wildcard endpoint at the end of the routes.

Open your last controller or better create one for static routes and add it to the router as the last controller to prevent any unexpected bypass of API endpoints.
I am working on async but you can easily refactor the code in your regular endpoint.

In the following example, we will accept the wildcard route and check;

We will respond with the index.html for route (/) endpoint.
Try to read the file, and respond with 404 in case of not found.
If we find the file, then check the content type regarding its extension and add the content-type header with its mapped mime in the response.

You can also change the code and respond with the index.html for not-found files to Single-Page Application compatibility.

static-controller

ENDPOINT_ASYNC("GET", "*", Wildcard) { ENDPOINT_ASYNC_INIT(Wildcard) Action act() override { std::string path = request->getPathTail(); // Load the home page in case of no path, it means calling root like: http://localhost/ if (path.empty()) { path = "index.html"; // This is my default home file, you can set your own home page as well. } // We will check the file if exist and send the index.html in case of not auto file = oatpp::String::loadFromFile(path.c_srt()); // Send 404 not found in case of no file OATPP_ASSERT_HTTP(file.get() != nullptr, Status::CODE_404, "File not found"); // As file already found, we can search the content type const std::string* contentType = getContentType(path); // Creating the response auto response = this->controller->createResponse(Status::CODE_200, file); if(contentType != nullptr) // Add the content-type header only if we have a known mime res->putHeader(Header::CONTENT_TYPE, *contentType); return _return(response); }; };
Enter fullscreen mode
Exit fullscreen mode

This is the basic implementation and simple example of serving static files over Oat++ and I hope will help you.


Discover more from Coursity

Subscribe to get the latest posts sent to your email.

Leave a Comment

Your email address will not be published. Required fields are marked *

Discover more from Coursity

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top