Repository: rucio/rucio
Issue: Flask endpoints return error with wrong Content-Type where webpy endpoints don't

Motivation
----------
```shell
# with webpy:
curl -XPOST -d '{"a": "b"}' -k https://localhost/traces/
# returns: Created

# with Flask:
curl -XPOST -d '{"a": "b"}' -k https://localhost/traces/                                                                                                                                                                                                                                                  
# returns: {"ExceptionClass": "ValueError", "ExceptionMessage": "Cannot decode json parameter list"}

# with Flask and Content-Type header:
curl -XPOST -d '{"a": "b"}' -k -H "Content-Type: application/json" https://localhost/traces/                                                                                                                                                                                                              
# returns: Created
```

Modification
------------
Debug problem and fix for all endpoints where applicable. This can only happen with endpoints where the Content-Type is not checked already, so there is a limited number of problematic endpoints.

Changed files:
lib/rucio/web/rest/flaskapi/v1/nongrid_traces.py, lib/rucio/web/rest/flaskapi/v1/tmp_dids.py, lib/rucio/web/rest/flaskapi/v1/dirac.py, lib/rucio/web/rest/flaskapi/v1/import.py, lib/rucio/web/rest/flaskapi/v1/accountlimits.py, lib/rucio/tests/test_replica.py, lib/rucio/web/rest/flaskapi/v1/rules.py, lib/rucio/web/rest/flaskapi/v1/traces.py, lib/rucio/web/rest/flaskapi/v1/meta.py, lib/rucio/web/rest/flaskapi/v1/vos.py, lib/rucio/web/rest/flaskapi/v1/replicas.py, lib/rucio/tests/test_trace.py, lib/rucio/web/rest/flaskapi/v1/lifetime_exceptions.py, lib/rucio/web/rest/flaskapi/v1/rses.py, lib/rucio/web/rest/flaskapi/v1/dids.py, lib/rucio/web/rest/flaskapi/v1/subscriptions.py, lib/rucio/web/rest/flaskapi/v1/accounts.py, lib/rucio/common/utils.py

diff --git a/lib/rucio/common/utils.py b/lib/rucio/common/utils.py
index ff39ca759..30b07ba3d 100644
--- a/lib/rucio/common/utils.py
+++ b/lib/rucio/common/utils.py
@@ -447,13 +447,10 @@ def parse_response(data):
     """
     JSON render function
     """
-    ret_obj = None
-    try:
-        ret_obj = data.decode('utf-8')
-    except AttributeError:
-        ret_obj = data
+    if hasattr(data, 'decode'):
+        data = data.decode('utf-8')
 
-    return json.loads(ret_obj, object_hook=datetime_parser)
+    return json.loads(data, object_hook=datetime_parser)
 
 
 def execute(cmd, blocking=True):
diff --git a/lib/rucio/tests/test_replica.py b/lib/rucio/tests/test_replica.py
index ede7db4d0..432f4a018 100644
--- a/lib/rucio/tests/test_replica.py
+++ b/lib/rucio/tests/test_replica.py
@@ -1036,7 +1036,7 @@ def test_list_replicas_streaming_error(content_type, vo, did_client, replica_cli
         # raise after yielding an element
         raise DatabaseException('Database error for testing')
 
-    dids_arg = dumps({'dids': [{'scope': 'mock', 'name': generate_uuid()}]})
+    json_data = dumps({'dids': [{'scope': 'mock', 'name': generate_uuid()}]})
     rest_backend = os.environ.get('REST_BACKEND', 'webpy')
     if rest_backend == 'webpy':
         def list_replicas_on_api():
@@ -1060,7 +1060,7 @@ def test_list_replicas_streaming_error(content_type, vo, did_client, replica_cli
 
             with mock.patch('rucio.web.rest.common.ctx', new=FakeCtx()), \\
                     mock.patch('rucio.web.rest.replica.ctx', new=FakeCtx()), \\
-                    mock.patch('rucio.web.rest.replica.data', return_value=dids_arg), \\
+                    mock.patch('rucio.web.rest.replica.data', return_value=json_data), \\
                     mock.patch('rucio.web.rest.replica.header'), \\
                     mock.patch('rucio.web.rest.replica.generate_http_error', side_effect=MockedHTTPError.generate), \\
                     mock.patch('rucio.web.rest.replica.list_replicas', side_effect=api_returns):
@@ -1085,7 +1085,8 @@ def test_list_replicas_streaming_error(content_type, vo, did_client, replica_cli
                     'request_id': generate_uuid(),
                 }
                 query_string = None
-                data = dids_arg
+                data = json_data
+                get_data = mock.MagicMock(return_value=json_data)
                 headers = Headers()
                 accept_mimetypes = FakeAcceptMimetypes()
                 remote_addr = '127.0.0.1'
diff --git a/lib/rucio/tests/test_trace.py b/lib/rucio/tests/test_trace.py
index d2de78353..9d1daa1a5 100644
--- a/lib/rucio/tests/test_trace.py
+++ b/lib/rucio/tests/test_trace.py
@@ -38,3 +38,12 @@ def test_submit_trace(rest_client):
 
     response = rest_client.post('/traces/', json=payload, content_type=[('Content-Type', 'application/octet-stream')])
     assert response.status_code == 201
+
+
+def test_submit_trace_wrong_content_type(rest_client):
+    """
+    TRACE (REST): submit data with wrong Content-Type to check backwards-compatibility.
+    This emulates the Content-Type default of a curl POST.
+    """
+    response = rest_client.post('/traces/', data='{"a": "b"}', content_type=[('Content-Type', 'application/x-www-form-urlencoded')])
+    assert response.status_code == 201
diff --git a/lib/rucio/web/rest/flaskapi/v1/accountlimits.py b/lib/rucio/web/rest/flaskapi/v1/accountlimits.py
index 0cf23af4a..98c1bd2e3 100644
--- a/lib/rucio/web/rest/flaskapi/v1/accountlimits.py
+++ b/lib/rucio/web/rest/flaskapi/v1/accountlimits.py
@@ -49,9 +49,8 @@ class LocalAccountLimit(MethodView):
         :status 404: RSE not found.
         :status 404: Account not found
         """
-        json_data = request.data
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
         try:
@@ -116,9 +115,8 @@ class GlobalAccountLimit(MethodView):
         :status 404: RSE not found.
         :status 404: Account not found
         """
-        json_data = request.data
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
         try:
diff --git a/lib/rucio/web/rest/flaskapi/v1/accounts.py b/lib/rucio/web/rest/flaskapi/v1/accounts.py
index b051a2f86..559763a92 100644
--- a/lib/rucio/web/rest/flaskapi/v1/accounts.py
+++ b/lib/rucio/web/rest/flaskapi/v1/accounts.py
@@ -88,9 +88,8 @@ class Attributes(MethodView):
         :status 404: Account not found.
         :status 500: Database Exception.
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
 
@@ -264,9 +263,8 @@ class AccountParameter(MethodView):
         :status 404: Account not found.
         :status 500: Database exception.
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
         for key, value in parameter.items():
@@ -298,7 +296,7 @@ class AccountParameter(MethodView):
         :status 500: Database exception.
         """
         try:
-            parameter = loads(request.data.decode())
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
 
@@ -454,9 +452,8 @@ class Identities(MethodView):
         :status 404: Account not found.
         :status 500: Database exception.
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
 
@@ -469,6 +466,7 @@ class Identities(MethodView):
         except KeyError as error:
             if error.args[0] == 'authtype' or error.args[0] == 'identity' or error.args[0] == 'email':
                 return generate_http_error_flask(400, 'KeyError', '%s not defined' % str(error))
+            raise
         except TypeError:
             return generate_http_error_flask(400, 'TypeError', 'body must be a json dictionary')
 
@@ -533,9 +531,8 @@ class Identities(MethodView):
         :status 404: Identity not found.
         :status 500: Database exception.
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
         try:
@@ -544,6 +541,7 @@ class Identities(MethodView):
         except KeyError as error:
             if error.args[0] == 'authtype' or error.args[0] == 'identity':
                 return generate_http_error_flask(400, 'KeyError', '%s not defined' % str(error))
+            raise
         except TypeError:
             return generate_http_error_flask(400, 'TypeError', 'body must be a json dictionary')
         try:
diff --git a/lib/rucio/web/rest/flaskapi/v1/dids.py b/lib/rucio/web/rest/flaskapi/v1/dids.py
index 41ed62f97..85ba538f4 100644
--- a/lib/rucio/web/rest/flaskapi/v1/dids.py
+++ b/lib/rucio/web/rest/flaskapi/v1/dids.py
@@ -320,7 +320,7 @@ class BulkDIDS(MethodView):
         :status 500: Database Exception
         """
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -359,7 +359,7 @@ class Attachments(MethodView):
 
         attachments, ignore_duplicate = [], False
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
             if type(json_data) is dict:
                 attachments = json_data.get('attachments')
                 ignore_duplicate = json_data.get('ignore_duplicate')
@@ -486,7 +486,7 @@ class DIDs(MethodView):
 
         statuses, meta, rules, lifetime, dids, rse = {}, {}, [], None, [], None
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
             type = json_data['type']
             if 'statuses' in json_data:
                 statuses = json_data['statuses']
@@ -562,9 +562,8 @@ class DIDs(MethodView):
             logging.exception("Internal Error")
             return str(error), 500
 
-        json_data = request.data
         try:
-            kwargs = loads(json_data)
+            kwargs = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json data parameter')
 
@@ -682,7 +681,7 @@ class Attachment(MethodView):
             return str(error), 500
 
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -728,7 +727,7 @@ class Attachment(MethodView):
             return str(error), 500
 
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
             if 'dids' in json_data:
                 dids = json_data['dids']
         except ValueError:
@@ -921,9 +920,8 @@ class Meta(MethodView):
             logging.exception("Internal Error")
             return str(error), 500
 
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             meta = params['meta']
             recursive = params.get('recursive', False)
         except ValueError:
@@ -1020,9 +1018,8 @@ class SingleMeta(MethodView):
             logging.exception("Internal Error")
             return str(error), 500
 
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             value = params['value']
             recursive = params.get('recursive', False)
         except ValueError:
@@ -1104,7 +1101,7 @@ class BulkMeta(MethodView):
         :returns: A list of dictionaries containing all meta.
         """
         try:
-            params = parse_response(request.data)
+            params = parse_response(request.get_data(as_text=True))
             dids = params['dids']
         except KeyError as error:
             return generate_http_error_flask(400, 'ValueError', 'Cannot find mandatory parameter : %s' % str(error))
@@ -1293,9 +1290,8 @@ class Resurrect(MethodView):
             500 Internal Error
 
         """
-        json_data = request.data
         try:
-            dids = loads(json_data)
+            dids = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -1379,7 +1375,7 @@ class Follow(MethodView):
             return str(error), 500
 
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -1421,7 +1417,7 @@ class Follow(MethodView):
             return str(error), 500
 
         try:
-            json_data = loads(request.data)
+            json_data = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
diff --git a/lib/rucio/web/rest/flaskapi/v1/dirac.py b/lib/rucio/web/rest/flaskapi/v1/dirac.py
index 34782157e..aa6002673 100644
--- a/lib/rucio/web/rest/flaskapi/v1/dirac.py
+++ b/lib/rucio/web/rest/flaskapi/v1/dirac.py
@@ -58,7 +58,7 @@ class AddFiles(MethodView):
         :status 503: Temporary error.
         """
         try:
-            parameters = parse_response(request.data)
+            parameters = parse_response(request.get_data(as_text=True))
         except (ValueError, InvalidType):
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
diff --git a/lib/rucio/web/rest/flaskapi/v1/import.py b/lib/rucio/web/rest/flaskapi/v1/import.py
index a1b244797..60bc9fef4 100644
--- a/lib/rucio/web/rest/flaskapi/v1/import.py
+++ b/lib/rucio/web/rest/flaskapi/v1/import.py
@@ -61,9 +61,8 @@ class Import(MethodView):
         :status 401: Invalid Auth Token
         :returns: dictionary with rucio data
         """
-        json_data = request.data
         try:
-            data_to_import = json_data and parse_response(json_data)
+            data_to_import = parse_response(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
 
diff --git a/lib/rucio/web/rest/flaskapi/v1/lifetime_exceptions.py b/lib/rucio/web/rest/flaskapi/v1/lifetime_exceptions.py
index 7c5cdd1e6..9a1bd79e0 100644
--- a/lib/rucio/web/rest/flaskapi/v1/lifetime_exceptions.py
+++ b/lib/rucio/web/rest/flaskapi/v1/lifetime_exceptions.py
@@ -84,10 +84,9 @@ class LifetimeException(MethodView):
         :status 500: Internal Error.
         :returns: The id for the newly created execption.
         """
-        json_data = request.data
         dids, pattern, comments, expires_at = [], None, None, None
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             if 'dids' in params:
                 dids = params['dids']
             if 'pattern' in params:
@@ -163,9 +162,8 @@ class LifetimeExceptionId(MethodView):
         :status 404: Lifetime Exception Not Found.
         :Status 500: Internal Error.
         """
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
         try:
diff --git a/lib/rucio/web/rest/flaskapi/v1/meta.py b/lib/rucio/web/rest/flaskapi/v1/meta.py
index d40005636..6ca5eb133 100644
--- a/lib/rucio/web/rest/flaskapi/v1/meta.py
+++ b/lib/rucio/web/rest/flaskapi/v1/meta.py
@@ -66,9 +66,9 @@ class Meta(MethodView):
         :status 409: Key already exists.
         :status 500: Internal Error.
         """
-        json_data = request.data.decode()
+        key_type, value_type, value_regexp = None, None, None
         try:
-            params = json_data and loads(json_data)
+            params = loads(request.get_data(as_text=True))
             if params and 'value_type' in params:
                 value_type = params['value_type']
             if params and 'value_regexp' in params:
@@ -129,9 +129,8 @@ class Values(MethodView):
         :status 409: Value already exists.
         :status 500: Internal Error.
         """
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             value = params['value']
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
diff --git a/lib/rucio/web/rest/flaskapi/v1/nongrid_traces.py b/lib/rucio/web/rest/flaskapi/v1/nongrid_traces.py
index 6dedea2df..8e18aff4c 100644
--- a/lib/rucio/web/rest/flaskapi/v1/nongrid_traces.py
+++ b/lib/rucio/web/rest/flaskapi/v1/nongrid_traces.py
@@ -53,7 +53,7 @@ class XAODTrace(MethodView):
         headers.set('Access-Control-Allow-Credentials', 'true')
 
         try:
-            payload = json.loads(request.data)
+            payload = json.loads(request.get_data(as_text=True))
 
             # generate entry timestamp
             payload['timeentry'] = int(time.time())
diff --git a/lib/rucio/web/rest/flaskapi/v1/replicas.py b/lib/rucio/web/rest/flaskapi/v1/replicas.py
index f0861c0aa..309389f5c 100644
--- a/lib/rucio/web/rest/flaskapi/v1/replicas.py
+++ b/lib/rucio/web/rest/flaskapi/v1/replicas.py
@@ -197,9 +197,8 @@ class Replicas(MethodView):
         :status 409: DID already exists.
         :status 503: Resource Temporary Unavailable.
         """
-        json_data = request.data
         try:
-            parameters = parse_response(json_data)
+            parameters = parse_response(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -241,9 +240,8 @@ class Replicas(MethodView):
         :status 401: Invalid auth token.
         :status 500: Internal Error.
         """
-        json_data = request.data
         try:
-            parameters = parse_response(json_data)
+            parameters = parse_response(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -276,9 +274,8 @@ class Replicas(MethodView):
         :status 404: Replica not found.
         :status 500: Internal Error.
         """
-        json_data = request.data
         try:
-            parameters = parse_response(json_data)
+            parameters = parse_response(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
@@ -349,7 +346,7 @@ class ListReplicas(MethodView):
                            'site': None}
 
         try:
-            params = parse_response(request.data)
+            params = parse_response(request.get_data(as_text=True))
             if 'dids' in params:
                 dids = params['dids']
             if 'schemes' in params:
@@ -517,11 +514,10 @@ class ReplicasDIDs(MethodView):
         :status 500: Internal Error.
         :returns: A list of dictionaries containing the mapping PFNs to DIDs.
         """
-        json_data = request.data
         rse, pfns = None, []
         rse = None
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             if 'pfns' in params:
                 pfns = params['pfns']
             if 'rse' in params:
@@ -564,11 +560,10 @@ class BadReplicas(MethodView):
         :status 500: Internal Error.
         :returns: A list of not successfully declared files.
         """
-        json_data = request.data
         pfns = []
 
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             if 'pfns' in params:
                 pfns = params['pfns']
             if 'reason' in params:
@@ -612,10 +607,9 @@ class SuspiciousReplicas(MethodView):
         :status 500: Internal Error.
         :returns: A list of not successfully declared files.
         """
-        json_data = request.data
         pfns = []
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             if 'pfns' in params:
                 pfns = params['pfns']
             if 'reason' in params:
@@ -834,9 +828,8 @@ class DatasetReplicasBulk(MethodView):
         :returns: A dictionary containing all replicas information.
         """
 
-        json_data = request.data
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             dids = params['dids']
             didslength = len(dids)
         except KeyError as error:
@@ -951,14 +944,13 @@ class BadDIDs(MethodView):
         :returns: A list of not successfully declared files.
         """
 
-        json_data = request.data
         dids = []
         rse = None
         reason = None
         state = None
         expires_at = None
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             if 'dids' in params:
                 dids = params['dids']
             if 'rse' in params:
@@ -1007,13 +999,12 @@ class BadPFNs(MethodView):
         :returns: A list of not successfully declared files.
         """
 
-        json_data = request.data
         pfns = []
         reason = None
         state = None
         expires_at = None
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             if 'pfns' in params:
                 pfns = params['pfns']
             if 'reason' in params:
@@ -1055,11 +1046,10 @@ class Tombstone(MethodView):
         :status 500: Internal Error.
         """
 
-        json_data = request.data
         replicas = []
 
         try:
-            params = parse_response(json_data)
+            params = parse_response(request.get_data(as_text=True))
             if 'replicas' in params:
                 replicas = params['replicas']
         except ValueError:
diff --git a/lib/rucio/web/rest/flaskapi/v1/rses.py b/lib/rucio/web/rest/flaskapi/v1/rses.py
index a4e7b85cd..734baf2fd 100644
--- a/lib/rucio/web/rest/flaskapi/v1/rses.py
+++ b/lib/rucio/web/rest/flaskapi/v1/rses.py
@@ -120,7 +120,6 @@ class RSE(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
         kwargs = {'deterministic': True,
                   'volatile': False, 'city': None, 'staging_area': False,
                   'region_code': None, 'country_name': None,
@@ -128,11 +127,15 @@ class RSE(MethodView):
                   'rse_type': None, 'latitude': None, 'longitude': None,
                   'ASN': None, 'availability': None}
         try:
-            parameters = json_data and loads(json_data)
-            if parameters:
-                for param in kwargs:
-                    if param in parameters:
-                        kwargs[param] = parameters[param]
+            data = request.get_data(as_text=True)
+            if data:
+                parameters = loads(data)
+            else:
+                parameters = {}
+
+            for param in kwargs:
+                if param in parameters:
+                    kwargs[param] = parameters[param]
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
         kwargs['issuer'] = request.environ.get('issuer')
@@ -170,11 +173,14 @@ class RSE(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data
         kwargs = {}
 
         try:
-            parameters = json_data and loads(json_data)
+            data = request.get_data(as_text=True)
+            if data:
+                parameters = loads(data)
+            else:
+                parameters = {}
             kwargs['parameters'] = parameters
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
@@ -266,9 +272,8 @@ class Attributes(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
 
@@ -473,9 +478,8 @@ class Protocol(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
         try:
-            parameters = loads(json_data)
+            parameters = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
 
@@ -557,9 +561,8 @@ class Protocol(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
 
@@ -666,9 +669,8 @@ class Usage(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
 
@@ -766,9 +768,8 @@ class Limits(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
         try:
@@ -798,9 +799,8 @@ class Limits(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
         try:
@@ -898,9 +898,8 @@ class Distance(MethodView):
         :status 404: RSE Not Found.
         :status 500: Internal Error.
         """
-        json_data = request.data.decode()
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
         try:
@@ -933,9 +932,8 @@ class Distance(MethodView):
         :status 404: RSE Not Found.
         :status 500: Internal Error.
         """
-        json_data = request.data.decode()
         try:
-            parameters = loads(json_data)
+            parameters = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
         try:
diff --git a/lib/rucio/web/rest/flaskapi/v1/rules.py b/lib/rucio/web/rest/flaskapi/v1/rules.py
index 076c55142..08de3f43f 100644
--- a/lib/rucio/web/rest/flaskapi/v1/rules.py
+++ b/lib/rucio/web/rest/flaskapi/v1/rules.py
@@ -64,7 +64,7 @@ class Rule(MethodView):
         """
         try:
             estimate_ttc = False
-            params = loads(request.data)
+            params = loads(request.get_data(as_text=True))
             if 'estimate_ttc' in params:
                 estimate_ttc = params['estimate_ttc']
         except ValueError:
@@ -91,9 +91,8 @@ class Rule(MethodView):
         :status 401: Invalid Auth Token
         :status 404: no rule found for id
         """
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             options = params['options']
             update_replication_rule(rule_id=rule_id, options=options, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
         except AccessDenied as error:
@@ -122,10 +121,9 @@ class Rule(MethodView):
         :status 401: Invalid Auth Token
         :status 404: no rule found for id
         """
-        json_data = request.data
         try:
             purge_replicas = None
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             if 'purge_replicas' in params:
                 purge_replicas = params['purge_replicas']
         except ValueError:
@@ -218,14 +216,13 @@ class AllRule(MethodView):
         :status 409: Invalid Object
         :returns: List of ids for created rules
         """
-        json_data = request.data
         try:
             grouping, weight, lifetime, locked, subscription_id, source_replica_expression, activity, notify,\\
                 purge_replicas, ignore_availability, comment, ask_approval, asynchronous, priority,\\
                 split_container, meta = 'DATASET', None, None, False, None, None, None, None, False, False, None,\\
                 False, False, 3, False, None
 
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             dids = params['dids']
             account = params['account']
             copies = params['copies']
@@ -365,11 +362,10 @@ class ReduceRule(MethodView):
         :status 409: Rule replace failed.
         :returns: List of rule ids
         """
-        json_data = request.data
         try:
             exclude_expression = None
 
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             copies = params['copies']
             if 'exclude_expression' in params:
                 exclude_expression = params['exclude_expression']
@@ -411,9 +407,8 @@ class MoveRule(MethodView):
         :status 409: Rule replace failed.
         :returns: List of rule ids.
         """
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             rule_id = params['rule_id']
             rse_expression = params['rse_expression']
         except ValueError:
diff --git a/lib/rucio/web/rest/flaskapi/v1/subscriptions.py b/lib/rucio/web/rest/flaskapi/v1/subscriptions.py
index 25bc698c0..be08db054 100644
--- a/lib/rucio/web/rest/flaskapi/v1/subscriptions.py
+++ b/lib/rucio/web/rest/flaskapi/v1/subscriptions.py
@@ -82,9 +82,8 @@ class Subscription(MethodView):
         :status 404: Subscription Not Found.
         :status 500: Internal Error.
         """
-        json_data = request.data.decode()
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             params = params['options']
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
@@ -127,9 +126,8 @@ class Subscription(MethodView):
         :returns: ID if newly created subscription.
         """
         dry_run = 0
-        json_data = request.data
         try:
-            params = loads(json_data)
+            params = loads(request.get_data(as_text=True))
             params = params['options']
             filter = params['filter']
             replication_rules = params['replication_rules']
diff --git a/lib/rucio/web/rest/flaskapi/v1/tmp_dids.py b/lib/rucio/web/rest/flaskapi/v1/tmp_dids.py
index 3af755beb..08f94e057 100644
--- a/lib/rucio/web/rest/flaskapi/v1/tmp_dids.py
+++ b/lib/rucio/web/rest/flaskapi/v1/tmp_dids.py
@@ -51,9 +51,8 @@ class BulkDIDS(MethodView):
         :status 500: Internal Error.
         """
 
-        json_data = request.data
         try:
-            dids = loads(json_data)
+            dids = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter list')
 
diff --git a/lib/rucio/web/rest/flaskapi/v1/traces.py b/lib/rucio/web/rest/flaskapi/v1/traces.py
index 7133ac5ef..95a3c42d0 100644
--- a/lib/rucio/web/rest/flaskapi/v1/traces.py
+++ b/lib/rucio/web/rest/flaskapi/v1/traces.py
@@ -23,8 +23,8 @@
 
 import calendar
 import datetime
-import logging
 import json
+import logging
 import uuid
 
 from flask import Flask, Blueprint, request
@@ -57,7 +57,7 @@ class Trace(MethodView):
         headers.set('Access-Control-Allow-Credentials', 'true')
 
         try:
-            payload = json.loads(request.data)
+            payload = json.loads(request.get_data(as_text=True))
 
             # generate entry timestamp
             payload['traceTimeentry'] = datetime.datetime.utcnow()
diff --git a/lib/rucio/web/rest/flaskapi/v1/vos.py b/lib/rucio/web/rest/flaskapi/v1/vos.py
index 6d336ceaa..beaf8f8d2 100644
--- a/lib/rucio/web/rest/flaskapi/v1/vos.py
+++ b/lib/rucio/web/rest/flaskapi/v1/vos.py
@@ -82,15 +82,17 @@ class VO(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
         kwargs = {'description': None, 'email': None}
 
         try:
-            parameters = json_data and loads(json_data)
-            if parameters:
-                for param in kwargs:
-                    if param in parameters:
-                        kwargs[param] = parameters[param]
+            data = request.get_data(as_text=True)
+            if data:
+                parameters = loads(data)
+            else:
+                parameters = {}
+            for param in kwargs:
+                if param in parameters:
+                    kwargs[param] = parameters[param]
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'Cannot decode json parameter dictionary')
         kwargs['issuer'] = request.environ.get('issuer')
@@ -127,10 +129,8 @@ class VO(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
-
         try:
-            parameters = loads(json_data)
+            parameters = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')
 
@@ -172,10 +172,8 @@ class RecoverVO(MethodView):
         :status 500: Internal Error.
 
         """
-        json_data = request.data.decode()
-
         try:
-            parameter = loads(json_data)
+            parameter = loads(request.get_data(as_text=True))
         except ValueError:
             return generate_http_error_flask(400, 'ValueError', 'cannot decode json parameter dictionary')