빠른 시작: Java를 사용하여 SignalR Service와 Azure Functions로 GitHub 별모양 개수를 표시하는 앱 만들기

이 문서에서는 Azure SignalR Service, Azure Functions 및 Java를 사용하여 클라이언트에 메시지를 브로드캐스트하는 서버리스 애플리케이션을 빌드합니다.

참고 항목

이 문서의 코드는 GitHub에서 사용할 수 있습니다.

필수 조건

  • Visual Studio Code와 같은 코드 편집기

  • 활성 구독이 있는 Azure 계정. 아직 계정이 없다면 무료로 계정을 만듭니다.

  • Azure Functions Core Tools. Azure 함수 앱을 로컬로 실행하는 데 사용됩니다.

    • Java의 필수 SignalR Service 바인딩은 Azure Function Core Tools 버전 2.4.419(호스트 버전 2.0.12332) 이상에서만 지원됩니다.
    • 확장을 설치하려면 Azure Functions Core Tools에 .NET Core SDK가 설치되어 있어야 합니다. 그러나 Java Azure 함수 앱을 빌드하는 데 .NET에 대한 지식이 필요하지 않습니다.
  • Java Developer Kit, 버전 11

  • Apache Maven 버전 3.0 이상

이 빠른 시작은 macOS, Windows 또는 Linux에서 실행할 수 있습니다.

Azure SignalR Service 인스턴스 만들기

이 섹션에서는 앱에 사용할 기본 Azure SignalR 인스턴스를 만듭니다. 다음 단계에서는 Azure Portal을 사용하여 새 인스턴스를 만들지만 Azure CLI를 사용할 수도 있습니다. 자세한 내용은 Azure SignalR Service CLI 참조에서 az signalr create 명령을 참조하세요.

  1. Azure Portal에 로그인합니다.
  2. 페이지의 왼쪽 상단에서 + 리소스 만들기를 선택합니다.
  3. 리소스 만들기 페이지의 검색 서비스 및 마켓플레이스 텍스트 상자에 signalr을 입력한 다음, 목록에서 SignalR Service를 선택합니다.
  4. SignalR Service 페이지에서 만들기를 선택합니다.
  5. 기본 탭에서 새 SignalR Service 인스턴스에 대한 필수 정보를 입력합니다. 다음 값을 입력합니다.
필드 제안 값 설명
구독 구독 선택 새 SignalR Service 인스턴스를 만드는 데 사용할 구독을 선택합니다.
리소스 그룹 SignalRTestResources라는 리소스 그룹 만들기 SignalR 리소스에 대한 리소스 그룹을 선택하거나 만듭니다. 기존 리소스 그룹을 사용하는 대신 이 자습서에 대한 새 리소스 그룹을 만드는 것이 유용합니다. 자습서를 완료한 후 리소스를 확보하려면 리소스 그룹을 삭제합니다.

리소스 그룹을 삭제하면 해당 그룹에 속한 리소스도 모두 삭제됩니다. 이 작업은 취소할 수 없습니다. 리소스 그룹을 삭제하기 전에 유지하려는 리소스가 포함되어 있지 않은지 확인합니다.

자세한 내용은 리소스 그룹을 사용하여 Azure 리소스 관리를 참조하세요.
리소스 이름 testsignalr SignalR 리소스에 사용할 고유한 리소스 이름을 입력합니다. 해당 지역에서 이미 testsignalr을 사용하고 있는 경우 이름이 고유해질 때까지 숫자나 문자를 추가합니다.

이름은 1~63자의 문자열로, 숫자, 영문자 및 하이픈(-) 문자만 포함할 수 있습니다. 이름은 하이픈 문자로 시작하거나 끝날 수 없고 연속되는 하이픈 문자는 유효하지 않습니다.
지역 지역 선택 새 SignalR Service 인스턴스에 적합한 지역을 선택합니다.

Azure SignalR Service는 현재 일부 지역에서 사용할 수 없습니다. 자세한 내용은 Azure SignalR Service 지역 가용성을 참조하세요.
가격 책정 계층 변경을 선택한 다음, 무료(개발/테스트 전용)를 선택합니다. 선택을 선택하여 선택한 가격 책정 계층을 확인합니다. Azure SignalR Service에는 무료, 표준 및 프리미엄의 세 가지 가격 책정 계층이 있습니다. 자습서는 필수 구성 요소에 달리 명시되지 않는 한 무료 계층을 사용합니다.

계층과 가격 책정 간의 기능 차이에 대한 자세한 내용은 Azure SignalR Service 가격 책정을 참조하세요.
서비스 모드 적절한 서비스 모드 선택 웹앱에서 SignalR 허브 논리를 호스팅하고 SignalR 서비스를 프록시로 사용하는 경우 기본값을 사용합니다. Azure Functions와 같은 서버리스 기술을 사용하여 SignalR 허브 논리를 호스팅하는 경우 서버리스를 사용합니다.

클래식 모드는 이전 버전과의 호환성만을 위한 것이므로 사용하지 않는 것이 좋습니다.

자세한 내용은 Azure SignalR Service의 서비스 모드를 참조하세요.

SignalR 자습서의 네트워킹태그 탭에서 설정을 변경할 필요가 없습니다.

  1. 기본 탭의 아래쪽에서 검토 + 만들기 단추를 선택합니다.
  2. 검토 + 만들기 탭에서 해당 값을 검토한 다음, 만들기를 선택합니다. 배포가 완료되는 데 몇 분 정도 걸립니다.
  3. 배포가 완료되면 리소스로 이동 단추를 선택합니다.
  4. SignalR 리소스 페이지의 Settings 아래 왼쪽 메뉴에서 Keys를 선택합니다.
  5. 기본 키의 연결 문자열을 복사합니다. 이 자습서의 뒷부분에서 앱을 구성하려면 이 연결 문자열이 필요합니다.

Azure 함수 앱을 구성하고 실행합니다.

Azure Function Core Tools, Java(샘플의 버전 11) 및 Maven이 설치되어 있는지 확인합니다.

  1. Maven을 사용하여 프로젝트를 초기화합니다.

    mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype -DjavaVersion=11
    

    Maven은 프로젝트 생성을 완료하는 데 필요한 값을 요청합니다. 다음 값을 제공합니다.

    prompt 설명
    groupId com.signalr Java에 대한 패키지 명명 규칙에 따라 모든 프로젝트에서 프로젝트를 고유하게 식별하는 값입니다.
    artifactId java 버전 번호가 없는 jar의 이름인 값입니다.
    version 1.0-SNAPSHOT 기본값을 선택합니다.
    패키지 com.signalr 생성된 함수 코드에 대한 Java 패키지인 값입니다. 기본값을 사용하세요.
  2. src/main/java/com/signalr 폴더로 이동하고 다음 코드를 Function.java에 복사합니다.

    package com.signalr;
    
    import com.google.gson.Gson;
    import com.microsoft.azure.functions.ExecutionContext;
    import com.microsoft.azure.functions.HttpMethod;
    import com.microsoft.azure.functions.HttpRequestMessage;
    import com.microsoft.azure.functions.HttpResponseMessage;
    import com.microsoft.azure.functions.HttpStatus;
    import com.microsoft.azure.functions.annotation.AuthorizationLevel;
    import com.microsoft.azure.functions.annotation.FunctionName;
    import com.microsoft.azure.functions.annotation.HttpTrigger;
    import com.microsoft.azure.functions.annotation.TimerTrigger;
    import com.microsoft.azure.functions.signalr.*;
    import com.microsoft.azure.functions.signalr.annotation.*;
    
    import org.apache.commons.io.IOUtils;
    
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.net.http.HttpResponse.BodyHandlers;
    import java.nio.charset.StandardCharsets;
    import java.util.Optional;
    
    public class Function {
        private static String Etag = "";
        private static String StarCount;
    
        @FunctionName("index")
        public HttpResponseMessage run(
                @HttpTrigger(
                    name = "req",
                    methods = {HttpMethod.GET},
                    authLevel = AuthorizationLevel.ANONYMOUS)HttpRequestMessage<Optional<String>> request,
                final ExecutionContext context) throws IOException {
    
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("content/index.html");
            String text = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
            return request.createResponseBuilder(HttpStatus.OK).header("Content-Type", "text/html").body(text).build();
        }
    
        @FunctionName("negotiate")
        public SignalRConnectionInfo negotiate(
                @HttpTrigger(
                    name = "req",
                    methods = { HttpMethod.POST },
                    authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> req,
                @SignalRConnectionInfoInput(
                    name = "connectionInfo",
                    hubName = "serverless") SignalRConnectionInfo connectionInfo) {
    
            return connectionInfo;
        }
    
        @FunctionName("broadcast")
        @SignalROutput(name = "$return", hubName = "serverless")
        public SignalRMessage broadcast(
            @TimerTrigger(name = "timeTrigger", schedule = "*/5 * * * * *") String timerInfo) throws IOException, InterruptedException {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest req = HttpRequest.newBuilder().uri(URI.create("https://api.github.com/repos/azure/azure-signalr")).header("User-Agent", "serverless").header("If-None-Match", Etag).build();
            HttpResponse<String> res = client.send(req, BodyHandlers.ofString());
            if (res.headers().firstValue("Etag").isPresent())
            {
                Etag = res.headers().firstValue("Etag").get();
            }
            if (res.statusCode() == 200)
            {
                Gson gson = new Gson();
                GitResult result = gson.fromJson(res.body(), GitResult.class);
                StarCount = result.stargazers_count;
            }
    
            return new SignalRMessage("newMessage", "Current start count of https://github.com/Azure/azure-signalr is:".concat(StarCount));
        }
    
        class GitResult {
            public String stargazers_count;
        }
    }
    
  3. 일부 종속성을 추가해야 합니다. pom.xml을 열고 코드에 사용된 다음 종속성을 추가합니다.

    <dependency>
        <groupId>com.microsoft.azure.functions</groupId>
        <artifactId>azure-functions-java-library-signalr</artifactId>
        <version>1.0.0</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.7</version>
    </dependency>
    
  4. 이 샘플의 클라이언트 인터페이스는 웹 페이지입니다. index 함수의 content/index.html에서 HTML 콘텐츠를 읽은 다음 resources 디렉터리에 새 파일 content/index.html을 만듭니다. 디렉터리 트리는 다음과 같아야 합니다.

        | - src
        | | - main
        | | | - java
        | | | | - com
        | | | | | - signalr
        | | | | | | - Function.java
        | | | - resources
        | | | | - content
        | | | | | - index.html
        | - pom.xml
        | - host.json
        | - local.settings.json
    
  5. index.html을 열고 다음 콘텐츠를 복사합니다.

    <html>
    
    <body>
        <h1>Azure SignalR Serverless Sample</h1>
        <div id="messages"></div>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.min.js"></script>
        <script>
        let messages = document.querySelector('#messages');
        const apiBaseUrl = window.location.origin;
        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);
        </script>
    </body>
    
    </html>
    
  6. Azure Functions가 작동하려면 스토리지 계정이 필요합니다. Azure Storage Emulator를 설치하고 실행할 수 있습니다.

  7. 이제 거의 완료되었습니다. 마지막 단계는 Azure Function 설정에 SignalR Service의 연결 문자열을 설정하는 것입니다.

    1. Azure Portal에서 검색 상자를 사용하여 이전에 배포한 Azure SignalR 인스턴스를 검색합니다. 인스턴스를 선택하여 엽니다.

      Search for the SignalR Service instance

    2. SignalR Service 인스턴스의 연결 문자열을 보려면 를 선택합니다.

      Screenshot that highlights the primary connection string.

    3. 기본 연결 문자열을 복사한 후 다음 명령을 실행합니다.

      func settings add AzureSignalRConnectionString "<signalr-connection-string>"
      # Also we need to set AzureWebJobsStorage as Azure Function's requirement
      func settings add AzureWebJobsStorage "UseDevelopmentStorage=true"
      
  8. 로컬에서 Azure Function 실행:

    mvn clean package
    mvn azure-functions:run
    

    Azure Function이 로컬에서 실행된 후 http://localhost:7071/api/index로 이동하면 현재 별 개수가 표시됩니다. GitHub에서 별 표시 또는 "별 표시 해제"를 수행하면 몇 초마다 별 개수가 새로 고쳐집니다.

리소스 정리

이 앱을 계속 사용하지 않으려면 다음 단계에 따라 이 빠른 시작에서 만든 리소스를 모두 삭제하세요. 요금은 발생되지 않습니다.

  1. Azure Portal에서 맨 왼쪽에 있는 리소스 그룹을 선택한 다음, 만든 리소스 그룹을 선택합니다. 또는 검색 상자를 사용하여 이름으로 리소스 그룹을 찾을 수 있습니다.

  2. 열린 창에서 리소스 그룹을 선택한 다음, 리소스 그룹 삭제를 클릭합니다.

  3. 새 창에서 삭제할 리소스 그룹의 이름을 입력한 다음, 삭제를 클릭합니다.

문제가 있나요? 문제 해결 가이드를 사용해 보거나 알려주세요.

다음 단계

이 빠른 시작에서는 로컬 호스트에서 실시간 서버리스 애플리케이션을 빌드하고 실행했습니다. 그런 다음, SignalR Service를 사용하여 클라이언트와 Azure Function 간 양방향 통신을 수행하는 방법을 자세히 알아봅니다.