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:
parent
f74424ef03
commit
47de4e2c9c
20
README.rst
20
README.rst
@ -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
|
||||||
|
@ -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
|
||||||
''')
|
''')
|
||||||
)
|
)
|
||||||
|
@ -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__':
|
||||||
|
@ -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\'.')
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user