Skip to content

Commit 5f371fd

Browse files
prepare 6.13.2 release (#146)
1 parent 9f270b8 commit 5f371fd

File tree

7 files changed

+112
-148
lines changed

7 files changed

+112
-148
lines changed

ldclient/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(self,
3737
variable, this is used regardless of whether the target URI is HTTP or HTTPS (the actual LaunchDarkly
3838
service uses HTTPS, but a Relay Proxy instance could use HTTP). Setting this Config parameter will
3939
override any proxy specified by an environment variable, but only for LaunchDarkly SDK connections.
40+
The URL may contain authentication parameters in the form http://username:password@host:port.
4041
:param string ca_certs: If using a custom certificate authority, set this to the file path of the
4142
certificate bundle.
4243
:param string cert_file: If using a custom client certificate, set this to the file path of the

ldclient/impl/http.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,18 @@ def create_pool_manager(self, num_pools, target_base_uri):
5454
ca_certs=ca_certs
5555
)
5656
else:
57+
# Get proxy authentication, if provided
58+
url = urllib3.util.parse_url(proxy_url)
59+
proxy_headers = None
60+
if url.auth != None:
61+
proxy_headers = urllib3.util.make_headers(proxy_basic_auth=url.auth)
62+
# Create a proxied connection
5763
return urllib3.ProxyManager(
5864
proxy_url,
5965
num_pools=num_pools,
6066
cert_reqs=cert_reqs,
61-
ca_certs = ca_certs
67+
ca_certs = ca_certs,
68+
proxy_headers=proxy_headers
6269
)
6370

6471
def _get_proxy_url(target_base_uri):

testing/http_util.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ def await_request(self):
7272

7373
def require_request(self):
7474
return self.requests.get(block=False)
75-
75+
76+
def wait_until_request_received(self):
77+
req = self.requests.get()
78+
self.requests.put(req)
79+
7680
def should_have_requests(self, count):
7781
if self.requests.qsize() != count:
7882
rs = []

testing/proxy_test_util.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from ldclient.config import Config, HTTPConfig
2+
from testing.http_util import start_server, BasicResponse, JsonResponse
3+
4+
# Runs tests of all of our supported proxy server configurations: secure or insecure, configured
5+
# by Config.http_proxy or by an environment variable, with or without authentication. The action
6+
# parameter is a function that takes three parameters: server, config, secure; the expectation is
7+
# that it causes an HTTP/HTTPS request to be made via the configured proxy. The caller must pass
8+
# in the monkeypatch fixture from pytest.
9+
def do_proxy_tests(action, action_method, monkeypatch):
10+
# We'll test each permutation of use_env_vars, secure, and use_auth, except that if secure is
11+
# true then we'll only test with use_auth=false because we don't have a way to test proxy
12+
# authorization over HTTPS (even though we believe it works).
13+
for (use_env_vars, secure, use_auth) in [
14+
(False, False, False),
15+
(False, False, True),
16+
(False, True, False),
17+
(True, False, False),
18+
(True, False, True),
19+
(True, True, False)]:
20+
test_desc = "%s, %s, %s" % (
21+
"using env vars" if use_env_vars else "using Config",
22+
"secure" if secure else "insecure",
23+
"with auth" if use_auth else "no auth")
24+
with start_server() as server:
25+
proxy_uri = server.uri.replace('http://', 'http://user:pass@') if use_auth else server.uri
26+
target_uri = 'https://not-real' if secure else 'http://not-real'
27+
if use_env_vars:
28+
monkeypatch.setenv('https_proxy' if secure else 'http_proxy', proxy_uri)
29+
config = Config(
30+
sdk_key = 'sdk_key',
31+
base_uri = target_uri,
32+
events_uri = target_uri,
33+
stream_uri = target_uri,
34+
http = None if use_env_vars else HTTPConfig(http_proxy=proxy_uri),
35+
diagnostic_opt_out = True)
36+
try:
37+
action(server, config, secure)
38+
except:
39+
print("test action failed (%s)" % test_desc)
40+
raise
41+
# For an insecure proxy request, our stub server behaves enough like the real thing to satisfy the
42+
# HTTP client, so we should be able to see the request go through. Note that the URI path will
43+
# actually be an absolute URI for a proxy request.
44+
try:
45+
req = server.require_request()
46+
except:
47+
print("server did not receive a request (%s)" % test_desc)
48+
raise
49+
expected_method = 'CONNECT' if secure else action_method
50+
assert req.method == expected_method, "method should be %s, was %s (%s)" % (expected_method, req.method, test_desc)
51+
if use_auth:
52+
expected_auth = 'Basic dXNlcjpwYXNz'
53+
actual_auth = req.headers.get('Proxy-Authorization')
54+
assert actual_auth == expected_auth, "auth header should be %s, was %s (%s)" % (expected_auth, actual_auth, test_desc)
55+
print("do_proxy_tests succeeded for: %s" % test_desc)

testing/test_event_processor.py

+8-46
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from ldclient.event_processor import DefaultEventProcessor
1010
from ldclient.util import log
1111
from testing.http_util import start_server, BasicResponse
12+
from testing.proxy_test_util import do_proxy_tests
1213
from testing.stub_util import MockResponse, MockHttp
1314

1415

@@ -558,52 +559,13 @@ def start_consuming_events():
558559
assert message1.param == event1
559560
assert had_no_more
560561

561-
def test_can_use_http_proxy_via_environment_var(monkeypatch):
562-
with start_server() as server:
563-
monkeypatch.setenv('http_proxy', server.uri)
564-
config = Config(sdk_key = 'sdk-key', events_uri = 'http://not-real', diagnostic_opt_out = True)
565-
_verify_http_proxy_is_used(server, config)
566-
567-
def test_can_use_https_proxy_via_environment_var(monkeypatch):
568-
with start_server() as server:
569-
monkeypatch.setenv('https_proxy', server.uri)
570-
config = Config(sdk_key = 'sdk-key', events_uri = 'https://not-real', diagnostic_opt_out = True)
571-
_verify_https_proxy_is_used(server, config)
572-
573-
def test_can_use_http_proxy_via_config():
574-
with start_server() as server:
575-
config = Config(sdk_key = 'sdk-key', events_uri = 'http://not-real', http_proxy=server.uri, diagnostic_opt_out = True)
576-
_verify_http_proxy_is_used(server, config)
577-
578-
def test_can_use_https_proxy_via_config():
579-
with start_server() as server:
580-
config = Config(sdk_key = 'sdk-key', events_uri = 'https://not-real', http_proxy=server.uri, diagnostic_opt_out = True)
581-
_verify_https_proxy_is_used(server, config)
582-
583-
def _verify_http_proxy_is_used(server, config):
584-
server.for_path(config.events_uri + '/bulk', BasicResponse(200))
585-
with DefaultEventProcessor(config) as ep:
586-
ep.send_event({ 'kind': 'identify', 'user': user })
587-
ep.flush()
588-
ep._wait_until_inactive()
589-
590-
# For an insecure proxy request, our stub server behaves enough like the real thing to satisfy the
591-
# HTTP client, so we should be able to see the request go through. Note that the URI path will
592-
# actually be an absolute URI for a proxy request.
593-
req = server.require_request()
594-
assert req.method == 'POST'
595-
596-
def _verify_https_proxy_is_used(server, config):
597-
server.for_path(config.events_uri + '/bulk', BasicResponse(200))
598-
with DefaultEventProcessor(config) as ep:
599-
ep.send_event({ 'kind': 'identify', 'user': user })
600-
ep.flush()
601-
ep._wait_until_inactive()
602-
603-
# Our simple stub server implementation can't really do HTTPS proxying, so the request will fail, but
604-
# it can still record that it *got* the request, which proves that the request went to the proxy.
605-
req = server.require_request()
606-
assert req.method == 'CONNECT'
562+
def test_http_proxy(monkeypatch):
563+
def _event_processor_proxy_test(server, config, secure):
564+
with DefaultEventProcessor(config) as ep:
565+
ep.send_event({ 'kind': 'identify', 'user': user })
566+
ep.flush()
567+
ep._wait_until_inactive()
568+
do_proxy_tests(_event_processor_proxy_test, 'POST', monkeypatch)
607569

608570
def verify_unrecoverable_http_error(status):
609571
with DefaultTestProcessor(sdk_key = 'SDK_KEY') as ep:

testing/test_feature_requester.py

+16-52
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ldclient.version import VERSION
77
from ldclient.versioned_data_kind import FEATURES, SEGMENTS
88
from testing.http_util import start_server, BasicResponse, JsonResponse
9-
9+
from testing.proxy_test_util import do_proxy_tests
1010

1111
def test_get_all_data_returns_data():
1212
with start_server() as server:
@@ -102,54 +102,18 @@ def test_get_all_data_can_use_cached_data():
102102
req = server.require_request()
103103
assert req.headers['If-None-Match'] == etag2
104104

105-
def test_can_use_http_proxy_via_environment_var(monkeypatch):
106-
with start_server() as server:
107-
monkeypatch.setenv('http_proxy', server.uri)
108-
config = Config(sdk_key = 'sdk-key', base_uri = 'http://not-real')
109-
_verify_http_proxy_is_used(server, config)
110-
111-
def test_can_use_https_proxy_via_environment_var(monkeypatch):
112-
with start_server() as server:
113-
monkeypatch.setenv('https_proxy', server.uri)
114-
config = Config(sdk_key = 'sdk-key', base_uri = 'https://not-real')
115-
_verify_https_proxy_is_used(server, config)
116-
117-
def test_can_use_http_proxy_via_config():
118-
with start_server() as server:
119-
config = Config(sdk_key = 'sdk-key', base_uri = 'http://not-real', http_proxy = server.uri)
120-
_verify_http_proxy_is_used(server, config)
121-
122-
def test_can_use_https_proxy_via_config():
123-
with start_server() as server:
124-
config = Config(sdk_key = 'sdk-key', base_uri = 'https://not-real', http_proxy = server.uri)
125-
_verify_https_proxy_is_used(server, config)
126-
127-
def _verify_http_proxy_is_used(server, config):
128-
fr = FeatureRequesterImpl(config)
129-
130-
resp_data = { 'flags': {}, 'segments': {} }
131-
expected_data = { FEATURES: {}, SEGMENTS: {} }
132-
server.for_path(config.base_uri + '/sdk/latest-all', JsonResponse(resp_data))
133-
134-
# For an insecure proxy request, our stub server behaves enough like the real thing to satisfy the
135-
# HTTP client, so we should be able to see the request go through. Note that the URI path will
136-
# actually be an absolute URI for a proxy request.
137-
result = fr.get_all_data()
138-
assert result == expected_data
139-
req = server.require_request()
140-
assert req.method == 'GET'
141-
142-
def _verify_https_proxy_is_used(server, config):
143-
fr = FeatureRequesterImpl(config)
144-
145-
resp_data = { 'flags': {}, 'segments': {} }
146-
server.for_path(config.base_uri + '/sdk/latest-all', JsonResponse(resp_data))
147-
148-
# Our simple stub server implementation can't really do HTTPS proxying, so the request will fail, but
149-
# it can still record that it *got* the request, which proves that the request went to the proxy.
150-
try:
151-
fr.get_all_data()
152-
except:
153-
pass
154-
req = server.require_request()
155-
assert req.method == 'CONNECT'
105+
def test_http_proxy(monkeypatch):
106+
def _feature_requester_proxy_test(server, config, secure):
107+
resp_data = { 'flags': {}, 'segments': {} }
108+
expected_data = { FEATURES: {}, SEGMENTS: {} }
109+
server.for_path(config.base_uri + '/sdk/latest-all', JsonResponse(resp_data))
110+
fr = FeatureRequesterImpl(config)
111+
if secure:
112+
try:
113+
fr.get_all_data()
114+
except:
115+
pass # we expect this to fail because we don't have a real HTTPS proxy server
116+
else:
117+
result = fr.get_all_data()
118+
assert result == expected_data
119+
do_proxy_tests(_feature_requester_proxy_test, 'GET', monkeypatch)

testing/test_streaming.py

+19-48
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ldclient.version import VERSION
1111
from ldclient.versioned_data_kind import FEATURES, SEGMENTS
1212
from testing.http_util import start_server, BasicResponse, CauseNetworkError, SequentialHandler
13+
from testing.proxy_test_util import do_proxy_tests
1314
from testing.stub_util import make_delete_event, make_patch_event, make_put_event, stream_content
1415

1516
brief_delay = 0.001
@@ -210,54 +211,24 @@ def test_unrecoverable_http_error(status):
210211
assert not sp.initialized()
211212
server.should_have_requests(1)
212213

213-
def test_can_use_http_proxy_via_environment_var(monkeypatch):
214-
with start_server() as server:
215-
config = Config(sdk_key = 'sdk-key', stream_uri = 'http://not-real')
216-
monkeypatch.setenv('http_proxy', server.uri)
217-
_verify_http_proxy_is_used(server, config)
218-
219-
def test_can_use_https_proxy_via_environment_var(monkeypatch):
220-
with start_server() as server:
221-
config = Config(sdk_key = 'sdk-key', stream_uri = 'https://not-real')
222-
monkeypatch.setenv('https_proxy', server.uri)
223-
_verify_https_proxy_is_used(server, config)
224-
225-
def test_can_use_http_proxy_via_config():
226-
with start_server() as server:
227-
config = Config(sdk_key = 'sdk-key', stream_uri = 'http://not-real', http_proxy=server.uri)
228-
_verify_http_proxy_is_used(server, config)
229-
230-
def test_can_use_https_proxy_via_config():
231-
with start_server() as server:
232-
config = Config(sdk_key = 'sdk-key', stream_uri = 'https://not-real', http_proxy=server.uri)
233-
_verify_https_proxy_is_used(server, config)
234-
235-
def _verify_http_proxy_is_used(server, config):
236-
store = InMemoryFeatureStore()
237-
ready = Event()
238-
with stream_content(make_put_event()) as stream:
239-
server.for_path(config.stream_base_uri + '/all', stream)
240-
with StreamingUpdateProcessor(config, store, ready, None) as sp:
241-
sp.start()
242-
# For an insecure proxy request, our stub server behaves enough like the real thing to satisfy the
243-
# HTTP client, so we should be able to see the request go through. Note that the URI path will
244-
# actually be an absolute URI for a proxy request.
245-
req = server.await_request()
246-
assert req.method == 'GET'
247-
ready.wait(start_wait)
248-
assert sp.initialized()
249-
250-
def _verify_https_proxy_is_used(server, config):
251-
store = InMemoryFeatureStore()
252-
ready = Event()
253-
with stream_content(make_put_event()) as stream:
254-
server.for_path(config.stream_base_uri + '/all', stream)
255-
with StreamingUpdateProcessor(config, store, ready, None) as sp:
256-
sp.start()
257-
# Our simple stub server implementation can't really do HTTPS proxying, so the request will fail, but
258-
# it can still record that it *got* the request, which proves that the request went to the proxy.
259-
req = server.await_request()
260-
assert req.method == 'CONNECT'
214+
def test_http_proxy(monkeypatch):
215+
def _stream_processor_proxy_test(server, config, secure):
216+
store = InMemoryFeatureStore()
217+
ready = Event()
218+
with stream_content(make_put_event()) as stream:
219+
server.for_path(config.stream_base_uri + '/all', stream)
220+
with StreamingUpdateProcessor(config, store, ready, None) as sp:
221+
sp.start()
222+
# Wait till the server has received a request. We need to do this even though do_proxy_tests also
223+
# does it, because if we return too soon out of this block, the object returned by stream_content
224+
# could be closed and the test server would no longer work.
225+
server.wait_until_request_received()
226+
if not secure:
227+
# We only do this part with HTTP, because with HTTPS we don't have a real enough proxy server
228+
# for the stream connection to work correctly - we can only detect the request.
229+
ready.wait(start_wait)
230+
assert sp.initialized()
231+
do_proxy_tests(_stream_processor_proxy_test, 'GET', monkeypatch)
261232

262233
def test_records_diagnostic_on_stream_init_success():
263234
store = InMemoryFeatureStore()

0 commit comments

Comments
 (0)