Skip to content

URL Utilities

Glaze provides URL encoding/decoding utilities for parsing query strings and form data. These utilities follow the WHATWG URL Standard for application/x-www-form-urlencoded parsing.

#include "glaze/net/url.hpp"

The URL utilities are also available when including glaze/net/http.hpp or glaze/net/http_router.hpp.

URL Decoding

Basic Usage

// Decode percent-encoded strings
std::string decoded = glz::url_decode("hello%20world");  // "hello world"
std::string path = glz::url_decode("path%2Fto%2Ffile");  // "path/to/file"

// Plus signs are decoded as spaces (form encoding)
std::string query = glz::url_decode("search+term");  // "search term"

Supported Encodings

Encoded Decoded
%20 space
%2F /
%3D =
%26 &
%3F ?
%23 #
+ space

Buffer Reuse (Zero-Allocation)

For high-performance scenarios, pass a reusable buffer to avoid allocations:

std::string buffer;
buffer.reserve(1024);  // Pre-allocate once

// Decode multiple strings without allocation (if buffer has capacity)
glz::url_decode("hello%20world", buffer);
std::cout << buffer << std::endl;  // "hello world"

glz::url_decode("foo%2Fbar", buffer);
std::cout << buffer << std::endl;  // "foo/bar"

URL Encoding

Basic Usage

// Encode strings for safe use in URLs
std::string encoded = glz::url_encode("hello world");     // "hello+world"
std::string path = glz::url_encode("path/to/file");       // "path%2Fto%2Ffile"
std::string special = glz::url_encode("a=b&c=d");         // "a%3Db%26c%3Dd"

Encoding Rules

  • Unreserved characters pass through unchanged: A-Z, a-z, 0-9, -, ., _, ~
  • Space is encoded as + (per application/x-www-form-urlencoded)
  • All other characters are percent-encoded (%XX)

Building Query Strings

// Build a safe query string
std::string query = "q=" + glz::url_encode(user_search) +
                    "&category=" + glz::url_encode(category);

// Example: user_search = "C++ templates", category = "programming/advanced"
// Result: "q=C%2B%2B+templates&category=programming%2Fadvanced"

Roundtrip Encoding

Encode and decode are symmetric:

std::string original = "Hello World! Special: /=&?#";
std::string encoded = glz::url_encode(original);
std::string decoded = glz::url_decode(encoded);

// decoded == original

Buffer Reuse (Zero-Allocation)

std::string buffer;
buffer.reserve(1024);

glz::url_encode("hello world", buffer);
std::cout << buffer << std::endl;  // "hello+world"

glz::url_encode("a/b", buffer);
std::cout << buffer << std::endl;  // "a%2Fb"

Parsing URL-Encoded Data

The parse_urlencoded function parses key=value&key2=value2 format used in: - URL query strings (?limit=10&offset=20) - Form POST bodies (application/x-www-form-urlencoded)

Basic Usage

// Parse query string
auto params = glz::parse_urlencoded("limit=10&offset=20&sort=name");

std::cout << params["limit"];   // "10"
std::cout << params["offset"];  // "20"
std::cout << params["sort"];    // "name"

Automatic Decoding

Keys and values are automatically URL-decoded:

auto params = glz::parse_urlencoded("name=John%20Doe&city=New+York");

std::cout << params["name"];  // "John Doe"
std::cout << params["city"];  // "New York"

Edge Cases

// Empty value
auto p1 = glz::parse_urlencoded("key=");
// p1["key"] == ""

// Key without value
auto p2 = glz::parse_urlencoded("flag");
// p2["flag"] == ""

// Duplicate keys (last value wins)
auto p3 = glz::parse_urlencoded("a=1&a=2&a=3");
// p3["a"] == "3"

// Empty string
auto p4 = glz::parse_urlencoded("");
// p4 is empty

// Empty keys are skipped
auto p5 = glz::parse_urlencoded("=value&a=1");
// p5.size() == 1, p5["a"] == "1"

Buffer Reuse (Zero-Allocation)

For server applications processing many requests, reuse buffers to minimize allocations:

// Reusable buffers - allocate once, reuse for all requests
std::unordered_map<std::string, std::string> params;
std::string key_buffer;
std::string value_buffer;

// Pre-reserve capacity
key_buffer.reserve(64);
value_buffer.reserve(256);

// Process multiple query strings without allocation
for (const auto& query : incoming_queries) {
    glz::parse_urlencoded(query, params, key_buffer, value_buffer);

    // Process params...
    handle_request(params);
}

There's also a two-argument overload that manages key/value buffers internally:

std::unordered_map<std::string, std::string> params;

glz::parse_urlencoded("foo=bar&baz=qux", params);
// params is populated, internal buffers used for decoding

Splitting URL Targets

The split_target function separates a URL path from its query string:

auto [path, query] = glz::split_target("/api/users?limit=10&offset=20");
// path  == "/api/users"
// query == "limit=10&offset=20"

auto [path2, query2] = glz::split_target("/api/users");
// path2  == "/api/users"
// query2 == ""  (empty)

This is useful when you need to process the path and query string separately:

std::string_view target = "/search?q=hello%20world&page=1";

auto [path, query_string] = glz::split_target(target);
auto params = glz::parse_urlencoded(query_string);

std::cout << "Path: " << path << std::endl;           // "/search"
std::cout << "Query: " << params["q"] << std::endl;   // "hello world"
std::cout << "Page: " << params["page"] << std::endl; // "1"

Integration with HTTP Server

When using the Glaze HTTP server, query parameters are automatically parsed and available in the request object:

server.get("/api/users", [](const glz::request& req, glz::response& res) {
    // Query parameters are automatically parsed
    // For request: GET /api/users?limit=10&offset=20

    if (auto it = req.query.find("limit"); it != req.query.end()) {
        int limit = std::stoi(it->second);  // 10
    }

    if (auto it = req.query.find("offset"); it != req.query.end()) {
        int offset = std::stoi(it->second);  // 20
    }

    // req.path contains just the path without query string
    // req.path == "/api/users"

    // req.target contains the full URL
    // req.target == "/api/users?limit=10&offset=20"
});

Parsing Form POST Data

For application/x-www-form-urlencoded POST requests, use parse_urlencoded on the request body:

server.post("/login", [](const glz::request& req, glz::response& res) {
    // Check content type
    auto ct = req.headers.find("content-type");
    if (ct == req.headers.end() ||
        ct->second.find("application/x-www-form-urlencoded") == std::string::npos) {
        res.status(415).json({{"error", "Unsupported content type"}});
        return;
    }

    // Parse form data from body
    auto form = glz::parse_urlencoded(req.body);

    std::string username = form["username"];
    std::string password = form["password"];

    // Authenticate...
});

API Reference

url_decode

// Returns decoded string (allocates)
[[nodiscard]] std::string url_decode(std::string_view input);

// Writes to buffer (can avoid allocation if buffer has capacity)
void url_decode(std::string_view input, std::string& output);

url_encode

// Returns encoded string (allocates)
[[nodiscard]] std::string url_encode(std::string_view input);

// Writes to buffer (can avoid allocation if buffer has capacity)
void url_encode(std::string_view input, std::string& output);

parse_urlencoded

// Returns new map (allocates)
[[nodiscard]] std::unordered_map<std::string, std::string>
    parse_urlencoded(std::string_view query_string);

// Writes to provided map (reuses map capacity)
void parse_urlencoded(std::string_view query_string,
                      std::unordered_map<std::string, std::string>& output);

// Full buffer control (minimal allocations)
void parse_urlencoded(std::string_view query_string,
                      std::unordered_map<std::string, std::string>& output,
                      std::string& key_buffer,
                      std::string& value_buffer);

split_target

struct target_components {
    std::string_view path{};
    std::string_view query_string{};
};

constexpr target_components split_target(std::string_view target) noexcept;

hex_char_to_int

// Convert hex character to integer (0-15), returns -1 for invalid input
constexpr int hex_char_to_int(char c) noexcept;