From 06344f6d238ef083e89125c27759f5ab0f8fc207 Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Sat, 26 Nov 2022 19:23:40 -0700 Subject: [PATCH 1/3] more complete support for portOptional added unit test of portOptional bumped version --- setup.py | 2 +- src/hio/__init__.py | 2 +- src/hio/core/http/clienting.py | 17 ++-- tests/core/http/test_clienting.py | 127 +++++++++++++++++++++++++++++- tests/help/test_timing.py | 2 +- 5 files changed, 141 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 0ed1620..7b1f058 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ setup( name='hio', - version='0.6.8', # also change in src/hio/__init__.py + version='0.6.9', # also change in src/hio/__init__.py license='Apache Software License 2.0', description='Hierarchical Concurrency with Async IO', long_description=("HIO Hierarchical Concurrency and Asynchronous IO Library. " diff --git a/src/hio/__init__.py b/src/hio/__init__.py index 86fdf80..160de1c 100644 --- a/src/hio/__init__.py +++ b/src/hio/__init__.py @@ -3,6 +3,6 @@ hio package """ -__version__ = '0.6.8' # also change in setup.py +__version__ = '0.6.9' # also change in setup.py from .hioing import Mixin, HioError, ValidationError, VersionError diff --git a/src/hio/core/http/clienting.py b/src/hio/core/http/clienting.py index 8a6edf8..ed5060a 100644 --- a/src/hio/core/http/clienting.py +++ b/src/hio/core/http/clienting.py @@ -65,7 +65,8 @@ def __init__(self, body = http request body data = dict to jsonify as body if provided fargs = dict to url form encode as body if provided - portOptional = True indicates to leave off port 80 for http or 443 for https in Host header to support + portOptional = True indicates to leave off port 80 for http or + 443 for https in Host header to support non-compliant server implementations. """ self.hostname, self.port = httping.normalizeHostPort(hostname, port, 80) @@ -98,7 +99,8 @@ def reinit(self, headers=None, body=None, data=None, - fargs=None): + fargs=None, + portOptional=None): """ Reinitialize anything that is not None This enables creating another request on a connection to the same host port @@ -135,6 +137,9 @@ def reinit(self, else: self.fargs = None + if portOptional is not None: + self.portOptional = True if portOptional else False + def rebuild(self, method=None, path=None, @@ -143,7 +148,8 @@ def rebuild(self, headers=None, body=None, data=None, - fargs=None): + fargs=None, + portOptional=None): """ Reinit then build and return request message This allows sending another request to same destination @@ -155,7 +161,8 @@ def rebuild(self, headers=headers, body=body, data=data, - fargs=fargs) + fargs=fargs, + portOptional=portOptional) return self.build() @@ -217,7 +224,7 @@ def build(self): host = u'[' + host + u']' if self.portOptional and (self.scheme, port) in (("http", 80), ("https", 443)): - value = host + value = host # leave port empty when portOptional and one of defaults else: value = "{0}:{1}".format(host, port) diff --git a/tests/core/http/test_clienting.py b/tests/core/http/test_clienting.py index a9e1e97..9d544aa 100644 --- a/tests/core/http/test_clienting.py +++ b/tests/core/http/test_clienting.py @@ -368,6 +368,131 @@ def test_client_request_echo(): 'verb': 'GET'} +def test_client_request_echo_port_empty(): + """ + Test HTTP Client request echo non blocking with portOptional set to True + Uses port 80 so may cause problems when testing + """ + with tcp.openServer(port = 80, bufsize=131072) as alpha: + + assert alpha.ha == ('0.0.0.0', 80) + assert alpha.eha == ('127.0.0.1', 80) + + host = alpha.eha[0] + port = alpha.eha[1] + method = u'GET' + path = u'/echo?name=fame' + headers = dict([('Accept', 'application/json')]) + + with (http.openClient(bufsize=131072, hostname=host, port=port, + method=method, path=path, headers=headers, portOptional=True) + as beta): + + assert not beta.connector.accepted + assert not beta.connector.connected + assert not beta.connector.cutoff + + assert beta.requester.portOptional # portOptional + assert beta.requester.lines == [] + + # connect Client Beta to Server Alpha + while True: + beta.connector.serviceConnect() + alpha.serviceConnects() + if beta.connector.connected and beta.connector.ca in alpha.ixes: + break + time.sleep(0.05) + + assert beta.connector.accepted + assert beta.connector.connected + assert not beta.connector.cutoff + assert beta.connector.ca == beta.connector.cs.getsockname() + assert beta.connector.ha == beta.connector.cs.getpeername() + assert alpha.eha == beta.connector.ha + + ixBeta = alpha.ixes[beta.connector.ca] + assert ixBeta.ca is not None + assert ixBeta.cs is not None + assert ixBeta.cs.getsockname() == beta.connector.cs.getpeername() + assert ixBeta.cs.getpeername() == beta.connector.cs.getsockname() + assert ixBeta.ca == beta.connector.ca + assert ixBeta.ha, beta.connector.ha + + # build request + msgOut = beta.requester.rebuild() + # port empty inhost header line + assert beta.requester.lines == [ + b'GET /echo?name=fame HTTP/1.1', + b'Host: 127.0.0.1', + b'Accept-Encoding: identity', + b'Accept: application/json', + b'', + b''] + + assert beta.requester.head == (b'GET /echo?name=fame HTTP/1.1\r\n' + b'Host: 127.0.0.1\r\n' + b'Accept-Encoding: identity' + b'\r\nAccept: application/json\r\n\r\n') + + assert msgOut == (b'GET /echo?name=fame HTTP/1.1\r\n' + b'Host: 127.0.0.1\r\n' + b'Accept-Encoding: identity' + b'\r\nAccept: application/json\r\n\r\n') + + + + beta.connector.tx(msgOut) + while beta.connector.txbs and not ixBeta.rxbs : + beta.connector.serviceSends() + time.sleep(0.05) + alpha.serviceReceivesAllIx() + time.sleep(0.05) + msgIn = bytes(ixBeta.rxbs) + assert msgIn == msgOut + ixBeta.clearRxbs() + + # build resonse + msgOut = (b'HTTP/1.1 200 OK\r\n' + b'Content-Length: 122\r\n' + b'Content-Type: application/json\r\n' + b'Date: Thu, 30 Apr 2015 19:37:17 GMT\r\n' + b'Server: IoBook.local\r\n\r\n' + b'{"content": null, "query": {"name": "fame"}, "verb": "GET", ' + b'"url": "http://127.0.0.1:8080/echo?name=fame", "action": null}') + + ixBeta.tx(msgOut) + while ixBeta.txbs or not beta.connector.rxbs: + alpha.serviceSendsAllIx() + time.sleep(0.05) + beta.connector.serviceReceives() + time.sleep(0.05) + msgIn = bytes(beta.connector.rxbs) + assert msgIn == msgOut + + while beta.respondent.parser: + beta.respondent.parse() + + assert not beta.connector.rxbs + assert list(beta.respondent.headers.items()) == [('Content-Length', '122'), + ('Content-Type', 'application/json'), + ('Date', 'Thu, 30 Apr 2015 19:37:17 GMT'), + ('Server', 'IoBook.local')] + + beta.respondent.dictify() # convert JSON data in body + + assert beta.respondent.body == (b'{"content": null, ' + b'"query": {"name": "fame"}, ' + b'"verb": "GET", "url' + b'": "http://127.0.0.1:8080/echo?name=fame", ' + b'"action": null}') + assert beta.respondent.data == {'action': None, + 'content': None, + 'query': {'name': 'fame'}, + 'url': 'http://127.0.0.1:8080/echo?name=fame', + 'verb': 'GET'} + + + def test_client_service_echo(): """ Test Client service echo nonblocking @@ -2362,4 +2487,4 @@ def test_client_port_options(): if __name__ == '__main__': - test_query_quoting() + test_client_request_echo_port_empty() diff --git a/tests/help/test_timing.py b/tests/help/test_timing.py index d8746d2..f6fd182 100644 --- a/tests/help/test_timing.py +++ b/tests/help/test_timing.py @@ -129,4 +129,4 @@ def test_monotimer(): """End Test """ if __name__ == "__main__": - test_monotimer() + test_timer() From 6ddbe0870513f9e88fd4e67dd9f3d3006c192b03 Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Sat, 26 Nov 2022 19:42:16 -0700 Subject: [PATCH 2/3] fixed timing test fixed port 80 portOptional text on linux --- tests/core/http/test_clienting.py | 9 ++++++--- tests/help/test_timing.py | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/core/http/test_clienting.py b/tests/core/http/test_clienting.py index 9d544aa..a8d68ea 100644 --- a/tests/core/http/test_clienting.py +++ b/tests/core/http/test_clienting.py @@ -374,9 +374,12 @@ def test_client_request_echo_port_empty(): Uses port 80 so may cause problems when testing """ with tcp.openServer(port = 80, bufsize=131072) as alpha: - - assert alpha.ha == ('0.0.0.0', 80) - assert alpha.eha == ('127.0.0.1', 80) + if sys.platform == "linux": + assert alpha.ha == ('', 80) + assert alpha.eha == ('127.0.0.1', 80) + else: + assert alpha.ha == ('0.0.0.0', 80) + assert alpha.eha == ('127.0.0.1', 80) host = alpha.eha[0] port = alpha.eha[1] diff --git a/tests/help/test_timing.py b/tests/help/test_timing.py index f6fd182..f58ff1a 100644 --- a/tests/help/test_timing.py +++ b/tests/help/test_timing.py @@ -15,12 +15,14 @@ def test_timer(): Test Timer class """ timer = Timer() + time.sleep(0.0001) assert timer.duration == 0.0 assert timer.elapsed > 0.0 assert timer.remaining < 0.0 assert timer.expired == True timer.restart(duration=0.125) + time.sleep(0.0001) assert timer.duration == 0.125 assert timer.elapsed > 0.0 assert timer.remaining > 0.0 @@ -29,6 +31,7 @@ def test_timer(): assert timer.expired == True timer.start(duration = 0.125) + time.sleep(0.0001) assert timer.duration == 0.125 assert timer.elapsed < 0.125 assert timer.remaining > 0.0 @@ -37,6 +40,7 @@ def test_timer(): assert timer.expired == True timer = Timer(duration=0.125) + time.sleep(0.0001) assert timer.duration == 0.125 assert timer.elapsed > 0.0 assert timer.remaining > 0.0 @@ -45,6 +49,7 @@ def test_timer(): assert timer.expired == True timer = Timer(duration=0.125, start=time.time() + 0.05) + time.sleep(0.0001) assert timer.duration == 0.125 assert timer.elapsed < 0.0 assert timer.remaining > 0.125 @@ -53,6 +58,7 @@ def test_timer(): assert timer.expired == True timer = Timer(duration=0.125, start=time.time() - 0.05) + time.sleep(0.0001) assert timer.duration == 0.125 assert timer.elapsed > 0.0 assert timer.remaining < 0.075 From 6f26dab44d48953e2e35474c837d1d5ed86713f2 Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Sat, 26 Nov 2022 20:02:46 -0700 Subject: [PATCH 3/3] fix test portOptional to skip server run on port 80 on linux --- tests/core/http/test_clienting.py | 148 +++++++++++++++--------------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/tests/core/http/test_clienting.py b/tests/core/http/test_clienting.py index a8d68ea..42442ae 100644 --- a/tests/core/http/test_clienting.py +++ b/tests/core/http/test_clienting.py @@ -371,7 +371,8 @@ def test_client_request_echo(): def test_client_request_echo_port_empty(): """ Test HTTP Client request echo non blocking with portOptional set to True - Uses port 80 so may cause problems when testing + Uses port 80 so may cause problems when testing on linux github + """ with tcp.openServer(port = 80, bufsize=131072) as alpha: if sys.platform == "linux": @@ -398,28 +399,29 @@ def test_client_request_echo_port_empty(): assert beta.requester.portOptional # portOptional assert beta.requester.lines == [] - # connect Client Beta to Server Alpha - while True: - beta.connector.serviceConnect() - alpha.serviceConnects() - if beta.connector.connected and beta.connector.ca in alpha.ixes: - break - time.sleep(0.05) - - assert beta.connector.accepted - assert beta.connector.connected - assert not beta.connector.cutoff - assert beta.connector.ca == beta.connector.cs.getsockname() - assert beta.connector.ha == beta.connector.cs.getpeername() - assert alpha.eha == beta.connector.ha - - ixBeta = alpha.ixes[beta.connector.ca] - assert ixBeta.ca is not None - assert ixBeta.cs is not None - assert ixBeta.cs.getsockname() == beta.connector.cs.getpeername() - assert ixBeta.cs.getpeername() == beta.connector.cs.getsockname() - assert ixBeta.ca == beta.connector.ca - assert ixBeta.ha, beta.connector.ha + if sys.platform != "linux": + # connect Client Beta to Server Alpha + while True: + beta.connector.serviceConnect() + alpha.serviceConnects() + if beta.connector.connected and beta.connector.ca in alpha.ixes: + break + time.sleep(0.05) + + assert beta.connector.accepted + assert beta.connector.connected + assert not beta.connector.cutoff + assert beta.connector.ca == beta.connector.cs.getsockname() + assert beta.connector.ha == beta.connector.cs.getpeername() + assert alpha.eha == beta.connector.ha + + ixBeta = alpha.ixes[beta.connector.ca] + assert ixBeta.ca is not None + assert ixBeta.cs is not None + assert ixBeta.cs.getsockname() == beta.connector.cs.getpeername() + assert ixBeta.cs.getpeername() == beta.connector.cs.getsockname() + assert ixBeta.ca == beta.connector.ca + assert ixBeta.ha, beta.connector.ha # build request msgOut = beta.requester.rebuild() @@ -443,56 +445,56 @@ def test_client_request_echo_port_empty(): b'\r\nAccept: application/json\r\n\r\n') - - beta.connector.tx(msgOut) - while beta.connector.txbs and not ixBeta.rxbs : - beta.connector.serviceSends() - time.sleep(0.05) - alpha.serviceReceivesAllIx() - time.sleep(0.05) - msgIn = bytes(ixBeta.rxbs) - assert msgIn == msgOut - ixBeta.clearRxbs() - - # build resonse - msgOut = (b'HTTP/1.1 200 OK\r\n' - b'Content-Length: 122\r\n' - b'Content-Type: application/json\r\n' - b'Date: Thu, 30 Apr 2015 19:37:17 GMT\r\n' - b'Server: IoBook.local\r\n\r\n' - b'{"content": null, "query": {"name": "fame"}, "verb": "GET", ' - b'"url": "http://127.0.0.1:8080/echo?name=fame", "action": null}') - - ixBeta.tx(msgOut) - while ixBeta.txbs or not beta.connector.rxbs: - alpha.serviceSendsAllIx() - time.sleep(0.05) - beta.connector.serviceReceives() - time.sleep(0.05) - msgIn = bytes(beta.connector.rxbs) - assert msgIn == msgOut - - while beta.respondent.parser: - beta.respondent.parse() - - assert not beta.connector.rxbs - assert list(beta.respondent.headers.items()) == [('Content-Length', '122'), - ('Content-Type', 'application/json'), - ('Date', 'Thu, 30 Apr 2015 19:37:17 GMT'), - ('Server', 'IoBook.local')] - - beta.respondent.dictify() # convert JSON data in body - - assert beta.respondent.body == (b'{"content": null, ' - b'"query": {"name": "fame"}, ' - b'"verb": "GET", "url' - b'": "http://127.0.0.1:8080/echo?name=fame", ' - b'"action": null}') - assert beta.respondent.data == {'action': None, - 'content': None, - 'query': {'name': 'fame'}, - 'url': 'http://127.0.0.1:8080/echo?name=fame', - 'verb': 'GET'} + if sys.platform != "linux": + beta.connector.tx(msgOut) + while beta.connector.txbs and not ixBeta.rxbs : + beta.connector.serviceSends() + time.sleep(0.05) + alpha.serviceReceivesAllIx() + time.sleep(0.05) + msgIn = bytes(ixBeta.rxbs) + assert msgIn == msgOut + ixBeta.clearRxbs() + + # build resonse + msgOut = (b'HTTP/1.1 200 OK\r\n' + b'Content-Length: 122\r\n' + b'Content-Type: application/json\r\n' + b'Date: Thu, 30 Apr 2015 19:37:17 GMT\r\n' + b'Server: IoBook.local\r\n\r\n' + b'{"content": null, "query": {"name": "fame"}, "verb": "GET", ' + b'"url": "http://127.0.0.1:8080/echo?name=fame", "action": null}') + + ixBeta.tx(msgOut) + while ixBeta.txbs or not beta.connector.rxbs: + alpha.serviceSendsAllIx() + time.sleep(0.05) + beta.connector.serviceReceives() + time.sleep(0.05) + msgIn = bytes(beta.connector.rxbs) + assert msgIn == msgOut + + while beta.respondent.parser: + beta.respondent.parse() + + assert not beta.connector.rxbs + assert list(beta.respondent.headers.items()) == [('Content-Length', '122'), + ('Content-Type', 'application/json'), + ('Date', 'Thu, 30 Apr 2015 19:37:17 GMT'), + ('Server', 'IoBook.local')] + + beta.respondent.dictify() # convert JSON data in body + + assert beta.respondent.body == (b'{"content": null, ' + b'"query": {"name": "fame"}, ' + b'"verb": "GET", "url' + b'": "http://127.0.0.1:8080/echo?name=fame", ' + b'"action": null}') + assert beta.respondent.data == {'action': None, + 'content': None, + 'query': {'name': 'fame'}, + 'url': 'http://127.0.0.1:8080/echo?name=fame', + 'verb': 'GET'}