diff --git a/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.core.js b/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.core.js
index 6abf4e3dbf..673dbce32e 100644
--- a/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.core.js
+++ b/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.core.js
@@ -100,7 +100,7 @@
isDisconnecting = function (connection) {
return connection.state === signalR.connectionState.disconnected;
},
-
+
supportsKeepAlive = function (connection) {
return connection._.keepAliveData.activated &&
connection.transport.supportsKeepAlive(connection);
@@ -403,7 +403,7 @@
state: signalR.connectionState.disconnected,
- clientProtocol: "1.4",
+ clientProtocol: "1.5",
reconnectDelay: 2000,
diff --git a/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.common.js b/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.common.js
index c5114bd18e..f8e780c372 100644
--- a/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.common.js
+++ b/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.common.js
@@ -206,13 +206,14 @@
throw new Error("Query string property must be either a string or object.");
},
- getUrl: function (connection, transport, reconnecting, poll) {
+ // BUG #2953: The url needs to be same otherwise it will cause a memory leak
+ getUrl: function (connection, transport, reconnecting, poll, ajaxPost) {
/// Gets the url for making a GET based connect request
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
url = baseUrl + connection.appRelativeUrl,
qs = "transport=" + transport;
- if (connection.groupsToken) {
+ if (!ajaxPost && connection.groupsToken) {
qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken);
}
@@ -226,13 +227,17 @@
url += "/reconnect";
}
- if (connection.messageId) {
+ if (!ajaxPost && connection.messageId) {
qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
}
}
url += "?" + qs;
url = transportLogic.prepareQueryString(connection, url);
- url += "&tid=" + Math.floor(Math.random() * 11);
+
+ if (!ajaxPost) {
+ url += "&tid=" + Math.floor(Math.random() * 11);
+ }
+
return url;
},
diff --git a/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.longPolling.js b/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.longPolling.js
index 142cffddba..26a3ec77f3 100644
--- a/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.longPolling.js
+++ b/src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.transports.longPolling.js
@@ -90,7 +90,16 @@
connect = (messageId === null),
reconnecting = !connect,
polling = !raiseReconnect,
- url = transportLogic.getUrl(instance, that.name, reconnecting, polling);
+ url = transportLogic.getUrl(instance, that.name, reconnecting, polling, true /* use Post for longPolling */),
+ postData = {};
+
+ if (instance.messageId) {
+ postData.messageId = instance.messageId;
+ }
+
+ if (instance.groupsToken) {
+ postData.groupsToken = instance.groupsToken;
+ }
// If we've disconnected during the time we've tried to re-instantiate the poll then stop.
if (isDisconnecting(instance) === true) {
@@ -105,6 +114,9 @@
}
},
url: url,
+ type: "POST",
+ contentType: signalR._.defaultContentType,
+ data: postData,
success: function (result) {
var minData,
delay = 0,
diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/ProtocolResolver.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/ProtocolResolver.cs
index 7e02be749e..c66cac40be 100644
--- a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/ProtocolResolver.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/ProtocolResolver.cs
@@ -12,7 +12,7 @@ public class ProtocolResolver
private readonly Version _minimumDelayedStartVersion = new Version(1, 4);
public ProtocolResolver() :
- this(new Version(1, 2), new Version(1, 4))
+ this(new Version(1, 2), new Version(1, 5))
{
}
diff --git a/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs b/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs
index 4764db2117..44730d0b4f 100644
--- a/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs
@@ -181,7 +181,7 @@ public Task ProcessRequest(IDictionary environment)
/// Thrown if the transport wasn't specified.
/// Thrown if the connection id wasn't specified.
///
- public virtual Task ProcessRequest(HostContext context)
+ public virtual async Task ProcessRequest(HostContext context)
{
if (context == null)
{
@@ -195,18 +195,21 @@ public virtual Task ProcessRequest(HostContext context)
if (IsNegotiationRequest(context.Request))
{
- return ProcessNegotiationRequest(context);
+ await ProcessNegotiationRequest(context).PreserveCulture();
+ return;
}
else if (IsPingRequest(context.Request))
{
- return ProcessPingRequest(context);
+ await ProcessPingRequest(context).PreserveCulture();
+ return;
}
Transport = GetTransport(context);
if (Transport == null)
{
- return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport));
+ await FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport)).PreserveCulture();
+ return;
}
string connectionToken = context.Request.QueryString["connectionToken"];
@@ -214,16 +217,18 @@ public virtual Task ProcessRequest(HostContext context)
// If there's no connection id then this is a bad request
if (String.IsNullOrEmpty(connectionToken))
{
- return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken));
+ await FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken)).PreserveCulture();
+ return;
}
string connectionId;
string message;
int statusCode;
-
+
if (!TryGetConnectionId(context, connectionToken, out connectionId, out message, out statusCode))
{
- return FailResponse(context.Response, message, statusCode);
+ await FailResponse(context.Response, message, statusCode).PreserveCulture();
+ return;
}
// Set the transport's connection id to the unprotected one
@@ -232,8 +237,11 @@ public virtual Task ProcessRequest(HostContext context)
// Get the user id from the request
string userId = UserIdProvider.GetUserId(context.Request);
+ // Get the groups oken from the request
+ string groupsToken = await Transport.GetGroupsToken().PreserveCulture();
+
IList signals = GetSignals(userId, connectionId);
- IList groups = AppendGroupPrefixes(context, connectionId);
+ IList groups = AppendGroupPrefixes(context, connectionId, groupsToken);
Connection connection = CreateConnection(connectionId, signals, groups);
@@ -245,7 +253,8 @@ public virtual Task ProcessRequest(HostContext context)
// because ProcessStartRequest calls OnConnected.
if (IsStartRequest(context.Request))
{
- return ProcessStartRequest(context, connectionId);
+ await ProcessStartRequest(context, connectionId).PreserveCulture();
+ return;
}
Transport.Connected = () =>
@@ -277,7 +286,7 @@ public virtual Task ProcessRequest(HostContext context)
}
};
- return Transport.ProcessRequest(connection).OrEmpty().Catch(Counters.ErrorsAllTotal, Counters.ErrorsAllPerSec);
+ await Transport.ProcessRequest(connection).OrEmpty().Catch(Counters.ErrorsAllTotal, Counters.ErrorsAllPerSec).PreserveCulture();
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to catch any exception when unprotecting data.")]
@@ -328,10 +337,8 @@ public virtual Task ProcessRequest(HostContext context)
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to prevent any failures in unprotecting")]
- internal IList VerifyGroups(HostContext context, string connectionId)
+ internal IList VerifyGroups(string connectionId, string groupsToken)
{
- string groupsToken = context.Request.QueryString["groupsToken"];
-
if (String.IsNullOrEmpty(groupsToken))
{
return ListHelper.Empty;
@@ -366,9 +373,9 @@ internal IList VerifyGroups(HostContext context, string connectionId)
return JsonSerializer.Parse(groupsValue);
}
- private IList AppendGroupPrefixes(HostContext context, string connectionId)
+ private IList AppendGroupPrefixes(HostContext context, string connectionId, string groupsToken)
{
- return (from g in OnRejoiningGroups(context.Request, VerifyGroups(context, connectionId), connectionId)
+ return (from g in OnRejoiningGroups(context.Request, VerifyGroups(connectionId, groupsToken), connectionId)
select GroupPrefix + g).ToList();
}
diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs
index 5469cca40f..f75d054dc0 100644
--- a/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs
@@ -19,7 +19,6 @@ public abstract class ForeverTransport : TransportDisconnectBase, ITransport
private readonly IPerformanceCounterManager _counters;
private readonly JsonSerializer _jsonSerializer;
- private string _lastMessageId;
private IDisposable _busRegistration;
internal RequestLifetime _transportLifetime;
@@ -52,19 +51,6 @@ protected virtual int MaxMessages
}
}
- protected string LastMessageId
- {
- get
- {
- if (_lastMessageId == null)
- {
- _lastMessageId = Context.Request.QueryString["messageId"];
- }
-
- return _lastMessageId;
- }
- }
-
protected JsonSerializer JsonSerializer
{
get { return _jsonSerializer; }
@@ -92,32 +78,31 @@ protected virtual void OnSendingResponse(PersistentResponse response)
internal Action BeforeReceive;
internal Action AfterRequestEnd;
- protected override void InitializePersistentState()
+ protected override async Task InitializePersistentState()
{
- base.InitializePersistentState();
+ await base.InitializePersistentState().PreserveCulture();
// The _transportLifetime must be initialized after calling base.InitializePersistentState since
// _transportLifetime depends on _requestLifetime.
_transportLifetime = new RequestLifetime(this, _requestLifeTime);
}
- protected Task ProcessRequestCore(ITransportConnection connection)
+ protected async Task ProcessRequestCore(ITransportConnection connection)
{
Connection = connection;
if (IsSendRequest)
{
- return ProcessSendRequest();
+ await ProcessSendRequest().PreserveCulture();
}
else if (IsAbortRequest)
{
- return Connection.Abort(ConnectionId);
+ await Connection.Abort(ConnectionId).PreserveCulture();
}
else
{
- InitializePersistentState();
-
- return ProcessReceiveRequest(connection);
+ await InitializePersistentState().PreserveCulture();
+ await ProcessReceiveRequest(connection).PreserveCulture();
}
}
diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/ITransport.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/ITransport.cs
index 772cca9030..eb61e5e9fa 100644
--- a/src/Microsoft.AspNet.SignalR.Core/Transports/ITransport.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/Transports/ITransport.cs
@@ -4,6 +4,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
+using Microsoft.AspNet.SignalR.Hosting;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Transports
{
@@ -37,6 +39,13 @@ public interface ITransport
///
string ConnectionId { get; set; }
+ ///
+ /// Get groupsToken in request over the transport.
+ ///
+ /// groupsToken in request
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is for async.")]
+ Task GetGroupsToken();
+
///
/// Processes the specified for this transport.
///
diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs
index 672be26416..b0a8a9bc9b 100644
--- a/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs
@@ -9,6 +9,7 @@
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Tracing;
using Newtonsoft.Json;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Transports
{
@@ -94,6 +95,30 @@ protected override bool SuppressReconnect
}
}
+ protected override async Task InitializeMessageId()
+ {
+ _lastMessageId = Context.Request.QueryString["messageId"];
+
+ if (_lastMessageId == null)
+ {
+ var form = await Context.Request.ReadForm().PreserveCulture();
+ _lastMessageId = form["messageId"];
+ }
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is for async.")]
+ public override async Task GetGroupsToken()
+ {
+ var groupsToken = Context.Request.QueryString["groupsToken"];
+
+ if (groupsToken == null)
+ {
+ var form = await Context.Request.ReadForm().PreserveCulture();
+ groupsToken = form["groupsToken"];
+ }
+ return groupsToken;
+ }
+
public override Task KeepAlive()
{
// Ensure delegate continues to use the C# Compiler static delegate caching optimization.
@@ -250,7 +275,7 @@ private static Task PerformCompleteSend(object state)
return PerformPartialSend(state);
}
-
+
private void AddTransportData(PersistentResponse response)
{
if (_configurationManager.LongPollDelay != TimeSpan.Zero)
diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs
index 64fcb07bed..28cefb7d33 100644
--- a/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs
+++ b/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs
@@ -26,6 +26,9 @@ public abstract class TransportDisconnectBase : ITrackingConnection
private int _ended;
private TransportConnectionStates _state;
+ [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "It can be set in any derived class.")]
+ protected string _lastMessageId;
+
internal static readonly Func _emptyTaskFunc = () => TaskAsyncHelper.Empty;
// The TCS that completes when the task returned by PersistentConnection.OnConnected does.
@@ -90,6 +93,26 @@ public string ConnectionId
set;
}
+ protected string LastMessageId
+ {
+ get
+ {
+ return _lastMessageId;
+ }
+ }
+
+ protected virtual Task InitializeMessageId()
+ {
+ _lastMessageId = Context.Request.QueryString["messageId"];
+ return TaskAsyncHelper.Empty;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is for async.")]
+ public virtual Task GetGroupsToken()
+ {
+ return TaskAsyncHelper.FromResult(Context.Request.QueryString["groupsToken"]);
+ }
+
public virtual TextWriter OutputWriter
{
get
@@ -126,7 +149,7 @@ public virtual bool IsAlive
{
// If the CTS is tripped or the request has ended then the connection isn't alive
return !(
- CancellationToken.IsCancellationRequested ||
+ CancellationToken.IsCancellationRequested ||
(_requestLifeTime != null && _requestLifeTime.Task.IsCompleted) ||
_lastWriteTask.IsCanceled ||
_lastWriteTask.IsFaulted
@@ -357,7 +380,7 @@ protected virtual internal Task EnqueueOperation(Func