1512 lines
63 KiB
C#
1512 lines
63 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using ZB.MOM.NatsNet.Server.Internal;
|
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
|
|
|
namespace ZB.MOM.NatsNet.Server;
|
|
|
|
public sealed partial class NatsServer
|
|
{
|
|
private readonly Lock _jsApiSubsLock = new();
|
|
private readonly List<JetStreamApiSubscription> _jsApiSubscriptions = [];
|
|
private IpQueue<JsApiRoutedRequest>? _jsApiRoutedReqs;
|
|
private IpQueue<DelayedApiResponse>? _delayedApiResponses;
|
|
|
|
internal void ProcessJSAPIRoutedRequests()
|
|
{
|
|
var queue = _jsApiRoutedReqs;
|
|
if (queue is null)
|
|
return;
|
|
|
|
var client = CreateInternalJetStreamClient();
|
|
var token = _quitCts.Token;
|
|
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
if (!queue.Ch.WaitToReadAsync(token).AsTask().GetAwaiter().GetResult())
|
|
break;
|
|
|
|
while (true)
|
|
{
|
|
var (request, ok) = queue.PopOne();
|
|
if (!ok)
|
|
break;
|
|
|
|
var start = DateTime.UtcNow;
|
|
request.Subscription.Handler(client, request.Account, request.Subject, request.Reply, request.Message);
|
|
var elapsed = DateTime.UtcNow - start;
|
|
if (elapsed >= TimeSpan.FromSeconds(1))
|
|
Warnf("Internal subscription on '{0}' took too long: {1}", request.Subject, elapsed);
|
|
|
|
var js = GetJetStreamState();
|
|
if (js is not null)
|
|
Interlocked.Add(ref js.ApiInflight, -1);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Warnf("JetStream routed API worker failed: {0}", ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal Exception? SetJetStreamExportSubs()
|
|
{
|
|
var js = GetJetStreamState();
|
|
if (js is null)
|
|
return new InvalidOperationException(JsApiErrors.NewJSNotEnabledError().Description ?? "jetstream not enabled");
|
|
|
|
var workers = Math.Min(Math.Max(Environment.ProcessorCount, 1), 16);
|
|
|
|
_jsApiRoutedReqs = IpQueue<JsApiRoutedRequest>.NewIPQueue("Routed JS API Requests");
|
|
_delayedApiResponses = IpQueue<DelayedApiResponse>.NewIPQueue("Delayed JS API Responses");
|
|
Interlocked.Exchange(ref js.QueueLimit, JsApiSubjects.JsDefaultRequestQueueLimit);
|
|
|
|
for (var i = 0; i < workers; i++)
|
|
StartGoRoutine(ProcessJSAPIRoutedRequests);
|
|
|
|
StartGoRoutine(DelayedAPIResponder);
|
|
|
|
lock (_jsApiSubsLock)
|
|
{
|
|
_jsApiSubscriptions.Clear();
|
|
_jsApiSubscriptions.AddRange(
|
|
[
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiAccountInfo, Handler = JsAccountInfoRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamCreate, Handler = JsStreamCreateRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamUpdate, Handler = JsStreamUpdateRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreams, Handler = JsStreamNamesRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamList, Handler = JsStreamListRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamInfo, Handler = JsStreamInfoRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamDelete, Handler = JsStreamDeleteRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiMsgDelete, Handler = JsMsgDeleteRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiMsgGet, Handler = JsMsgGetRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamPurge, Handler = JsStreamPurgeRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamRestore, Handler = JsStreamRestoreRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamSnapshot, Handler = JsStreamSnapshotRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerCreateEx, Handler = JsConsumerCreateRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerCreate, Handler = JsConsumerCreateRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiDurableCreate, Handler = JsConsumerCreateRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumers, Handler = JsConsumerNamesRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerList, Handler = JsConsumerListRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerInfo, Handler = JsConsumerInfoRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerDelete, Handler = JsConsumerDeleteRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerPause, Handler = JsConsumerPauseRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerUnpin, Handler = JsConsumerUnpinRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamLeaderStepDown, Handler = JsStreamLeaderStepDownRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiConsumerLeaderStepDown, Handler = JsConsumerLeaderStepDownRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiStreamRemovePeer, Handler = JsStreamRemovePeerRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiRemoveServer, Handler = JsLeaderServerRemoveRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiServerStreamMove, Handler = JsLeaderServerStreamMoveRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiServerStreamCancelMove, Handler = JsLeaderServerStreamCancelMoveRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiAccountPurge, Handler = JsLeaderAccountPurgeRequest },
|
|
new JetStreamApiSubscription { Subject = JsApiSubjects.JsApiLeaderStepDown, Handler = JsLeaderStepDownRequest },
|
|
]);
|
|
}
|
|
|
|
var sys = SystemAccount();
|
|
if (sys is null)
|
|
return null;
|
|
|
|
var err = sys.AddServiceExport(JsApiSubjects.JsAllApi, null);
|
|
if (err is not null)
|
|
Warnf("Error setting up jetstream service exports: {0}", err.Message);
|
|
return err;
|
|
}
|
|
|
|
internal JetStreamApiSubscription? MatchJetStreamApiSubscription(string subject)
|
|
{
|
|
lock (_jsApiSubsLock)
|
|
{
|
|
foreach (var sub in _jsApiSubscriptions)
|
|
{
|
|
if (JetStreamApi.SubjectMatches(sub.Subject, subject))
|
|
return sub;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal (bool Queued, long Dropped) EnqueueJSApiRequest(JetStream js, JsApiRoutedRequest request)
|
|
{
|
|
var queue = _jsApiRoutedReqs;
|
|
if (queue is null)
|
|
return (false, 0);
|
|
|
|
var (pending, error) = queue.Push(request);
|
|
var limit = Interlocked.Read(ref js.QueueLimit);
|
|
if (limit <= 0)
|
|
limit = JsApiSubjects.JsDefaultRequestQueueLimit;
|
|
|
|
if (error is null && pending < limit)
|
|
return (true, 0);
|
|
|
|
RateLimitFormatWarnf("JetStream API queue limit reached, dropping {0} requests", pending);
|
|
var drained = queue.Drain();
|
|
if (drained > 0)
|
|
Interlocked.Add(ref js.ApiInflight, -drained);
|
|
|
|
return (false, drained);
|
|
}
|
|
|
|
internal void SendAPIResponse(ClientInfo? ci, Account acc, string subject, string reply, string request, string response)
|
|
{
|
|
acc.TrackAPI();
|
|
if (!string.IsNullOrWhiteSpace(reply))
|
|
_ = SendInternalAccountMsgWithReply(acc, reply, string.Empty, null, response, false);
|
|
|
|
SendJetStreamAPIAuditAdvisory(ci, acc, subject, request, response);
|
|
}
|
|
|
|
internal void SendAPIErrResponse(ClientInfo? ci, Account acc, string subject, string reply, string request, string response)
|
|
{
|
|
acc.TrackAPIErr();
|
|
if (!string.IsNullOrWhiteSpace(reply))
|
|
_ = SendInternalAccountMsgWithReply(acc, reply, string.Empty, null, response, false);
|
|
|
|
SendJetStreamAPIAuditAdvisory(ci, acc, subject, request, response);
|
|
}
|
|
|
|
internal void DelayedAPIResponder()
|
|
{
|
|
var queue = _delayedApiResponses;
|
|
if (queue is null)
|
|
return;
|
|
|
|
var token = _quitCts.Token;
|
|
DelayedApiResponse? head = null;
|
|
DelayedApiResponse? tail = null;
|
|
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
var (entry, ok) = queue.PopOne();
|
|
if (!ok)
|
|
break;
|
|
JetStreamApi.AddDelayedResponse(ref head, ref tail, entry);
|
|
}
|
|
|
|
var now = DateTime.UtcNow;
|
|
if (head is not null && head.DeadlineUtc <= now)
|
|
{
|
|
var entry = head;
|
|
head = head.Next;
|
|
if (head is null)
|
|
tail = null;
|
|
|
|
if (entry.RawResponse)
|
|
_ = SendInternalAccountMsgWithReply(entry.Account, entry.Subject, string.Empty, entry.Header, entry.Response, false);
|
|
else
|
|
SendAPIErrResponse(entry.ClientInfo, entry.Account, entry.Subject, entry.Reply, entry.Request, entry.Response);
|
|
|
|
continue;
|
|
}
|
|
|
|
var wait = head is null
|
|
? TimeSpan.FromSeconds(1)
|
|
: head.DeadlineUtc - now;
|
|
|
|
if (wait < TimeSpan.Zero)
|
|
wait = TimeSpan.Zero;
|
|
|
|
_ = Task.WhenAny(
|
|
queue.Ch.WaitToReadAsync(token).AsTask(),
|
|
Task.Delay(wait, token)).GetAwaiter().GetResult();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Warnf("Delayed JetStream API responder failed: {0}", ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void SendDelayedAPIErrResponse(
|
|
ClientInfo? ci,
|
|
Account acc,
|
|
string subject,
|
|
string reply,
|
|
string request,
|
|
string response,
|
|
TimeSpan duration)
|
|
{
|
|
_delayedApiResponses?.Push(new DelayedApiResponse
|
|
{
|
|
ClientInfo = ci,
|
|
Account = acc,
|
|
Subject = subject,
|
|
Reply = reply,
|
|
Request = request,
|
|
Response = response,
|
|
DeadlineUtc = DateTime.UtcNow.Add(duration),
|
|
RawResponse = false,
|
|
});
|
|
}
|
|
|
|
internal void SendDelayedErrResponse(Account acc, string subject, byte[]? header, string response, TimeSpan duration)
|
|
{
|
|
_delayedApiResponses?.Push(new DelayedApiResponse
|
|
{
|
|
Account = acc,
|
|
Subject = subject,
|
|
Reply = string.Empty,
|
|
Header = header,
|
|
Response = response,
|
|
DeadlineUtc = DateTime.UtcNow.Add(duration),
|
|
RawResponse = true,
|
|
});
|
|
}
|
|
|
|
internal (ClientInfo? ClientInfo, Account? Account, byte[] Header, byte[] Message, Exception? Error) GetRequestInfo(ClientConnection c, byte[] raw)
|
|
{
|
|
var (hdr, msg) = c.MsgParts(raw);
|
|
|
|
ClientInfo? clientInfo = null;
|
|
var clientInfoBytes = NatsMessageHeaders.GetHeader(AccountEventConstants.ClientInfoHeader, hdr);
|
|
if (clientInfoBytes is { Length: > 0 })
|
|
{
|
|
try
|
|
{
|
|
clientInfo = JsonSerializer.Deserialize<ClientInfo>(clientInfoBytes);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (null, null, hdr, msg, ex);
|
|
}
|
|
}
|
|
|
|
Account? acc = null;
|
|
var serviceAccount = clientInfo?.ServiceAccount();
|
|
if (!string.IsNullOrWhiteSpace(serviceAccount))
|
|
(acc, _) = LookupAccount(serviceAccount);
|
|
|
|
if (acc is null)
|
|
{
|
|
acc = c.Account() as Account;
|
|
acc ??= SystemAccount();
|
|
}
|
|
|
|
if (acc is null)
|
|
return (clientInfo, null, hdr, msg, ServerErrors.ErrMissingAccount);
|
|
|
|
return (clientInfo, acc, hdr, msg, null);
|
|
}
|
|
|
|
internal Exception? UnmarshalRequest<T>(ClientConnection c, Account acc, string subject, byte[] message, out T? value)
|
|
{
|
|
var strictMode = JetStreamConfig()?.Strict ?? true;
|
|
value = JetStreamApi.DeserializeStrict<T>(message, strictMode);
|
|
if (value is not null)
|
|
return null;
|
|
|
|
var err = new InvalidOperationException("unable to deserialize JetStream API request");
|
|
c.RateLimitWarnf("Invalid JetStream request '{0} > {1}': {2}", acc.Name, subject, err.Message);
|
|
return err;
|
|
}
|
|
|
|
internal Exception? UnmarshalRequest(ClientConnection c, Account acc, string subject, byte[] message, object destination)
|
|
{
|
|
var strictMode = JetStreamConfig()?.Strict ?? true;
|
|
object? parsed;
|
|
|
|
try
|
|
{
|
|
parsed = JsonSerializer.Deserialize(message, destination.GetType(), new JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
|
});
|
|
}
|
|
catch (Exception ex) when (!strictMode)
|
|
{
|
|
try
|
|
{
|
|
parsed = JsonSerializer.Deserialize(message, destination.GetType());
|
|
}
|
|
catch
|
|
{
|
|
c.RateLimitWarnf("Invalid JetStream request '{0} > {1}': {2}", acc.Name, subject, ex.Message);
|
|
return ex;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
c.RateLimitWarnf("Invalid JetStream request '{0} > {1}': {2}", acc.Name, subject, ex.Message);
|
|
return ex;
|
|
}
|
|
|
|
if (parsed is null)
|
|
return new InvalidOperationException("empty JetStream API request body");
|
|
|
|
foreach (var property in destination.GetType().GetProperties())
|
|
{
|
|
if (!property.CanRead || !property.CanWrite)
|
|
continue;
|
|
property.SetValue(destination, property.GetValue(parsed));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal void JsAccountInfoRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
if (!JetStreamEnabled())
|
|
return;
|
|
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
{
|
|
Warnf("Malformed JetStream API Request: {0}", Encoding.UTF8.GetString(msg));
|
|
return;
|
|
}
|
|
|
|
var response = new JsApiAccountInfoResponse
|
|
{
|
|
Type = JsApiSubjects.JsApiAccountInfoResponseType,
|
|
};
|
|
|
|
var requiredApiLevel = NatsMessageHeaders.GetHeader(JsApiSubjects.JsRequiredApiLevel, hdr);
|
|
var reqApiHeader = requiredApiLevel is null ? null : Encoding.ASCII.GetString(requiredApiLevel);
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(reqApiHeader))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (hasJetStream, shouldError) = requestAcc.CheckJetStream();
|
|
if (!hasJetStream)
|
|
{
|
|
if (!shouldError)
|
|
return;
|
|
|
|
response.Error = JsApiErrors.NewJSNotEnabledForAccountError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var stats = requestAcc.JetStreamUsage();
|
|
response.Memory = stats.Memory;
|
|
response.Store = stats.Store;
|
|
response.ReservedMemory = stats.ReservedMemory;
|
|
response.ReservedStore = stats.ReservedStore;
|
|
response.Streams = stats.Streams;
|
|
response.Consumers = stats.Consumers;
|
|
response.Limits = stats.Limits;
|
|
response.Domain = stats.Domain;
|
|
response.Api = stats.Api;
|
|
response.Tiers = stats.Tiers;
|
|
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamCreateRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamCreateResponse { Type = JsApiSubjects.JsApiStreamCreateResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (hasJetStream, shouldError) = requestAcc.CheckJetStream();
|
|
if (!hasJetStream)
|
|
{
|
|
if (shouldError)
|
|
{
|
|
response.Error = JsApiErrors.NewJSNotEnabledForAccountError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var request = new StreamConfigRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
if (!string.Equals(streamName, request.Config.Name, StringComparison.Ordinal))
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamMismatchError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (streamName.IndexOfAny(['\\', '/']) >= 0)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNameContainsPathSeparatorsError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var jsa = requestAcc.JetStream;
|
|
if (jsa is null)
|
|
return;
|
|
|
|
jsa.Lock.EnterWriteLock();
|
|
try
|
|
{
|
|
if (jsa.Streams.ContainsKey(streamName))
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNameExistError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var stream = NatsStream.Create(requestAcc, request.Config, jsa, null, null, this);
|
|
if (stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamCreateError(new InvalidOperationException("stream creation failed"));
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
jsa.Streams[streamName] = stream;
|
|
response.Config = stream.GetConfig();
|
|
response.State = stream.State();
|
|
response.DidCreate = true;
|
|
}
|
|
finally
|
|
{
|
|
jsa.Lock.ExitWriteLock();
|
|
}
|
|
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamUpdateRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamUpdateResponse { Type = JsApiSubjects.JsApiStreamUpdateResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (hasJetStream, shouldError) = requestAcc.CheckJetStream();
|
|
if (!hasJetStream)
|
|
{
|
|
if (shouldError)
|
|
{
|
|
response.Error = JsApiErrors.NewJSNotEnabledForAccountError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var request = new StreamConfigRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
if (!string.Equals(streamName, request.Config.Name, StringComparison.Ordinal))
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamMismatchError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
stream.UpdateConfig(request.Config);
|
|
response.Info = stream.GetInfo();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamNamesRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamNamesResponse
|
|
{
|
|
Type = JsApiSubjects.JsApiStreamNamesResponseType,
|
|
Streams = [],
|
|
Limit = JsApiSubjects.JsApiNamesLimit,
|
|
};
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (hasJetStream, shouldError) = requestAcc.CheckJetStream();
|
|
if (!hasJetStream)
|
|
{
|
|
if (shouldError)
|
|
{
|
|
response.Error = JsApiErrors.NewJSNotEnabledForAccountError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var offset = 0;
|
|
var filter = string.Empty;
|
|
if (JetStreamApi.IsJSONObjectOrArray(msg))
|
|
{
|
|
var request = new JsApiStreamNamesRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
offset = request.Offset;
|
|
filter = request.Subject ?? string.Empty;
|
|
}
|
|
|
|
var streams = requestAcc.FilteredStreams(filter)
|
|
.OrderBy(s => s.Config.Name, StringComparer.Ordinal)
|
|
.Select(s => s.Config.Name)
|
|
.ToList();
|
|
|
|
response.Total = streams.Count;
|
|
if (offset > streams.Count)
|
|
offset = streams.Count;
|
|
response.Offset = offset;
|
|
response.Streams = streams.Skip(offset).Take(JsApiSubjects.JsApiNamesLimit).ToList();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamListRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamListResponse
|
|
{
|
|
Type = JsApiSubjects.JsApiStreamListResponseType,
|
|
Streams = [],
|
|
Limit = JsApiSubjects.JsApiListLimit,
|
|
};
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (hasJetStream, shouldError) = requestAcc.CheckJetStream();
|
|
if (!hasJetStream)
|
|
{
|
|
if (shouldError)
|
|
{
|
|
response.Error = JsApiErrors.NewJSNotEnabledForAccountError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var offset = 0;
|
|
var filter = string.Empty;
|
|
if (JetStreamApi.IsJSONObjectOrArray(msg))
|
|
{
|
|
var request = new JsApiStreamListRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
offset = request.Offset;
|
|
filter = request.Subject ?? string.Empty;
|
|
}
|
|
|
|
var streams = requestAcc.FilteredStreams(filter)
|
|
.OrderBy(s => s.Config.Name, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
response.Total = streams.Count;
|
|
if (offset > streams.Count)
|
|
offset = streams.Count;
|
|
response.Offset = offset;
|
|
response.Streams = streams.Skip(offset).Take(JsApiSubjects.JsApiListLimit).Select(s => s.GetInfo()).ToList();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamInfoRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamInfoResponse { Type = JsApiSubjects.JsApiStreamInfoResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (hasJetStream, shouldError) = requestAcc.CheckJetStream();
|
|
if (!hasJetStream)
|
|
{
|
|
if (shouldError)
|
|
{
|
|
response.Error = JsApiErrors.NewJSNotEnabledForAccountError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Info = stream.GetInfo();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamLeaderStepDownRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamLeaderStepDownResponse { Type = JsApiSubjects.JsApiStreamLeaderStepDownResponseType };
|
|
if (!JetStreamEnabledForDomain())
|
|
{
|
|
response.Error = JsApiErrors.NewJSClusterRequiredError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = true;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerLeaderStepDownRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerLeaderStepDownResponse { Type = JsApiSubjects.JsApiConsumerLeaderStepDownResponseType };
|
|
if (!JetStreamEnabledForDomain())
|
|
{
|
|
response.Error = JsApiErrors.NewJSClusterRequiredError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = true;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamRemovePeerRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamRemovePeerResponse { Type = JsApiSubjects.JsApiStreamRemovePeerResponseType };
|
|
if (!JetStreamEnabledForDomain())
|
|
{
|
|
response.Error = JsApiErrors.NewJSClusterRequiredError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (JetStreamApi.IsEmptyRequest(msg))
|
|
{
|
|
response.Error = JsApiErrors.NewJSBadRequestError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = true;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsLeaderServerRemoveRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null || !ReferenceEquals(requestAcc, SystemAccount()))
|
|
return;
|
|
|
|
var response = new JsApiMetaServerRemoveResponse { Type = JsApiSubjects.JsApiMetaServerRemoveResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (JetStreamApi.IsEmptyRequest(msg))
|
|
{
|
|
response.Error = JsApiErrors.NewJSBadRequestError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiMetaServerRemoveRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = !string.IsNullOrWhiteSpace(request.PeerId) || !string.IsNullOrWhiteSpace(request.Server);
|
|
if (!response.Success)
|
|
response.Error = JsApiErrors.NewJSClusterServerNotMemberError();
|
|
|
|
if (response.Error is null)
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
else
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal string[] PeerSetToNames(string[] peers)
|
|
{
|
|
var names = new string[peers.Length];
|
|
for (var i = 0; i < peers.Length; i++)
|
|
{
|
|
var info = GetNodeInfo(peers[i]);
|
|
names[i] = info?.Name ?? peers[i];
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
internal string NameToPeer(JetStream js, string serverName, string clusterName, string domainName)
|
|
{
|
|
_ = js;
|
|
foreach (var (nodeId, _) in _nodeToInfo)
|
|
{
|
|
var info = GetNodeInfo(nodeId);
|
|
if (info is null || !string.Equals(info.Name, serverName, StringComparison.Ordinal))
|
|
continue;
|
|
if (!string.IsNullOrWhiteSpace(clusterName) && !string.Equals(info.Cluster, clusterName, StringComparison.Ordinal))
|
|
continue;
|
|
if (!string.IsNullOrWhiteSpace(domainName) && !string.Equals(info.Domain, domainName, StringComparison.Ordinal))
|
|
continue;
|
|
return nodeId;
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
internal void JsLeaderServerStreamMoveRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamUpdateResponse { Type = JsApiSubjects.JsApiStreamUpdateResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var req = new JsApiMetaServerStreamMoveRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, req) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = Internal.DataStructures.SubscriptionIndex.TokenAt(subject, 7);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Info = stream.GetInfo();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsLeaderServerStreamCancelMoveRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamUpdateResponse { Type = JsApiSubjects.JsApiStreamUpdateResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = Internal.DataStructures.SubscriptionIndex.TokenAt(subject, 7);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Info = stream.GetInfo();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsLeaderAccountPurgeRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null || !ReferenceEquals(requestAcc, SystemAccount()))
|
|
return;
|
|
|
|
var response = new JsApiAccountPurgeResponse { Type = JsApiSubjects.JsApiAccountPurgeResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Initiated = true;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsLeaderStepDownRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null || !ReferenceEquals(requestAcc, SystemAccount()))
|
|
return;
|
|
|
|
var response = new JsApiLeaderStepDownResponse { Type = JsApiSubjects.JsApiLeaderStepDownResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = true;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamDeleteRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamDeleteResponse { Type = JsApiSubjects.JsApiStreamDeleteResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
if (!IsEmptyRequest(msg))
|
|
{
|
|
response.Error = JsApiErrors.NewJSNotEmptyRequestError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
requestAcc.JetStream?.Lock.EnterWriteLock();
|
|
try
|
|
{
|
|
stream.Delete();
|
|
requestAcc.JetStream?.Streams.Remove(streamName);
|
|
response.Success = true;
|
|
}
|
|
finally
|
|
{
|
|
requestAcc.JetStream?.Lock.ExitWriteLock();
|
|
}
|
|
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsMsgDeleteRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiMsgDeleteResponse { Type = JsApiSubjects.JsApiMsgDeleteResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiMsgDeleteRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = SubscriptionIndex.TokenAt(subject, 6);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream?.Store is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (removed, removeErr) = request.NoErase
|
|
? stream.Store.RemoveMsg(request.Seq)
|
|
: stream.Store.EraseMsg(request.Seq);
|
|
if (removeErr is not null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamMsgDeleteFailedError(removeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = removed;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsMsgGetRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiMsgGetResponse { Type = JsApiSubjects.JsApiMsgGetResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiMsgGetRequest();
|
|
if (JetStreamApi.IsJSONObjectOrArray(msg))
|
|
{
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
}
|
|
|
|
var streamName = SubscriptionIndex.TokenAt(subject, 6);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream?.Store is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var storeMsg = stream.Store.LoadMsg(request.Seq, null);
|
|
if (storeMsg is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSNoMessageFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Message = new StoredMsg();
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamPurgeRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamPurgeResponse { Type = JsApiSubjects.JsApiStreamPurgeResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiStreamPurgeRequest();
|
|
if (JetStreamApi.IsJSONObjectOrArray(msg) &&
|
|
UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream?.Store is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
(ulong purged, Exception? purgeErr) result = string.IsNullOrWhiteSpace(request.Subject) && request.Sequence == 0 && request.Keep == 0
|
|
? stream.Store.Purge()
|
|
: stream.Store.PurgeEx(request.Subject ?? string.Empty, request.Sequence, request.Keep);
|
|
|
|
if (result.purgeErr is not null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamPurgeFailedError(result.purgeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = true;
|
|
response.Purged = result.purged;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerUnpinRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerUnpinResponse { Type = JsApiSubjects.JsApiConsumerUnpinResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiConsumerUnpinRequest();
|
|
if (JetStreamApi.IsJSONObjectOrArray(msg) &&
|
|
UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsStreamRestoreRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamRestoreResponse { Type = JsApiSubjects.JsApiStreamRestoreResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiStreamRestoreRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
var restoreErr = ProcessStreamRestore(requestAcc, streamName, request);
|
|
if (restoreErr is not null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamRestoreError(restoreErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.DeliverSubject = $"$JSC.R.{streamName}";
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal Exception? ProcessStreamRestore(Account acc, string streamName, JsApiStreamRestoreRequest request)
|
|
{
|
|
_ = request;
|
|
var (stream, lookupErr) = acc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
return lookupErr ?? new InvalidOperationException("stream not found");
|
|
|
|
stream.UpdateConfig(stream.GetConfig());
|
|
return null;
|
|
}
|
|
|
|
internal void JsStreamSnapshotRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiStreamSnapshotResponse { Type = JsApiSubjects.JsApiStreamSnapshotResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiStreamSnapshotRequest();
|
|
if (JetStreamApi.IsJSONObjectOrArray(msg) &&
|
|
UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var streamName = StreamNameFromSubject(subject);
|
|
var (stream, lookupErr) = requestAcc.LookupStream(streamName);
|
|
if (lookupErr is not null || stream is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var (info, snapErr) = StreamSnapshot(stream, request);
|
|
if (snapErr is not null || info is null)
|
|
{
|
|
response.Error = JsApiErrors.NewJSStreamSnapshotError(snapErr ?? new InvalidOperationException("snapshot failed"));
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Config = info.Config;
|
|
response.State = info.State;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal (StreamInfo? Info, Exception? Error) StreamSnapshot(NatsStream stream, JsApiStreamSnapshotRequest request)
|
|
{
|
|
if (stream.Store is null)
|
|
return (null, new InvalidOperationException("stream store not initialized"));
|
|
|
|
var (_, err) = stream.Store.Snapshot(TimeSpan.FromSeconds(30), !request.NoConsumers, request.CheckMsgs);
|
|
if (err is not null)
|
|
return (null, err);
|
|
return (stream.GetInfo(), null);
|
|
}
|
|
|
|
internal void JsConsumerCreateRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerCreateResponse { Type = JsApiSubjects.JsApiConsumerCreateResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Error = JsApiErrors.NewJSClusterUnSupportFeatureError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerNamesRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerNamesResponse
|
|
{
|
|
Type = JsApiSubjects.JsApiConsumerNamesResponseType,
|
|
Consumers = [],
|
|
Limit = JsApiSubjects.JsApiNamesLimit,
|
|
Total = 0,
|
|
Offset = 0,
|
|
};
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerListRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerListResponse
|
|
{
|
|
Type = JsApiSubjects.JsApiConsumerListResponseType,
|
|
Consumers = [],
|
|
Limit = JsApiSubjects.JsApiListLimit,
|
|
Total = 0,
|
|
Offset = 0,
|
|
};
|
|
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerInfoRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerInfoResponse { Type = JsApiSubjects.JsApiConsumerInfoResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Error = JsApiErrors.NewJSConsumerNotFoundError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerDeleteRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerDeleteResponse { Type = JsApiSubjects.JsApiConsumerDeleteResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Success = true;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal void JsConsumerPauseRequest(ClientConnection c, Account acc, string subject, string reply, byte[] rawMessage)
|
|
{
|
|
var (ci, requestAcc, hdr, msg, err) = GetRequestInfo(c, rawMessage);
|
|
if (err is not null || requestAcc is null)
|
|
return;
|
|
|
|
var response = new JsApiConsumerPauseResponse { Type = JsApiSubjects.JsApiConsumerPauseResponseType };
|
|
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiHeader(hdr)))
|
|
{
|
|
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
var request = new JsApiConsumerPauseRequest();
|
|
if (UnmarshalRequest(c, requestAcc, subject, msg, request) is Exception decodeErr)
|
|
{
|
|
response.Error = JsApiErrors.NewJSInvalidJSONError(decodeErr);
|
|
SendAPIErrResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
return;
|
|
}
|
|
|
|
response.Paused = request.PauseUntil > DateTime.UtcNow;
|
|
response.PauseUntil = request.PauseUntil;
|
|
response.PauseRemaining = request.PauseUntil <= DateTime.UtcNow ? TimeSpan.Zero : request.PauseUntil - DateTime.UtcNow;
|
|
SendAPIResponse(ci, requestAcc, subject, reply, Encoding.UTF8.GetString(msg), JsonResponse(response));
|
|
}
|
|
|
|
internal static bool IsEmptyRequest(byte[] request) => JetStreamApi.IsEmptyRequest(request);
|
|
|
|
internal (string PreferredLeader, JsApiError? Error) GetStepDownPreferredPlacement(IRaftNode group, Placement? placement)
|
|
{
|
|
if (placement is null)
|
|
return (string.Empty, null);
|
|
|
|
if (!string.IsNullOrWhiteSpace(placement.Preferred))
|
|
{
|
|
foreach (var peer in group.Peers())
|
|
{
|
|
var info = GetNodeInfo(peer.Id);
|
|
if (info is not null && string.Equals(info.Name, placement.Preferred, StringComparison.Ordinal))
|
|
{
|
|
if (peer.Id == group.ID())
|
|
{
|
|
return (string.Empty, JsApiErrors.NewJSClusterNoPeersError(
|
|
new InvalidOperationException($"preferred server '{placement.Preferred}' is already leader")));
|
|
}
|
|
|
|
return (peer.Id, null);
|
|
}
|
|
}
|
|
|
|
return (string.Empty, JsApiErrors.NewJSClusterNoPeersError(
|
|
new InvalidOperationException($"preferred server '{placement.Preferred}' not known")));
|
|
}
|
|
|
|
foreach (var peer in group.Peers())
|
|
{
|
|
if (peer.Id == group.ID())
|
|
continue;
|
|
|
|
var info = GetNodeInfo(peer.Id);
|
|
if (info is null || info.Offline)
|
|
continue;
|
|
|
|
if (!string.IsNullOrWhiteSpace(placement.Cluster) &&
|
|
!string.Equals(info.Cluster, placement.Cluster, StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (placement.Tags is { Length: > 0 } &&
|
|
placement.Tags.Except(info.Tags, StringComparer.Ordinal).Any())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return (peer.Id, null);
|
|
}
|
|
|
|
return (string.Empty, JsApiErrors.NewJSClusterNoPeersError(new InvalidOperationException("no replacement peer connected")));
|
|
}
|
|
|
|
private static string? GetRequiredApiHeader(byte[] header)
|
|
{
|
|
var value = NatsMessageHeaders.GetHeader(JsApiSubjects.JsRequiredApiLevel, header);
|
|
return value is null ? null : Encoding.ASCII.GetString(value);
|
|
}
|
|
|
|
internal static string StreamNameFromSubject(string subject) => JetStreamApi.StreamNameFromSubject(subject);
|
|
|
|
internal static string ConsumerNameFromSubject(string subject) => JetStreamApi.ConsumerNameFromSubject(subject);
|
|
|
|
internal string JsonResponse(object response)
|
|
{
|
|
try
|
|
{
|
|
return JsonSerializer.Serialize(response);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Warnf("Problem marshaling JSON for JetStream API: {0}", ex.Message);
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
internal void SendJetStreamAPIAuditAdvisory(ClientInfo? ci, Account acc, string subject, string request, string response)
|
|
{
|
|
_ = ci;
|
|
_ = acc;
|
|
_ = subject;
|
|
_ = request;
|
|
_ = response;
|
|
}
|
|
|
|
private Exception? SendInternalAccountMsgWithReply(Account account, string subject, string reply, byte[]? header, string response, bool trackApi)
|
|
{
|
|
_ = trackApi;
|
|
|
|
try
|
|
{
|
|
var sendQueue = account.GetSendQueue() ?? NewSendQueue(account);
|
|
SendQueue.Send(sendQueue, subject, reply, header ?? [], Encoding.UTF8.GetBytes(response));
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return ex;
|
|
}
|
|
}
|
|
}
|