Compare commits

...

34 Commits
2.3.0 ... 2.4.0

Author SHA1 Message Date
bb36897054 2.4.0 2021-02-06 11:17:24 +01:00
173e622567 Update upload command 2021-02-06 11:17:14 +01:00
3426030370 Fix README formatting 2021-02-06 11:13:04 +01:00
d014498713 Changelog entry for cookie expiration based on Set-Cookie: max-age=<n>
#1029
2021-02-06 11:02:26 +01:00
5414d1853e Refactoring
#1029
2021-02-06 10:58:36 +01:00
1ac8f69651 Add more output matching tests 2021-02-06 10:52:30 +01:00
3c07a25326 Add support for max-age=0 cookie expiry (#1029)
Close #998
2021-02-06 10:50:34 +01:00
cf78a12e46 Show --check-status warning with --quiet as well. (#1026)
Fixes #1028
2021-01-31 00:58:56 +01:00
0f1e098cc4 Fix incorrect separators and introduce assert_output_matches() (close #1027) 2021-01-30 22:14:57 +01:00
0401d7b31c fixed typo (#1024) 2021-01-21 22:35:41 +01:00
795627f965 Update chat icon 2021-01-13 22:24:20 +01:00
21cc008cb2 Add Linux Solus install to README (#1018) 2021-01-13 21:52:00 +01:00
77b8c37cb0 Update changelog
#1020
2021-01-13 21:49:53 +01:00
db685d58b5 Decode headers using utf-8 only if they are not str (#1020) 2021-01-13 21:45:56 +01:00
44ae67685d Add HTTPie Discord link to README (#1016)
* add discord link

* update link
2021-01-12 15:27:45 +01:00
6922a0c912 Switch from httpbin.org to pie.dev 2020-12-24 21:34:30 +01:00
2afdc958c6 Update URLs 2020-12-23 22:07:27 +01:00
57b1baf1d1 Add a test to reproduce #1006 2020-12-22 22:56:45 +01:00
1828da6a50 Update --stream example comment 2020-12-21 12:17:04 +01:00
0629f2ff42 Fix --stream example II 2020-12-21 12:14:41 +01:00
d71b7eee81 Fix --stream example
Close #1002
2020-12-21 12:12:11 +01:00
9883a46575 Cleanup (#993) 2020-12-21 12:03:25 +01:00
2409077a6d Clarify 2020-12-21 11:51:19 +01:00
02971b938d Fix documentation on file upload (#1000)
As documented later on in "File upload forms", the correct syntax to set
the mimetype of the upload is `field@file;type=filetype`
2020-12-21 11:47:47 +01:00
f7e77efe4b Test on Python 3.9 (#986) 2020-12-21 11:42:21 +01:00
5d8bd0da7c python -m pip (#1005) 2020-12-21 11:38:00 +01:00
3c6e7c73fe Update setup.py 2020-12-19 14:07:31 +01:00
d64c0ee415 Remove funding request 2020-12-18 17:54:13 +01:00
311a5ede70 Simplify Hello World 2020-10-29 10:07:45 +01:00
f64c90010f Simplify Hello World 2020-10-29 10:06:26 +01:00
8456ddb27c Update brew instructions for dev 2020-10-25 21:48:09 +01:00
cf254680b7 Update homebrew 2020-10-25 21:43:20 +01:00
42c4a7596b 2.4.0-dev 2020-10-25 21:36:24 +01:00
1573058811 v2.3.0 2020-10-25 21:12:38 +01:00
37 changed files with 929 additions and 375 deletions

12
.github/FUNDING.yml vendored
View File

@ -1,12 +0,0 @@
# These are supported funding model platforms
github: jakubroztocil # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom:

View File

@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
with: with:
python-version: 3.8 python-version: 3.9
- run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install --upgrade pip setuptools wheel
- run: make install - run: make install
- run: make pycodestyle - run: make pycodestyle
@ -23,10 +23,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest, windows-latest]
python-version: [3.6, 3.7, 3.8] python-version: [3.6, 3.7, 3.8, 3.9]
exclude:
- os: windows-latest
python-version: 3.8
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1

View File

@ -8,7 +8,7 @@ HTTPie authors
Patches and ideas Patches and ideas
----------------- -----------------
`Complete list of contributors on GitHub <https://github.com/jakubroztocil/httpie/graphs/contributors>`_ `Complete list of contributors on GitHub <https://github.com/httpie/httpie/graphs/contributors>`_
* `Cláudia T. Delgado <https://github.com/claudiatd>`_ (logo) * `Cláudia T. Delgado <https://github.com/claudiatd>`_ (logo)
* `Hank Gay <https://github.com/gthank>`_ * `Hank Gay <https://github.com/gthank>`_
@ -38,5 +38,6 @@ Patches and ideas
* `Edward Yang <https://github.com/honorabrutroll>`_ * `Edward Yang <https://github.com/honorabrutroll>`_
* `Aleksandr Vinokurov <https://github.com/aleksandr-vin>`_ * `Aleksandr Vinokurov <https://github.com/aleksandr-vin>`_
* `Jeff Byrnes <https://github.com/jeffbyrnes>`_ * `Jeff Byrnes <https://github.com/jeffbyrnes>`_
* `Denis Belavin <https://github.com/LuckyDenis>`_

View File

@ -6,13 +6,24 @@ This document records all notable changes to `HTTPie <https://httpie.org>`_.
This project adheres to `Semantic Versioning <https://semver.org/>`_. This project adheres to `Semantic Versioning <https://semver.org/>`_.
`2.3.0-dev`_ (unreleased)
`2.4.0`_ (2021-02-06)
---------------------
* Added support for ``--session`` cookie expiration based on ``Set-Cookie: max-age=<n>``. (`#1029`_)
* Show a ``--check-status`` warning with ``--quiet`` as well, not only when the output si redirected. (`#1026`_)
* Fixed upload with ``--session`` (`#1020`_).
* Fixed a missing blank line between request and response (`#1006`_).
`2.3.0`_ (2020-10-25)
------------------------- -------------------------
* Added support for streamed uploads (`#201`_).
* Added support for multipart upload streaming (`#684`_). * Added support for multipart upload streaming (`#684`_).
* Added support for body-from-file upload streaming (``http httpbin.org/post @file``). * Added support for body-from-file upload streaming (``http pie.dev/post @file``).
* Added ``--chunked`` to allow chunked transfer encoding. * Added ``--chunked`` to enable chunked transfer encoding (`#753`_).
* Added ``--multipart`` to allow ``multipart/form-data`` encoding for non-file ``--form`` requests as well. * Added ``--multipart`` to allow ``multipart/form-data`` encoding for non-file ``--form`` requests as well.
* Added support for preserving field order in multipart requests (`#903`_).
* Added ``--boundary`` to allow a custom boundary string for ``multipart/form-data`` requests. * Added ``--boundary`` to allow a custom boundary string for ``multipart/form-data`` requests.
* Added support for combining cookies specified on the CLI and in a session file (`#932`_). * Added support for combining cookies specified on the CLI and in a session file (`#932`_).
* Added out of the box SOCKS support with no extra installation (`#904`_). * Added out of the box SOCKS support with no extra installation (`#904`_).
@ -419,57 +430,65 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
* Initial public release * Initial public release
.. _`0.1.0`: https://github.com/jakubroztocil/httpie/commit/b966efa .. _`0.1.0`: https://github.com/httpie/httpie/commit/b966efa
.. _0.1.4: https://github.com/jakubroztocil/httpie/compare/b966efa...0.1.4 .. _0.1.4: https://github.com/httpie/httpie/compare/b966efa...0.1.4
.. _0.1.5: https://github.com/jakubroztocil/httpie/compare/0.1.4...0.1.5 .. _0.1.5: https://github.com/httpie/httpie/compare/0.1.4...0.1.5
.. _0.1.6: https://github.com/jakubroztocil/httpie/compare/0.1.5...0.1.6 .. _0.1.6: https://github.com/httpie/httpie/compare/0.1.5...0.1.6
.. _0.2.0: https://github.com/jakubroztocil/httpie/compare/0.1.6...0.2.0 .. _0.2.0: https://github.com/httpie/httpie/compare/0.1.6...0.2.0
.. _0.2.1: https://github.com/jakubroztocil/httpie/compare/0.2.0...0.2.1 .. _0.2.1: https://github.com/httpie/httpie/compare/0.2.0...0.2.1
.. _0.2.2: https://github.com/jakubroztocil/httpie/compare/0.2.1...0.2.2 .. _0.2.2: https://github.com/httpie/httpie/compare/0.2.1...0.2.2
.. _0.2.5: https://github.com/jakubroztocil/httpie/compare/0.2.2...0.2.5 .. _0.2.5: https://github.com/httpie/httpie/compare/0.2.2...0.2.5
.. _0.2.6: https://github.com/jakubroztocil/httpie/compare/0.2.5...0.2.6 .. _0.2.6: https://github.com/httpie/httpie/compare/0.2.5...0.2.6
.. _0.2.7: https://github.com/jakubroztocil/httpie/compare/0.2.5...0.2.7 .. _0.2.7: https://github.com/httpie/httpie/compare/0.2.5...0.2.7
.. _0.3.0: https://github.com/jakubroztocil/httpie/compare/0.2.7...0.3.0 .. _0.3.0: https://github.com/httpie/httpie/compare/0.2.7...0.3.0
.. _0.4.0: https://github.com/jakubroztocil/httpie/compare/0.3.0...0.4.0 .. _0.4.0: https://github.com/httpie/httpie/compare/0.3.0...0.4.0
.. _0.4.1: https://github.com/jakubroztocil/httpie/compare/0.4.0...0.4.1 .. _0.4.1: https://github.com/httpie/httpie/compare/0.4.0...0.4.1
.. _0.5.0: https://github.com/jakubroztocil/httpie/compare/0.4.1...0.5.0 .. _0.5.0: https://github.com/httpie/httpie/compare/0.4.1...0.5.0
.. _0.5.1: https://github.com/jakubroztocil/httpie/compare/0.5.0...0.5.1 .. _0.5.1: https://github.com/httpie/httpie/compare/0.5.0...0.5.1
.. _0.6.0: https://github.com/jakubroztocil/httpie/compare/0.5.1...0.6.0 .. _0.6.0: https://github.com/httpie/httpie/compare/0.5.1...0.6.0
.. _0.7.1: https://github.com/jakubroztocil/httpie/compare/0.6.0...0.7.1 .. _0.7.1: https://github.com/httpie/httpie/compare/0.6.0...0.7.1
.. _0.8.0: https://github.com/jakubroztocil/httpie/compare/0.7.1...0.8.0 .. _0.8.0: https://github.com/httpie/httpie/compare/0.7.1...0.8.0
.. _0.9.0: https://github.com/jakubroztocil/httpie/compare/0.8.0...0.9.0 .. _0.9.0: https://github.com/httpie/httpie/compare/0.8.0...0.9.0
.. _0.9.1: https://github.com/jakubroztocil/httpie/compare/0.9.0...0.9.1 .. _0.9.1: https://github.com/httpie/httpie/compare/0.9.0...0.9.1
.. _0.9.2: https://github.com/jakubroztocil/httpie/compare/0.9.1...0.9.2 .. _0.9.2: https://github.com/httpie/httpie/compare/0.9.1...0.9.2
.. _0.9.3: https://github.com/jakubroztocil/httpie/compare/0.9.2...0.9.3 .. _0.9.3: https://github.com/httpie/httpie/compare/0.9.2...0.9.3
.. _0.9.4: https://github.com/jakubroztocil/httpie/compare/0.9.3...0.9.4 .. _0.9.4: https://github.com/httpie/httpie/compare/0.9.3...0.9.4
.. _0.9.6: https://github.com/jakubroztocil/httpie/compare/0.9.4...0.9.6 .. _0.9.6: https://github.com/httpie/httpie/compare/0.9.4...0.9.6
.. _0.9.8: https://github.com/jakubroztocil/httpie/compare/0.9.6...0.9.8 .. _0.9.8: https://github.com/httpie/httpie/compare/0.9.6...0.9.8
.. _0.9.9: https://github.com/jakubroztocil/httpie/compare/0.9.8...0.9.9 .. _0.9.9: https://github.com/httpie/httpie/compare/0.9.8...0.9.9
.. _1.0.0: https://github.com/jakubroztocil/httpie/compare/0.9.9...1.0.0 .. _1.0.0: https://github.com/httpie/httpie/compare/0.9.9...1.0.0
.. _1.0.1: https://github.com/jakubroztocil/httpie/compare/1.0.0...1.0.1 .. _1.0.1: https://github.com/httpie/httpie/compare/1.0.0...1.0.1
.. _1.0.2: https://github.com/jakubroztocil/httpie/compare/1.0.1...1.0.2 .. _1.0.2: https://github.com/httpie/httpie/compare/1.0.1...1.0.2
.. _1.0.3: https://github.com/jakubroztocil/httpie/compare/1.0.2...1.0.3 .. _1.0.3: https://github.com/httpie/httpie/compare/1.0.2...1.0.3
.. _2.0.0: https://github.com/jakubroztocil/httpie/compare/1.0.3...2.0.0 .. _2.0.0: https://github.com/httpie/httpie/compare/1.0.3...2.0.0
.. _2.1.0: https://github.com/jakubroztocil/httpie/compare/2.0.0...2.1.0 .. _2.1.0: https://github.com/httpie/httpie/compare/2.0.0...2.1.0
.. _2.2.0: https://github.com/jakubroztocil/httpie/compare/2.1.0...2.2.0 .. _2.2.0: https://github.com/httpie/httpie/compare/2.1.0...2.2.0
.. _2.3.0-dev: https://github.com/jakubroztocil/httpie/compare/2.2.0...master .. _2.3.0: https://github.com/httpie/httpie/compare/2.2.0...2.3.0
.. _2.4.0: https://github.com/httpie/httpie/compare/2.3.0...2.4.0
.. _2.5.0-dev: https://github.com/httpie/httpie/compare/2.4.0...master
.. _#128: https://github.com/httpie/httpie/issues/128
.. _#128: https://github.com/jakubroztocil/httpie/issues/128 .. _#201: https://github.com/httpie/httpie/issues/201
.. _#488: https://github.com/jakubroztocil/httpie/issues/488 .. _#488: https://github.com/httpie/httpie/issues/488
.. _#668: https://github.com/jakubroztocil/httpie/issues/668 .. _#668: https://github.com/httpie/httpie/issues/668
.. _#684: https://github.com/jakubroztocil/httpie/issues/684 .. _#684: https://github.com/httpie/httpie/issues/684
.. _#718: https://github.com/jakubroztocil/httpie/issues/718 .. _#718: https://github.com/httpie/httpie/issues/718
.. _#719: https://github.com/jakubroztocil/httpie/issues/719 .. _#719: https://github.com/httpie/httpie/issues/719
.. _#840: https://github.com/jakubroztocil/httpie/issues/840 .. _#753: https://github.com/httpie/httpie/issues/753
.. _#853: https://github.com/jakubroztocil/httpie/issues/853 .. _#840: https://github.com/httpie/httpie/issues/840
.. _#852: https://github.com/jakubroztocil/httpie/issues/852 .. _#853: https://github.com/httpie/httpie/issues/853
.. _#870: https://github.com/jakubroztocil/httpie/issues/870 .. _#852: https://github.com/httpie/httpie/issues/852
.. _#895: https://github.com/jakubroztocil/httpie/issues/895 .. _#870: https://github.com/httpie/httpie/issues/870
.. _#920: https://github.com/jakubroztocil/httpie/issues/920 .. _#895: https://github.com/httpie/httpie/issues/895
.. _#904: https://github.com/jakubroztocil/httpie/issues/904 .. _#903: https://github.com/httpie/httpie/issues/903
.. _#925: https://github.com/jakubroztocil/httpie/issues/925 .. _#920: https://github.com/httpie/httpie/issues/920
.. _#932: https://github.com/jakubroztocil/httpie/issues/932 .. _#904: https://github.com/httpie/httpie/issues/904
.. _#934: https://github.com/jakubroztocil/httpie/issues/934 .. _#925: https://github.com/httpie/httpie/issues/925
.. _#943: https://github.com/jakubroztocil/httpie/issues/943 .. _#932: https://github.com/httpie/httpie/issues/932
.. _#963: https://github.com/jakubroztocil/httpie/issues/963 .. _#934: https://github.com/httpie/httpie/issues/934
.. _#943: https://github.com/httpie/httpie/issues/943
.. _#963: https://github.com/httpie/httpie/issues/963
.. _#1006: https://github.com/httpie/httpie/issues/1006
.. _#1020: https://github.com/httpie/httpie/issues/1020
.. _#1026: https://github.com/httpie/httpie/issues/1026
.. _#1029: https://github.com/httpie/httpie/issues/1029

View File

@ -53,7 +53,7 @@ Development Environment
Getting the code Getting the code
**************** ****************
Go to https://github.com/jakubroztocil/httpie and fork the project repository. Go to https://github.com/httpie/httpie and fork the project repository.
.. code-block:: bash .. code-block:: bash
@ -132,7 +132,7 @@ Testing & CI
Please add tests for any new features and bug fixes. Please add tests for any new features and bug fixes.
When you open a pull request, When you open a pull request,
`GitHub Actions <https://github.com/jakubroztocil/httpie/actions>`_ `GitHub Actions <https://github.com/httpie/httpie/actions>`_
will automatically run HTTPies `test suite`_ against your code will automatically run HTTPies `test suite`_ against your code
so please make sure all checks pass. so please make sure all checks pass.
@ -230,10 +230,10 @@ Use ``pytest`` to run tests locally with an active virtual environment:
Finally, feel free to add yourself to `AUTHORS`_! Finally, feel free to add yourself to `AUTHORS`_!
.. _existing issues: https://github.com/jakubroztocil/httpie/issues?state=open .. _existing issues: https://github.com/httpie/httpie/issues?state=open
.. _AUTHORS: https://github.com/jakubroztocil/httpie/blob/master/AUTHORS.rst .. _AUTHORS: https://github.com/httpie/httpie/blob/master/AUTHORS.rst
.. _Makefile: https://github.com/jakubroztocil/httpie/blob/master/Makefile .. _Makefile: https://github.com/httpie/httpie/blob/master/Makefile
.. _venv: https://docs.python.org/3/library/venv.html .. _venv: https://docs.python.org/3/library/venv.html
.. _pytest: https://pytest.org/ .. _pytest: https://pytest.org/
.. _Style Guide for Python Code: https://python.org/dev/peps/pep-0008/ .. _Style Guide for Python Code: https://python.org/dev/peps/pep-0008/
.. _test suite: https://github.com/jakubroztocil/httpie/tree/master/tests .. _test suite: https://github.com/httpie/httpie/tree/master/tests

View File

@ -3,5 +3,5 @@ include README.rst
include CHANGELOG.rst include CHANGELOG.rst
include AUTHORS.rst include AUTHORS.rst
# <https://github.com/jakubroztocil/httpie/issues/182> # <https://github.com/httpie/httpie/issues/182>
recursive-include tests/ * recursive-include tests/ *

View File

@ -2,6 +2,8 @@
# See ./CONTRIBUTING.rst # See ./CONTRIBUTING.rst
############################################################################### ###############################################################################
.PHONY: build
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
VERSION=$(shell grep __version__ httpie/__init__.py) VERSION=$(shell grep __version__ httpie/__init__.py)
REQUIREMENTS=requirements-dev.txt REQUIREMENTS=requirements-dev.txt
@ -111,6 +113,9 @@ test-bdist-wheel: clean venv
@echo @echo
twine-check:
twine check dist/*
pycodestyle: pycodestyle:
@echo $(H1)Running pycodestyle$(H1END) @echo $(H1)Running pycodestyle$(H1END)
@[ -f $(VENV_BIN)/pycodestyle ] || $(VENV_PIP) install pycodestyle @[ -f $(VENV_BIN)/pycodestyle ] || $(VENV_PIP) install pycodestyle
@ -131,6 +136,11 @@ codecov-upload:
############################################################################### ###############################################################################
build:
rm -rf build/
$(VENV_PYTHON) setup.py sdist bdist_wheel
publish: test-all publish-no-test publish: test-all publish-no-test
@ -138,8 +148,9 @@ publish-no-test:
@echo $(H1)Testing wheel build an installation$(H1END) @echo $(H1)Testing wheel build an installation$(H1END)
@echo "$(VERSION)" @echo "$(VERSION)"
@echo "$(VERSION)" | grep -q "dev" && echo '!!!Not publishing dev version!!!' && exit 1 || echo ok @echo "$(VERSION)" | grep -q "dev" && echo '!!!Not publishing dev version!!!' && exit 1 || echo ok
$(VENV_PYTHON) setup.py sdist bdist_wheel make build
$(VENV_BIN)/twine upload dist/* make twine-check
$(VENV_BIN)/twine upload --repository=httpie dist/*
@echo @echo

View File

@ -30,9 +30,11 @@ They use simple and natural syntax and provide formatted and colorized output.
About this document About this document
=================== ===================
This documentation is best viewed at `httpie.org/docs <https://httpie.org/docs>`_, This documentation is best viewed at `httpie.org/docs <https://httpie.org/docs>`_.
where you can select your corresponding HTTPie version as well as run examples directly from the
You can select your corresponding HTTPie version as well as run examples directly from the
browser using a `termible.io <https://termible.io?utm_source=httpie-readme>`_ embedded terminal. browser using a `termible.io <https://termible.io?utm_source=httpie-readme>`_ embedded terminal.
If you are reading this on GitHub, then this text covers the current *development* version. If you are reading this on GitHub, then this text covers the current *development* version.
You are invited to submit fixes and improvements to the the docs by editing You are invited to submit fixes and improvements to the the docs by editing
`README.rst <https://github.com/httpie/httpie/blob/master/README.rst>`_. `README.rst <https://github.com/httpie/httpie/blob/master/README.rst>`_.
@ -117,6 +119,11 @@ system package manager, for example:
# Arch Linux # Arch Linux
$ pacman -S httpie $ pacman -S httpie
.. code-block:: bash
# Solus
$ eopkg install httpie
Windows, etc. Windows, etc.
------------- -------------
@ -128,9 +135,9 @@ and always provides the latest version) is to use `pip`_:
.. code-block:: bash .. code-block:: bash
# Make sure we have an up-to-date version of pip and setuptools: # Make sure we have an up-to-date version of pip and setuptools:
$ pip install --upgrade pip setuptools $ python -m pip install --upgrade pip setuptools
$ pip install --upgrade httpie $ python -m pip install --upgrade httpie
(If ``pip`` installation fails for some reason, you can try (If ``pip`` installation fails for some reason, you can try
@ -160,7 +167,8 @@ On macOS you can install it with Homebrew:
.. code-block:: bash .. code-block:: bash
$ brew install httpie --HEAD $ brew uninstall --force httpie
$ brew install --HEAD httpie
Otherwise with ``pip``: Otherwise with ``pip``:
@ -189,7 +197,7 @@ Hello World:
.. code-block:: bash .. code-block:: bash
$ http https://httpie.org/hello $ https httpie.io/hello
Synopsis: Synopsis:
@ -209,28 +217,28 @@ Custom `HTTP method`_, `HTTP headers`_ and `JSON`_ data:
.. code-block:: bash .. code-block:: bash
$ http PUT httpbin.org/put X-API-Token:123 name=John $ http PUT pie.dev/put X-API-Token:123 name=John
Submitting `forms`_: Submitting `forms`_:
.. code-block:: bash .. code-block:: bash
$ http -f POST httpbin.org/post hello=World $ http -f POST pie.dev/post hello=World
See the request that is being sent using one of the `output options`_: See the request that is being sent using one of the `output options`_:
.. code-block:: bash .. code-block:: bash
$ http -v httpbin.org/get $ http -v pie.dev/get
Build and print a request without sending it using `offline mode`_: Build and print a request without sending it using `offline mode`_:
.. code-block:: bash .. code-block:: bash
$ http --offline httpbin.org/post hello=offline $ http --offline pie.dev/post hello=offline
Use `GitHub API`_ to post a comment on an Use `GitHub API`_ to post a comment on an
@ -246,21 +254,21 @@ Upload a file using `redirected input`_:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/post < files/data.json $ http pie.dev/post < files/data.json
Download a file and save it via `redirected output`_: Download a file and save it via `redirected output`_:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/image/png > image.png $ http pie.dev/image/png > image.png
Download a file ``wget`` style: Download a file ``wget`` style:
.. code-block:: bash .. code-block:: bash
$ http --download httpbin.org/image/png $ http --download pie.dev/image/png
Use named `sessions`_ to make certain aspects of the communication persistent Use named `sessions`_ to make certain aspects of the communication persistent
between requests to the same host: between requests to the same host:
@ -268,12 +276,12 @@ between requests to the same host:
.. code-block:: bash .. code-block:: bash
$ http --session=logged-in -a username:password httpbin.org/get API-Key:123 $ http --session=logged-in -a username:password pie.dev/get API-Key:123
.. code-block:: bash .. code-block:: bash
$ http --session=logged-in httpbin.org/headers $ http --session=logged-in pie.dev/headers
Set a custom ``Host`` header to work around missing DNS records: Set a custom ``Host`` header to work around missing DNS records:
@ -292,7 +300,7 @@ The name of the HTTP method comes right before the URL argument:
.. code-block:: bash .. code-block:: bash
$ http DELETE httpbin.org/delete $ http DELETE pie.dev/delete
Which looks similar to the actual ``Request-Line`` that is sent: Which looks similar to the actual ``Request-Line`` that is sent:
@ -461,34 +469,34 @@ their type is distinguished only by the separator used:
``:``, ``=``, ``:=``, ``==``, ``@``, ``=@``, and ``:=@``. The ones with an ``:``, ``=``, ``:=``, ``==``, ``@``, ``=@``, and ``:=@``. The ones with an
``@`` expect a file path as value. ``@`` expect a file path as value.
+-----------------------+-----------------------------------------------------+ +------------------------------+---------------------------------------------------+
| Item Type | Description | | Item Type | Description |
+=======================+=====================================================+ +==============================+===================================================+
| HTTP Headers | Arbitrary HTTP header, e.g. ``X-API-Token:123``. | | HTTP Headers | Arbitrary HTTP header, e.g. ``X-API-Token:123``. |
| ``Name:Value`` | | | ``Name:Value`` | |
+-----------------------+-----------------------------------------------------+ +------------------------------+---------------------------------------------------+
| URL parameters | Appends the given name/value pair as a query | | URL parameters | Appends the given name/value pair as a query |
| ``name==value`` | string parameter to the URL. | | ``name==value`` | string parameter to the URL. |
| | The ``==`` separator is used. | | | The ``==`` separator is used. |
+-----------------------+-----------------------------------------------------+ +------------------------------+---------------------------------------------------+
| Data Fields | Request data fields to be serialized as a JSON | | Data Fields | Request data fields to be serialized as a JSON |
| ``field=value``, | object (default), to be form-encoded | | ``field=value``, | object (default), to be form-encoded |
| ``field=@file.txt`` | (with ``--form, -f``), or to be serialized as | | ``field=@file.txt`` | (with ``--form, -f``), or to be serialized as |
| | ``multipart/form-data`` (with ``--multipart``). | | | ``multipart/form-data`` (with ``--multipart``). |
+-----------------------+-----------------------------------------------------+ +------------------------------+---------------------------------------------------+
| Raw JSON fields | Useful when sending JSON and one or | | Raw JSON fields | Useful when sending JSON and one or |
| ``field:=json``, | more fields need to be a ``Boolean``, ``Number``, | | ``field:=json``, | more fields need to be a ``Boolean``, ``Number``, |
| ``field:=@file.json`` | nested ``Object``, or an ``Array``, e.g., | | ``field:=@file.json`` | nested ``Object``, or an ``Array``, e.g., |
| | ``meals:='["ham","spam"]'`` or ``pies:=[1,2,3]`` | | | ``meals:='["ham","spam"]'`` or ``pies:=[1,2,3]`` |
| | (note the quotes). | | | (note the quotes). |
+-----------------------+-----------------------------------------------------+ +------------------------------+---------------------------------------------------+
| Fields upload fields | Only available with ``--form, -f`` and | | Fields upload fields | Only available with ``--form, -f`` and |
| ``field@/dir/file`` | ``--multipart``. | | ``field@/dir/file`` | ``--multipart``. |
| ``field@file;type`` | For example ``screenshot@~/Pictures/img.png``, or | | ``field@file;type=mime`` | For example ``screenshot@~/Pictures/img.png``, or |
| | ``'cv@cv.txt;text/markdown'``. | | | ``'cv@cv.txt;type=text/markdown'``. |
| | With ``--form``, the presence of a file field | | | With ``--form``, the presence of a file field |
| | results in a ``--multipart`` request. | | | results in a ``--multipart`` request. |
+-----------------------+-----------------------------------------------------+ +------------------------------+---------------------------------------------------+
Note that data fields arent the only way to specify request data: Note that data fields arent the only way to specify request data:
@ -510,7 +518,7 @@ token ``--`` to prevent confusion with ``--arguments``:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/post -- -name-starting-with-dash=foo -Unusual-Header:bar $ http pie.dev/post -- -name-starting-with-dash=foo -Unusual-Header:bar
.. code-block:: http .. code-block:: http
@ -535,14 +543,15 @@ Simple example:
.. code-block:: bash .. code-block:: bash
$ http PUT httpbin.org/put name=John email=john@example.org $ http PUT pie.dev/put name=John email=john@example.org
.. code-block:: http .. code-block:: http
PUT / HTTP/1.1 PUT / HTTP/1.1
Accept: application/json, */*;q=0.5 Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate Accept-Encoding: gzip, deflate
Content-Type: application/json Content-Type: application/json
Host: httpbin.org Host: pie.dev
{ {
"name": "John", "name": "John",
@ -585,7 +594,7 @@ fields using ``=@`` and ``:=@``:
.. code-block:: bash .. code-block:: bash
$ http PUT httpbin.org/put \ $ http PUT pie.dev/put \
name=John \ # String (default) name=John \ # String (default)
age:=29 \ # Raw JSON — Number age:=29 \ # Raw JSON — Number
married:=false \ # Raw JSON — Boolean married:=false \ # Raw JSON — Boolean
@ -600,7 +609,7 @@ fields using ``=@`` and ``:=@``:
PUT /person/1 HTTP/1.1 PUT /person/1 HTTP/1.1
Accept: application/json, */*;q=0.5 Accept: application/json, */*;q=0.5
Content-Type: application/json Content-Type: application/json
Host: httpbin.org Host: pie.dev
{ {
"age": 29, "age": 29,
@ -630,11 +639,11 @@ In such cases, its better to pass the full raw JSON data via
.. code-block:: bash .. code-block:: bash
$ echo '{"hello": "world"}' | http POST httpbin.org/post $ echo '{"hello": "world"}' | http POST pie.dev/post
.. code-block:: bash .. code-block:: bash
$ http POST httpbin.org/post < files/data.json $ http POST pie.dev/post < files/data.json
Furthermore, this syntax only allows you to send an object as the JSON document, but not an array, etc. Furthermore, this syntax only allows you to send an object as the JSON document, but not an array, etc.
Here, again, the solution is to use `redirected input`_. Here, again, the solution is to use `redirected input`_.
@ -655,7 +664,7 @@ Regular forms
.. code-block:: bash .. code-block:: bash
$ http --form POST httpbin.org/post name='John Smith' $ http --form POST pie.dev/post name='John Smith'
.. code-block:: http .. code-block:: http
@ -674,7 +683,7 @@ If one or more file fields is present, the serialization and content type is
.. code-block:: bash .. code-block:: bash
$ http -f POST httpbin.org/post name='John Smith' cv@~/files/data.xml $ http -f POST pie.dev/post name='John Smith' cv@~/files/data.xml
The request above is the same as if the following HTML form were The request above is the same as if the following HTML form were
@ -695,7 +704,7 @@ override the inferred content type:
.. code-block:: bash .. code-block:: bash
$ http -f POST httpbin.org/post name='John Smith' cv@'~/files/data.bin;type=application/pdf' $ http -f POST pie.dev/post name='John Smith' cv@'~/files/data.bin;type=application/pdf'
To perform a ``multipart/form-data`` request even without any files, use To perform a ``multipart/form-data`` request even without any files, use
``--multipart`` instead of ``--form``: ``--multipart`` instead of ``--form``:
@ -769,7 +778,7 @@ To set custom headers you can use the ``Header:Value`` notation:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/headers User-Agent:Bacon/1.0 'Cookie:valued-visitor=yes;foo=bar' \ $ http pie.dev/headers User-Agent:Bacon/1.0 'Cookie:valued-visitor=yes;foo=bar' \
X-Foo:Bar Referer:https://httpie.org/ X-Foo:Bar Referer:https://httpie.org/
@ -779,7 +788,7 @@ To set custom headers you can use the ``Header:Value`` notation:
Accept: */* Accept: */*
Accept-Encoding: gzip, deflate Accept-Encoding: gzip, deflate
Cookie: valued-visitor=yes;foo=bar Cookie: valued-visitor=yes;foo=bar
Host: httpbin.org Host: pie.dev
Referer: https://httpie.org/ Referer: https://httpie.org/
User-Agent: Bacon/1.0 User-Agent: Bacon/1.0
X-Foo: Bar X-Foo: Bar
@ -800,7 +809,7 @@ There are a couple of default headers that HTTPie sets:
Any of these can be overwritten and some of them unset (see bellow). Any of these can be overwritten and some of them unset (see below).
@ -813,7 +822,7 @@ To unset a previously specified header
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/headers Accept: User-Agent: $ http pie.dev/headers Accept: User-Agent:
To send a header with an empty value, use ``Header;``: To send a header with an empty value, use ``Header;``:
@ -821,7 +830,7 @@ To send a header with an empty value, use ``Header;``:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/headers 'Header;' $ http pie.dev/headers 'Header;'
Limiting response headers Limiting response headers
@ -833,7 +842,7 @@ HTTPie reads before giving up (the default ``0``, i.e., theres no limit).
.. code-block:: bash .. code-block:: bash
$ http --max-headers=100 httpbin.org/get $ http --max-headers=100 pie.dev/get
@ -864,13 +873,13 @@ Generating raw requests that can be sent with any other client:
.. code-block:: bash .. code-block:: bash
# 1. save a raw request to a file: # 1. save a raw request to a file:
$ http --offline POST httpbin.org/post hello=world > request.http $ http --offline POST pie.dev/post hello=world > request.http
.. code-block:: bash .. code-block:: bash
# 2. send it over the wire with, for example, the fantastic netcat tool: # 2. send it over the wire with, for example, the fantastic netcat tool:
$ nc httpbin.org 80 < request.http $ nc pie.dev 80 < request.http
You can also use the ``--offline`` mode for debugging and exploring HTTP and HTTPie, and for “dry runs”. You can also use the ``--offline`` mode for debugging and exploring HTTP and HTTPie, and for “dry runs”.
@ -893,7 +902,7 @@ Send a single cookie:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/cookies Cookie:sessionid=foo $ http pie.dev/cookies Cookie:sessionid=foo
.. code-block:: http .. code-block:: http
@ -902,7 +911,7 @@ Send a single cookie:
Accept-Encoding: gzip, deflate Accept-Encoding: gzip, deflate
Connection: keep-alive Connection: keep-alive
Cookie: sessionid=foo Cookie: sessionid=foo
Host: httpbin.org Host: pie.dev
User-Agent: HTTPie/0.9.9 User-Agent: HTTPie/0.9.9
@ -911,7 +920,7 @@ Send multiple cookies
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/cookies 'Cookie:sessionid=foo;another-cookie=bar' $ http pie.dev/cookies 'Cookie:sessionid=foo;another-cookie=bar'
.. code-block:: http .. code-block:: http
@ -920,7 +929,7 @@ Send multiple cookies
Accept-Encoding: gzip, deflate Accept-Encoding: gzip, deflate
Connection: keep-alive Connection: keep-alive
Cookie: sessionid=foo;another-cookie=bar Cookie: sessionid=foo;another-cookie=bar
Host: httpbin.org Host: pie.dev
User-Agent: HTTPie/0.9.9 User-Agent: HTTPie/0.9.9
@ -957,7 +966,7 @@ Basic auth
.. code-block:: bash .. code-block:: bash
$ http -a username:password httpbin.org/basic-auth/username/password $ http -a username:password pie.dev/basic-auth/username/password
Digest auth Digest auth
@ -966,7 +975,7 @@ Digest auth
.. code-block:: bash .. code-block:: bash
$ http -A digest -a username:password httpbin.org/digest-auth/httpie/username/password $ http -A digest -a username:password pie.dev/digest-auth/httpie/username/password
Password prompt Password prompt
@ -974,7 +983,7 @@ Password prompt
.. code-block:: bash .. code-block:: bash
$ http -a username httpbin.org/basic-auth/username/password $ http -a username pie.dev/basic-auth/username/password
Empty password Empty password
@ -982,7 +991,7 @@ Empty password
.. code-block:: bash .. code-block:: bash
$ http -a username: httpbin.org/headers $ http -a username: pie.dev/headers
``.netrc`` ``.netrc``
@ -996,13 +1005,13 @@ For example:
.. code-block:: bash .. code-block:: bash
$ cat ~/.netrc $ cat ~/.netrc
machine httpbin.org machine pie.dev
login httpie login httpie
password test password test
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/basic-auth/httpie/test $ http pie.dev/basic-auth/httpie/test
HTTP/1.1 200 OK HTTP/1.1 200 OK
[...] [...]
@ -1010,7 +1019,7 @@ This can be disabled with the ``--ignore-netrc`` option:
.. code-block:: bash .. code-block:: bash
$ http --ignore-netrc httpbin.org/basic-auth/httpie/test $ http --ignore-netrc pie.dev/basic-auth/httpie/test
HTTP/1.1 401 UNAUTHORIZED HTTP/1.1 401 UNAUTHORIZED
[...] [...]
@ -1044,7 +1053,7 @@ response is shown:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/redirect/3 $ http pie.dev/redirect/3
Follow ``Location`` Follow ``Location``
@ -1056,7 +1065,7 @@ and show the final response instead, use the ``--follow, -F`` option:
.. code-block:: bash .. code-block:: bash
$ http --follow httpbin.org/redirect/3 $ http --follow pie.dev/redirect/3
Showing intermediary redirect responses Showing intermediary redirect responses
@ -1068,7 +1077,7 @@ then use the ``--all`` option as well:
.. code-block:: bash .. code-block:: bash
$ http --follow --all httpbin.org/redirect/3 $ http --follow --all pie.dev/redirect/3
@ -1081,7 +1090,7 @@ To change the default limit of maximum ``30`` redirects, use the
.. code-block:: bash .. code-block:: bash
$ http --follow --all --max-redirects=2 httpbin.org/redirect/3 $ http --follow --all --max-redirects=2 pie.dev/redirect/3
Proxies Proxies
@ -1141,7 +1150,7 @@ To skip the hosts SSL certificate verification, you can pass ``--verify=no``
.. code-block:: bash .. code-block:: bash
$ http --verify=no https://httpbin.org/get $ http --verify=no https://pie.dev/get
Custom CA bundle Custom CA bundle
@ -1200,7 +1209,7 @@ It should be a string in the
.. code-block:: bash .. code-block:: bash
$ http --ciphers=ECDHE-RSA-AES128-GCM-SHA256 https://httpbin.org/get $ http --ciphers=ECDHE-RSA-AES128-GCM-SHA256 https://pie.dev/get
Note: these cipher strings do not change the negotiated version of SSL or TLS, Note: these cipher strings do not change the negotiated version of SSL or TLS,
they only affect the list of available cipher suites. they only affect the list of available cipher suites.
@ -1247,7 +1256,7 @@ Print request and response headers:
.. code-block:: bash .. code-block:: bash
$ http --print=Hh PUT httpbin.org/put hello=world $ http --print=Hh PUT pie.dev/put hello=world
Verbose output Verbose output
-------------- --------------
@ -1257,12 +1266,12 @@ documentation examples:
.. code-block:: bash .. code-block:: bash
$ http --verbose PUT httpbin.org/put hello=world $ http --verbose PUT pie.dev/put hello=world
PUT /put HTTP/1.1 PUT /put HTTP/1.1
Accept: application/json, */*;q=0.5 Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate Accept-Encoding: gzip, deflate
Content-Type: application/json Content-Type: application/json
Host: httpbin.org Host: pie.dev
User-Agent: HTTPie/0.2.7dev User-Agent: HTTPie/0.2.7dev
{ {
@ -1285,13 +1294,13 @@ Quiet output
------------ ------------
``--quiet`` redirects all output that would otherwise go to ``stdout`` ``--quiet`` redirects all output that would otherwise go to ``stdout``
and ``stderr`` (except for error messages) to ``/dev/null``. and ``stderr`` to ``/dev/null`` (except for errors and warnings).
This doesnt affect output to a file via ``--output`` or ``--download``. This doesnt affect output to a file via ``--output`` or ``--download``.
.. code-block:: bash .. code-block:: bash
# There will be no output: # There will be no output:
$ http --quiet httpbin.org/post enjoy='the silence' $ http --quiet pie.dev/post enjoy='the silence'
Viewing intermediary requests/responses Viewing intermediary requests/responses
@ -1306,7 +1315,7 @@ authentication is used (``--auth=digest``), etc.
.. code-block:: bash .. code-block:: bash
# Include all responses that lead to the final one: # Include all responses that lead to the final one:
$ http --all --follow httpbin.org/redirect/3 $ http --all --follow pie.dev/redirect/3
The intermediary requests/response are by default formatted according to The intermediary requests/response are by default formatted according to
@ -1318,7 +1327,7 @@ arguments as ``--print, -p`` but applies to the intermediary requests only.
.. code-block:: bash .. code-block:: bash
# Print the intermediary requests/responses differently than the final one: # Print the intermediary requests/responses differently than the final one:
$ http -A digest -a foo:bar --all -p Hh -P H httpbin.org/digest-auth/auth/foo/bar $ http -A digest -a foo:bar --all -p Hh -P H pie.dev/digest-auth/auth/foo/bar
Conditional body download Conditional body download
@ -1334,7 +1343,7 @@ status code after an update:
.. code-block:: bash .. code-block:: bash
$ http --headers PATCH httpbin.org/patch name='New Name' $ http --headers PATCH pie.dev/patch name='New Name'
Since we are only printing the HTTP headers here, the connection to the server Since we are only printing the HTTP headers here, the connection to the server
@ -1361,49 +1370,49 @@ Redirect from a file:
.. code-block:: bash .. code-block:: bash
$ http PUT httpbin.org/put X-API-Token:123 < files/data.json $ http PUT pie.dev/put X-API-Token:123 < files/data.json
Or the output of another program: Or the output of another program:
.. code-block:: bash .. code-block:: bash
$ grep '401 Unauthorized' /var/log/httpd/error_log | http POST httpbin.org/post $ grep '401 Unauthorized' /var/log/httpd/error_log | http POST pie.dev/post
You can use ``echo`` for simple data: You can use ``echo`` for simple data:
.. code-block:: bash .. code-block:: bash
$ echo '{"name": "John"}' | http PATCH httpbin.org/patch X-API-Token:123 $ echo '{"name": "John"}' | http PATCH pie.dev/patch X-API-Token:123
You can also use a Bash *here string*: You can also use a Bash *here string*:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/post <<<'{"name": "John"}' $ http pie.dev/post <<<'{"name": "John"}'
You can even pipe web services together using HTTPie: You can even pipe web services together using HTTPie:
.. code-block:: bash .. code-block:: bash
$ http GET https://api.github.com/repos/httpie/httpie | http POST httpbin.org/post $ http GET https://api.github.com/repos/httpie/httpie | http POST pie.dev/post
You can use ``cat`` to enter multiline data on the terminal: You can use ``cat`` to enter multiline data on the terminal:
.. code-block:: bash .. code-block:: bash
$ cat | http POST httpbin.org/post $ cat | http POST pie.dev/post
<paste> <paste>
^D ^D
.. code-block:: bash .. code-block:: bash
$ cat | http POST httpbin.org/post Content-Type:text/plain $ cat | http POST pie.dev/post Content-Type:text/plain
- buy milk - buy milk
- call parents - call parents
^D ^D
@ -1413,7 +1422,7 @@ On OS X, you can send the contents of the clipboard with ``pbpaste``:
.. code-block:: bash .. code-block:: bash
$ pbpaste | http PUT httpbin.org/put $ pbpaste | http PUT pie.dev/put
Passing data through ``stdin`` cannot be combined with data fields specified Passing data through ``stdin`` cannot be combined with data fields specified
@ -1442,7 +1451,7 @@ verbatim contents of that XML file with ``Content-Type: application/xml``:
.. code-block:: bash .. code-block:: bash
$ http PUT httpbin.org/put @files/data.xml $ http PUT pie.dev/put @files/data.xml
File uploads are always streamed to avoid memory issues with large files. File uploads are always streamed to avoid memory issues with large files.
@ -1456,19 +1465,19 @@ You can use the ``--chunked`` flag to instruct HTTPie to use
.. code-block:: bash .. code-block:: bash
$ http --chunked PUT httpbin.org/put hello=world $ http --chunked PUT pie.dev/put hello=world
.. code-block:: bash .. code-block:: bash
$ http --chunked --multipart PUT httpbin.org/put hello=world foo@files/data.xml $ http --chunked --multipart PUT pie.dev/put hello=world foo@files/data.xml
.. code-block:: bash .. code-block:: bash
$ http --chunked httpbin.org/post @files/data.xml $ http --chunked pie.dev/post @files/data.xml
.. code-block:: bash .. code-block:: bash
$ cat files/data.xml | http --chunked httpbin.org/post $ cat files/data.xml | http --chunked pie.dev/post
@ -1522,7 +1531,7 @@ sorting, and specify a custom JSON indent size:
.. code-block:: bash .. code-block:: bash
$ http --format-options headers.sort:false,json.sort_keys:false,json.indent:2 httpbin.org/get $ http --format-options headers.sort:false,json.sort_keys:false,json.indent:2 pie.dev/get
This is something you will typically store as one of the default options in your This is something you will typically store as one of the default options in your
`config`_ file. See ``http --help`` for all the available formatting options. `config`_ file. See ``http --help`` for all the available formatting options.
@ -1542,7 +1551,7 @@ that the response body is binary,
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/bytes/2000 $ http pie.dev/bytes/2000
You will nearly instantly see something like this: You will nearly instantly see something like this:
@ -1575,7 +1584,7 @@ Download a file:
.. code-block:: bash .. code-block:: bash
$ http httpbin.org/image/png > image.png $ http pie.dev/image/png > image.png
Download an image of Octocat, resize it using ImageMagick, upload it elsewhere: Download an image of Octocat, resize it using ImageMagick, upload it elsewhere:
@ -1590,7 +1599,7 @@ Force colorizing and formatting, and show both the request and the response in
.. code-block:: bash .. code-block:: bash
$ http --pretty=all --verbose httpbin.org/get | less -R $ http --pretty=all --verbose pie.dev/get | less -R
The ``-R`` flag tells ``less`` to interpret color escape sequences included The ``-R`` flag tells ``less`` to interpret color escape sequences included
@ -1719,17 +1728,15 @@ Prettified streamed response:
.. code-block:: bash .. code-block:: bash
$ http --stream -f -a YOUR-TWITTER-NAME https://stream.twitter.com/1/statuses/filter.json track='Justin Bieber' $ http --stream pie.dev/stream/3
Streamed output by small chunks à la ``tail -f``: Streamed output by small chunks à la ``tail -f``:
.. code-block:: bash .. code-block:: bash
# Send each new tweet (JSON object) mentioning "Apple" to another # Send each new line (JSON object) to another URL as soon as it arrives from a streaming API:
# server as soon as it arrives from the Twitter streaming API: $ http --stream pie.dev/stream/3 | while read line; do echo "$line" | http pie.dev/post ; done
$ http --stream -f -a YOUR-TWITTER-NAME https://stream.twitter.com/1/statuses/filter.json track=Apple \
| while read tweet; do echo "$tweet" | http POST example.org/tweets ; done
Sessions Sessions
======== ========
@ -1749,7 +1756,7 @@ to the same host.
.. code-block:: bash .. code-block:: bash
# Create a new session: # Create a new session:
$ http --session=./session.json httpbin.org/headers API-Token:123 $ http --session=./session.json pie.dev/headers API-Token:123
.. code-block:: bash .. code-block:: bash
@ -1760,7 +1767,7 @@ to the same host.
.. code-block:: bash .. code-block:: bash
# Re-use the existing session — the API-Token header will be set: # Re-use the existing session — the API-Token header will be set:
$ http --session=./session.json httpbin.org/headers $ http --session=./session.json pie.dev/headers
All session data, including credentials, cookie data, All session data, including credentials, cookie data,
@ -1775,11 +1782,11 @@ Named sessions
You can create one or more named session per host. For example, this is how You can create one or more named session per host. For example, this is how
you can create a new session named ``user1`` for ``httpbin.org``: you can create a new session named ``user1`` for ``pie.dev``:
.. code-block:: bash .. code-block:: bash
$ http --session=user1 -a user1:password httpbin.org/get X-Foo:Bar $ http --session=user1 -a user1:password pie.dev/get X-Foo:Bar
From now on, you can refer to the session by its name (``user1``). When you choose From now on, you can refer to the session by its name (``user1``). When you choose
to use the session again, any previously specified authentication or HTTP headers to use the session again, any previously specified authentication or HTTP headers
@ -1787,13 +1794,13 @@ will automatically be set:
.. code-block:: bash .. code-block:: bash
$ http --session=user1 httpbin.org/get $ http --session=user1 pie.dev/get
To create or reuse a different session, simple specify a different name: To create or reuse a different session, simple specify a different name:
.. code-block:: bash .. code-block:: bash
$ http --session=user2 -a user2:password httpbin.org/get X-Bar:Foo $ http --session=user2 -a user2:password pie.dev/get X-Bar:Foo
Named sessionss data is stored in JSON files inside the ``sessions`` Named sessionss data is stored in JSON files inside the ``sessions``
subdirectory of the `config`_ directory, typically: subdirectory of the `config`_ directory, typically:
@ -1806,7 +1813,7 @@ you should be able list the generated sessions files using:
.. code-block:: bash .. code-block:: bash
$ ls -l ~/.config/httpie/sessions/httpbin.org $ ls -l ~/.config/httpie/sessions/pie.dev
Anonymous sessions Anonymous sessions
@ -1848,12 +1855,12 @@ exchange after it has been created, specify the session name via
.. code-block:: bash .. code-block:: bash
# If the session file doesnt exist, then it is created: # If the session file doesnt exist, then it is created:
$ http --session-read-only=./ro-session.json httpbin.org/headers Custom-Header:orig-value $ http --session-read-only=./ro-session.json pie.dev/headers Custom-Header:orig-value
.. code-block:: bash .. code-block:: bash
# But it is not updated: # But it is not updated:
$ http --session-read-only=./ro-session.json httpbin.org/headers Custom-Header:new-value $ http --session-read-only=./ro-session.json pie.dev/headers Custom-Header:new-value
Cookie Storage Behaviour Cookie Storage Behaviour
------------------------ ------------------------
@ -1866,13 +1873,13 @@ To set a cookie within a Session there are three options:
.. code-block:: bash .. code-block:: bash
$ http --session=./session.json httpbin.org/cookie/set?foo=bar $ http --session=./session.json pie.dev/cookie/set?foo=bar
2. Set the cookie name and value through the command line as seen in `cookies`_ 2. Set the cookie name and value through the command line as seen in `cookies`_
.. code-block:: bash .. code-block:: bash
$ http --session=./session.json httpbin.org/headers Cookie:foo=bar $ http --session=./session.json pie.dev/headers Cookie:foo=bar
3. Manually set cookie parameters in the json file of the session 3. Manually set cookie parameters in the json file of the session
@ -1937,7 +1944,7 @@ environment variable:
.. code-block:: bash .. code-block:: bash
$ export HTTPIE_CONFIG_DIR=/tmp/httpie $ export HTTPIE_CONFIG_DIR=/tmp/httpie
$ http httpbin.org/get $ http pie.dev/get
@ -1998,7 +2005,7 @@ respectively.
#!/bin/bash #!/bin/bash
if http --check-status --ignore-stdin --timeout=2.5 HEAD httpbin.org/get &> /dev/null; then if http --check-status --ignore-stdin --timeout=2.5 HEAD pie.dev/get &> /dev/null; then
echo 'OK!' echo 'OK!'
else else
case $? in case $? in
@ -2047,7 +2054,7 @@ HTTP request:
.. code-block:: http .. code-block:: http
POST /post HTTP/1.1 POST /post HTTP/1.1
Host: httpbin.org Host: pie.dev
X-API-Key: 123 X-API-Key: 123
User-Agent: Bacon/1.0 User-Agent: Bacon/1.0
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
@ -2059,7 +2066,7 @@ with the HTTPie command that sends it:
.. code-block:: bash .. code-block:: bash
$ http -f POST httpbin.org/post \ $ http -f POST pie.dev/post \
X-API-Key:123 \ X-API-Key:123 \
User-Agent:Bacon/1.0 \ User-Agent:Bacon/1.0 \
name=value \ name=value \
@ -2082,15 +2089,15 @@ HTTPie reaches its final version ``1.0``. All changes are recorded in the
User support Community and Support
------------ ---------------------
Please use the following support channels: HTTPie has the following community channels:
* `GitHub issues <https://github.com/jkbr/httpie/issues>`_ * `GitHub issues <https://github.com/jkbr/httpie/issues>`_
for bug reports and feature requests. for bug reports and feature requests.
* `Our Gitter chat room <https://gitter.im/jkbrzt/httpie>`_ * `Discord server <https://httpie.io/chat>`_
to ask questions, discuss features, and for general discussion. to ask questions, discuss features, and for general API development discussion.
* `StackOverflow <https://stackoverflow.com>`_ * `StackOverflow <https://stackoverflow.com>`_
to ask questions (please make sure to use the to ask questions (please make sure to use the
`httpie <https://stackoverflow.com/questions/tagged/httpie>`_ tag). `httpie <https://stackoverflow.com/questions/tagged/httpie>`_ tag).
@ -2195,9 +2202,9 @@ have contributed.
:target: https://github.com/httpie/httpie/actions :target: https://github.com/httpie/httpie/actions
:alt: Build status of the master branch on Mac/Linux/Windows :alt: Build status of the master branch on Mac/Linux/Windows
.. |gitter| image:: https://img.shields.io/gitter/room/jkbrzt/httpie.svg?style=flat-square .. |gitter| image:: https://img.shields.io/badge/chat-on%20Discord-brightgreen?style=flat-square
:target: https://gitter.im/jkbrzt/httpie :target: https://httpie.io/chat
:alt: Chat on Gitter :alt: Chat on Discord
.. |downloads| image:: https://pepy.tech/badge/httpie .. |downloads| image:: https://pepy.tech/badge/httpie
:target: https://pepy.tech/project/httpie :target: https://pepy.tech/project/httpie

View File

@ -9,22 +9,20 @@ class Httpie < Formula
desc "User-friendly cURL replacement (command-line HTTP client)" desc "User-friendly cURL replacement (command-line HTTP client)"
homepage "https://httpie.org/" homepage "https://httpie.org/"
url "https://files.pythonhosted.org/packages/37/6c/0d050f49e3b2bac589367d0c3aee9c078e23c6914b0210ffc0117218bdaf/httpie-2.2.0.tar.gz" url "https://files.pythonhosted.org/packages/b4/d4/712645808103f2d15c281b9eacd184c88754ef7e9a322d9a30ba343fd341/httpie-2.3.0.tar.gz"
sha256 "31ac28088ee6a0b6f3ba7a53379000c4d1910c1708c9ff768f84b111c14405a0" sha256 "d540571991d07329d217c31bf1ff95fd217957da2aa2def09bcfa0c0fca0cf96"
head "https://github.com/jakubroztocil/httpie.git" license "BSD-3-Clause"
head "https://github.com/httpie/httpie.git"
bottle do livecheck do
cellar :any_skip_relocation url :stable
sha256 "25f0e58f81a2cdd9cba772f07d67591533b4b31a2b970a356701aa046d4d9638" => :catalina
sha256 "be158ebb4cfd327ebea02f7b8b8d63d093e474cd303eafff4a2b56b0611983a2" => :mojave
sha256 "f331edb94183bfc5fa9de4b4abf148cc91a3a8b3c0e24cc1f5e6b0a4172dd34d" => :high_sierra
end end
depends_on "python@3.8" depends_on "python@3.9"
resource "Pygments" do resource "Pygments" do
url "https://files.pythonhosted.org/packages/6e/4d/4d2fe93a35dfba417311a4ff627489a947b01dc0cc377a3673c00cf7e4b2/Pygments-2.6.1.tar.gz" url "https://files.pythonhosted.org/packages/5d/0e/ff13c055b014d634ed17e9e9345a312c28ec6a06448ba6d6ccfa77c3b5e8/Pygments-2.7.2.tar.gz"
sha256 "647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44" sha256 "381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"
end end
resource "requests" do resource "requests" do
@ -32,19 +30,24 @@ class Httpie < Formula
sha256 "b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b" sha256 "b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"
end end
resource "requests-toolbelt" do
url "https://files.pythonhosted.org/packages/28/30/7bf7e5071081f761766d46820e52f4b16c8a08fef02d2eb4682ca7534310/requests-toolbelt-0.9.1.tar.gz"
sha256 "968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
end
resource "certifi" do resource "certifi" do
url "https://files.pythonhosted.org/packages/b4/19/53433f37a31543364c8676f30b291d128cdf4cd5b31b755b7890f8e89ac8/certifi-2020.4.5.2.tar.gz" url "https://files.pythonhosted.org/packages/40/a7/ded59fa294b85ca206082306bba75469a38ea1c7d44ea7e1d64f5443d67a/certifi-2020.6.20.tar.gz"
sha256 "5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1" sha256 "5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"
end end
resource "urllib3" do resource "urllib3" do
url "https://files.pythonhosted.org/packages/05/8c/40cd6949373e23081b3ea20d5594ae523e681b6f472e600fbc95ed046a36/urllib3-1.25.9.tar.gz" url "https://files.pythonhosted.org/packages/76/d9/bbbafc76b18da706451fa91bc2ebe21c0daf8868ef3c30b869ac7cb7f01d/urllib3-1.25.11.tar.gz"
sha256 "3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527" sha256 "8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"
end end
resource "idna" do resource "idna" do
url "https://files.pythonhosted.org/packages/cb/19/57503b5de719ee45e83472f339f617b0c01ad75cba44aba1e4c97c2b0abd/idna-2.9.tar.gz" url "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz"
sha256 "7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb" sha256 "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
end end
resource "chardet" do resource "chardet" do

View File

@ -3,6 +3,6 @@ HTTPie: command-line HTTP client for the API era.
""" """
__version__ = '2.3.0-dev' __version__ = '2.4.0'
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__licence__ = 'BSD' __licence__ = 'BSD'

View File

@ -5,15 +5,6 @@ import enum
import re import re
# TODO: Use MultiDict for headers once added to `requests`.
# <https://github.com/jakubroztocil/httpie/issues/130>
# ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
# <https://tools.ietf.org/html/rfc3986#section-3.1>
from enum import Enum
URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE) URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE)
HTTP_POST = 'POST' HTTP_POST = 'POST'

View File

@ -37,7 +37,7 @@ parser = HTTPieArgumentParser(
Suggestions and bug reports are greatly appreciated: Suggestions and bug reports are greatly appreciated:
https://github.com/jakubroztocil/httpie/issues https://github.com/httpie/httpie/issues
'''), '''),
) )

View File

@ -1,5 +1,4 @@
import os import os
from io import BytesIO
from typing import Callable, Dict, IO, List, Optional, Tuple, Union from typing import Callable, Dict, IO, List, Optional, Tuple, Union
from httpie.cli.argtypes import KeyValueArg from httpie.cli.argtypes import KeyValueArg

View File

@ -134,7 +134,7 @@ def collect_messages(
# noinspection PyProtectedMember # noinspection PyProtectedMember
@contextmanager @contextmanager
def max_headers(limit): def max_headers(limit):
# <https://github.com/jakubroztocil/httpie/issues/802> # <https://github.com/httpie/httpie/issues/802>
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
orig = http.client._MAXHEADERS orig = http.client._MAXHEADERS
http.client._MAXHEADERS = limit or float('Inf') http.client._MAXHEADERS = limit or float('Inf')
@ -188,7 +188,7 @@ def finalize_headers(headers: RequestHeadersDict) -> RequestHeadersDict:
# Also, requests raises `InvalidHeader` for leading spaces. # Also, requests raises `InvalidHeader` for leading spaces.
value = value.strip() value = value.strip()
if isinstance(value, str): if isinstance(value, str):
# See <https://github.com/jakubroztocil/httpie/issues/212> # See <https://github.com/httpie/httpie/issues/212>
value = value.encode('utf8') value = value.encode('utf8')
final_headers[name] = value final_headers[name] = value
return final_headers return final_headers
@ -304,7 +304,7 @@ def ensure_path_as_is(orig_url: str, prepped_url: str) -> str:
untouched because other (welcome) processing on the URL might have untouched because other (welcome) processing on the URL might have
taken place. taken place.
<https://github.com/jakubroztocil/httpie/issues/895> <https://github.com/httpie/httpie/issues/895>
<https://ec.haxx.se/http/http-basics#path-as-is> <https://ec.haxx.se/http/http-basics#path-as-is>

View File

@ -9,26 +9,17 @@ from pygments import __version__ as pygments_version
from requests import __version__ as requests_version from requests import __version__ as requests_version
from httpie import __version__ as httpie_version from httpie import __version__ as httpie_version
from httpie.cli.constants import ( from httpie.cli.constants import OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY, OUT_RESP_HEAD
OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY,
OUT_RESP_HEAD,
)
from httpie.client import collect_messages from httpie.client import collect_messages
from httpie.context import Environment from httpie.context import Environment
from httpie.downloads import Downloader from httpie.downloads import Downloader
from httpie.output.writer import ( from httpie.output.writer import write_message, write_stream, MESSAGE_SEPARATOR_BYTES
write_message,
write_stream,
)
from httpie.plugins.registry import plugin_manager from httpie.plugins.registry import plugin_manager
from httpie.status import ExitStatus, http_status_to_exit_status from httpie.status import ExitStatus, http_status_to_exit_status
# noinspection PyDefaultArgument # noinspection PyDefaultArgument
def main( def main(args: List[Union[str, bytes]] = sys.argv, env=Environment()) -> ExitStatus:
args: List[Union[str, bytes]] = sys.argv,
env=Environment(),
) -> ExitStatus:
""" """
The main function. The main function.
@ -134,40 +125,22 @@ def get_output_options(
}[type(message)] }[type(message)]
def program( def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
args: argparse.Namespace,
env: Environment,
) -> ExitStatus:
""" """
The main program without error handling. The main program without error handling.
""" """
# TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere.
exit_status = ExitStatus.SUCCESS exit_status = ExitStatus.SUCCESS
downloader = None downloader = None
try:
if args.download:
args.follow = True # --download implies --follow.
downloader = Downloader(
output_file=args.output_file,
progress_file=env.stderr,
resume=args.download_resume
)
downloader.pre_request(args.headers)
needs_separator = False
def maybe_separate():
nonlocal needs_separator
if env.stdout.isatty() and needs_separator:
needs_separator = False
getattr(env.stdout, 'buffer', env.stdout).write(b'\n\n')
initial_request: Optional[requests.PreparedRequest] = None initial_request: Optional[requests.PreparedRequest] = None
final_response: Optional[requests.Response] = None final_response: Optional[requests.Response] = None
def separate():
getattr(env.stdout, 'buffer', env.stdout).write(MESSAGE_SEPARATOR_BYTES)
def request_body_read_callback(chunk: bytes): def request_body_read_callback(chunk: bytes):
should_pipe_to_stdout = ( should_pipe_to_stdout = bool(
# Request body output desired # Request body output desired
OUT_REQ_BODY in args.output_options OUT_REQ_BODY in args.output_options
# & not `.read()` already pre-request (e.g., for compression) # & not `.read()` already pre-request (e.g., for compression)
@ -180,66 +153,54 @@ def program(
msg.is_body_upload_chunk = True msg.is_body_upload_chunk = True
msg.body = chunk msg.body = chunk
msg.headers = initial_request.headers msg.headers = initial_request.headers
write_message( write_message(requests_message=msg, env=env, args=args, with_body=True, with_headers=False)
requests_message=msg,
env=env,
args=args,
with_body=True,
with_headers=False
)
messages = collect_messages( try:
args=args, if args.download:
config_dir=env.config.directory, args.follow = True # --download implies --follow.
request_body_read_callback=request_body_read_callback downloader = Downloader(output_file=args.output_file, progress_file=env.stderr, resume=args.download_resume)
) downloader.pre_request(args.headers)
messages = collect_messages(args=args, config_dir=env.config.directory,
request_body_read_callback=request_body_read_callback)
force_separator = False
prev_with_body = False
# Process messages as theyre generated
for message in messages: for message in messages:
maybe_separate()
is_request = isinstance(message, requests.PreparedRequest) is_request = isinstance(message, requests.PreparedRequest)
with_headers, with_body = get_output_options( with_headers, with_body = get_output_options(args=args, message=message)
args=args, message=message) do_write_body = with_body
if prev_with_body and (with_headers or with_body) and (force_separator or not env.stdout_isatty):
# Separate after a previous message with body, if needed. See test_tokens.py.
separate()
force_separator = False
if is_request: if is_request:
if not initial_request: if not initial_request:
initial_request = message initial_request = message
is_streamed_upload = not isinstance( is_streamed_upload = not isinstance(message.body, (str, bytes))
message.body, (str, bytes))
if with_body: if with_body:
with_body = not is_streamed_upload do_write_body = not is_streamed_upload
needs_separator = is_streamed_upload force_separator = is_streamed_upload and env.stdout_isatty
else: else:
final_response = message final_response = message
if args.check_status or downloader: if args.check_status or downloader:
exit_status = http_status_to_exit_status( exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
http_status=message.status_code, if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet):
follow=args.follow env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level='warning')
) write_message(requests_message=message, env=env, args=args, with_headers=with_headers,
if (not env.stdout_isatty with_body=do_write_body)
and exit_status != ExitStatus.SUCCESS): prev_with_body = with_body
env.log_error(
f'HTTP {message.raw.status} {message.raw.reason}',
level='warning'
)
write_message(
requests_message=message,
env=env,
args=args,
with_headers=with_headers,
with_body=with_body,
)
maybe_separate()
# Cleanup
if force_separator:
separate()
if downloader and exit_status == ExitStatus.SUCCESS: if downloader and exit_status == ExitStatus.SUCCESS:
# Last response body download. # Last response body download.
download_stream, download_to = downloader.start( download_stream, download_to = downloader.start(
initial_url=initial_request.url, initial_url=initial_request.url,
final_response=final_response, final_response=final_response,
) )
write_stream( write_stream(stream=download_stream, outfile=download_to, flush=False)
stream=download_stream,
outfile=download_to,
flush=False,
)
downloader.finish() downloader.finish()
if downloader.interrupted: if downloader.interrupted:
exit_status = ExitStatus.ERROR exit_status = ExitStatus.ERROR
@ -253,9 +214,7 @@ def program(
finally: finally:
if downloader and not downloader.finished: if downloader and not downloader.finished:
downloader.failed() downloader.failed()
if not isinstance(args, list) and args.output_file and args.output_file_specified:
if (not isinstance(args, list) and args.output_file
and args.output_file_specified):
args.output_file.close() args.output_file.close()
@ -283,6 +242,6 @@ def decode_raw_args(
""" """
return [ return [
arg.decode(stdin_encoding) arg.decode(stdin_encoding)
if type(arg) == bytes else arg if type(arg) is bytes else arg
for arg in args for arg in args
] ]

View File

@ -247,7 +247,7 @@ class Downloader:
assert not self.status.time_started assert not self.status.time_started
# FIXME: some servers still might sent Content-Encoding: gzip # FIXME: some servers still might sent Content-Encoding: gzip
# <https://github.com/jakubroztocil/httpie/issues/423> # <https://github.com/httpie/httpie/issues/423>
try: try:
total_size = int(final_response.headers['Content-Length']) total_size = int(final_response.headers['Content-Length'])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):

View File

@ -12,6 +12,10 @@ from httpie.output.streams import (
) )
MESSAGE_SEPARATOR = '\n\n'
MESSAGE_SEPARATOR_BYTES = MESSAGE_SEPARATOR.encode()
def write_message( def write_message(
requests_message: Union[requests.PreparedRequest, requests.Response], requests_message: Union[requests.PreparedRequest, requests.Response],
env: Environment, env: Environment,
@ -111,7 +115,7 @@ def build_output_stream_for_message(
and not getattr(requests_message, 'is_body_upload_chunk', False)): and not getattr(requests_message, 'is_body_upload_chunk', False)):
# Ensure a blank line after the response body. # Ensure a blank line after the response body.
# For terminal output only. # For terminal output only.
yield b'\n\n' yield MESSAGE_SEPARATOR_BYTES
def get_stream_type_and_kwargs( def get_stream_type_and_kwargs(

View File

@ -19,7 +19,7 @@ class HTTPBasicAuth(requests.auth.HTTPBasicAuth):
""" """
Override username/password serialization to allow unicode. Override username/password serialization to allow unicode.
See https://github.com/jakubroztocil/httpie/issues/212 See https://github.com/httpie/httpie/issues/212
""" """
# noinspection PyTypeChecker # noinspection PyTypeChecker

View File

@ -77,7 +77,9 @@ class Session(BaseConfigDict):
if value is None: if value is None:
continue # Ignore explicitly unset headers continue # Ignore explicitly unset headers
if type(value) is not str:
value = value.decode('utf8') value = value.decode('utf8')
if name.lower() == 'user-agent' and value.startswith('HTTPie/'): if name.lower() == 'user-agent' and value.startswith('HTTPie/'):
continue continue

View File

@ -109,6 +109,8 @@ def get_expired_cookies(
for attrs in attr_sets for attrs in attr_sets
] ]
_max_age_to_expires(cookies=cookies, now=now)
return [ return [
{ {
'name': cookie['name'], 'name': cookie['name'],
@ -117,3 +119,18 @@ def get_expired_cookies(
for cookie in cookies for cookie in cookies
if is_expired(expires=cookie.get('expires')) if is_expired(expires=cookie.get('expires'))
] ]
def _max_age_to_expires(cookies, now):
"""
Translate `max-age` into `expires` for Requests to take it into account.
HACK/FIXME: <https://github.com/psf/requests/issues/5743>
"""
for cookie in cookies:
if 'expires' in cookie:
continue
max_age = cookie.get('max-age')
if max_age and max_age.isdigit():
cookie['expires'] = now + float(max_age)

View File

@ -4,7 +4,7 @@
[tool:pytest] [tool:pytest]
# <https://docs.pytest.org/en/latest/customize.html> # <https://docs.pytest.org/en/latest/customize.html>
norecursedirs = tests/fixtures norecursedirs = tests/fixtures
addopts = --tb=native addopts = --tb=native --doctest-modules
[pycodestyle] [pycodestyle]

View File

@ -15,11 +15,14 @@ class PyTest(TestCommand):
and runs the tests with no fancy stuff like parallel execution. and runs the tests with no fancy stuff like parallel execution.
""" """
def finalize_options(self): def finalize_options(self):
TestCommand.finalize_options(self) TestCommand.finalize_options(self)
self.test_args = [ self.test_args = [
'--doctest-modules', '--verbose', '--doctest-modules',
'./httpie', './tests' '--verbose',
'./httpie',
'./tests',
] ]
self.test_suite = True self.test_suite = True
@ -71,8 +74,9 @@ setup(
version=httpie.__version__, version=httpie.__version__,
description=httpie.__doc__.strip(), description=httpie.__doc__.strip(),
long_description=long_description(), long_description=long_description(),
long_description_content_type='text/x-rst',
url='https://httpie.org/', url='https://httpie.org/',
download_url=f'https://github.com/jakubroztocil/httpie/archive/{httpie.__version__}.tar.gz', download_url=f'https://github.com/httpie/httpie/archive/{httpie.__version__}.tar.gz',
author=httpie.__author__, author=httpie.__author__,
author_email='jakub@roztocil.co', author_email='jakub@roztocil.co',
license=httpie.__licence__, license=httpie.__licence__,
@ -104,10 +108,9 @@ setup(
'Topic :: Utilities' 'Topic :: Utilities'
], ],
project_urls={ project_urls={
'Documentation': 'https://httpie.org/docs', 'GitHub': 'https://github.com/httpie/httpie',
'Source': 'https://github.com/jakubroztocil/httpie',
'Online Demo': 'https://httpie.org/run',
'Donate': 'https://httpie.org/donate',
'Twitter': 'https://twitter.com/httpie', 'Twitter': 'https://twitter.com/httpie',
'Documentation': 'https://httpie.org/docs',
'Online Demo': 'https://httpie.org/run',
}, },
) )

View File

@ -5,4 +5,4 @@ HTTPie Test Suite
Please see `CONTRIBUTING`_. Please see `CONTRIBUTING`_.
.. _CONTRIBUTING: https://github.com/jakubroztocil/httpie/blob/master/CONTRIBUTING.rst .. _CONTRIBUTING: https://github.com/httpie/httpie/blob/master/CONTRIBUTING.rst

View File

@ -58,7 +58,7 @@ def test_credentials_in_url_auth_flag_has_priority(httpbin_both):
]) ])
def test_only_username_in_url(url): def test_only_username_in_url(url):
""" """
https://github.com/jakubroztocil/httpie/issues/242 https://github.com/httpie/httpie/issues/242
""" """
args = httpie.cli.definition.parser.parse_args(args=[url], env=MockEnvironment()) args = httpie.cli.definition.parser.parse_args(args=[url], env=MockEnvironment())

View File

@ -11,7 +11,7 @@ from fixtures import FILE_PATH
def test_default_headers_case_insensitive(httpbin): def test_default_headers_case_insensitive(httpbin):
""" """
<https://github.com/jakubroztocil/httpie/issues/644> <https://github.com/httpie/httpie/issues/644>
""" """
r = http( r = http(
'--debug', '--debug',
@ -63,7 +63,7 @@ class TestAutoContentTypeAndAcceptHeaders:
""" """
def test_GET_no_data_no_auto_headers(self, httpbin): def test_GET_no_data_no_auto_headers(self, httpbin):
# https://github.com/jakubroztocil/httpie/issues/62 # https://github.com/httpie/httpie/issues/62
r = http('GET', httpbin.url + '/headers') r = http('GET', httpbin.url + '/headers')
assert HTTP_OK in r assert HTTP_OK in r
assert r.json['headers']['Accept'] == '*/*' assert r.json['headers']['Accept'] == '*/*'
@ -94,7 +94,7 @@ class TestAutoContentTypeAndAcceptHeaders:
assert HTTP_OK in r assert HTTP_OK in r
assert r.json['headers']['Accept'] == JSON_ACCEPT assert r.json['headers']['Accept'] == JSON_ACCEPT
# Make sure Content-Type gets set even with no data. # Make sure Content-Type gets set even with no data.
# https://github.com/jakubroztocil/httpie/issues/137 # https://github.com/httpie/httpie/issues/137
assert 'application/json' in r.json['headers']['Content-Type'] assert 'application/json' in r.json['headers']['Content-Type']
def test_GET_explicit_JSON_explicit_headers(self, httpbin): def test_GET_explicit_JSON_explicit_headers(self, httpbin):

View File

@ -5,7 +5,6 @@ from urllib.request import urlopen
import pytest import pytest
import mock import mock
import requests
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from httpie.downloads import ( from httpie.downloads import (

View File

@ -39,7 +39,7 @@ def test_debug():
def test_help(): def test_help():
r = http('--help', tolerate_error_exit_status=True) r = http('--help', tolerate_error_exit_status=True)
assert r.exit_status == ExitStatus.SUCCESS assert r.exit_status == ExitStatus.SUCCESS
assert 'https://github.com/jakubroztocil/httpie/issues' in r assert 'https://github.com/httpie/httpie/issues' in r
def test_version(): def test_version():
@ -121,7 +121,7 @@ def test_POST_file(httpbin_both):
def test_form_POST_file_redirected_stdin(httpbin): def test_form_POST_file_redirected_stdin(httpbin):
""" """
<https://github.com/jakubroztocil/httpie/issues/840> <https://github.com/httpie/httpie/issues/840>
""" """
with open(FILE_PATH) as f: with open(FILE_PATH) as f:

View File

@ -54,6 +54,21 @@ class TestQuietFlag:
assert r == '' assert r == ''
assert r.stderr == '' assert r.stderr == ''
def test_quiet_with_check_status_non_zero(self, httpbin):
r = http(
'--quiet', '--check-status', httpbin + '/status/500',
tolerate_error_exit_status=True,
)
assert 'http: warning: HTTP 500' in r.stderr
def test_quiet_with_check_status_non_zero_pipe(self, httpbin):
r = http(
'--quiet', '--check-status', httpbin + '/status/500',
tolerate_error_exit_status=True,
env=MockEnvironment(stdout_isatty=False)
)
assert 'http: warning: HTTP 500' in r.stderr
@mock.patch('httpie.cli.argtypes.AuthCredentials._getpass', @mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',
new=lambda self, prompt: 'password') new=lambda self, prompt: 'password')
def test_quiet_with_password_prompt(self, httpbin): def test_quiet_with_password_prompt(self, httpbin):
@ -127,7 +142,7 @@ class TestVerboseFlag:
assert r.count('__test__') == 2 assert r.count('__test__') == 2
def test_verbose_form(self, httpbin): def test_verbose_form(self, httpbin):
# https://github.com/jakubroztocil/httpie/issues/53 # https://github.com/httpie/httpie/issues/53
r = http('--verbose', '--form', 'POST', httpbin.url + '/post', r = http('--verbose', '--form', 'POST', httpbin.url + '/post',
'A=B', 'C=D') 'A=B', 'C=D')
assert HTTP_OK in r assert HTTP_OK in r

View File

@ -1,16 +1,17 @@
"""Miscellaneous regression tests""" """Miscellaneous regression tests"""
import pytest import pytest
from utils import http, HTTP_OK
from httpie.compat import is_windows from httpie.compat import is_windows
from tests.utils.matching import assert_output_matches, Expect
from utils import HTTP_OK, MockEnvironment, http
def test_Host_header_overwrite(httpbin): def test_Host_header_overwrite(httpbin):
""" """
https://github.com/jakubroztocil/httpie/issues/235 https://github.com/httpie/httpie/issues/235
""" """
host = 'httpbin.org' host = 'pie.dev'
url = httpbin.url + '/get' url = httpbin.url + '/get'
r = http('--print=hH', url, 'host:{0}'.format(host)) r = http('--print=hH', url, 'host:{0}'.format(host))
assert HTTP_OK in r assert HTTP_OK in r
@ -21,7 +22,28 @@ def test_Host_header_overwrite(httpbin):
@pytest.mark.skipif(is_windows, reason='Unix-only') @pytest.mark.skipif(is_windows, reason='Unix-only')
def test_output_devnull(httpbin): def test_output_devnull(httpbin):
""" """
https://github.com/jakubroztocil/httpie/issues/252 https://github.com/httpie/httpie/issues/252
""" """
http('--output=/dev/null', httpbin + '/get') http('--output=/dev/null', httpbin + '/get')
def test_verbose_redirected_stdout_separator(httpbin):
"""
<https://github.com/httpie/httpie/issues/1006>
"""
r = http(
'-v',
httpbin.url + '/post',
'a=b',
env=MockEnvironment(stdout_isatty=False),
)
assert '}HTTP/' not in r
assert_output_matches(r, [
Expect.REQUEST_HEADERS,
Expect.BODY,
Expect.SEPARATOR,
Expect.RESPONSE_HEADERS,
Expect.BODY,
])

View File

@ -16,6 +16,7 @@ from httpie.sessions import Session
from httpie.utils import get_expired_cookies from httpie.utils import get_expired_cookies
from tests.test_auth_plugins import basic_auth from tests.test_auth_plugins import basic_auth
from utils import HTTP_OK, MockEnvironment, http, mk_config_dir from utils import HTTP_OK, MockEnvironment, http, mk_config_dir
from fixtures import FILE_PATH_ARG
class SessionTestBase: class SessionTestBase:
@ -161,6 +162,12 @@ class TestSession(SessionTestBase):
assert 'Content-Type' not in r2.json['headers'] assert 'Content-Type' not in r2.json['headers']
assert 'If-Unmodified-Since' not in r2.json['headers'] assert 'If-Unmodified-Since' not in r2.json['headers']
def test_session_with_upload(self, httpbin):
self.start_session(httpbin)
r = http('--session=test', '--form', '--verbose', 'POST', httpbin.url + '/post',
f'test-file@{FILE_PATH_ARG}', 'foo=bar', env=self.env())
assert HTTP_OK in r
def test_session_by_path(self, httpbin): def test_session_by_path(self, httpbin):
self.start_session(httpbin) self.start_session(httpbin)
session_path = self.config_dir / 'session-by-path.json' session_path = self.config_dir / 'session-by-path.json'
@ -193,7 +200,7 @@ class TestSession(SessionTestBase):
def test_session_default_header_value_overwritten(self, httpbin): def test_session_default_header_value_overwritten(self, httpbin):
self.start_session(httpbin) self.start_session(httpbin)
# https://github.com/jakubroztocil/httpie/issues/180 # https://github.com/httpie/httpie/issues/180
r1 = http('--session=test', r1 = http('--session=test',
httpbin.url + '/headers', 'User-Agent:custom', httpbin.url + '/headers', 'User-Agent:custom',
env=self.env()) env=self.env())
@ -205,7 +212,7 @@ class TestSession(SessionTestBase):
assert r2.json['headers']['User-Agent'] == 'custom' assert r2.json['headers']['User-Agent'] == 'custom'
def test_download_in_session(self, httpbin): def test_download_in_session(self, httpbin):
# https://github.com/jakubroztocil/httpie/issues/412 # https://github.com/httpie/httpie/issues/412
self.start_session(httpbin) self.start_session(httpbin)
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(gettempdir()) os.chdir(gettempdir())
@ -338,6 +345,15 @@ class TestExpiredCookies(CookieTestBase):
assert 'cookie1' in updated_session['cookies'] assert 'cookie1' in updated_session['cookies']
assert 'cookie2' not in updated_session['cookies'] assert 'cookie2' not in updated_session['cookies']
def test_get_expired_cookies_using_max_age(self):
headers = [
('Set-Cookie', 'one=two; Max-Age=0; path=/; domain=.tumblr.com; HttpOnly')
]
expected_expired = [
{'name': 'one', 'path': '/'}
]
assert get_expired_cookies(headers, now=None) == expected_expired
@pytest.mark.parametrize( @pytest.mark.parametrize(
argnames=['headers', 'now', 'expected_expired'], argnames=['headers', 'now', 'expected_expired'],
argvalues=[ argvalues=[

View File

@ -11,7 +11,7 @@ from utils import HTTP_OK, TESTS_ROOT, http
try: try:
# Handle OpenSSL errors, if installed. # Handle OpenSSL errors, if installed.
# See <https://github.com/jakubroztocil/httpie/issues/729> # See <https://github.com/httpie/httpie/issues/729>
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import OpenSSL.SSL import OpenSSL.SSL
ssl_errors = ( ssl_errors = (

141
tests/test_tokens.py Normal file
View File

@ -0,0 +1,141 @@
"""
The ideas behind these test and the named templates is to ensure consistent output
across all supported different scenarios:
TODO: cover more scenarios
* terminal vs. redirect stdout
* different combinations of `--print=HBhb` (request/response headers/body)
* multipart requests
* streamed uploads
"""
from tests.utils.matching import assert_output_matches, Expect
from utils import http, HTTP_OK, MockEnvironment, HTTPBIN_WITH_CHUNKED_SUPPORT
RAW_REQUEST = [
Expect.REQUEST_HEADERS,
Expect.BODY,
]
RAW_RESPONSE = [
Expect.RESPONSE_HEADERS,
Expect.BODY,
]
RAW_EXCHANGE = [
*RAW_REQUEST,
Expect.SEPARATOR, # Good choice?
*RAW_RESPONSE,
]
RAW_BODY = [
Expect.BODY,
]
TERMINAL_REQUEST = [
*RAW_REQUEST,
Expect.SEPARATOR,
]
TERMINAL_RESPONSE = [
*RAW_RESPONSE,
Expect.SEPARATOR,
]
TERMINAL_EXCHANGE = [
*TERMINAL_REQUEST,
*TERMINAL_RESPONSE,
]
TERMINAL_BODY = [
RAW_BODY,
Expect.SEPARATOR
]
def test_headers():
r = http('--print=H', '--offline', 'pie.dev')
assert_output_matches(r, [Expect.REQUEST_HEADERS])
def test_redirected_headers():
r = http('--print=H', '--offline', 'pie.dev', env=MockEnvironment(stdout_isatty=False))
assert_output_matches(r, [Expect.REQUEST_HEADERS])
def test_terminal_headers_and_body():
r = http('--print=HB', '--offline', 'pie.dev', 'AAA=BBB')
assert_output_matches(r, TERMINAL_REQUEST)
def test_terminal_request_headers_response_body(httpbin):
r = http('--print=Hb', httpbin + '/get')
assert_output_matches(r, TERMINAL_REQUEST)
def test_raw_request_headers_response_body(httpbin):
r = http('--print=Hb', httpbin + '/get', env=MockEnvironment(stdout_isatty=False))
assert_output_matches(r, RAW_REQUEST)
def test_terminal_request_headers_response_headers(httpbin):
r = http('--print=Hh', httpbin + '/get')
assert_output_matches(r, [Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS])
def test_raw_request_headers_response_headers(httpbin):
r = http('--print=Hh', httpbin + '/get')
assert_output_matches(r, [Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS])
def test_terminal_request_body_response_body(httpbin):
r = http('--print=Hh', httpbin + '/get')
assert_output_matches(r, [Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS])
def test_raw_headers_and_body():
r = http(
'--print=HB', '--offline', 'pie.dev', 'AAA=BBB',
env=MockEnvironment(stdout_isatty=False),
)
assert_output_matches(r, RAW_REQUEST)
def test_raw_body():
r = http(
'--print=B', '--offline', 'pie.dev', 'AAA=BBB',
env=MockEnvironment(stdout_isatty=False),
)
assert_output_matches(r, RAW_BODY)
def test_raw_exchange(httpbin):
r = http('--verbose', httpbin + '/post', 'a=b', env=MockEnvironment(stdout_isatty=False))
assert HTTP_OK in r
assert_output_matches(r, RAW_EXCHANGE)
def test_terminal_exchange(httpbin):
r = http('--verbose', httpbin + '/post', 'a=b')
assert HTTP_OK in r
assert_output_matches(r, TERMINAL_EXCHANGE)
def test_headers_multipart_body_separator():
r = http('--print=HB', '--multipart', '--offline', 'pie.dev', 'AAA=BBB')
assert_output_matches(r, TERMINAL_REQUEST)
def test_redirected_headers_multipart_no_separator():
r = http(
'--print=HB', '--multipart', '--offline', 'pie.dev', 'AAA=BBB',
env=MockEnvironment(stdout_isatty=False),
)
assert_output_matches(r, RAW_REQUEST)
def test_verbose_chunked():
r = http('--verbose', '--chunked', HTTPBIN_WITH_CHUNKED_SUPPORT + '/post', 'hello=world')
assert HTTP_OK in r
assert 'Transfer-Encoding: chunked' in r
assert_output_matches(r, TERMINAL_EXCHANGE)
def test_request_headers_response_body(httpbin):
r = http('--print=Hb', httpbin + '/get')
assert_output_matches(r, TERMINAL_REQUEST)

View File

@ -52,6 +52,23 @@ def test_chunked_stdin():
assert r.count(FILE_CONTENT) == 2 assert r.count(FILE_CONTENT) == 2
def test_chunked_stdin_multiple_chunks():
stdin_bytes = FILE_PATH.read_bytes() + b'\n' + FILE_PATH.read_bytes()
r = http(
'--verbose',
'--chunked',
HTTPBIN_WITH_CHUNKED_SUPPORT + '/post',
env=MockEnvironment(
stdin=StdinBytesIO(stdin_bytes),
stdin_isatty=False,
stdout_isatty=True,
)
)
assert HTTP_OK in r
assert 'Transfer-Encoding: chunked' in r
assert r.count(FILE_CONTENT) == 4
class TestMultipartFormDataFileUpload: class TestMultipartFormDataFileUpload:
def test_non_existent_file_raises_parse_error(self, httpbin): def test_non_existent_file_raises_parse_error(self, httpbin):

View File

@ -1,12 +1,14 @@
# coding=utf-8 # coding=utf-8
"""Utilities for HTTPie test suite.""" """Utilities for HTTPie test suite."""
import re
import shlex
import sys import sys
import time import time
import json import json
import tempfile import tempfile
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import Optional, Union from typing import Optional, Union, List
from httpie.status import ExitStatus from httpie.status import ExitStatus
from httpie.config import Config from httpie.config import Config
@ -17,10 +19,10 @@ from httpie.core import main
# pytest-httpbin currently does not support chunked requests: # pytest-httpbin currently does not support chunked requests:
# <https://github.com/kevin1024/pytest-httpbin/issues/33> # <https://github.com/kevin1024/pytest-httpbin/issues/33>
# <https://github.com/kevin1024/pytest-httpbin/issues/28> # <https://github.com/kevin1024/pytest-httpbin/issues/28>
HTTPBIN_WITH_CHUNKED_SUPPORT = 'http://httpbin.org' HTTPBIN_WITH_CHUNKED_SUPPORT = 'http://pie.dev'
TESTS_ROOT = Path(__file__).parent TESTS_ROOT = Path(__file__).parent.parent
CRLF = '\r\n' CRLF = '\r\n'
COLOR = '\x1b[' COLOR = '\x1b['
HTTP_OK = '200 OK' HTTP_OK = '200 OK'
@ -49,7 +51,7 @@ class StdinBytesIO(BytesIO):
class MockEnvironment(Environment): class MockEnvironment(Environment):
"""Environment subclass with reasonable defaults for testing.""" """Environment subclass with reasonable defaults for testing."""
colors = 0 colors = 0 # For easier debugging
stdin_isatty = True, stdin_isatty = True,
stdout_isatty = True stdout_isatty = True
is_windows = False is_windows = False
@ -113,6 +115,15 @@ class BaseCLIResponse:
devnull: str = None devnull: str = None
json: dict = None json: dict = None
exit_status: ExitStatus = None exit_status: ExitStatus = None
command: str = None
args: List[str] = []
complete_args: List[str] = []
@property
def command(self):
cmd = ' '.join(shlex.quote(arg) for arg in ['http', *self.args])
# pytest-httpbin to real httpbin.
return re.sub(r'127\.0\.0\.1:\d+', 'httpbin.org', cmd)
class BytesCLIResponse(bytes, BaseCLIResponse): class BytesCLIResponse(bytes, BaseCLIResponse):
@ -198,7 +209,7 @@ def http(
Example: Example:
$ http --auth=user:password GET httpbin.org/basic-auth/user/password $ http --auth=user:password GET pie.dev/basic-auth/user/password
>>> httpbin = getfixture('httpbin') >>> httpbin = getfixture('httpbin')
>>> r = http('-a', 'user:pw', httpbin.url + '/basic-auth/user/pw') >>> r = http('-a', 'user:pw', httpbin.url + '/basic-auth/user/pw')
@ -284,10 +295,13 @@ def http(
r.devnull = devnull_output r.devnull = devnull_output
r.stderr = stderr.read() r.stderr = stderr.read()
r.exit_status = exit_status r.exit_status = exit_status
r.args = args
r.complete_args = ' '.join(complete_args)
if r.exit_status != ExitStatus.SUCCESS: if r.exit_status != ExitStatus.SUCCESS:
sys.stderr.write(r.stderr) sys.stderr.write(r.stderr)
# print(f'\n\n$ {r.command}\n')
return r return r
finally: finally:

View File

@ -0,0 +1,32 @@
from typing import Iterable
import pytest
from tests.utils.matching.parsing import OutputMatchingError, expect_tokens, Expect
__all__ = [
'assert_output_matches',
'assert_output_does_not_match',
'Expect',
]
def assert_output_matches(output: str, tokens: Iterable[Expect]):
r"""
Check the command `output` for an exact full sequence of `tokens`.
>>> out = 'GET / HTTP/1.1\r\nAAA:BBB\r\n\r\nCCC\n\n'
>>> assert_output_matches(out, [Expect.REQUEST_HEADERS, Expect.BODY, Expect.SEPARATOR])
"""
# TODO: auto-remove ansi colors to allow for testing of colorized output as well.
expect_tokens(tokens=tokens, s=output)
def assert_output_does_not_match(output: str, tokens: Iterable[Expect]):
r"""
>>> assert_output_does_not_match('\r\n', [Expect.BODY])
"""
with pytest.raises(OutputMatchingError):
assert_output_matches(output=output, tokens=tokens)

View File

@ -0,0 +1,107 @@
import re
from typing import Iterable
from enum import Enum, auto
from httpie.output.writer import MESSAGE_SEPARATOR
from tests.utils import CRLF
class Expect(Enum):
"""
Predefined token types we can expect in the output.
"""
REQUEST_HEADERS = auto()
RESPONSE_HEADERS = auto()
BODY = auto()
SEPARATOR = auto()
SEPARATOR_RE = re.compile(f'^{MESSAGE_SEPARATOR}')
def make_headers_re(message_type: Expect):
assert message_type in {Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS}
# language=RegExp
crlf = r'[\r][\n]'
non_crlf = rf'[^{CRLF}]'
# language=RegExp
http_version = r'HTTP/\d+\.\d+'
if message_type is Expect.REQUEST_HEADERS:
# POST /post HTTP/1.1
start_line_re = fr'{non_crlf}*{http_version}{crlf}'
else:
# HTTP/1.1 200 OK
start_line_re = fr'{http_version}{non_crlf}*{crlf}'
return re.compile(
fr'''
^
{start_line_re}
({non_crlf}+:{non_crlf}+{crlf})+
{crlf}
''',
flags=re.VERBOSE
)
BODY_ENDINGS = [
MESSAGE_SEPARATOR,
CRLF, # Not really but useful for testing (just remember not to include it in a body).
]
TOKEN_REGEX_MAP = {
Expect.REQUEST_HEADERS: make_headers_re(Expect.REQUEST_HEADERS),
Expect.RESPONSE_HEADERS: make_headers_re(Expect.RESPONSE_HEADERS),
Expect.SEPARATOR: SEPARATOR_RE,
}
class OutputMatchingError(ValueError):
pass
def expect_tokens(tokens: Iterable[Expect], s: str):
for token in tokens:
s = expect_token(token, s)
if s:
raise OutputMatchingError(f'Unmatched remaining output for {tokens} in {s!r}')
def expect_token(token: Expect, s: str) -> str:
if token is Expect.BODY:
s = expect_body(s)
else:
s = expect_regex(token, s)
return s
def expect_regex(token: Expect, s: str) -> str:
match = TOKEN_REGEX_MAP[token].match(s)
if not match:
raise OutputMatchingError(f'No match for {token} in {s!r}')
return s[match.end():]
def expect_body(s: str) -> str:
"""
We require some text, and continue to read until we find an ending or until the end of the string.
"""
if 'content-disposition:' in s.lower():
# Multipart body heuristic.
final_boundary_re = re.compile('\r\n--[^-]+?--\r\n')
match = final_boundary_re.search(s)
if match:
return s[match.end():]
endings = [s.index(sep) for sep in BODY_ENDINGS if sep in s]
if not endings:
s = '' # Only body
else:
end = min(endings)
if end == 0:
raise OutputMatchingError(f'Empty body: {s!r}')
s = s[end:]
return s

View File

@ -0,0 +1,190 @@
"""
Here we test our output parsing and matching implementation, not HTTPie itself.
"""
from httpie.output.writer import MESSAGE_SEPARATOR
from tests.utils import CRLF
from tests.utils.matching import assert_output_does_not_match, assert_output_matches, Expect
def test_assert_output_matches_headers_incomplete():
assert_output_does_not_match(f'HTTP/1.1{CRLF}', [Expect.RESPONSE_HEADERS])
def test_assert_output_matches_headers_unterminated():
assert_output_does_not_match(
(
f'HTTP/1.1{CRLF}'
f'AAA:BBB'
f'{CRLF}'
),
[Expect.RESPONSE_HEADERS],
)
def test_assert_output_matches_response_headers():
assert_output_matches(
(
f'HTTP/1.1 200 OK{CRLF}'
f'AAA:BBB{CRLF}'
f'{CRLF}'
),
[Expect.RESPONSE_HEADERS],
)
def test_assert_output_matches_request_headers():
assert_output_matches(
(
f'GET / HTTP/1.1{CRLF}'
f'AAA:BBB{CRLF}'
f'{CRLF}'
),
[Expect.REQUEST_HEADERS],
)
def test_assert_output_matches_headers_and_separator():
assert_output_matches(
(
f'HTTP/1.1{CRLF}'
f'AAA:BBB{CRLF}'
f'{CRLF}'
f'{MESSAGE_SEPARATOR}'
),
[Expect.RESPONSE_HEADERS, Expect.SEPARATOR],
)
def test_assert_output_matches_body_unmatched_crlf():
assert_output_does_not_match(f'AAA{CRLF}', [Expect.BODY])
def test_assert_output_matches_body_unmatched_separator():
assert_output_does_not_match(f'AAA{MESSAGE_SEPARATOR}', [Expect.BODY])
def test_assert_output_matches_body_and_separator():
assert_output_matches(f'AAA{MESSAGE_SEPARATOR}', [Expect.BODY, Expect.SEPARATOR])
def test_assert_output_matches_body_r():
assert_output_matches(f'AAA\r', [Expect.BODY])
def test_assert_output_matches_body_n():
assert_output_matches(f'AAA\n', [Expect.BODY])
def test_assert_output_matches_body_r_body():
assert_output_matches(f'AAA\rBBB', [Expect.BODY])
def test_assert_output_matches_body_n_body():
assert_output_matches(f'AAA\nBBB', [Expect.BODY])
def test_assert_output_matches_headers_and_body():
assert_output_matches(
(
f'HTTP/1.1{CRLF}'
f'AAA:BBB{CRLF}'
f'{CRLF}'
f'CCC'
),
[Expect.RESPONSE_HEADERS, Expect.BODY]
)
def test_assert_output_matches_headers_with_body_and_separator():
assert_output_matches(
(
f'HTTP/1.1 {CRLF}'
f'AAA:BBB{CRLF}{CRLF}'
f'CCC{MESSAGE_SEPARATOR}'
),
[Expect.RESPONSE_HEADERS, Expect.BODY, Expect.SEPARATOR]
)
def test_assert_output_matches_multiple_messages():
assert_output_matches(
(
f'POST / HTTP/1.1{CRLF}'
f'AAA:BBB{CRLF}'
f'{CRLF}'
f'CCC'
f'{MESSAGE_SEPARATOR}'
f'HTTP/1.1 200 OK{CRLF}'
f'EEE:FFF{CRLF}'
f'{CRLF}'
f'GGG'
f'{MESSAGE_SEPARATOR}'
), [
Expect.REQUEST_HEADERS,
Expect.BODY,
Expect.SEPARATOR,
Expect.RESPONSE_HEADERS,
Expect.BODY,
Expect.SEPARATOR,
]
)
def test_assert_output_matches_multipart_body():
output = (
'POST / HTTP/1.1\r\n'
'User-Agent: HTTPie/2.4.0-dev\r\n'
'Accept-Encoding: gzip, deflate\r\n'
'Accept: */*\r\n'
'Connection: keep-alive\r\n'
'Content-Type: multipart/form-data; boundary=1e22169de43e4a2e8d9e41c0a1c93cc5\r\n'
'Content-Length: 212\r\n'
'Host: pie.dev\r\n'
'\r\n'
'--1e22169de43e4a2e8d9e41c0a1c93cc5\r\n'
'Content-Disposition: form-data; name="AAA"\r\n'
'\r\n'
'BBB\r\n'
'--1e22169de43e4a2e8d9e41c0a1c93cc5\r\n'
'Content-Disposition: form-data; name="CCC"\r\n'
'\r\n'
'DDD\r\n'
'--1e22169de43e4a2e8d9e41c0a1c93cc5--\r\n'
)
assert_output_matches(output, [Expect.REQUEST_HEADERS, Expect.BODY])
def test_assert_output_matches_multipart_body_with_separator():
output = (
'POST / HTTP/1.1\r\n'
'User-Agent: HTTPie/2.4.0-dev\r\n'
'Accept-Encoding: gzip, deflate\r\n'
'Accept: */*\r\n'
'Connection: keep-alive\r\n'
'Content-Type: multipart/form-data; boundary=1e22169de43e4a2e8d9e41c0a1c93cc5\r\n'
'Content-Length: 212\r\n'
'Host: pie.dev\r\n'
'\r\n'
'--1e22169de43e4a2e8d9e41c0a1c93cc5\r\n'
'Content-Disposition: form-data; name="AAA"\r\n'
'\r\n'
'BBB\r\n'
'--1e22169de43e4a2e8d9e41c0a1c93cc5\r\n'
'Content-Disposition: form-data; name="CCC"\r\n'
'\r\n'
'DDD\r\n'
'--1e22169de43e4a2e8d9e41c0a1c93cc5--\r\n'
f'{MESSAGE_SEPARATOR}'
)
assert_output_matches(output, [Expect.REQUEST_HEADERS, Expect.BODY, Expect.SEPARATOR])
def test_assert_output_matches_multiple_separators():
assert_output_matches(
MESSAGE_SEPARATOR + MESSAGE_SEPARATOR + 'AAA' + MESSAGE_SEPARATOR + MESSAGE_SEPARATOR,
[Expect.SEPARATOR, Expect.SEPARATOR, Expect.BODY, Expect.SEPARATOR, Expect.SEPARATOR]
)