Example #1
0
 /**
  * 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);
     }
 }
Example #2
0
 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));
     }
 }
Example #3
0
 /**
  * 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));
     }
 }