Repository: ably/ably-python
Pull request #399 (2022-12-21 01:02:30)

RSC7c
Implemented [RSC7c](https://sdk.ably.com/builds/ably/specification/main/features/#RSC7c) as described.


Patch:
diff --git a/ably/http/http.py b/ably/http/http.py
index e2607ca..51b52b6 100644
--- a/ably/http/http.py
+++ b/ably/http/http.py
@@ -171,6 +171,8 @@ class Http:
         else:
             all_headers = HttpUtils.default_get_headers(self.options.use_binary_protocol)
 
+        params = HttpUtils.get_query_params(self.options)
+
         if not skip_auth:
             if self.auth.auth_mechanism == Auth.Method.BASIC and self.preferred_scheme.lower() == 'http':
                 raise AblyException(
@@ -197,6 +199,7 @@ class Http:
                 method=method,
                 url=url,
                 content=body,
+                params=params,
                 headers=all_headers,
                 timeout=timeout,
             )
diff --git a/ably/http/httputils.py b/ably/http/httputils.py
index 0517f96..a621a6b 100644
--- a/ably/http/httputils.py
+++ b/ably/http/httputils.py
@@ -1,3 +1,5 @@
+import base64
+import os
 import platform
 
 import ably
@@ -36,3 +38,12 @@ class HttpUtils:
         return {
             'Host': host,
         }
+
+    @staticmethod
+    def get_query_params(options):
+        params = {}
+
+        if options.add_request_ids:
+            params['request_id'] = base64.urlsafe_b64encode(os.urandom(12)).decode('ascii')
+
+        return params
diff --git a/ably/types/options.py b/ably/types/options.py
index 38ef8ed..24ef4d8 100644
--- a/ably/types/options.py
+++ b/ably/types/options.py
@@ -12,7 +12,7 @@ class Options(AuthOptions):
                  http_open_timeout=None, http_request_timeout=None,
                  http_max_retry_count=None, http_max_retry_duration=None,
                  fallback_hosts=None, fallback_hosts_use_default=None, fallback_retry_timeout=None,
-                 idempotent_rest_publishing=None,
+                 idempotent_rest_publishing=None, add_request_ids=False,
                  **kwargs):
         super().__init__(**kwargs)
 
@@ -46,6 +46,7 @@ class Options(AuthOptions):
         self.__fallback_hosts_use_default = fallback_hosts_use_default
         self.__fallback_retry_timeout = fallback_retry_timeout
         self.__idempotent_rest_publishing = idempotent_rest_publishing
+        self.__add_request_ids = add_request_ids
 
         self.__rest_hosts = self.__get_rest_hosts()
 
@@ -181,6 +182,10 @@ class Options(AuthOptions):
     def idempotent_rest_publishing(self):
         return self.__idempotent_rest_publishing
 
+    @property
+    def add_request_ids(self):
+        return self.__add_request_ids
+
     def __get_rest_hosts(self):
         """
         Return the list of hosts as they should be tried. First comes the main


Test patch:
diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py
index 7ac8001..b9fc57d 100644
--- a/test/ably/resthttp_test.py
+++ b/test/ably/resthttp_test.py
@@ -1,3 +1,4 @@
+import base64
 import re
 import time
 
@@ -208,6 +209,29 @@ class TestRestHttp(BaseAsyncTestCase):
         assert re.search(expr, r.request.headers['Ably-Agent'])
         await ably.close()
 
+    # RSC7c
+    async def test_add_request_ids(self):
+        # With request id
+        ably = await RestSetup.get_ably_rest(add_request_ids=True)
+        r = await ably.http.make_request('HEAD', '/time', skip_auth=True)
+        assert 'request_id' in r.request.url.params
+        request_id1 = r.request.url.params['request_id']
+        assert len(base64.urlsafe_b64decode(request_id1)) == 12
+
+        # With request id and new request
+        r = await ably.http.make_request('HEAD', '/time', skip_auth=True)
+        assert 'request_id' in r.request.url.params
+        request_id2 = r.request.url.params['request_id']
+        assert len(base64.urlsafe_b64decode(request_id2)) == 12
+        assert request_id1 != request_id2
+        await ably.close()
+
+        # With request id and new request
+        ably = await RestSetup.get_ably_rest()
+        r = await ably.http.make_request('HEAD', '/time', skip_auth=True)
+        assert 'request_id' not in r.request.url.params
+        await ably.close()
+
     async def test_request_over_http2(self):
         url = 'https://www.example.com'
         respx.get(url).mock(return_value=Response(status_code=200))