Как локально подключить React к SignalR с помощью функции Azure ⇐ C#
-
Anonymous
Как локально подключить React к SignalR с помощью функции Azure
Following along with the Azure SignalR Service Serverless Quick Start tutorial I was able to run the hosted html file just fine while also pinging the negotiate function from postman or a browser works as well. The problem I'm having is getting a React JS application to do the exact same thing. Instead of connecting to SignalR like the html's JS block, I get this error:
HttpConnection.ts:350 Uncaught (in promise) Error: Failed to complete negotiation with the server: TypeError: Failed to fetch at HttpConnection._getNegotiationResponse (HttpConnection.ts:350:35) at async HttpConnection._startInternal (HttpConnection.ts:246:41) at async HttpConnection.start (HttpConnection.ts:136:9) at async _HubConnection._startInternal (HubConnection.ts:228:9) at async _HubConnection._startWithStateTransitions (HubConnection.ts:202:13) I understand that the fetch is failing, but I don't understand why. There shouldn't be a CORS problem since Postman and the Browser can access it just fine, however, I'm at a loss for what else could be wrong since the example index.html file works fine, but not the React one.
Here's the Azure Function:
using System; using System.Net; using System.Net.Http.Json; using System.Text.Json.Serialization; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; namespace csharp_isolated; public class Functions { private static readonly HttpClient HttpClient = new(); private static string Etag = string.Empty; private static int StarCount = 0; [Function("index")] public static HttpResponseData GetHomePage([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req) { var response = req.CreateResponse(HttpStatusCode.OK); response.WriteString(File.ReadAllText("content/index.html")); response.Headers.Add("Content-Type", "text/html"); return response; } [Function("negotiate")] public static HttpResponseData Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req, [SignalRConnectionInfoInput(HubName = "serverless")] string connectionInfo) { var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "application/json"); response.WriteString(connectionInfo); return response; } [Function("broadcast")] [SignalROutput(HubName = "serverless")] public static async Task Broadcast([TimerTrigger("*/5 * * * * *")] TimerInfo timerInfo) { var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/azure/azure-signalr"); request.Headers.UserAgent.ParseAdd("Serverless"); request.Headers.Add("If-None-Match", Etag); var response = await HttpClient.SendAsync(request); if (response.Headers.Contains("Etag")) { Etag = response.Headers.GetValues("Etag").First(); } if (response.StatusCode == HttpStatusCode.OK) { var result = await response.Content.ReadFromJsonAsync(); if (result != null) { StarCount = result.StarCount; } } int randomNum = Random.Shared.Next(1, 10); // helps to visualize that the messages are going through return new SignalRMessageAction("newMessage", [$"Current star count of https://github.com/Azure/azure-signalr is: {StarCount + randomNum}"]); } private class GitResult { [JsonPropertyName("stargazers_count")] public int StarCount { get; set; } } } and here is the React app (using TypeScript + Vite)
import * as React from 'react'; import './App.css'; import * as signalR from "@microsoft/signalr"; const URL = "http://localhost:7071/api"; const list: string[] = []; interface MessageProps { HubConnection: signalR.HubConnection } const Messages: React.FC = (props: MessageProps) => { const { HubConnection } = props; React.useEffect(() => { HubConnection.on("newMessage", message => list.push(message)); }, []); return {list.map((message, index) => {message}
)} } const App: React.FC = () => { const hubConnection = new signalR.HubConnectionBuilder() .withUrl(URL) .configureLogging(signalR.LogLevel.Information) .build(); hubConnection.start() .catch(console.error); return } export default App;
What really confuses me is how the JS in the html file works just fine:
let messages = document.querySelector('#messages'); const apiBaseUrl = window.location.origin; console.log('base url:', apiBaseUrl); const connection = new signalR.HubConnectionBuilder() .withUrl(apiBaseUrl + '/api') .configureLogging(signalR.LogLevel.Information) .build(); connection.on('newMessage', (message) => { document.getElementById("messages").innerHTML = message; }); connection.start() .catch(console.error); but what looks like essentially the same thing in React is DOA due to a fetch failure from the SignalR JS.
The only thing I can figure is that there is in fact a CORS problem from running the react app on localhost:5173 and the Azure Function on localhost:7071, but the browser and Postman can each hit the negotiate route without any access problems, it's just the react app that fails.
network tab output from React page:
Request URL: http://localhost:7071/api/negotiate?negotiateVersion=1 Referrer Policy: strict-origin-when-cross-origin -- Edit --
I should also point out that the local.settings.json file contains:
{ ... "Host": { "LocalHttpPort": 7071, "CORS": "*", "CORSCredentials": false }, ... } Ok, so it is a CORS issue, now I'm seeing:
localhost/:1 Access to fetch at 'http://localhost:7071/api/negotiate?negotiateVersion=1' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Also tried to specify the urls in the host settings to address the above * comment:
localhost/:1 Access to fetch at 'http://localhost:7071/api/negotiate?negotiateVersion=1' from origin 'http://localhost:5123' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. Even with host set to * or specific URLs CORS is still saying no. How exactly is one supposed to set CORS for a local setup when local.settings.json is not enough?
Источник: https://stackoverflow.com/questions/779 ... on-locally
Following along with the Azure SignalR Service Serverless Quick Start tutorial I was able to run the hosted html file just fine while also pinging the negotiate function from postman or a browser works as well. The problem I'm having is getting a React JS application to do the exact same thing. Instead of connecting to SignalR like the html's JS block, I get this error:
HttpConnection.ts:350 Uncaught (in promise) Error: Failed to complete negotiation with the server: TypeError: Failed to fetch at HttpConnection._getNegotiationResponse (HttpConnection.ts:350:35) at async HttpConnection._startInternal (HttpConnection.ts:246:41) at async HttpConnection.start (HttpConnection.ts:136:9) at async _HubConnection._startInternal (HubConnection.ts:228:9) at async _HubConnection._startWithStateTransitions (HubConnection.ts:202:13) I understand that the fetch is failing, but I don't understand why. There shouldn't be a CORS problem since Postman and the Browser can access it just fine, however, I'm at a loss for what else could be wrong since the example index.html file works fine, but not the React one.
Here's the Azure Function:
using System; using System.Net; using System.Net.Http.Json; using System.Text.Json.Serialization; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; namespace csharp_isolated; public class Functions { private static readonly HttpClient HttpClient = new(); private static string Etag = string.Empty; private static int StarCount = 0; [Function("index")] public static HttpResponseData GetHomePage([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req) { var response = req.CreateResponse(HttpStatusCode.OK); response.WriteString(File.ReadAllText("content/index.html")); response.Headers.Add("Content-Type", "text/html"); return response; } [Function("negotiate")] public static HttpResponseData Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req, [SignalRConnectionInfoInput(HubName = "serverless")] string connectionInfo) { var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "application/json"); response.WriteString(connectionInfo); return response; } [Function("broadcast")] [SignalROutput(HubName = "serverless")] public static async Task Broadcast([TimerTrigger("*/5 * * * * *")] TimerInfo timerInfo) { var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/azure/azure-signalr"); request.Headers.UserAgent.ParseAdd("Serverless"); request.Headers.Add("If-None-Match", Etag); var response = await HttpClient.SendAsync(request); if (response.Headers.Contains("Etag")) { Etag = response.Headers.GetValues("Etag").First(); } if (response.StatusCode == HttpStatusCode.OK) { var result = await response.Content.ReadFromJsonAsync(); if (result != null) { StarCount = result.StarCount; } } int randomNum = Random.Shared.Next(1, 10); // helps to visualize that the messages are going through return new SignalRMessageAction("newMessage", [$"Current star count of https://github.com/Azure/azure-signalr is: {StarCount + randomNum}"]); } private class GitResult { [JsonPropertyName("stargazers_count")] public int StarCount { get; set; } } } and here is the React app (using TypeScript + Vite)
import * as React from 'react'; import './App.css'; import * as signalR from "@microsoft/signalr"; const URL = "http://localhost:7071/api"; const list: string[] = []; interface MessageProps { HubConnection: signalR.HubConnection } const Messages: React.FC = (props: MessageProps) => { const { HubConnection } = props; React.useEffect(() => { HubConnection.on("newMessage", message => list.push(message)); }, []); return {list.map((message, index) => {message}
)} } const App: React.FC = () => { const hubConnection = new signalR.HubConnectionBuilder() .withUrl(URL) .configureLogging(signalR.LogLevel.Information) .build(); hubConnection.start() .catch(console.error); return } export default App;
What really confuses me is how the JS in the html file works just fine:
let messages = document.querySelector('#messages'); const apiBaseUrl = window.location.origin; console.log('base url:', apiBaseUrl); const connection = new signalR.HubConnectionBuilder() .withUrl(apiBaseUrl + '/api') .configureLogging(signalR.LogLevel.Information) .build(); connection.on('newMessage', (message) => { document.getElementById("messages").innerHTML = message; }); connection.start() .catch(console.error); but what looks like essentially the same thing in React is DOA due to a fetch failure from the SignalR JS.
The only thing I can figure is that there is in fact a CORS problem from running the react app on localhost:5173 and the Azure Function on localhost:7071, but the browser and Postman can each hit the negotiate route without any access problems, it's just the react app that fails.
network tab output from React page:
Request URL: http://localhost:7071/api/negotiate?negotiateVersion=1 Referrer Policy: strict-origin-when-cross-origin -- Edit --
I should also point out that the local.settings.json file contains:
{ ... "Host": { "LocalHttpPort": 7071, "CORS": "*", "CORSCredentials": false }, ... } Ok, so it is a CORS issue, now I'm seeing:
localhost/:1 Access to fetch at 'http://localhost:7071/api/negotiate?negotiateVersion=1' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Also tried to specify the urls in the host settings to address the above * comment:
localhost/:1 Access to fetch at 'http://localhost:7071/api/negotiate?negotiateVersion=1' from origin 'http://localhost:5123' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. Even with host set to * or specific URLs CORS is still saying no. How exactly is one supposed to set CORS for a local setup when local.settings.json is not enough?
Источник: https://stackoverflow.com/questions/779 ... on-locally
Мобильная версия