/// <summary> /// Creates new channel with custom event handler, authenticator and options /// </summary> /// <exception cref="OperationCanceledException">Thrown when channel limit is exceeded for the server</exception> /// <exception cref="DuplicateNameException">Thrown when there is already a channel with same name</exception> public Channel CreateChannel(string name, IChannelAuthenticator authenticator, IChannelEventHandler eventHandler, IMessageDeliveryHandler deliveryHandler, Action <ChannelOptions> optionsAction) { ChannelOptions options = ChannelOptions.CloneFrom(Options); optionsAction(options); return(CreateChannel(name, authenticator, eventHandler, deliveryHandler, options)); }
/// <summary> /// Sets status of the queue /// </summary> public async Task SetStatus(QueueStatus status, IMessageDeliveryHandler newDeliveryHandler = null) { QueueStatus prevStatus = Status; IQueueState prevState = State; if (prevStatus == status) { return; } QueueStatusAction leave = await State.LeaveStatus(status); if (leave == QueueStatusAction.Deny) { return; } if (leave == QueueStatusAction.DenyAndTrigger) { await Trigger(); return; } Status = status; State = QueueStateFactory.Create(this, status); QueueStatusAction enter = await State.EnterStatus(prevStatus); if (enter == QueueStatusAction.Deny || enter == QueueStatusAction.DenyAndTrigger) { Status = prevStatus; State = prevState; await prevState.EnterStatus(prevStatus); if (enter == QueueStatusAction.DenyAndTrigger) { await Trigger(); } return; } if (Channel.EventHandler != null) { await Channel.EventHandler.OnQueueStatusChanged(this, prevStatus, status); } if (enter == QueueStatusAction.AllowAndTrigger) { _ = Trigger(); } }
/// <summary> /// Creates new queue in the channel /// </summary> /// <exception cref="NoNullAllowedException">Thrown when server does not have default delivery handler implementation</exception> /// <exception cref="OperationCanceledException">Thrown when queue limit is exceeded for the channel</exception> /// <exception cref="DuplicateNameException">Thrown when there is already a queue with same id</exception> public async Task <ChannelQueue> CreateQueue(ushort queueId, ChannelQueueOptions options, IMessageDeliveryHandler deliveryHandler) { if (deliveryHandler == null) { throw new NoNullAllowedException("Delivery handler cannot be null."); } //multiple queues are not allowed if (!Options.AllowMultipleQueues && _queues.Count > 0) { return(null); } //if content type is not allowed for this channel, return null if (Options.AllowedQueues != null && Options.AllowedQueues.Length > 0) { if (!Options.AllowedQueues.Contains(queueId)) { return(null); } } if (Options.QueueLimit > 0 && Options.QueueLimit >= _queues.Count) { throw new OperationCanceledException("Queue limit is exceeded for the channel"); } ChannelQueue queue = _queues.Find(x => x.Id == queueId); if (queue != null) { throw new DuplicateNameException($"The channel has already a queue with same content type: {queueId}"); } queue = new ChannelQueue(this, queueId, options, deliveryHandler); _queues.Add(queue); if (EventHandler != null) { await EventHandler.OnQueueCreated(queue, this); } return(queue); }
/// <summary> /// Loads messages of queues in configuration /// </summary> public async Task LoadQueues(HorseMq server) { foreach (QueueConfiguration queueConfiguration in Config.Queues) { HorseQueue queue = server.FindQueue(queueConfiguration.Name); if (queue == null) { if (server.DeliveryHandlerFactory != null) { queue = await server.CreateQueue(queueConfiguration.Name, queueConfiguration.Configuration.ToOptions(), async builder => { builder.DeliveryHandlerHeader = queueConfiguration.DeliveryHandler; IMessageDeliveryHandler handler = await server.DeliveryHandlerFactory(builder); builder.OnAfterCompleted(b => { }); //don't trigger created events, it's already created and reloading return(handler); }); } else { queue = await Extensions.CreateQueue(server, queueConfiguration.Name, (DeleteWhen)queueConfiguration.DeleteWhen, (ProducerAckDecision)queueConfiguration.ProducerAck, queueConfiguration.Configuration.ToOptions()); } //queue creation not permitted, skip if (queue == null) { continue; } } else { if (queue.DeliveryHandler is IPersistentDeliveryHandler deliveryHandler) { await deliveryHandler.Initialize(); } } queueConfiguration.Queue = queue; } }
internal Channel(MqServer server, ChannelOptions options, string name, IChannelAuthenticator authenticator, IChannelEventHandler eventHandler, IMessageDeliveryHandler deliveryHandler) { Server = server; Options = options; Name = name; Authenticator = authenticator; EventHandler = eventHandler; DeliveryHandler = deliveryHandler; _queues = new SafeList <ChannelQueue>(8); _clients = new SafeList <ChannelClient>(256); }
/// <summary> /// Creates new channel with custom event handler, authenticator and options /// </summary> /// <exception cref="OperationCanceledException">Thrown when channel limit is exceeded for the server</exception> /// <exception cref="DuplicateNameException">Thrown when there is already a channel with same name</exception> public Channel CreateChannel(string name, IChannelAuthenticator authenticator, IChannelEventHandler eventHandler, IMessageDeliveryHandler deliveryHandler, ChannelOptions options) { if (Options.ChannelLimit > 0 && _channels.Count >= Options.ChannelLimit) { throw new OperationCanceledException("Channel limit is exceeded for the server"); } Channel channel = _channels.Find(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (channel != null) { throw new DuplicateNameException("There is already a channel with same name: " + name); } channel = new Channel(this, options, name, authenticator, eventHandler, deliveryHandler); _channels.Add(channel); return(channel); }
/// <summary> /// Creates new queue and sends response /// </summary> private async Task CreateQueue(MqClient client, TmqMessage message) { ushort?contentType; NetworkOptionsBuilder builder = null; if (message.Length == 2) { byte[] bytes = new byte[2]; await message.Content.ReadAsync(bytes); contentType = BitConverter.ToUInt16(bytes); } else { builder = new NetworkOptionsBuilder(); builder.Load(message.ToString()); contentType = builder.Id; } Channel channel = await CreateChannel(client, message, true); ChannelQueue queue = channel.FindQueue(contentType.Value); //if queue exists, we can't create. return duplicate response. if (queue != null) { if (message.ResponseRequired) { await client.SendAsync(MessageBuilder.ResponseStatus(message, KnownContentTypes.Duplicate)); } return; } //check authority if client can create queue if (_server.Authorization != null) { bool grant = await _server.Authorization.CanCreateQueue(client, channel, contentType.Value, builder); if (!grant) { if (message.ResponseRequired) { await client.SendAsync(MessageBuilder.ResponseStatus(message, KnownContentTypes.Unauthorized)); } return; } } //creates new queue ChannelQueueOptions options = ChannelQueueOptions.CloneFrom(channel.Options); IMessageDeliveryHandler delivery = channel.DeliveryHandler; if (builder != null) { builder.ApplyToQueue(options); if (!string.IsNullOrEmpty(builder.MessageDeliveryHandler)) { IMessageDeliveryHandler found = _server.Registry.GetMessageDelivery(builder.MessageDeliveryHandler); if (found != null) { delivery = found; } } } queue = await channel.CreateQueue(contentType.Value, options, delivery); //if creation successful, sends response if (queue != null && message.ResponseRequired) { await client.SendAsync(MessageBuilder.ResponseStatus(message, KnownContentTypes.Ok)); } }
/// <summary> /// Creates new channel /// </summary> private async Task <Channel> CreateChannel(MqClient client, TmqMessage message, bool createForQueue) { Channel channel = _server.FindChannel(message.Target); if (channel != null) { return(channel); } //check create channel access if (_server.Authorization != null) { bool grant = await _server.Authorization.CanCreateChannel(client, _server, message.Target); if (!grant) { if (message.ResponseRequired) { await client.SendAsync(MessageBuilder.ResponseStatus(message, KnownContentTypes.Unauthorized)); } return(null); } } if (!createForQueue && message.Length > 0 && message.Content != null && message.Content.Length > 0) { NetworkOptionsBuilder builder = new NetworkOptionsBuilder(); builder.Load(message.ToString()); ChannelOptions options = ChannelOptions.CloneFrom(_server.Options); builder.ApplyToChannel(options); IChannelEventHandler eventHandler = _server.DefaultChannelEventHandler; if (!string.IsNullOrEmpty(builder.ChannelEventHandler)) { IChannelEventHandler e = _server.Registry.GetChannelEvent(builder.ChannelEventHandler); if (e != null) { eventHandler = e; } } IChannelAuthenticator authenticator = _server.DefaultChannelAuthenticator; if (!string.IsNullOrEmpty(builder.ChannelAuthenticator)) { IChannelAuthenticator e = _server.Registry.GetChannelAuthenticator(builder.ChannelAuthenticator); if (e != null) { authenticator = e; } } IMessageDeliveryHandler deliveryHandler = _server.DefaultDeliveryHandler; if (!string.IsNullOrEmpty(builder.MessageDeliveryHandler)) { IMessageDeliveryHandler e = _server.Registry.GetMessageDelivery(builder.MessageDeliveryHandler); if (e != null) { deliveryHandler = e; } } Channel ch = _server.CreateChannel(message.Target, authenticator, eventHandler, deliveryHandler, options); if (ch != null && message.ResponseRequired) { await client.SendAsync(MessageBuilder.ResponseStatus(message, KnownContentTypes.Ok)); } } Channel c = _server.CreateChannel(message.Target); if (!createForQueue && c != null && message.ResponseRequired) { await client.SendAsync(MessageBuilder.ResponseStatus(message, KnownContentTypes.Ok)); } return(c); }
/// <summary> /// Adds default delivery handler implementation to builder /// </summary> public void AddDefaultDeliveryHandler <T>(params object[] ctorArgs) where T : class, IMessageDeliveryHandler, new() { IMessageDeliveryHandler handler = Activator.CreateInstance(typeof(T), ctorArgs) as IMessageDeliveryHandler; _messageDeliveryHandler = handler; }
/// <summary> /// Adds default delivery handler implementation to builder /// </summary> public void AddDefaultDeliveryHandler <T>() where T : class, IMessageDeliveryHandler, new() { _messageDeliveryHandler = new T(); }
/// <summary> /// Adds default delivery handler implementation to builder /// </summary> public void AddDefaultDeliveryHandler(IMessageDeliveryHandler handler) { _messageDeliveryHandler = handler; }
/// <summary> /// Register a message delivery handler to the container /// </summary> public void Register(string key, IMessageDeliveryHandler value) { lock (_messageDeliveryHandlers) _messageDeliveryHandlers.Add(key, value); }
/// <summary> /// Pushes a message into the queue. /// </summary> internal async Task <PushResult> Push(QueueMessage message, MqClient sender) { if (!IsInitialized) { try { UpdateOptionsByMessage(message.Message); DeliveryHandlerBuilder handlerBuilder = new DeliveryHandlerBuilder { Server = Server, Queue = this, Headers = message.Message.Headers, DeliveryHandlerHeader = message.Message.FindHeader(HorseHeaders.DELIVERY_HANDLER) }; IMessageDeliveryHandler deliveryHandler = await Server.DeliveryHandlerFactory(handlerBuilder); InitializeQueue(deliveryHandler); handlerBuilder.TriggerAfterCompleted(); } catch (Exception e) { Server.SendError("INITIALIZE_IN_PUSH", e, $"QueueName:{Name}"); throw; } } if (Status == QueueStatus.Stopped) { return(PushResult.StatusNotSupported); } if (Options.MessageLimit > 0 && PriorityMessagesList.Count + MessagesList.Count >= Options.MessageLimit) { return(PushResult.LimitExceeded); } if (Options.MessageSizeLimit > 0 && message.Message.Length > Options.MessageSizeLimit) { return(PushResult.LimitExceeded); } //remove operational headers that are should not be sent to consumers or saved to disk message.Message.RemoveHeaders(HorseHeaders.DELAY_BETWEEN_MESSAGES, HorseHeaders.ACKNOWLEDGE, HorseHeaders.QUEUE_NAME, HorseHeaders.QUEUE_STATUS, HorseHeaders.QUEUE_TOPIC, HorseHeaders.PUT_BACK_DELAY, HorseHeaders.DELIVERY, HorseHeaders.DELIVERY_HANDLER, HorseHeaders.CC); //prepare properties message.Message.WaitResponse = Options.Acknowledge != QueueAckDecision.None; //if message doesn't have message id and "UseMessageId" option is enabled, create new message id for the message if (Options.UseMessageId && string.IsNullOrEmpty(message.Message.MessageId)) { message.Message.SetMessageId(Server.MessageIdGenerator.Create()); } //if we have an option maximum wait duration for message, set it after message joined to the queue. //time keeper will check this value and if message time is up, it will remove message from the queue. if (Options.MessageTimeout > TimeSpan.Zero) { message.Deadline = DateTime.UtcNow.Add(Options.MessageTimeout); } if (Options.HideClientNames) { message.Message.SetSource(null); } try { //fire message receive event Info.AddMessageReceive(); Decision decision = await DeliveryHandler.ReceivedFromProducer(this, message, sender); message.Decision = decision; bool allow = await ApplyDecision(decision, message); if (!allow) { return(PushResult.Success); } //trigger message produced event OnMessageProduced.Trigger(message); if (State.CanEnqueue(message)) { await RunInListSync(() => AddMessage(message)); if (State.TriggerSupported && !_triggering) { _ = Trigger(); } } else { _ = State.Push(message); } return(PushResult.Success); } catch (Exception ex) { Server.SendError("PUSH", ex, $"QueueName:{Name}"); Info.AddError(); try { Decision decision = await DeliveryHandler.ExceptionThrown(this, State.ProcessingMessage, ex); if (State.ProcessingMessage != null) { await ApplyDecision(decision, State.ProcessingMessage); if (!State.ProcessingMessage.IsInQueue) { if (decision.PutBack == PutBackDecision.Start) { AddMessage(State.ProcessingMessage, false); } else if (decision.PutBack == PutBackDecision.End) { AddMessage(State.ProcessingMessage); } } } } catch //if developer does wrong operation, we should not stop { } } return(PushResult.Success); }
/// <summary> /// Sets status of the queue /// </summary> public async Task SetStatus(QueueStatus status, IMessageDeliveryHandler newDeliveryHandler = null) { QueueStatus prevStatus = Status; try { IQueueState prevState = State; if (prevStatus == status) { return; } QueueStatusAction leave = await State.LeaveStatus(status); if (leave == QueueStatusAction.Deny) { return; } if (leave == QueueStatusAction.DenyAndTrigger) { await Trigger(); return; } Status = status; State = QueueStateFactory.Create(this, status); QueueStatusAction enter = await State.EnterStatus(prevStatus); if (enter == QueueStatusAction.Deny || enter == QueueStatusAction.DenyAndTrigger) { Status = prevStatus; State = prevState; await prevState.EnterStatus(prevStatus); if (enter == QueueStatusAction.DenyAndTrigger) { await Trigger(); } return; } if (newDeliveryHandler != null) { DeliveryHandler = newDeliveryHandler; } foreach (IQueueEventHandler handler in Server.QueueEventHandlers) { await handler.OnStatusChanged(this, prevStatus, status); } if (enter == QueueStatusAction.AllowAndTrigger) { _ = Trigger(); } } catch (Exception e) { Server.SendError("SET_QUEUE_STATUS", e, $"QueueName:{Name}, PrevStatus:{prevStatus} NextStatus:{status}"); } }
internal async Task <HorseQueue> CreateQueue(string queueName, QueueOptions options, HorseMessage requestMessage, Func <DeliveryHandlerBuilder, Task <IMessageDeliveryHandler> > asyncHandler, bool hideException, bool returnIfExists) { await _createQueueLocker.WaitAsync(); try { if (!Filter.CheckNameEligibility(queueName)) { throw new InvalidOperationException("Invalid queue name"); } if (Options.QueueLimit > 0 && Options.QueueLimit >= _queues.Count) { throw new OperationCanceledException("Queue limit is exceeded for the server"); } HorseQueue queue = _queues.Find(x => x.Name == queueName); if (queue != null) { if (returnIfExists) { return(queue); } throw new DuplicateNameException($"The server has already a queue with same name: {queueName}"); } string topic = null; bool statusSpecified = false; //when queue is created by subscriber, it will be initialized if status is specified if (requestMessage != null) { string waitForAck = requestMessage.FindHeader(HorseHeaders.ACKNOWLEDGE); if (!string.IsNullOrEmpty(waitForAck)) { switch (waitForAck.Trim().ToLower()) { case "none": options.Acknowledge = QueueAckDecision.None; break; case "request": options.Acknowledge = QueueAckDecision.JustRequest; break; case "wait": options.Acknowledge = QueueAckDecision.WaitForAcknowledge; break; } } string queueStatus = requestMessage.FindHeader(HorseHeaders.QUEUE_STATUS); if (queueStatus != null) { statusSpecified = true; options.Status = QueueStatusHelper.FindStatus(queueStatus); } topic = requestMessage.FindHeader(HorseHeaders.QUEUE_TOPIC); string delay = requestMessage.FindHeader(HorseHeaders.DELAY_BETWEEN_MESSAGES); if (!string.IsNullOrEmpty(delay)) { options.DelayBetweenMessages = Convert.ToInt32(delay); } } queue = new HorseQueue(this, queueName, options); if (!string.IsNullOrEmpty(topic)) { queue.Topic = topic; } DeliveryHandlerBuilder handlerBuilder = new DeliveryHandlerBuilder { Server = this, Queue = queue }; if (requestMessage != null) { handlerBuilder.DeliveryHandlerHeader = requestMessage.FindHeader(HorseHeaders.DELIVERY_HANDLER); handlerBuilder.Headers = requestMessage.Headers; } bool initialize; //if queue creation is triggered by consumer subscription, we might skip initialization if (requestMessage != null && requestMessage.Type == MessageType.Server && requestMessage.ContentType == KnownContentTypes.Subscribe) { initialize = statusSpecified; } else { initialize = true; } if (initialize) { IMessageDeliveryHandler deliveryHandler = await asyncHandler(handlerBuilder); queue.InitializeQueue(deliveryHandler); } _queues.Add(queue); foreach (IQueueEventHandler handler in _queueEventHandlers) { await handler.OnCreated(queue); } if (initialize) { handlerBuilder.TriggerAfterCompleted(); } OnQueueCreated.Trigger(queue); return(queue); } catch (Exception e) { SendError("CREATE_QUEUE", e, $"QueueName:{queueName}"); if (!hideException) { throw; } return(null); } finally { try { _createQueueLocker.Release(); } catch { } } }