Vix.cpp v2.6.0 is here Read the blog
Skip to content

Performance

The performance group improves HTTP response efficiency.

It helps with:

txt
compressing dynamic route responses
adding ETags
returning 304 Not Modified
compressing eligible static file responses through a hook
1
2
3
4

This page is about response performance around HTTP routes.

It does not replace routing, static file serving, or caching.

Core still owns:

txt
routes
handlers
static files
templates
server lifecycle
1
2
3
4
5

The middleware module adds reusable performance behavior around responses.

What performance provides

The performance group includes:

FeaturePurpose
compression()Compress dynamic route responses when the client accepts it
etag()Generate ETags for GET and HEAD responses
static_compression.hppProvide an optional static response compression hook

The main namespace is:

cpp
namespace vix::middleware::performance
1

When using vix::App, use the App adapter when a direct App preset is not available:

cpp
vix::middleware::app::adapt_ctx(...)
1

Performance vs HTTP cache

Performance middleware and HTTP cache solve different problems.

txt
HTTP cache
  stores dynamic GET responses server-side
  can skip the handler on cache hit

compression
  reduces response body size

ETag
  lets clients revalidate a response
  can return 304 Not Modified

static response hook
  can compress files served by app.static_dir(...)
1
2
3
4
5
6
7
8
9
10
11
12
13

Use them together when appropriate, but keep their roles separate.

A practical route stack can look like this:

cpp
app.use("/api", middleware::app::security_headers_dev());
app.use("/api", middleware::app::rate_limit_dev());

app.use("/api", middleware::app::http_cache({
  .ttl_ms = 30'000
}));

app.use("/api", middleware::app::adapt_ctx(
  middleware::performance::etag()
));

app.use("/api", middleware::app::adapt_ctx(
  middleware::performance::compression()
));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

The idea is:

txt
HTTP cache
  may replay a response and skip the handler

ETag
  can validate the final body

compression
  can reduce the final response body
1
2
3
4
5
6
7
8

The exact order depends on how your application writes responses and how you want to combine cache validation and compression.

Start simple.

Add one performance feature at a time, test headers, then combine them.

Compression

compression() compresses dynamic route responses when the client sends an acceptable Accept-Encoding header.

It is useful for:

txt
large JSON responses
HTML responses
text responses
API lists
generated documents
1
2
3
4
5

It should not be confused with static file compression.

This middleware runs in the normal App middleware chain.

txt
request
  -> route middleware
  -> handler writes response
  -> compression middleware may compress response
  -> response
1
2
3
4
5

Dynamic compression example

cpp
#include <string>

#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use(middleware::app::adapt_ctx(
    middleware::performance::compression({
      .min_size = 8,
      .add_vary = true,
      .enabled = true
    })
  ));

  app.get("/", [](Request &, Response &res)
  {
    res.text("Compression middleware installed. Try /large.");
  });

  app.get("/large", [](Request &, Response &res)
  {
    res.status(200).text(std::string(2048, 'a'));
  });

  app.get("/small", [](Request &, Response &res)
  {
    res.status(200).text("small");
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

Run:

bash
vix run compression_demo.cpp
1

Request without compression:

bash
curl -i http://127.0.0.1:8080/large
1

Request with compression support:

bash
curl -i \
  http://127.0.0.1:8080/large \
  -H "Accept-Encoding: gzip"
1
2
3

Small response:

bash
curl -i \
  http://127.0.0.1:8080/small \
  -H "Accept-Encoding: gzip"
1
2
3

The small response may not be compressed because it is below min_size.

Compression options

Main options:

OptionPurpose
min_sizeMinimum body size before compression is considered
add_varyAdd Vary: Accept-Encoding
enabledEnable or disable compression
gzip_levelCompression level when gzip support is available

Example:

cpp
vix::middleware::performance::CompressionOptions opt;

opt.min_size = 1024;
opt.add_vary = true;
opt.enabled = true;

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::performance::compression(opt)
));
1
2
3
4
5
6
7
8
9

Use a minimum size to avoid wasting CPU on tiny responses.

A practical starting value is:

txt
1024 bytes
1

Vary header

When compression depends on Accept-Encoding, the response should include:

txt
Vary: Accept-Encoding
1

This tells caches that the response can differ depending on the request's Accept-Encoding header.

Enable it with:

cpp
.add_vary = true
1

This is usually the correct default for compressed responses.

When not to compress

Avoid compression for:

txt
very small responses
already compressed data
streaming responses
CPU-sensitive endpoints under heavy load
responses where latency matters more than bandwidth
1
2
3
4
5

Examples of already compressed data:

txt
jpg
png
webp
zip
gz
mp4
1
2
3
4
5
6

For static files, prefer pre-compressed assets at the deployment layer when possible.

Use Vix static response compression when you want Vix itself to handle eligible static responses.

ETag

etag() generates an entity tag for successful GET and HEAD responses.

The ETag lets a client ask:

txt
Has this response changed since the last time I fetched it?
1

If the client sends If-None-Match and the tag matches, the middleware can return:

txt
304 Not Modified
1

This avoids sending the body again.

ETag example

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use(middleware::app::adapt_ctx(
    middleware::performance::etag({
      .weak = true,
      .add_cache_control_if_missing = false,
      .min_body_size = 1
    })
  ));

  app.get("/article", [](Request &, Response &res)
  {
    res.text("Hello from Vix");
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Run:

bash
vix run etag_demo.cpp
1

First request:

bash
curl -i http://127.0.0.1:8080/article
1

Look for:

txt
ETag: ...
1

Then revalidate with that value:

bash
curl -i \
  http://127.0.0.1:8080/article \
  -H 'If-None-Match: <etag-value>'
1
2
3

If the ETag matches, expected status:

txt
304 Not Modified
1

ETag options

Main options:

OptionPurpose
weakGenerate weak ETags such as W/"..."
add_cache_control_if_missingAdd a default Cache-Control header if missing
cache_controlCache-Control value to add when enabled
min_body_sizeMinimum body size before adding an ETag

Example:

cpp
vix::middleware::performance::EtagOptions opt;

opt.weak = true;
opt.add_cache_control_if_missing = true;
opt.cache_control = "public, max-age=0";
opt.min_body_size = 1;

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::performance::etag(opt)
));
1
2
3
4
5
6
7
8
9
10

Use ETags when clients may repeatedly fetch the same response and can benefit from revalidation.

ETag vs HTTP cache

ETag does not skip your route handler by itself in the same way server-side HTTP cache can.

ETag helps the client avoid downloading the body when it already has the latest version.

txt
HTTP cache middleware
  server stores response
  cache hit can skip handler

ETag middleware
  response gets a validation tag
  client can revalidate
  server can return 304
1
2
3
4
5
6
7
8

They can complement each other.

Use HTTP cache for server-side reuse.

Use ETag for client-side revalidation.

Static response compression

Static file serving belongs to vix::App.

Use:

cpp
app.static_dir(
  "public",
  "/",
  "index.html",
  true,
  "public, max-age=3600",
  true,
  false
);
1
2
3
4
5
6
7
8
9

Core handles:

txt
public directory
mount path
index file
Cache-Control
fallthrough
SPA fallback
file response
1
2
3
4
5
6
7

The middleware module can provide an optional hook after a static file response has been written.

That hook can compress eligible static file responses.

cpp
vix::App::set_static_response_hook(
  vix::middleware::performance::compressed_static_response_hook()
);
1
2
3

This is not route middleware.

It is a static response hook.

Static compression with options

Use the same compression options when you want dynamic and static responses to follow the same compression policy.

cpp
vix::middleware::performance::CompressionOptions compression_options{
  .min_size = 1024,
  .add_vary = true,
  .enabled = true
};

app.use(vix::middleware::app::adapt_ctx(
  vix::middleware::performance::compression(compression_options)
));

vix::App::set_static_response_hook(
  vix::middleware::performance::compressed_static_response_hook(compression_options)
);

app.static_dir(
  "public",
  "/",
  "index.html",
  true,
  "public, max-age=3600",
  true,
  false
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

This does two different things:

txt
app.use(compression(...))
  compresses dynamic route responses

App::set_static_response_hook(...)
  compresses eligible static file responses served by app.static_dir(...)
1
2
3
4
5

Keep those responsibilities separate.

Configuration-driven static compression

Generated applications may wire static behavior from environment or config values.

Example configuration:

dotenv
PUBLIC_PATH=public
PUBLIC_MOUNT=/
PUBLIC_INDEX=index.html
PUBLIC_CACHE_CONTROL=public, max-age=3600
PUBLIC_SPA_FALLBACK=false
PUBLIC_COMPRESSION=false
PUBLIC_COMPRESSION_MIN_SIZE=1024
1
2
3
4
5
6
7

Bootstrap code can read those values and wire Core plus the optional middleware hook.

cpp
const std::string publicPath = cfg.getString("public.path", "public");
const std::string publicMount = cfg.getString("public.mount", "/");
const std::string publicIndex = cfg.getString("public.index", "index.html");
const std::string publicCacheControl =
  cfg.getString("public.cache_control", "public, max-age=3600");

const bool publicSpaFallback = cfg.getBool("public.spa_fallback", false);
const bool publicCompression = cfg.getBool("public.compression", false);
const int publicCompressionMinSize =
  cfg.getInt("public.compression_min_size", 1024);

if (publicCompression)
{
  const auto compressionOptions =
    vix::middleware::performance::CompressionOptions{
      .min_size = static_cast<std::size_t>(publicCompressionMinSize),
      .add_vary = true,
      .enabled = true
    };

  app.use(vix::middleware::app::adapt_ctx(
    vix::middleware::performance::compression(compressionOptions)
  ));

  vix::App::set_static_response_hook(
    vix::middleware::performance::compressed_static_response_hook(compressionOptions)
  );
}

app.static_dir(
  publicPath,
  publicMount,
  publicIndex,
  true,
  publicCacheControl,
  true,
  publicSpaFallback
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

The model is:

txt
Core reads static file configuration through app.static_dir(...)
Middleware contributes compression only when enabled
Bootstrap connects configuration to both
1
2
3

Static files are not middleware

Do not describe static file serving as a middleware feature.

This is Core:

cpp
app.static_dir(...)
1

This is middleware enhancement:

cpp
vix::App::set_static_response_hook(
  vix::middleware::performance::compressed_static_response_hook()
);
1
2
3

This is route middleware:

cpp
app.use(vix::middleware::app::adapt_ctx(
  vix::middleware::performance::compression()
));
1
2
3

The distinction matters:

txt
app.static_dir(...)
  serves files

app.use(compression(...))
  compresses dynamic route responses

set_static_response_hook(...)
  can compress static file responses after Core writes them
1
2
3
4
5
6
7
8

Compression and ETag together

Compression and ETag can both touch the response body or headers.

When combining them, test the actual headers returned by your application.

A simple starting point is:

cpp
app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::performance::etag()
));

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::performance::compression()
));
1
2
3
4
5
6
7

Then inspect:

bash
curl -i \
  http://127.0.0.1:8080/api/data \
  -H "Accept-Encoding: gzip"
1
2
3

Check:

txt
ETag
Vary
Content-Encoding
status code
body behavior
1
2
3
4
5

If your deployment already compresses responses at Nginx, CDN, or another proxy, avoid double compression.

Use one compression layer deliberately.

Complete dynamic performance example

cpp
#include <string>

#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::security_headers_dev());

  app.use("/api", middleware::app::adapt_ctx(
    middleware::performance::etag({
      .weak = true,
      .add_cache_control_if_missing = true,
      .cache_control = "public, max-age=0",
      .min_body_size = 1
    })
  ));

  app.use("/api", middleware::app::adapt_ctx(
    middleware::performance::compression({
      .min_size = 1024,
      .add_vary = true,
      .enabled = true
    })
  ));

  app.get("/api/data", [](Request &, Response &res)
  {
    res.status(200).text(std::string(4096, 'x'));
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

Run:

bash
vix run performance_demo.cpp
1

Request normally:

bash
curl -i http://127.0.0.1:8080/api/data
1

Request with gzip support:

bash
curl -i \
  http://127.0.0.1:8080/api/data \
  -H "Accept-Encoding: gzip"
1
2
3

Revalidate with ETag:

bash
curl -i \
  http://127.0.0.1:8080/api/data \
  -H 'If-None-Match: <etag-value>'
1
2
3

Complete static compression example

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  vix::middleware::performance::CompressionOptions options{
    .min_size = 1024,
    .add_vary = true,
    .enabled = true
  };

  vix::App::set_static_response_hook(
    vix::middleware::performance::compressed_static_response_hook(options)
  );

  app.static_dir(
    "public",
    "/",
    "index.html",
    true,
    "public, max-age=3600",
    true,
    true
  );

  app.get("/api/health", [](Request &, Response &res)
  {
    res.json({
      "ok", true
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

This example only enables static response compression.

It does not install dynamic route compression.

To compress dynamic routes too, also add:

cpp
app.use(vix::middleware::app::adapt_ctx(
  vix::middleware::performance::compression(options)
));
1
2
3

Summary

Use the performance group to reduce repeated transfer cost and improve HTTP response behavior.

Use:

cpp
compression()
1

for dynamic route responses.

Use:

cpp
etag()
1

for client revalidation.

Use:

cpp
compressed_static_response_hook()
1

to enhance static file responses served by Core.

Remember the separation:

txt
HTTP cache skips repeated dynamic handler work
Compression reduces body size
ETag enables client revalidation
Core serves static files
Middleware can enhance static responses
1
2
3
4
5

Released under the MIT License.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.