mirror of
https://github.com/httpie/cli.git
synced 2025-08-13 22:39:04 +02:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
bb36897054 | |||
173e622567 | |||
3426030370 | |||
d014498713 | |||
5414d1853e | |||
1ac8f69651 | |||
3c07a25326 | |||
cf78a12e46 | |||
0f1e098cc4 | |||
0401d7b31c | |||
795627f965 | |||
21cc008cb2 | |||
77b8c37cb0 | |||
db685d58b5 | |||
44ae67685d | |||
6922a0c912 | |||
2afdc958c6 | |||
57b1baf1d1 | |||
1828da6a50 | |||
0629f2ff42 | |||
d71b7eee81 | |||
9883a46575 | |||
2409077a6d | |||
02971b938d | |||
f7e77efe4b | |||
5d8bd0da7c | |||
3c6e7c73fe | |||
d64c0ee415 | |||
311a5ede70 | |||
f64c90010f | |||
8456ddb27c | |||
cf254680b7 | |||
42c4a7596b | |||
1573058811 |
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@ -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:
|
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- run: python -m pip install --upgrade pip setuptools wheel
|
||||
- run: make install
|
||||
- run: make pycodestyle
|
||||
@ -23,10 +23,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
python-version: [3.6, 3.7, 3.8]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: 3.8
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
|
@ -8,7 +8,7 @@ HTTPie authors
|
||||
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)
|
||||
* `Hank Gay <https://github.com/gthank>`_
|
||||
@ -38,5 +38,6 @@ Patches and ideas
|
||||
* `Edward Yang <https://github.com/honorabrutroll>`_
|
||||
* `Aleksandr Vinokurov <https://github.com/aleksandr-vin>`_
|
||||
* `Jeff Byrnes <https://github.com/jeffbyrnes>`_
|
||||
* `Denis Belavin <https://github.com/LuckyDenis>`_
|
||||
|
||||
|
||||
|
131
CHANGELOG.rst
131
CHANGELOG.rst
@ -6,13 +6,24 @@ This document records all notable changes to `HTTPie <https://httpie.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 body-from-file upload streaming (``http httpbin.org/post @file``).
|
||||
* Added ``--chunked`` to allow chunked transfer encoding.
|
||||
* Added support for body-from-file upload streaming (``http pie.dev/post @file``).
|
||||
* Added ``--chunked`` to enable chunked transfer encoding (`#753`_).
|
||||
* 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 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`_).
|
||||
@ -419,57 +430,65 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
||||
* Initial public release
|
||||
|
||||
|
||||
.. _`0.1.0`: https://github.com/jakubroztocil/httpie/commit/b966efa
|
||||
.. _0.1.4: https://github.com/jakubroztocil/httpie/compare/b966efa...0.1.4
|
||||
.. _0.1.5: https://github.com/jakubroztocil/httpie/compare/0.1.4...0.1.5
|
||||
.. _0.1.6: https://github.com/jakubroztocil/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.1: https://github.com/jakubroztocil/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.5: https://github.com/jakubroztocil/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.7: https://github.com/jakubroztocil/httpie/compare/0.2.5...0.2.7
|
||||
.. _0.3.0: https://github.com/jakubroztocil/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.1: https://github.com/jakubroztocil/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.1: https://github.com/jakubroztocil/httpie/compare/0.5.0...0.5.1
|
||||
.. _0.6.0: https://github.com/jakubroztocil/httpie/compare/0.5.1...0.6.0
|
||||
.. _0.7.1: https://github.com/jakubroztocil/httpie/compare/0.6.0...0.7.1
|
||||
.. _0.8.0: https://github.com/jakubroztocil/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.1: https://github.com/jakubroztocil/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.3: https://github.com/jakubroztocil/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.6: https://github.com/jakubroztocil/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.9: https://github.com/jakubroztocil/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.1: https://github.com/jakubroztocil/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.3: https://github.com/jakubroztocil/httpie/compare/1.0.2...1.0.3
|
||||
.. _2.0.0: https://github.com/jakubroztocil/httpie/compare/1.0.3...2.0.0
|
||||
.. _2.1.0: https://github.com/jakubroztocil/httpie/compare/2.0.0...2.1.0
|
||||
.. _2.2.0: https://github.com/jakubroztocil/httpie/compare/2.1.0...2.2.0
|
||||
.. _2.3.0-dev: https://github.com/jakubroztocil/httpie/compare/2.2.0...master
|
||||
.. _`0.1.0`: https://github.com/httpie/httpie/commit/b966efa
|
||||
.. _0.1.4: https://github.com/httpie/httpie/compare/b966efa...0.1.4
|
||||
.. _0.1.5: https://github.com/httpie/httpie/compare/0.1.4...0.1.5
|
||||
.. _0.1.6: https://github.com/httpie/httpie/compare/0.1.5...0.1.6
|
||||
.. _0.2.0: https://github.com/httpie/httpie/compare/0.1.6...0.2.0
|
||||
.. _0.2.1: https://github.com/httpie/httpie/compare/0.2.0...0.2.1
|
||||
.. _0.2.2: https://github.com/httpie/httpie/compare/0.2.1...0.2.2
|
||||
.. _0.2.5: https://github.com/httpie/httpie/compare/0.2.2...0.2.5
|
||||
.. _0.2.6: https://github.com/httpie/httpie/compare/0.2.5...0.2.6
|
||||
.. _0.2.7: https://github.com/httpie/httpie/compare/0.2.5...0.2.7
|
||||
.. _0.3.0: https://github.com/httpie/httpie/compare/0.2.7...0.3.0
|
||||
.. _0.4.0: https://github.com/httpie/httpie/compare/0.3.0...0.4.0
|
||||
.. _0.4.1: https://github.com/httpie/httpie/compare/0.4.0...0.4.1
|
||||
.. _0.5.0: https://github.com/httpie/httpie/compare/0.4.1...0.5.0
|
||||
.. _0.5.1: https://github.com/httpie/httpie/compare/0.5.0...0.5.1
|
||||
.. _0.6.0: https://github.com/httpie/httpie/compare/0.5.1...0.6.0
|
||||
.. _0.7.1: https://github.com/httpie/httpie/compare/0.6.0...0.7.1
|
||||
.. _0.8.0: https://github.com/httpie/httpie/compare/0.7.1...0.8.0
|
||||
.. _0.9.0: https://github.com/httpie/httpie/compare/0.8.0...0.9.0
|
||||
.. _0.9.1: https://github.com/httpie/httpie/compare/0.9.0...0.9.1
|
||||
.. _0.9.2: https://github.com/httpie/httpie/compare/0.9.1...0.9.2
|
||||
.. _0.9.3: https://github.com/httpie/httpie/compare/0.9.2...0.9.3
|
||||
.. _0.9.4: https://github.com/httpie/httpie/compare/0.9.3...0.9.4
|
||||
.. _0.9.6: https://github.com/httpie/httpie/compare/0.9.4...0.9.6
|
||||
.. _0.9.8: https://github.com/httpie/httpie/compare/0.9.6...0.9.8
|
||||
.. _0.9.9: https://github.com/httpie/httpie/compare/0.9.8...0.9.9
|
||||
.. _1.0.0: https://github.com/httpie/httpie/compare/0.9.9...1.0.0
|
||||
.. _1.0.1: https://github.com/httpie/httpie/compare/1.0.0...1.0.1
|
||||
.. _1.0.2: https://github.com/httpie/httpie/compare/1.0.1...1.0.2
|
||||
.. _1.0.3: https://github.com/httpie/httpie/compare/1.0.2...1.0.3
|
||||
.. _2.0.0: https://github.com/httpie/httpie/compare/1.0.3...2.0.0
|
||||
.. _2.1.0: https://github.com/httpie/httpie/compare/2.0.0...2.1.0
|
||||
.. _2.2.0: https://github.com/httpie/httpie/compare/2.1.0...2.2.0
|
||||
.. _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/jakubroztocil/httpie/issues/128
|
||||
.. _#488: https://github.com/jakubroztocil/httpie/issues/488
|
||||
.. _#668: https://github.com/jakubroztocil/httpie/issues/668
|
||||
.. _#684: https://github.com/jakubroztocil/httpie/issues/684
|
||||
.. _#718: https://github.com/jakubroztocil/httpie/issues/718
|
||||
.. _#719: https://github.com/jakubroztocil/httpie/issues/719
|
||||
.. _#840: https://github.com/jakubroztocil/httpie/issues/840
|
||||
.. _#853: https://github.com/jakubroztocil/httpie/issues/853
|
||||
.. _#852: https://github.com/jakubroztocil/httpie/issues/852
|
||||
.. _#870: https://github.com/jakubroztocil/httpie/issues/870
|
||||
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
||||
.. _#920: https://github.com/jakubroztocil/httpie/issues/920
|
||||
.. _#904: https://github.com/jakubroztocil/httpie/issues/904
|
||||
.. _#925: https://github.com/jakubroztocil/httpie/issues/925
|
||||
.. _#932: https://github.com/jakubroztocil/httpie/issues/932
|
||||
.. _#934: https://github.com/jakubroztocil/httpie/issues/934
|
||||
.. _#943: https://github.com/jakubroztocil/httpie/issues/943
|
||||
.. _#963: https://github.com/jakubroztocil/httpie/issues/963
|
||||
.. _#128: https://github.com/httpie/httpie/issues/128
|
||||
.. _#201: https://github.com/httpie/httpie/issues/201
|
||||
.. _#488: https://github.com/httpie/httpie/issues/488
|
||||
.. _#668: https://github.com/httpie/httpie/issues/668
|
||||
.. _#684: https://github.com/httpie/httpie/issues/684
|
||||
.. _#718: https://github.com/httpie/httpie/issues/718
|
||||
.. _#719: https://github.com/httpie/httpie/issues/719
|
||||
.. _#753: https://github.com/httpie/httpie/issues/753
|
||||
.. _#840: https://github.com/httpie/httpie/issues/840
|
||||
.. _#853: https://github.com/httpie/httpie/issues/853
|
||||
.. _#852: https://github.com/httpie/httpie/issues/852
|
||||
.. _#870: https://github.com/httpie/httpie/issues/870
|
||||
.. _#895: https://github.com/httpie/httpie/issues/895
|
||||
.. _#903: https://github.com/httpie/httpie/issues/903
|
||||
.. _#920: https://github.com/httpie/httpie/issues/920
|
||||
.. _#904: https://github.com/httpie/httpie/issues/904
|
||||
.. _#925: https://github.com/httpie/httpie/issues/925
|
||||
.. _#932: https://github.com/httpie/httpie/issues/932
|
||||
.. _#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
|
||||
|
@ -53,7 +53,7 @@ Development Environment
|
||||
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
|
||||
@ -132,7 +132,7 @@ Testing & CI
|
||||
Please add tests for any new features and bug fixes.
|
||||
|
||||
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 HTTPie’s `test suite`_ against your code
|
||||
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`_!
|
||||
|
||||
|
||||
.. _existing issues: https://github.com/jakubroztocil/httpie/issues?state=open
|
||||
.. _AUTHORS: https://github.com/jakubroztocil/httpie/blob/master/AUTHORS.rst
|
||||
.. _Makefile: https://github.com/jakubroztocil/httpie/blob/master/Makefile
|
||||
.. _existing issues: https://github.com/httpie/httpie/issues?state=open
|
||||
.. _AUTHORS: https://github.com/httpie/httpie/blob/master/AUTHORS.rst
|
||||
.. _Makefile: https://github.com/httpie/httpie/blob/master/Makefile
|
||||
.. _venv: https://docs.python.org/3/library/venv.html
|
||||
.. _pytest: https://pytest.org/
|
||||
.. _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
|
||||
|
@ -3,5 +3,5 @@ include README.rst
|
||||
include CHANGELOG.rst
|
||||
include AUTHORS.rst
|
||||
|
||||
# <https://github.com/jakubroztocil/httpie/issues/182>
|
||||
# <https://github.com/httpie/httpie/issues/182>
|
||||
recursive-include tests/ *
|
||||
|
15
Makefile
15
Makefile
@ -2,6 +2,8 @@
|
||||
# See ./CONTRIBUTING.rst
|
||||
###############################################################################
|
||||
|
||||
.PHONY: build
|
||||
|
||||
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
VERSION=$(shell grep __version__ httpie/__init__.py)
|
||||
REQUIREMENTS=requirements-dev.txt
|
||||
@ -111,6 +113,9 @@ test-bdist-wheel: clean venv
|
||||
@echo
|
||||
|
||||
|
||||
twine-check:
|
||||
twine check dist/*
|
||||
|
||||
pycodestyle:
|
||||
@echo $(H1)Running pycodestyle$(H1END)
|
||||
@[ -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
|
||||
|
||||
|
||||
@ -138,8 +148,9 @@ publish-no-test:
|
||||
@echo $(H1)Testing wheel build an installation$(H1END)
|
||||
@echo "$(VERSION)"
|
||||
@echo "$(VERSION)" | grep -q "dev" && echo '!!!Not publishing dev version!!!' && exit 1 || echo ok
|
||||
$(VENV_PYTHON) setup.py sdist bdist_wheel
|
||||
$(VENV_BIN)/twine upload dist/*
|
||||
make build
|
||||
make twine-check
|
||||
$(VENV_BIN)/twine upload --repository=httpie dist/*
|
||||
@echo
|
||||
|
||||
|
||||
|
271
README.rst
271
README.rst
@ -30,9 +30,11 @@ They use simple and natural syntax and provide formatted and colorized output.
|
||||
About this document
|
||||
===================
|
||||
|
||||
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
|
||||
This documentation is best viewed at `httpie.org/docs <https://httpie.org/docs>`_.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
`README.rst <https://github.com/httpie/httpie/blob/master/README.rst>`_.
|
||||
@ -117,6 +119,11 @@ system package manager, for example:
|
||||
# Arch Linux
|
||||
$ pacman -S httpie
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Solus
|
||||
$ eopkg install httpie
|
||||
|
||||
|
||||
Windows, etc.
|
||||
-------------
|
||||
@ -128,9 +135,9 @@ and always provides the latest version) is to use `pip`_:
|
||||
.. code-block:: bash
|
||||
|
||||
# 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
|
||||
@ -160,7 +167,8 @@ On macOS you can install it with Homebrew:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ brew install httpie --HEAD
|
||||
$ brew uninstall --force httpie
|
||||
$ brew install --HEAD httpie
|
||||
|
||||
|
||||
Otherwise with ``pip``:
|
||||
@ -189,7 +197,7 @@ Hello World:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http https://httpie.org/hello
|
||||
$ https httpie.io/hello
|
||||
|
||||
|
||||
Synopsis:
|
||||
@ -209,28 +217,28 @@ Custom `HTTP method`_, `HTTP headers`_ and `JSON`_ data:
|
||||
|
||||
.. 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`_:
|
||||
|
||||
.. 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`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http -v httpbin.org/get
|
||||
$ http -v pie.dev/get
|
||||
|
||||
|
||||
Build and print a request without sending it using `offline mode`_:
|
||||
|
||||
.. 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
|
||||
@ -246,21 +254,21 @@ Upload a file using `redirected input`_:
|
||||
|
||||
.. 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`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/image/png > image.png
|
||||
$ http pie.dev/image/png > image.png
|
||||
|
||||
|
||||
Download a file ``wget`` style:
|
||||
|
||||
.. 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
|
||||
between requests to the same host:
|
||||
@ -268,12 +276,12 @@ between requests to the same host:
|
||||
|
||||
.. 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
|
||||
|
||||
$ 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:
|
||||
@ -292,7 +300,7 @@ The name of the HTTP method comes right before the URL argument:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http DELETE httpbin.org/delete
|
||||
$ http DELETE pie.dev/delete
|
||||
|
||||
|
||||
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
|
||||
``@`` expect a file path as value.
|
||||
|
||||
+-----------------------+-----------------------------------------------------+
|
||||
| Item Type | Description |
|
||||
+=======================+=====================================================+
|
||||
| HTTP Headers | Arbitrary HTTP header, e.g. ``X-API-Token:123``. |
|
||||
| ``Name:Value`` | |
|
||||
+-----------------------+-----------------------------------------------------+
|
||||
| URL parameters | Appends the given name/value pair as a query |
|
||||
| ``name==value`` | string parameter to the URL. |
|
||||
| | The ``==`` separator is used. |
|
||||
+-----------------------+-----------------------------------------------------+
|
||||
| Data Fields | Request data fields to be serialized as a JSON |
|
||||
| ``field=value``, | object (default), to be form-encoded |
|
||||
| ``field=@file.txt`` | (with ``--form, -f``), or to be serialized as |
|
||||
| | ``multipart/form-data`` (with ``--multipart``). |
|
||||
+-----------------------+-----------------------------------------------------+
|
||||
| Raw JSON fields | Useful when sending JSON and one or |
|
||||
| ``field:=json``, | more fields need to be a ``Boolean``, ``Number``, |
|
||||
| ``field:=@file.json`` | nested ``Object``, or an ``Array``, e.g., |
|
||||
| | ``meals:='["ham","spam"]'`` or ``pies:=[1,2,3]`` |
|
||||
| | (note the quotes). |
|
||||
+-----------------------+-----------------------------------------------------+
|
||||
| Fields upload fields | Only available with ``--form, -f`` and |
|
||||
| ``field@/dir/file`` | ``--multipart``. |
|
||||
| ``field@file;type`` | For example ``screenshot@~/Pictures/img.png``, or |
|
||||
| | ``'cv@cv.txt;text/markdown'``. |
|
||||
| | With ``--form``, the presence of a file field |
|
||||
| | results in a ``--multipart`` request. |
|
||||
+-----------------------+-----------------------------------------------------+
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| Item Type | Description |
|
||||
+==============================+===================================================+
|
||||
| HTTP Headers | Arbitrary HTTP header, e.g. ``X-API-Token:123``. |
|
||||
| ``Name:Value`` | |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| URL parameters | Appends the given name/value pair as a query |
|
||||
| ``name==value`` | string parameter to the URL. |
|
||||
| | The ``==`` separator is used. |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| Data Fields | Request data fields to be serialized as a JSON |
|
||||
| ``field=value``, | object (default), to be form-encoded |
|
||||
| ``field=@file.txt`` | (with ``--form, -f``), or to be serialized as |
|
||||
| | ``multipart/form-data`` (with ``--multipart``). |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| Raw JSON fields | Useful when sending JSON and one or |
|
||||
| ``field:=json``, | more fields need to be a ``Boolean``, ``Number``, |
|
||||
| ``field:=@file.json`` | nested ``Object``, or an ``Array``, e.g., |
|
||||
| | ``meals:='["ham","spam"]'`` or ``pies:=[1,2,3]`` |
|
||||
| | (note the quotes). |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| Fields upload fields | Only available with ``--form, -f`` and |
|
||||
| ``field@/dir/file`` | ``--multipart``. |
|
||||
| ``field@file;type=mime`` | For example ``screenshot@~/Pictures/img.png``, or |
|
||||
| | ``'cv@cv.txt;type=text/markdown'``. |
|
||||
| | With ``--form``, the presence of a file field |
|
||||
| | results in a ``--multipart`` request. |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
|
||||
|
||||
Note that data fields aren’t the only way to specify request data:
|
||||
@ -510,7 +518,7 @@ token ``--`` to prevent confusion with ``--arguments``:
|
||||
|
||||
.. 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
|
||||
|
||||
@ -535,14 +543,15 @@ Simple example:
|
||||
|
||||
.. 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
|
||||
|
||||
PUT / HTTP/1.1
|
||||
Accept: application/json, */*;q=0.5
|
||||
Accept-Encoding: gzip, deflate
|
||||
Content-Type: application/json
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
|
||||
{
|
||||
"name": "John",
|
||||
@ -585,7 +594,7 @@ fields using ``=@`` and ``:=@``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http PUT httpbin.org/put \
|
||||
$ http PUT pie.dev/put \
|
||||
name=John \ # String (default)
|
||||
age:=29 \ # Raw JSON — Number
|
||||
married:=false \ # Raw JSON — Boolean
|
||||
@ -600,7 +609,7 @@ fields using ``=@`` and ``:=@``:
|
||||
PUT /person/1 HTTP/1.1
|
||||
Accept: application/json, */*;q=0.5
|
||||
Content-Type: application/json
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
|
||||
{
|
||||
"age": 29,
|
||||
@ -630,11 +639,11 @@ In such cases, it’s better to pass the full raw JSON data via
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ echo '{"hello": "world"}' | http POST httpbin.org/post
|
||||
$ echo '{"hello": "world"}' | http POST pie.dev/post
|
||||
|
||||
.. 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.
|
||||
Here, again, the solution is to use `redirected input`_.
|
||||
@ -655,7 +664,7 @@ Regular forms
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --form POST httpbin.org/post name='John Smith'
|
||||
$ http --form POST pie.dev/post name='John Smith'
|
||||
|
||||
|
||||
.. code-block:: http
|
||||
@ -674,7 +683,7 @@ If one or more file fields is present, the serialization and content type is
|
||||
|
||||
.. 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
|
||||
@ -695,7 +704,7 @@ override the inferred content type:
|
||||
|
||||
.. 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
|
||||
``--multipart`` instead of ``--form``:
|
||||
@ -769,7 +778,7 @@ To set custom headers you can use the ``Header:Value`` notation:
|
||||
|
||||
.. 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/
|
||||
|
||||
|
||||
@ -779,7 +788,7 @@ To set custom headers you can use the ``Header:Value`` notation:
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip, deflate
|
||||
Cookie: valued-visitor=yes;foo=bar
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
Referer: https://httpie.org/
|
||||
User-Agent: Bacon/1.0
|
||||
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
|
||||
|
||||
$ http httpbin.org/headers Accept: User-Agent:
|
||||
$ http pie.dev/headers Accept: User-Agent:
|
||||
|
||||
|
||||
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
|
||||
|
||||
$ http httpbin.org/headers 'Header;'
|
||||
$ http pie.dev/headers 'Header;'
|
||||
|
||||
|
||||
Limiting response headers
|
||||
@ -833,7 +842,7 @@ HTTPie reads before giving up (the default ``0``, i.e., there’s no limit).
|
||||
|
||||
.. 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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”.
|
||||
@ -893,7 +902,7 @@ Send a single cookie:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/cookies Cookie:sessionid=foo
|
||||
$ http pie.dev/cookies Cookie:sessionid=foo
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@ -902,7 +911,7 @@ Send a single cookie:
|
||||
Accept-Encoding: gzip, deflate
|
||||
Connection: keep-alive
|
||||
Cookie: sessionid=foo
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
User-Agent: HTTPie/0.9.9
|
||||
|
||||
|
||||
@ -911,7 +920,7 @@ Send multiple cookies
|
||||
|
||||
.. 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
|
||||
|
||||
@ -920,7 +929,7 @@ Send multiple cookies
|
||||
Accept-Encoding: gzip, deflate
|
||||
Connection: keep-alive
|
||||
Cookie: sessionid=foo;another-cookie=bar
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
User-Agent: HTTPie/0.9.9
|
||||
|
||||
|
||||
@ -957,7 +966,7 @@ Basic auth
|
||||
|
||||
.. 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
|
||||
@ -966,7 +975,7 @@ Digest auth
|
||||
|
||||
.. 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
|
||||
@ -974,7 +983,7 @@ Password prompt
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http -a username httpbin.org/basic-auth/username/password
|
||||
$ http -a username pie.dev/basic-auth/username/password
|
||||
|
||||
|
||||
Empty password
|
||||
@ -982,7 +991,7 @@ Empty password
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http -a username: httpbin.org/headers
|
||||
$ http -a username: pie.dev/headers
|
||||
|
||||
|
||||
``.netrc``
|
||||
@ -996,13 +1005,13 @@ For example:
|
||||
.. code-block:: bash
|
||||
|
||||
$ cat ~/.netrc
|
||||
machine httpbin.org
|
||||
machine pie.dev
|
||||
login httpie
|
||||
password test
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/basic-auth/httpie/test
|
||||
$ http pie.dev/basic-auth/httpie/test
|
||||
HTTP/1.1 200 OK
|
||||
[...]
|
||||
|
||||
@ -1010,7 +1019,7 @@ This can be disabled with the ``--ignore-netrc`` option:
|
||||
|
||||
.. 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
|
||||
[...]
|
||||
|
||||
@ -1044,7 +1053,7 @@ response is shown:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/redirect/3
|
||||
$ http pie.dev/redirect/3
|
||||
|
||||
|
||||
Follow ``Location``
|
||||
@ -1056,7 +1065,7 @@ and show the final response instead, use the ``--follow, -F`` option:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --follow httpbin.org/redirect/3
|
||||
$ http --follow pie.dev/redirect/3
|
||||
|
||||
|
||||
Showing intermediary redirect responses
|
||||
@ -1068,7 +1077,7 @@ then use the ``--all`` option as well:
|
||||
|
||||
.. 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
|
||||
|
||||
$ http --follow --all --max-redirects=2 httpbin.org/redirect/3
|
||||
$ http --follow --all --max-redirects=2 pie.dev/redirect/3
|
||||
|
||||
|
||||
Proxies
|
||||
@ -1141,7 +1150,7 @@ To skip the host’s SSL certificate verification, you can pass ``--verify=no``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --verify=no https://httpbin.org/get
|
||||
$ http --verify=no https://pie.dev/get
|
||||
|
||||
|
||||
Custom CA bundle
|
||||
@ -1200,7 +1209,7 @@ It should be a string in the
|
||||
|
||||
.. 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,
|
||||
they only affect the list of available cipher suites.
|
||||
@ -1247,7 +1256,7 @@ Print request and response headers:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --print=Hh PUT httpbin.org/put hello=world
|
||||
$ http --print=Hh PUT pie.dev/put hello=world
|
||||
|
||||
Verbose output
|
||||
--------------
|
||||
@ -1257,12 +1266,12 @@ documentation examples:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --verbose PUT httpbin.org/put hello=world
|
||||
$ http --verbose PUT pie.dev/put hello=world
|
||||
PUT /put HTTP/1.1
|
||||
Accept: application/json, */*;q=0.5
|
||||
Accept-Encoding: gzip, deflate
|
||||
Content-Type: application/json
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
User-Agent: HTTPie/0.2.7dev
|
||||
|
||||
{
|
||||
@ -1285,13 +1294,13 @@ Quiet output
|
||||
------------
|
||||
|
||||
``--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 doesn’t affect output to a file via ``--output`` or ``--download``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# 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
|
||||
@ -1306,7 +1315,7 @@ authentication is used (``--auth=digest``), etc.
|
||||
.. code-block:: bash
|
||||
|
||||
# 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
|
||||
@ -1318,7 +1327,7 @@ arguments as ``--print, -p`` but applies to the intermediary requests only.
|
||||
.. code-block:: bash
|
||||
|
||||
# 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
|
||||
@ -1334,7 +1343,7 @@ status code after an update:
|
||||
|
||||
.. 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
|
||||
@ -1361,49 +1370,49 @@ Redirect from a file:
|
||||
|
||||
.. 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:
|
||||
|
||||
.. 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:
|
||||
|
||||
.. 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*:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/post <<<'{"name": "John"}'
|
||||
$ http pie.dev/post <<<'{"name": "John"}'
|
||||
|
||||
|
||||
You can even pipe web services together using HTTPie:
|
||||
|
||||
.. 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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ cat | http POST httpbin.org/post
|
||||
$ cat | http POST pie.dev/post
|
||||
<paste>
|
||||
^D
|
||||
|
||||
|
||||
.. 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
|
||||
- call parents
|
||||
^D
|
||||
@ -1413,7 +1422,7 @@ On OS X, you can send the contents of the clipboard with ``pbpaste``:
|
||||
|
||||
.. 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
|
||||
@ -1442,7 +1451,7 @@ verbatim contents of that XML file with ``Content-Type: application/xml``:
|
||||
|
||||
.. 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.
|
||||
|
||||
@ -1456,19 +1465,19 @@ You can use the ``--chunked`` flag to instruct HTTPie to use
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --chunked PUT httpbin.org/put hello=world
|
||||
$ http --chunked PUT pie.dev/put hello=world
|
||||
|
||||
.. 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
|
||||
|
||||
$ http --chunked httpbin.org/post @files/data.xml
|
||||
$ http --chunked pie.dev/post @files/data.xml
|
||||
|
||||
.. 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
|
||||
|
||||
$ 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
|
||||
`config`_ file. See ``http --help`` for all the available formatting options.
|
||||
@ -1542,7 +1551,7 @@ that the response body is binary,
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/bytes/2000
|
||||
$ http pie.dev/bytes/2000
|
||||
|
||||
|
||||
You will nearly instantly see something like this:
|
||||
@ -1575,7 +1584,7 @@ Download a file:
|
||||
|
||||
.. 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:
|
||||
@ -1590,7 +1599,7 @@ Force colorizing and formatting, and show both the request and the response in
|
||||
|
||||
.. 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
|
||||
@ -1719,17 +1728,15 @@ Prettified streamed response:
|
||||
|
||||
.. 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``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Send each new tweet (JSON object) mentioning "Apple" to another
|
||||
# server as soon as it arrives from the Twitter streaming API:
|
||||
$ 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
|
||||
# Send each new line (JSON object) to another URL as soon as it arrives from a streaming API:
|
||||
$ http --stream pie.dev/stream/3 | while read line; do echo "$line" | http pie.dev/post ; done
|
||||
|
||||
Sessions
|
||||
========
|
||||
@ -1749,7 +1756,7 @@ to the same host.
|
||||
.. code-block:: bash
|
||||
|
||||
# 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
|
||||
@ -1760,7 +1767,7 @@ to the same host.
|
||||
.. code-block:: bash
|
||||
|
||||
# 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,
|
||||
@ -1775,11 +1782,11 @@ Named sessions
|
||||
|
||||
|
||||
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
|
||||
|
||||
$ 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
|
||||
to use the session again, any previously specified authentication or HTTP headers
|
||||
@ -1787,13 +1794,13 @@ will automatically be set:
|
||||
|
||||
.. 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:
|
||||
|
||||
.. 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 sessions’s data is stored in JSON files inside the ``sessions``
|
||||
subdirectory of the `config`_ directory, typically:
|
||||
@ -1806,7 +1813,7 @@ you should be able list the generated sessions files using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ls -l ~/.config/httpie/sessions/httpbin.org
|
||||
$ ls -l ~/.config/httpie/sessions/pie.dev
|
||||
|
||||
|
||||
Anonymous sessions
|
||||
@ -1848,12 +1855,12 @@ exchange after it has been created, specify the session name via
|
||||
.. code-block:: bash
|
||||
|
||||
# If the session file doesn’t 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
|
||||
|
||||
# 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
|
||||
------------------------
|
||||
@ -1866,13 +1873,13 @@ To set a cookie within a Session there are three options:
|
||||
|
||||
.. 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`_
|
||||
|
||||
.. 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
|
||||
|
||||
@ -1937,7 +1944,7 @@ environment variable:
|
||||
.. code-block:: bash
|
||||
|
||||
$ export HTTPIE_CONFIG_DIR=/tmp/httpie
|
||||
$ http httpbin.org/get
|
||||
$ http pie.dev/get
|
||||
|
||||
|
||||
|
||||
@ -1998,7 +2005,7 @@ respectively.
|
||||
|
||||
#!/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!'
|
||||
else
|
||||
case $? in
|
||||
@ -2047,7 +2054,7 @@ HTTP request:
|
||||
.. code-block:: http
|
||||
|
||||
POST /post HTTP/1.1
|
||||
Host: httpbin.org
|
||||
Host: pie.dev
|
||||
X-API-Key: 123
|
||||
User-Agent: Bacon/1.0
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
@ -2059,7 +2066,7 @@ with the HTTPie command that sends it:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http -f POST httpbin.org/post \
|
||||
$ http -f POST pie.dev/post \
|
||||
X-API-Key:123 \
|
||||
User-Agent:Bacon/1.0 \
|
||||
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>`_
|
||||
for bug reports and feature requests.
|
||||
* `Our Gitter chat room <https://gitter.im/jkbrzt/httpie>`_
|
||||
to ask questions, discuss features, and for general discussion.
|
||||
* `Discord server <https://httpie.io/chat>`_
|
||||
to ask questions, discuss features, and for general API development discussion.
|
||||
* `StackOverflow <https://stackoverflow.com>`_
|
||||
to ask questions (please make sure to use the
|
||||
`httpie <https://stackoverflow.com/questions/tagged/httpie>`_ tag).
|
||||
@ -2195,9 +2202,9 @@ have contributed.
|
||||
:target: https://github.com/httpie/httpie/actions
|
||||
: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
|
||||
:target: https://gitter.im/jkbrzt/httpie
|
||||
:alt: Chat on Gitter
|
||||
.. |gitter| image:: https://img.shields.io/badge/chat-on%20Discord-brightgreen?style=flat-square
|
||||
:target: https://httpie.io/chat
|
||||
:alt: Chat on Discord
|
||||
|
||||
.. |downloads| image:: https://pepy.tech/badge/httpie
|
||||
:target: https://pepy.tech/project/httpie
|
||||
|
@ -9,22 +9,20 @@ class Httpie < Formula
|
||||
|
||||
desc "User-friendly cURL replacement (command-line HTTP client)"
|
||||
homepage "https://httpie.org/"
|
||||
url "https://files.pythonhosted.org/packages/37/6c/0d050f49e3b2bac589367d0c3aee9c078e23c6914b0210ffc0117218bdaf/httpie-2.2.0.tar.gz"
|
||||
sha256 "31ac28088ee6a0b6f3ba7a53379000c4d1910c1708c9ff768f84b111c14405a0"
|
||||
head "https://github.com/jakubroztocil/httpie.git"
|
||||
url "https://files.pythonhosted.org/packages/b4/d4/712645808103f2d15c281b9eacd184c88754ef7e9a322d9a30ba343fd341/httpie-2.3.0.tar.gz"
|
||||
sha256 "d540571991d07329d217c31bf1ff95fd217957da2aa2def09bcfa0c0fca0cf96"
|
||||
license "BSD-3-Clause"
|
||||
head "https://github.com/httpie/httpie.git"
|
||||
|
||||
bottle do
|
||||
cellar :any_skip_relocation
|
||||
sha256 "25f0e58f81a2cdd9cba772f07d67591533b4b31a2b970a356701aa046d4d9638" => :catalina
|
||||
sha256 "be158ebb4cfd327ebea02f7b8b8d63d093e474cd303eafff4a2b56b0611983a2" => :mojave
|
||||
sha256 "f331edb94183bfc5fa9de4b4abf148cc91a3a8b3c0e24cc1f5e6b0a4172dd34d" => :high_sierra
|
||||
livecheck do
|
||||
url :stable
|
||||
end
|
||||
|
||||
depends_on "python@3.8"
|
||||
depends_on "python@3.9"
|
||||
|
||||
resource "Pygments" do
|
||||
url "https://files.pythonhosted.org/packages/6e/4d/4d2fe93a35dfba417311a4ff627489a947b01dc0cc377a3673c00cf7e4b2/Pygments-2.6.1.tar.gz"
|
||||
sha256 "647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"
|
||||
url "https://files.pythonhosted.org/packages/5d/0e/ff13c055b014d634ed17e9e9345a312c28ec6a06448ba6d6ccfa77c3b5e8/Pygments-2.7.2.tar.gz"
|
||||
sha256 "381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"
|
||||
end
|
||||
|
||||
resource "requests" do
|
||||
@ -32,19 +30,24 @@ class Httpie < Formula
|
||||
sha256 "b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"
|
||||
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
|
||||
url "https://files.pythonhosted.org/packages/b4/19/53433f37a31543364c8676f30b291d128cdf4cd5b31b755b7890f8e89ac8/certifi-2020.4.5.2.tar.gz"
|
||||
sha256 "5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"
|
||||
url "https://files.pythonhosted.org/packages/40/a7/ded59fa294b85ca206082306bba75469a38ea1c7d44ea7e1d64f5443d67a/certifi-2020.6.20.tar.gz"
|
||||
sha256 "5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"
|
||||
end
|
||||
|
||||
resource "urllib3" do
|
||||
url "https://files.pythonhosted.org/packages/05/8c/40cd6949373e23081b3ea20d5594ae523e681b6f472e600fbc95ed046a36/urllib3-1.25.9.tar.gz"
|
||||
sha256 "3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"
|
||||
url "https://files.pythonhosted.org/packages/76/d9/bbbafc76b18da706451fa91bc2ebe21c0daf8868ef3c30b869ac7cb7f01d/urllib3-1.25.11.tar.gz"
|
||||
sha256 "8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"
|
||||
end
|
||||
|
||||
resource "idna" do
|
||||
url "https://files.pythonhosted.org/packages/cb/19/57503b5de719ee45e83472f339f617b0c01ad75cba44aba1e4c97c2b0abd/idna-2.9.tar.gz"
|
||||
sha256 "7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"
|
||||
url "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz"
|
||||
sha256 "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
|
||||
end
|
||||
|
||||
resource "chardet" do
|
||||
|
@ -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'
|
||||
__licence__ = 'BSD'
|
||||
|
@ -5,15 +5,6 @@ import enum
|
||||
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)
|
||||
|
||||
HTTP_POST = 'POST'
|
||||
|
@ -37,7 +37,7 @@ parser = HTTPieArgumentParser(
|
||||
|
||||
Suggestions and bug reports are greatly appreciated:
|
||||
|
||||
https://github.com/jakubroztocil/httpie/issues
|
||||
https://github.com/httpie/httpie/issues
|
||||
|
||||
'''),
|
||||
)
|
||||
|
@ -1,5 +1,4 @@
|
||||
import os
|
||||
from io import BytesIO
|
||||
from typing import Callable, Dict, IO, List, Optional, Tuple, Union
|
||||
|
||||
from httpie.cli.argtypes import KeyValueArg
|
||||
|
@ -134,7 +134,7 @@ def collect_messages(
|
||||
# noinspection PyProtectedMember
|
||||
@contextmanager
|
||||
def max_headers(limit):
|
||||
# <https://github.com/jakubroztocil/httpie/issues/802>
|
||||
# <https://github.com/httpie/httpie/issues/802>
|
||||
# noinspection PyUnresolvedReferences
|
||||
orig = http.client._MAXHEADERS
|
||||
http.client._MAXHEADERS = limit or float('Inf')
|
||||
@ -188,7 +188,7 @@ def finalize_headers(headers: RequestHeadersDict) -> RequestHeadersDict:
|
||||
# Also, requests raises `InvalidHeader` for leading spaces.
|
||||
value = value.strip()
|
||||
if isinstance(value, str):
|
||||
# See <https://github.com/jakubroztocil/httpie/issues/212>
|
||||
# See <https://github.com/httpie/httpie/issues/212>
|
||||
value = value.encode('utf8')
|
||||
final_headers[name] = value
|
||||
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
|
||||
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>
|
||||
|
147
httpie/core.py
147
httpie/core.py
@ -9,26 +9,17 @@ from pygments import __version__ as pygments_version
|
||||
from requests import __version__ as requests_version
|
||||
|
||||
from httpie import __version__ as httpie_version
|
||||
from httpie.cli.constants import (
|
||||
OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY,
|
||||
OUT_RESP_HEAD,
|
||||
)
|
||||
from httpie.cli.constants import OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY, OUT_RESP_HEAD
|
||||
from httpie.client import collect_messages
|
||||
from httpie.context import Environment
|
||||
from httpie.downloads import Downloader
|
||||
from httpie.output.writer import (
|
||||
write_message,
|
||||
write_stream,
|
||||
)
|
||||
from httpie.output.writer import write_message, write_stream, MESSAGE_SEPARATOR_BYTES
|
||||
from httpie.plugins.registry import plugin_manager
|
||||
from httpie.status import ExitStatus, http_status_to_exit_status
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
def main(
|
||||
args: List[Union[str, bytes]] = sys.argv,
|
||||
env=Environment(),
|
||||
) -> ExitStatus:
|
||||
def main(args: List[Union[str, bytes]] = sys.argv, env=Environment()) -> ExitStatus:
|
||||
"""
|
||||
The main function.
|
||||
|
||||
@ -134,112 +125,82 @@ def get_output_options(
|
||||
}[type(message)]
|
||||
|
||||
|
||||
def program(
|
||||
args: argparse.Namespace,
|
||||
env: Environment,
|
||||
) -> ExitStatus:
|
||||
def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
||||
"""
|
||||
The main program without error handling.
|
||||
|
||||
"""
|
||||
# TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere.
|
||||
exit_status = ExitStatus.SUCCESS
|
||||
downloader = None
|
||||
initial_request: Optional[requests.PreparedRequest] = 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):
|
||||
should_pipe_to_stdout = bool(
|
||||
# Request body output desired
|
||||
OUT_REQ_BODY in args.output_options
|
||||
# & not `.read()` already pre-request (e.g., for compression)
|
||||
and initial_request
|
||||
# & non-EOF chunk
|
||||
and chunk
|
||||
)
|
||||
if should_pipe_to_stdout:
|
||||
msg = requests.PreparedRequest()
|
||||
msg.is_body_upload_chunk = True
|
||||
msg.body = chunk
|
||||
msg.headers = initial_request.headers
|
||||
write_message(requests_message=msg, env=env, args=args, with_body=True, with_headers=False)
|
||||
|
||||
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 = 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
|
||||
|
||||
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
|
||||
final_response: Optional[requests.Response] = None
|
||||
|
||||
def request_body_read_callback(chunk: bytes):
|
||||
should_pipe_to_stdout = (
|
||||
# Request body output desired
|
||||
OUT_REQ_BODY in args.output_options
|
||||
# & not `.read()` already pre-request (e.g., for compression)
|
||||
and initial_request
|
||||
# & non-EOF chunk
|
||||
and chunk
|
||||
)
|
||||
if should_pipe_to_stdout:
|
||||
msg = requests.PreparedRequest()
|
||||
msg.is_body_upload_chunk = True
|
||||
msg.body = chunk
|
||||
msg.headers = initial_request.headers
|
||||
write_message(
|
||||
requests_message=msg,
|
||||
env=env,
|
||||
args=args,
|
||||
with_body=True,
|
||||
with_headers=False
|
||||
)
|
||||
|
||||
messages = collect_messages(
|
||||
args=args,
|
||||
config_dir=env.config.directory,
|
||||
request_body_read_callback=request_body_read_callback
|
||||
)
|
||||
# Process messages as they’re generated
|
||||
for message in messages:
|
||||
maybe_separate()
|
||||
is_request = isinstance(message, requests.PreparedRequest)
|
||||
with_headers, with_body = get_output_options(
|
||||
args=args, message=message)
|
||||
with_headers, with_body = get_output_options(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 not initial_request:
|
||||
initial_request = message
|
||||
is_streamed_upload = not isinstance(
|
||||
message.body, (str, bytes))
|
||||
is_streamed_upload = not isinstance(message.body, (str, bytes))
|
||||
if with_body:
|
||||
with_body = not is_streamed_upload
|
||||
needs_separator = is_streamed_upload
|
||||
do_write_body = not is_streamed_upload
|
||||
force_separator = is_streamed_upload and env.stdout_isatty
|
||||
else:
|
||||
final_response = message
|
||||
if args.check_status or downloader:
|
||||
exit_status = http_status_to_exit_status(
|
||||
http_status=message.status_code,
|
||||
follow=args.follow
|
||||
)
|
||||
if (not env.stdout_isatty
|
||||
and exit_status != ExitStatus.SUCCESS):
|
||||
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()
|
||||
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
||||
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet):
|
||||
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=do_write_body)
|
||||
prev_with_body = with_body
|
||||
|
||||
# Cleanup
|
||||
if force_separator:
|
||||
separate()
|
||||
if downloader and exit_status == ExitStatus.SUCCESS:
|
||||
# Last response body download.
|
||||
download_stream, download_to = downloader.start(
|
||||
initial_url=initial_request.url,
|
||||
final_response=final_response,
|
||||
)
|
||||
write_stream(
|
||||
stream=download_stream,
|
||||
outfile=download_to,
|
||||
flush=False,
|
||||
)
|
||||
write_stream(stream=download_stream, outfile=download_to, flush=False)
|
||||
downloader.finish()
|
||||
if downloader.interrupted:
|
||||
exit_status = ExitStatus.ERROR
|
||||
@ -253,9 +214,7 @@ def program(
|
||||
finally:
|
||||
if downloader and not downloader.finished:
|
||||
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()
|
||||
|
||||
|
||||
@ -283,6 +242,6 @@ def decode_raw_args(
|
||||
"""
|
||||
return [
|
||||
arg.decode(stdin_encoding)
|
||||
if type(arg) == bytes else arg
|
||||
if type(arg) is bytes else arg
|
||||
for arg in args
|
||||
]
|
||||
|
@ -247,7 +247,7 @@ class Downloader:
|
||||
assert not self.status.time_started
|
||||
|
||||
# FIXME: some servers still might sent Content-Encoding: gzip
|
||||
# <https://github.com/jakubroztocil/httpie/issues/423>
|
||||
# <https://github.com/httpie/httpie/issues/423>
|
||||
try:
|
||||
total_size = int(final_response.headers['Content-Length'])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
|
@ -12,6 +12,10 @@ from httpie.output.streams import (
|
||||
)
|
||||
|
||||
|
||||
MESSAGE_SEPARATOR = '\n\n'
|
||||
MESSAGE_SEPARATOR_BYTES = MESSAGE_SEPARATOR.encode()
|
||||
|
||||
|
||||
def write_message(
|
||||
requests_message: Union[requests.PreparedRequest, requests.Response],
|
||||
env: Environment,
|
||||
@ -111,7 +115,7 @@ def build_output_stream_for_message(
|
||||
and not getattr(requests_message, 'is_body_upload_chunk', False)):
|
||||
# Ensure a blank line after the response body.
|
||||
# For terminal output only.
|
||||
yield b'\n\n'
|
||||
yield MESSAGE_SEPARATOR_BYTES
|
||||
|
||||
|
||||
def get_stream_type_and_kwargs(
|
||||
|
@ -19,7 +19,7 @@ class HTTPBasicAuth(requests.auth.HTTPBasicAuth):
|
||||
"""
|
||||
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
|
||||
|
@ -77,7 +77,9 @@ class Session(BaseConfigDict):
|
||||
if value is None:
|
||||
continue # Ignore explicitly unset headers
|
||||
|
||||
value = value.decode('utf8')
|
||||
if type(value) is not str:
|
||||
value = value.decode('utf8')
|
||||
|
||||
if name.lower() == 'user-agent' and value.startswith('HTTPie/'):
|
||||
continue
|
||||
|
||||
|
@ -109,6 +109,8 @@ def get_expired_cookies(
|
||||
for attrs in attr_sets
|
||||
]
|
||||
|
||||
_max_age_to_expires(cookies=cookies, now=now)
|
||||
|
||||
return [
|
||||
{
|
||||
'name': cookie['name'],
|
||||
@ -117,3 +119,18 @@ def get_expired_cookies(
|
||||
for cookie in cookies
|
||||
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)
|
||||
|
@ -4,7 +4,7 @@
|
||||
[tool:pytest]
|
||||
# <https://docs.pytest.org/en/latest/customize.html>
|
||||
norecursedirs = tests/fixtures
|
||||
addopts = --tb=native
|
||||
addopts = --tb=native --doctest-modules
|
||||
|
||||
|
||||
[pycodestyle]
|
||||
|
17
setup.py
17
setup.py
@ -15,11 +15,14 @@ class PyTest(TestCommand):
|
||||
and runs the tests with no fancy stuff like parallel execution.
|
||||
|
||||
"""
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.test_args = [
|
||||
'--doctest-modules', '--verbose',
|
||||
'./httpie', './tests'
|
||||
'--doctest-modules',
|
||||
'--verbose',
|
||||
'./httpie',
|
||||
'./tests',
|
||||
]
|
||||
self.test_suite = True
|
||||
|
||||
@ -71,8 +74,9 @@ setup(
|
||||
version=httpie.__version__,
|
||||
description=httpie.__doc__.strip(),
|
||||
long_description=long_description(),
|
||||
long_description_content_type='text/x-rst',
|
||||
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_email='jakub@roztocil.co',
|
||||
license=httpie.__licence__,
|
||||
@ -104,10 +108,9 @@ setup(
|
||||
'Topic :: Utilities'
|
||||
],
|
||||
project_urls={
|
||||
'Documentation': 'https://httpie.org/docs',
|
||||
'Source': 'https://github.com/jakubroztocil/httpie',
|
||||
'Online Demo': 'https://httpie.org/run',
|
||||
'Donate': 'https://httpie.org/donate',
|
||||
'GitHub': 'https://github.com/httpie/httpie',
|
||||
'Twitter': 'https://twitter.com/httpie',
|
||||
'Documentation': 'https://httpie.org/docs',
|
||||
'Online Demo': 'https://httpie.org/run',
|
||||
},
|
||||
)
|
||||
|
@ -5,4 +5,4 @@ HTTPie Test Suite
|
||||
Please see `CONTRIBUTING`_.
|
||||
|
||||
|
||||
.. _CONTRIBUTING: https://github.com/jakubroztocil/httpie/blob/master/CONTRIBUTING.rst
|
||||
.. _CONTRIBUTING: https://github.com/httpie/httpie/blob/master/CONTRIBUTING.rst
|
||||
|
@ -58,7 +58,7 @@ def test_credentials_in_url_auth_flag_has_priority(httpbin_both):
|
||||
])
|
||||
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())
|
||||
|
@ -11,7 +11,7 @@ from fixtures import FILE_PATH
|
||||
|
||||
def test_default_headers_case_insensitive(httpbin):
|
||||
"""
|
||||
<https://github.com/jakubroztocil/httpie/issues/644>
|
||||
<https://github.com/httpie/httpie/issues/644>
|
||||
"""
|
||||
r = http(
|
||||
'--debug',
|
||||
@ -63,7 +63,7 @@ class TestAutoContentTypeAndAcceptHeaders:
|
||||
"""
|
||||
|
||||
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')
|
||||
assert HTTP_OK in r
|
||||
assert r.json['headers']['Accept'] == '*/*'
|
||||
@ -94,7 +94,7 @@ class TestAutoContentTypeAndAcceptHeaders:
|
||||
assert HTTP_OK in r
|
||||
assert r.json['headers']['Accept'] == JSON_ACCEPT
|
||||
# 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']
|
||||
|
||||
def test_GET_explicit_JSON_explicit_headers(self, httpbin):
|
||||
|
@ -5,7 +5,6 @@ from urllib.request import urlopen
|
||||
|
||||
import pytest
|
||||
import mock
|
||||
import requests
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
from httpie.downloads import (
|
||||
|
@ -39,7 +39,7 @@ def test_debug():
|
||||
def test_help():
|
||||
r = http('--help', tolerate_error_exit_status=True)
|
||||
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():
|
||||
@ -121,7 +121,7 @@ def test_POST_file(httpbin_both):
|
||||
|
||||
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:
|
||||
@ -182,4 +182,4 @@ def test_json_input_preserve_order(httpbin_both):
|
||||
'order:={"map":{"1":"first","2":"second"}}')
|
||||
assert HTTP_OK in r
|
||||
assert r.json['data'] == \
|
||||
'{"order": {"map": {"1": "first", "2": "second"}}}'
|
||||
'{"order": {"map": {"1": "first", "2": "second"}}}'
|
||||
|
@ -54,6 +54,21 @@ class TestQuietFlag:
|
||||
assert r == ''
|
||||
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',
|
||||
new=lambda self, prompt: 'password')
|
||||
def test_quiet_with_password_prompt(self, httpbin):
|
||||
@ -127,7 +142,7 @@ class TestVerboseFlag:
|
||||
assert r.count('__test__') == 2
|
||||
|
||||
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',
|
||||
'A=B', 'C=D')
|
||||
assert HTTP_OK in r
|
||||
|
@ -1,16 +1,17 @@
|
||||
"""Miscellaneous regression tests"""
|
||||
import pytest
|
||||
|
||||
from utils import http, HTTP_OK
|
||||
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):
|
||||
"""
|
||||
https://github.com/jakubroztocil/httpie/issues/235
|
||||
https://github.com/httpie/httpie/issues/235
|
||||
|
||||
"""
|
||||
host = 'httpbin.org'
|
||||
host = 'pie.dev'
|
||||
url = httpbin.url + '/get'
|
||||
r = http('--print=hH', url, 'host:{0}'.format(host))
|
||||
assert HTTP_OK in r
|
||||
@ -21,7 +22,28 @@ def test_Host_header_overwrite(httpbin):
|
||||
@pytest.mark.skipif(is_windows, reason='Unix-only')
|
||||
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')
|
||||
|
||||
|
||||
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,
|
||||
])
|
||||
|
@ -16,6 +16,7 @@ from httpie.sessions import Session
|
||||
from httpie.utils import get_expired_cookies
|
||||
from tests.test_auth_plugins import basic_auth
|
||||
from utils import HTTP_OK, MockEnvironment, http, mk_config_dir
|
||||
from fixtures import FILE_PATH_ARG
|
||||
|
||||
|
||||
class SessionTestBase:
|
||||
@ -161,6 +162,12 @@ class TestSession(SessionTestBase):
|
||||
assert 'Content-Type' 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):
|
||||
self.start_session(httpbin)
|
||||
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):
|
||||
self.start_session(httpbin)
|
||||
# https://github.com/jakubroztocil/httpie/issues/180
|
||||
# https://github.com/httpie/httpie/issues/180
|
||||
r1 = http('--session=test',
|
||||
httpbin.url + '/headers', 'User-Agent:custom',
|
||||
env=self.env())
|
||||
@ -205,7 +212,7 @@ class TestSession(SessionTestBase):
|
||||
assert r2.json['headers']['User-Agent'] == 'custom'
|
||||
|
||||
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)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(gettempdir())
|
||||
@ -338,6 +345,15 @@ class TestExpiredCookies(CookieTestBase):
|
||||
assert 'cookie1' 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(
|
||||
argnames=['headers', 'now', 'expected_expired'],
|
||||
argvalues=[
|
||||
|
@ -11,7 +11,7 @@ from utils import HTTP_OK, TESTS_ROOT, http
|
||||
|
||||
try:
|
||||
# Handle OpenSSL errors, if installed.
|
||||
# See <https://github.com/jakubroztocil/httpie/issues/729>
|
||||
# See <https://github.com/httpie/httpie/issues/729>
|
||||
# noinspection PyUnresolvedReferences
|
||||
import OpenSSL.SSL
|
||||
ssl_errors = (
|
||||
|
141
tests/test_tokens.py
Normal file
141
tests/test_tokens.py
Normal 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)
|
@ -52,6 +52,23 @@ def test_chunked_stdin():
|
||||
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:
|
||||
|
||||
def test_non_existent_file_raises_parse_error(self, httpbin):
|
||||
|
@ -1,12 +1,14 @@
|
||||
# coding=utf-8
|
||||
"""Utilities for HTTPie test suite."""
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import tempfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union, List
|
||||
|
||||
from httpie.status import ExitStatus
|
||||
from httpie.config import Config
|
||||
@ -17,10 +19,10 @@ from httpie.core import main
|
||||
# pytest-httpbin currently does not support chunked requests:
|
||||
# <https://github.com/kevin1024/pytest-httpbin/issues/33>
|
||||
# <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'
|
||||
COLOR = '\x1b['
|
||||
HTTP_OK = '200 OK'
|
||||
@ -49,7 +51,7 @@ class StdinBytesIO(BytesIO):
|
||||
|
||||
class MockEnvironment(Environment):
|
||||
"""Environment subclass with reasonable defaults for testing."""
|
||||
colors = 0
|
||||
colors = 0 # For easier debugging
|
||||
stdin_isatty = True,
|
||||
stdout_isatty = True
|
||||
is_windows = False
|
||||
@ -113,6 +115,15 @@ class BaseCLIResponse:
|
||||
devnull: str = None
|
||||
json: dict = 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):
|
||||
@ -198,7 +209,7 @@ def http(
|
||||
|
||||
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')
|
||||
>>> r = http('-a', 'user:pw', httpbin.url + '/basic-auth/user/pw')
|
||||
@ -284,10 +295,13 @@ def http(
|
||||
r.devnull = devnull_output
|
||||
r.stderr = stderr.read()
|
||||
r.exit_status = exit_status
|
||||
r.args = args
|
||||
r.complete_args = ' '.join(complete_args)
|
||||
|
||||
if r.exit_status != ExitStatus.SUCCESS:
|
||||
sys.stderr.write(r.stderr)
|
||||
|
||||
# print(f'\n\n$ {r.command}\n')
|
||||
return r
|
||||
|
||||
finally:
|
32
tests/utils/matching/__init__.py
Normal file
32
tests/utils/matching/__init__.py
Normal 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)
|
107
tests/utils/matching/parsing.py
Normal file
107
tests/utils/matching/parsing.py
Normal 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
|
190
tests/utils/matching/test_matching.py
Normal file
190
tests/utils/matching/test_matching.py
Normal 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]
|
||||
)
|
Reference in New Issue
Block a user