Added initial support for persistent sessions.

This commit is contained in:
Jakub Roztocil 2012-08-17 23:23:02 +02:00
parent 1ed43c1a1e
commit 0b3bad9c81
6 changed files with 188 additions and 65 deletions

View File

@ -502,6 +502,30 @@ path. The path can also be configured via the environment variable
``REQUESTS_CA_BUNDLE``. ``REQUESTS_CA_BUNDLE``.
========
Sessions
========
HTTPie supports named sessions, where several options and cookies sent
by the server persists between requests:
.. code-block:: bash
http --session=user1 --auth=user1:password example.org
Now you can always refer to the session by passing ``--session=user1``,
and the credentials and cookies will be reused:
http --session=user1 GET example.org
Since sessions are named, you can switch between multiple sessions:
http --session=user2 --auth=user2:password example.org
Note that session cookies respect domain and path.
Session data are store in ``~/httpie/sessions/<name>.pickle``.
============== ==============
Output Options Output Options
============== ==============
@ -958,6 +982,7 @@ Changelog
*You can click a version name to see a diff with the previous one.* *You can click a version name to see a diff with the previous one.*
* `0.2.8-alpha`_ * `0.2.8-alpha`_
* Added session support.
* CRLF HTTP header field separation in the output. * CRLF HTTP header field separation in the output.
* Added exit status code ``2`` for timed-out requests. * Added exit status code ``2`` for timed-out requests.
* Added the option to separate colorizing and formatting * Added the option to separate colorizing and formatting

View File

@ -169,6 +169,16 @@ parser.add_argument(
''') ''')
) )
parser.add_argument(
'--session', metavar='NAME',
help=_('''
Create or reuse a session.
Withing a session, values of --auth, --timeout,
--verify, --proxies are persistent, as well as any
cookies sent by the server.
''')
)
# ``requests.request`` keyword arguments. # ``requests.request`` keyword arguments.
parser.add_argument( parser.add_argument(
'--auth', '-a', metavar='USER[:PASS]', '--auth', '-a', metavar='USER[:PASS]',

77
httpie/client.py Normal file
View File

@ -0,0 +1,77 @@
import json
import sys
from pprint import pformat
import requests
import requests.auth
from .import sessions
FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8'
def get_response(args):
requests_kwargs = get_requests_kwargs(args)
if args.debug:
sys.stderr.write(
'\n>>> requests.request(%s)\n\n' % pformat(requests_kwargs))
if args.session:
return sessions.get_response(args.session, requests_kwargs)
else:
return requests.request(**requests_kwargs)
def get_requests_kwargs(args):
"""Send the request and return a `request.Response`."""
auto_json = args.data and not args.form
if args.json or auto_json:
if 'Content-Type' not in args.headers and args.data:
args.headers['Content-Type'] = JSON
if 'Accept' not in args.headers:
# Default Accept to JSON as well.
args.headers['Accept'] = 'application/json'
if isinstance(args.data, dict):
# If not empty, serialize the data `dict` parsed from arguments.
# Otherwise set it to `None` avoid sending "{}".
args.data = json.dumps(args.data) if args.data else None
elif args.form:
if not args.files and 'Content-Type' not in args.headers:
# If sending files, `requests` will set
# the `Content-Type` for us.
args.headers['Content-Type'] = FORM
credentials = None
if args.auth:
credentials = {
'basic': requests.auth.HTTPBasicAuth,
'digest': requests.auth.HTTPDigestAuth,
}[args.auth_type](args.auth.key, args.auth.value)
kwargs = {
'prefetch': False,
'method': args.method.lower(),
'url': args.url,
'headers': args.headers,
'data': args.data,
'verify': {
'yes': True,
'no': False
}.get(args.verify,args.verify),
'timeout': args.timeout,
'auth': credentials,
'proxies': dict((p.key, p.value) for p in args.proxy),
'files': args.files,
'allow_redirects': args.allow_redirects,
'params': args.params
}
return kwargs

6
httpie/config.py Normal file
View File

@ -0,0 +1,6 @@
import os
__author__ = 'jakub'
CONFIG_DIR = os.path.expanduser('~/.httpie')

View File

@ -10,79 +10,22 @@ Invocation flow:
5. Exit. 5. Exit.
""" """
from _socket import gaierror
import sys import sys
import json
import errno import errno
from pprint import pformat
import requests import requests
import requests.auth
from requests.compat import str from requests.compat import str
from httpie import __version__ as httpie_version from httpie import __version__ as httpie_version
from requests import __version__ as requests_version from requests import __version__ as requests_version
from pygments import __version__ as pygments_version from pygments import __version__ as pygments_version
from .cli import parser from .cli import parser
from .client import get_response
from .models import Environment from .models import Environment
from .output import output_stream, write from .output import output_stream, write
from . import EXIT from . import EXIT
FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8'
def get_requests_kwargs(args):
"""Send the request and return a `request.Response`."""
auto_json = args.data and not args.form
if args.json or auto_json:
if 'Content-Type' not in args.headers and args.data:
args.headers['Content-Type'] = JSON
if 'Accept' not in args.headers:
# Default Accept to JSON as well.
args.headers['Accept'] = 'application/json'
if isinstance(args.data, dict):
# If not empty, serialize the data `dict` parsed from arguments.
# Otherwise set it to `None` avoid sending "{}".
args.data = json.dumps(args.data) if args.data else None
elif args.form:
if not args.files and 'Content-Type' not in args.headers:
# If sending files, `requests` will set
# the `Content-Type` for us.
args.headers['Content-Type'] = FORM
credentials = None
if args.auth:
credentials = {
'basic': requests.auth.HTTPBasicAuth,
'digest': requests.auth.HTTPDigestAuth,
}[args.auth_type](args.auth.key, args.auth.value)
kwargs = {
'prefetch': False,
'method': args.method.lower(),
'url': args.url,
'headers': args.headers,
'data': args.data,
'verify': {
'yes': True,
'no': False
}.get(args.verify,args.verify),
'timeout': args.timeout,
'auth': credentials,
'proxies': dict((p.key, p.value) for p in args.proxy),
'files': args.files,
'allow_redirects': args.allow_redirects,
'params': args.params
}
return kwargs
def get_exist_status(code, allow_redirects=False): def get_exist_status(code, allow_redirects=False):
"""Translate HTTP status code to exit status.""" """Translate HTTP status code to exit status."""
@ -121,13 +64,7 @@ def main(args=sys.argv[1:], env=Environment()):
try: try:
args = parser.parse_args(args=args, env=env) args = parser.parse_args(args=args, env=env)
kwargs = get_requests_kwargs(args) response = get_response(args)
if args.debug:
sys.stderr.write(
'\n>>> requests.request(%s)\n\n' % pformat(kwargs))
response = requests.request(**kwargs)
if args.check_status: if args.check_status:
status = get_exist_status(response.status_code, status = get_exist_status(response.status_code,

68
httpie/sessions.py Normal file
View File

@ -0,0 +1,68 @@
import os
import pickle
import errno
from requests import Session
from .config import CONFIG_DIR
SESSIONS_DIR = os.path.join(CONFIG_DIR, 'sessions')
def get_response(name, request_kwargs):
session = load(name)
session_kwargs, request_kwargs = split_kwargs(request_kwargs)
headers = session_kwargs.pop('headers', None)
if headers:
session.headers.update(headers)
session.__dict__.update(session_kwargs)
try:
response = session.request(**request_kwargs)
except Exception:
raise
else:
save(session, name)
return response
def split_kwargs(requests_kwargs):
session = {}
request = {}
session_attrs = [
'auth', 'timeout',
'verify', 'proxies',
'params'
]
for k, v in requests_kwargs.items():
if v is not None:
if k in session_attrs:
session[k] = v
else:
request[k] = v
return session, request
def get_path(name):
try:
os.makedirs(SESSIONS_DIR, mode=0o700)
except OSError as e:
if e.errno != errno.EEXIST:
raise
return os.path.join(SESSIONS_DIR, name + '.pickle')
def load(name):
try:
with open(get_path(name), 'rb') as f:
return pickle.load(f)
except IOError as e:
if e.errno != errno.ENOENT:
raise
return Session()
def save(session, name):
with open(get_path(name), 'wb') as f:
pickle.dump(session, f)