2 # ==================================================================================
3 # Copyright (c) 2022 Nokia
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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 # ==================================================================================
22 def initResponse(status=200, response="OK"):
26 init the reponse data for handler to get all details defined
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
42 return {'response': response, 'status': status, 'payload': None, 'ctype': 'application/json', 'attachment': None, 'mode': 'plain'}
45 class RestHandler(http.server.BaseHTTPRequestHandler):
47 def _findUrihandler(self, uri, keys):
50 if uri.find(value['uri']) >= 0:
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'])
60 if response['payload'] is not None:
61 # payload has been set
62 length = len(response['payload'])
64 self.send_header('Content-length', length)
65 if response['attachment'] is not None:
66 self.send_header('Content-Disposition', "attachment; filename=" + response['attachment'])
68 if response['payload'] is not None:
69 if response['mode'] == 'plain':
71 self.wfile.write(response['payload'].encode('utf-8'))
72 elif response['mode'] == 'binary':
74 self.wfile.write(response['payload'])
76 def add_handler(self, method=None, name=None, uri=None, callback=None):
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:
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)
91 http method GET, POST, DELETE
93 unique name - used for map name
95 http uri part which triggers the callback function
97 function to be used for http method processing
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()
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
124 response = initResponse(status=404, response='Not Found')
125 cbname, hndl = self._findUrihandler(self.path, self.handlers['get'])
127 # call the defined callback handler
128 response = hndl['cb'](cbname, self.path, None, self.headers['Content-Type'])
129 self._sendResponse(response)
131 except (socket.error, IOError):
136 response = initResponse(status=404, response='Not Found')
137 cbname, hndl = self._findUrihandler(self.path, self.handlers['delete'])
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):
147 response = initResponse(status=404, response='Not Found')
148 cbname, hndl = self._findUrihandler(self.path, self.handlers['post'])
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'])
154 self._sendResponse(response)
155 except (socket.error, IOError):
159 class ThreadedHTTPServer(object):
161 handler = RestHandler
162 server_class = http.server.HTTPServer
164 def __init__(self, host, port):
171 http listen interface ip ("0.0.0.0" binds all interfaces)
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
182 starts the thread serving http requests
184 self.server_thread.start()
189 stops thread serving http requests
191 self.server.socket.close()
192 self.server.server_close()
193 self.server.shutdown()