Permalink
Browse files

Sessions are now host-bound.

  • Loading branch information...
1 parent f74424e commit 47de4e2c9c5c92ebb97124bdcb9c7d021d1d3d85 @jkbrzt committed Aug 19, 2012
Showing with 129 additions and 91 deletions.
  1. +12 −8 README.rst
  2. +1 −1 httpie/cli.py
  3. +2 −2 httpie/manage.py
  4. +114 −80 httpie/sessions.py
View
@@ -506,31 +506,35 @@ path. The path can also be configured via the environment variable
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:
.. 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
- 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``
-(``%APPDATA%\httpie\sessions`` on Windows).
+.. code-block:: bash
+
+ $ http --session user2 -a user2:password example.org X-Bar:Foo
You can view and manipulate existing sessions via the ``httpie`` management
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
View
@@ -25,7 +25,7 @@ def _(text):
parser = Parser(
description='%s <http://httpie.org>' % __doc__.strip(),
epilog=_('''
- Suggestions and bugs reports are appreciated:
+ Suggestions and bug reports are greatly appreciated:
https://github.com/jkbr/httpie/issues
''')
)
View
@@ -18,12 +18,12 @@
# Only sessions as of now.
-sessions.add_actions(subparsers)
+sessions.add_commands(subparsers)
def main():
args = parser.parse_args()
- args.action(args)
+ args.command(args)
if __name__ == '__main__':
View
@@ -1,6 +1,7 @@
"""Persistent, JSON-serialized sessions.
"""
+import shutil
import os
import sys
import json
@@ -9,6 +10,7 @@
import codecs
import subprocess
+from requests.compat import urlparse
from requests import Session as RSession
from requests.cookies import RequestsCookieJar, create_cookie
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
@@ -23,21 +25,23 @@
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.
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)
if auth:
session.auth = auth
elif session.auth:
request_kwargs['auth'] = session.auth
-
- # Use the merged headers for the request
- request_kwargs['headers'] = session['headers']
-
rsession = RSession(cookies=session.cookies)
try:
response = rsession.request(**request_kwargs)
@@ -49,17 +53,73 @@ def get_response(name, request_kwargs):
return response
+class Host(object):
+
+ def __init__(self, name):
+ self.name = name
+
+ 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
+ def path(self):
+ 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, name, *args, **kwargs):
+ def __init__(self, host, name, *args, **kwargs):
super(Session, self).__init__(*args, **kwargs)
+ self.host = host
self.name = name
- self.setdefault('cookies', {})
- self.setdefault('headers', {})
+ self['headers'] = {}
+ self['cookies'] = {}
@property
def path(self):
- return type(self).get_path(self.name)
+ 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
def cookies(self):
@@ -73,18 +133,18 @@ def cookies(self):
@cookies.setter
def cookies(self, jar):
- exclude = [
+ excluded = [
'_rest', 'name', 'port_specified',
'domain_specified', 'domain_initial_dot',
- 'path_specified'
+ 'path_specified', 'comment', 'comment_url'
]
self['cookies'] = {}
for host in jar._cookies.values():
for path in host.values():
for name, cookie in path.items():
cookie_dict = {}
for k, v in cookie.__dict__.items():
- if k not in exclude:
+ if k not in excluded:
cookie_dict[k] = v
self['cookies'][name] = cookie_dict
@@ -97,7 +157,6 @@ def auth(self):
'digest': HTTPDigestAuth}[auth['type']]
return Auth(auth['username'], auth['password'])
-
@auth.setter
def auth(self, cred):
self['auth'] = {
@@ -107,105 +166,80 @@ def auth(self, cred):
'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 load(cls, name):
- try:
- with open(cls.get_path(name), 'rt') as f:
- try:
- data = json.load(f)
- except ValueError as e:
- raise ValueError('Invalid session: %s [%s]' %
- (e.message, f.name))
-
- 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 list_command(args):
+ if args.host:
+ for name, path in Host(args.host):
+ print(name + ' [' + path + ']')
+ else:
+ for host in Host.all():
+ print(host.name)
+ for name, path in host:
+ print(' ' + name + ' [' + path + ']')
-def show_action(args):
- if not args.name:
- for fn in sorted(glob.glob1(SESSIONS_DIR, '*.json')):
- print(os.path.splitext(fn)[0])
- return
- path = Session.get_path(args.name)
+def show_command(args):
+ path = Session(Host(args.host), args.name).path
if not os.path.exists(path):
sys.stderr.write('Session "%s" does not exist [%s].\n'
% (args.name, path))
sys.exit(1)
with codecs.open(path, encoding='utf8') as f:
print(path + ':\n')
- print(PygmentsProcessor().process_body(
- f.read(), 'application/json', 'json'))
+ proc = PygmentsProcessor()
+ print(proc.process_body(f.read(), 'application/json', 'json'))
print('')
-def delete_action(args):
+def delete_command(args):
+ host = Host(args.host)
if not args.name:
- for path in glob.glob(os.path.join(SESSIONS_DIR, '*.json')):
- 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)
+ host.delete()
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)
if not editor:
sys.stderr.write(
'You need to configure the environment variable EDITOR.\n')
sys.exit(1)
command = editor.split()
- command.append(Session.get_path(args.name))
+ command.append(Session(Host(args.host), args.name).path)
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 = subparsers.add_parser('session-show', help='list or show sessions')
- show.set_defaults(action=show_action)
- show.add_argument('name', nargs='?',
- help='When omitted, HTTPie prints a list of existing sessions.'
- ' When specified, the session data is printed.')
+ show.set_defaults(command=show_command)
+ show.add_argument('host')
+ show.add_argument('name')
# Edit
- edit = subparsers.add_parser('session-edit', help='edit a session in $EDITOR')
- edit.set_defaults(action=edit_action)
+ edit = subparsers.add_parser(
+ 'session-edit', help='edit a session in $EDITOR')
+ edit.set_defaults(command=edit_command)
+ edit.add_argument('host')
edit.add_argument('name')
# Delete
delete = subparsers.add_parser('session-delete', help='delete a session')
- delete.set_defaults(action=delete_action)
- delete_group = delete.add_mutually_exclusive_group(required=True)
- delete_group.add_argument(
- '--all', action='store_true',
- help='Delete all sessions from %s' % SESSIONS_DIR)
- 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\'.')
+ delete.set_defaults(command=delete_command)
+ delete.add_argument('host')
+ delete.add_argument('name', nargs='?',
+ help='The name of the session to be deleted.'
+ ' If not specified, all host sessions are deleted.')

0 comments on commit 47de4e2

Please sign in to comment.