If we look at any existing web server API, like ASP.Net Core
below:
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Serve default files like index.html
app.UseDefaultFiles();
// Serve static files from wwwroot/
app.UseStaticFiles();
app.Run();
or Flask
:
from flask import Flask
app = Flask(
__name__,
static_folder='static', # default
static_url_path='' # serve at root instead of '/static'
)
@app.route('/')
def index():
# Sends static/index.html
return app.send_static_file('index.html')
if __name__ == '__main__':
app.run(debug=True)
or NodeJS:
// server.js
import { createServer } from 'http';
import { createReadStream, statSync } from 'fs';
import { extname, join } from 'path';
const PUBLIC_DIR = join(process.cwd(), 'public');
createServer((req, res) => {
// Map URL → file path
const urlPath = req.url === '/' ? '/index.html' : req.url;
const filePath = join(PUBLIC_DIR, urlPath);
try {
const stats = statSync(filePath);
if (stats.isFile()) {
const stream = createReadStream(filePath);
// Minimal mime-type handling:
const ext = extname(filePath).slice(1);
res.setHeader('Content-Type', {
html: 'text/html',
js: 'application/javascript',
css: 'text/css',
png: 'image/png',
jpg: 'image/jpeg',
}[ext] || 'application/octet-stream');
stream.pipe(res);
return;
}
} catch {
// fall through to 404
}
res.statusCode = 404;
res.end('Not found');
}).listen(3000, () => console.log('Listening on http://localhost:3000'));
With Express.js
:
// app.js
import express from 'express';
const app = express();
// Serve index.html automatically at '/'
app.use(express.static('public'));
// (Optional) Serve another folder at /assets
app.use('/assets', express.static('PublicAssets'));
app.listen(3000, () => {
console.log('Static server running on http://localhost:3000');
});
There are some common features:
- We define what the server does
- We provide some "endpoints"
- And we let the server run wild
There is an obvious data-driven pattern. Now, if you have followed Divooka's development, or have seen a few use cases of the Dataflow context (e.g. with plotting), it would immediately appear to you: wow, we should be able to do this trivially in Divooka!
Indeed it is!
Ol'ista - The Divooka Way
Ol'ista (codename Oista
) is Divooka Explore's builtin web framework.
A web server is configured using capabilities, those include: static file hosting, FTP server, web API, etc.
Serve Static Files
Create FTP Server
Serve Web API
Standard Components
For most common things that almost all web frameworks provide, Ol'ista provides it through the Static File Capability and Web API Capability. Configuration is done through succession of compositional APIs:
You can serve many capabilities at once:
Dynamic Behaviors
Dynamic behaviors or what's typically understood as web APIs are served through Web API Capability
, and Divooka enables it through function references:
Server Side
Full Setup (Server and Client)
Result on Client
Dynamic Webpages
Most web frameworks offers some kind of template language for easier generated HTML contents.
ASP .NET Core (Razor)
Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] – MyApp</title>
</head>
<body>
<header>
@await Html.PartialAsync("_Nav")
</header>
<main role="main">
@RenderBody()
</main>
<footer>
@RenderSection("Scripts", required: false)
</footer>
</body>
</html>
Views/Home/Index.cshtml
@{
ViewData["Title"] = "Home";
var items = new[] { "Apple", "Banana", "Cherry" };
}
<h1>Hello, @User.Identity.Name!</h1>
<ul>
@foreach (var item in items)
{
<li>@item</li>
}
</ul>
@section Scripts {
<script>console.log("Page loaded");</script>
}
Python Flask (Jinja2)
templates/base.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}MyApp{% endblock %}</title>
</head>
<body>
<header>
{% include "nav.html" %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block scripts %}{% endblock %}
</footer>
</body>
</html>
templates/index.html
{% extends "base.html" %}
{% block title %}Home – MyApp{% endblock %}
{% block content %}
<h1>Hello, {{ current_user.name }}!</h1>
<ul>
{% for fruit in ["Apple","Banana","Cherry"] %}
<li>{{ fruit }}</li>
{% endfor %}
</ul>
{% endblock %}
{% block scripts %}
<script>console.log("Page loaded");</script>
{% endblock %}
Ruby on Rails (ERB)
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= content_for?(:title) ? yield(:title) : "MyApp" %></title>
</head>
<body>
<header>
<%= render "nav" %>
</header>
<main>
<%= yield %>
</main>
<footer>
<%= yield :scripts if content_for?(:scripts) %>
</footer>
</body>
</html>
app/views/home/index.html.erb
<% content_for :title, "Home – MyApp" %>
<h1>Hello, <%= current_user.name %>!</h1>
<ul>
<% ["Apple","Banana","Cherry"].each do |fruit| %>
<li><%= fruit %></li>
<% end %>
</ul>
<% content_for :scripts do %>
<script>console.log("Page loaded");</script>
<% end %>
Among those, ASP.Net Core and Ruby on Rails appear cleaner.
Comparison
Feature | ASP .NET Core (Razor) | Python Flask (Jinja2) | Ruby on Rails (ERB) |
---|---|---|---|
File extension | .cshtml |
.html (or .jinja2 ) |
.html.erb |
Expression delimiter |
@Model.Property or @(...)
|
{{ variable }} |
<%= @variable %> |
Statement delimiter |
@{ ... } , @if(...) { ... }
|
{% ... %} |
<% ... %> |
Partial/templates include | @await Html.PartialAsync("_Nav") |
{% include "nav.html" %} |
<%= render "nav" %> |
For Divooka, as a start, I plan to implement something I call Markit - a simple string replacement. This awaits further work and will be updated on Ol'ista's wiki.
References
- The Ol'ista framework for Divooka
- Full setup walkthrough for the example.
Top comments (0)