RIC-642 related changes: REST subscription, rnib enhancements, symptomdata, rest...
[ric-plt/xapp-frame-py.git] / ricxappframe / xapp_rest.py
1 #!/usr/bin/env python3
2 # ==================================================================================
3 #       Copyright (c) 2022 Nokia
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #          http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 # ==================================================================================
17 import socket
18 import http.server
19 import threading
20
21
22 def initResponse(status=200, response="OK"):
23
24     """
25     initResponse
26         init the reponse data for handler to get all details defined
27
28     Parameters
29     ----------
30     status: int
31         http status code
32     response: string
33         http response text
34     Returns
35     -------
36     response - http response text
37     status - http status code
38     payload - payload data for the client (response data text or attachment file data)
39     attachment - file name of the attached payload data
40     mode text (utf-8) or binary data
41     """
42     return {'response': response, 'status': status, 'payload': None, 'ctype': 'application/json', 'attachment': None, 'mode': 'plain'}
43
44
45 class RestHandler(http.server.BaseHTTPRequestHandler):
46
47     def _findUrihandler(self, uri, keys):
48         for key in keys:
49             value = keys[key]
50             if uri.find(value['uri']) >= 0:
51                 return key, value
52         return None, None
53
54     def _sendResponse(self, response):
55         # sends the reponse according to the initResponse() response data
56         self.send_response(response['status'])
57         self.send_header("Server-name", "XAPP REST SERVER 0.9")
58         self.send_header('Content-type', response['ctype'])
59
60         if response['payload'] is not None:
61             # payload has been set
62             length = len(response['payload'])
63             if length != 0:
64                 self.send_header('Content-length', length)
65             if response['attachment'] is not None:
66                 self.send_header('Content-Disposition', "attachment; filename=" + response['attachment'])
67         self.end_headers()
68         if response['payload'] is not None:
69             if response['mode'] == 'plain':
70                 # ascii mode
71                 self.wfile.write(response['payload'].encode('utf-8'))
72             elif response['mode'] == 'binary':
73                 # binary mode
74                 self.wfile.write(response['payload'])
75
76     def add_handler(self, method=None, name=None, uri=None, callback=None):
77         """
78         Adds the function handler for given uri. The function callback is matched in first matching
79         uri. So prepare your handlers setup in such a way that those won't override each other. For example you can setup
80         usual xapp handler in this list:
81
82             server = ricrest.ThreadedHTTPServer(address, port)
83             server.handler.add_handler(self.server.handler, "GET", "config", "/ric/v1/config", self.configGetHandler)
84             server.handler.add_handler(self.server.handler, "GET", "healthAlive", "/ric/v1/health/alive", self.healthyGetAliveHandler)
85             server.handler.add_handler(self.server.handler, "GET", "healthReady", "/ric/v1/health/ready", self.healthyGetReadyHandler)
86             server.handler.add_handler(self.server.handler, "GET", "symptomdata", "/ric/v1/symptomdata", self.symptomdataGetHandler)
87
88         Parameters
89         ----------
90         method string
91             http method GET, POST, DELETE
92         name string
93             unique name - used for map name
94         uri string
95             http uri part which triggers the callback function
96         cb  function
97             function to be used for http method processing
98         """
99         if not hasattr(self, 'handlers'):
100             # init method can't be used becuase it has been inherited from base object
101             # so check the handlers existence and create if not defined
102             self.lock = threading.Lock()
103             self.handlers = dict()
104             self.handlers["get"] = dict()
105             self.handlers["post"] = dict()
106             self.handlers["delete"] = dict()
107         self.lock.acquire()
108         if method == "GET":
109             self.handlers["get"][name] = dict()
110             self.handlers["get"][name]['uri'] = uri
111             self.handlers["get"][name]['cb'] = callback
112         elif method == "POST":
113             self.handlers["post"][name] = dict()
114             self.handlers["post"][name]['uri'] = uri
115             self.handlers["post"][name]['cb'] = callback
116         elif method == "DELETE":
117             self.handlers["delete"][name] = dict()
118             self.handlers["delete"][name]['uri'] = uri
119             self.handlers["delete"][name]['cb'] = callback
120         self.lock.release()
121
122     def do_GET(self):
123         try:
124             response = initResponse(status=404, response='Not Found')
125             cbname, hndl = self._findUrihandler(self.path, self.handlers['get'])
126             if hndl is not None:
127                 # call the defined callback handler
128                 response = hndl['cb'](cbname, self.path, None, self.headers['Content-Type'])
129             self._sendResponse(response)
130
131         except (socket.error, IOError):
132             pass
133
134     def do_DELETE(self):
135         try:
136             response = initResponse(status=404, response='Not Found')
137             cbname, hndl = self._findUrihandler(self.path, self.handlers['delete'])
138             if hndl is not None:
139                 # call the defined callback handler
140                 response = hndl['cb'](cbname, self.path, None, self.headers['Content-Type'])
141             self._sendResponse(response)
142         except (socket.error, IOError):
143             pass
144
145     def do_POST(self):
146         try:
147             response = initResponse(status=404, response='Not Found')
148             cbname, hndl = self._findUrihandler(self.path, self.handlers['post'])
149             if hndl is not None:
150                 data = self.rfile.read(int(self.headers['Content-Length']))
151                 # call the defined callback handler
152                 response = hndl['cb'](cbname, self.path, data, self.headers['Content-Type'])
153                 print(response)
154             self._sendResponse(response)
155         except (socket.error, IOError):
156             pass
157
158
159 class ThreadedHTTPServer(object):
160
161     handler = RestHandler
162     server_class = http.server.HTTPServer
163
164     def __init__(self, host, port):
165         """
166         init
167
168         Parameters
169         ----------
170         host string
171             http listen interface ip ("0.0.0.0" binds all interfaces)
172         port int
173             listen service port
174         """
175         self.server = self.server_class((host, port), self.handler)
176         self.server_thread = threading.Thread(target=self.server.serve_forever)
177         self.server_thread.daemon = True
178
179     def start(self):
180         """
181         start
182             starts the thread serving http requests
183         """
184         self.server_thread.start()
185
186     def stop(self):
187         """
188         stop
189             stops thread serving http requests
190         """
191         self.server.socket.close()
192         self.server.server_close()
193         self.server.shutdown()