HTTP

The system provides following Python APIs to inspect and modify HTTP/2 message.

alc.http.get_message()

Returns the current HTTP message object of type alc.http.Request or alc.http.Response. In case HTTP processing is currently not active, none is returned.

The alc.http.Request class

class alc.http.Request

Class representing an HTTP request.

method

HTTP method (GET/POST/PUT/…), value of the :method pseudo-header.

This attribute is read-only.

path

HTTP path, value of the :path pseudo-header.

headers

A dict of headers where the keys are the header-names (in lowercase) and the values are the header values, both str. Pseudo-headers are not included.

This attribute is read-only, the dict can not be modified.

body

Body of the request: a str in case of a regular message or a tuple of alc.http.MessagePart in case of a multipart message.

client

The client endpoint, a str formatted as “ip:port”.

server

The server endpoint, a str formatted as “ip:port”.

ingress

A boolean indicating whether this request is ingressing (True) or egressing (False).

str(request)

The string representation of a request contains the headers (including :method and :path pseudo-headers) and body of the request, formatted similar to how a HTTP/1.x message is transmitted.

This can be used for debugging purposes, but should not be used in production scripts to obtain any of the information in the request, as the specialized methods are much more performant.

The alc.http.Response class

class alc.http.Response

Class representing an HTTP response.

status

HTTP status (200/404/501/…), value of the :status pseudo-header.

This attribute is read-only.

headers

A dict of headers where the keys are the header-names (in lowercase) and the values are the header values, both str. Pseudo-headers are not included.

This attribute is read-only, the dict can not be modified.

body

Body of the response: a str in case of a regular message or a tuple of alc.http.MessagePart in case of a multipart message.

client

The client endpoint, a str formatted as “ip:port”.

server

The server endpoint, a str formatted as “ip:port”.

ingress

A boolean indicating whether this response is ingressing (True) or egressing (False).

str(response)

The string representation of a request contains the headers (including :method and :path pseudo-headers) and body of the request, formatted similar to how a HTTP/1.x message is transmitted.

This can be used for debugging purposes, but should not be used in production scripts to obtain any of the information in the request, as the specialized methods are much more performant.

The alc.http.MessagePart class

class alc.http.MessagePart

Class representing a single part of a multipart request/response.

headers

A dict of headers where the keys are the header-names (in lowercase) and the values are the header values, both str. Pseudo-headers are not included.

This attribute is read-only, the dict can also not be modified.

body()

Body of the message part as a str. Can be modified to be a str or bytes.

str(part)

The string representation of a request contains the headers (including :method and :path pseudo-headers) and body of the request, formatted similar to how a HTTP/1.x message is transmitted.

This can be used for debugging purposes, but should not be used in production scripts to obtain any of the information in the request, as the specialized methods are much more performant.

Examples

Example 1: Check for Request/Response and modify body

 1from alc import http
 2import json
 3
 4msg = http.get_message()
 5
 6# You can check if the message is a request or response by asserting its type
 7if isinstance(msg, http.Request):
 8
 9    # The body can either be singular or multipart,
10    # which is easily checked by checking if it's a tuple or a str.
11    if 'sm-contexts' in msg.path and isinstance(msg.body, tuple):
12        # A tuple is easily iterated over
13        for part in msg.body:
14            # Using the get-method on a dict will return None in case the header is not there.
15            # This method is preferred over using try-catch in combination with indexing the dict
16            # using the msg.headers['content-type'] syntax.
17            if part.headers.get('content-type') == 'application/json':
18                # fetch the body and decode as json
19                body = json.loads(part.body)
20                # if present, always set selection-mode to VERIFIED in the message from AMF
21                if 'selMode' in body:
22                    body['selMode'] = 'VERIFIED'
23                    # re-encode to json and set the body to the modified json
24                part.body = json.dumps(body)
25
26    # The same but for a single body
27    elif 'sm-policies' in msg.path and msg.headers.get('content-type') == 'application/json':
28        body = json.loads(msg.body)
29        if 'ipDomain' in body:
30            body['ipDomain'] = 'vprn3'
31            # re-encode to json and set the body to the modified json
32            msg.body = json.dumps(body)

Example 2: Modify the Request path

 1from alc import http
 2import json
 3import re
 4
 5regex = re.compile(r"([?&])gpsi=msisdn-\d+&?")
 6
 7msg = http.get_message()
 8if type(msg) is http.Request:
 9    if 'npcf-smpolicycontrol' in msg.path:
10        msg.path = regex.sub(r"\1", msg.path)

Example 3: More complex script to add QOS data

  1from alc import http
  2import json
  3
  4# dict containing qos-data descriptions
  5QOS_DATA = {
  6    "QoS Data 1": {
  7        "5qi": 1,
  8        "qosId": "QoS Data 1",
  9        "arp": {"preemptCap": "NOT_PREEMPT", "priorityLevel": 9, "preemptVuln": "NOT_PREEMPTABLE"},
 10        "defQosFlowIndication": False,
 11        "gbrDl": "2.000 Mbps",
 12        "maxbrDl": "2.000 Mbps",
 13        "reflectiveQos": False,
 14        "gbrUl": "2.000 Mbps",
 15        "maxbrUl": "2.000 Mbps",
 16        "qnc": False,
 17    },
 18    "QoS Data 2": {
 19        "5qi": 2,
 20        "qosId": "QoS Data 2",
 21        "arp": {"preemptCap": "NOT_PREEMPT", "priorityLevel": 9, "preemptVuln": "NOT_PREEMPTABLE"},
 22        "defQosFlowIndication": False,
 23        "gbrDl": "2.000 Mbps",
 24        "maxbrDl": "2.000 Mbps",
 25        "reflectiveQos": False,
 26        "gbrUl": "2.000 Mbps",
 27        "maxbrUl": "2.000 Mbps",
 28        "qnc": False,
 29    }
 30}
 31
 32
 33def add_qos_data(body):
 34    body_decoded = json.loads(body)
 35
 36    if not isinstance(body_decoded, dict):
 37        return None  # body is not a json dictionary
 38
 39    sm_policy_decision = body_decoded.get('smPolicyDecision')
 40    if sm_policy_decision is None:
 41        return None  # no smPolicyDecision (e.g. no PCF update-notify) -> skip
 42
 43    pcc_rules = sm_policy_decision.get('pccRules')
 44    if pcc_rules is None:
 45        return None  # no pcc-rules in smPolicyDecision -> skip
 46
 47    # fetch all "refQosData" that is referred by added pcc-rules in this message
 48    ref_qos_data = { ref_qos_data for rule in pcc_rules.values() for ref_qos_data in rule.get('refQosData', []) }
 49
 50    # construct the preprovisioned qos-data for the ones referenced in this message
 51    added_qos_data = { qos_data_name: QOS_DATA[qos_data_name] for qos_data_name in ref_qos_data if qos_data_name in QOS_DATA }
 52
 53    if not added_qos_data:
 54        return None  # nothing to add
 55
 56    if 'qosDecs' in sm_policy_decision:
 57        # add the extra qos-data to the existing qosDecs
 58        sm_policy_decision['qosDecs'].update(added_qos_data)
 59    else:
 60        # qos-data didn't exist -> add the extra qosDecs to the smPolicyDecision
 61        sm_policy_decision['qosDecs'] = added_qos_data
 62
 63    # return the body with the added info
 64    return json.dumps(body_decoded)
 65
 66
 67def add_qos_data_to_msg():
 68    msg = http.get_message()
 69
 70    if not isinstance(msg, http.Request):
 71        return  # no request (can be omitted if python-policy only enables script on http2 request)
 72
 73    if not msg.method == "POST":
 74        return  # no POST method -> skip
 75    if not msg.path.startswith("/npcf-smpolicycontrol/v1/sm-policies"):
 76        return  # no nPCF -> skip
 77    if not msg.path.endswith('/update'):
 78        return  # no update -> skip
 79
 80    content_type = msg.headers.get('content-type')
 81    if content_type != 'application/json':
 82        return  # no json content -> skip
 83    if msg.body == 'null':
 84        return  # message is empty -> skip
 85
 86    if isinstance(msg.body, tuple):
 87        # multipart body
 88        result = []
 89        has_changed = False
 90        for part in msg.body:
 91            new_part = add_qos_data(part)
 92            if new_part is not None:
 93                result.append(new_part)
 94                has_changed = True
 95            else:
 96                result.append(part)
 97        if has_changed:
 98            msg.body = tuple(result)
 99    else:
100        # single body
101        result = add_qos_data(msg.body)
102        if result is not None:
103            msg.body = result
104
105
106add_qos_data_to_msg()