Пример #1
0
 def japronto_add_routes(routes):
     router = ProntoRouter()
     for url, controller, methods, method_dict in routes:
         try:
             if len(methods) > 1:
                 router.add_route(url, dummy, methods=methods)
             else:
                 router.add_route(url, dummy, method=methods[0])
         except ValueError:
             # Japronto don't accept several unnamed {} patterns
             pass
     return router.get_matcher()
Пример #2
0
    def router(self):
        if not self._router:
            self._router = Router()

        return self._router
Пример #3
0
class Application:
    def __init__(self,
                 *,
                 reaper_settings=None,
                 log_request=None,
                 protocol_factory=None,
                 debug=False):
        self._router = None
        self._loop = None
        self._connections = set()
        self._reaper_settings = reaper_settings or {}
        self._error_handlers = []
        self._log_request = log_request
        self._request_extensions = {}
        self._protocol_factory = protocol_factory or Protocol
        self._debug = debug

    @property
    def loop(self):
        if not self._loop:
            self._loop = uvloop.new_event_loop()

        return self._loop

    @property
    def router(self):
        if not self._router:
            self._router = Router()

        return self._router

    def __finalize(self):
        self.loop
        self.router

        self._reaper = Reaper(self, **self._reaper_settings)
        self._matcher = self._router.get_matcher()

    def protocol_error_handler(self, error):
        print(error)

        error = error.encode('utf-8')

        response = [
            'HTTP/1.0 400 Bad Request\r\n',
            'Content-Type: text/plain; charset=utf-8\r\n',
            'Content-Length: {}\r\n\r\n'.format(len(error))
        ]

        return ''.join(response).encode('utf-8') + error

    def default_request_logger(self, request):
        print(request.remote_addr, request.method, request.path)

    def add_error_handler(self, typ, handler):
        self._error_handlers.append((typ, handler))

    def default_error_handler(self, request, exception):
        if isinstance(exception, RouteNotFoundException):
            return request.Response(code=404, text='Not Found')
        if isinstance(exception, asyncio.CancelledError):
            return request.Response(code=503, text='Service unavailable')

        # FIXME traceback should be only available in debug mode
        tb = traceback.format_exception(None, exception,
                                        exception.__traceback__)
        tb = ''.join(tb)
        print(tb, file=sys.stderr, end='')
        return request.Response(
            code=500, text=tb if self._debug else 'Internval Server Error')

    def error_handler(self, request, exception):
        for typ, handler in self._error_handlers:
            if typ is not None and not isinstance(exception, typ):
                continue

            try:
                return handler(request, exception)
            except:
                print('-- Exception in error_handler occured:')
                traceback.print_exc()

            print('-- while handling:')
            traceback.print_exception(None, exception, exception.__traceback__)
            return request.Response(code=500, text='Internal Server Error')

        return self.default_error_handler(request, exception)

    def _get_idle_and_busy_connections(self):
        # FIXME if there is buffered data in gather the connections should be
        # considered busy, now it's idle
        return \
            [c for c in self._connections if c.pipeline_empty], \
            [c for c in self._connections if not c.pipeline_empty]

    async def drain(self):
        # TODO idle connections will close connection with half-read requests
        idle, busy = self._get_idle_and_busy_connections()
        for c in idle:
            c.transport.close()
#       for c in busy_connections:
#            need to implement something that makes protocol.on_data
#            start rejecting incoming data
#            this closes transposrt unfortunately
#            sock = c.transport.get_extra_info('socket')
#            sock.shutdown(socket.SHUT_RD)

        if idle or busy:
            print('Draining connections...')
        else:
            return

        if idle:
            print('{} idle connections closed immediately'.format(len(idle)))
        if busy:
            print('{} connections busy, read-end closed'.format(len(busy)))

        for x in range(5, 0, -1):
            await asyncio.sleep(1)
            idle, busy = self._get_idle_and_busy_connections()
            for c in idle:
                c.transport.close()
            if not busy:
                break
            else:
                print("{} seconds remaining, {} connections still busy".format(
                    x, len(busy)))

        _, busy = self._get_idle_and_busy_connections()
        if busy:
            print('Forcefully killing {} connections'.format(len(busy)))
        for c in busy:
            c.pipeline_cancel()

    def extend_request(self, handler, *, name=None, property=False):
        if not name:
            name = handler.__name__

        self._request_extensions[name] = (handler, property)

    def serve(self, *, sock, host, port, reloader_pid):
        self.__finalize()

        loop = self.loop
        asyncio.set_event_loop(loop)

        server_coro = loop.create_server(lambda: self._protocol_factory(self),
                                         sock=sock)

        server = loop.run_until_complete(server_coro)

        loop.add_signal_handler(signal.SIGTERM, loop.stop)
        loop.add_signal_handler(signal.SIGINT, loop.stop)

        if reloader_pid:
            from japronto.reloader import ChangeDetector
            detector = ChangeDetector(loop)
            detector.start()

        print('Accepting connections on http://{}:{}'.format(host, port))

        try:
            loop.run_forever()
        finally:
            server.close()
            loop.run_until_complete(server.wait_closed())
            loop.run_until_complete(self.drain())
            self._reaper.stop()
            loop.close()

            # break reference and cleanup matcher buffer
            del self._matcher

    def _run(self,
             *,
             host,
             port,
             worker_num=None,
             reloader_pid=None,
             debug=None):
        self._debug = debug or self._debug
        if self._debug and not self._log_request:
            self._log_request = self._debug

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((host, port))
        os.set_inheritable(sock.fileno(), True)

        workers = set()

        terminating = False

        def stop(sig, frame):
            nonlocal terminating
            if reloader_pid and sig == signal.SIGHUP:
                print('Reload request received')
            elif not terminating:
                terminating = True
                print('Termination request received')
            for worker in workers:
                worker.terminate()

        signal.signal(signal.SIGINT, stop)
        signal.signal(signal.SIGTERM, stop)
        signal.signal(signal.SIGHUP, stop)

        for _ in range(worker_num or 1):
            worker = multiprocessing.Process(target=self.serve,
                                             kwargs=dict(
                                                 sock=sock,
                                                 host=host,
                                                 port=port,
                                                 reloader_pid=reloader_pid))
            worker.daemon = True
            worker.start()
            workers.add(worker)

        # prevent further operations on socket in parent
        sock.close()

        for worker in workers:
            worker.join()

            if worker.exitcode != 0:
                print('Worker exited with code {}!'.format(worker.exitcode))

    def run(self,
            host='0.0.0.0',
            port=8080,
            *,
            worker_num=None,
            reload=False,
            debug=False):
        if os.environ.get('_JAPR_IGNORE_RUN'):
            return

        reloader_pid = None
        if reload:
            if '_JAPR_RELOADER' not in os.environ:
                from japronto.reloader import exec_reloader
                exec_reloader(host=host, port=port, worker_num=worker_num)
            else:
                reloader_pid = int(os.environ['_JAPR_RELOADER'])

        self._run(host=host,
                  port=port,
                  worker_num=worker_num,
                  reloader_pid=reloader_pid,
                  debug=debug)
Пример #4
0
    def router(self):
        if not self._router:
            self._router = Router()

        return self._router
Пример #5
0
class Application:
    def __init__(self, *, reaper_settings=None, log_request=None,
                 protocol_factory=None, debug=False):
        self._router = None
        self._loop = None
        self._connections = set()
        self._reaper_settings = reaper_settings or {}
        self._error_handlers = []
        self._log_request = log_request
        self._request_extensions = {}
        self._protocol_factory = protocol_factory or Protocol
        self._debug = debug

    @property
    def loop(self):
        if not self._loop:
            self._loop = uvloop.new_event_loop()

        return self._loop

    @property
    def router(self):
        if not self._router:
            self._router = Router()

        return self._router

    def __finalize(self):
        self.loop
        self.router

        self._reaper = Reaper(self, **self._reaper_settings)
        self._matcher = self._router.get_matcher()

    def protocol_error_handler(self, error):
        print(error)

        error = error.encode('utf-8')

        response = [
            'HTTP/1.0 400 Bad Request\r\n',
            'Content-Type: text/plain; charset=utf-8\r\n',
            'Content-Length: {}\r\n\r\n'.format(len(error))]

        return ''.join(response).encode('utf-8') + error

    def default_request_logger(self, request):
        print(request.remote_addr, request.method, request.path)

    def add_error_handler(self, typ, handler):
        self._error_handlers.append((typ, handler))

    def default_error_handler(self, request, exception):
        if isinstance(exception, RouteNotFoundException):
            return request.Response(code=404, text='Not Found')
        if isinstance(exception, asyncio.CancelledError):
            return request.Response(code=503, text='Service unavailable')

        tb = traceback.format_exception(
            None, exception, exception.__traceback__)
        tb = ''.join(tb)
        print(tb, file=sys.stderr, end='')
        return request.Response(
            code=500,
            text=tb if self._debug else 'Internal Server Error')

    def error_handler(self, request, exception):
        for typ, handler in self._error_handlers:
            if typ is not None and not isinstance(exception, typ):
                continue

            try:
                return handler(request, exception)
            except:
                print('-- Exception in error_handler occured:')
                traceback.print_exc()

            print('-- while handling:')
            traceback.print_exception(None, exception, exception.__traceback__)
            return request.Response(
                code=500, text='Internal Server Error')

        return self.default_error_handler(request, exception)

    def _get_idle_and_busy_connections(self):
        # FIXME if there is buffered data in gather the connections should be
        # considered busy, now it's idle
        return \
            [c for c in self._connections if c.pipeline_empty], \
            [c for c in self._connections if not c.pipeline_empty]

    async def drain(self):
        # TODO idle connections will close connection with half-read requests
        idle, busy = self._get_idle_and_busy_connections()
        for c in idle:
            c.transport.close()
#       for c in busy_connections:
#            need to implement something that makes protocol.on_data
#            start rejecting incoming data
#            this closes transport unfortunately
#            sock = c.transport.get_extra_info('socket')
#            sock.shutdown(socket.SHUT_RD)

        if idle or busy:
            print('Draining connections...')
        else:
            return

        if idle:
            print('{} idle connections closed immediately'.format(len(idle)))
        if busy:
            print('{} connections busy, read-end closed'.format(len(busy)))

        for x in range(5, 0, -1):
            await asyncio.sleep(1)
            idle, busy = self._get_idle_and_busy_connections()
            for c in idle:
                c.transport.close()
            if not busy:
                break
            else:
                print(
                    "{} seconds remaining, {} connections still busy"
                    .format(x, len(busy)))

        _, busy = self._get_idle_and_busy_connections()
        if busy:
            print('Forcefully killing {} connections'.format(len(busy)))
        for c in busy:
            c.pipeline_cancel()

    def extend_request(self, handler, *, name=None, property=False):
        if not name:
            name = handler.__name__

        self._request_extensions[name] = (handler, property)

    def serve(self, *, sock, host, port, reloader_pid):
        faulthandler.enable()
        self.__finalize()

        loop = self.loop
        asyncio.set_event_loop(loop)

        server_coro = loop.create_server(
            lambda: self._protocol_factory(self), sock=sock)

        server = loop.run_until_complete(server_coro)

        loop.add_signal_handler(signal.SIGTERM, loop.stop)
        loop.add_signal_handler(signal.SIGINT, loop.stop)

        if reloader_pid:
            from japronto.reloader import ChangeDetector
            detector = ChangeDetector(loop)
            detector.start()

        print('Accepting connections on http://{}:{}'.format(host, port))

        try:
            loop.run_forever()
        finally:
            server.close()
            loop.run_until_complete(server.wait_closed())
            loop.run_until_complete(self.drain())
            self._reaper.stop()
            loop.close()

            # break reference and cleanup matcher buffer
            del self._matcher

    def _run(self, *, host, port, worker_num=None, reloader_pid=None,
             debug=None):
        self._debug = debug or self._debug
        if self._debug and not self._log_request:
            self._log_request = self._debug

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((host, port))
        os.set_inheritable(sock.fileno(), True)

        workers = set()

        terminating = False

        def stop(sig, frame):
            nonlocal terminating
            if reloader_pid and sig == signal.SIGHUP:
                print('Reload request received')
            elif not terminating:
                terminating = True
                print('Termination request received')
            for worker in workers:
                worker.terminate()

        signal.signal(signal.SIGINT, stop)
        signal.signal(signal.SIGTERM, stop)
        signal.signal(signal.SIGHUP, stop)

        for _ in range(worker_num or 1):
            worker = multiprocessing.Process(
                target=self.serve,
                kwargs=dict(sock=sock, host=host, port=port,
                            reloader_pid=reloader_pid))
            worker.daemon = True
            worker.start()
            workers.add(worker)

        # prevent further operations on socket in parent
        sock.close()

        for worker in workers:
            worker.join()

            if worker.exitcode > 0:
                print('Worker exited with code {}'.format(worker.exitcode))
            elif worker.exitcode < 0:
                try:
                    signame = signames[-worker.exitcode]
                except KeyError:
                    print(
                        'Worker crashed with unknown code {}!'
                        .format(worker.exitcode))
                else:
                    print('Worker crashed on signal {}!'.format(signame))

    def run(self, host='0.0.0.0', port=8080, *, worker_num=None, reload=False,
            debug=False):
        if os.environ.get('_JAPR_IGNORE_RUN'):
            return

        reloader_pid = None
        if reload:
            if '_JAPR_RELOADER' not in os.environ:
                from japronto.reloader import exec_reloader
                exec_reloader(host=host, port=port, worker_num=worker_num)
            else:
                reloader_pid = int(os.environ['_JAPR_RELOADER'])

        self._run(
            host=host, port=port, worker_num=worker_num,
            reloader_pid=reloader_pid, debug=debug)