HTTP Header Normalizer

Paste a raw header block and get one canonical form back — names re-cased, values trimmed, duplicates folded, lines sorted. Two captures of the same request finally diff cleanly.

Try:
Normalized headers

About this tool

HTTP field names are case-insensitive on the wire, but every log, diff, and string comparison treats them as plain text. So the same request shows up as Content-Type, content-type, and CONTENT-TYPE; values arrive with ragged spacing after the colon; repeated names appear in a different order in every capture. Comparing two of those blocks by eye is miserable.

This tool rewrites a pasted header block into one canonical form. Names are re-cased, values are trimmed, obsolete indented continuation lines are joined, repeated names are folded, and the lines are sorted, so two captures of the same request line up.

A worked example with the defaults — canonical casing, sorted by name, duplicates joined, folds unwrapped:

GET /v1/items?page=2 HTTP/1.1
host:   api.example.com
ACCEPT: application/json
accept: text/plain
x-request-id:   9f3c
user-agent: demo/1.0
content-type:application/json

becomes

GET /v1/items?page=2 HTTP/1.1
Accept: application/json, text/plain
Content-Type: application/json
Host: api.example.com
User-Agent: demo/1.0
X-Request-ID: 9f3c

The optional request line at the top is kept verbatim, x-request-id gets its conventional X-Request-ID spelling from the built-in exception table, and the two accept lines merge into one comma-joined field.

Field name casing offers canonical Title-Case, lowercase (what HTTP/2 and most proxy logs use), UPPERCASE, or preserving whatever casing each name had the first time it appeared. Canonical mode knows the names that plain title-casing gets wrong: ETag, WWW-Authenticate, DNT, TE, Content-MD5, X-XSS-Protection, Sec-WebSocket-Key, X-UA-Compatible, Last-Event-ID, and similar.

Drop these names and Keep only these names take comma-separated lists with an optional trailing * prefix wildcard, so authorization,cookie,x-* redacts credentials and internal headers before a capture goes into a bug report, and host,content-type reduces a block to the two fields you actually want to compare.

Result can be the normalized block, the same headers as copy-pasteable curl -H flags, or a CSV summary of what changed.

Limits and edge cases:

FAQ

Does changing header name casing change the request?

No. RFC 7230 (HTTP/1.1) defines field names as case-insensitive, so Content-Type, content-type, and CONTENT-TYPE are the same header to any conformant server. Casing matters only to your own tooling: string comparisons, diffs, log greps, and code that indexes a header map. HTTP/2 and HTTP/3 go further and require lowercase names on the wire, which is why lowercase is offered as a mode and why pseudo-headers are always emitted lowercase.

Why are duplicate headers joined with a comma by default?

Because that is what the specification says a recipient may do: a field that appears more than once can be combined into one field whose value is the values joined with a comma, in the order they arrived. That makes the block canonical and diff-friendly. The exception is Set-Cookie, which must never be combined, so it always stays one line per cookie. If your parser treats repeats as first-wins or last-wins, pick that mode explicitly, or choose the list mode to keep every occurrence on its own line.

What is an indented continuation line?

Old HTTP allowed a long field value to be wrapped onto extra lines that begin with a space or a tab — line folding. It is deprecated, but it still shows up in captures, mail-style dumps, and hand-written fixtures. With Join indented continuation lines on, the wrapped text is appended to the header above it with a single space, which is how a modern parser reads it. Turn it off to keep the fold as an indented second line. A continuation with no header in front of it is an error either way, since there is nothing to attach it to.

Can I use this to redact an Authorization header before sharing a capture?

Yes, and that is a common use. Put the sensitive names in Drop these names — for example authorization,cookie,set-cookie,x-api-key — or use a prefix rule such as x-internal-*. The matching is case-insensitive, so you do not have to guess how the header was spelled. Use Keep only these names for the opposite approach: list the few fields worth sharing and everything else disappears. Note that the values are removed, not masked, so nothing sensitive remains in the output.

Is anything sent to a server?

No. The page runs the same Rust compiled to WebAssembly directly in your browser, and the command-line version runs locally. Pasted headers are not uploaded, logged, or used to make a request — the tool only rewrites the text you give it. That matters here, because header blocks routinely contain cookies and bearer tokens.

Developer & Automation Access

Run it from the terminal

Same engine as this page, headless — via the gizza CLI:

gizza tool http-header-normalizer "GET /v1/items?page=2 HTTP/1.1
host:   api.example.com
ACCEPT: application/json
accept: text/plain
x-request-id:   9f3c
content-type:application/json"

New to the CLI? Get gizza →

Open it by URL

Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:

https://gizza.ai/tools/http-header-normalizer/?input=GET%20%2Fv1%2Fitems%3Fpage%3D2%20HTTP%2F1.1%0Ahost%3A%20%20%20api.example.com%0AACCEPT%3A%20application%2Fjson%0Aaccept%3A%20text%2Fplain%0Ax-request-id%3A%20%20%209f3c%0Acontent-type%3Aapplication%2Fjson&case=canonical&sort=name&duplicates=combine&unfold=true&drop_empty=true&drop_headers=authorization%2Ccookie%2Cx-internal-token&keep_headers=host%2Ccontent-type&output=headers

Machine-readable descriptor: tool.json — title + parameters JSON Schema for agents.