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``.
========
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
==============
@ -958,6 +982,7 @@ Changelog
*You can click a version name to see a diff with the previous one.*
* `0.2.8-alpha`_
* Added session support.
* CRLF HTTP header field separation in the output.
* Added exit status code ``2`` for timed-out requests.
* 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.
parser.add_argument(
'--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.
"""
from _socket import gaierror
import sys
import json
import errno
from pprint import pformat
import requests
import requests.auth
from requests.compat import str
from httpie import __version__ as httpie_version
from requests import __version__ as requests_version
from pygments import __version__ as pygments_version
from .cli import parser
from .client import get_response
from .models import Environment
from .output import output_stream, write
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):
"""Translate HTTP status code to exit status."""
@ -121,13 +64,7 @@ def main(args=sys.argv[1:], env=Environment()):
try:
args = parser.parse_args(args=args, env=env)
kwargs = get_requests_kwargs(args)
if args.debug:
sys.stderr.write(
'\n>>> requests.request(%s)\n\n' % pformat(kwargs))
response = requests.request(**kwargs)
response = get_response(args)
if args.check_status:
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)