# HTTPie Admin & Developer Guide

This guide is the deep reference for administrators, platform engineers, and developers working with or extending HTTPie — the command-line HTTP client (`http`, `https`, and the `httpie` management interface). It covers the codebase architecture, the request/response pipeline, the plugin API, configuration internals, a security analysis, and contribution guidance.

It is the companion to the **HTTPie User Guide** (which covers day-to-day command usage). Everything here is derived from the HTTPie source tree at version `3.2.4` (commit `5b604c3`). HTTPie is licensed **BSD-3-Clause** and is implemented in Python, requiring **Python 3.7 or newer**.

---

## Table of contents

- [Executive summary](#executive-summary)
- [Architecture overview](#architecture-overview)
- [The request/response pipeline](#the-requestresponse-pipeline)
- [Repository structure](#repository-structure)
- [Module reference](#module-reference)
- [Component inventory](#component-inventory)
- [The plugin API](#the-plugin-api)
- [Configuration and state on disk](#configuration-and-state-on-disk)
- [Sessions: format and lifecycle](#sessions-format-and-lifecycle)
- [The update-check subsystem](#the-update-check-subsystem)
- [Security analysis](#security-analysis)
- [Requirements and dependencies](#requirements-and-dependencies)
- [Code quality and conventions](#code-quality-and-conventions)
- [Testing strategy](#testing-strategy)
- [Building and packaging](#building-and-packaging)
- [Potential improvements](#potential-improvements)
- [Contributing](#contributing)
- [Quick reference](#quick-reference)

---

## Executive summary

HTTPie is a single-purpose, user-facing CLI: it turns a concise command line into an HTTP request, sends it via the [Requests](https://requests.readthedocs.io/) library, and renders a colorized, formatted response with [Pygments](https://pygments.org/). Its defining design choice is that **the command-line syntax mirrors the HTTP message** — the order and shape of arguments map directly onto the request that goes over the wire.

Architecturally, HTTPie is a thin, well-factored layer over Requests/urllib3:

- A **custom argument layer** (`httpie/cli/`) parses both `--flags` and a small request-item DSL (`Header:value`, `field=value`, `field:=json`, `param==value`, `field@file`, …).
- A **client layer** (`httpie/client.py`) translates parsed arguments into a `requests.Request`, mounts HTTPie's own transport adapters, and yields request/response message objects.
- An **output layer** (`httpie/output/`) selects a stream class and a chain of formatter plugins to decode, prettify, and write each message.
- A **plugin system** (`httpie/plugins/`) exposes four extension points (auth, transport, converter, formatter) loaded from Python entry points.
- A **management binary** (`httpie/manager/`) handles plugin install/list/uninstall and session upgrades, distinct from the request commands.

There is **no server, no daemon (except a short-lived update fetcher), and no persistent process**. State lives in a config directory: `config.json`, session files, and a cached version-info file.

---

## Architecture overview

```
                              ┌─────────────────────────────────────────┐
   argv ($ http / https /     │              ENTRY POINTS                │
   httpie ...)  ───────────►  │  __main__.py → core.raw_main()           │
                              │  (program_name basename → default scheme)│
                              └───────────────┬─────────────────────────┘
                                              │
                    ┌─────────────────────────▼──────────────────────────┐
                    │                 CLI / PARSING LAYER                 │
                    │  cli/definition.py  ParserSpec → to_argparse()      │
                    │  cli/argparser.py   HTTPieArgumentParser            │
                    │  cli/requestitems.py  RequestItems.from_args()      │
                    │  cli/argtypes.py    KeyValueArgType, auth, SSL      │
                    │  cli/nested_json/   interpret_nested_json()         │
                    └─────────────────────────┬──────────────────────────┘
                                              │ argparse.Namespace
                    ┌─────────────────────────▼──────────────────────────┐
                    │                   CLIENT LAYER                      │
                    │  client.collect_messages()                          │
                    │   ├─ get_httpie_session()    (sessions.py)          │
                    │   ├─ build_requests_session()                       │
                    │   │     mount HTTPieHTTPAdapter / HTTPieHTTPSAdapter │
                    │   │     + TransportPlugin adapters                  │
                    │   ├─ make_request_kwargs / make_send_kwargs         │
                    │   └─ yields PreparedRequest / Response objects      │
                    └─────────────────────────┬──────────────────────────┘
                                              │ RequestsMessage stream
                    ┌─────────────────────────▼──────────────────────────┐
                    │                   OUTPUT LAYER                      │
                    │  output/writer.py    write_message()                │
                    │  output/streams.py   Raw/Encoded/Pretty/Buffered    │
                    │  output/processing.py  Formatting, Conversion       │
                    │  output/formatters/  Headers/JSON/XML/Colors        │
                    └─────────────────────────┬──────────────────────────┘
                                              │
                                       stdout / stderr / file
                              (Downloader for --download; Rich for progress)

   PLUGINS (entry points)         CONFIG / STATE (~/.config/httpie)
   ├─ httpie.plugins.auth.v1      ├─ config.json
   ├─ httpie.plugins.transport.v1 ├─ sessions/<host>/<name>.json
   ├─ httpie.plugins.converter.v1 ├─ version_info.json
   └─ httpie.plugins.formatter.v1 └─ plugins/  (isolated install dir)
```

### Two request binaries, one codebase

`http` and `https` are the same program. The only difference is the basename of `argv[0]`: `core.raw_main()` sets `env.program_name = os.path.basename(program_name)`, and `argparser._process_url()` chooses the default scheme — when invoked as `https`, the scheme defaults to `https://`; otherwise it uses `--default-scheme` (default `http`). The `httpie` binary is a separate entry point (`httpie/manager/__main__.py`) with its own parser.

---

## The request/response pipeline

The full path from command line to printed response, with the responsible functions:

1. **Entry** — `httpie/__main__.py::main()` calls `core.main()`, which calls `raw_main(parser, program, args, env)`.
2. **Pre-flight** — `raw_main()` decodes raw byte args, detects daemon mode, loads installed plugins (`plugin_manager.load_installed_plugins`), prepends `config.default_options`, and handles `--debug`/`--traceback`.
3. **Parse** — `HTTPieArgumentParser.parse_args()` runs a sequence of private processors: `_process_request_type`, `_process_download_options`, `_process_output_options`, `_process_pretty_options`, `_process_format_options`, `_guess_method`, `_parse_items`, `_process_url`, `_process_auth`, `_process_ssl_cert`.
4. **Request items** — `RequestItems.from_args()` dispatches each item by its separator to a processor function, populating `headers`, `data`, `params`, `files`, and `multipart_data`. JSON nesting is resolved by `cli/nested_json/interpret_nested_json()`.
5. **Method inference** — `_guess_method()` defaults to `POST` if there is input data, else `GET`.
6. **Build & send** — `client.collect_messages()` builds a `requests.Session`, mounts the HTTPie adapters and any transport plugins, prepares the request, applies `--path-as-is`/`--compress`, and yields each `PreparedRequest` and `Response`. Redirects are followed manually (`response.next`) governed by `--follow`/`--max-redirects`.
7. **Output** — `core.program()` iterates the yielded messages and calls `output/writer.write_message()`, which picks a stream class (`get_stream_type_and_kwargs`) and writes it to `env.stdout`.
8. **Download** — when `--download` is set, `Downloader` (in `downloads.py`) handles filename resolution, resume (`Range`/`206`), and progress display.
9. **Exit** — `core` maps outcomes to `ExitStatus` codes and HTTPie exits.

### Output stream selection

`output/writer.py::get_stream_type_and_kwargs()` chooses one of four stream classes based on whether output is a TTY, whether prettification is enabled, and whether streaming is active:

| Stream class | Used when |
|--------------|-----------|
| `RawStream` | Output is redirected and not prettified — bytes pass through untouched. |
| `EncodedStream` | TTY output without formatting — re-encodes to the terminal encoding, suppresses binary. |
| `PrettyStream` | Prettified **and** streaming — formats line by line, flushed immediately. |
| `BufferedPrettyStream` | Prettified, non-streaming — buffers the whole body, then formats once. |

Auto-streaming kicks in for `Content-Type: text/event-stream`.

---

## Repository structure

```
httpie/
├── __init__.py            Package metadata (__version__ = '3.2.4')
├── __main__.py            `http` / `https` entry point
├── core.py                raw_main(), main(), program() — orchestration
├── client.py              Request building, Requests session, redirects
├── models.py              HTTPMessage / HTTPRequest / HTTPResponse, OutputOptions
├── sessions.py            Persistent JSON sessions
├── config.py              Config + BaseConfigDict, config dir resolution
├── context.py             Environment (streams, config, Rich consoles)
├── downloads.py           Download mode (Downloader, DownloadStatus)
├── uploads.py             Streaming uploads, chunked & compressed bodies
├── ssl_.py                HTTPieHTTPSAdapter, TLS version / cipher handling
├── adapters.py            HTTPieHTTPAdapter (multi-value header preservation)
├── cookies.py             HTTPieCookiePolicy (localhost-as-secure)
├── encoding.py            Charset detection / smart decode/encode
├── compat.py              Python version shims, importlib_metadata helpers
├── status.py              ExitStatus IntEnum, http_status_to_exit_status()
├── utils.py               JSON dup-key dict, cookie parsing, lockfiles
├── cli/                   Argument parsing & request-item DSL
│   ├── definition.py      The `http`/`https` parser spec (every flag)
│   ├── argparser.py       HTTPieArgumentParser + processing pipeline
│   ├── argtypes.py        KeyValueArgType, AuthCredentials, SSLCredentials
│   ├── constants.py       Separator constants, output option chars, enums
│   ├── requestitems.py    RequestItems — separator → processor dispatch
│   ├── options.py         ParserSpec / Group / Argument abstraction
│   ├── dicts.py           HTTPHeadersDict (multidict), multi-value dicts
│   ├── exceptions.py      ParseError
│   ├── utils.py           Manual / LazyChoices argparse actions
│   └── nested_json/       Nested JSON path parser (tokens, parse, interpret)
├── output/                Response rendering
│   ├── writer.py          write_message(), stream selection
│   ├── streams.py         Raw/Encoded/Pretty/BufferedPretty streams
│   ├── processing.py      Formatting + Conversion delegates
│   ├── models.py          ProcessingOptions
│   ├── formatters/        colors, headers, json, xml formatter plugins
│   ├── lexers/            Pygments lexers (http, json, metadata)
│   └── ui/                Rich help, palette, progress, man pages
├── plugins/               Plugin system
│   ├── base.py            BasePlugin + Auth/Transport/Converter/Formatter
│   ├── builtin.py         Basic / Digest / Bearer auth
│   ├── manager.py         PluginManager (entry-point loading)
│   └── registry.py        Global plugin_manager + built-in registration
├── manager/               The `httpie` management binary
│   ├── __main__.py        Entry point + http/https confusion detection
│   ├── cli.py             COMMANDS tree (cli, plugins, sessions)
│   ├── core.py            Dispatch to tasks
│   ├── compat.py          pip discovery / subprocess (run_pip)
│   └── tasks/             check_updates, export_args, plugins, sessions
├── internal/              Update-check daemon
│   ├── update_warnings.py Version fetch + warning logic
│   ├── daemons.py         Detached process spawning (double-fork / Windows)
│   └── daemon_runner.py   Daemon task runner
└── legacy/                Session-format back-compat shims (v3.1.0, v3.2.0)
```

---

## Module reference

### Core orchestration — `core.py`

`raw_main()` is the universal entry: it is shared by `http`/`https` (with the `http` parser) and `httpie` (with the manager parser, `use_default_options=False`). It centralizes error handling, mapping exceptions to exit codes:

| Exception | Exit status |
|-----------|-------------|
| `requests.Timeout` | `ERROR_TIMEOUT` (2) |
| `requests.TooManyRedirects` | `ERROR_TOO_MANY_REDIRECTS` (6) |
| `requests.ConnectionError` (DNS) | `ERROR` (1), with a DNS-specific hint |
| `NestedJSONSyntaxError` | `ERROR` (1) |
| `KeyboardInterrupt` | `ERROR_CTRL_C` (130) |

`program()` drives the actual request/response loop, message separation, and the download finalization.

### Client — `client.py`

`collect_messages()` is the generator at the heart of request execution. Key responsibilities:

- Loads/updates the HTTPie session when `--session`/`--session-read-only` is set.
- Builds default headers (`make_default_headers`): `User-Agent: HTTPie/<version>`, plus JSON or form `Content-Type`/`Accept` based on data and `--form`.
- Manually preserves repeated headers across Requests' preparation (`apply_missing_repeated_headers`) — Requests collapses duplicate headers, so HTTPie re-applies them.
- Wraps `http.client._MAXHEADERS` via the `max_headers()` context manager for `--max-headers`.
- Streams the request body through `uploads.prepare_request_body()` for chunked/compressed/file uploads.

### Models — `models.py`

`HTTPMessage`/`HTTPRequest`/`HTTPResponse` wrap Requests' objects to expose a uniform `headers`/`metadata`/`iter_body`/`iter_lines` interface for the output layer. `OutputOptions` is a `NamedTuple` describing which parts (headers/body/meta) of a given message to print, derived from the `--print` string.

### Headers dict — `cli/dicts.py`

`HTTPHeadersDict` (a case-insensitive `multidict.CIMultiDict`) is central to HTTPie's correct handling of multi-valued headers — a behavior plain Requests does not preserve. Setting a header value to `None` unsets it; `add()` supports multiple values per name.

---

## Component inventory

| Component | Class / location | Role |
|-----------|------------------|------|
| Argument parser | `HTTPieArgumentParser` (`cli/argparser.py`) | Parse args + request items; multi-stage processing. |
| Manager parser | `HTTPieManagerArgumentParser` | Parser for the `httpie` binary. |
| Parser spec | `ParserSpec`/`Group`/`Argument` (`cli/options.py`) | Declarative parser definition, serializable to JSON (`export-args`). |
| Request items | `RequestItems` (`cli/requestitems.py`) | Separator-based item classification. |
| HTTP adapters | `HTTPieHTTPAdapter`, `HTTPieHTTPSAdapter` | Header preservation; custom TLS context. |
| Session | `Session` (`sessions.py`) | Persistent headers/auth/cookies per host. |
| Config | `Config` (`config.py`) | `config.json` loader + computed paths. |
| Environment | `Environment` (`context.py`) | Streams, config, Rich consoles, TTY detection. |
| Downloader | `Downloader`, `DownloadStatus` (`downloads.py`) | wget-style download with resume. |
| Plugin manager | `PluginManager` (`plugins/manager.py`) | Entry-point discovery and registration. |
| Output streams | `BaseStream` and subclasses (`output/streams.py`) | Encoding/formatting/binary suppression. |
| Exit status | `ExitStatus` (`status.py`) | Process exit-code constants. |

---

## The plugin API

HTTPie's extensibility is built on Python **entry points**. `PluginManager.iter_entry_points()` scans four groups and registers each discovered class:

| Entry-point group | Base class | Extends |
|-------------------|-----------|---------|
| `httpie.plugins.auth.v1` | `AuthPlugin` | New authentication mechanisms. |
| `httpie.plugins.transport.v1` | `TransportPlugin` | Requests transport adapters mounted on a URL prefix. |
| `httpie.plugins.converter.v1` | `ConverterPlugin` | Convert binary bodies to text for display. |
| `httpie.plugins.formatter.v1` | `FormatterPlugin` | Post-process headers/body/metadata for display. |

> [!WARNING]
> The plugin API carries an explicit in-source disclaimer: *"The plugin API is still work in progress and will probably be completely reworked in the future."* Pin HTTPie versions when depending on plugin internals.

### Writing an auth plugin

```python
from httpie.plugins import AuthPlugin
import requests

class MyTokenAuth(requests.auth.AuthBase):
    def __init__(self, token):
        self.token = token
    def __call__(self, request):
        request.headers['X-Token'] = self.token
        return request

class MyAuthPlugin(AuthPlugin):
    name = 'My token auth'
    auth_type = 'my-token'      # value passed to --auth-type / -A
    description = 'Sends an X-Token header'
    # Tunable class attributes:
    auth_require = True         # require --auth, -a
    auth_parse = False          # don't split -a as username:password
    netrc_parse = False         # don't fall back to ~/.netrc
    prompt_password = False     # don't prompt when password is missing

    def get_auth(self, username=None, password=None):
        return MyTokenAuth(self.raw_auth)
```

Register it in your package's `setup.cfg`/`pyproject.toml`:

```ini
[options.entry_points]
httpie.plugins.auth.v1 =
    my_token = my_package:MyAuthPlugin
```

### The other three extension points

| Base class | Override | Purpose |
|-----------|----------|---------|
| `TransportPlugin` | `prefix`, `get_adapter()` | Mount a Requests adapter for a custom URL scheme (e.g. `http+unix://`). |
| `ConverterPlugin` | `supports(mime)`, `convert(body)` | Turn a binary MIME type into displayable text. |
| `FormatterPlugin` | `format_headers/body/metadata` | Transform output; `group_name = 'format'`. |

### Built-in plugins

Registered in `plugins/registry.py`:

- **Auth:** `BasicAuthPlugin` (`basic`), `DigestAuthPlugin` (`digest`), `BearerAuthPlugin` (`bearer`).
- **Formatters:** `HeadersFormatter`, `JSONFormatter`, `XMLFormatter`, `ColorFormatter`.

### Plugin installation internals

`httpie cli plugins install` does **not** use a normal `pip install` into HTTPie's environment. Instead, `manager/compat.py::run_pip()` invokes pip (the bundled interpreter's `-m pip`, or a discovered system `pip` when running as a frozen binary) to install into an **isolated `plugins_dir`** (default: `<config_dir>/plugins`). At runtime, `plugin_manager.load_installed_plugins()` temporarily adds that directory to `sys.path` (`enable_plugins`) before scanning entry points. This is why plugins installed this way must also be uninstalled this way — see the warning emitted on load failure.

---

## Configuration and state on disk

### Config directory resolution

`config.py::get_default_config_dir()` resolves the directory in this order:

1. `$HTTPIE_CONFIG_DIR` if set.
2. On Windows: `%APPDATA%\httpie`.
3. Legacy `~/.httpie` if it already exists.
4. `$XDG_CONFIG_HOME/httpie` (defaulting to `~/.config/httpie`).

### `config.json`

A simple JSON file (created on demand). Recognized keys:

| Key | Default | Meaning |
|-----|---------|---------|
| `default_options` | `[]` | Options prepended to every `http`/`https` invocation. |
| `plugins_dir` | `<config_dir>/plugins` | Isolated plugin install location. |
| `version_info_file` | `<config_dir>/version_info.json` | Cache for the update checker. |
| `developer_mode` | `false` | Surfaces normally suppressed errors (for HTTPie development). |
| `disable_update_warnings` | `false` | Disables the new-release check entirely. |

`default_options` is prepended only for the request commands (`use_default_options=True`); the `httpie` manager binary ignores it.

### On-disk layout

```
$HTTPIE_CONFIG_DIR/        (default ~/.config/httpie)
├── config.json
├── version_info.json      Cached latest-version data (update checker)
├── plugins/               Isolated plugin install dir
└── sessions/
    └── <host>/
        └── <name>.json     Named session files
```

---

## Sessions: format and lifecycle

A session (`sessions.py::Session`, a `BaseConfigDict` subclass) is a JSON document persisting three things between requests to the same host:

- **headers** — except names prefixed with `Content-` or `If-` (`SESSION_IGNORED_HEADER_PREFIXES`), which are request-specific.
- **auth** — `{type, raw_auth}` (or legacy `{type, username, password}`).
- **cookies** — with `name/value/domain/path/expires/secure` fields.

### Session resolution

`get_httpie_session()` decides between named and anonymous sessions: if the session name contains a path separator it is treated as an **anonymous** file path; otherwise it is a **named** session stored at `sessions/<host>/<name>.json` under the config directory. Session names are validated against `^[a-zA-Z0-9_.-]+$`.

### Cookie domain binding

Cookies carry a `domain` field that binds them to a host and its subdomains. A `null` domain makes a cookie **unbound** (sent to any host, including across cross-domain redirects). The legacy upgrade path and the `--bind-cookies` flag exist to remediate previously unbound cookies — see [Security analysis](#security-analysis).

### Format upgrades

When HTTPie detects an obsolete session format it warns and offers upgrade commands:

```bash
httpie cli sessions upgrade-all
httpie cli sessions upgrade <host> <session-name-or-path> [--bind-cookies]
```

The `legacy/` package (`v3_1_0_session_cookie_format`, `v3_2_0_session_header_format`) implements the format migrations applied during `pre_process_data`/`post_process_data`.

---

## The update-check subsystem

HTTPie periodically checks whether a newer release is available (`internal/update_warnings.py`). Operators should understand its behavior:

- On each request, `check_updates()` runs (unless `disable_update_warnings` is set).
- At most every **2 weeks** (`FETCH_INTERVAL`), it spawns a **detached background daemon** (`internal/daemons.py`, via double-fork on POSIX or a hidden process on Windows) that fetches `https://packages.httpie.io/latest.json`.
- Results are cached in `version_info.json` (written under a lockfile to avoid races between concurrent `http` processes).
- A warning is shown at most once per week (`WARN_INTERVAL`), pointing to the install method recorded in `BUILD_CHANNEL`.

> [!CAUTION]
> The update fetch uses `requests.get(PACKAGE_INDEX_LINK, verify=False)` — TLS certificate verification is **disabled** for the version-check request. In hardened or air-gapped environments, set `disable_update_warnings: true` in `config.json` to stop the network call entirely.

---

## Security analysis

HTTPie is a client tool that runs with the invoking user's privileges. The threat surface is mostly about credential handling, TLS, and untrusted servers.

### Credential storage

> [!CAUTION]
> **Sessions store secrets in plain text.** Authentication credentials, prompted passwords, cookies, and custom headers are all written to session JSON files in cleartext. Anyone with read access to the file can recover them. Treat session files like private keys: restrict filesystem permissions and avoid committing them.

- Basic-auth credentials are base64-encoded (not encrypted) into the `Authorization` header — standard HTTP basic auth.
- `.netrc` credentials are honored by default; disable per-request with `--ignore-netrc`.

### TLS / SSL

- `--verify=no` disables certificate verification (`cert_reqs=CERT_NONE`), enabling man-in-the-middle attacks. Use a CA bundle (`--verify=/path` or `REQUESTS_CA_BUNDLE`) instead where possible.
- `urllib3` insecure-request warnings are globally disabled (`urllib3.disable_warnings()` in `client.py`) — so HTTPie won't nag about `--verify=no`, which means the operator must self-enforce verification.
- TLS version (`--ssl`) and ciphers (`--ciphers`) are configurable; defaults negotiate the highest mutually supported protocol.
- Client certificates with encrypted keys prompt for a passphrase (or accept `--cert-key-pass`).
- The **update check disables verification** (see above).

### Cookie scoping

- `HTTPieCookiePolicy` (`cookies.py`) treats `localhost` and `*.localhost` as secure hosts, matching modern browser behavior — secure cookies are returned to localhost over plain HTTP.
- Unbound session cookies (`domain: null`) are sent across hosts and redirect chains. This was the subject of security advisory **GHSA-9w4w-cpc8-h2fq**; the `--bind-cookies` upgrade flag binds such cookies to their session host as mitigation.

### Untrusted input handling

- Binary response bodies are suppressed in the terminal (`BinarySuppressedError`) to prevent terminal-escape injection from hostile servers.
- XML formatting uses defused parsing to avoid XML entity-expansion attacks (the `output/formatters/xml.py` formatter; the test suite includes XML-bomb fixtures under `tests/fixtures/xmldata/invalid/`).
- File uploads and large bodies are streamed, bounding memory use against large inputs.

### Operational hardening checklist

- [ ] Set restrictive permissions on the config directory and session files (`chmod 700 ~/.config/httpie`).
- [ ] Prefer `--verify=<ca-bundle>` over `--verify=no`; never disable verification against untrusted networks.
- [ ] Set `disable_update_warnings: true` in locked-down/air-gapped environments.
- [ ] Review and pin community plugins — they execute arbitrary code and are not vetted by the HTTPie project.
- [ ] Run `httpie cli sessions upgrade-all --bind-cookies` to remediate legacy unbound cookies.
- [ ] In scripts, always use `--ignore-stdin` and `--timeout`.

---

## Requirements and dependencies

| Requirement | Detail |
|-------------|--------|
| Python | 3.7 or newer (`MIN_SUPPORTED_PY_VERSION = (3, 7)`). |
| Platforms | Linux, macOS, Windows, FreeBSD. |
| License | BSD-3-Clause. |

### Runtime dependency stack

HTTPie is a thin layer over a small set of mature libraries, evident from its imports:

| Library | Used for |
|---------|----------|
| **requests** | The HTTP transport (sessions, adapters, auth base classes). |
| **Pygments** | Syntax highlighting of headers and bodies. |
| **charset-normalizer** | Response encoding detection (`encoding.py`). |
| **requests-toolbelt** | Streaming multipart encoding (`MultipartEncoder`). |
| **multidict** | Case-insensitive, multi-valued header dict (`HTTPHeadersDict`). |
| **rich** | Help rendering, progress bars, and colorized error consoles. |
| **colorama** | ANSI color translation on Windows. |

> [!NOTE]
> On Windows, output streams are wrapped with `colorama` for ANSI handling. On non-Windows systems, `curses` (when available) is used to detect terminal color support.

---

## Code quality and conventions

- **Layered, single-responsibility modules.** Parsing, client, output, and plugins are cleanly separated. The CLI definition (`cli/definition.py`) is declarative and serializable, which is what powers `httpie cli export-args`.
- **Declarative parser spec.** Rather than calling argparse directly, HTTPie builds an abstract `ParserSpec` of groups and arguments, then renders it to argparse (`to_argparse`) — and can also serialize it to JSON for external tooling.
- **Type hints** throughout, with `typing.NamedTuple` for immutable value objects (`OutputOptions`, `ProcessingOptions`, `Argument`).
- **Compatibility shims** isolated in `compat.py` (cached_property backport, `importlib_metadata` differences across 3.7–3.10).
- **Custom exceptions** map cleanly to user-facing errors: `ParseError`, `ConfigFileError`, `ContentRangeError`, `NestedJSONSyntaxError`, `PipError`.
- **Known maintainability notes** acknowledged in-source: the parsing pipeline (`HTTPieArgumentParser.parse_args`) is a long ordered sequence of `_process_*` methods with `TODO` markers calling for refactor; `core.program()` is flagged for simplification of its separator logic.

---

## Testing strategy

The `tests/` directory holds a broad suite (40+ test modules) covering each feature area, runnable with `pytest`:

| Test area | Modules |
|-----------|---------|
| CLI & parsing | `test_cli.py`, `test_cli_ui.py`, `test_cli_utils.py`, `test_parser_schema.py`, `test_tokens.py` |
| Requests/responses | `test_httpie.py`, `test_redirects.py`, `test_stream.py`, `test_output.py` |
| Auth | `test_auth.py`, `test_auth_plugins.py` |
| Data & encoding | `test_json.py`, `test_xml.py`, `test_encoding.py`, `test_binary.py`, `test_uploads.py`, `test_compress.py` |
| Sessions & cookies | `test_sessions.py`, `test_cookie.py`, `test_cookie_on_redirects.py` |
| SSL | `test_ssl.py` |
| Management | `test_httpie_cli.py`, `test_plugins_cli.py`, `test_transport_plugin.py` |
| Behavior | `test_exit_status.py`, `test_errors.py`, `test_defaults.py`, `test_offline.py`, `test_meta.py`, `test_downloads.py`, `test_regressions.py`, `test_update_warnings.py`, `test_windows.py` |

Test infrastructure (`tests/utils/`) provides `MockEnvironment`, an embedded `http_server`, a plugins-CLI fixture harness, and a response-matching DSL (`tests/utils/matching/`).

---

## Building and packaging

- **PyPI** is the canonical distribution (`python -m pip install httpie`); `setup.py` delegates to `setup.cfg`.
- **OS packages** are maintained for APT, DNF/Yum, pacman, Homebrew, MacPorts, Chocolatey, Snap, and FreshPorts (see `docs/installation/methods.yml` — the single source of truth for the documented install commands).
- **Standalone Linux binaries** are built (PyInstaller-style) and published at `packages.httpie.io/binaries/`. Frozen builds set `is_frozen`, which changes plugin pip discovery and Python-version site-path handling.
- **Man pages** for `http`, `https`, and `httpie` are generated at release time (`extras/scripts/generate_man_pages.py`); the `HTTPIE_BUILDING_MAN_PAGES` env var suppresses system-specific content (like the default cipher list) so man pages are reproducible.
- **`__build_channel__.py`** records the packaging method per build; the update checker uses it to point users at the right upgrade instructions.

---

## Potential improvements

Drawn from in-source `TODO`/`FIXME` markers and observed structure:

1. **Refactor the argument-processing pipeline.** `HTTPieArgumentParser.parse_args()` runs a long, order-sensitive chain of `_process_*` methods with several `TODO`s requesting type-annotated, immutable arg structures.
2. **Simplify `core.program()`.** An in-source `TODO` calls out the message-separator logic as needing extraction.
3. **Stabilize the plugin API.** The package docstring warns it "will probably be completely reworked" — versioned entry-point groups (`.v1`) anticipate this, but a documented stability contract would help the plugin ecosystem.
4. **Reconsider `--chunked` opt-in for stdin.** A `TODO` in `uploads.py` notes that chunked transfer for stdin could become opt-out (a breaking change deferred to a future major version).
5. **Verify the update check over TLS.** The `verify=False` fetch in `update_warnings.py` is a hardening gap worth closing.
6. **Resolve open community issues**, e.g. JSON `Content-Type` not being set when a single header is present ([#1640](https://github.com/httpie/cli/issues/1640) / [#1834](https://github.com/httpie/cli/issues/1834)) and argument-parsing differences on Python 3.13+ ([#1838](https://github.com/httpie/cli/issues/1838)).

---

## Contributing

- **Repository:** [github.com/httpie/cli](https://github.com/httpie/cli) — see `CONTRIBUTING.md`.
- **Docs are source-controlled:** the user manual lives in `docs/README.md`; the installation section is **generated** from `docs/installation/methods.yml` via `docs/installation/generate.py` (don't edit the generated block by hand).
- **Run the tests:** `pytest` from the repo root.
- **Security policy:** [github.com/httpie/cli/security/policy](https://github.com/httpie/cli/security/policy).
- **License:** BSD-3-Clause. Created by Jakub Roztocil and contributors.

> [!TIP]
> Because the parser spec is serializable (`httpie cli export-args`), external tools (shells, editors) can build autocomplete from HTTPie's own definition rather than re-implementing the flag list. If you add a flag in `cli/definition.py`, it automatically appears in the exported schema.

---

## Quick reference

### Entry points

| Binary | Module | Purpose |
|--------|--------|---------|
| `http` | `httpie/__main__.py` | Send requests (default scheme `http://`). |
| `https` | same | Send requests (default scheme `https://`). |
| `httpie` | `httpie/manager/__main__.py` | Manage plugins, config, sessions. |

### Plugin entry-point groups

| Group | Base class |
|-------|-----------|
| `httpie.plugins.auth.v1` | `AuthPlugin` |
| `httpie.plugins.transport.v1` | `TransportPlugin` |
| `httpie.plugins.converter.v1` | `ConverterPlugin` |
| `httpie.plugins.formatter.v1` | `FormatterPlugin` |

### Management commands

| Command | Purpose |
|---------|---------|
| `httpie cli check-updates` | Check for a newer release. |
| `httpie cli export-args [-f json]` | Export the parser spec. |
| `httpie cli plugins install/list/upgrade/uninstall` | Manage plugins. |
| `httpie cli sessions upgrade <host> <session> [--bind-cookies]` | Upgrade one session. |
| `httpie cli sessions upgrade-all [--bind-cookies]` | Upgrade all named sessions. |

### Exit status codes

| Code | Meaning | Code | Meaning |
|------|---------|------|---------|
| `0` | Success | `5` | HTTP 5xx |
| `1` | Error | `6` | Too many redirects |
| `2` | Timeout | `7` | Plugin error |
| `3` | HTTP 3xx | `130` | Ctrl-C |
| `4` | HTTP 4xx | | |

### Key files on disk

| Path | Purpose |
|------|---------|
| `$HTTPIE_CONFIG_DIR/config.json` | User configuration. |
| `.../sessions/<host>/<name>.json` | Named sessions. |
| `.../version_info.json` | Update-check cache. |
| `.../plugins/` | Isolated plugin installs. |

---

_Generated June 2026 from httpie/cli at 5b604c3. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
