예제 #1
0
    def work(self):
        error_log.info('Entering endless loop of processing sockets.')

        while True:
            sock, address = (None, None)

            # noinspection PyBroadException
            try:
                if not self.connections:
                    self.recv_new_sockets()

                sock, address = self.connections.popleft()
                self.handle_connection(socket=sock, address=address)
            except SignalReceivedException as err:
                if err.signum == ws.signals.SIGTERM:
                    error_log.info('Breaking work() loop due to signal %s.',
                                   ws.signals.Signals(err.signum).name)
                    break
                else:
                    error_log.exception('Unknown signal during work() loop')
            except KeyboardInterrupt:
                break
            except Exception:
                error_log.exception('Exception occurred during work() loop.')
                continue
            finally:
                if sock:
                    sock.shutdown(ws.sockets.SHUT_RDWR, pass_silently=True)
                    sock.close(pass_silently=True)

        # noinspection PyUnreachableCode
        self.cleanup()

        return 0
예제 #2
0
    def cleanup(self):
        """ Closing listening socket and reap children.

        This method sleeps for the maximum timeout of SIGTERM signal sent to
        a worker. (Worker.sigterm_timeout)
        """
        # don't cleanup workers because their sockets were already closed
        # during the self.fork_worker() call
        if self.execution_context == self.ExecutionContext.worker:
            return

        error_log.info("Closing server's listening socket")
        try:
            self.sock.close()
        except OSError:
            error_log.exception('close() on listening socket failed.')

        ws.signals.signal(ws.signals.SIGCHLD, ws.signals.SIG_DFL)
        active_workers = [
            worker for worker in self.workers.values()
            if worker.pid not in self.reaped_pids
        ]
        for worker in active_workers:
            worker.terminate()

        if active_workers:
            timeout = max(worker.sigterm_timeout for worker in active_workers)
            error_log.info('Waiting %s seconds for children to finish.',
                           timeout)
            time.sleep(timeout)

        for worker in active_workers:
            pid, exit = os.waitpid(worker.pid, os.WNOHANG)
            if not pid:
                worker.kill_if_hanged()
예제 #3
0
    def close(self, pass_silently=False):
        error_log.debug2('Closing socket %d', self.fileno())

        if pass_silently:
            try:
                super().close()
            except OSError:
                error_log.exception('Closing socket %s failed.', self.fileno())
        else:
            super().close()
예제 #4
0
    def shutdown(self, how, pass_silently=False):
        error_log.debug2(SHUTDOWN_MSGS[how], super().fileno())

        if pass_silently:
            try:
                super().shutdown(how)
            except OSError:
                error_log.exception('Shutting down socket %s failed.',
                                    self.fileno())
        else:
            super().shutdown(how)
예제 #5
0
    def cleanup(self):
        error_log.info('Cleaning up... %s total leftover connections.',
                       len(self.connections))
        self.fd_transport.discard()

        for sock, address in self.connections:
            # noinspection PyBroadException
            try:
                res = hutils.build_response(503)
                self.handle_connection(socket=sock,
                                       address=address,
                                       quick_reply_with=res)
            except Exception:
                error_log.exception(
                    'Error while cleaning up client on '
                    '%s / %s', sock, address)
            finally:
                sock.close(pass_silently=True)
예제 #6
0
    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_val:
            return False

        if self.exchange['written']:
            error_log.warning(
                'An exception occurred after worker had sent bytes over '
                'the socket(fileno=%s). Client will receive an invalid '
                'HTTP response.',
                self.sock.fileno()
            )
            return False

        if exc_handler.can_handle(exc_val):
            response, suppress = exc_handler.handle(exc_val)
            if not response:
                # no need to send back a response
                return suppress
        else:
            error_log.exception('Could not handle exception. Client will '
                                'receive a 500 Internal Server Error.')
            response, suppress = ws.http.utils.build_response(500), False

        response.headers['Connection'] = 'close'

        try:
            self.respond(response)
        except OSError as e:
            error_log.warning('During cleanup of worker tried to respond to '
                              'client and close the connection but: '
                              'caught OSError with ERRNO=%s and MSG=%s',
                              e.errno, e.strerror)

            if e.errno == errno.ECONNRESET:
                error_log.warning('Client stopped listening prematurely.'
                                  ' (no Connection: close header was received)')
                return suppress
            else:
                raise

        return suppress
예제 #7
0
def main():
    # Main process should never exit from the server.listen() loop unless
    # an exception occurs.
    fd_limit = subprocess.check_output(['ulimit', '-n'], shell=True)
    error_log.info('ulimit -n is "%s"', fd_limit.decode('ascii'))
    server = Server()
    server.setup()
    with profile(SERVER_PROFILING_ON):
        # noinspection PyBroadException
        try:
            server.listen()
        except SignalReceivedException as err:
            if err.signum == ws.signals.SIGTERM:
                error_log.info('SIGTERM signal broke listen() loop.')
            else:
                error_log.exception('Unknown signal broke listen() loop.')
        except KeyboardInterrupt:
            error_log.info('KeyboardInterrupt broke listen() loop.')
        except BaseException:
            error_log.exception('Unhandled exception broke listen() loop.')
        finally:
            server.cleanup()
예제 #8
0
    def fork_workers(self, count=1):
        error_log.info('Forking %s workers', self.process_count_limit)
        assert isinstance(count, int)

        for _ in range(count):
            fd_transport = ws.sockets.FDTransport()
            pid = os.fork()
            if pid:
                self.execution_context = self.ExecutionContext.main
                error_log.debug('Forked worker with pid=%s', pid)
                ws.sockets.randomize_ssl_after_fork()
                fd_transport.mode = 'sender'
                wp = WorkerProcess(pid=pid, fd_transport=fd_transport)
                self.workers[wp.pid] = wp
            else:
                self.execution_context = self.ExecutionContext.worker
                ws.signals.reset_handlers(excluding={ws.signals.SIGTERM})
                ws.signals.signal(ws.signals.SIGCHLD, ws.signals.SIG_IGN)
                # noinspection PyBroadException
                try:
                    ws.logs.setup_worker_handlers()
                    fd_transport.mode = 'receiver'
                    for other_worker in self.workers.values():
                        other_worker.close_ipc()
                    self.sock.close()
                    os.close(0)
                    os.close(1)
                    os.close(2)
                    with profile(WORKER_PROFILING_ON):
                        worker = ws.worker.Worker(fd_transport=fd_transport)
                        exit_code = worker.work()
                except BaseException:
                    error_log.exception('Worker failed.')
                    exit_code = 1

                # noinspection PyProtectedMember
                os._exit(exit_code)
예제 #9
0
def execute_script(request, socket, body_start=b''):
    assert isinstance(socket, (ws.sockets.SSLSocket, ws.sockets.Socket))
    assert isinstance(body_start, (bytes, bytearray))
    assert can_handle_request(request)

    uri = request.request_line.request_target
    cgi_script = find_cgi_script(uri)
    error_log.info('Executing CGI script %s', cgi_script.name)
    script_env = prepare_cgi_script_env(request, socket)
    error_log.debug('CGIScript environment will be: %s', script_env)

    has_body = 'Content-Length' in request.headers

    try:
        proc = subprocess.Popen(
            args=os.path.abspath(cgi_script.script_path),
            env=script_env,
            stdin=subprocess.PIPE if has_body else socket.fileno(),
            stdout=socket.fileno())
    except (OSError, ValueError):
        error_log.exception(
            'Failed to open subprocess for cgi script {}'.format(
                cgi_script.name))
        return ws.http.utils.build_response(500)

    # noinspection PyBroadException
    try:
        if not has_body:
            return

        error_log.debug('Request to CGI has body. Writing to stdin...')
        start = time.time()

        length = int(request.headers['Content-Length'])
        read_bytes = 0
        chunk_size = 4096
        timeout_reached = False
        while read_bytes < length and not timeout_reached:
            if body_start:
                chunk = body_start
                body_start = b''
            else:
                chunk = socket.recv(chunk_size)
                read_bytes += len(chunk)
            if not chunk:
                break

            while chunk:
                avail = select.select([], [proc.stdin], [], cgi_script.timeout)
                _, wlist, _ = avail
                if wlist:
                    written = wlist[0].write(chunk)
                    chunk = chunk[written:]
                else:
                    timeout_reached = True
                    break
            if time.time() - start > cgi_script.timeout:
                timeout_reached = True

        if timeout_reached:
            error_log.warning(
                'CGI script %s took too long to read body. '
                'Leaving process alive but no more data will '
                'be piped to it.', cgi_script)
    except ws.sockets.TimeoutException:
        error_log.warning("Client sent data too slowly - socket timed out.")
    except Exception:
        error_log.exception('Failed to write body to CGI script.')
    finally:
        try:
            proc.stdin.close()
        except OSError as err:
            error_log.warning(
                'Closing CGI stdin pipe failed. ERRNO=%s MSG=%s.', err.errno,
                err.strerror)
예제 #10
0
def server_err_handler(exc):
    error_log.exception('Internal server error.')
    return ws.http.utils.build_response(500), True