source: ogAgent-Git/src/opengnsys/httpserver.py @ ca12de8

decorare-oglive-methodsfixes-winlgromero-filebeatmainnew-browserno-ptt-paramogcore1oglogoglog2override-moduleping1ping2ping3ping4report-progresstls
Last change on this file since ca12de8 was 8a36992, checked in by Natalia Serrano <natalia.serrano@…>, 8 months ago

refs #708 handle some invalid URLs and return 404

  • Property mode set to 100644
File size: 7.0 KB
RevLine 
[11f7a07]1# -*- coding: utf-8 -*-
2#
3# Copyright (c) 2015 Virtual Cable S.L.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without modification,
7# are permitted provided that the following conditions are met:
8#
9#    * Redistributions of source code must retain the above copyright notice,
10#      this list of conditions and the following disclaimer.
11#    * Redistributions in binary form must reproduce the above copyright notice,
12#      this list of conditions and the following disclaimer in the documentation
13#      and/or other materials provided with the distribution.
14#    * Neither the name of Virtual Cable S.L. nor the names of its contributors
15#      may be used to endorse or promote products derived from this software
16#      without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[2350389]28"""
[11f7a07]29@author: Adolfo Gómez, dkmaster at dkmon dot com
[2350389]30"""
[53e7d45]31
[11f7a07]32
[2350389]33import json
34import ssl
35import threading
[11f7a07]36from six.moves.socketserver import ThreadingMixIn  # @UnresolvedImport
37from six.moves.BaseHTTPServer import BaseHTTPRequestHandler  # @UnresolvedImport
38from six.moves.BaseHTTPServer import HTTPServer  # @UnresolvedImport
39from six.moves.urllib.parse import unquote  # @UnresolvedImport
40
41from .utils import exceptionToMessage
42from .certs import createSelfSignedCert
43from .log import logger
44
[e274dc0]45
[11f7a07]46class HTTPServerHandler(BaseHTTPRequestHandler):
47    service = None
48    protocol_version = 'HTTP/1.0'
49    server_version = 'OpenGnsys Agent Server'
50    sys_version = ''
51   
52    def sendJsonError(self, code, message):
53        self.send_response(code)
54        self.send_header('Content-type', 'application/json')
55        self.end_headers()
[e274dc0]56        self.wfile.write(str.encode(json.dumps({'error': message})))
[11f7a07]57        return
58
59    def sendJsonResponse(self, data):
[d7a7a1f]60        try: self.send_response(200)
[8c6a652]61        except Exception as e: logger.warn ('exception: "{}"'.format(str(e)))
[11f7a07]62        data = json.dumps(data)
63        self.send_header('Content-type', 'application/json')
[2350389]64        self.send_header('Content-Length', str(len(data)))
[11f7a07]65        self.end_headers()
66        # Send the html message
[e274dc0]67        self.wfile.write(str.encode(data))
68
[11f7a07]69    def parseUrl(self):
[e274dc0]70        """
71        Very simple path & params splitter
72        """
[11f7a07]73        path = self.path.split('?')[0][1:].split('/')
74       
75        try:
76            params = dict((v[0], unquote(v[1])) for v in (v.split('=') for v in self.path.split('?')[1].split('&')))
77        except Exception:
78            params = {}
79
80        for v in self.service.modules:
81            if v.name == path[0]:  # Case Sensitive!!!!
[e274dc0]82                return v, path[1:], params
[11f7a07]83           
[e274dc0]84        return None, path, params
[11f7a07]85   
[2350389]86    def notifyMessage(self, module, path, get_params, post_params):
[e274dc0]87        """
[11f7a07]88        Locates witch module will process the message based on path (first folder on url path)
[e274dc0]89        """
[11f7a07]90        try:
[8a36992]91            if module is None:
92                raise Exception ({ '_httpcode': 404, '_msg': f'Module {path[0]} not found' })
[2350389]93            data = module.processServerMessage(path, get_params, post_params, self)
[11f7a07]94            self.sendJsonResponse(data)
95        except Exception as e:
96            logger.exception()
[5b058a5]97            n_args = len (e.args)
98            if 0 == n_args:
99                logger.error ('Empty exception raised from message processor for "{}"'.format(path[0]))
100                self.sendJsonError(500, exceptionToMessage(e))
101            else:
102                arg0 = e.args[0]
103                if type (arg0) is str:
104                    logger.error ('Message processor for "{}" returned exception string "{}"'.format(path[0], str(e)))
105                    self.sendJsonError (500, exceptionToMessage(e))
106                elif type (arg0) is dict:
107                    if '_httpcode' in arg0:
108                        logger.warning ('Message processor for "{}" returned HTTP code "{}" with exception string "{}"'.format(path[0], str(arg0['_httpcode']), str(arg0['_msg'])))
109                        self.sendJsonError (arg0['_httpcode'], arg0['_msg'])
110                    else:
111                        logger.error ('Message processor for "{}" returned exception dict "{}" with no HTTP code'.format(path[0], str(e)))
112                        self.sendJsonError (500, exceptionToMessage(e))
113                else:
114                    logger.error ('Message processor for "{}" returned non-string and non-dict exception "{}"'.format(path[0], str(e)))
115                    self.sendJsonError (500, exceptionToMessage(e))
116            ## not reached
[11f7a07]117           
118    def do_GET(self):
119        module, path, params = self.parseUrl()
120       
121        self.notifyMessage(module, path, params, None)
122       
123    def do_POST(self):
[2350389]124        module, path, get_params = self.parseUrl()
125        post_params = None
[11f7a07]126
[b0b6500]127        # Tries to get JSON content (UTF-8 encoded)
[11f7a07]128        try:
[2350389]129            length = int(self.headers.get('content-length'))
[b0b6500]130            content = self.rfile.read(length).decode('utf-8')
[2350389]131            logger.debug('length: {0}, content >>{1}<<'.format(length, content))
132            post_params = json.loads(content)
[11f7a07]133        except Exception as e:
134            self.sendJsonError(500, exceptionToMessage(e))
135           
[2350389]136        self.notifyMessage(module, path, get_params, post_params)
[11f7a07]137
138    def log_error(self, fmt, *args):
139        logger.error('HTTP ' + fmt % args)
140       
141    def log_message(self, fmt, *args):
[10fab78]142        logger.debug('HTTP ' + fmt % args)
[11f7a07]143       
144
145class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
146    pass
147
[e274dc0]148
[11f7a07]149class HTTPServerThread(threading.Thread):
150    def __init__(self, address, service):
151        super(self.__class__, self).__init__()
152
153        HTTPServerHandler.service = service  # Keep tracking of service so we can intercact with it
154
155        self.certFile = createSelfSignedCert()
156        self.server = HTTPThreadingServer(address, HTTPServerHandler)
[d7a7a1f]157        context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
158        context.load_cert_chain(certfile=self.certFile)
159        self.server.socket = context.wrap_socket(self.server.socket, server_side=True)
[11f7a07]160       
161        logger.debug('Initialized HTTPS Server thread on {}'.format(address))
162
163    def getServerUrl(self):
164        return 'https://{}:{}/'.format(self.server.server_address[0], self.server.server_address[1])
165
166    def stop(self):
167        self.server.shutdown()
168
169    def run(self):
170        self.server.serve_forever()
Note: See TracBrowser for help on using the repository browser.