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
oralc.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()