Brian S
Brian S

Reputation: 405

SignalR Core Apache WS Unexpected response code: 200

I'm currently running a .Net Core application with SignalR on an Ubuntu Apache web server. This .Net Core app is reachable by the example.com/api path.

I also have a Vue JS app running on the webserver as well. When I attempt to establish a websocket connection to the the SignalR application, the following error occurs:

WebSocket connection to 'wss://example.com/api/mainHub?id=V3XZrhdsQDTTMIZvQ-8cbQ' failed: Error during WebSocket handshake: Unexpected response code: 200

Error: Failed to start the transport 'WebSockets': null

The Apache config for the .Net Core app is as follows:

#Main/Desired Path - https://example.com
<VirtualHost *:443>
        DocumentRoot /var/www/example.com/dist/spa
        ServerName example.com

#SSL Certs
        SSLEngine on
        SSLProtocol all -SSLv2
        SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
        SSLCertificateFile /etc/apache2/ssl/example.crt
        SSLCertificateKeyFile /etc/apache2/ssl/server.key
        SSLCertificateChainFile /etc/apache2/ssl/example.ca-bundle

#Upgrade Websockets
        RewriteEngine on
        RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
        RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
        RewriteRule /(.*) ws://localhost:5000/$1 [P]

#Other specific directories
        ProxyPreserveHost On
        ProxyPass "/api" http://localhost:5000
        ProxyPassReverse "/api" http://localhost:5000

</VirtualHost>

I've added the RewriteEngine to upgrade the websockets, I'm not sure if the issue is with the configuration on Apache, or in the .Net Core app itself.

The .Net Core app has the following in the startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options => options.AddPolicy("CorsPolicy", builder =>
            {
                builder
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials()
                    .WithOrigins("*");
            }));
            services.AddSignalR();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //first handle any websocket requests
            app.UseWebSockets();

            app.UseCors("CorsPolicy");
            app.UseSignalR(routes =>
            {
                routes.MapHub<mainHub>("/mainHub");
                routes.MapHub<accounthub>("/accountHub");
            });
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }

It seems that either something is incorrect in the startup.cs that is sending a 200 response back to the client when establishing the websocket connection or the Apache virtual host is not properly upgrading the websocket.

Upvotes: 3

Views: 2315

Answers (2)

Popescu Valentin
Popescu Valentin

Reputation: 51

I have found this on a site ( http://shyammakwana.me/server/websockets-with-apache-reverse-proxy-with-ssl.html ) which seems to work also for me. The apache config for the given site should be like this

<IfModule mod_ssl.c>
<VirtualHost *:443>
  RewriteEngine On
  ProxyPreserveHost On
  ProxyRequests Off

  # allow for upgrading to websockets
  RewriteEngine On
  RewriteCond %{HTTP:Upgrade} =websocket [NC]
  RewriteRule /(.*)           ws://localhost:5000/$1 [P,L]
  RewriteCond %{HTTP:Upgrade} !=websocket [NC]
  RewriteRule /(.*)           http://localhost:5000/$1 [P,L]


  ProxyPass "/" "http://localhost:5000/"
  ProxyPassReverse "/" "http://localhost:5000/"

  ProxyPass "/chatHub" "ws://localhost:5000/chatHub"
  ProxyPassReverse "/chatHub" "ws://localhost:5000/chatHub"

  ServerName site.com
  
SSLCertificateFile /etc/letsencrypt/live/site.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

Also you should have these modules enabled:

a2enmod   proxy
a2enmod   proxy_wstunnel

Upvotes: 5

Steve Teece
Steve Teece

Reputation: 63

I am also getting this error while deploying a ASP.NET Core 3.1 Blazor Server-side app to an Ubuntu 18.04 server running Apache2.

In combination with this error, I am also getting a 404 error trying to access static resources (.css files, images and scripts) from the Linux server, but everything works perfectly in an IIS environment. I've also noted that the contents of the WWWRoot folder in my solution do not appear to be copied to the linux server when I publish the app. I am using the FolderProfile method of publishing, then copying the contents of the publish folder to the linux server via SFTP.

The only "real" change from the boilerplate startup.Configure class (from the New Blazor-Server Web App template in VS2019) I have made is to add the UseForwardHeaders directive as described at https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-3.1

I've also added Authentication and Authorisation to the app, but once again, the whole thing runs perfectly on IIS, but it needs to be installed on a production linux server once testing is complete.

My Startup.Configure looks like:

 public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            // Enable SyncFusion Community Edition License
            Syncfusion.Licensing.SyncfusionLicenseProvider
                .RegisterLicense("Key-Removed");

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();


            // Use Authentication and Authorisation providers in the application
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }

Also, on the linux side, the virtual host configuration file looks like this:

#<VirtualHost *:*>
#    RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
#</VirtualHost>

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
    ServerName www.grafton.internal
    ErrorLog ${APACHE_LOG_DIR}labapp_error.log
    CustomLog ${APACHE_LOG_DIR}labapp_access.log common
</VirtualHost>

Note I have commented out the top 3 lines because I could not get this expression to work correctly - Apache failed to start with every variation of the 2nd line I tried. I believe this might be related to my problem, but I am not sure what to do about it.

I also want to stress that this app is for internal use only, and the web server is only visible on our Intranet - there are no external / internet facing servers or sites and access is limited to a very few people via physical access to the servers, and restricted fixed IP addresses, so I am not at all concerned with using SSL.

Any assistance with this one would be appreciated. I've been trying to get this running for a week, and only have a week left before it MUST be in the linux test environment.

Upvotes: -1

Related Questions