Repository: dmwm/wmcore
Issue: pycurl_manager should properly parse HTTP/2 HTTP reponses

**Impact of the bug**
Current version of pucurl only able to parse HTTP/1.1 responses, while HTTP standard support HTTP/1, HTTP/1.1, and HTTP/2

**Describe the bug**
pycurl_manager parses HTTP response via regexp, see
https://github.com/dmwm/WMCore/blob/master/src/python/WMCore/Services/pycurl_manager.py#L103-L105
and the following pattern is wrong:
```
>>> import re
>>> row="HTTP/2 200"
>>> startRegex = r"^HTTP/\\d.\\d \\d{3}"
>>> re.search(startRegex, row)
```

**How to reproduce it**
```
>>> import re
>>> row="HTTP/2 200"
>>> startRegex = r"^HTTP/\\d.\\d \\d{3}"
>>> re.search(startRegex, row)
```

**Expected behavior**
The fix should include ability to properly parse HTTP/1.1 and HTTP/2 HTTP responses, e.g.
```
>>> startRegex = r"^HTTP/(\\d|\\d.\\d) \\d{3}"
>>> re.search(startRegex, "HTTP/1.1 200")
<re.Match object; span=(0, 12), match='HTTP/1.1 200'>
>>> re.search(startRegex, "HTTP/2 200")
<re.Match object; span=(0, 10), match='HTTP/2 200'>
```

**Additional context and error message**
This is the reason why reqmgr2-tasks pod is failing on k8s using pypi deployment.

Changed files:
test/python/WMCore_t/Services_t/pycurl_manager_t.py, src/python/WMCore/Services/pycurl_manager.py

diff --git a/src/python/WMCore/Services/pycurl_manager.py b/src/python/WMCore/Services/pycurl_manager.py
index 72fb4338f..149201f42 100644
--- a/src/python/WMCore/Services/pycurl_manager.py
+++ b/src/python/WMCore/Services/pycurl_manager.py
@@ -100,9 +100,8 @@ class ResponseHeader(object):
 
     def parse(self, response):
         """Parse response header and assign class member data"""
-        startRegex = r"^HTTP/\\d.\\d \\d{3}"
-        continueRegex = r"^HTTP/\\d.\\d 100"  # Continue: client should continue its request
-        replaceRegex = r"^HTTP/\\d.\\d"
+        startRegex = r"^HTTP/(\\d|\\d.\\d) \\d{3}" # to match "HTTP/1.1 200" and "HTTP/2 200"
+        continueRegex = r"^HTTP/(\\d|\\d.\\d) 100"  # Continue: client should continue its request
 
         response = decodeBytesToUnicode(response)
 
@@ -113,10 +112,11 @@ class ResponseHeader(object):
             if re.search(startRegex, row):
                 if re.search(continueRegex, row):
                     continue
-                res = re.sub(replaceRegex, "", row).strip()
-                status, reason = res.split(' ', 1)
-                self.status = int(status)
-                self.reason = reason
+                # split HTTP header row on empty space
+                # for HTTP/proto STATUS REASON
+                arr = row.split(' ')
+                self.status = int(arr[1])
+                self.reason = ' '.join(arr[2:])
                 continue
             try:
                 key, val = row.split(':', 1)
diff --git a/test/python/WMCore_t/Services_t/pycurl_manager_t.py b/test/python/WMCore_t/Services_t/pycurl_manager_t.py
index 54cf9a7eb..8b1db7b30 100644
--- a/test/python/WMCore_t/Services_t/pycurl_manager_t.py
+++ b/test/python/WMCore_t/Services_t/pycurl_manager_t.py
@@ -266,5 +266,28 @@ class PyCurlManager(unittest.TestCase):
             # https://github.com/PyCQA/pylint/issues/437
             traceback.print_exc()
 
+    def testHTTPResponse(self):
+        """
+        Test a HTTP response parsing for different HTTP protocols
+        """
+        # parse HTTP/1.1 responses
+        response = b'HTTP/1.1 200 OK\\r\\ncache-control: max-age=300\\r\\ncontent-length: 123\\r\\n\\r\\n'
+        obj = ResponseHeader(response)
+        self.assertEqual(obj.status, 200)
+
+        response = b'HTTP/1.1 400 Not Found\\r\\ncache-control: max-age=300\\r\\ncontent-length: 123\\r\\n\\r\\n'
+        obj = ResponseHeader(response)
+        self.assertEqual(obj.status, 400)
+
+        # parse HTTP/2 responses
+        response = b'HTTP/2 200 OK\\r\\ncache-control: max-age=300\\r\\ncontent-length: 123\\r\\n\\r\\n'
+        obj = ResponseHeader(response)
+        self.assertEqual(obj.status, 200)
+
+        response = b'HTTP/2 400 Not Found\\r\\ncache-control: max-age=300\\r\\ncontent-length: 123\\r\\n\\r\\n'
+        obj = ResponseHeader(response)
+        self.assertEqual(obj.status, 400)
+
+
 if __name__ == "__main__":
     unittest.main()