blob: 1f4a4e86dc7f1d30bec2f956a4054206ff5a1413 [file] [log] [blame]
ehietala12486342022-06-15 12:21:41 +03001#!/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# ==================================================================================
17import socket
18import http.server
19import threading
20
21
22def 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
45class 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
159class 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()