| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632 |
- #if !BESTHTTP_DISABLE_SIGNALR_CORE
- using System.Threading;
- #if CSHARP_7_OR_LATER
- using System.Threading.Tasks;
- #endif
- using BestHTTP.Futures;
- using BestHTTP.SignalRCore.Authentication;
- using BestHTTP.SignalRCore.Messages;
- using System;
- using System.Collections.Generic;
- using BestHTTP.Logger;
- using System.Collections.Concurrent;
- using BestHTTP.PlatformSupport.Threading;
- namespace BestHTTP.SignalRCore
- {
- public sealed class HubConnection : BestHTTP.Extensions.IHeartbeat
- {
- public static readonly object[] EmptyArgs = new object[0];
- /// <summary>
- /// Uri of the Hub endpoint
- /// </summary>
- public Uri Uri { get; private set; }
- /// <summary>
- /// Current state of this connection.
- /// </summary>
- public ConnectionStates State {
- get { return (ConnectionStates)this._state; }
- private set {
- Interlocked.Exchange(ref this._state, (int)value);
- }
- }
- private volatile int _state;
- /// <summary>
- /// Current, active ITransport instance.
- /// </summary>
- public ITransport Transport { get; private set; }
- /// <summary>
- /// The IProtocol implementation that will parse, encode and decode messages.
- /// </summary>
- public IProtocol Protocol { get; private set; }
- /// <summary>
- /// This event is called when the connection is redirected to a new uri.
- /// </summary>
- public event Action<HubConnection, Uri, Uri> OnRedirected;
- /// <summary>
- /// This event is called when successfully connected to the hub.
- /// </summary>
- public event Action<HubConnection> OnConnected;
- /// <summary>
- /// This event is called when an unexpected error happen and the connection is closed.
- /// </summary>
- public event Action<HubConnection, string> OnError;
- /// <summary>
- /// This event is called when the connection is gracefully terminated.
- /// </summary>
- public event Action<HubConnection> OnClosed;
- /// <summary>
- /// This event is called for every server-sent message. When returns false, no further processing of the message is done by the plugin.
- /// </summary>
- public event Func<HubConnection, Message, bool> OnMessage;
- /// <summary>
- /// Called when the HubConnection start its reconnection process after loosing its underlying connection.
- /// </summary>
- public event Action<HubConnection, string> OnReconnecting;
- /// <summary>
- /// Called after a successful reconnection.
- /// </summary>
- public event Action<HubConnection> OnReconnected;
- /// <summary>
- /// Called for transport related events.
- /// </summary>
- public event Action<HubConnection, ITransport, TransportEvents> OnTransportEvent;
- /// <summary>
- /// An IAuthenticationProvider implementation that will be used to authenticate the connection.
- /// </summary>
- public IAuthenticationProvider AuthenticationProvider { get; set; }
- /// <summary>
- /// Negotiation response sent by the server.
- /// </summary>
- public NegotiationResult NegotiationResult { get; private set; }
- /// <summary>
- /// Options that has been used to create the HubConnection.
- /// </summary>
- public HubOptions Options { get; private set; }
- /// <summary>
- /// How many times this connection is redirected.
- /// </summary>
- public int RedirectCount { get; private set; }
- /// <summary>
- /// The reconnect policy that will be used when the underlying connection is lost. Its default value is null.
- /// </summary>
- public IRetryPolicy ReconnectPolicy { get; set; }
- /// <summary>
- /// Logging context of this HubConnection instance.
- /// </summary>
- public LoggingContext Context { get; private set; }
- /// <summary>
- /// This will be increment to add a unique id to every message the plugin will send.
- /// </summary>
- private long lastInvocationId = 1;
- /// <summary>
- /// Id of the last streaming parameter.
- /// </summary>
- private int lastStreamId = 1;
- /// <summary>
- /// Store the callback for all sent message that expect a return value from the server. All sent message has
- /// a unique invocationId that will be sent back from the server.
- /// </summary>
- private ConcurrentDictionary<long, InvocationDefinition> invocations = new ConcurrentDictionary<long, InvocationDefinition>();
- /// <summary>
- /// This is where we store the methodname => callback mapping.
- /// </summary>
- private ConcurrentDictionary<string, Subscription> subscriptions = new ConcurrentDictionary<string, Subscription>(StringComparer.OrdinalIgnoreCase);
- /// <summary>
- /// When we sent out the last message to the server.
- /// </summary>
- private DateTime lastMessageSentAt;
- private DateTime lastMessageReceivedAt;
- private DateTime connectionStartedAt;
- private RetryContext currentContext;
- private DateTime reconnectStartTime = DateTime.MinValue;
- private DateTime reconnectAt;
- private List<TransportTypes> triedoutTransports = new List<TransportTypes>();
- private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
- private bool pausedInLastFrame;
- public HubConnection(Uri hubUri, IProtocol protocol)
- : this(hubUri, protocol, new HubOptions())
- {
- }
- public HubConnection(Uri hubUri, IProtocol protocol, HubOptions options)
- {
- this.Context = new LoggingContext(this);
- this.Uri = hubUri;
- this.State = ConnectionStates.Initial;
- this.Options = options;
- this.Protocol = protocol;
- this.Protocol.Connection = this;
- this.AuthenticationProvider = new DefaultAccessTokenAuthenticator(this);
- }
- public void StartConnect()
- {
- if (this.State != ConnectionStates.Initial &&
- this.State != ConnectionStates.Redirected &&
- this.State != ConnectionStates.Reconnecting)
- {
- HTTPManager.Logger.Warning("HubConnection", "StartConnect - Expected Initial or Redirected state, got " + this.State.ToString(), this.Context);
- return;
- }
- if (this.State == ConnectionStates.Initial)
- {
- this.connectionStartedAt = DateTime.Now;
- HTTPManager.Heartbeats.Subscribe(this);
- }
- HTTPManager.Logger.Verbose("HubConnection", $"StartConnect State: {this.State}, connectionStartedAt: {this.connectionStartedAt.ToString(System.Globalization.CultureInfo.InvariantCulture)}", this.Context);
- if (this.AuthenticationProvider != null && this.AuthenticationProvider.IsPreAuthRequired)
- {
- HTTPManager.Logger.Information("HubConnection", "StartConnect - Authenticating", this.Context);
- SetState(ConnectionStates.Authenticating, null, this.defaultReconnect);
- this.AuthenticationProvider.OnAuthenticationSucceded += OnAuthenticationSucceded;
- this.AuthenticationProvider.OnAuthenticationFailed += OnAuthenticationFailed;
- // Start the authentication process
- this.AuthenticationProvider.StartAuthentication();
- }
- else
- StartNegotiation();
- }
- #if CSHARP_7_OR_LATER
- TaskCompletionSource<HubConnection> connectAsyncTaskCompletionSource;
- public Task<HubConnection> ConnectAsync()
- {
- if (this.State != ConnectionStates.Initial && this.State != ConnectionStates.Redirected && this.State != ConnectionStates.Reconnecting)
- throw new Exception("HubConnection - ConnectAsync - Expected Initial or Redirected state, got " + this.State.ToString());
- if (this.connectAsyncTaskCompletionSource != null)
- throw new Exception("Connect process already started!");
- this.connectAsyncTaskCompletionSource = new TaskCompletionSource<HubConnection>();
- this.OnConnected += OnAsyncConnectedCallback;
- this.OnError += OnAsyncConnectFailedCallback;
- this.StartConnect();
- return connectAsyncTaskCompletionSource.Task;
- }
- private void OnAsyncConnectedCallback(HubConnection hub)
- {
- this.OnConnected -= OnAsyncConnectedCallback;
- this.OnError -= OnAsyncConnectFailedCallback;
- this.connectAsyncTaskCompletionSource.TrySetResult(this);
- this.connectAsyncTaskCompletionSource = null;
- }
- private void OnAsyncConnectFailedCallback(HubConnection hub, string error)
- {
- this.OnConnected -= OnAsyncConnectedCallback;
- this.OnError -= OnAsyncConnectFailedCallback;
- this.connectAsyncTaskCompletionSource.TrySetException(new Exception(error));
- this.connectAsyncTaskCompletionSource = null;
- }
- #endif
- private void OnAuthenticationSucceded(IAuthenticationProvider provider)
- {
- HTTPManager.Logger.Verbose("HubConnection", "OnAuthenticationSucceded", this.Context);
- this.AuthenticationProvider.OnAuthenticationSucceded -= OnAuthenticationSucceded;
- this.AuthenticationProvider.OnAuthenticationFailed -= OnAuthenticationFailed;
- StartNegotiation();
- }
- private void OnAuthenticationFailed(IAuthenticationProvider provider, string reason)
- {
- HTTPManager.Logger.Error("HubConnection", "OnAuthenticationFailed: " + reason, this.Context);
- this.AuthenticationProvider.OnAuthenticationSucceded -= OnAuthenticationSucceded;
- this.AuthenticationProvider.OnAuthenticationFailed -= OnAuthenticationFailed;
- SetState(ConnectionStates.Closed, reason, this.defaultReconnect);
- }
- private void StartNegotiation()
- {
- HTTPManager.Logger.Verbose("HubConnection", "StartNegotiation", this.Context);
- if (this.State == ConnectionStates.CloseInitiated)
- {
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- return;
- }
- #if !BESTHTTP_DISABLE_WEBSOCKET
- if (this.Options.SkipNegotiation && this.Options.PreferedTransport == TransportTypes.WebSocket)
- {
- HTTPManager.Logger.Verbose("HubConnection", "Skipping negotiation", this.Context);
- ConnectImpl(this.Options.PreferedTransport);
- return;
- }
- #endif
- SetState(ConnectionStates.Negotiating, null, this.defaultReconnect);
- // https://github.com/dotnet/aspnetcore/blob/master/src/SignalR/docs/specs/TransportProtocols.md#post-endpoint-basenegotiate-request
- // Send out a negotiation request. While we could skip it and connect right with the websocket transport
- // it might return with additional information that could be useful.
- UriBuilder builder = new UriBuilder(this.Uri);
- if (builder.Path.EndsWith("/"))
- builder.Path += "negotiate";
- else
- builder.Path += "/negotiate";
- string query = builder.Query;
- if (string.IsNullOrEmpty(query))
- query = "negotiateVersion=1";
- else
- query = query.Remove(0, 1) + "&negotiateVersion=1";
- builder.Query = query;
- var request = new HTTPRequest(builder.Uri, HTTPMethods.Post, OnNegotiationRequestFinished);
- request.Context.Add("Hub", this.Context);
- if (this.AuthenticationProvider != null)
- this.AuthenticationProvider.PrepareRequest(request);
- request.Send();
- }
-
- private void ConnectImpl(TransportTypes transport)
- {
- HTTPManager.Logger.Verbose("HubConnection", "ConnectImpl - " + transport, this.Context);
- switch (transport)
- {
- #if !BESTHTTP_DISABLE_WEBSOCKET
- case TransportTypes.WebSocket:
- if (this.NegotiationResult != null && !IsTransportSupported("WebSockets"))
- {
- SetState(ConnectionStates.Closed, "Couldn't use preferred transport, as the 'WebSockets' transport isn't supported by the server!", this.defaultReconnect);
- return;
- }
- this.Transport = new Transports.WebSocketTransport(this);
- this.Transport.OnStateChanged += Transport_OnStateChanged;
- break;
- #endif
- case TransportTypes.LongPolling:
- if (this.NegotiationResult != null && !IsTransportSupported("LongPolling"))
- {
- SetState(ConnectionStates.Closed, "Couldn't use preferred transport, as the 'LongPolling' transport isn't supported by the server!", this.defaultReconnect);
- return;
- }
- this.Transport = new Transports.LongPollingTransport(this);
- this.Transport.OnStateChanged += Transport_OnStateChanged;
- break;
- default:
- SetState(ConnectionStates.Closed, "Unsupported transport: " + transport, this.defaultReconnect);
- break;
- }
- try
- {
- if (this.OnTransportEvent != null)
- this.OnTransportEvent(this, this.Transport, TransportEvents.SelectedToConnect);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "ConnectImpl - OnTransportEvent exception in user code!", ex, this.Context);
- }
- this.Transport.StartConnect();
- }
- private bool IsTransportSupported(string transportName)
- {
- // https://github.com/dotnet/aspnetcore/blob/master/src/SignalR/docs/specs/TransportProtocols.md#post-endpoint-basenegotiate-request
- // If the negotiation response contains only the url and accessToken, no 'availableTransports' list is sent
- if (this.NegotiationResult.SupportedTransports == null)
- return true;
- for (int i = 0; i < this.NegotiationResult.SupportedTransports.Count; ++i)
- if (this.NegotiationResult.SupportedTransports[i].Name.Equals(transportName, StringComparison.OrdinalIgnoreCase))
- return true;
- return false;
- }
- private void OnNegotiationRequestFinished(HTTPRequest req, HTTPResponse resp)
- {
- if (this.State == ConnectionStates.Closed)
- return;
- if (this.State == ConnectionStates.CloseInitiated)
- {
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- return;
- }
- string errorReason = null;
- switch (req.State)
- {
- // The request finished without any problem.
- case HTTPRequestStates.Finished:
- if (resp.IsSuccess)
- {
- HTTPManager.Logger.Information("HubConnection", "Negotiation Request Finished Successfully! Response: " + resp.DataAsText, this.Context);
- // Parse negotiation
- this.NegotiationResult = NegotiationResult.Parse(resp, out errorReason, this);
- // Room for improvement: check validity of the negotiation result:
- // If url and accessToken is present, the other two must be null.
- // https://github.com/dotnet/aspnetcore/blob/master/src/SignalR/docs/specs/TransportProtocols.md#post-endpoint-basenegotiate-request
- if (string.IsNullOrEmpty(errorReason))
- {
- if (this.NegotiationResult.Url != null)
- {
- this.SetState(ConnectionStates.Redirected, null, this.defaultReconnect);
- if (++this.RedirectCount >= this.Options.MaxRedirects)
- errorReason = string.Format("MaxRedirects ({0:N0}) reached!", this.Options.MaxRedirects);
- else
- {
- var oldUri = this.Uri;
- this.Uri = this.NegotiationResult.Url;
- if (this.OnRedirected != null)
- {
- try
- {
- this.OnRedirected(this, oldUri, Uri);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnNegotiationRequestFinished - OnRedirected", ex, this.Context);
- }
- }
- StartConnect();
- }
- }
- else
- ConnectImpl(this.Options.PreferedTransport);
- }
- }
- else // Internal server error?
- errorReason = string.Format("Negotiation Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
- resp.StatusCode,
- resp.Message,
- resp.DataAsText);
- break;
- // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
- case HTTPRequestStates.Error:
- errorReason = "Negotiation Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
- break;
- // The request aborted, initiated by the user.
- case HTTPRequestStates.Aborted:
- errorReason = "Negotiation Request Aborted!";
- break;
- // Connecting to the server is timed out.
- case HTTPRequestStates.ConnectionTimedOut:
- errorReason = "Negotiation Request - Connection Timed Out!";
- break;
- // The request didn't finished in the given time.
- case HTTPRequestStates.TimedOut:
- errorReason = "Negotiation Request - Processing the request Timed Out!";
- break;
- }
- if (errorReason != null)
- {
- if (this.ReconnectPolicy != null)
- {
- RetryContext context = new RetryContext
- {
- ElapsedTime = DateTime.Now - this.connectionStartedAt,
- PreviousRetryCount = this.currentContext.PreviousRetryCount,
- RetryReason = errorReason
- };
- TimeSpan? nextAttempt = null;
- try
- {
- nextAttempt = this.ReconnectPolicy.GetNextRetryDelay(context);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "ReconnectPolicy.GetNextRetryDelay", ex, this.Context);
- }
- if (nextAttempt == null)
- {
- this.NegotiationResult = new NegotiationResult();
- this.NegotiationResult.NegotiationResponse = resp;
- SetState(ConnectionStates.Closed, errorReason, this.defaultReconnect);
- }
- else
- {
- HTTPManager.Logger.Information("HubConnection", "Next reconnect attempt after " + nextAttempt.Value.ToString(), this.Context);
- this.currentContext = context;
- this.currentContext.PreviousRetryCount += 1;
- this.reconnectAt = DateTime.Now + nextAttempt.Value;
- this.SetState(ConnectionStates.Reconnecting, null, this.defaultReconnect);
- }
- }
- else
- {
- this.NegotiationResult = new NegotiationResult();
- this.NegotiationResult.NegotiationResponse = resp;
- SetState(ConnectionStates.Closed, errorReason, this.defaultReconnect);
- }
- }
- }
- public void StartClose()
- {
- HTTPManager.Logger.Verbose("HubConnection", "StartClose", this.Context);
- this.defaultReconnect = false;
- switch(this.State)
- {
- case ConnectionStates.Initial:
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- break;
- case ConnectionStates.Authenticating:
- this.AuthenticationProvider.OnAuthenticationSucceded -= OnAuthenticationSucceded;
- this.AuthenticationProvider.OnAuthenticationFailed -= OnAuthenticationFailed;
- this.AuthenticationProvider.Cancel();
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- break;
- case ConnectionStates.Reconnecting:
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- break;
- case ConnectionStates.CloseInitiated:
- case ConnectionStates.Closed:
- // Already initiated/closed
- break;
- default:
- if (HTTPManager.IsQuitting)
- {
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- }
- else
- {
- SetState(ConnectionStates.CloseInitiated, null, this.defaultReconnect);
- if (this.Transport != null)
- this.Transport.StartClose();
- }
- break;
- }
- }
- #if CSHARP_7_OR_LATER
- TaskCompletionSource<HubConnection> closeAsyncTaskCompletionSource;
- public Task<HubConnection> CloseAsync()
- {
- if (this.closeAsyncTaskCompletionSource != null)
- throw new Exception("CloseAsync already called!");
- this.closeAsyncTaskCompletionSource = new TaskCompletionSource<HubConnection>();
- this.OnClosed += OnClosedAsyncCallback;
- this.OnError += OnClosedAsyncErrorCallback;
- // Avoid race condition by caching task prior to StartClose,
- // which asynchronously calls OnClosedAsyncCallback, which nulls
- // this.closeAsyncTaskCompletionSource immediately before we have
- // a chance to read from it.
- var task = this.closeAsyncTaskCompletionSource.Task;
- this.StartClose();
- return task;
- }
- void OnClosedAsyncCallback(HubConnection hub)
- {
- this.OnClosed -= OnClosedAsyncCallback;
- this.OnError -= OnClosedAsyncErrorCallback;
- this.closeAsyncTaskCompletionSource.TrySetResult(this);
- this.closeAsyncTaskCompletionSource = null;
- }
- void OnClosedAsyncErrorCallback(HubConnection hub, string error)
- {
- this.OnClosed -= OnClosedAsyncCallback;
- this.OnError -= OnClosedAsyncErrorCallback;
- this.closeAsyncTaskCompletionSource.TrySetException(new Exception(error));
- this.closeAsyncTaskCompletionSource = null;
- }
- #endif
- public IFuture<TResult> Invoke<TResult>(string target, params object[] args)
- {
- Future<TResult> future = new Future<TResult>();
- try
- {
- long id = InvokeImp(target,
- args,
- (message) =>
- {
- bool isSuccess = string.IsNullOrEmpty(message.error);
- if (isSuccess)
- future.Assign((TResult)this.Protocol.ConvertTo(typeof(TResult), message.result));
- else
- future.Fail(new Exception(message.error));
- },
- typeof(TResult));
- if (id < 0)
- future.Fail(new Exception("Not in Connected state! Current state: " + this.State));
- }
- catch(Exception ex)
- {
- future.Fail(ex);
- }
- return future;
- }
- #if CSHARP_7_OR_LATER
- public Task<TResult> InvokeAsync<TResult>(string target, params object[] args)
- {
- return InvokeAsync<TResult>(target, default(CancellationToken), args);
- }
- public Task<TResult> InvokeAsync<TResult>(string target, CancellationToken cancellationToken = default, params object[] args)
- {
- TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
- try
- {
- long id = InvokeImp(target,
- args,
- (message) =>
- {
- if (cancellationToken.IsCancellationRequested)
- {
- tcs.TrySetCanceled(cancellationToken);
- return;
- }
- bool isSuccess = string.IsNullOrEmpty(message.error);
- if (isSuccess)
- tcs.TrySetResult((TResult)this.Protocol.ConvertTo(typeof(TResult), message.result));
- else
- tcs.TrySetException(new Exception(message.error));
- },
- typeof(TResult));
- if (id < 0)
- tcs.TrySetException(new Exception("Not in Connected state! Current state: " + this.State));
- else
- cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
- }
- catch (Exception ex)
- {
- tcs.SetException(ex);
- }
- return tcs.Task;
- }
- #endif
- public IFuture<object> Send(string target, params object[] args)
- {
- Future<object> future = new Future<object>();
- try
- {
- long id = InvokeImp(target,
- args,
- (message) =>
- {
- bool isSuccess = string.IsNullOrEmpty(message.error);
- if (isSuccess)
- future.Assign(message.item);
- else
- future.Fail(new Exception(message.error));
- },
- typeof(object));
- if (id < 0)
- future.Fail(new Exception("Not in Connected state! Current state: " + this.State));
- }
- catch (Exception ex)
- {
- future.Fail(ex);
- }
- return future;
- }
- #if CSHARP_7_OR_LATER
- public Task<object> SendAsync(string target, params object[] args)
- {
- return SendAsync(target, default(CancellationToken), args);
- }
- public Task<object> SendAsync(string target, CancellationToken cancellationToken = default, params object[] args)
- {
- TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
- try
- {
- long id = InvokeImp(target,
- args,
- (message) =>
- {
- if (cancellationToken.IsCancellationRequested)
- {
- tcs.TrySetCanceled(cancellationToken);
- return;
- }
- bool isSuccess = string.IsNullOrEmpty(message.error);
- if (isSuccess)
- tcs.TrySetResult(message.item);
- else
- tcs.TrySetException(new Exception(message.error));
- },
- typeof(object));
- if (id < 0)
- tcs.TrySetException(new Exception("Not in Connected state! Current state: " + this.State));
- else
- cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
- }
- catch (Exception ex)
- {
- tcs.TrySetException(ex);
- }
- return tcs.Task;
- }
- #endif
- private long InvokeImp(string target, object[] args, Action<Message> callback, Type itemType, bool isStreamingInvocation = false)
- {
- if (this.State != ConnectionStates.Connected)
- return -1;
- bool blockingInvocation = callback == null;
- long invocationId = blockingInvocation ? 0 : System.Threading.Interlocked.Increment(ref this.lastInvocationId);
- var message = new Message
- {
- type = isStreamingInvocation ? MessageTypes.StreamInvocation : MessageTypes.Invocation,
- invocationId = blockingInvocation ? null : invocationId.ToString(),
- target = target,
- arguments = args,
- nonblocking = callback == null,
- };
- SendMessage(message);
- if (!blockingInvocation)
- if (!this.invocations.TryAdd(invocationId, new InvocationDefinition { callback = callback, returnType = itemType }))
- HTTPManager.Logger.Warning("HubConnection", "InvokeImp - invocations already contains id: " + invocationId, this.Context);
- return invocationId;
- }
- internal void SendMessage(Message message)
- {
- // https://github.com/Benedicht/BestHTTP-Issues/issues/146
- if (this.State == ConnectionStates.Closed)
- return;
- if (HTTPManager.Logger.Level == Logger.Loglevels.All)
- HTTPManager.Logger.Verbose("HubConnection", "SendMessage: " + message.ToString(), this.Context);
- try
- {
- using (new WriteLock(this.rwLock))
- {
- var encoded = this.Protocol.EncodeMessage(message);
- if (encoded.Data != null)
- {
- this.lastMessageSentAt = DateTime.Now;
- this.Transport.Send(encoded);
- }
- }
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "SendMessage", ex, this.Context);
- throw;
- }
- }
- public DownStreamItemController<TDown> GetDownStreamController<TDown>(string target, params object[] args)
- {
- long invocationId = System.Threading.Interlocked.Increment(ref this.lastInvocationId);
- var future = new Future<TDown>();
- future.BeginProcess();
- var controller = new DownStreamItemController<TDown>(this, invocationId, future);
- Action<Message> callback = (Message msg) =>
- {
- switch (msg.type)
- {
- // StreamItem message contains only one item.
- case MessageTypes.StreamItem:
- {
- if (controller.IsCanceled)
- break;
- TDown item = (TDown)this.Protocol.ConvertTo(typeof(TDown), msg.item);
- future.AssignItem(item);
- break;
- }
- case MessageTypes.Completion:
- {
- bool isSuccess = string.IsNullOrEmpty(msg.error);
- if (isSuccess)
- {
- // While completion message must not contain any result, this should be future-proof
- if (!controller.IsCanceled && msg.result != null)
- {
- TDown result = (TDown)this.Protocol.ConvertTo(typeof(TDown), msg.result);
- future.AssignItem(result);
- }
- future.Finish();
- }
- else
- future.Fail(new Exception(msg.error));
- break;
- }
- }
- };
-
- var message = new Message
- {
- type = MessageTypes.StreamInvocation,
- invocationId = invocationId.ToString(),
- target = target,
- arguments = args,
- nonblocking = false,
- };
- try
- {
- SendMessage(message);
- }
- catch(Exception ex)
- {
- future.Fail(ex);
- }
- if (callback != null)
- if (!this.invocations.TryAdd(invocationId, new InvocationDefinition { callback = callback, returnType = typeof(TDown) }))
- HTTPManager.Logger.Warning("HubConnection", "GetDownStreamController - invocations already contains id: " + invocationId, this.Context);
- return controller;
- }
- public UpStreamItemController<TResult> GetUpStreamController<TResult>(string target, int paramCount, bool downStream, object[] args)
- {
- Future<TResult> future = new Future<TResult>();
- future.BeginProcess();
- long invocationId = System.Threading.Interlocked.Increment(ref this.lastInvocationId);
- string[] streamIds = new string[paramCount];
- for (int i = 0; i < paramCount; i++)
- streamIds[i] = System.Threading.Interlocked.Increment(ref this.lastStreamId).ToString();
- var controller = new UpStreamItemController<TResult>(this, invocationId, streamIds, future);
- Action<Message> callback = (Message msg) => {
- switch (msg.type)
- {
- // StreamItem message contains only one item.
- case MessageTypes.StreamItem:
- {
- if (controller.IsCanceled)
- break;
- TResult item = (TResult)this.Protocol.ConvertTo(typeof(TResult), msg.item);
- future.AssignItem(item);
- break;
- }
- case MessageTypes.Completion:
- {
- bool isSuccess = string.IsNullOrEmpty(msg.error);
- if (isSuccess)
- {
- // While completion message must not contain any result, this should be future-proof
- if (!controller.IsCanceled && msg.result != null)
- {
- TResult result = (TResult)this.Protocol.ConvertTo(typeof(TResult), msg.result);
- future.AssignItem(result);
- }
- future.Finish();
- }
- else
- {
- var ex = new Exception(msg.error);
- future.Fail(ex);
- }
- break;
- }
- }
- };
- var messageToSend = new Message
- {
- type = downStream ? MessageTypes.StreamInvocation : MessageTypes.Invocation,
- invocationId = invocationId.ToString(),
- target = target,
- arguments = args,
- streamIds = streamIds,
- nonblocking = false,
- };
- try
- {
- SendMessage(messageToSend);
- }
- catch(Exception ex)
- {
- future.Fail(ex);
- }
- if (!this.invocations.TryAdd(invocationId, new InvocationDefinition { callback = callback, returnType = typeof(TResult) }))
- HTTPManager.Logger.Warning("HubConnection", "GetUpStreamController - invocations already contains id: " + invocationId, this.Context);
- return controller;
- }
- public void On(string methodName, Action callback)
- {
- On(methodName, null, (args) => callback());
- }
- public void On<T1>(string methodName, Action<T1> callback)
- {
- On(methodName, new Type[] { typeof(T1) }, (args) => callback((T1)args[0]));
- }
- public void On<T1, T2>(string methodName, Action<T1, T2> callback)
- {
- On(methodName,
- new Type[] { typeof(T1), typeof(T2) },
- (args) => callback((T1)args[0], (T2)args[1]));
- }
- public void On<T1, T2, T3>(string methodName, Action<T1, T2, T3> callback)
- {
- On(methodName,
- new Type[] { typeof(T1), typeof(T2), typeof(T3) },
- (args) => callback((T1)args[0], (T2)args[1], (T3)args[2]));
- }
- public void On<T1, T2, T3, T4>(string methodName, Action<T1, T2, T3, T4> callback)
- {
- On(methodName,
- new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) },
- (args) => callback((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3]));
- }
- private void On(string methodName, Type[] paramTypes, Action<object[]> callback)
- {
- this.subscriptions.GetOrAdd(methodName, _ => new Subscription())
- .Add(paramTypes, callback);
- }
- public void On<Result>(string methodName, Func<Result> callback)
- {
- OnFunc<Result>(methodName, null, (args) => callback());
- }
- public void On<T1, Result>(string methodName, Func<T1, Result> callback)
- {
- OnFunc<Result>(methodName, new Type[] { typeof(T1) }, (args) => callback((T1)args[0]));
- }
- public void On<T1, T2, Result>(string methodName, Func<T1, T2, Result> callback)
- {
- OnFunc<Result>(methodName, new Type[] { typeof(T1), typeof(T2) }, (args) => callback((T1)args[0], (T2)args[1]));
- }
- public void On<T1, T2, T3, Result>(string methodName, Func<T1, T2, T3, Result> callback)
- {
- OnFunc<Result>(methodName, new Type[] { typeof(T1), typeof(T2), typeof(T3) }, (args) => callback((T1)args[0], (T2)args[1], (T3)args[2]));
- }
- public void On<T1, T2, T3, T4, Result>(string methodName, Func<T1, T2, T3, T4, Result> callback)
- {
- OnFunc<Result>(methodName, new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, (args) => callback((T1)args[0], (T2)args[1], (T3)args[2], (T4)args[3]));
- }
- // https://github.com/dotnet/aspnetcore/issues/5280
- private void OnFunc<Result>(string methodName, Type[] paramTypes, Func<object[], object> callback)
- {
- this.subscriptions.GetOrAdd(methodName, _ => new Subscription())
- .AddFunc(typeof(Result), paramTypes, callback);
- }
- /// <summary>
- /// Remove all event handlers for <paramref name="methodName"/> that subscribed with an On call.
- /// </summary>
- public void Remove(string methodName)
- {
- Subscription _;
- this.subscriptions.TryRemove(methodName, out _);
- }
- internal Subscription GetSubscription(string methodName)
- {
- Subscription subscribtion = null;
- this.subscriptions.TryGetValue(methodName, out subscribtion);
- return subscribtion;
- }
- internal Type GetItemType(long invocationId)
- {
- InvocationDefinition def;
- this.invocations.TryGetValue(invocationId, out def);
- return def.returnType;
- }
- List<Message> delayedMessages;
- internal void OnMessages(List<Message> messages)
- {
- this.lastMessageReceivedAt = DateTime.Now;
- if (pausedInLastFrame)
- {
- if (this.delayedMessages == null)
- this.delayedMessages = new List<Message>(messages.Count);
- foreach(var msg in messages)
- delayedMessages.Add(msg);
- messages.Clear();
- }
- for (int messageIdx = 0; messageIdx < messages.Count; ++messageIdx)
- {
- var message = messages[messageIdx];
- if (this.OnMessage != null)
- {
- try
- {
- if (!this.OnMessage(this, message))
- continue;
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnMessage user code!", ex, this.Context);
- }
- }
- switch (message.type)
- {
- case MessageTypes.Handshake:
- break;
- case MessageTypes.Invocation:
- {
- Subscription subscribtion = null;
- if (this.subscriptions.TryGetValue(message.target, out subscribtion))
- {
- if (subscribtion.callbacks?.Count == 0 && subscribtion.functionCallbacks?.Count == 0)
- HTTPManager.Logger.Warning("HubConnection", $"No callback for invocation '{message.ToString()}'", this.Context);
- for (int i = 0; i < subscribtion.callbacks.Count; ++i)
- {
- var callbackDesc = subscribtion.callbacks[i];
- object[] realArgs = null;
- try
- {
- realArgs = this.Protocol.GetRealArguments(callbackDesc.ParamTypes, message.arguments);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnMessages - Invocation - GetRealArguments", ex, this.Context);
- }
- try
- {
- callbackDesc.Callback.Invoke(realArgs);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnMessages - Invocation - Invoke", ex, this.Context);
- }
- }
- if (subscribtion.functionCallbacks != null)
- {
- for (int i = 0; i < subscribtion.functionCallbacks.Count; ++i)
- {
- var callbackDesc = subscribtion.functionCallbacks[i];
- object[] realArgs = null;
- try
- {
- realArgs = this.Protocol.GetRealArguments(callbackDesc.ParamTypes, message.arguments);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnMessages - Function Invocation - GetRealArguments", ex, this.Context);
- }
- try
- {
- var result = callbackDesc.Callback(realArgs);
- if (result is Task task && task.GetType() is Type taskType && taskType.IsGenericType)
- {
- task.ContinueWith((t) =>
- {
- Exception error = null;
- try
- {
- if (t.IsCanceled || t.IsFaulted)
- {
- error = t.Exception.InnerException ?? new TaskCanceledException();
- }
- else
- {
- var prop = taskType.GetProperty("Result");
- var taskResult = prop.GetValue(t);
- SendMessage(new Message { type = MessageTypes.Completion, invocationId = message.invocationId, result = taskResult });
- }
- }
- catch (Exception ex)
- {
- error = ex;
- }
- if (error != null)
- SendMessage(new Message { type = MessageTypes.Completion, invocationId = message.invocationId, error = error.Message });
- });
- }
- else
- SendMessage(new Message { type = MessageTypes.Completion, invocationId = message.invocationId, result = result });
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnMessages - Function Invocation - Invoke", ex, this.Context);
- SendMessage(new Message { type = MessageTypes.Completion, invocationId = message.invocationId, error = ex.Message });
- }
- }
- }
- }
- else
- HTTPManager.Logger.Warning("HubConnection", $"No subscription could be found for invocation '{message.ToString()}'", this.Context);
- break;
- }
- case MessageTypes.StreamItem:
- {
- long invocationId;
- if (long.TryParse(message.invocationId, out invocationId))
- {
- InvocationDefinition def;
- if (this.invocations.TryGetValue(invocationId, out def) && def.callback != null)
- {
- try
- {
- def.callback(message);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnMessages - StreamItem - callback", ex, this.Context);
- }
- }
- }
- break;
- }
- case MessageTypes.Completion:
- {
- long invocationId;
- if (long.TryParse(message.invocationId, out invocationId))
- {
- InvocationDefinition def;
- if (this.invocations.TryRemove(invocationId, out def) && def.callback != null)
- {
- try
- {
- def.callback(message);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnMessages - Completion - callback", ex, this.Context);
- }
- }
- }
- break;
- }
- case MessageTypes.Ping:
- // Send back an answer
- SendMessage(new Message() { type = MessageTypes.Ping });
- break;
- case MessageTypes.Close:
- SetState(ConnectionStates.Closed, message.error, message.allowReconnect);
- if (this.Transport != null)
- this.Transport.StartClose();
- return;
- }
- }
- }
- private void Transport_OnStateChanged(TransportStates oldState, TransportStates newState)
- {
- HTTPManager.Logger.Verbose("HubConnection", string.Format("Transport_OnStateChanged - oldState: {0} newState: {1}", oldState.ToString(), newState.ToString()), this.Context);
- if (this.State == ConnectionStates.Closed)
- {
- HTTPManager.Logger.Verbose("HubConnection", "Transport_OnStateChanged - already closed!", this.Context);
- return;
- }
- switch (newState)
- {
- case TransportStates.Connected:
- try
- {
- if (this.OnTransportEvent != null)
- this.OnTransportEvent(this, this.Transport, TransportEvents.Connected);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnTransportEvent user code!", ex, this.Context);
- }
- SetState(ConnectionStates.Connected, null, this.defaultReconnect);
- break;
- case TransportStates.Failed:
- if (this.State == ConnectionStates.Negotiating && !HTTPManager.IsQuitting)
- {
- try
- {
- if (this.OnTransportEvent != null)
- this.OnTransportEvent(this, this.Transport, TransportEvents.FailedToConnect);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnTransportEvent user code!", ex, this.Context);
- }
- this.triedoutTransports.Add(this.Transport.TransportType);
- var nextTransport = GetNextTransportToTry();
- if (nextTransport == null)
- {
- var reason = this.Transport.ErrorReason;
- this.Transport = null;
- SetState(ConnectionStates.Closed, reason, this.defaultReconnect);
- }
- else
- ConnectImpl(nextTransport.Value);
- }
- else
- {
- try
- {
- if (this.OnTransportEvent != null)
- this.OnTransportEvent(this, this.Transport, TransportEvents.ClosedWithError);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnTransportEvent user code!", ex, this.Context);
- }
- var reason = this.Transport.ErrorReason;
- this.Transport = null;
- SetState(ConnectionStates.Closed, HTTPManager.IsQuitting ? null : reason, this.defaultReconnect);
- }
- break;
- case TransportStates.Closed:
- {
- try
- {
- if (this.OnTransportEvent != null)
- this.OnTransportEvent(this, this.Transport, TransportEvents.Closed);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnTransportEvent user code!", ex, this.Context);
- }
- // Check wheter we have any delayed message and a Close message among them. If there's one, delay the SetState(Close) too.
- if (this.delayedMessages == null || this.delayedMessages.FindLast(dm => dm.type == MessageTypes.Close).type != MessageTypes.Close)
- SetState(ConnectionStates.Closed, null, this.defaultReconnect);
- }
- break;
- }
- }
- private TransportTypes? GetNextTransportToTry()
- {
- foreach (TransportTypes val in Enum.GetValues(typeof(TransportTypes)))
- if (!this.triedoutTransports.Contains(val) && IsTransportSupported(val.ToString()))
- return val;
- return null;
- }
- bool defaultReconnect = true;
- private void SetState(ConnectionStates state, string errorReason, bool allowReconnect)
- {
- HTTPManager.Logger.Information("HubConnection", string.Format("SetState - from State: '{0}' to State: '{1}', errorReason: '{2}', allowReconnect: {3}, isQuitting: {4}", this.State, state, errorReason, allowReconnect, HTTPManager.IsQuitting), this.Context);
- if (this.State == state)
- return;
- var previousState = this.State;
- this.State = state;
- switch (state)
- {
- case ConnectionStates.Initial:
- case ConnectionStates.Authenticating:
- case ConnectionStates.Negotiating:
- case ConnectionStates.CloseInitiated:
- break;
- case ConnectionStates.Reconnecting:
- break;
- case ConnectionStates.Connected:
- // If reconnectStartTime isn't its default value we reconnected
- if (this.reconnectStartTime != DateTime.MinValue)
- {
- try
- {
- if (this.OnReconnected != null)
- this.OnReconnected(this);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "OnReconnected", ex, this.Context);
- }
- }
- else
- {
- try
- {
- if (this.OnConnected != null)
- this.OnConnected(this);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnConnected user code!", ex, this.Context);
- }
- }
- this.lastMessageSentAt = DateTime.Now;
- this.lastMessageReceivedAt = DateTime.Now;
- // Clean up reconnect related fields
- this.currentContext = new RetryContext();
- this.reconnectStartTime = DateTime.MinValue;
- this.reconnectAt = DateTime.MinValue;
- HTTPUpdateDelegator.OnApplicationForegroundStateChanged -= this.OnApplicationForegroundStateChanged;
- HTTPUpdateDelegator.OnApplicationForegroundStateChanged += this.OnApplicationForegroundStateChanged;
- break;
- case ConnectionStates.Closed:
- // Go through all invocations and cancel them.
- var error = new Message();
- error.type = MessageTypes.Close;
- error.error = errorReason;
- foreach (var kvp in this.invocations)
- {
- try
- {
- kvp.Value.callback(error);
- }
- catch
- { }
- }
- this.invocations.Clear();
- // No errorReason? It's an expected closure.
- if (errorReason == null && (!allowReconnect || HTTPManager.IsQuitting))
- {
- if (this.OnClosed != null)
- {
- try
- {
- this.OnClosed(this);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnClosed user code!", ex, this.Context);
- }
- }
- }
- else
- {
- // If possible, try to reconnect
- if (allowReconnect && this.ReconnectPolicy != null && (previousState == ConnectionStates.Connected || this.reconnectStartTime != DateTime.MinValue))
- {
- // It's the first attempt after a successful connection
- if (this.reconnectStartTime == DateTime.MinValue)
- {
- this.connectionStartedAt = this.reconnectStartTime = DateTime.Now;
- try
- {
- if (this.OnReconnecting != null)
- this.OnReconnecting(this, errorReason);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "SetState - ConnectionStates.Reconnecting", ex, this.Context);
- }
- }
- RetryContext context = new RetryContext
- {
- ElapsedTime = DateTime.Now - this.reconnectStartTime,
- PreviousRetryCount = this.currentContext.PreviousRetryCount,
- RetryReason = errorReason
- };
- TimeSpan? nextAttempt = null;
- try
- {
- nextAttempt = this.ReconnectPolicy.GetNextRetryDelay(context);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "ReconnectPolicy.GetNextRetryDelay", ex, this.Context);
- }
- // No more reconnect attempt, we are closing
- if (nextAttempt == null)
- {
- HTTPManager.Logger.Warning("HubConnection", "No more reconnect attempt!", this.Context);
- // Clean up everything
- this.currentContext = new RetryContext();
- this.reconnectStartTime = DateTime.MinValue;
- this.reconnectAt = DateTime.MinValue;
- }
- else
- {
- HTTPManager.Logger.Information("HubConnection", "Next reconnect attempt after " + nextAttempt.Value.ToString(), this.Context);
- this.currentContext = context;
- this.currentContext.PreviousRetryCount += 1;
- this.reconnectAt = DateTime.Now + nextAttempt.Value;
- this.SetState(ConnectionStates.Reconnecting, null, this.defaultReconnect);
- return;
- }
- }
- if (this.OnError != null)
- {
- try
- {
- this.OnError(this, errorReason);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in OnError user code!", ex, this.Context);
- }
- }
- }
- break;
- }
- }
- private void OnApplicationForegroundStateChanged(bool isPaused)
- {
- pausedInLastFrame = !isPaused;
- HTTPManager.Logger.Information("HubConnection", $"OnApplicationForegroundStateChanged isPaused: {isPaused} pausedInLastFrame: {pausedInLastFrame}", this.Context);
- }
- void BestHTTP.Extensions.IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
- {
- switch (this.State)
- {
- case ConnectionStates.Negotiating:
- case ConnectionStates.Authenticating:
- case ConnectionStates.Redirected:
- if (DateTime.Now >= this.connectionStartedAt + this.Options.ConnectTimeout)
- {
- if (this.AuthenticationProvider != null)
- {
- this.AuthenticationProvider.OnAuthenticationSucceded -= OnAuthenticationSucceded;
- this.AuthenticationProvider.OnAuthenticationFailed -= OnAuthenticationFailed;
- try
- {
- this.AuthenticationProvider.Cancel();
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("HubConnection", "Exception in AuthenticationProvider.Cancel !", ex, this.Context);
- }
- }
- if (this.Transport != null)
- {
- this.Transport.OnStateChanged -= Transport_OnStateChanged;
- this.Transport.StartClose();
- }
- SetState(ConnectionStates.Closed, string.Format("Couldn't connect in the given time({0})!", this.Options.ConnectTimeout), this.defaultReconnect);
- }
- break;
- case ConnectionStates.Connected:
- if (this.delayedMessages?.Count > 0)
- {
- pausedInLastFrame = false;
- try
- {
- // if there's any Close message clear any other one.
- int idx = this.delayedMessages.FindLastIndex(dm => dm.type == MessageTypes.Close);
- if (idx > 0)
- this.delayedMessages.RemoveRange(0, idx);
- OnMessages(this.delayedMessages);
- }
- finally
- {
- this.delayedMessages.Clear();
- }
- }
- // Still connected? Check pinging.
- if (this.State == ConnectionStates.Connected)
- {
- if (this.Options.PingInterval != TimeSpan.Zero && DateTime.Now - this.lastMessageReceivedAt >= this.Options.PingTimeoutInterval)
- {
- // The transport itself can be in a failure state or in a completely valid one, so while we do not want to receive anything from it, we have to try to close it
- if (this.Transport != null)
- {
- this.Transport.OnStateChanged -= Transport_OnStateChanged;
- this.Transport.StartClose();
- }
- SetState(ConnectionStates.Closed,
- string.Format("PingInterval set to '{0}' and no message is received since '{1}'. PingTimeoutInterval: '{2}'", this.Options.PingInterval, this.lastMessageReceivedAt, this.Options.PingTimeoutInterval),
- this.defaultReconnect);
- }
- else if (this.Options.PingInterval != TimeSpan.Zero && DateTime.Now - this.lastMessageSentAt >= this.Options.PingInterval)
- SendMessage(new Message() { type = MessageTypes.Ping });
- }
- break;
- case ConnectionStates.Reconnecting:
- if (this.reconnectAt != DateTime.MinValue && DateTime.Now >= this.reconnectAt)
- {
- this.delayedMessages?.Clear();
- this.connectionStartedAt = DateTime.Now;
- this.reconnectAt = DateTime.MinValue;
- this.triedoutTransports.Clear();
- this.StartConnect();
- }
- break;
- case ConnectionStates.Closed:
- CleanUp();
- break;
- }
- }
- private void CleanUp()
- {
- HTTPManager.Logger.Information("HubConnection", "CleanUp", this.Context);
- this.delayedMessages?.Clear();
- HTTPManager.Heartbeats.Unsubscribe(this);
- HTTPUpdateDelegator.OnApplicationForegroundStateChanged -= this.OnApplicationForegroundStateChanged;
- this.rwLock?.Dispose();
- this.rwLock = null;
- }
- }
- }
- #endif
|