1
0
mirror of https://github.com/httpie/cli.git synced 2025-02-19 19:00:14 +02:00

Sessions are now host-bound.

This commit is contained in:
Jakub Roztocil 2012-08-19 04:58:14 +02:00
parent f74424ef03
commit 47de4e2c9c
4 changed files with 131 additions and 93 deletions

View File

@ -506,31 +506,35 @@ path. The path can also be configured via the environment variable
Sessions Sessions
======== ========
*This is an experimental feature.* *NOTE: This is an experimental feature. Feedback appretiated.*
HTTPie supports named sessions, where custom headers, authorization, HTTPie supports named, per-host sessions, where custom headers, authorization,
and cookies sent by the server persist between requests: and cookies sent by the server persist between requests:
.. code-block:: bash .. code-block:: bash
http --session=user1 --auth=user1:password example.org X-Foo:Bar $ http --session user1 -a user1:password example.org X-Foo:Bar
Now you can always refer to the session by passing ``--session=user1``: Now you can refer to the session by its name:
.. code-block:: bash .. code-block:: bash
http --session=user1 example.org $ http --session user1 example.org
Note that cookies respect the cookie domain and path. To switch to another session simple pass a different name:
Session data are stored in ``~/.httpie/sessions/<name>.json`` .. code-block:: bash
(``%APPDATA%\httpie\sessions`` on Windows).
$ http --session user2 -a user2:password example.org X-Bar:Foo
You can view and manipulate existing sessions via the ``httpie`` management You can view and manipulate existing sessions via the ``httpie`` management
command, see ``httpie --help``. command, see ``httpie --help``.
Sessions are stored as JSON in ``~/.httpie/sessions/<host>/<name>.json``
(``%APPDATA%\httpie\sessions\<host>\<name>.json`` on Windows).
============== ==============
Output Options Output Options

View File

@ -25,7 +25,7 @@ def _(text):
parser = Parser( parser = Parser(
description='%s <http://httpie.org>' % __doc__.strip(), description='%s <http://httpie.org>' % __doc__.strip(),
epilog=_(''' epilog=_('''
Suggestions and bugs reports are appreciated: Suggestions and bug reports are greatly appreciated:
https://github.com/jkbr/httpie/issues https://github.com/jkbr/httpie/issues
''') ''')
) )

View File

@ -18,12 +18,12 @@ subparsers = parser.add_subparsers()
# Only sessions as of now. # Only sessions as of now.
sessions.add_actions(subparsers) sessions.add_commands(subparsers)
def main(): def main():
args = parser.parse_args() args = parser.parse_args()
args.action(args) args.command(args)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,6 +1,7 @@
"""Persistent, JSON-serialized sessions. """Persistent, JSON-serialized sessions.
""" """
import shutil
import os import os
import sys import sys
import json import json
@ -9,6 +10,7 @@ import errno
import codecs import codecs
import subprocess import subprocess
from requests.compat import urlparse
from requests import Session as RSession from requests import Session as RSession
from requests.cookies import RequestsCookieJar, create_cookie from requests.cookies import RequestsCookieJar, create_cookie
from requests.auth import HTTPBasicAuth, HTTPDigestAuth from requests.auth import HTTPBasicAuth, HTTPDigestAuth
@ -23,10 +25,16 @@ SESSIONS_DIR = os.path.join(CONFIG_DIR, 'sessions')
def get_response(name, request_kwargs): def get_response(name, request_kwargs):
session = Session.load(name) host = Host(request_kwargs['headers'].get('Host', None)
or urlparse(request_kwargs['url']).netloc.split('@')[-1])
session = Session(host, name)
session.load()
# Update session headers with the request headers. # Update session headers with the request headers.
session['headers'].update(request_kwargs.get('headers', {})) session['headers'].update(request_kwargs.get('headers', {}))
# Use the merged headers for the request
request_kwargs['headers'] = session['headers']
auth = request_kwargs.get('auth', None) auth = request_kwargs.get('auth', None)
if auth: if auth:
@ -34,10 +42,6 @@ def get_response(name, request_kwargs):
elif session.auth: elif session.auth:
request_kwargs['auth'] = session.auth request_kwargs['auth'] = session.auth
# Use the merged headers for the request
request_kwargs['headers'] = session['headers']
rsession = RSession(cookies=session.cookies) rsession = RSession(cookies=session.cookies)
try: try:
response = rsession.request(**request_kwargs) response = rsession.request(**request_kwargs)
@ -49,17 +53,73 @@ def get_response(name, request_kwargs):
return response return response
class Session(dict): class Host(object):
def __init__(self, name, *args, **kwargs): def __init__(self, name):
super(Session, self).__init__(*args, **kwargs)
self.name = name self.name = name
self.setdefault('cookies', {})
self.setdefault('headers', {}) def __iter__(self):
for fn in sorted(glob.glob1(self.path, '*.json')):
yield os.path.splitext(fn)[0], os.path.join(self.path, fn)
def delete(self):
shutil.rmtree(self.path)
@property @property
def path(self): def path(self):
return type(self).get_path(self.name) path = os.path.join(SESSIONS_DIR, self.name)
try:
os.makedirs(path, mode=0o700)
except OSError as e:
if e.errno != errno.EEXIST:
raise
return path
@classmethod
def all(cls):
for name in sorted(glob.glob1(SESSIONS_DIR, '*')):
if os.path.isdir(os.path.join(SESSIONS_DIR, name)):
yield Host(name)
class Session(dict):
def __init__(self, host, name, *args, **kwargs):
super(Session, self).__init__(*args, **kwargs)
self.host = host
self.name = name
self['headers'] = {}
self['cookies'] = {}
@property
def path(self):
return os.path.join(self.host.path, self.name + '.json')
def load(self):
try:
with open(self.path, 'rt') as f:
try:
data = json.load(f)
except ValueError as e:
raise ValueError('Invalid session: %s [%s]' %
(e.message, self.path))
self.update(data)
except IOError as e:
if e.errno != errno.ENOENT:
raise
def save(self):
self['__version__'] = __version__
with open(self.path, 'wb') as f:
json.dump(self, f, indent=4, sort_keys=True, ensure_ascii=True)
f.write(b'\n')
def delete(self):
try:
os.unlink(self.path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
@property @property
def cookies(self): def cookies(self):
@ -73,10 +133,10 @@ class Session(dict):
@cookies.setter @cookies.setter
def cookies(self, jar): def cookies(self, jar):
exclude = [ excluded = [
'_rest', 'name', 'port_specified', '_rest', 'name', 'port_specified',
'domain_specified', 'domain_initial_dot', 'domain_specified', 'domain_initial_dot',
'path_specified' 'path_specified', 'comment', 'comment_url'
] ]
self['cookies'] = {} self['cookies'] = {}
for host in jar._cookies.values(): for host in jar._cookies.values():
@ -84,7 +144,7 @@ class Session(dict):
for name, cookie in path.items(): for name, cookie in path.items():
cookie_dict = {} cookie_dict = {}
for k, v in cookie.__dict__.items(): for k, v in cookie.__dict__.items():
if k not in exclude: if k not in excluded:
cookie_dict[k] = v cookie_dict[k] = v
self['cookies'][name] = cookie_dict self['cookies'][name] = cookie_dict
@ -97,7 +157,6 @@ class Session(dict):
'digest': HTTPDigestAuth}[auth['type']] 'digest': HTTPDigestAuth}[auth['type']]
return Auth(auth['username'], auth['password']) return Auth(auth['username'], auth['password'])
@auth.setter @auth.setter
def auth(self, cred): def auth(self, cred):
self['auth'] = { self['auth'] = {
@ -107,46 +166,20 @@ class Session(dict):
'password': cred.password, 'password': cred.password,
} }
def save(self):
self['__version__'] = __version__
with open(self.path, 'wb') as f:
json.dump(self, f, indent=4, sort_keys=True, ensure_ascii=True)
f.write(b'\n')
@classmethod def list_command(args):
def load(cls, name): if args.host:
try: for name, path in Host(args.host):
with open(cls.get_path(name), 'rt') as f: print(name + ' [' + path + ']')
try: else:
data = json.load(f) for host in Host.all():
except ValueError as e: print(host.name)
raise ValueError('Invalid session: %s [%s]' % for name, path in host:
(e.message, f.name)) print(' ' + name + ' [' + path + ']')
return cls(name, data)
except IOError as e:
if e.errno != errno.ENOENT:
raise
return cls(name)
@classmethod
def get_path(cls, 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 + '.json')
def show_action(args): def show_command(args):
if not args.name: path = Session(Host(args.host), args.name).path
for fn in sorted(glob.glob1(SESSIONS_DIR, '*.json')):
print(os.path.splitext(fn)[0])
return
path = Session.get_path(args.name)
if not os.path.exists(path): if not os.path.exists(path):
sys.stderr.write('Session "%s" does not exist [%s].\n' sys.stderr.write('Session "%s" does not exist [%s].\n'
% (args.name, path)) % (args.name, path))
@ -154,58 +187,59 @@ def show_action(args):
with codecs.open(path, encoding='utf8') as f: with codecs.open(path, encoding='utf8') as f:
print(path + ':\n') print(path + ':\n')
print(PygmentsProcessor().process_body( proc = PygmentsProcessor()
f.read(), 'application/json', 'json')) print(proc.process_body(f.read(), 'application/json', 'json'))
print('') print('')
def delete_action(args): def delete_command(args):
host = Host(args.host)
if not args.name: if not args.name:
for path in glob.glob(os.path.join(SESSIONS_DIR, '*.json')): host.delete()
os.unlink(path)
return
path = Session.get_path(args.name)
if not os.path.exists(path):
sys.stderr.write('Session "%s" does not exist [%s].\n'
% (args.name, path))
sys.exit(1)
else: else:
os.unlink(path) session = Session(host, args.name)
try:
session.delete()
except OSError as e:
if e.errno != errno.ENOENT:
raise
def edit_action(args): def edit_command(args):
editor = os.environ.get('EDITOR', None) editor = os.environ.get('EDITOR', None)
if not editor: if not editor:
sys.stderr.write( sys.stderr.write(
'You need to configure the environment variable EDITOR.\n') 'You need to configure the environment variable EDITOR.\n')
sys.exit(1) sys.exit(1)
command = editor.split() command = editor.split()
command.append(Session.get_path(args.name)) command.append(Session(Host(args.host), args.name).path)
subprocess.call(command) subprocess.call(command)
def add_actions(subparsers): def add_commands(subparsers):
# List
list_ = subparsers.add_parser('session-list', help='list sessions')
list_.set_defaults(command=list_command)
list_.add_argument('host', nargs='?')
# Show # Show
show = subparsers.add_parser('session-show', help='list or show sessions') show = subparsers.add_parser('session-show', help='list or show sessions')
show.set_defaults(action=show_action) show.set_defaults(command=show_command)
show.add_argument('name', nargs='?', show.add_argument('host')
help='When omitted, HTTPie prints a list of existing sessions.' show.add_argument('name')
' When specified, the session data is printed.')
# Edit # Edit
edit = subparsers.add_parser('session-edit', help='edit a session in $EDITOR') edit = subparsers.add_parser(
edit.set_defaults(action=edit_action) 'session-edit', help='edit a session in $EDITOR')
edit.set_defaults(command=edit_command)
edit.add_argument('host')
edit.add_argument('name') edit.add_argument('name')
# Delete # Delete
delete = subparsers.add_parser('session-delete', help='delete a session') delete = subparsers.add_parser('session-delete', help='delete a session')
delete.set_defaults(action=delete_action) delete.set_defaults(command=delete_command)
delete_group = delete.add_mutually_exclusive_group(required=True) delete.add_argument('host')
delete_group.add_argument( delete.add_argument('name', nargs='?',
'--all', action='store_true', help='The name of the session to be deleted.'
help='Delete all sessions from %s' % SESSIONS_DIR) ' If not specified, all host sessions are deleted.')
delete_group.add_argument(
'name', nargs='?',
help='The name of the session to be deleted. ' \
'To see a list existing sessions, run `httpie sessions show\'.')