Skip to content

Commit 711b895

Browse files
Merge pull request splunk#209 from abroglesc/additional-headers-get-method
Additional headers on every request and fix unicode decoding issue
2 parents 0a4ac01 + 353d773 commit 711b895

File tree

2 files changed

+51
-33
lines changed

2 files changed

+51
-33
lines changed

splunklib/binding.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,30 @@
2525
"""
2626

2727
from __future__ import absolute_import
28+
29+
import io
2830
import logging
2931
import socket
3032
import ssl
31-
from io import BytesIO
32-
33-
from splunklib.six.moves import urllib
34-
import io
3533
import sys
36-
3734
from base64 import b64encode
35+
from contextlib import contextmanager
3836
from datetime import datetime
3937
from functools import wraps
38+
from io import BytesIO
39+
from xml.etree.ElementTree import XML
40+
41+
from splunklib import six
4042
from splunklib.six import StringIO
43+
from splunklib.six.moves import urllib
4144

42-
from contextlib import contextmanager
45+
from .data import record
4346

44-
from xml.etree.ElementTree import XML
45-
from splunklib import six
4647
try:
4748
from xml.etree.ElementTree import ParseError
4849
except ImportError as e:
4950
from xml.parsers.expat import ExpatError as ParseError
5051

51-
from .data import record
5252

5353
__all__ = [
5454
"AuthenticationError",
@@ -449,6 +449,8 @@ class Context(object):
449449
:type username: ``string``
450450
:param password: The password for the Splunk account.
451451
:type password: ``string``
452+
:param headers: List of extra HTTP headers to send (optional).
453+
:type headers: ``list`` of 2-tuples.
452454
:param handler: The HTTP request handler (optional).
453455
:returns: A ``Context`` instance.
454456
@@ -478,6 +480,7 @@ def __init__(self, handler=None, **kwargs):
478480
self.password = kwargs.get("password", "")
479481
self.basic = kwargs.get("basic", False)
480482
self.autologin = kwargs.get("autologin", False)
483+
self.additional_headers = kwargs.get("headers", [])
481484

482485
# Store any cookies in the self.http._cookies dict
483486
if "cookie" in kwargs and kwargs['cookie'] not in [None, _NoAuthenticationToken]:
@@ -613,7 +616,7 @@ def delete(self, path_segment, owner=None, app=None, sharing=None, **query):
613616

614617
@_authentication
615618
@_log_duration
616-
def get(self, path_segment, owner=None, app=None, sharing=None, **query):
619+
def get(self, path_segment, owner=None, app=None, headers=None, sharing=None, **query):
617620
"""Performs a GET operation from the REST path segment with the given
618621
namespace and query.
619622
@@ -636,6 +639,8 @@ def get(self, path_segment, owner=None, app=None, sharing=None, **query):
636639
:type owner: ``string``
637640
:param app: The app context of the namespace (optional).
638641
:type app: ``string``
642+
:param headers: List of extra HTTP headers to send (optional).
643+
:type headers: ``list`` of 2-tuples.
639644
:param sharing: The sharing mode of the namespace (optional).
640645
:type sharing: ``string``
641646
:param query: All other keyword arguments, which are used as query
@@ -663,10 +668,14 @@ def get(self, path_segment, owner=None, app=None, sharing=None, **query):
663668
c.logout()
664669
c.get('apps/local') # raises AuthenticationError
665670
"""
671+
if headers is None:
672+
headers = []
673+
666674
path = self.authority + self._abspath(path_segment, owner=owner,
667675
app=app, sharing=sharing)
668676
logging.debug("GET request to %s (body: %s)", path, repr(query))
669-
response = self.http.get(path, self._auth_headers, **query)
677+
all_headers = headers + self.additional_headers + self._auth_headers
678+
response = self.http.get(path, all_headers, **query)
670679
return response
671680

672681
@_authentication
@@ -738,7 +747,7 @@ def post(self, path_segment, owner=None, app=None, sharing=None, headers=None, *
738747

739748
path = self.authority + self._abspath(path_segment, owner=owner, app=app, sharing=sharing)
740749
logging.debug("POST request to %s (body: %s)", path, repr(query))
741-
all_headers = headers + self._auth_headers
750+
all_headers = headers + self.additional_headers + self._auth_headers
742751
response = self.http.post(path, all_headers, **query)
743752
return response
744753

@@ -804,7 +813,7 @@ def request(self, path_segment, method="GET", headers=None, body="",
804813
path = self.authority \
805814
+ self._abspath(path_segment, owner=owner,
806815
app=app, sharing=sharing)
807-
all_headers = headers + self._auth_headers
816+
all_headers = headers + self.additional_headers + self._auth_headers
808817
logging.debug("%s request to %s (headers: %s, body: %s)",
809818
method, path, str(all_headers), repr(body))
810819
response = self.http.request(path,
@@ -858,6 +867,7 @@ def login(self):
858867
self.authority + self._abspath("/services/auth/login"),
859868
username=self.username,
860869
password=self.password,
870+
headers=self.additional_headers,
861871
cookie="1") # In Splunk 6.2+, passing "cookie=1" will return the "set-cookie" header
862872

863873
body = response.body.read()
@@ -968,6 +978,8 @@ def connect(**kwargs):
968978
:type username: ``string``
969979
:param password: The password for the Splunk account.
970980
:type password: ``string``
981+
:param headers: List of extra HTTP headers to send (optional).
982+
:type headers: ``list`` of 2-tuples.
971983
:param autologin: When ``True``, automatically tries to log in again if the
972984
session terminates.
973985
:type autologin: ``Boolean``
@@ -1190,7 +1202,7 @@ def post(self, url, headers=None, **kwargs):
11901202
# to support the receivers/stream endpoint.
11911203
if 'body' in kwargs:
11921204
# We only use application/x-www-form-urlencoded if there is no other
1193-
# Content-Type header present. This can happen in cases where we
1205+
# Content-Type header present. This can happen in cases where we
11941206
# send requests as application/json, e.g. for KV Store.
11951207
if len([x for x in headers if x[0].lower() == "content-type"]) == 0:
11961208
headers.append(("Content-Type", "application/x-www-form-urlencoded"))

splunklib/client.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,22 @@
5858
my_app.package() # Creates a compressed package of this application
5959
"""
6060

61+
import contextlib
6162
import datetime
6263
import json
63-
from splunklib.six.moves import urllib
6464
import logging
65-
from time import sleep
66-
from datetime import datetime, timedelta
6765
import socket
68-
import contextlib
66+
from datetime import datetime, timedelta
67+
from time import sleep
6968

7069
from splunklib import six
71-
from .binding import Context, HTTPError, AuthenticationError, namespace, UrlEncoded, _encode, _make_cookie_header, _NoAuthenticationToken
72-
from .data import record
70+
from splunklib.six.moves import urllib
71+
7372
from . import data
73+
from .binding import (AuthenticationError, Context, HTTPError, UrlEncoded,
74+
_encode, _make_cookie_header, _NoAuthenticationToken,
75+
namespace)
76+
from .data import record
7477

7578
__all__ = [
7679
"connect",
@@ -193,8 +196,11 @@ def _path(base, name):
193196

194197

195198
# Load an atom record from the body of the given response
199+
# this will ultimately be sent to an xml ElementTree so we
200+
# should use the xmlcharrefreplace option
196201
def _load_atom(response, match=None):
197-
return data.load(response.body.read().decode('utf-8'), match)
202+
return data.load(response.body.read()
203+
.decode('utf-8', 'xmlcharrefreplace'), match)
198204

199205

200206
# Load an array of atom entries from the body of the given response
@@ -557,7 +563,7 @@ def restart(self, timeout=None):
557563
# This message will be deleted once the server actually restarts.
558564
self.messages.create(name="restart_required", **msg)
559565
result = self.post("/services/server/control/restart")
560-
if timeout is None:
566+
if timeout is None:
561567
return result
562568
start = datetime.now()
563569
diff = timedelta(seconds=timeout)
@@ -1631,7 +1637,7 @@ def get(self, name="", owner=None, app=None, sharing=None, **query):
16311637
and ``status``
16321638
16331639
Example:
1634-
1640+
16351641
import splunklib.client
16361642
s = client.service(...)
16371643
saved_searches = s.saved_searches
@@ -1685,7 +1691,7 @@ def __getitem__(self, key):
16851691
# The superclass implementation is designed for collections that contain
16861692
# entities. This collection (Configurations) contains collections
16871693
# (ConfigurationFile).
1688-
#
1694+
#
16891695
# The configurations endpoint returns multiple entities when we ask for a single file.
16901696
# This screws up the default implementation of __getitem__ from Collection, which thinks
16911697
# that multiple entities means a name collision, so we have to override it here.
@@ -1749,9 +1755,9 @@ class Stanza(Entity):
17491755
"""This class contains a single configuration stanza."""
17501756

17511757
def submit(self, stanza):
1752-
"""Adds keys to the current configuration stanza as a
1758+
"""Adds keys to the current configuration stanza as a
17531759
dictionary of key-value pairs.
1754-
1760+
17551761
:param stanza: A dictionary of key-value pairs for the stanza.
17561762
:type stanza: ``dict``
17571763
:return: The :class:`Stanza` object.
@@ -1962,7 +1968,7 @@ def attach(self, host=None, source=None, sourcetype=None):
19621968
cookie_or_auth_header.encode('utf-8'),
19631969
b"X-Splunk-Input-Mode: Streaming\r\n",
19641970
b"\r\n"]
1965-
1971+
19661972
for h in headers:
19671973
sock.write(h)
19681974
return sock
@@ -3695,13 +3701,13 @@ def batch_find(self, *dbqueries):
36953701
36963702
:param dbqueries: Array of individual queries as dictionaries
36973703
:type dbqueries: ``array`` of ``dict``
3698-
3704+
36993705
:return: Results of each query
37003706
:rtype: ``array`` of ``array``
37013707
"""
3702-
if len(dbqueries) < 1:
3708+
if len(dbqueries) < 1:
37033709
raise Exception('Must have at least one query.')
3704-
3710+
37053711
data = json.dumps(dbqueries)
37063712

37073713
return json.loads(self._post('batch_find', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
@@ -3712,13 +3718,13 @@ def batch_save(self, *documents):
37123718
37133719
:param documents: Array of documents to save as dictionaries
37143720
:type documents: ``array`` of ``dict``
3715-
3721+
37163722
:return: Results of update operation as overall stats
37173723
:rtype: ``dict``
37183724
"""
3719-
if len(documents) < 1:
3725+
if len(documents) < 1:
37203726
raise Exception('Must have at least one document.')
3721-
3727+
37223728
data = json.dumps(documents)
37233729

37243730
return json.loads(self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))

0 commit comments

Comments
 (0)