コード例 #1
0
ファイル: connection.py プロジェクト: xrick/BlackSheep
class ClientConnection(asyncio.Protocol):

    __slots__ = (
        'loop',
        'pool',
        'transport',
        'open',
        '_connection_lost',
        'writing_paused',
        'writable',
        'ready',
        'response_ready',
        'request',
        'expect_100_continue',
        '_pending_task',
        '_can_release',
        '_upgraded'
    )

    def __init__(self, loop, pool):
        self.loop = loop
        self.pool = weakref.ref(pool)
        self.transport = None
        self.open = False
        self.writing_paused = False
        self.writable = asyncio.Event()
        self.ready = asyncio.Event()
        self.response_ready = asyncio.Event()
        self.expect_100_continue = False
        self.request = None
        self.request_timeout = 20
        self.headers = []
        self.response = None
        self.parser = httptools.HttpResponseParser(self)
        self._connection_lost = False
        self._pending_task = None
        self._can_release = False
        self._upgraded = False

    def reset(self):
        self.headers.clear()
        self.request = None
        self.response = None
        self.writing_paused = False
        self.writable.set()
        self.expect_100_continue = False
        self.parser = httptools.HttpResponseParser(self)
        self._connection_lost = False
        self._pending_task = None
        self._can_release = False
        self._upgraded = False

    def pause_writing(self):
        super().pause_writing()
        self.writing_paused = True
        self.writable.clear()

    def resume_writing(self):
        super().resume_writing()
        self.writing_paused = False
        self.writable.set()

    def connection_made(self, transport):
        self.transport = transport
        self.open = True
        self.ready.set()

    async def _wait_response(self):
        await self.response_ready.wait()

        self._pending_task = False
        if self._can_release:
            self.loop.call_soon(self.release)

        response = self.response

        if 99 < response.status < 200:
            # Handle 1xx informational
            #  https://tools.ietf.org/html/rfc7231#section-6.2

            if response.status == 101:
                # 101 Upgrade is a final response as it's used to switch protocols with WebSockets handshake.
                # returns the response object with status 101 and access to transport
                self._upgraded = True
                return UpgradeResponse(response, self.transport)

            if response.status == 100 and self.expect_100_continue:
                await self._send_body(self.request)

            # ignore;
            self.response_ready.clear()
            self.headers.clear()

            # await the final response
            return await self._wait_response()

        if self._connection_lost:  # TODO: should also check if the response is not complete?
            raise ConnectionClosedError(False)

        self.response_ready.clear()
        return response

    async def _write_chunks(self, request, method):
        async for chunk in method(request):
            if self._can_release:
                # the server returned a response before we ended sending the request
                return await self._wait_response()

            if not self.open:
                raise ConnectionClosedError(False)

            if self.writing_paused:
                await self.writable.wait()
            self.transport.write(chunk)

    async def _send_body(self, request):
        await self._write_chunks(request, write_request_body_only)

    async def send(self, request):
        if not self.open:
            # NB: if the connection is closed here, it is always possible to try again with a new connection
            # instead, if it happens later; we cannot retry because we started sending a request
            raise ConnectionClosedError(True)

        self.request = request
        self._pending_task = True

        if request_has_body(request) and request.expect_100_continue():
            # don't send the body immediately; instead, wait for HTTP 100 Continue interim response from server
            self.expect_100_continue = True
            self.transport.write(write_request_without_body(request))

            return await self._wait_response()

        if is_small_request(request):
            self.transport.write(write_small_request(request))
        else:
            await self._write_chunks(request, write_request)

        return await self._wait_response()

    def close(self):
        if self.open:
            self.open = False
            if self.transport:
                self.transport.close()

    def data_received(self, data):
        try:
            self.parser.feed_data(data)
        except HttpParserCallbackError:
            self.close()
            raise
        except HttpParserError as pex:
            raise InvalidResponseFromServer(pex)

    def connection_lost(self, exc):
        self._connection_lost = True
        self.ready.clear()
        self.open = False

        if self._pending_task:
            self.response_ready.set()

    def on_header(self, name, value):
        self.headers.append(Header(name, value))

    def on_headers_complete(self):
        status = self.parser.get_status_code()
        self.response = Response(
            status,
            Headers(self.headers),
            None
        )
        self.response_ready.set()

    def on_message_complete(self):
        if self.response:
            self.response.complete.set()

        if self._pending_task:
            # the server returned a response before we ended sending the request,
            # the connection cannot be released now - this can happen for our Bad Requests
            self._can_release = True
            self.response_ready.set()
        else:
            # request-response cycle completed now,
            # the connection can be returned to its pool
            self.loop.call_soon(self.release)

    def release(self):
        if not self.open or self._upgraded:
            # if the connection was upgraded, its transport is used for web sockets,
            # it cannot return to its pool for other cycles
            return

        if self.parser.should_keep_alive():
            self.reset()
            pool = self.pool()

            if pool:
                pool.try_return_connection(self)
        else:
            self.close()

    def on_body(self, value: bytes):
        self.response.extend_body(value)
コード例 #2
0
ファイル: connection.py プロジェクト: hzlmn/BlackSheep
class ClientConnection(asyncio.Protocol):

    __slots__ = ('loop', 'pool', 'transport', 'open', '_connection_lost',
                 'writing_paused', 'writable', 'ready', 'response_ready',
                 '_pending_task')

    def __init__(self, loop, pool):
        self.loop = loop
        self.pool = weakref.ref(pool)
        self.transport = None
        self.open = False
        self.writing_paused = False
        self.writable = asyncio.Event()
        self.ready = asyncio.Event()
        self.response_ready = asyncio.Event()

        # per request state
        self.headers = []
        self.response = None
        self.parser = httptools.HttpResponseParser(self)
        self._connection_lost = False
        self._pending_task = None
        self._can_release = False

    def reset(self):
        self.headers.clear()
        self.response = None
        self.writing_paused = False
        self.writable.set()
        self.parser = httptools.HttpResponseParser(self)
        self._connection_lost = False
        self._pending_task = None
        self._can_release = False

    def pause_writing(self):
        super().pause_writing()
        self.writing_paused = True
        self.writable.clear()

    def resume_writing(self):
        super().resume_writing()
        self.writing_paused = False
        self.writable.set()

    def connection_made(self, transport):
        self.transport = transport
        self.open = True
        self.ready.set()

    async def _wait_response(self):
        await self.response_ready.wait()

        self._pending_task = False
        if self._can_release:
            self.loop.call_soon(self.release)

        response = self.response

        if self._connection_lost:  # TODO: should also check if the response is not complete?
            raise ConnectionClosedError(False)

        self.response_ready.clear()
        return response

    async def send(self, request):
        if not self.open:
            # NB: if the connection is closed here, it is always possible to try again with a new connection
            # instead, if it happens later; we cannot retry because we started sending a request
            raise ConnectionClosedError(True)

        self._pending_task = True

        if is_small_request(request):
            self.transport.write(write_small_request(request))
        else:
            async for chunk in write_request(request):
                if self._can_release:
                    # the server returned a response before we ended sending the request
                    return await self._wait_response()

                if not self.open:
                    raise ConnectionClosedError(False)

                if self.writing_paused:
                    await self.writable.wait()
                self.transport.write(chunk)

        return await self._wait_response()

    def close(self):
        if self.open:
            self.open = False
            if self.transport:
                self.transport.close()

    def data_received(self, data):
        try:
            self.parser.feed_data(data)
        except HttpParserCallbackError:
            self.close()
            raise
        except HttpParserError as pex:
            raise InvalidResponseFromServer(pex)

    def connection_lost(self, exc):
        self._connection_lost = True
        self.ready.clear()
        self.open = False

        if self._pending_task:
            self.response_ready.set()

    def on_header(self, name, value):
        self.headers.append(Header(name, value))

    def on_headers_complete(self):
        status = self.parser.get_status_code()
        self.response = Response(status, Headers(self.headers), None)
        self.response_ready.set()

    def on_message_complete(self):
        if self.response:
            self.response.complete.set()

        if self._pending_task:
            # the server returned a response before we ended sending the request,
            # the connection cannot be released now - this can happen for our Bad Requests
            self._can_release = True
            self.response_ready.set()
        else:
            # request-response cycle completed now,
            # the connection can be returned to its pool
            self.loop.call_soon(self.release)

    def release(self):
        if not self.open:
            return

        if self.parser.should_keep_alive():
            self.reset()
            pool = self.pool()

            if pool:
                pool.try_return_connection(self)
        else:
            self.close()

    def on_body(self, value: bytes):
        self.response.extend_body(value)