Azure SignalR Service를 사용하여 Azure Functions 개발 및 구성

Azure Functions 애플리케이션은 Azure SignalR Service 바인딩을 사용하여 실시간 기능을 추가할 수 있습니다. 클라이언트 애플리케이션은 여러 언어로 제공되는 클라이언트 SDK를 사용하여 Azure SignalR Service에 연결하고 실시간 메시지를 수신합니다.

이 문서에서는 SignalR Service와 통합된 Azure 함수 앱을 개발하고 구성하는 개념을 설명합니다.

SignalR Service 구성

Azure SignalR Service는 다양한 모드에서 구성할 수 있습니다. Azure Functions와 함께 사용하는 경우 서비스는 ‘서버리스’ 모드로 구성해야 합니다.

Azure Portal에서 SignalR Service 리소스의 ‘설정’ 페이지를 찾습니다. ‘서비스 모드’를 ‘서버리스’로 설정합니다.

SignalR Service 모드

Azure Functions 개발

Azure SignalR Service 및 Azure Functions로 구축된 서버리스 실시간 애플리케이션에는 최소한 다음과 같은 두 가지 Azure Functions가 필요합니다.

  • 클라이언트가 유효한 SignalR Service 액세스 토큰 및 엔드포인트 URL을 얻기 위해 호출하는 negotiate 함수
  • SignalR Service 클라이언트로 전송된 메시지를 처리하는 하나 이상의 함수

협상 함수

클라이언트 애플리케이션에는 Azure SignalR Service에 연결하기 위해 유효한 액세스 토큰이 필요합니다. 액세스 토큰은 익명이거나 사용자 ID에 인증될 수 있습니다. 서버리스 SignalR Service 애플리케이션에는 토큰 및 기타 연결 정보(예: SignalR Service 엔드포인트 URL)를 얻기 위해 negotiate라는 HTTP 엔드포인트가 필요합니다.

HTTP 트리거 Azure 함수 및 SignalRConnectionInfo 입력 바인딩을 사용하여 연결 정보 개체를 생성합니다. 함수는 /negotiate로 끝나는 HTTP 경로를 포함해야 합니다.

C#의 클래스 기반 모델을 사용하면 SignalRConnectionInfo 입력 바인딩이 필요하지 않으며 사용자 지정 클레임을 훨씬 더 쉽게 추가할 수 있습니다. 자세한 내용은 클래스 기반 모델에서 경험 협상을 참조하세요.

negotiate 함수에 대한 자세한 내용은 Azure Functions 개발을 참조하세요.

인증된 토큰을 만드는 방법에 대한 내용은 App Service 인증 사용을 참조하세요.

SignalR Service에서 보낸 메시지 처리

SignalRTrigger 바인딩을 사용하여 SignalR Service에서 보낸 메시지를 처리합니다. 클라이언트가 메시지를 보내거나 클라이언트가 연결되거나 연결이 끊어지면 알림을 받을 수 있습니다.

자세한 내용은 SignalR Service 트리거 바인딩 참조를 참조하세요.

또한 클라이언트의 메시지가 있을 때 서비스가 함수를 트리거하도록 함수 엔드포인트를 업스트림 엔드포인트로 구성해야 합니다. 업스트림 엔드포인트를 구성하는 방법에 대한 자세한 내용은 업스트림 엔드포인트를 참조하세요.

참고 항목

SignalR Service는 서버리스 모드에서 클라이언트의 StreamInvocation 메시지를 지원하지 않습니다.

메시지 보내기 및 그룹 멤버 자격 관리

SignalR 출력 바인딩을 사용하여 Azure SignalR Service에 연결된 클라이언트에 메시지를 보냅니다. 모든 클라이언트에 메시지를 브로드캐스트하거나 일부 클라이언트에만 보낼 수 있습니다. 예를 들어 특정 사용자 ID로 인증된 클라이언트 또는 특정 그룹에만 메시지를 보냅니다.

사용자를 하나 이상의 그룹에 추가할 수 있습니다. SignalR 출력 바인딩을 사용하여 그룹에서 사용자를 추가하거나 제거할 수도 있습니다.

자세한 내용은 SignalR 출력 바인딩 참조를 참조하세요.

SignalR 허브

SignalR에는 허브라는 개념이 있습니다. 각 클라이언트 연결과 Azure Functions 보낸 각 메시지의 범위는 특정 허브로 지정됩니다. 허브를 사용하여 연결과 메시지를 논리적 네임스페이스로 구분할 수 있습니다.

클래스 기반 모델

클래스 기반 모델은 C# 전용입니다.

클래스 기반 모델은 SignalR 입력 및 출력 바인딩을 다음 기능으로 대체할 수 있는 더 나은 프로그래밍 환경을 제공합니다.

  • 보다 유연한 협상, 메시지 보내기 및 그룹 관리 환경.
  • 연결 닫기, 연결, 사용자 또는 그룹의 존재 여부 확인 등 더 많은 관리 기능이 지원됩니다.
  • 강력한 형식의 허브
  • 통합 허브 이름 및 연결 문자열 설정을 한 곳에 배치합니다.

다음 코드는 클래스 기반 모델에서 SignalR 바인딩을 작성하는 방법을 보여 줍니다.

첫째, 클래스 ServerlessHub에서 파생된 허브를 정의합니다.

[SignalRConnection("AzureSignalRConnectionString")]
public class Functions : ServerlessHub
{
    private const string HubName = nameof(Functions); // Used by SignalR trigger only

    public Functions(IServiceProvider serviceProvider) : base(serviceProvider)
    {
    }

    [Function("negotiate")]
    public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
    {
        var negotiateResponse = await NegotiateAsync(new() { UserId = req.Headers.GetValues("userId").FirstOrDefault() });
        var response = req.CreateResponse();
        response.WriteBytes(negotiateResponse.ToArray());
        return response;
    }

    [Function("Broadcast")]
    public Task Broadcast(
    [SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
    {
        return Clients.All.SendAsync("newMessage", new NewMessage(invocationContext, message));
    }

    [Function("JoinGroup")]
    public Task JoinGroup([SignalRTrigger(HubName, "messages", "JoinGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
    {
        return Groups.AddToGroupAsync(connectionId, groupName);
    }
}

Program.cs 파일에서 서버리스 허브를 등록합니다.

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(b => b.Services
        .AddServerlessHub<Functions>())
    .Build();

클래스 기반 모델의 협상 환경

SignalR 입력 바인딩 [SignalRConnectionInfoInput]을 사용하는 것보다는 클래스 기반 모델의 협상이 더 유연할 수 있습니다. 기본 클래스 ServerlessHub에는 사용자가 userId, claims 등과 같은 협상 옵션을 사용자 지정할 수 있는 메서드 NegotiateAsync이(가) 있습니다.

Task<BinaryData> NegotiateAsync(NegotiationOptions? options = null)

클래스 기반 모델에서 메시지 보내기 및 환경 관리

기본 클래스 ServerlessHub에서 제공하는 멤버에 액세스하여 메시지를 보내거나, 그룹을 관리하거나, 클라이언트를 관리할 수 있습니다.

  • ServerlessHub.Clients(으)로 클라이언트에 메시지를 전송합니다.
  • ServerlessHub.Groups(으)로 그룹에 연결 추가, 그룹에서 연결 제거 등 그룹과의 연결을 관리합니다.
  • ServerlessHub.UserGroups(으)로 그룹에 사용자를 추가하거나 그룹에서 사용자를 제거하는 등 그룹으로 사용자를 관리합니다.
  • ServerlessHub.ClientManager(으)로 연결 존재 여부 확인, 연결 닫기 등을 수행합니다.

강력한 형식의 허브

강력한 형식의 허브를 사용하면 클라이언트에 메시지를 보낼 때 강력하게 입력된 메서드를 사용할 수 있습니다. 클래스 기반 모델에서 강력하게 형식화된 허브를 사용하려면 클라이언트 메서드를 인터페이스 T(으)로 추출하고 허브 클래스를 ServerlessHub<T>에서 파생된 것으로 만드세요.

다음 코드는 클라이언트 메서드에 대한 인터페이스 샘플입니다.

public interface IChatClient
{
    Task newMessage(NewMessage message);
}

그런 다음 다음과 같이 강력하게 입력된 메서드를 사용할 수 있습니다.

[SignalRConnection("AzureSignalRConnectionString")]
public class Functions : ServerlessHub<IChatClient>
{
    private const string HubName = nameof(Functions);  // Used by SignalR trigger only

    public Functions(IServiceProvider serviceProvider) : base(serviceProvider)
    {
    }

    [Function("Broadcast")]
    public Task Broadcast(
    [SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
    {
        return Clients.All.newMessage(new NewMessage(invocationContext, message));
    }
}

참고 항목

GitHub에서 전체 프로젝트 샘플을 가져올 수 있습니다.

통합 허브 이름 및 연결 문자열 설정을 한 곳에 배치

  • 서버리스 허브의 클래스 이름은 자동으로 HubName(으)로 사용됩니다.
  • 다음과 같이 서버리스 허브 클래스에 사용되는 SignalRConnection 특성을 알아차렸을 수 있습니다.
    [SignalRConnection("AzureSignalRConnectionString")]
    public class Functions : ServerlessHub<IChatClient>
    
    서버리스 허브의 연결 문자열 위치를 사용자 지정할 수 있습니다. 이 값이 없으면 기본값 AzureSignalRConnectionString이(가) 사용됩니다.

Important

SignalR 트리거 및 서버리스 허브는 독립적입니다. 따라서 서버리스 허브 및 SignalRConnection 특성의 클래스 이름은 서버리스 허브 내에서 SignalR 트리거를 사용하더라도 SignalR 트리거의 설정을 변경하지 않습니다.

클라이언트 개발

SignalR 클라이언트 애플리케이션은 여러 언어 중 하나의 SignalR 클라이언트 SDK를 사용하여 손쉽게 Azure SignalR Service에 연결하고 해당 서비스의 메시지를 수신할 수 있습니다.

클라이언트 연결 구성

SignalR Service에 연결하려면 클라이언트가 다음 단계로 구성된 성공적인 연결 협상을 완료해야 합니다.

  1. 위에 설명된 negotiate HTTP 엔드포인트에 요청하여 유효한 연결 정보 얻기
  2. 서비스 엔드포인트 URL 및 negotiate 엔드포인트에서 가져온 액세스 토큰을 사용하여 SignalR Service에 연결

SignalR 클라이언트 SDK에는 협상 핸드셰이크를 수행하는 데 필요한 논리가 포함되어 있습니다. 협상 엔드포인트의 URL에서 negotiate 세그먼트를 뺀 값을 SDK의 HubConnectionBuilder에 전달합니다. 다음은 JavaScript의 예제입니다.

const connection = new signalR.HubConnectionBuilder()
  .withUrl("https://my-signalr-function-app.azurewebsites.net/api")
  .build();

규칙에 따라 SDK는 자동으로 /negotiate를 URL에 추가하고 이를 사용하여 협상을 시작합니다.

참고 항목

브라우저에서 JavaScript/TypeScript SDK를 사용하는 경우 함수 앱에서 CORS(원본 간 리소스 공유)를 사용하도록 설정해야 합니다.

SignalR 클라이언트 SDK를 사용하는 방법에 관한 자세한 내용은 해당 언어의 설명서를 참조하세요.

클라이언트에서 서비스로 메시지 보내기

SignalR 리소스에 대해 업스트림을 구성한 경우 SignalR 클라이언트를 사용하여 클라이언트에서 Azure Functions로 메시지를 보낼 수 있습니다. 다음은 JavaScript의 예제입니다.

connection.send("method1", "arg1", "arg2");

Azure Functions 구성

Azure SignalR Service와 통합되는 Azure 함수 앱은 지속적인 배포, zip 배포패키지에서 실행과 같은 기술을 사용하여 일반적인 Azure 함수 앱처럼 배포할 수 있습니다.

그러나 SignalR Service 바인딩을 사용하는 앱에 대한 몇 가지 특별한 고려 사항이 있습니다. 클라이언트가 브라우저에서 실행되는 경우 CORS를 사용하도록 설정해야 합니다. 또한 앱에 인증이 필요한 경우 협상 엔드포인트를 App Service 인증과 통합할 수 있습니다.

CORS 사용

JavaScript/TypeScript 클라이언트는 협상 함수에 대한 HTTP 요청을 만들어 연결 협상을 시작합니다. 클라이언트 애플리케이션이 Azure Function 앱과 다른 도메인에서 호스트되는 경우 함수 앱에서 CORS(원본 간 리소스 공유)를 사용하도록 설정해야 합니다. 그러지 않으면 브라우저에서 요청을 차단합니다.

Localhost

로컬 컴퓨터에서 함수 앱을 실행하는 경우 local.settings.jsonHost 섹션을 추가하여 CORS를 사용하도록 설정할 수 있습니다. Host 섹션에서 다음 두 개의 속성을 추가합니다.

  • CORS - 클라이언트 애플리케이션의 원본인 기준 URL을 입력합니다.
  • CORSCredentials - true로 설정하여 “withCredentials” 요청을 허용합니다.

예시:

{
  "IsEncrypted": false,
  "Values": {
    // values
  },
  "Host": {
    "CORS": "http://localhost:8080",
    "CORSCredentials": true
  }
}

클라우드 - Azure Functions CORS

Azure 함수 앱에서 CORS를 사용하려면 Azure Portal에서 함수 앱의 ‘플랫폼 기능’ 탭 아래 CORS 구성 화면으로 이동합니다.

참고 항목

CORS 구성은 Azure Functions Linux 사용 플랜에서 아직 사용할 수 없습니다. Azure API Management를 사용하여 CORS를 사용하도록 설정합니다.

SignalR 클라이언트가 협상 함수를 호출하려면 Access-Control-Allow-Credentials를 사용하여 CORS를 사용하도록 설정해야 합니다. 사용하도록 설정하려면 확인란을 선택하세요.

‘허용된 원본’ 섹션에서 웹 애플리케이션의 원본 기준 URL을 사용하여 항목을 추가합니다.

CORS 구성

클라우드 - Azure API Management

Azure API Management는 기존 백 엔드 서비스에 기능을 추가하는 API 게이트웨이를 제공합니다. API 게이트웨이를 사용하여 함수 앱에 CORS를 추가할 수 있습니다. 작업 단위 요금 가격 책정 및 월간 무료 제공이 있는 소비 계층을 제공합니다.

Azure 함수 앱을 가져오는 방법에 관한 정보는 API Management 설명서를 참조하세요. 가져온 후에는 인바운드 정책을 추가하여 Access-Control-Allow-Credentials 지원을 통해 CORS를 사용하도록 설정할 수 있습니다.

<cors allow-credentials="true">
  <allowed-origins>
    <origin>https://azure-samples.github.io</origin>
  </allowed-origins>
  <allowed-methods>
    <method>GET</method>
    <method>POST</method>
  </allowed-methods>
  <allowed-headers>
    <header>*</header>
  </allowed-headers>
  <expose-headers>
    <header>*</header>
  </expose-headers>
</cors>

API Management URL을 사용하도록 SignalR 클라이언트를 구성합니다.

App Service 인증 사용

Azure Functions에는 Facebook, Twitter, Microsoft 계정, Google, Azure Active Directory 등 인기 있는 공급자를 지원하는 기본 제공 인증이 있습니다. 이 기능은 SignalRConnectionInfo 바인딩과 통합하여 사용자 ID에 인증된 Azure SignalR Service에 대한 연결을 만들 수 있습니다. 애플리케이션은 해당 사용자 ID를 대상으로 하는 SignalR 출력 바인딩을 사용하여 메시지를 보낼 수 있습니다.

Azure Portal에 있는 함수 앱의 ‘플랫폼 기능’ 탭에서 ‘인증/권한 부여’ 설정 창을 엽니다. App Service 인증 설명서에 따라 선택한 ID 공급자를 사용하여 인증을 구성합니다.

구성된 후 인증된 HTTP 요청에는 각각 인증된 ID의 사용자 이름과 사용자 ID가 들어 있는 x-ms-client-principal-namex-ms-client-principal-id 헤더가 포함됩니다.

SignalRConnectionInfo 바인딩 구성에서 이 헤더를 사용하여 인증된 연결을 만들 수 있습니다. 다음은 x-ms-client-principal-id 헤더를 사용하는 예제 C# 협상 함수입니다.

[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
    [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
    [SignalRConnectionInfo
        (HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
        SignalRConnectionInfo connectionInfo)
{
    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
    return connectionInfo;
}

그런 다음, SignalR 메시지의 UserId 속성을 설정하여 해당 사용자에게 메시지를 보낼 수 있습니다.

[FunctionName("SendMessage")]
public static Task SendMessage(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
    [SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    return signalRMessages.AddAsync(
        new SignalRMessage
        {
            // the message will only be sent to these user IDs
            UserId = "userId1",
            Target = "newMessage",
            Arguments = new [] { message }
        });
}

다른 언어에 관한 정보는 Azure Functions 참조의 Azure SignalR Service 바인딩을 참조하세요.

다음 단계

이 문서에서는 Azure Functions를 사용하여 서버리스 SignalR Service 애플리케이션을 개발하고 구성하는 방법을 알아보았습니다. SignalR Service 개요 페이지의 빠른 시작 또는 자습서 중 하나를 사용하여 애플리케이션을 직접 만들어 봅니다.