Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions frameworks/cutelyst/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)

find_package(Qt6 6.5 REQUIRED COMPONENTS Core Network)
find_package(Cutelyst5Qt6 5.1.0 REQUIRED)
find_package(ASql1Qt6 REQUIRED)
find_package(Cutelee6Qt6 REQUIRED)

add_executable(server
main.cpp
Expand All @@ -22,4 +24,9 @@ target_link_libraries(server PRIVATE
Qt6::Network
Cutelyst::Core
Cutelyst::Server
Cutelyst::View::Cutelee
ASql::Core
ASql::Pg
)

# fortunes.html is loaded from CUTELEE_INCLUDE_PATH or /templates at runtime.
54 changes: 52 additions & 2 deletions frameworks/cutelyst/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ ENV DEBIAN_FRONTEND=noninteractive

# Chunked request bodies + TCP-fragmented CRLF (HttpArena baseline / 8gbit).
ARG CUTELYST_REF=5ec5ce95d80a87b38ea45a326945bb22e515a23a
# ASql 1.x (async Postgres). Pin when tagging a release.
ARG ASQL_REF=eebafdde3069008626a558b15ae641ab356c799b
ARG CUTELEE_REF=v6.2.0

RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
Expand All @@ -15,9 +18,45 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
qt6-base-dev \
qt6-tools-dev \
qt6-l10n-tools \
qt6-declarative-dev \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src

# Django-style templates for fortunes (not in Ubuntu archives).
RUN git init cutelee \
&& cd cutelee \
&& git remote add origin https://github.com/cutelyst/cutelee.git \
&& git fetch --depth 1 origin "${CUTELEE_REF}" \
&& git checkout FETCH_HEAD \
&& cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_TEMPLATES=ON \
-DBUILD_TEXTDOCUMENT=OFF \
-DBUILD_TESTS=OFF \
-DBUILD_DOCS=OFF \
&& cmake --build build \
&& cmake --install build

# Async Postgres driver for async-db / fortunes.
RUN git init asql \
&& cd asql \
&& git remote add origin https://github.com/cutelyst/asql.git \
&& git fetch --depth 1 origin "${ASQL_REF}" \
&& git checkout FETCH_HEAD \
&& cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DASQL_DRIVER_POSTGRES=ON \
-DASQL_DRIVER_MYSQL=OFF \
-DASQL_DRIVER_ODBC=OFF \
-DASQL_DRIVER_SQLITE=OFF \
-DASQL_BUILD_DEMOS=OFF \
-DASQL_MIGRATION_TOOL=OFF \
-DBUILD_TESTING=OFF \
&& cmake --build build \
&& cmake --install build

RUN git init cutelyst \
&& cd cutelyst \
&& git remote add origin https://github.com/cutelyst/cutelyst.git \
Expand All @@ -26,6 +65,7 @@ RUN git init cutelyst \
&& cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_DOCS=OFF \
-DPLUGIN_VIEW_CUTELEE=ON \
&& cmake --build build \
&& cmake --install build

Expand All @@ -40,14 +80,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ninja-build \
pkg-config \
qt6-base-dev \
qt6-declarative-dev \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

COPY --from=cutelyst-build /usr /usr

WORKDIR /app
COPY CMakeLists.txt main.cpp app.cpp app.h root.cpp root.h ./
COPY CMakeLists.txt main.cpp app.cpp app.h root.cpp root.h fortunes.html ./
RUN cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release \
&& cmake --build build
&& cmake --build build \
&& mkdir -p /app/templates \
&& cp fortunes.html /app/templates/fortunes.html

FROM ubuntu:26.04

Expand All @@ -57,13 +101,19 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
wget \
libpq5 \
libqt6qml6 \
&& wget -q https://github.com/cutelyst/cutelyst/releases/download/v5.1.0/cutelyst5-qt6_5.1.0_amd64.deb \
&& apt-get install -y --no-install-recommends ./cutelyst5-qt6_5.1.0_amd64.deb \
&& rm -f cutelyst5-qt6_5.1.0_amd64.deb \
&& rm -rf /var/lib/apt/lists/*

COPY --from=cutelyst-build /usr/lib/ /usr/lib/
COPY --from=app-build /app/build/server /server
COPY --from=app-build /app/templates /templates

ENV CUTELEE_INCLUDE_PATH=/templates
ENV LANG=C.UTF-8

EXPOSE 8080 8081
CMD ["/server"]
8 changes: 7 additions & 1 deletion frameworks/cutelyst/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Cutelyst on Cutelyst::Server, one worker thread per core.

- **Language:** C++23
- **Framework:** Cutelyst (Qt 6), built from upstream commit `5ec5ce95` (chunked request body support on top of 5.1)
- **SQL:** [ASql](https://github.com/cutelyst/asql) async Postgres (`ASql::Pg`), thread-local pools sized from `DATABASE_MAX_CONN`
- **Templates:** Cutelee (`Cutelyst::View::Cutelee`) for fortunes
- **Build:** CMake on Ubuntu 26.04

## Endpoints
Expand All @@ -18,10 +20,14 @@ Cutelyst on Cutelyst::Server, one worker thread per core.
| `/delay/{ms}` | GET | Async wait via `ASync` + `QTimer`, then echoes `ms` |
| `/json/{count}?m=N` | GET | First `count` dataset items with `total = price * quantity * m` |
| `/echo` | POST | Returns the request body back verbatim |
| `/async-db?min=&max=&limit=` | GET | Async Postgres range query on `items` (ASql) |
| `/fortunes` | GET | Postgres `fortune` rows + runtime row, rendered with Cutelee |

## Notes

- Routing through Cutelyst controllers (`:Local` / `:AutoArgs`)
- Routing through Cutelyst controllers (`:Local` / `:AutoArgs` / `:Path`)
- JSON through `Response::setJsonObjectBody` (`QJsonDocument`)
- TLS on `:8081` when `/certs/server.crt` and `/certs/server.key` are mounted
- `json-comp` is not subscribed yet: Cutelyst has no built-in dynamic response gzip/brotli middleware under standard rules
- Postgres: read `DATABASE_URL` and size each worker-thread ASql pool as `max(1, min(DATABASE_MAX_CONN, 240) / idealThreadCount)` so 64 threads stay under the sidecar `max_connections`
- Fortunes template lives at `/templates/fortunes.html` (`CUTELEE_INCLUDE_PATH`)
50 changes: 50 additions & 0 deletions frameworks/cutelyst/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@

#include "root.h"

#include <Cutelyst/Plugins/View/Cutelee/cuteleeview.h>

#include <acoroexpected.h>
#include <adatabase.h>
#include <apg.h>
#include <apool.h>

#include <QCoreApplication>
#include <QDir>
#include <QThread>

#include <cstdlib>

using namespace ASql;
using namespace Cutelyst;
using namespace Qt::StringLiterals;

ArenaApp::ArenaApp(QObject *parent)
Expand All @@ -14,6 +27,43 @@ ArenaApp::ArenaApp(QObject *parent)

bool ArenaApp::init()
{
auto *view = new CuteleeView(this);
const char *env = std::getenv("CUTELEE_INCLUDE_PATH");
const QString includePath = env ? QString::fromLocal8Bit(env) : u"/templates"_s;
view->setIncludePaths({includePath, QDir::currentPath()});
view->preloadTemplates();

new Root(this);
return true;
}

bool ArenaApp::postFork()
{
// ASql pools are thread_local; each Cutelyst::Server worker thread gets its own pool.
const char *url = std::getenv("DATABASE_URL");
if (!url || !*url) {
return true;
}

int maxConn = 256;
if (const char *env = std::getenv("DATABASE_MAX_CONN")) {
bool ok = false;
const int parsed = QByteArray(env).toInt(&ok);
if (ok && parsed > 0) {
maxConn = parsed;
}
}

const int threads = qMax(1, QThread::idealThreadCount());
const int perThread = qMax(1, qMin(maxConn, 240) / threads);

APool::create(APg::factory(QString::fromUtf8(url)));
APool::setMaxIdleConnections(perThread);
APool::setMaxConnections(perThread);
APool::setSetupHook([](ADatabase db) -> ACoroTerminator {
db.enterPipelineMode(std::chrono::milliseconds{500});
co_return;
});

return true;
}
1 change: 1 addition & 0 deletions frameworks/cutelyst/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ArenaApp : public Cutelyst::Application
~ArenaApp() override = default;

bool init() override;
bool postFork() override;
};

#endif
15 changes: 15 additions & 0 deletions frameworks/cutelyst/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Optional wrapper so local re-runs can skip a full rebuild.
# HttpArena calls this when present (see scripts/lib/framework.sh).
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
IMAGE_NAME="${IMAGE_NAME:-httparena-cutelyst}"

if [[ "${SKIP_FRAMEWORK_BUILD:-0}" == "1" ]] \
&& docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
echo "[build] SKIP_FRAMEWORK_BUILD=1 — reusing existing $IMAGE_NAME"
exit 0
fi

docker build -t "$IMAGE_NAME" "$SCRIPT_DIR"
1 change: 1 addition & 0 deletions frameworks/cutelyst/fortunes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>{% for fortune in fortunes %}<tr><td>{{ fortune.id }}</td><td>{{ fortune.message }}</td></tr>{% endfor %}</table></body></html>
6 changes: 4 additions & 2 deletions frameworks/cutelyst/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"response": true
},
"engine": "Cutelyst::Server",
"description": "Cutelyst on Cutelyst::Server with one thread per core. Catalyst-style controllers and routing, QJsonDocument serialization, async delays via ASync and QTimer.",
"description": "Cutelyst on Cutelyst::Server with one thread per core. Catalyst-style controllers and routing, QJsonDocument serialization, async delays via ASync and QTimer, async Postgres via ASql, fortunes via Cutelee.",
"repo": "https://github.com/cutelyst/cutelyst",
"enabled": true,
"tests": [
Expand All @@ -22,7 +22,9 @@
"pipelined",
"limited-conn",
"json-tls",
"8gbit"
"8gbit",
"async-db",
"fortunes"
],
"maintainers": ["dantti"]
}
Loading
Loading