# Module `lib.server.httpserver`

This class is used to manage an http server. This class contains few lines of code, this is to save memory. The core of the server is in the other class HttpServerCore, which is loaded into memory only when connecting an HTTP client. It takes a little while the first time you connect, but limits memory consumption if not in use. If you have enough memory (SPIRAM or other), just start the server with the preload option at True.

Expand source code

    # Distributed under Pycameresp License
    # Copyright (c) 2023 Remi BERTHOLET
    # historically based on :
    # https://github.com/jczic/MicroWebSrv/blob/master/microWebSocket.py
    # but I have modified a lot, there must still be some original functions.
    # pylint:disable=consider-using-f-string
    """ This class is used to manage an http server.
    This class contains few lines of code, this is to save memory.
    The core of the server is in the other class HttpServerCore, which is loaded into memory only when connecting an HTTP client.
    It takes a little while the first time you connect, but limits memory consumption if not in use.
    If you have enough memory (SPIRAM or other), just start the server with the preload option at True. """
    import re
    import server.server
    import tools.logger
    import tools.filesystem
    import tools.strings
    import tools.tasking

    class HttpServerInstance(tools.tasking.ServerInstance):
            """ Instance of server Http """
            def __init__(self, **kwargs):
                    """ Constructor """
                    tools.tasking.ServerInstance.__init__(self, **kwargs)
                    self.server = None
                    self.kwargs = kwargs

            def preload(self):
                    """ Method used to preload page template.
                    You must define the content of a callback, which only import the python module with your pages """
                    started = False
                    if HttpServer.preload():
                            started = True

                    if self.server is None:
                            tools.logger.syslog("Http '%s' started"%self.kwargs.get("name",""))
                            from server.httpservercore import HttpServerCore
                            self.server = HttpServerCore(**self.kwargs)
                            started = True

                    if started:
                            tools.logger.syslog("Http '%s' ready on %d"%(self.kwargs.get("name",""), self.kwargs.get("port",0)))

            async def on_connection(self, reader, writer):
                    """ Http server connection detected """
                    try:
                            # Preload the server
                            self.preload()

                            # Call on connection method
                            await self.server.on_connection(reader, writer)
                    except Exception as err:
                            tools.logger.syslog(err)

    class HttpServer:
            """ Http main class """
            routes       = {}
            wildroutes   = []
            menus        = []
            www_dir      = None
            pages        = []
            loaded       = [False]
            config       = None
            login_checker= None

            @staticmethod
            def call_preload(loader):
                    """ Call preload html page callback """
                    if loader is not None:
                            if tools.filesystem.ismicropython():
                                    ModuleNotFound = ImportError
                            else:
                                    ModuleNotFound = ModuleNotFoundError
                            try:
                                    loader()
                            except ModuleNotFound as err:
                                    tools.logger.syslog("Preload html page : %s"%str(err))
                            except Exception as err:
                                    tools.logger.syslog(err)

            @staticmethod
            def preload():
                    """ Method used to preload page template.
                    You must define the content of a callback, which only import the python module with your pages """
                    result = False
                    if HttpServer.loaded[0] is False:
                            from htmltemplate import WWW_DIR
                            tools.logger.syslog("Html load pages")
                            if len(HttpServer.pages) > 0:
                                    for loader in HttpServer.pages:
                                            HttpServer.call_preload(loader)
                            HttpServer.www_dir = WWW_DIR
                            result = True
                            HttpServer.loaded[0] = True
                    return result

            @staticmethod
            def add_pages():
                    """ Add an html page loader which will be called when connecting to the http server """
                    def add_pages(function):
                            HttpServer.pages.append(function)
                            return function
                    return add_pages

            @staticmethod
            def add_route(url, **kwargs):
                    """ Add a route to select an html page.
                    For the server to know the pages, it must imperatively use this decorator """
                    def add_route(function):
                            if tools.strings.tobytes(url[-1]) == ord(b"*"):
                                    HttpServer.wildroutes.append([tools.strings.tobytes(url),(function, kwargs)])
                            else:
                                    kwargs["index"] = len(HttpServer.menus)
                                    HttpServer.routes[tools.strings.tobytes(url)] = (function, kwargs)
                            if kwargs.get("available", True):
                                    if "item" in kwargs and "menu" in kwargs:
                                            HttpServer.menus.append([kwargs["menu"], kwargs["item"],len(HttpServer.menus), tools.strings.tobytes(url)])
                                            HttpServer.menus.sort()
                            return function
                    return add_route

            @staticmethod
            def remove_route(url=None):
                    """ Remove a route of html page """
                    if url is None:
                            HttpServer.routes = {}
                            HttpServer.menus = []
                    else:
                            route = HttpServer.routes.get(url, None)
                            if route:
                                    del HttpServer.routes[url]
                                    title = route[1].get("title", None)
                                    if title:
                                            i = 0
                                            for item in HttpServer.menus:
                                                    index, route, titl = item
                                                    if titl == title:
                                                            del HttpServer.menus[i]
                                                            break
                                                    i += 1

            @staticmethod
            def get_menus():
                    """ Used to get the informations of menu """
                    return HttpServer.menus

            @staticmethod
            def set_login_checker():
                    """ Change the default login checker """
                    def setter(checker):
                            HttpServer.login_checker = checker
                    return setter

            @staticmethod
            async def is_logged(request, response):
                    """ Check the login """
                    if HttpServer.login_checker is not None:
                            return await HttpServer.login_checker(request, response)
                    return True

            @staticmethod
            def search_route(request):
                    """ Search route according to the request """
                    function, args = None, None

                    if request.method == b"PUT":
                            directory, file = tools.filesystem.split(tools.strings.tostrings(request.path))
                            found = HttpServer.routes.get(tools.strings.tobytes(directory),None)
                            if found:
                                    function, args = found
                            return function, args
                    else:
                            found = HttpServer.routes.get(request.path,None)
                            if found is None:
                                    for route, func in HttpServer.wildroutes:
                                            if re.match(tools.strings.tostrings(route), tools.strings.tostrings(request.path)):
                                                    found = func
                                                    break
                                    if found is None:
                                            staticRe = re.compile("^/("+tools.strings.tostrings(HttpServer.www_dir)+"/.+|.+)")
                                            if staticRe.match(tools.strings.tostrings(request.path)):
                                                    function, args = HttpServer.static_pages, {}
                                    else:
                                            function, args = found
                            else:
                                    function, args = found
                    return function, args

            @staticmethod
            async def static_pages(request, response, args):
                    """ Treat the case of static pages """
                    path = tools.strings.tobytes(HttpServer.www_dir) + request.path
                    path = path.replace(b"//",b"/")
                    if b".." in path:
                            await response.send_error(status=b"403",content=b"Forbidden")
                    else:
                            await response.send_file(path, headers=request.headers)

            @staticmethod
            def init():
                    """ Initialize http server """
                    if HttpServer.config is None:
                            HttpServer.config = server.server.ServerConfig()
                            HttpServer.config.load_create()
                    else:
                            HttpServer.config.refresh()

            @staticmethod
            def start(**kwargs):
                    """ Start http server instance on selected port """
                    HttpServer.init()
                    # If http activated
                    if HttpServer.config.http:
                            kwargs["port"] = kwargs.get("http_port",80)
                            kwargs["name"] = kwargs.get("name","Http")
                            kwargs["backlog"] = kwargs.get("backlog",5)
                            tools.tasking.Tasks.create_server(HttpServerInstance(**kwargs))
                    else:
                            tools.logger.syslog("Http server disabled in config")

## Classes

`class HttpServer`

  Http main class

  Expand source code

      class HttpServer:
              """ Http main class """
              routes       = {}
              wildroutes   = []
              menus        = []
              www_dir      = None
              pages        = []
              loaded       = [False]
              config       = None
              login_checker= None

              @staticmethod
              def call_preload(loader):
                      """ Call preload html page callback """
                      if loader is not None:
                              if tools.filesystem.ismicropython():
                                      ModuleNotFound = ImportError
                              else:
                                      ModuleNotFound = ModuleNotFoundError
                              try:
                                      loader()
                              except ModuleNotFound as err:
                                      tools.logger.syslog("Preload html page : %s"%str(err))
                              except Exception as err:
                                      tools.logger.syslog(err)

              @staticmethod
              def preload():
                      """ Method used to preload page template.
                      You must define the content of a callback, which only import the python module with your pages """
                      result = False
                      if HttpServer.loaded[0] is False:
                              from htmltemplate import WWW_DIR
                              tools.logger.syslog("Html load pages")
                              if len(HttpServer.pages) > 0:
                                      for loader in HttpServer.pages:
                                              HttpServer.call_preload(loader)
                              HttpServer.www_dir = WWW_DIR
                              result = True
                              HttpServer.loaded[0] = True
                      return result

              @staticmethod
              def add_pages():
                      """ Add an html page loader which will be called when connecting to the http server """
                      def add_pages(function):
                              HttpServer.pages.append(function)
                              return function
                      return add_pages

              @staticmethod
              def add_route(url, **kwargs):
                      """ Add a route to select an html page.
                      For the server to know the pages, it must imperatively use this decorator """
                      def add_route(function):
                              if tools.strings.tobytes(url[-1]) == ord(b"*"):
                                      HttpServer.wildroutes.append([tools.strings.tobytes(url),(function, kwargs)])
                              else:
                                      kwargs["index"] = len(HttpServer.menus)
                                      HttpServer.routes[tools.strings.tobytes(url)] = (function, kwargs)
                              if kwargs.get("available", True):
                                      if "item" in kwargs and "menu" in kwargs:
                                              HttpServer.menus.append([kwargs["menu"], kwargs["item"],len(HttpServer.menus), tools.strings.tobytes(url)])
                                              HttpServer.menus.sort()
                              return function
                      return add_route

              @staticmethod
              def remove_route(url=None):
                      """ Remove a route of html page """
                      if url is None:
                              HttpServer.routes = {}
                              HttpServer.menus = []
                      else:
                              route = HttpServer.routes.get(url, None)
                              if route:
                                      del HttpServer.routes[url]
                                      title = route[1].get("title", None)
                                      if title:
                                              i = 0
                                              for item in HttpServer.menus:
                                                      index, route, titl = item
                                                      if titl == title:
                                                              del HttpServer.menus[i]
                                                              break
                                                      i += 1

              @staticmethod
              def get_menus():
                      """ Used to get the informations of menu """
                      return HttpServer.menus

              @staticmethod
              def set_login_checker():
                      """ Change the default login checker """
                      def setter(checker):
                              HttpServer.login_checker = checker
                      return setter

              @staticmethod
              async def is_logged(request, response):
                      """ Check the login """
                      if HttpServer.login_checker is not None:
                              return await HttpServer.login_checker(request, response)
                      return True

              @staticmethod
              def search_route(request):
                      """ Search route according to the request """
                      function, args = None, None

                      if request.method == b"PUT":
                              directory, file = tools.filesystem.split(tools.strings.tostrings(request.path))
                              found = HttpServer.routes.get(tools.strings.tobytes(directory),None)
                              if found:
                                      function, args = found
                              return function, args
                      else:
                              found = HttpServer.routes.get(request.path,None)
                              if found is None:
                                      for route, func in HttpServer.wildroutes:
                                              if re.match(tools.strings.tostrings(route), tools.strings.tostrings(request.path)):
                                                      found = func
                                                      break
                                      if found is None:
                                              staticRe = re.compile("^/("+tools.strings.tostrings(HttpServer.www_dir)+"/.+|.+)")
                                              if staticRe.match(tools.strings.tostrings(request.path)):
                                                      function, args = HttpServer.static_pages, {}
                                      else:
                                              function, args = found
                              else:
                                      function, args = found
                      return function, args

              @staticmethod
              async def static_pages(request, response, args):
                      """ Treat the case of static pages """
                      path = tools.strings.tobytes(HttpServer.www_dir) + request.path
                      path = path.replace(b"//",b"/")
                      if b".." in path:
                              await response.send_error(status=b"403",content=b"Forbidden")
                      else:
                              await response.send_file(path, headers=request.headers)

              @staticmethod
              def init():
                      """ Initialize http server """
                      if HttpServer.config is None:
                              HttpServer.config = server.server.ServerConfig()
                              HttpServer.config.load_create()
                      else:
                              HttpServer.config.refresh()

              @staticmethod
              def start(**kwargs):
                      """ Start http server instance on selected port """
                      HttpServer.init()
                      # If http activated
                      if HttpServer.config.http:
                              kwargs["port"] = kwargs.get("http_port",80)
                              kwargs["name"] = kwargs.get("name","Http")
                              kwargs["backlog"] = kwargs.get("backlog",5)
                              tools.tasking.Tasks.create_server(HttpServerInstance(**kwargs))
                      else:
                              tools.logger.syslog("Http server disabled in config")

  ### Class variables

  `var config`
  `var loaded`
  `var login_checker`
  `var menus`
  `var pages`
  `var routes`
  `var wildroutes`
  `var www_dir`

  ### Static methods

  `def add_pages()`

    Add an html page loader which will be called when connecting to the http server

    Expand source code

        @staticmethod
        def add_pages():
                """ Add an html page loader which will be called when connecting to the http server """
                def add_pages(function):
                        HttpServer.pages.append(function)
                        return function
                return add_pages

  `def add_route(url, **kwargs)`

    Add a route to select an html page. For the server to know the pages, it must imperatively use this decorator

    Expand source code

        @staticmethod
        def add_route(url, **kwargs):
                """ Add a route to select an html page.
                For the server to know the pages, it must imperatively use this decorator """
                def add_route(function):
                        if tools.strings.tobytes(url[-1]) == ord(b"*"):
                                HttpServer.wildroutes.append([tools.strings.tobytes(url),(function, kwargs)])
                        else:
                                kwargs["index"] = len(HttpServer.menus)
                                HttpServer.routes[tools.strings.tobytes(url)] = (function, kwargs)
                        if kwargs.get("available", True):
                                if "item" in kwargs and "menu" in kwargs:
                                        HttpServer.menus.append([kwargs["menu"], kwargs["item"],len(HttpServer.menus), tools.strings.tobytes(url)])
                                        HttpServer.menus.sort()
                        return function
                return add_route

  `def call_preload(loader)`

    Call preload html page callback

    Expand source code

        @staticmethod
        def call_preload(loader):
                """ Call preload html page callback """
                if loader is not None:
                        if tools.filesystem.ismicropython():
                                ModuleNotFound = ImportError
                        else:
                                ModuleNotFound = ModuleNotFoundError
                        try:
                                loader()
                        except ModuleNotFound as err:
                                tools.logger.syslog("Preload html page : %s"%str(err))
                        except Exception as err:
                                tools.logger.syslog(err)

  `def get_menus()`

    Used to get the informations of menu

    Expand source code

        @staticmethod
        def get_menus():
                """ Used to get the informations of menu """
                return HttpServer.menus

  `def init()`

    Initialize http server

    Expand source code

        @staticmethod
        def init():
                """ Initialize http server """
                if HttpServer.config is None:
                        HttpServer.config = server.server.ServerConfig()
                        HttpServer.config.load_create()
                else:
                        HttpServer.config.refresh()

  `async def is_logged(request, response)`

    Check the login

    Expand source code

        @staticmethod
        async def is_logged(request, response):
                """ Check the login """
                if HttpServer.login_checker is not None:
                        return await HttpServer.login_checker(request, response)
                return True

  `def preload()`

    Method used to preload page template. You must define the content of a callback, which only import the python module with your pages

    Expand source code

        @staticmethod
        def preload():
                """ Method used to preload page template.
                You must define the content of a callback, which only import the python module with your pages """
                result = False
                if HttpServer.loaded[0] is False:
                        from htmltemplate import WWW_DIR
                        tools.logger.syslog("Html load pages")
                        if len(HttpServer.pages) > 0:
                                for loader in HttpServer.pages:
                                        HttpServer.call_preload(loader)
                        HttpServer.www_dir = WWW_DIR
                        result = True
                        HttpServer.loaded[0] = True
                return result

  `def remove_route(url=None)`

    Remove a route of html page

    Expand source code

        @staticmethod
        def remove_route(url=None):
                """ Remove a route of html page """
                if url is None:
                        HttpServer.routes = {}
                        HttpServer.menus = []
                else:
                        route = HttpServer.routes.get(url, None)
                        if route:
                                del HttpServer.routes[url]
                                title = route[1].get("title", None)
                                if title:
                                        i = 0
                                        for item in HttpServer.menus:
                                                index, route, titl = item
                                                if titl == title:
                                                        del HttpServer.menus[i]
                                                        break
                                                i += 1

  `def search_route(request)`

    Search route according to the request

    Expand source code

        @staticmethod
        def search_route(request):
                """ Search route according to the request """
                function, args = None, None

                if request.method == b"PUT":
                        directory, file = tools.filesystem.split(tools.strings.tostrings(request.path))
                        found = HttpServer.routes.get(tools.strings.tobytes(directory),None)
                        if found:
                                function, args = found
                        return function, args
                else:
                        found = HttpServer.routes.get(request.path,None)
                        if found is None:
                                for route, func in HttpServer.wildroutes:
                                        if re.match(tools.strings.tostrings(route), tools.strings.tostrings(request.path)):
                                                found = func
                                                break
                                if found is None:
                                        staticRe = re.compile("^/("+tools.strings.tostrings(HttpServer.www_dir)+"/.+|.+)")
                                        if staticRe.match(tools.strings.tostrings(request.path)):
                                                function, args = HttpServer.static_pages, {}
                                else:
                                        function, args = found
                        else:
                                function, args = found
                return function, args

  `def set_login_checker()`

    Change the default login checker

    Expand source code

        @staticmethod
        def set_login_checker():
                """ Change the default login checker """
                def setter(checker):
                        HttpServer.login_checker = checker
                return setter

  `def start(**kwargs)`

    Start http server instance on selected port

    Expand source code

        @staticmethod
        def start(**kwargs):
                """ Start http server instance on selected port """
                HttpServer.init()
                # If http activated
                if HttpServer.config.http:
                        kwargs["port"] = kwargs.get("http_port",80)
                        kwargs["name"] = kwargs.get("name","Http")
                        kwargs["backlog"] = kwargs.get("backlog",5)
                        tools.tasking.Tasks.create_server(HttpServerInstance(**kwargs))
                else:
                        tools.logger.syslog("Http server disabled in config")

  `async def static_pages(request, response, args)`

    Treat the case of static pages

    Expand source code

        @staticmethod
        async def static_pages(request, response, args):
                """ Treat the case of static pages """
                path = tools.strings.tobytes(HttpServer.www_dir) + request.path
                path = path.replace(b"//",b"/")
                if b".." in path:
                        await response.send_error(status=b"403",content=b"Forbidden")
                else:
                        await response.send_file(path, headers=request.headers)

`class HttpServerInstance (**kwargs)`

  Instance of server Http

  Constructor

  Expand source code

      class HttpServerInstance(tools.tasking.ServerInstance):
              """ Instance of server Http """
              def __init__(self, **kwargs):
                      """ Constructor """
                      tools.tasking.ServerInstance.__init__(self, **kwargs)
                      self.server = None
                      self.kwargs = kwargs

              def preload(self):
                      """ Method used to preload page template.
                      You must define the content of a callback, which only import the python module with your pages """
                      started = False
                      if HttpServer.preload():
                              started = True

                      if self.server is None:
                              tools.logger.syslog("Http '%s' started"%self.kwargs.get("name",""))
                              from server.httpservercore import HttpServerCore
                              self.server = HttpServerCore(**self.kwargs)
                              started = True

                      if started:
                              tools.logger.syslog("Http '%s' ready on %d"%(self.kwargs.get("name",""), self.kwargs.get("port",0)))

              async def on_connection(self, reader, writer):
                      """ Http server connection detected """
                      try:
                              # Preload the server
                              self.preload()

                              # Call on connection method
                              await self.server.on_connection(reader, writer)
                      except Exception as err:
                              tools.logger.syslog(err)

  ### Ancestors

  - tools.tasking.ServerInstance

  ### Methods

  `async def on_connection(self, reader, writer)`

    Http server connection detected

    Expand source code

        async def on_connection(self, reader, writer):
                """ Http server connection detected """
                try:
                        # Preload the server
                        self.preload()

                        # Call on connection method
                        await self.server.on_connection(reader, writer)
                except Exception as err:
                        tools.logger.syslog(err)

  `def preload(self)`

    Method used to preload page template. You must define the content of a callback, which only import the python module with your pages

    Expand source code

        def preload(self):
                """ Method used to preload page template.
                You must define the content of a callback, which only import the python module with your pages """
                started = False
                if HttpServer.preload():
                        started = True

                if self.server is None:
                        tools.logger.syslog("Http '%s' started"%self.kwargs.get("name",""))
                        from server.httpservercore import HttpServerCore
                        self.server = HttpServerCore(**self.kwargs)
                        started = True

                if started:
                        tools.logger.syslog("Http '%s' ready on %d"%(self.kwargs.get("name",""), self.kwargs.get("port",0)))