Http11Probe

libuvcpp

Language: C++ Β· View source on GitHub

A C++11 HTTP framework built on libuv's event loop, serving through uvcpp_web_app -- the library's high-level server, which owns routing, the HEAD-to-GET fallback and the automatic OPTIONS answer. Built against the pinned v1.6.2 linux-x64 release package, whose shared object already contains libuv, OpenSSL, nghttp2, ngtcp2 and zlib; the sha256 in the Dockerfile is what fixes which bytes get compiled. 1.6.2 is the first release that rejects a missing, duplicated or invalid Host header (RFC 9112 section 3.2) and an all-blank Transfer-Encoding (RFC 9112 section 6.1); it scores 157/159 where 1.6.1 scores 149/159.

Dockerfile

# libuvcpp entry for Http11Probe.
#
# The build downloads the official v1.6.2 linux-x64 release package and compiles
# server.cpp against it -- the same artifact a user gets from the GitHub release.
# That package's shared object already carries libuv, OpenSSL, nghttp2, ngtcp2
# and zlib, so the runtime image needs no HTTP library and no toolchain.
#
# 1.6.2 is the first release whose parser answers 400 to an HTTP/1.1 request with
# no Host header, with more than one, or with an invalid one (RFC 9112 section
# 3.2), and to an all-blank Transfer-Encoding (RFC 9112 section 6.1) -- the
# latter previously fell through to Content-Length framing while keeping the
# connection alive. Entries built against 1.6.1 or earlier report 149/159 on the
# probe; 1.6.2 reports 157/159.
#
# The download is pinned by sha256 rather than by tag: a release tag can be moved
# and its assets re-uploaded, so the hash is what fixes which bytes get built.
# The transfer is forced over HTTPS, redirects included, rather than left to
# curl's default of following a redirect into whatever scheme it names.
#
# Both stages are ubuntu:24.04 (glibc 2.39; the release package's floor is
# 2.34), so the compiler that links the binary and the libc that runs it are the
# same ones. Alpine is not an option: the package is glibc-linked.

FROM ubuntu:24.04 AS build

ARG LIBUVCPP_VERSION=1.6.2
ARG LIBUVCPP_SHA256=636f07346314d5f5b8fc3dbae34861a6b4441c337fc24a632011c0d7ca72d4fe

RUN apt-get update \
 && apt-get install -y --no-install-recommends \
        ca-certificates curl g++ unzip \
 && rm -rf /var/lib/apt/lists/*

WORKDIR /build
RUN curl -fsSL --proto '=https' --proto-redir '=https' --tlsv1.2 \
        --retry 3 --retry-connrefused -o libuvcpp.zip \
        "https://github.com/Antruly/libuvcpp/releases/download/v${LIBUVCPP_VERSION}/libuvcpp-${LIBUVCPP_VERSION}-linux-x64.zip" \
 && echo "${LIBUVCPP_SHA256}  libuvcpp.zip" | sha256sum -c - \
 && unzip -q libuvcpp.zip -d /opt \
 && rm -f libuvcpp.zip

COPY src/Servers/LibuvcppServer/server.cpp .
RUN g++ -std=c++11 -O2 -DNDEBUG -o /probe-server server.cpp \
        -I/opt/libuvcpp-1.6.2-linux-x64/include \
        -L/opt/libuvcpp-1.6.2-linux-x64/lib \
        -luvcpp -Wl,-rpath,/opt/libuvcpp-1.6.2-linux-x64/lib -pthread

FROM ubuntu:24.04

# g++ links the C++ runtime dynamically, so the runtime layer needs it.
RUN apt-get update \
 && apt-get install -y --no-install-recommends libstdc++6 \
 && rm -rf /var/lib/apt/lists/*

COPY --from=build /opt/libuvcpp-1.6.2-linux-x64/lib/libuvcpp.so \
                  /opt/libuvcpp-1.6.2-linux-x64/lib/libuvcpp.so
COPY --from=build /probe-server /usr/local/bin/probe-server

USER nobody
ENTRYPOINT ["probe-server", "8080"]

Source β€” server.cpp

// libuvcpp entry for Http11Probe.
//
// Three endpoints, per docs/content/add-a-framework: `/` (baseline, and body
// echo on POST), `/echo` (the headers the server received) and `/cookie` (the
// cookies the server parsed). Everything goes through `uvcpp_web_app`, the
// library's documented high-level server.

#include <webapp/uvcpp_web_app.h>
#include <webapp/uvcpp_web_util.h>

#include <cstdio>
#include <cstdlib>
#include <string>
#include <utility>
#include <vector>

using namespace uvcpp;

namespace {

typedef std::vector<std::pair<std::string, std::string> > cookie_list;

std::string cookies_to_lines(const cookie_list& cs) {
  std::string out;
  for (size_t i = 0; i < cs.size(); ++i) {
    out += cs[i].first;
    out += '=';
    out += cs[i].second;
    out += '\n';
  }
  return out;
}

// The baseline endpoint: 200 for GET, and the request body echoed back for
// POST.
void root_get(uvcpp_web_request&, uvcpp_web_response& resp, uvcpp_web_next) {
  resp.text("OK");
  resp.end();
}

void root_post(uvcpp_web_request& req, uvcpp_web_response& resp, uvcpp_web_next) {
  const char* p = req.body_data();
  if (p == nullptr) {
    resp.text(std::string());
  } else {
    resp.body(p, req.body_size(), "text/plain");
  }
  resp.end();
}

// One "name: value" line per header, in the order the parser recorded them.
void echo(uvcpp_web_request& req, uvcpp_web_response& resp, uvcpp_web_next) {
  std::string out;
  const http_headers& hs = req.headers();
  for (size_t i = 0; i < hs.size(); ++i) {
    out += hs[i].name;
    out += ": ";
    out += hs[i].value;
    out += '\n';
  }
  resp.text(out);
  resp.end();
}

// What the library's own cookie parser made of the Cookie header.
void cookie(uvcpp_web_request& req, uvcpp_web_response& resp, uvcpp_web_next) {
  resp.text(cookies_to_lines(web_parse_cookies(req.header("cookie"))));
  resp.end();
}

}  // namespace

int main(int argc, char** argv) {
  const int port = argc > 1 ? std::atoi(argv[1]) : 8080;

  uvcpp_web_app app;

  // `/*path` is the catch-all; the framework's own HEAD-to-GET fallback and
  // automatic OPTIONS answer the other methods, so neither is registered here.
  app.get("/", root_get);
  app.post("/", root_post);
  app.get("/echo", echo);
  app.post("/echo", echo);
  app.get("/cookie", cookie);
  app.post("/cookie", cookie);
  app.get("/*path", root_get);
  app.post("/*path", root_post);

  // Everything else is the framework's default: 0.0.0.0, port 8080 unless
  // overridden, and the documented HEAD/OPTIONS behaviour above.
  app.set_port(port);

  const int rc = app.start_background();
  if (rc != 0) {
    std::fprintf(stderr, "listen on %d failed: %d\n", port, rc);
    return 1;
  }
  app.join();
  return 0;
}

Test Results

Loading results...

Compliance

Smuggling

Malformed Input

Caching

Cookies

Http11Probe β€” HTTP/1.1 compliance & smuggling testerSource on GitHub