1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
# Copyright (c) 2016 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements HTTP requests GET, PUT, POST, DELETE used in communication with
honeycomb.
"""
from requests import request, RequestException, Timeout, TooManyRedirects, \
HTTPError, ConnectionError
from requests.auth import HTTPBasicAuth
from robot.api import logger
from robot.api.deco import keyword
HTTP_CODES = {"OK": 200,
"UNAUTHORIZED": 401,
"FORBIDDEN": 403,
"NOT_FOUND": 404,
"SERVICE_UNAVAILABLE": 503}
class HTTPRequestError(Exception):
"""Exception raised by HTTPRequest objects."""
def __init__(self, msg, enable_logging=True):
"""Sets the exception message and enables / disables logging
It is not wanted to log errors when using these keywords together
with keywords like "Wait until keyword succeeds".
:param msg: Message to be displayed and logged
:param enable_logging: When True, logging is enabled, otherwise
logging is disabled.
:type msg: str
:type enable_logging: bool
"""
super(HTTPRequestError, self).__init__()
self._msg = msg
self._repr_msg = self.__module__ + '.' + \
self.__class__.__name__ + ": " + self._msg
if enable_logging:
logger.error(self._msg)
logger.debug(self._repr_msg)
def __repr__(self):
return repr(self._repr_msg)
def __str__(self):
return str(self._repr_msg)
class HTTPRequest(object):
"""A class implementing HTTP requests."""
def __init__(self):
pass
@staticmethod
def create_full_url(ip_addr, port, path):
"""Creates full url including IP, port, and path to data.
:param ip_addr: Server IP
:param port: Communication port
:param path: Path to data
:type ip_addr: str
:type port: str or int
:type path: str
:return: full url
:rtype: str
"""
return "http://{ip}:{port}{path}".format(ip=ip_addr, port=port,
path=path)
@staticmethod
def _http_request(method, node, path, enable_logging=True, **kwargs):
"""Sends specified HTTP request and returns status code and
response content
:param method: The method to be performed on the resource identified by
the given request URI
:param node: honeycomb node
:param path: URL path, e.g. /index.html
:param enable_logging: used to suppress errors when checking
honeycomb state during suite setup and teardown
:param kwargs: named parameters accepted by request.request:
params -- (optional) Dictionary or bytes to be sent in the query
string for the Request.
data -- (optional) Dictionary, bytes, or file-like object to
send in the body of the Request.
json -- (optional) json data to send in the body of the Request.
headers -- (optional) Dictionary of HTTP Headers to send with
the Request.
cookies -- (optional) Dict or CookieJar object to send with the
Request.
files -- (optional) Dictionary of 'name': file-like-objects
(or {'name': ('filename', fileobj)}) for multipart encoding upload.
timeout (float or tuple) -- (optional) How long to wait for the
server to send data before giving up, as a float, or a (connect
timeout, read timeout) tuple.
allow_redirects (bool) -- (optional) Boolean. Set to True if POST/
PUT/DELETE redirect following is allowed.
proxies -- (optional) Dictionary mapping protocol to the URL of
the proxy.
verify -- (optional) whether the SSL cert will be verified.
A CA_BUNDLE path can also be provided. Defaults to True.
stream -- (optional) if False, the response content will be
immediately downloaded.
cert -- (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
:type method: str
:type node: dict
:type path: str
:type enable_logging: bool
:type kwargs: dict
:return: Status code and content of response
:rtype: tuple
:raises HTTPRequestError: If
1. it is not possible to connect
2. invalid HTTP response comes from server
3. request exceeded the configured number of maximum re-directions
4. request timed out
5. there is any other unexpected HTTP request exception
"""
timeout = kwargs["timeout"]
url = HTTPRequest.create_full_url(node['host'],
node['honeycomb']['port'],
path)
try:
auth = HTTPBasicAuth(node['honeycomb']['user'],
node['honeycomb']['passwd'])
rsp = request(method, url, auth=auth, **kwargs)
return rsp.status_code, rsp.content
except ConnectionError as err:
# Switching the logging on / off is needed only for
# "requests.ConnectionError"
if enable_logging:
raise HTTPRequestError("Not possible to connect to {0}\n".
format(url) + repr(err))
else:
raise HTTPRequestError("Not possible to connect to {0}\n".
format(url) + repr(err),
enable_logging=False)
except HTTPError as err:
raise HTTPRequestError("Invalid HTTP response from {0}\n".
format(url) + repr(err))
except TooManyRedirects as err:
raise HTTPRequestError("Request exceeded the configured number "
"of maximum re-directions\n" + repr(err))
except Timeout as err:
raise HTTPRequestError("Request timed out. Timeout is set to "
"{0}\n".format(timeout) + repr(err))
except RequestException as err:
raise HTTPRequestError("Unexpected HTTP request exception.\n" +
repr(err))
@staticmethod
@keyword(name="HTTP Get")
def get(node, path, headers=None, timeout=10, enable_logging=True):
"""Sends a GET request and returns the response and status code.
:param node: honeycomb node
:param path: URL path, e.g. /index.html
:param headers: Dictionary of HTTP Headers to send with the Request.
:param timeout: How long to wait for the server to send data before
giving up, as a float, or a (connect timeout, read timeout) tuple.
:param enable_logging: Used to suppress errors when checking
honeycomb state during suite setup and teardown. When True, logging
is enabled, otherwise logging is disabled.
:type node: dict
:type path: str
:type headers: dict
:type timeout: float or tuple
:type enable_logging: bool
:return: Status code and content of response
:rtype: tuple
"""
return HTTPRequest._http_request('GET', node, path,
enable_logging=enable_logging,
headers=headers, timeout=timeout)
@staticmethod
@keyword(name="HTTP Put")
def put(node, path, headers=None, payload=None, timeout=10):
"""Sends a PUT request and returns the response and status code.
:param node: honeycomb node
:param path: URL path, e.g. /index.html
:param headers: Dictionary of HTTP Headers to send with the Request.
:param payload: Dictionary, bytes, or file-like object to send in
the body of the Request.
:param timeout: How long to wait for the server to send data before
giving up, as a float, or a (connect timeout, read timeout) tuple.
:type node: dict
:type path: str
:type headers: dict
:type payload: dict, bytes, or file-like object
:type timeout: float or tuple
:return: Status code and content of response
:rtype: tuple
"""
return HTTPRequest._http_request('PUT', node, path, headers=headers,
data=payload, timeout=timeout)
@staticmethod
@keyword(name="HTTP Post")
def post(node, path, headers=None, payload=None, json=None, timeout=10):
"""Sends a POST request and returns the response and status code.
:param node: honeycomb node
:param path: URL path, e.g. /index.html
:param headers: Dictionary of HTTP Headers to send with the Request.
:param payload: Dictionary, bytes, or file-like object to send in
the body of the Request.
:param json: json data to send in the body of the Request
:param timeout: How long to wait for the server to send data before
giving up, as a float, or a (connect timeout, read timeout) tuple.
:type node: dict
:type path: str
:type headers: dict
:type payload: dict, bytes, or file-like object
:type json: str
:type timeout: float or tuple
:return: Status code and content of response
:rtype: tuple
"""
return HTTPRequest._http_request('POST', node, path, headers=headers,
data=payload, json=json,
timeout=timeout)
@staticmethod
@keyword(name="HTTP Delete")
def delete(node, path, timeout=10):
"""Sends a DELETE request and returns the response and status code.
:param node: honeycomb node
:param path: URL path, e.g. /index.html
:param timeout: How long to wait for the server to send data before
giving up, as a float, or a (connect timeout, read timeout) tuple.
:type node: dict
:type path: str
:type timeout: float or tuple
:return: Status code and content of response
:rtype: tuple
"""
return HTTPRequest._http_request('DELETE', node, path, timeout=timeout)
|