CodeVetter · Repo Unpack · Behavior traces
httpie/cli
Three flows carry most of the behaviour. Startup walks __main__ -> core.raw_main -> plugin loading -> parser.parse_args -> check_updates -> core.program. The primary request flow walks program -> collect_messages -> requests.Session.send -> OutputOptions.from_message -> write_message -> stream class -> stdout buffer, message by message, as a generator so headers can print before the body arrives. Persistence walks get_httpie_session -> Session.load -> pre_process_data (with legacy fixups) -> update_headers -> save -> post_process_data. A fourth notable flow is the update daemon, which double-forks out of the main process to fetch a version index without blocking the user.
- Startup, step by step: `__main__.main()` imports `core.main`, which imports `httpie.cli.definition.parser` and calls `raw_main(parser, program, argv, Environment())`; `raw_main` sets `env.program_name`, calls `decode_raw_args`, checks `is_daemon_mode`, calls `plugin_manager.load_installed_plugins`, prepends default options, then `parser.parse_args(args, env)`.
- `--debug` is handled before parsing: `print_debug_info` writes HTTPie/Requests/Pygments/Python/platform versions plus `repr(env)` and `repr(plugin_manager)` to stderr, and a bare `http --debug` returns SUCCESS without ever building a request.
- After a successful parse, `check_updates(env)` runs before `main_program`; the `_update_checker` decorator wraps it so that every exception is swallowed by `suppress(BaseException)` unless `developer_mode` is set in config.json.
- Primary request trace: `program()` builds `ProcessingOptions.from_raw_args`, optionally constructs a `Downloader` and calls `downloader.pre_request(args.headers)`, then iterates `collect_messages(env, args, request_body_read_callback)`, calling `OutputOptions.from_message` and `write_message` per message.
- Inside `collect_messages` the order is: resolve session -> `make_request_kwargs` -> `make_send_kwargs` -> `build_requests_session` -> apply session headers/cookies/auth -> `requests.Request(**kwargs)` -> `session.prepare_request` -> `transform_headers` -> `ensure_path_as_is` (if `--path-as-is`) -> `compress_request` (if `--compress`) -> yield/send loop.
- `max_headers` is a context manager that temporarily monkey-patches `http.client._MAXHEADERS` around the single `session.send()` call so `--max-headers` can exceed the stdlib's default cap, restoring it in a finally block.
- Write trace: `write_message` -> `build_output_stream_for_message` -> `get_stream_type_and_kwargs` picks RawStream (non-tty, no prettify), EncodedStream (tty, no prettify), PrettyStream (`--stream` or `text/event-stream` auto-detect) or BufferedPrettyStream, then `write_stream` pushes each chunk to `outfile.buffer`, flushing when tty or streaming.
- `BaseStream.__iter__` defines the on-wire ordering for one message: headers, then `\r\n\r\n`, then body chunks (each fed to `on_body_chunk_downloaded`), then -- if `--meta` -- a blank line and the metadata block.
- Message separation is stateful: `program()` tracks `prev_with_body` and `force_separator`, emitting `MESSAGE_SEPARATOR_BYTES` between messages when the previous one had a body and either output is redirected or a streamed upload forced it.
- Download trace: after the response loop, `downloader.start(initial_url, final_response)` builds a `RawStream` over the response with `chunk_downloaded` as the progress callback, `write_stream` drains it to the output file, `downloader.finish()` stops the display, and a size mismatch sets `downloader.interrupted` and an error exit status.
- Session persistence trace: `get_httpie_session` resolves the bound hostname (falling back to `localhost` for hostname-less URLs), builds the path, and calls `Session.load()`, which runs `read_raw_config` then `pre_process_data` -- importing cookies into a `RequestsCookieJar` with `HTTPieCookiePolicy` and headers into an `HTTPHeadersDict`.
- On the way out, the session is saved only when it is new or `--session-read-only` was not used: cookies are replaced with the requests session jar, expired cookies removed, then `save()` writes JSON and stamps `__meta__.httpie` with the current version.
- Update-daemon trace: `maybe_fetch_updates` checks the 2-week FETCH_INTERVAL, calls `spawn_daemon('fetch_updates')`, which on POSIX double-forks, calls `setsid()`, closes stdio, and re-enters `httpie.core.main(['http', 'fetch_updates', '--daemon'])` -- except on Darwin, where it spawns a subprocess instead because double-fork is unreliable there.
- Manager trace with a confusing invocation: `manager.__main__.main` calls `raw_main(use_default_options=False)`; if argparse raises, `is_http_command()` silently re-parses the args with the http parser inside `env.as_silent()` and, on success, prints the 'use http/https instead' hint before returning ERROR.
- Session upgrade trace: `httpie cli sessions upgrade <host> <name>` loads the session with warnings suppressed, selects fixers whose version is greater than the stored `__meta__.httpie` (3.1.0 cookie-format, 3.2.0 header-format), applies them in order, and calls `save(bump_version=True)`.