Skip to content

Commit

Permalink
Make sure session and default headers play nice
Browse files Browse the repository at this point in the history
Before: headers = default + args + session
Now:    headers = default + session + args

Fixes #180
  • Loading branch information
jkbrzt committed May 8, 2014
1 parent 3e1b62f commit 858555a
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 44 deletions.
1 change: 1 addition & 0 deletions README.rst
Expand Up @@ -1246,6 +1246,7 @@ Changelog
* Added ``--cert`` and ``--certkey`` parameters to specify a client side
certificate and private key for SSL
* Improved unicode support.
* Fixed ``User-Agent`` overwriting when used within a session.
* Switched from ``unittest`` to ``pytest``.
* Various test suite improvements.
* Added `CONTRIBUTING`_.
Expand Down
70 changes: 41 additions & 29 deletions httpie/client.py
Expand Up @@ -18,26 +18,27 @@
def get_response(args, config_dir):
"""Send the request and return a `request.Response`."""

requests_kwargs = get_requests_kwargs(args)

if args.debug:
sys.stderr.write('\n>>> requests.request(%s)\n\n'
% pformat(requests_kwargs))

if not args.session and not args.session_read_only:
requests_kwargs = get_requests_kwargs(args)
if args.debug:
dump_request(requests_kwargs)
response = requests.request(**requests_kwargs)
else:
response = sessions.get_response(
args=args,
config_dir=config_dir,
session_name=args.session or args.session_read_only,
requests_kwargs=requests_kwargs,
read_only=bool(args.session_read_only),
)

return response


def dump_request(kwargs):
sys.stderr.write('\n>>> requests.request(%s)\n\n'
% pformat(kwargs))


def encode_headers(headers):
# This allows for unicode headers which is non-standard but practical.
# See: https://github.com/jakubroztocil/httpie/issues/212
Expand All @@ -47,36 +48,47 @@ def encode_headers(headers):
)


def get_requests_kwargs(args):
"""Translate our `args` into `requests.request` keyword arguments."""

implicit_headers = {
def get_default_headers(args):
default_headers = {
'User-Agent': DEFAULT_UA
}

auto_json = args.data and not args.form
# FIXME: Accept is set to JSON with `http url @./file.txt`.
if args.json or auto_json:
implicit_headers['Accept'] = 'application/json'
default_headers['Accept'] = 'application/json'
if args.json or (auto_json and args.data):
implicit_headers['Content-Type'] = JSON

if isinstance(args.data, dict):
if args.data:
args.data = json.dumps(args.data)
else:
# We need to set data to an empty string to prevent requests
# from assigning an empty list to `response.request.data`.
args.data = ''
default_headers['Content-Type'] = JSON

elif args.form and not args.files:
# If sending files, `requests` will set
# the `Content-Type` for us.
implicit_headers['Content-Type'] = FORM

for name, value in implicit_headers.items():
if name not in args.headers:
args.headers[name] = value
default_headers['Content-Type'] = FORM
return default_headers


def get_requests_kwargs(args, base_headers=None):
"""
Translate our `args` into `requests.request` keyword arguments.
"""
# Serialize JSON data, if needed.
data = args.data
auto_json = data and not args.form
if args.json or auto_json and isinstance(data, dict):
if data:
data = json.dumps(data)
else:
# We need to set data to an empty string to prevent requests
# from assigning an empty list to `response.request.data`.
data = ''

# Finalize headers.
headers = get_default_headers(args)
if base_headers:
headers.update(base_headers)
headers.update(args.headers)
headers = encode_headers(headers)

credentials = None
if args.auth:
Expand All @@ -87,14 +99,14 @@ def get_requests_kwargs(args):
if args.cert:
cert = args.cert
if args.certkey:
cert = (cert, args.certkey)
cert = cert, args.certkey

kwargs = {
'stream': True,
'method': args.method.lower(),
'url': args.url,
'headers': encode_headers(args.headers),
'data': args.data,
'headers': headers,
'data': data,
'verify': {
'yes': True,
'no': False
Expand Down
23 changes: 8 additions & 15 deletions httpie/sessions.py
Expand Up @@ -21,21 +21,17 @@
SESSION_IGNORED_HEADER_PREFIXES = ['Content-', 'If-']


def get_response(session_name, requests_kwargs, config_dir, args,
read_only=False):
def get_response(session_name, config_dir, args, read_only=False):
"""Like `client.get_response`, but applies permanent
aspects of the session to the request.
"""
from .client import encode_headers
from .client import get_requests_kwargs, dump_request
if os.path.sep in session_name:
path = os.path.expanduser(session_name)
else:
hostname = (
requests_kwargs['headers'].get('Host', None)
or urlsplit(requests_kwargs['url']).netloc.split('@')[-1]
)

hostname = (args.headers.get('Host', None)
or urlsplit(args.url).netloc.split('@')[-1])
assert re.match('^[a-zA-Z0-9_.:-]+$', hostname)

# host:port => host_port
Expand All @@ -48,13 +44,10 @@ def get_response(session_name, requests_kwargs, config_dir, args,
session = Session(path)
session.load()

request_headers = requests_kwargs.get('headers', {})

merged_headers = dict(session.headers)
merged_headers.update(request_headers)
requests_kwargs['headers'] = encode_headers(merged_headers)

session.update_headers(request_headers)
requests_kwargs = get_requests_kwargs(args, base_headers=session.headers)
if args.debug:
dump_request(requests_kwargs)
session.update_headers(requests_kwargs['headers'])

if args.auth:
session.auth = {
Expand Down
11 changes: 11 additions & 0 deletions tests/test_sessions.py
Expand Up @@ -137,3 +137,14 @@ def test_session_unicode(self):
assert (r2.json['headers']['Authorization']
== HTTPBasicAuth.make_header(u'test', UNICODE))
assert r2.json['headers']['Test'] == UNICODE

def test_session_default_header_value_overwritten(self):
# https://github.com/jakubroztocil/httpie/issues/180
r1 = http('--session=test', httpbin('/headers'), 'User-Agent:custom',
env=self.env())
assert HTTP_OK in r1
assert r1.json['headers']['User-Agent'] == 'custom'

r2 = http('--session=test', httpbin('/headers'), env=self.env())
assert HTTP_OK in r2
assert r2.json['headers']['User-Agent'] == 'custom'

0 comments on commit 858555a

Please sign in to comment.