mirror of
https://github.com/httpie/cli.git
synced 2025-08-16 02:08:21 +02:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
8e96238323 | |||
8a9206eceb | |||
8ac3c5961c | |||
487c7a9221 | |||
6d65668355 | |||
3e5115e4a2 | |||
2b8b572f22 | |||
af737fd338 | |||
ee375b6942 | |||
becb63de9a | |||
86c8abc485 | |||
8f6bee9196 | |||
9c2c058ae5 | |||
6238b59e72 | |||
702c21aa91 | |||
aab5cd9da0 | |||
8c0f0b578c | |||
bb4881a873 | |||
3a1726b4ed | |||
e1fa57d228 | |||
bfc64bce21 | |||
595dc51b2d | |||
83fa772247 | |||
49a0fb6e0f | |||
41e822ca2f | |||
1124d68946 | |||
c3735d0422 | |||
364b91cbc4 | |||
c8e06b55e1 | |||
5acbc904b7 | |||
0c7c248dce | |||
caf60cbc65 | |||
2b0e642842 | |||
e25948f6a0 | |||
ec245a1e80 | |||
6259b5dd3b |
19
.travis.yml
19
.travis.yml
@ -15,7 +15,8 @@ python:
|
||||
- pypy
|
||||
- 3.4
|
||||
- 3.5
|
||||
- pypy3
|
||||
# Currently fails because of a Flask issue
|
||||
# - pypy3
|
||||
|
||||
matrix:
|
||||
|
||||
@ -44,6 +45,11 @@ matrix:
|
||||
- TOXENV=py35
|
||||
- BREW_INSTALL=python3
|
||||
|
||||
# Python Codestyle
|
||||
- os: linux
|
||||
python: 3.5
|
||||
env: CODESTYLE=true
|
||||
|
||||
install:
|
||||
- |
|
||||
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
|
||||
@ -53,11 +59,20 @@ install:
|
||||
fi
|
||||
sudo pip install tox
|
||||
fi
|
||||
if [[ $CODESTYLE ]]; then
|
||||
pip install pycodestyle
|
||||
fi
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
|
||||
make
|
||||
if [[ $CODESTYLE ]]; then
|
||||
# 241 - multiple spaces after ‘,’
|
||||
# 501 - line too long
|
||||
pycodestyle --ignore=E241,E501
|
||||
else
|
||||
make
|
||||
fi
|
||||
else
|
||||
PATH="/usr/local/bin:$PATH" tox -e "$TOXENV"
|
||||
fi
|
||||
|
@ -8,7 +8,7 @@ HTTPie authors
|
||||
Patches and ideas
|
||||
-----------------
|
||||
|
||||
`Complete list of contributors on GitHib <https://github.com/jkbrzt/httpie/graphs/contributors>`_
|
||||
`Complete list of contributors on GitHub <https://github.com/jkbrzt/httpie/graphs/contributors>`_
|
||||
|
||||
* `Cláudia T. Delgado <https://github.com/claudiatd>`_ (logo)
|
||||
* `Hank Gay <https://github.com/gthank>`_
|
||||
|
@ -10,6 +10,27 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
|
||||
-------------------------
|
||||
|
||||
|
||||
`0.9.6`_ (2016-08-13)
|
||||
---------------------
|
||||
|
||||
* Added Python 3 as a dependency for Homebrew installations
|
||||
to ensure some of the newer HTTP features work out of the box
|
||||
for macOS users (starting with HTTPie 0.9.4.).
|
||||
* Added the ability to unset a request header with ``Header:``, and send an
|
||||
empty value with ``Header;``.
|
||||
* Added ``--default-scheme <URL_SCHEME>`` to enable things like
|
||||
``$ alias https='http --default-scheme=https``.
|
||||
* Added ``-I`` as a shortcut for ``--ignore-stdin``.
|
||||
* Added fish shell completion (located in ``extras/httpie-completion.fish``
|
||||
in the Github repo).
|
||||
* Updated ``requests`` to 2.10.0 so that SOCKS support can be added via
|
||||
``pip install requests[socks]``.
|
||||
* Changed the default JSON ``Accept`` header from ``application/json``
|
||||
to ``application/json, */*``.
|
||||
* Changed the pre-processing of request HTTP headers so that any leading
|
||||
and trailing whitespace is removed.
|
||||
|
||||
|
||||
`0.9.4`_ (2016-07-01)
|
||||
---------------------
|
||||
|
||||
@ -289,4 +310,5 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
|
||||
.. _0.9.2: https://github.com/jkbrzt/httpie/compare/0.9.1...0.9.2
|
||||
.. _0.9.3: https://github.com/jkbrzt/httpie/compare/0.9.2...0.9.3
|
||||
.. _0.9.4: https://github.com/jkbrzt/httpie/compare/0.9.3...0.9.4
|
||||
.. _0.9.6: https://github.com/jkbrzt/httpie/compare/0.9.3...0.9.6
|
||||
.. _1.0.0-dev: https://github.com/jkbrzt/httpie/compare/0.9.4...master
|
||||
|
10
Makefile
10
Makefile
@ -57,10 +57,12 @@ test-bdist-wheel: clean uninstall-httpie
|
||||
test-all: uninstall-all clean init test test-tox test-dist
|
||||
|
||||
|
||||
publish: test-all
|
||||
publish: test-all publish-no-test
|
||||
|
||||
publish-no-test:
|
||||
@echo $(TAG)Testing wheel build an installation$(END)
|
||||
@echo "$(VERSION)"
|
||||
@echo "$(VERSION)" | grep -q "dev" && echo "!!!Not publishing dev version!!!" && exit 1
|
||||
@echo "$(VERSION)" | grep -q "dev" && echo '!!!Not publishing dev version!!!' && exit 1 || echo ok
|
||||
python setup.py register
|
||||
python setup.py sdist upload
|
||||
python setup.py bdist_wheel upload
|
||||
@ -92,3 +94,7 @@ uninstall-all: uninstall-httpie
|
||||
|
||||
@echo $(TAG)Uninstalling development requirements$(END)
|
||||
- pip uninstall --yes -r $(REQUIREMENTS)
|
||||
|
||||
|
||||
homebrew-formula-vars:
|
||||
extras/get-homebrew-formula-vars.py
|
||||
|
139
README.rst
139
README.rst
@ -35,7 +35,7 @@ HTTPie is written in Python, and under the hood it uses the excellent
|
||||
|
||||
|
||||
=============
|
||||
Main Features
|
||||
Main features
|
||||
=============
|
||||
|
||||
* Expressive and intuitive syntax
|
||||
@ -117,6 +117,17 @@ The **latest development version** can be installed directly from GitHub:
|
||||
$ pip install --upgrade https://github.com/jkbrzt/httpie/archive/master.tar.gz
|
||||
|
||||
|
||||
--------------
|
||||
Python version
|
||||
--------------
|
||||
|
||||
Although Python 2.6 and 2.7 are supported as well, it is recommended to install
|
||||
HTTPie against the latest Python 3.x whenever possible. That will ensure that
|
||||
some of the newer HTTP features, such as `SNI (Server Name Indication)`_,
|
||||
work out of the box.
|
||||
Python 3 is the default for Homebrew installations starting with version 0.9.4.
|
||||
To see which version HTTPie uses, run ``http --debug``.
|
||||
|
||||
|
||||
=====
|
||||
Usage
|
||||
@ -172,7 +183,7 @@ with `authentication`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http -a USERNAME POST https://api.github.com/repos/jkbrzt/httpie/issues/83/comments body='HTTPie is awesome!'
|
||||
$ http -a USERNAME POST https://api.github.com/repos/jkbrzt/httpie/issues/83/comments body='HTTPie is awesome! :heart:'
|
||||
|
||||
|
||||
Upload a file using `redirected input`_:
|
||||
@ -220,7 +231,7 @@ advanced usage, and also features additional examples.*
|
||||
|
||||
|
||||
===========
|
||||
HTTP Method
|
||||
HTTP method
|
||||
===========
|
||||
|
||||
The name of the HTTP method comes right before the URL argument:
|
||||
@ -302,9 +313,16 @@ this command:
|
||||
|
||||
GET /?search=HTTPie+logo&tbm=isch HTTP/1.1
|
||||
|
||||
You can use the ``--default-scheme <URL_SCHEME>`` option to create
|
||||
shortcuts for other protocols than HTTP:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ alias https='http --default-scheme=https'
|
||||
|
||||
|
||||
=============
|
||||
Request Items
|
||||
Request items
|
||||
=============
|
||||
|
||||
There are a few different *request item* types that provide a
|
||||
@ -385,13 +403,13 @@ both of which can be overwritten:
|
||||
|
||||
================ =======================================
|
||||
``Content-Type`` ``application/json``
|
||||
``Accept`` ``application/json``
|
||||
``Accept`` ``application/json, */*``
|
||||
================ =======================================
|
||||
|
||||
You can use ``--json, -j`` to explicitly set ``Accept``
|
||||
to ``application/json`` regardless of whether you are sending data
|
||||
(it's a shortcut for setting the header via the usual header notation –
|
||||
``http url Accept:application/json``). Additionally,
|
||||
``http url Accept:application/json, */*``). Additionally,
|
||||
HTTPie will try to detect JSON responses even when the
|
||||
``Content-Type`` is incorrectly ``text/plain`` or unknown.
|
||||
|
||||
@ -404,7 +422,7 @@ Simple example:
|
||||
.. code-block:: http
|
||||
|
||||
PUT / HTTP/1.1
|
||||
Accept: application/json
|
||||
Accept: application/json, */*
|
||||
Accept-Encoding: gzip, deflate
|
||||
Content-Type: application/json
|
||||
Host: example.org
|
||||
@ -431,7 +449,7 @@ fields using ``=@`` and ``:=@``:
|
||||
.. code-block:: http
|
||||
|
||||
PUT /person/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Accept: application/json, */*
|
||||
Content-Type: application/json
|
||||
Host: api.example.com
|
||||
|
||||
@ -471,7 +489,7 @@ via the `config`_ file.
|
||||
|
||||
|
||||
-------------
|
||||
Regular Forms
|
||||
Regular forms
|
||||
-------------
|
||||
|
||||
.. code-block:: bash
|
||||
@ -489,7 +507,7 @@ Regular Forms
|
||||
|
||||
|
||||
-----------------
|
||||
File Upload Forms
|
||||
File upload forms
|
||||
-----------------
|
||||
|
||||
If one or more file fields is present, the serialization and content type is
|
||||
@ -515,7 +533,7 @@ Note that ``@`` is used to simulate a file upload form field, whereas
|
||||
|
||||
|
||||
============
|
||||
HTTP Headers
|
||||
HTTP headers
|
||||
============
|
||||
|
||||
To set custom headers you can use the ``Header:Value`` notation:
|
||||
@ -549,7 +567,23 @@ There are a couple of default headers that HTTPie sets:
|
||||
Host: <taken-from-URL>
|
||||
|
||||
|
||||
Any of the default headers can be overwritten.
|
||||
Any of the default headers can be overwritten and some of them unset.
|
||||
|
||||
To unset a header that has already been specified (such a one of the default
|
||||
headers), use ``Header:``:
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/headers Accept: User-Agent:
|
||||
|
||||
|
||||
To send a header with an empty value, use ``Header;``:
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http httpbin.org/headers 'Header;'
|
||||
|
||||
|
||||
==============
|
||||
@ -614,7 +648,7 @@ Authorization information from your ``~/.netrc`` file is honored as well:
|
||||
|
||||
|
||||
------------
|
||||
Auth Plugins
|
||||
Auth plugins
|
||||
------------
|
||||
|
||||
* `httpie-oauth <https://github.com/jkbrzt/httpie-oauth>`_: OAuth
|
||||
@ -628,7 +662,7 @@ Auth Plugins
|
||||
|
||||
|
||||
==============
|
||||
HTTP Redirects
|
||||
HTTP redirects
|
||||
==============
|
||||
|
||||
By default, HTTP redirects are not followed and only the first
|
||||
@ -679,6 +713,24 @@ In your ``~/.bash_profile``:
|
||||
export NO_PROXY=localhost,example.com
|
||||
|
||||
|
||||
-----
|
||||
SOCKS
|
||||
-----
|
||||
|
||||
To enable SOCKS proxy support please install ``requests[socks]`` using ``pip``:
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install -U requests[socks]
|
||||
|
||||
Usage is the same as for other types of `proxies`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --proxy=http:socks5://user:pass@host:port --proxy=https:socks5://user:pass@host:port example.org
|
||||
|
||||
|
||||
=====
|
||||
HTTPS
|
||||
=====
|
||||
@ -750,8 +802,8 @@ available set of protocols may vary depending on your OpenSSL installation.)
|
||||
SNI (Server Name Indication)
|
||||
----------------------------
|
||||
|
||||
If you use HTTPie with Python < 2.7.9
|
||||
(can be verified with ``python --version``) and need to talk to servers that
|
||||
If you use HTTPie with `Python version`_ lower than 2.7.9
|
||||
(can be verified with ``http --debug``) and need to talk to servers that
|
||||
use **SNI (Server Name Indication)** you need to install some additional
|
||||
dependencies:
|
||||
|
||||
@ -768,7 +820,7 @@ You can use the following command to test SNI support:
|
||||
|
||||
|
||||
==============
|
||||
Output Options
|
||||
Output options
|
||||
==============
|
||||
|
||||
By default, HTTPie only outputs the final response and the whole response
|
||||
@ -791,7 +843,7 @@ documentation examples:
|
||||
|
||||
$ http --verbose PUT httpbin.org/put hello=world
|
||||
PUT /put HTTP/1.1
|
||||
Accept: application/json
|
||||
Accept: application/json, */*
|
||||
Accept-Encoding: gzip, deflate
|
||||
Content-Type: application/json
|
||||
Host: httpbin.org
|
||||
@ -835,11 +887,11 @@ Print request and response headers:
|
||||
|
||||
|
||||
---------------------------------------
|
||||
Viewing Intermediary Requests/Responses
|
||||
Viewing intermediary requests/responses
|
||||
---------------------------------------
|
||||
|
||||
To see *all* the HTTP communication, i.e. the final request/resposne as
|
||||
well as any possible intermediary requests/responses, use the **``--all``**
|
||||
To see *all* the HTTP communication, i.e. the final request/response as
|
||||
well as any possible intermediary requests/responses, use the ``--all``
|
||||
option. The intermediary HTTP communication include followed redirects
|
||||
(with ``--follow``), the first unauthorized request when HTTP digest
|
||||
authentication is used (``--auth=digest``), etc.
|
||||
@ -863,7 +915,7 @@ arguments as ``--print, -p`` but applies to the intermediary requests only.
|
||||
|
||||
|
||||
-------------------------
|
||||
Conditional Body Download
|
||||
Conditional body download
|
||||
-------------------------
|
||||
|
||||
As an optimization, the response body is downloaded from the server
|
||||
@ -961,9 +1013,9 @@ To prevent HTTPie from reading ``stdin`` data you can use the
|
||||
``--ignore-stdin`` option.
|
||||
|
||||
|
||||
-------------------------
|
||||
Body Data From a Filename
|
||||
-------------------------
|
||||
----------------------------
|
||||
Request data from a filename
|
||||
----------------------------
|
||||
|
||||
**An alternative to redirected** ``stdin`` is specifying a filename (as
|
||||
``@/path/to/file``) whose content is used as if it came from ``stdin``.
|
||||
@ -979,7 +1031,7 @@ verbatim contents of that XML file with ``Content-Type: application/xml``:
|
||||
|
||||
|
||||
===============
|
||||
Terminal Output
|
||||
Terminal output
|
||||
===============
|
||||
|
||||
HTTPie does several things by default in order to make its terminal output
|
||||
@ -987,7 +1039,7 @@ easy to read.
|
||||
|
||||
|
||||
---------------------
|
||||
Colors and Formatting
|
||||
Colors and formatting
|
||||
---------------------
|
||||
|
||||
Syntax highlighting is applied to HTTP headers and bodies (where it makes
|
||||
@ -1042,7 +1094,7 @@ You will nearly instantly see something like this:
|
||||
|
||||
|
||||
=================
|
||||
Redirected Output
|
||||
Redirected output
|
||||
=================
|
||||
|
||||
HTTPie uses **different defaults** for redirected output than for
|
||||
@ -1093,7 +1145,7 @@ by adding the following to your ``~/.bash_profile``:
|
||||
|
||||
|
||||
=============
|
||||
Download Mode
|
||||
Download mode
|
||||
=============
|
||||
|
||||
HTTPie features a download mode in which it acts similarly to ``wget``.
|
||||
@ -1150,7 +1202,7 @@ Other notes:
|
||||
|
||||
|
||||
==================
|
||||
Streamed Responses
|
||||
Streamed responses
|
||||
==================
|
||||
|
||||
Responses are downloaded and printed in chunks, which allows for streaming
|
||||
@ -1197,7 +1249,7 @@ ones starting with ``Content-`` or ``If-``), authorization, and cookies
|
||||
to the same host.
|
||||
|
||||
--------------
|
||||
Named Sessions
|
||||
Named sessions
|
||||
--------------
|
||||
|
||||
Create a new session named ``user1`` for ``example.org``:
|
||||
@ -1228,7 +1280,7 @@ Named sessions' data is stored in JSON files in the directory
|
||||
(``%APPDATA%\httpie\sessions\<host>\<name>.json`` on Windows).
|
||||
|
||||
------------------
|
||||
Anonymous Sessions
|
||||
Anonymous sessions
|
||||
------------------
|
||||
|
||||
Instead of a name, you can also directly specify a path to a session file. This
|
||||
@ -1322,7 +1374,7 @@ Also, the ``--timeout`` option allows to overwrite the default 30s timeout:
|
||||
|
||||
|
||||
================
|
||||
Interface Design
|
||||
Interface design
|
||||
================
|
||||
|
||||
The syntax of the command arguments closely corresponds to the actual HTTP
|
||||
@ -1396,28 +1448,39 @@ have contributed.
|
||||
Logo
|
||||
====
|
||||
|
||||
Please see `claudiatd/httpie-artwork`_
|
||||
See `claudiatd/httpie-artwork`_
|
||||
|
||||
|
||||
==========
|
||||
Contribute
|
||||
==========
|
||||
|
||||
Please see `CONTRIBUTING <https://github.com/jkbrzt/httpie/blob/master/CONTRIBUTING.rst>`_.
|
||||
See `CONTRIBUTING <https://github.com/jkbrzt/httpie/blob/master/CONTRIBUTING.rst>`_.
|
||||
|
||||
|
||||
==========
|
||||
Change Log
|
||||
Change log
|
||||
==========
|
||||
|
||||
Please see `CHANGELOG <https://github.com/jkbrzt/httpie/blob/master/CHANGELOG.rst>`_.
|
||||
See `CHANGELOG <https://github.com/jkbrzt/httpie/blob/master/CHANGELOG.rst>`_.
|
||||
|
||||
|
||||
=======
|
||||
Licence
|
||||
=======
|
||||
|
||||
Please see `LICENSE <https://github.com/jkbrzt/httpie/blob/master/LICENSE>`_.
|
||||
See `LICENSE <https://github.com/jkbrzt/httpie/blob/master/LICENSE>`_.
|
||||
|
||||
|
||||
|
||||
================
|
||||
Related projects
|
||||
================
|
||||
|
||||
* `jq <https://stedolan.github.io/jq/>`_ — a command-line JSON processor that
|
||||
works great in conjunction with HTTPie
|
||||
* `http-prompt <https://github.com/eliangcs/http-prompt>`_ — an interactive
|
||||
shell for HTTPie featuring autocomplete and command syntax highlighting
|
||||
|
||||
|
||||
|
||||
|
55
extras/get-homebrew-formula-vars.py
Executable file
55
extras/get-homebrew-formula-vars.py
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Generate URLs and file hashes to be included in the Homebrew formula
|
||||
after a new release of HTTPie is published on PyPi.
|
||||
|
||||
https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb
|
||||
|
||||
"""
|
||||
import hashlib
|
||||
import requests
|
||||
|
||||
|
||||
PACKAGES = [
|
||||
'httpie',
|
||||
'requests',
|
||||
'pygments',
|
||||
]
|
||||
|
||||
|
||||
def get_info(package_name):
|
||||
api_url = 'https://pypi.python.org/pypi/{}/json'.format(package_name)
|
||||
resp = requests.get(api_url).json()
|
||||
hasher = hashlib.sha256()
|
||||
for release in resp['urls']:
|
||||
download_url = release['url']
|
||||
if download_url.endswith('.tar.gz'):
|
||||
hasher.update(requests.get(download_url).content)
|
||||
return {
|
||||
'name': package_name,
|
||||
'url': download_url,
|
||||
'sha256': hasher.hexdigest(),
|
||||
}
|
||||
else:
|
||||
raise RuntimeError(
|
||||
'{}: download not found: {}'.format(package_name, resp))
|
||||
|
||||
|
||||
packages = {
|
||||
package_name: get_info(package_name) for package_name in PACKAGES
|
||||
}
|
||||
|
||||
|
||||
httpie_info = packages.pop('httpie')
|
||||
print("""
|
||||
url "{url}"
|
||||
sha256 "{sha256}"
|
||||
""".format(**httpie_info))
|
||||
|
||||
|
||||
for package_info in packages.values():
|
||||
print("""
|
||||
resource "{name}" do
|
||||
url "{url}"
|
||||
sha256 "{sha256}"
|
||||
end""".format(**package_info))
|
60
extras/httpie-completion.fish
Normal file
60
extras/httpie-completion.fish
Normal file
@ -0,0 +1,60 @@
|
||||
function __fish_httpie_auth_types
|
||||
echo "basic"\t"Basic HTTP auth"
|
||||
echo "digest"\t"Digest HTTP auth"
|
||||
end
|
||||
|
||||
function __fish_httpie_styles
|
||||
echo "autumn"
|
||||
echo "borland"
|
||||
echo "bw"
|
||||
echo "colorful"
|
||||
echo "default"
|
||||
echo "emacs"
|
||||
echo "friendly"
|
||||
echo "fruity"
|
||||
echo "igor"
|
||||
echo "manni"
|
||||
echo "monokai"
|
||||
echo "murphy"
|
||||
echo "native"
|
||||
echo "paraiso-dark"
|
||||
echo "paraiso-light"
|
||||
echo "pastie"
|
||||
echo "perldoc"
|
||||
echo "rrt"
|
||||
echo "solarized"
|
||||
echo "tango"
|
||||
echo "trac"
|
||||
echo "vim"
|
||||
echo "vs"
|
||||
echo "xcode"
|
||||
end
|
||||
|
||||
complete -x -c http -s s -l style -d 'Output coloring style (default is "monokai")' -A -a "autumn borland bw colorful default emacs friendly fruity igor manni monokai murphy native paraiso-dark paraiso-light pastie perldoc rrt solarized tango trac vim vs xcode"
|
||||
complete -c http -s f -l form -d 'Data items from the command line are serialized as form fields'
|
||||
complete -c http -s j -l json -d '(default) Data items from the command line are serialized as a JSON object'
|
||||
complete -x -c http -l pretty -d 'Controls output processing' -a "all colors format none" -A
|
||||
complete -x -c http -s s -l style -d 'Output coloring style (default is "monokai")' -A -a "autumn borland bw colorful default emacs friendly fruity igor manni monokai murphy native paraiso-dark paraiso-light pastie perldoc rrt solarized tango trac vim vs xcode"
|
||||
complete -x -c http -s p -l print -d 'String specifying what the output should contain'
|
||||
complete -c http -s v -l verbose -d 'Print the whole request as well as the response'
|
||||
complete -c http -s h -l headers -d 'Print only the response headers'
|
||||
complete -c http -s b -l body -d 'Print only the response body'
|
||||
complete -c http -s S -l stream -d 'Always stream the output by line'
|
||||
complete -c http -s o -l output -d 'Save output to FILE'
|
||||
complete -c http -s d -l download -d 'Do not print the response body to stdout'
|
||||
complete -c http -s c -l continue -d 'Resume an interrupted download'
|
||||
complete -x -c http -l session -d 'Create, or reuse and update a session'
|
||||
complete -x -c http -s a -l auth -d 'If only the username is provided (-a username), HTTPie will prompt for the password'
|
||||
complete -x -c http -l auth-type -d 'The authentication mechanism to be used' -a '(__fish_httpie_auth_types)' -A
|
||||
complete -x -c http -l proxy -d 'String mapping protocol to the URL of the proxy'
|
||||
complete -c http -l follow -d 'Allow full redirects'
|
||||
complete -x -c http -l verify -d 'SSL cert verification'
|
||||
complete -c http -l cert -d 'SSL cert'
|
||||
complete -c http -l cert-key -d 'Private SSL cert key'
|
||||
complete -x -c http -l timeout -d 'Connection timeout in seconds'
|
||||
complete -c http -l check-status -d 'Error with non-200 HTTP status code'
|
||||
complete -c http -l ignore-stdin -d 'Do not attempt to read stdin'
|
||||
complete -c http -l help -d 'Show help'
|
||||
complete -c http -l version -d 'Show version'
|
||||
complete -c http -l traceback -d 'Prints exception traceback should one occur'
|
||||
complete -c http -l debug -d 'Show debugging information'
|
@ -3,7 +3,7 @@ HTTPie - a CLI, cURL-like tool for humans.
|
||||
|
||||
"""
|
||||
__author__ = 'Jakub Roztocil'
|
||||
__version__ = '0.9.4'
|
||||
__version__ = '0.9.6'
|
||||
__licence__ = 'BSD'
|
||||
|
||||
|
||||
|
@ -89,6 +89,7 @@ positional.add_argument(
|
||||
metavar='URL',
|
||||
help="""
|
||||
The scheme defaults to 'http://' if the URL does not include one.
|
||||
(You can override this with: --default-scheme=https)
|
||||
|
||||
You can also use a shorthand for localhost
|
||||
|
||||
@ -212,7 +213,7 @@ output_processing.add_argument(
|
||||
""".format(
|
||||
default=DEFAULT_STYLE,
|
||||
available='\n'.join(
|
||||
'{0}{1}'.format(8*' ', line.strip())
|
||||
'{0}{1}'.format(8 * ' ', line.strip())
|
||||
for line in wrap(', '.join(sorted(AVAILABLE_STYLES)), 60)
|
||||
).rstrip(),
|
||||
)
|
||||
@ -576,7 +577,7 @@ ssl.add_argument(
|
||||
troubleshooting = parser.add_argument_group(title='Troubleshooting')
|
||||
|
||||
troubleshooting.add_argument(
|
||||
'--ignore-stdin',
|
||||
'--ignore-stdin', '-I',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="""
|
||||
@ -611,6 +612,14 @@ troubleshooting.add_argument(
|
||||
|
||||
"""
|
||||
)
|
||||
troubleshooting.add_argument(
|
||||
'--default-scheme',
|
||||
default="http",
|
||||
help="""
|
||||
The default scheme to use if not specified in the URL.
|
||||
|
||||
"""
|
||||
)
|
||||
troubleshooting.add_argument(
|
||||
'--debug',
|
||||
action='store_true',
|
||||
|
@ -1,6 +1,5 @@
|
||||
import json
|
||||
import sys
|
||||
from pprint import pformat
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
@ -24,8 +23,9 @@ except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
FORM = 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
JSON = 'application/json'
|
||||
FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
JSON_CONTENT_TYPE = 'application/json'
|
||||
JSON_ACCEPT = '{0}, */*'.format(JSON_CONTENT_TYPE)
|
||||
DEFAULT_UA = 'HTTPie/%s' % __version__
|
||||
|
||||
|
||||
@ -85,13 +85,23 @@ def dump_request(kwargs):
|
||||
% repr_dict_nice(kwargs))
|
||||
|
||||
|
||||
def encode_headers(headers):
|
||||
# This allows for unicode headers which is non-standard but practical.
|
||||
# See: https://github.com/jkbrzt/httpie/issues/212
|
||||
return dict(
|
||||
(name, value.encode('utf8') if isinstance(value, str) else value)
|
||||
for name, value in headers.items()
|
||||
)
|
||||
def finalize_headers(headers):
|
||||
final_headers = {}
|
||||
for name, value in headers.items():
|
||||
if value is not None:
|
||||
|
||||
# >leading or trailing LWS MAY be removed without
|
||||
# >changing the semantics of the field value"
|
||||
# -https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
|
||||
# Also, requests raises `InvalidHeader` for leading spaces.
|
||||
value = value.strip()
|
||||
|
||||
if isinstance(value, str):
|
||||
# See: https://github.com/jkbrzt/httpie/issues/212
|
||||
value = value.encode('utf8')
|
||||
|
||||
final_headers[name] = value
|
||||
return final_headers
|
||||
|
||||
|
||||
def get_default_headers(args):
|
||||
@ -100,16 +110,15 @@ def get_default_headers(args):
|
||||
}
|
||||
|
||||
auto_json = args.data and not args.form
|
||||
# FIXME: Accept is set to JSON with `http url @./file.txt`.
|
||||
if args.json or auto_json:
|
||||
default_headers['Accept'] = 'application/json'
|
||||
default_headers['Accept'] = JSON_ACCEPT
|
||||
if args.json or (auto_json and args.data):
|
||||
default_headers['Content-Type'] = JSON
|
||||
default_headers['Content-Type'] = JSON_CONTENT_TYPE
|
||||
|
||||
elif args.form and not args.files:
|
||||
# If sending files, `requests` will set
|
||||
# the `Content-Type` for us.
|
||||
default_headers['Content-Type'] = FORM
|
||||
default_headers['Content-Type'] = FORM_CONTENT_TYPE
|
||||
return default_headers
|
||||
|
||||
|
||||
@ -134,7 +143,7 @@ def get_requests_kwargs(args, base_headers=None):
|
||||
if base_headers:
|
||||
headers.update(base_headers)
|
||||
headers.update(args.headers)
|
||||
headers = encode_headers(headers)
|
||||
headers = finalize_headers(headers)
|
||||
|
||||
credentials = None
|
||||
if args.auth:
|
||||
|
@ -14,10 +14,14 @@ is_windows = 'win32' in str(sys.platform).lower()
|
||||
|
||||
|
||||
if is_py2:
|
||||
# noinspection PyShadowingBuiltins
|
||||
bytes = str
|
||||
# noinspection PyUnresolvedReferences,PyShadowingBuiltins
|
||||
str = unicode
|
||||
elif is_py3:
|
||||
# noinspection PyShadowingBuiltins
|
||||
str = str
|
||||
# noinspection PyShadowingBuiltins
|
||||
bytes = bytes
|
||||
|
||||
|
||||
@ -32,7 +36,7 @@ try: # pragma: no cover
|
||||
# noinspection PyCompatibility
|
||||
from urllib.request import urlopen
|
||||
except ImportError: # pragma: no cover
|
||||
# noinspection PyCompatibility
|
||||
# noinspection PyCompatibility,PyUnresolvedReferences
|
||||
from urllib2 import urlopen
|
||||
|
||||
try: # pragma: no cover
|
||||
@ -40,10 +44,10 @@ try: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
# Python 2.6 OrderedDict class, needed for headers, parameters, etc .###
|
||||
# <https://pypi.python.org/pypi/ordereddict/1.1>
|
||||
# noinspection PyCompatibility
|
||||
# noinspection PyCompatibility,PyUnresolvedReferences
|
||||
from UserDict import DictMixin
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
# noinspection PyShadowingBuiltins,PyCompatibility
|
||||
class OrderedDict(dict, DictMixin):
|
||||
# Copyright (c) 2009 Raymond Hettinger
|
||||
#
|
||||
@ -115,6 +119,7 @@ except ImportError: # pragma: no cover
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
if last:
|
||||
# noinspection PyUnresolvedReferences
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
|
@ -243,7 +243,7 @@ def main(args=sys.argv[1:], env=Environment(), custom_log_error=None):
|
||||
except requests.TooManyRedirects:
|
||||
exit_status = ExitStatus.ERROR_TOO_MANY_REDIRECTS
|
||||
log_error('Too many redirects (--max-redirects=%s).',
|
||||
parsed_args.max_redirects)
|
||||
parsed_args.max_redirects)
|
||||
except Exception as e:
|
||||
# TODO: Further distinction between expected and unexpected errors.
|
||||
msg = str(e)
|
||||
|
@ -78,15 +78,15 @@ def parse_content_range(content_range, resumed_from):
|
||||
# last-byte-pos value, is invalid. The recipient of an invalid
|
||||
# byte-content-range- spec MUST ignore it and any content
|
||||
# transferred along with it."
|
||||
if (first_byte_pos >= last_byte_pos
|
||||
or (instance_length is not None
|
||||
and instance_length <= last_byte_pos)):
|
||||
if (first_byte_pos >= last_byte_pos or
|
||||
(instance_length is not None and
|
||||
instance_length <= last_byte_pos)):
|
||||
raise ContentRangeError(
|
||||
'Invalid Content-Range returned: %r' % content_range)
|
||||
|
||||
if (first_byte_pos != resumed_from
|
||||
or (instance_length is not None
|
||||
and last_byte_pos + 1 != instance_length)):
|
||||
if (first_byte_pos != resumed_from or
|
||||
(instance_length is not None and
|
||||
last_byte_pos + 1 != instance_length)):
|
||||
# Not what we asked for.
|
||||
raise ContentRangeError(
|
||||
'Unexpected Content-Range returned (%r)'
|
||||
@ -308,9 +308,9 @@ class Downloader(object):
|
||||
@property
|
||||
def interrupted(self):
|
||||
return (
|
||||
self.finished
|
||||
and self.status.total_size
|
||||
and self.status.total_size != self.status.downloaded
|
||||
self.finished and
|
||||
self.status.total_size and
|
||||
self.status.total_size != self.status.downloaded
|
||||
)
|
||||
|
||||
def chunk_downloaded(self, chunk):
|
||||
@ -399,8 +399,8 @@ class ProgressReporterThread(threading.Thread):
|
||||
if now - self._prev_time >= self._update_interval:
|
||||
downloaded = self.status.downloaded
|
||||
try:
|
||||
speed = ((downloaded - self._prev_bytes)
|
||||
/ (now - self._prev_time))
|
||||
speed = ((downloaded - self._prev_bytes) /
|
||||
(now - self._prev_time))
|
||||
except ZeroDivisionError:
|
||||
speed = 0
|
||||
|
||||
@ -434,11 +434,11 @@ class ProgressReporterThread(threading.Thread):
|
||||
self._prev_bytes = downloaded
|
||||
|
||||
self.output.write(
|
||||
CLEAR_LINE
|
||||
+ ' '
|
||||
+ SPINNER[self._spinner_pos]
|
||||
+ ' '
|
||||
+ self._status_line
|
||||
CLEAR_LINE +
|
||||
' ' +
|
||||
SPINNER[self._spinner_pos] +
|
||||
' ' +
|
||||
self._status_line
|
||||
)
|
||||
self.output.flush()
|
||||
|
||||
@ -447,8 +447,8 @@ class ProgressReporterThread(threading.Thread):
|
||||
else 0)
|
||||
|
||||
def sum_up(self):
|
||||
actually_downloaded = (self.status.downloaded
|
||||
- self.status.resumed_from)
|
||||
actually_downloaded = (
|
||||
self.status.downloaded - self.status.resumed_from)
|
||||
time_taken = self.status.time_finished - self.status.time_started
|
||||
|
||||
self.output.write(CLEAR_LINE)
|
||||
@ -463,8 +463,8 @@ class ProgressReporterThread(threading.Thread):
|
||||
|
||||
self.output.write(SUMMARY.format(
|
||||
downloaded=humanize_bytes(actually_downloaded),
|
||||
total=(self.status.total_size
|
||||
and humanize_bytes(self.status.total_size)),
|
||||
total=(self.status.total_size and
|
||||
humanize_bytes(self.status.total_size)),
|
||||
speed=humanize_bytes(speed),
|
||||
time=time_taken,
|
||||
))
|
||||
|
@ -28,12 +28,11 @@ URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE)
|
||||
|
||||
HTTP_POST = 'POST'
|
||||
HTTP_GET = 'GET'
|
||||
HTTP = 'http://'
|
||||
HTTPS = 'https://'
|
||||
|
||||
|
||||
# Various separators used in args
|
||||
SEP_HEADERS = ':'
|
||||
SEP_HEADERS_EMPTY = ';'
|
||||
SEP_CREDENTIALS = ':'
|
||||
SEP_PROXY = ':'
|
||||
SEP_DATA = '='
|
||||
@ -67,6 +66,7 @@ SEP_GROUP_RAW_JSON_ITEMS = frozenset([
|
||||
# Separators allowed in ITEM arguments
|
||||
SEP_GROUP_ALL_ITEMS = frozenset([
|
||||
SEP_HEADERS,
|
||||
SEP_HEADERS_EMPTY,
|
||||
SEP_QUERY,
|
||||
SEP_DATA,
|
||||
SEP_DATA_RAW_JSON,
|
||||
@ -151,7 +151,7 @@ class HTTPieArgumentParser(ArgumentParser):
|
||||
if not self.args.ignore_stdin and not env.stdin_isatty:
|
||||
self._body_from_file(self.env.stdin)
|
||||
if not URL_SCHEME_RE.match(self.args.url):
|
||||
scheme = HTTP
|
||||
scheme = self.args.default_scheme + "://"
|
||||
|
||||
# See if we're using curl style shorthand for localhost (:3000/foo)
|
||||
shorthand = re.match(r'^:(?!:)(\d*)(/?.*)$', self.args.url)
|
||||
@ -309,9 +309,10 @@ class HTTPieArgumentParser(ArgumentParser):
|
||||
self.args.url = self.args.method
|
||||
# Infer the method
|
||||
has_data = (
|
||||
(not self.args.ignore_stdin and not self.env.stdin_isatty)
|
||||
or any(item.sep in SEP_GROUP_DATA_ITEMS
|
||||
for item in self.args.items)
|
||||
(not self.args.ignore_stdin and
|
||||
not self.env.stdin_isatty) or
|
||||
any(item.sep in SEP_GROUP_DATA_ITEMS
|
||||
for item in self.args.items)
|
||||
)
|
||||
self.args.method = HTTP_POST if has_data else HTTP_GET
|
||||
|
||||
@ -439,8 +440,8 @@ class SessionNameValidator(object):
|
||||
|
||||
def __call__(self, value):
|
||||
# Session name can be a path or just a name.
|
||||
if (os.path.sep not in value
|
||||
and not VALID_SESSION_NAME_PATTERN.search(value)):
|
||||
if (os.path.sep not in value and
|
||||
not VALID_SESSION_NAME_PATTERN.search(value)):
|
||||
raise ArgumentError(None, self.error_message)
|
||||
return value
|
||||
|
||||
@ -655,11 +656,20 @@ def parse_items(items,
|
||||
data = []
|
||||
files = []
|
||||
params = []
|
||||
|
||||
for item in items:
|
||||
value = item.value
|
||||
|
||||
if item.sep == SEP_HEADERS:
|
||||
if value == '':
|
||||
# No value => unset the header
|
||||
value = None
|
||||
target = headers
|
||||
elif item.sep == SEP_HEADERS_EMPTY:
|
||||
if item.value:
|
||||
raise ParseError(
|
||||
'Invalid item "%s" '
|
||||
'(to specify an empty header use `Header;`)'
|
||||
% item.orig
|
||||
)
|
||||
target = headers
|
||||
elif item.sep == SEP_QUERY:
|
||||
target = params
|
||||
|
@ -5,6 +5,7 @@ import requests.auth
|
||||
from httpie.plugins.base import AuthPlugin
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class BuiltinAuthPlugin(AuthPlugin):
|
||||
|
||||
package_name = '(builtin)'
|
||||
|
5
setup.py
5
setup.py
@ -35,10 +35,11 @@ tests_require = [
|
||||
|
||||
|
||||
install_requires = [
|
||||
'requests>=2.3.0',
|
||||
'Pygments>=1.5'
|
||||
'requests>=2.11.0',
|
||||
'Pygments>=2.1.3'
|
||||
]
|
||||
|
||||
|
||||
# Conditional dependencies:
|
||||
|
||||
# sdist
|
||||
|
@ -68,10 +68,11 @@ class TestItemParsing:
|
||||
def test_valid_items(self):
|
||||
items = input.parse_items([
|
||||
self.key_value('string=value'),
|
||||
self.key_value('header:value'),
|
||||
self.key_value('Header:value'),
|
||||
self.key_value('Unset-Header:'),
|
||||
self.key_value('Empty-Header;'),
|
||||
self.key_value('list:=["a", 1, {}, false]'),
|
||||
self.key_value('obj:={"a": "b"}'),
|
||||
self.key_value('eh:'),
|
||||
self.key_value('ed='),
|
||||
self.key_value('bool:=true'),
|
||||
self.key_value('file@' + FILE_PATH_ARG),
|
||||
@ -83,7 +84,11 @@ class TestItemParsing:
|
||||
# Parsed headers
|
||||
# `requests.structures.CaseInsensitiveDict` => `dict`
|
||||
headers = dict(items.headers._store.values())
|
||||
assert headers == {'header': 'value', 'eh': ''}
|
||||
assert headers == {
|
||||
'Header': 'value',
|
||||
'Unset-Header': None,
|
||||
'Empty-Header': ''
|
||||
}
|
||||
|
||||
# Parsed data
|
||||
raw_json_embed = items.data.pop('raw-json-embed')
|
||||
@ -103,8 +108,8 @@ class TestItemParsing:
|
||||
|
||||
# Parsed file fields
|
||||
assert 'file' in items.files
|
||||
assert (items.files['file'][1].read().strip().decode('utf8')
|
||||
== FILE_CONTENT)
|
||||
assert (items.files['file'][1].read().strip().
|
||||
decode('utf8') == FILE_CONTENT)
|
||||
|
||||
def test_multiple_file_fields_with_same_field_name(self):
|
||||
items = input.parse_items([
|
||||
@ -325,8 +330,18 @@ class TestIgnoreStdin:
|
||||
|
||||
class TestSchemes:
|
||||
|
||||
def test_custom_scheme(self):
|
||||
def test_invalid_custom_scheme(self):
|
||||
# InvalidSchema is expected because HTTPie
|
||||
# shouldn't touch a formally valid scheme.
|
||||
with pytest.raises(InvalidSchema):
|
||||
http('foo+bar-BAZ.123://bah')
|
||||
|
||||
def test_invalid_scheme_via_via_default_scheme(self):
|
||||
# InvalidSchema is expected because HTTPie
|
||||
# shouldn't touch a formally valid scheme.
|
||||
with pytest.raises(InvalidSchema):
|
||||
http('bah', '--default=scheme=foo+bar-BAZ.123')
|
||||
|
||||
def test_default_scheme(self, httpbin_secure):
|
||||
url = '{0}:{1}'.format(httpbin_secure.host, httpbin_secure.port)
|
||||
assert HTTP_OK in http(url, '--default-scheme=https')
|
||||
|
@ -2,6 +2,7 @@
|
||||
Tests for the provided defaults regarding HTTP method, and --json vs. --form.
|
||||
|
||||
"""
|
||||
from httpie.client import JSON_ACCEPT
|
||||
from utils import TestEnvironment, http, HTTP_OK
|
||||
from fixtures import FILE_PATH
|
||||
|
||||
@ -58,20 +59,20 @@ class TestAutoContentTypeAndAcceptHeaders:
|
||||
def test_POST_with_data_auto_JSON_headers(self, httpbin):
|
||||
r = http('POST', httpbin.url + '/post', 'a=b')
|
||||
assert HTTP_OK in r
|
||||
assert '"Accept": "application/json"' in r
|
||||
assert '"Content-Type": "application/json' in r
|
||||
assert r.json['headers']['Accept'] == JSON_ACCEPT
|
||||
assert r.json['headers']['Content-Type'] == 'application/json'
|
||||
|
||||
def test_GET_with_data_auto_JSON_headers(self, httpbin):
|
||||
# JSON headers should automatically be set also for GET with data.
|
||||
r = http('POST', httpbin.url + '/post', 'a=b')
|
||||
assert HTTP_OK in r
|
||||
assert '"Accept": "application/json"' in r, r
|
||||
assert '"Content-Type": "application/json' in r
|
||||
assert r.json['headers']['Accept'] == JSON_ACCEPT
|
||||
assert r.json['headers']['Content-Type'] == 'application/json'
|
||||
|
||||
def test_POST_explicit_JSON_auto_JSON_accept(self, httpbin):
|
||||
r = http('--json', 'POST', httpbin.url + '/post')
|
||||
assert HTTP_OK in r
|
||||
assert r.json['headers']['Accept'] == 'application/json'
|
||||
assert r.json['headers']['Accept'] == JSON_ACCEPT
|
||||
# Make sure Content-Type gets set even with no data.
|
||||
# https://github.com/jkbrzt/httpie/issues/137
|
||||
assert 'application/json' in r.json['headers']['Content-Type']
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""High-level tests."""
|
||||
import pytest
|
||||
|
||||
from httpie.input import ParseError
|
||||
from utils import TestEnvironment, http, HTTP_OK
|
||||
from fixtures import FILE_PATH, FILE_CONTENT
|
||||
|
||||
@ -75,6 +77,27 @@ def test_headers(httpbin_both):
|
||||
assert '"Foo": "bar"' in r
|
||||
|
||||
|
||||
def test_headers_unset(httpbin_both):
|
||||
r = http('GET', httpbin_both + '/headers')
|
||||
assert 'Accept' in r.json['headers'] # default Accept present
|
||||
|
||||
r = http('GET', httpbin_both + '/headers', 'Accept:')
|
||||
assert 'Accept' not in r.json['headers'] # default Accept unset
|
||||
|
||||
|
||||
def test_headers_empty_value(httpbin_both):
|
||||
r = http('GET', httpbin_both + '/headers')
|
||||
assert r.json['headers']['Accept'] # default Accept has value
|
||||
|
||||
r = http('GET', httpbin_both + '/headers', 'Accept;')
|
||||
assert r.json['headers']['Accept'] == '' # Accept has no value
|
||||
|
||||
|
||||
def test_headers_empty_value_with_value_gives_error(httpbin):
|
||||
with pytest.raises(ParseError):
|
||||
http('GET', httpbin + '/headers', 'Accept;SYNTAX_ERROR')
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
is_py26,
|
||||
reason='the `object_pairs_hook` arg for `json.loads()` is Py>2.6 only'
|
||||
|
10
tox.ini
10
tox.ini
@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
[tox]
|
||||
envlist = py26, py27, py35, pypy
|
||||
envlist = py26, py27, py35, pypy, codestyle
|
||||
|
||||
|
||||
[testenv]
|
||||
@ -20,3 +20,11 @@ commands =
|
||||
--verbose \
|
||||
--doctest-modules \
|
||||
{posargs:./httpie ./tests}
|
||||
|
||||
[testenv:codestyle]
|
||||
deps = pycodestyle
|
||||
commands =
|
||||
pycodestyle \
|
||||
--ignore=E241,E501
|
||||
# 241 - multiple spaces after ‘,’
|
||||
# 501 - line too long
|
||||
|
Reference in New Issue
Block a user