Updated instructions

2019-10-11

  1. Cross domain allows AllowAnyOrigin to use the following scheme
services.AddCors(options => options.AddPolicy("SignalR",
   builder =>
   {
       builder.AllowAnyMethod()
              .AllowAnyHeader()
              .SetIsOriginAllowed(str => true)
              .AllowCredentials();
   }));
Copy the code
  1. Enable it using the following methodsMessagePack
  1. The background to installMicrosoft.AspNetCore.SignalR.Protocols.MessagePackpackage
  2. Js installednpm install @aspnet/signalr-protocol-msgpack
AddSignalr().addMessagepackProtocol (); Add the following code / / js end const connection = new signalR. HubConnectionBuilder () withUrl ("/chatHub "). WithHubProtocol (new SignalR. Separate protocols. Msgpack. MessagePackHubProtocol ()) / / add this line. The build ();Copy the code
3.0 New Functions
  1. Signalr Hub registration mode changed toUseEndpointsIn the
//<=2.2.0 mode app.usesignalr (routes => {routes.MapHub<ChatHub>("/chat"); }); App.useendpoints (endPoints => {endpoints.MapHub<ChatHub>(); });Copy the code
  1. useNewtonsoftJson
  1. Install AspNetCore SignalR NewtonsoftJson
  2. Use the following code to add
services.AddSignalR() .AddNewtonsoftJsonProtocol(...) ;Copy the code
  1. Break line reconnection

You can specify the reconnection interval by passing an array of durations based on milliseconds:

.withAutomaticReconnect([0, 3000, 5000, 10000, 15000, 30000])
Copy the code

The following code updates the UI using onreconnecting when trying to connect:

connection.onreconnecting((error) => {
    const status = `Connection lost due to error "${error}". 
    console.log(error);
});
Copy the code

The following code updates the UI using onReConnected when connecting:

connection.onreconnected((connectionId) => { ... todo });Copy the code

2019-05-27

  1. See a lot of people demand wechat customer service, specially uploaded to Github, modify the version for compiled JS modification, need to take yourself. Making the address
  2. Starting with NetCore 2.2, cross-domain access requires using WithOrigins(Domins) to specify a specific domain name. AllowAnyOrigin is invalid. For details, refer to the cross-domain documentation

background

Due to the recent company to do wechat small program chat, so. NetFramwork SignalR version is no longer available. JQuery cannot be used because there are no Windows objects in the applet. Signalr js client is dependent on JQuery.

So look at the Core version of SignarlR, after testing, found that it can run in wechat, but we need to change the Webscoekt in JS client to wechat own.

purpose

The main purpose of this article is to introduce the usage. NetCore version SignalR has some pits and provides a solution. It’s mostly just a simple official demo introduction. Not really put into use, some of the small problems have not been in-depth excavation and processing.

Cross-domain problem

The.NET Frmawork version is simple, referring to the corresponding package and simply adding AddCors(), while the Core version is more precise. ConfigureServices adds the following code

services.AddCors(options => options.AddPolicy("SignalR",
   builder =>
   {
       builder.AllowAnyMethod() // Allow arbitrary requests
              .AllowAnyHeader() // Allow arbitrary headers
              .AllowAnyOrigin() // Any origin is allowed
              .AllowCredentials();// Allow validation
            //.withorigins (domins) // Specify a specific domain name to access
   }));
Copy the code

The defined cross-domain policy is then used in Configure

 app.UseCors("SignalR");
Copy the code

Use Redis Scale Out

Like.NET Framwork, the.netcore version of SignalR can use Redis to communicate between multiple servers. However, if Redis does not connect successfully, the program will not report an error, but the communication can not be used normally. For the.NET Framwork version,SignalR’s address is 404.

So I want to monitor if Redis is connected successfully at startup. But SignalR’s official documentation is simple to use, not even how Redis is configured. So you have to go to the biggest dating sites. Look through the issue one by one, finally found how to monitor.

Poke me to see the details

The following configuration allows you to monitor whether the Redis connection is successful.

services.AddSignalR()
        .AddMessagePackProtocol()
        .AddRedis(o =>
        {
            o.ConnectionFactory = async writer =>
            {
                var config = new ConfigurationOptions
                {
                    AbortOnConnectFail = false
                };
                config.EndPoints.Add(IPAddress.Loopback, 0);
                config.SetDefaultPorts();
                var connection = await ConnectionMultiplexer.ConnectAsync(config, writer);
                connection.ConnectionFailed += (_, e) =>
                {
                    Console.WriteLine("Connection Redis failed.");
                };

                if(! connection.IsConnected) { Console.WriteLine("Connection did not connect.");
                }

                return connection;
            };
        });
Copy the code

However, it was found that Redis connected twice in this way, which should not be the case. Plus, I have too much work to study the source code. So ask the author directly in this issue. We still don’t know why. See the link above for details.

WebSocket load balancing configuration

Using load balancing to forward requests requires special configuration for WebSocket requests.

I asked the operation and maintenance student to configure it. After the configuration, he told me that this link can only be used for GET request, not POST request. Manual black question mark…

In this case, only WebSocket mode can be used, such as LongPollin and SSE protocol can not be used.

Oh, my god. Is that so bad? So I asked operations to send me the configuration code, as follows

Proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_connect_timeout 300; proxy_read_timeout 300; proxy_send_timeout 300;Copy the code

So I published the application to a local VIRTUAL machine and ran it as a Docker. Then write the configuration to the nginx configuration file.

Finding that the POST request really cannot be made, returns 400. 400 means the request is abnormal. There must be something wrong with this configuration. So I went to the dating website to find the issue, and sure enough, I found it. In an issue, the configuration provided is as follows

Proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection;Copy the code

The difference is that proxy_set_header Connection didn’t die, so I changed the configuration and it worked.

Proxy_set_header Connection cannot be written to death. There is no problem with other requests.

ConnectionId access

The ConnectionId is no longer available in the JS client code. That is, if you want to use it, you need to change the source code to add. That’s fine, but Microsoft should never have made such a stupid mistake. Why not open the ConnectionId when it is returned during the Negotiate request? Is it a bug? There shouldn’t be such a low-level bug.

Then went to see issues, sure enough, there are also people asked, the author also has an explanation.

Check out dating sites

ConnectonId is used by the server. The client should not use this uncontrollable mode to communicate. Group or User can be used for controlled communication, and examples are given.

By the way, in use. In the VERSION of Net Framwork, our website used ConnectionId to communicate, and often reconnection led to the change of ConnectionId, thus the communication failed.

Therefore, I adjusted the design idea and adopted Group for communication.

All the above are done, hard for so long, according to the truth should be no problem! So launch!

The hole to

My local test is ok, the test machine is not a problem, so sent to the production environment, the results of the problem appeared.

Since both the local and test environments are single-server, the tests are fine. In production, you have multiple servers. No matter how I set up my JS, after executing the negotiate request, the subsequent Connection request must be 404 and return No Connection with that Id.

My first thought when I saw this error was, did Redis fail to connect, so I had to run alone? So I added all kinds of monitoring to the Redis code and found that the connection was successful. The code has been reviewed many times, there is really no place to change.

So the official documents go through one by one. Finally found that Js can do the following configuration

let connection = new signalR.HubConnectionBuilder()
    .withUrl("/myhub", {
        skipNegotiation: true.transport: signalR.HttpTransportType.WebSockets
    });
    .build();
Copy the code

The above code means skip the negotiate handshake and connect directly using the WebSocket.

It’s documented. Oh, my god, it works. Because only one request was sent to establish a communication connection.

Now I am not calm, can only deploy one server? How does that guarantee stability? This is still used in wechat small program (js client has been modified), the lower version can not use Websocket, do not care about the lower version? How does the machine resist to live without large flow? Do you want to do your own communications instead?

I had no choice but to make a big move. Clone the source code, take some time to look around, and find the following code

private async Task<HttpConnectionContext> GetConnectionAsync(HttpContext context) { var connectionId = GetConnectionId(context); if (StringValues.IsNullOrEmpty(connectionId)) { // There's no connection ID: bad request context.Response.StatusCode = StatusCodes.Status400BadRequest; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("Connection ID required"); return null; } if (! _manager.TryGetConnection(connectionId, out var connection)) { // No connection with that ID: Not Found context.Response.StatusCode = StatusCodes.Status404NotFound; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("No Connection with that ID"); return null; } return connection; }Copy the code

So what does this code mean? If a connection is not found locally, return 404!

Oh, my god. Is it a bug in the code?

As a bonus

In the.NET Framwork version, the source code validates the ConnectionId. If the verification is successful, but no connection can be found locally, a new connection will be created to realize communication between multiple servers. That’s why I have the above question.

The downside is that you can’t monitor when the client is disconnected.

So I made an issue and asked the author. Poke me to see the details

The response was

It’s not a bug it’s by design. ASP.NET Core SignalR requires sticky sessions when using scale out. This means you need to pin a connection to a particular serve

What does that mean? It’s not a bug. It’s designed that way. When using SignalR, for session persistence, requests always land on the same server. This is more stable and allows you to monitor the client in real time.

So I asked the operation and maintenance students to configure session persistence on the load and tested it again. Finally, it was ok.

conclusion

In the process of using SignalR this time, I encountered too many potholes. Took a couple of hours to organize and record and share with you. I hope it helps those who are ready or planning to use it. Net of the Core. Neter