11
11
# License for the specific language governing permissions and limitations
12
12
# under the License.
13
13
14
+ from gabbi import utils
15
+ from gabbi .handlers import jsonhandler
16
+ import httpx
17
+ import logging
14
18
import os
15
19
import sys
16
20
17
- import certifi
18
- import urllib3
19
-
20
- from gabbi .handlers import jsonhandler
21
- from gabbi import utils
22
-
23
- # Disable SSL warnings otherwise tests which process stderr will get
24
- # extra information.
25
- urllib3 .disable_warnings ()
21
+ logging .getLogger ("httpx" ).setLevel (logging .WARNING )
26
22
27
23
28
- class Http ( urllib3 . PoolManager ) :
29
- """A subclass of the ``urllib3.PoolManager`` to munge the data .
24
+ class Http :
25
+ """A class to munge the HTTP response .
30
26
31
27
This transforms the response to look more like what httplib2
32
28
provided when it was used as the HTTP client.
33
29
"""
34
30
31
+ def __init__ (self , ** kwargs ):
32
+ self .extensions = {}
33
+ if "server_hostname" in kwargs :
34
+ self .extensions ["sni_hostname" ] = kwargs ["server_hostname" ]
35
+ self .client = httpx .Client (verify = kwargs .get ("cert_validate" , True ))
36
+
35
37
def request (self , absolute_uri , method , body , headers , redirect , timeout ):
36
- if redirect :
37
- retry = urllib3 .util .Retry (raise_on_redirect = False , redirect = 5 )
38
- else :
39
- retry = urllib3 .util .Retry (total = False , redirect = False )
40
- response = super (Http , self ).request (
41
- method ,
42
- absolute_uri ,
43
- body = body ,
44
- headers = headers ,
45
- retries = retry ,
46
- timeout = timeout ,
47
- )
38
+ try :
39
+ response = self .client .request (
40
+ method = method ,
41
+ url = absolute_uri ,
42
+ headers = headers ,
43
+ content = body ,
44
+ timeout = timeout ,
45
+ follow_redirects = redirect ,
46
+ extensions = self .extensions ,
47
+ )
48
+ except httpx .ConnectError as error :
49
+ raise RuntimeError (
50
+ f"{ error } .\n \n Method: { method } \n URL: { absolute_uri } "
51
+ ) from error
48
52
49
53
# Transform response into something akin to httplib2
50
54
# response object.
51
- content = response .data
52
- status = response .status
53
- reason = response .reason
55
+ content = response .content
56
+ status = response .status_code
57
+ reason = response .reason_phrase
58
+ http_version = response .http_version
54
59
headers = response .headers
55
- headers ['status' ] = str (status )
56
- headers ['reason' ] = reason
60
+ headers ["status" ] = str (status )
61
+ headers ["reason" ] = str (reason )
62
+ headers ["http_protocol_version" ] = str (http_version )
57
63
58
- # Shut down open PoolManagers whose connections have completed to
59
- # save on socket file descriptors.
60
- self .clear ()
61
64
return headers , content
62
65
63
66
@@ -85,59 +88,71 @@ class VerboseHttp(Http):
85
88
# Can include response object attributes that are not
86
89
# technically headers.
87
90
HEADER_BLACKLIST = [
88
- 'status' ,
89
- 'reason' ,
91
+ "status" ,
92
+ "reason" ,
93
+ "http_protocol_version" ,
90
94
]
91
95
92
- REQUEST_PREFIX = '>'
93
- RESPONSE_PREFIX = '<'
96
+ REQUEST_PREFIX = ">"
97
+ RESPONSE_PREFIX = "<"
94
98
COLORMAP = {
95
- ' caption' : os .environ .get (' GABBI_CAPTION_COLOR' , ' BLUE' ).upper (),
96
- ' header' : os .environ .get (' GABBI_HEADER_COLOR' , ' YELLOW' ).upper (),
97
- ' request' : os .environ .get (' GABBI_REQUEST_COLOR' , ' CYAN' ).upper (),
98
- ' status' : os .environ .get (' GABBI_STATUS_COLOR' , ' CYAN' ).upper (),
99
+ " caption" : os .environ .get (" GABBI_CAPTION_COLOR" , " BLUE" ).upper (),
100
+ " header" : os .environ .get (" GABBI_HEADER_COLOR" , " YELLOW" ).upper (),
101
+ " request" : os .environ .get (" GABBI_REQUEST_COLOR" , " CYAN" ).upper (),
102
+ " status" : os .environ .get (" GABBI_STATUS_COLOR" , " CYAN" ).upper (),
99
103
}
100
104
101
105
def __init__ (self , ** kwargs ):
102
- self .caption = kwargs .pop (' caption' )
103
- self ._show_body = kwargs .pop (' body' )
104
- self ._show_headers = kwargs .pop (' headers' )
105
- self ._use_color = kwargs .pop (' colorize' )
106
- self ._stream = kwargs .pop (' stream' )
106
+ self .caption = kwargs .pop (" caption" )
107
+ self ._show_body = kwargs .pop (" body" )
108
+ self ._show_headers = kwargs .pop (" headers" )
109
+ self ._use_color = kwargs .pop (" colorize" )
110
+ self ._stream = kwargs .pop (" stream" )
107
111
if self ._use_color :
108
112
self .colorize = utils .get_colorizer (self ._stream )
109
- super (VerboseHttp , self ).__init__ (** kwargs )
113
+ super ().__init__ (** kwargs )
110
114
111
115
def request (self , absolute_uri , method , body , headers , redirect , timeout ):
112
116
"""Display request parameters before requesting."""
113
117
114
- self ._verbose_output ('#### %s ####' % self .caption ,
115
- color = self .COLORMAP ['caption' ])
116
- self ._verbose_output ('%s %s' % (method , absolute_uri ),
117
- prefix = self .REQUEST_PREFIX ,
118
- color = self .COLORMAP ['request' ])
118
+ self ._verbose_output (
119
+ f"#### { self .caption } ####" ,
120
+ color = self .COLORMAP ["caption" ],
121
+ )
122
+ self ._verbose_output (
123
+ f"{ method } { absolute_uri } " ,
124
+ prefix = self .REQUEST_PREFIX ,
125
+ color = self .COLORMAP ["request" ],
126
+ )
119
127
120
128
self ._print_headers (headers , prefix = self .REQUEST_PREFIX )
121
129
self ._print_body (headers , body )
122
130
123
- response , content = super (VerboseHttp , self ).request (
124
- absolute_uri , method , body , headers , redirect , timeout )
131
+ response , content = super ().request (
132
+ absolute_uri ,
133
+ method ,
134
+ body ,
135
+ headers ,
136
+ redirect ,
137
+ timeout ,
138
+ )
125
139
126
140
# Blank line for division
127
- self ._verbose_output ('' )
128
- self ._verbose_output ('%s %s' % (response ['status' ],
129
- response ['reason' ]),
130
- prefix = self .RESPONSE_PREFIX ,
131
- color = self .COLORMAP ['status' ])
141
+ self ._verbose_output ("" )
142
+ self ._verbose_output (
143
+ f'{ response ["status" ]} { response ["reason" ]} ' ,
144
+ prefix = self .RESPONSE_PREFIX ,
145
+ color = self .COLORMAP ["status" ],
146
+ )
132
147
self ._print_headers (response , prefix = self .RESPONSE_PREFIX )
133
148
134
149
# response body
135
150
self ._print_body (response , content )
136
- self ._verbose_output ('' )
151
+ self ._verbose_output ("" )
137
152
138
153
return (response , content )
139
154
140
- def _print_headers (self , headers , prefix = '' ):
155
+ def _print_headers (self , headers , prefix = "" ):
141
156
"""Output request or response headers."""
142
157
if self ._show_headers :
143
158
for key in headers :
@@ -148,11 +163,11 @@ def _print_body(self, headers, content):
148
163
"""Output body if not binary."""
149
164
# Use text/plain as the default so that when there is not content-type
150
165
# we can still see the output.
151
- content_type = utils .extract_content_type (headers , ' text/plain' )[0 ]
166
+ content_type = utils .extract_content_type (headers , " text/plain" )[0 ]
152
167
if self ._show_body and utils .not_binary (content_type ):
153
168
content = utils .decode_response_content (headers , content )
154
169
if isinstance (content , bytes ):
155
- content = content .decode (' utf-8' )
170
+ content = content .decode (" utf-8" )
156
171
# TODO(cdent): Using the JSONHandler here instead of
157
172
# just the json module to make it clear that eventually
158
173
# we could pretty print any printable output by using a
@@ -166,57 +181,50 @@ def _print_body(self, headers, content):
166
181
except ValueError :
167
182
# It it didn't decode for some reason treat it as a string.
168
183
pass
169
- self ._verbose_output ('' )
184
+ self ._verbose_output ("" )
170
185
if content :
171
186
self ._verbose_output (content )
172
187
173
- def _print_header (self , name , value , prefix = '' , stream = None ):
188
+ def _print_header (self , name , value , prefix = "" , stream = None ):
174
189
"""Output one single header."""
175
- header = self .colorize (self .COLORMAP ['header' ], "%s:" % name )
176
- self ._verbose_output ("%s %s" % (header , value ), prefix = prefix ,
177
- stream = stream )
190
+ header = self .colorize (self .COLORMAP ["header" ], f"{ name } :" )
191
+ self ._verbose_output (f"{ header } { value } " , prefix = prefix , stream = stream )
178
192
179
- def _verbose_output (self , message , prefix = '' , color = None , stream = None ):
193
+ def _verbose_output (self , message , prefix = "" , color = None , stream = None ):
180
194
"""Output a message."""
181
195
stream = stream or self ._stream
182
196
if prefix and message :
183
- print (prefix , end = ' ' , file = stream )
197
+ print (prefix , end = " " , file = stream )
184
198
if color :
185
199
message = self .colorize (color , message )
186
200
print (message , file = stream )
187
201
188
202
189
203
def get_http (
190
204
verbose = False ,
191
- caption = '' ,
205
+ caption = "" ,
192
206
cert_validate = True ,
193
207
hostname = None ,
194
208
timeout = 30 ,
195
209
):
196
210
"""Return an ``Http`` class for making requests."""
197
- cert_validation = {'cert_reqs' : 'CERT_NONE' } if not cert_validate else {}
198
-
199
211
if not verbose :
200
212
return Http (
201
- strict = True ,
202
- ca_certs = certifi .where (),
203
213
server_hostname = hostname ,
204
214
timeout = timeout ,
205
- ** cert_validation
215
+ cert_validate = cert_validate ,
206
216
)
207
217
208
- headers = False if verbose == ' body' else True
209
- body = False if verbose == ' headers' else True
218
+ headers = verbose != " body"
219
+ body = verbose != " headers"
210
220
211
221
return VerboseHttp (
212
222
headers = headers ,
213
223
body = body ,
214
224
stream = sys .stdout ,
215
225
caption = caption ,
216
226
colorize = True ,
217
- strict = True ,
218
- ca_certs = certifi .where (),
219
227
server_hostname = hostname ,
220
228
timeout = timeout ,
221
- ** cert_validation
229
+ cert_validate = cert_validate ,
222
230
)
0 commit comments