/** * Perform the "open" phase of the handshake. * * @recoil-coroutine * * @param DuplexChannel $frames A channel over which frames are sent * and received. * @param ConnectionOptions $options The options used when establishing * the connection. */ private function open(DuplexChannel $frames, ConnectionOptions $options) : Generator { (yield $frames->write(ConnectionOpenFrame::create($options->vhost()))); try { $frame = (yield $frames->read()); } catch (ChannelClosedException $e) { throw SingleConnectionException::authorizationFailed($options, $e); } if ($frame instanceof ConnectionCloseFrame) { if ($frame->replyCode === Constants::NOT_ALLOWED && $frame->classId === ConnectionOpenFrame::METHOD_ID >> 16 && $frame->methodId === (ConnectionOpenFrame::METHOD_ID & 0xff)) { throw SingleConnectionException::authorizationFailed($options); } throw SingleConnectionException::closedUnexpectedly($options); } elseif (!$frame instanceof ConnectionOpenOkFrame) { throw ProtocolException::unexpectedFrame($frame, 'expected ' . ConnectionOpenOkFrame::class); } }
private function readLoop() : Generator { try { while ($this->state === ConnectionState::OPEN) { $frame = (yield $this->transport->read()); $this->heartbeatsSinceFrameReceived = 0; if ($frame instanceof HeartbeatFrame) { continue; } elseif ($frame->frameChannelId > 0) { $channelManager = $this->channels[$frame->frameChannelId] ?? null; if ($channelManager === null) { throw ProtocolException::unexpectedFrame($frame, \sprintf('channel #%s is closed', $frame->frameChannelId)); } (yield $channelManager->dispatch($frame)); } elseif ($frame instanceof ConnectionCloseFrame) { (yield $this->transport->close()); throw AmqpException::create($frame->replyText, $frame->replyCode); } else { throw ProtocolException::unexpectedFrame($frame, 'received on channel #0'); } } while ($this->state === ConnectionState::CLOSING) { $frame = (yield $this->transport->read()); if ($frame instanceof ConnectionCloseOkFrame) { (yield $this->transport->close()); (yield $this->onClose()); } } } catch (ChannelClosedException $e) { (yield $this->onClose(SingleConnectionException::closedUnexpectedly($this->options, $e))); } catch (Throwable $e) { assert(Debug::dumpException('read-loop', $e)); (yield $this->onClose($e)); } }
/** * Notify the channel of an incoming frame. * * @recoil-coroutine * * @param IncomingFrame $frame The received frame. * * @throws ProtocolException The incoming frame violates the expectations of the AMQP protocol. */ public function dispatch(IncomingFrame $frame) : Generator { assert($frame->frameChannelId === $this->channelId); // Handle frames during normal operation ... if ($this->state === ChannelState::OPEN) { // The broker has closed the channel ... if ($frame instanceof ChannelCloseFrame) { (yield $this->onCloseByBroker($frame)); // The broker has acknowledged a client-side open after already opened ... } elseif ($frame instanceof ChannelOpenOkFrame) { goto unexpectedFrame; // The broker has acknowledged a client-side close that was never made ... } elseif ($frame instanceof ChannelCloseOkFrame) { goto unexpectedFrame; // The broker has requested a client->broker flow start/stop ... } elseif ($frame instanceof ChannelFlowFrame) { // Not suppported (and unused by RabbitMQ). // The AMQP spec dictates that method frames may not be interleaved // with content frames, which makes it unclear how the client should // respond to a flow frame if it's in the middle of transmitting content // We're expecting a method and we got one ... } elseif ($this->expectedFrame === Constants::FRAME_METHOD && $frame instanceof MethodFrame) { // The frame will be followed by content ... if ($frame instanceof ContentPrecursorFrame) { $this->expectedFrame = Constants::FRAME_HEADER; } $waitMethod = $frame::METHOD_ID; // Check if there is a suspended strand waiting for this frame // type as a response ... ($callers =& $this->callers[$waitMethod]) ?? null; if ($callers) { foreach ($callers as $id => $strand) { $sentMethod = $this->callersByWaitMethod[$waitMethod][$id]; unset($this->callers[$waitMethod][$id], $this->callersBySentMethod[$sentMethod][$id], $this->callersByWaitMethod[$waitMethod][$id]); $strand->send($frame); break; } // Otherwise dispatch the frame to any listeners ... } elseif (isset($this->listeners[$waitMethod])) { assert(false, 'not implemented'); // foreach ($this->listeners[$waitMethod] as $callback) { // $callback(null, $frame); // } } // We're expecting a content header and we got one ... } elseif ($this->expectedFrame === Constants::FRAME_HEADER && $frame instanceof HeaderFrame) { // There are no body frames to follow ... if ($frame->contentLength === 0) { $this->expectedFrame = Constants::FRAME_METHOD; if ($this->onIncomingContent) { $callback = $this->onIncomingContent; $this->onIncomingContent = null; $callback(null, $frame, true); } // There are body frames to follow ... } else { $this->expectedFrame = Constants::FRAME_BODY; $this->incomingBytesRemaining = $frame->contentLength; if ($this->onIncomingContent) { $callback = $this->onIncomingContent; $callback(null, $frame, false); } } // We're expecting some content and we got some ... } elseif ($this->expectedFrame === Constants::FRAME_BODY && $frame instanceof BodyFrame) { $this->incomingBytesRemaining -= strlen($frame->content); if ($this->incomingBytesRemaining < 0) { throw ProtocolException::create(\sprintf('Expected content length was exceeded on channel #%d by %d byte(s).', $this->channelId, -$this->incomingBytesRemaining)); } elseif ($this->incomingBytesRemaining === 0) { $this->expectedFrame = Constants::FRAME_METHOD; if ($this->onIncomingContent) { $callback = $this->onIncomingContent; $this->onIncomingContent = null; $callback(null, $frame, true); } } elseif ($this->onIncomingContent) { $callback = $this->onIncomingContent; $callback(null, $frame, false); } // Otherwise, bail with a protocol exception ... } else { goto unexpectedFrame; } // The broker has acknowledged our open request ... } elseif ($this->state === ChannelState::OPENING && $frame instanceof ChannelOpenOkFrame) { $this->state = ChannelState::OPEN; $strand = $this->openingStrand; $this->openingStrand = null; (yield Recoil::resume($strand)); // We're waiting on a close acknowledgement, blackhole everything else ... } elseif ($this->state === ChannelState::CLOSING) { if ($frame instanceof ChannelCloseOkFrame) { (yield $this->onClose()); } } else { unexpectedFrame: throw ProtocolException::unexpectedFrame($frame, \sprintf('received on channel #%d, state: %d, expect: %d', $this->channelId, $this->state, $this->expectedFrame)); } }