피아노 수업 - MuleSoft 독립 WebSocket 어플리케이션

맨날 API 날이에요.


만약 당신이 매일 피아노를 연습한다면, 당신의 연주를 녹음하고 선생님과 공유하고 싶지만, 휴대전화나 PC에 저장하고 싶지 않을 수도 있습니다.
이것이 바로 API의 용무지다!
이'피아노 수업'은 동영상을 포착하고 웹소켓이 연결된 무어소프트 API를 통해 AWS S3 버킷으로 데이터를 전송하는 앱이다.
그리고
이 프로그램은 완전히 MuleSoft가 실행될 때 실행됩니다. 네, 포함되어 있습니다.CloudHub worker 1명은 웹 서버,api 서버, 데이터베이스 서버 역할을 합니다.다른 서버는 필요 없어요!!


리소스


Demo@CloudHub

  • https://mule-worker-skd-piano-lesson.us-e2.cloudhub.io:8082/login
  • 제가 서명한 증서에 대해 사과드립니다.프레젠테이션을 사용하려면 인증서 오류를 무시하고 브라우저에서 인증서 오류를 잠시 신뢰해야 합니다.나는 이 프로젝트의 CA 서명 증서나 전용 부하 균형기가 없다.
  • 프레젠테이션에서 다음 자격 증명을 사용할 수 있습니다.
  • 사용자 이름:saki
  • 비밀번호:saki
  • 녹음 조심...우리는 상술한 계좌를 공유하기 때문에 모든 사용자가 당신의 녹음을 볼 수 있습니다.만약 녹음을 삭제하고 싶다면, 그날의 녹음을 다시 녹음해서 덮어써야 한다.
  • S3 데이터 저장소를 느슨하게 하십시오...이 응용 프로그램은 1회 녹화의 크기를 제한하지만...잘 대해주세요!
  • github

  • https://github.com/ssakoda/piano-lesson
  • 설치 설명서와 운영 설명서는 GitHub 읽어보기 파일에 있습니다.md
  • 건축과 설계


    건축하다



    이 응용 프로그램은 두 가지 서비스인 "MuleSoft CloudHub"와 "AWS S3"을 사용합니다.AWS S3는 파일 스토리지만 제공합니다.기타 모든 기능은 MuleSoft CloudHub에서 실행할 때 제공됩니다.

    무레스포드


    MuleSoft가 실행될 때 응용 프로그램은 주로 4개의 흐름 설정 파일로 구성되어 있는데 그것이 바로 웹,api, 데이터베이스와 데이터이다.웹과api는 "HTTP 탐지기"를 포함하여 사용자 장치에 함수를 공개하는 데 사용되며, 다른 것은 내부 논리의 집합일 뿐입니다.
    같은 호스트 이름과 포트에서 온 모든 포트를 제공함으로써 우리는'cookies'('SameSite=Strict'사용)를 안전하게 사용하여'세션'기능(https://'접근과'wss://'접근 사이까지!)을 실현할 수 있습니다.
    WebFlow는 React 프레임워크에서 지원하는 SPA(한 페이지 응용 프로그램) 리소스를 제공합니다.이 SPA에는 HTML 파일과 자바스크립트 파일 두 개의 자원만 포함됩니다.파일이 두 개밖에 없기 때문에, MuleSoft가 실행될 때 파일 구성 요소와 HTTP 탐지기를 사용하여 모든 HTML 파일과 자바스크립트 파일의 내용을 보내서 특정한 GET 호출에 응답할 수 있습니다.

    AWS S3


    AWS S3는'다부분 업로드'와'객체 사전 서명 URL'이라는 두 가지 유용한 기능을 제공합니다.'다부분 업로드'는 응용 프로그램 자체가 5MB를 넘을 필요가 없기 때문에 이런 응용 프로그램에 매우 유용하다.응용 프로그램은 최소한 5MB의 데이터 블록을 업로드하고 ETAg를 업로드가 끝날 때까지 되돌려줍니다.객체 사전 서명 URL은 응용 프로그램에서 비디오를 재생할 수 있도록 저장된 데이터 파일을 다운로드합니다.
    클라이언트 응용 프로그램에서 호출할 수 있지만, 이것은 HTMT/js에 AWS 증빙서류가 노출되는 심각한 안전 위험을 초래할 수 있다.이것이 바로 이 클라이언트 프로그램이 같은 실행할 때 (호스트 이름/포트) MuleSoft API를 호출하고 쿠키에서 JWT 영패를 공유하는 이유입니다. API는 AWS 인증서를 사용하여 AWS S3 API를 호출합니다.

    고려 요소



    모바일 브라우저(iOS Safari)
    이 응용 프로그램은 iOS(Safari)에서 실행할 수 없습니다.현재 iOS Safari는 '미디어 레코더' 를 지원하지 않는다. (실험적인 기능인 '미디어 레코더' 를 사용하면 작동할 수 있을 것 같지만, 버퍼는 지원하지 않는다.)미디어 흐름을 직접 처리해야 돼...

    데이터베이스 및 객체 저장소
    이 프로그램은 ObjectStore를 데이터베이스로 사용하지만 대상만 저장하고 TTL 등 제한이 있어 실제 데이터베이스가 아닌 생산 데이터베이스로 사용하기에 적합하지 않다.캐시와 임시 메모리를 사용하는 데는 대상 저장이 좋지만 사무적 데이터 저장은 안 된다.

    네트워크 서버...
    좋아요.현대화된 웹 서버 (CA와 서명한 인증서) 가 있습니다. 좋습니다.

    설계


    유동



    웹(skd 피아노 수업 웹.xml)
    이 프로세스는 SPA 리소스 및 WebSocket 엔드포인트를 제공합니다.
  • 수욕자원
    /js/login.index“,/js/login/index“,/js/login/index“,/js/login.html“,/js/login/index“,/js/login/index“,/js/login.html“,/js/login/index“,/js/login/index“,/js/login/index“,/js/login.html“,/js.
    사용자 이름/비밀번호가 조건에 부합될 때만 목적지'/'를 사용하여'/login'응답 302를 발표합니다.
  • WebSocket 엔드포인트
    wss 프로토콜이 있는 '/ws/data' 는 웹 소켓 구성 요소가 있는 웹 소켓 서버로 충당합니다.https://docs.mulesoft.com/websockets-connector/1.0/websockets-connector-cloudhub에 따르면 공유 부하 균형기(SLB)는 ws/wss 데이터를 전파하지 않는다(호스트 이름과 포트가https와 완전히 같아도!).전용 로드 밸런서(DLB)가 없기 때문에 프레젠테이션에서 "mule worker"호스트 이름과 8082 포트를 사용해야 합니다
  • .

    api(skd piano lesson.xml)
    이 프로세스는 데이터베이스 액세스, AWS S3 액세스와 같은 서버측 기능을 제공합니다.이것은 API 키트를 사용하여 라우터 이전에 세션(cookie의 JWT)이 항상 검증됩니다.'웹과 API 서버'가 있어요. 좋아요!

    데이터베이스(skd piano lessiondb.xml)
    이 유통 과류 인용 구성 요소는 웹과api 흐름 내부에서 호출된 데이터베이스 접근을 제공합니다.이 기능은 Mongo DB 서버나 유사한 것에 공개할 수 있지만, 이 프로그램은 '대상 저장' 기능의 제한을 요구합니다. (생산에 사용하지 마십시오.)

    데이터(skd piano lesson data.xml)
    이 흐름은 웹 흐름에서 호출되는 웹소켓 데이터 처리 과정을 제공합니다.WebSocket 클라이언트의 첫 번째 메시지는 Id 등의 속성을 기록하는 것이다. 이 과정은'WebSocket 키(sec WebSocket key header value)'와'기록 Id'의 맵을 대상 저장소에 저장하여 두 번째 메시지(이진법 영상 데이터)가 WebSocket 키를 통해 기록 Id와 링크할 수 있도록 한다.

    init(skd 피아노 수업 init.xml)
    이 흐름은 초기 데이터(관리자 자격 증명)를 만들기 위해 스케줄러만 표시됩니다.런타임 관리자에서 이 스케줄러를 실행하면 초기 데이터를 불러올 수 있습니다.

    구현


    세션 및 검증


    MuleSoft runtime는 진정한 웹 서버가 아니기 때문에, 나는 어쩔 수 없이 간단한 세션 기능을 실현해야 하지만, 이것은 듣기에 그렇게 어렵지 않다.키는 JWT 및 Set Cookie header 입니다.
    JWT(Json WebToken)는 양측 간에 전송할 성명을 나타내는 빈틈없는 URL 보안 방식이다.따라서 비밀 문자열로 사용자 id를 암호화하고 로그인 과정이 끝날 때 클라이언트의 브라우저 쿠키에 암호화 문자열을 설정합니다.

    MuleSoft는 암호화 기능을 제공하지만 암호화를 명확하게 처리하기 위해 "com.auth0.jwt.jwt"패키지(groupId="com.auth0",artifactId="javajwt")가 있는 자바를 사용했습니다.이것은 매우 간단하다.

    문자열을 클라이언트의 쿠키로 설정하려면 로그인 요청 응답의'set 쿠키'헤더에 다음 문자열을 설정하십시오(성공적으로 로그인하면 사용자를'/'로 안내하기 때문에 HTTP 탐지기 응답에'Location'헤더와 상태 302를 설정합니다.


    세션을 검증하려면 "쿠키"요청 헤더를 검사하고 세션 id 문자열 형식을 검증하며 JWT를 검증합니다.


    이런 검증 논리는 API 프로세스뿐만 아니라 정적 자원 접근도 설정할 수 있다. 예를 들어'/index.html'과 웹소켓 서버 프로세스는 완전히 같은 호스트 이름과 포트 번호를 사용하기 때문이다!

    그물주머니


    처음에 나는'socket.io'https://socket.io/docs/v3/index.html를 사용하여 노드가 있는 웹소켓 클라이언트를 실현했다.js, 그러나 놀랍고 불행하게도, 이것은 MuleSoft WebSocket 구성 요소를 호환하지 않는 것 같습니다.

    무슨 콘센트?목위일무

    Socket.IO is NOT a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds additional metadata to each packet. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a plain WebSocket server either.
    https://socket.io/docs/v3/index.html


    좋아요.그런 다음 브라우저 기본 WebSocket API를 사용해야 합니다."socket.io"는 바이너리 데이터와 속성 같은 여러 가지 데이터를 동시에 보낼 수 있기 때문에 매우 유용하다.
    const socket = io('ws://localhost:3000');
    
    socket.on('connect', () => {
      // either with send()
      socket.send('Hello!');
    
      // or with emit() and custom event names
      socket.emit('salutations', 'Hello!', { 'mr': 'john' }, Uint8Array.from([1, 2, 3, 4]));
    });
    
    // handle the event sent with socket.emit()
    socket.on('greetings', (elem1, elem2, elem3) => {
      console.log(elem1, elem2, elem3);
    });
    
    https://socket.io/docs/v3/index.html
    그러나 네이티브 웹소켓 API에서는 이러한'전송'을 허용하지 않습니다.내 HTTP 헤더를 사용하려고 했지만, 원본 웹 소켓 API나 Mule Soft 웹 소켓 구성 요소에서는 헤더에 접근할 수 없습니다."녹음 id"를 보내야 합니다. 이것은 이전에 API가 "/API/start"를 호출해서 생성한 것과 서로 연결된 이진 데이터입니다.
    그리고 'recording id' 를 첫 번째 웹소켓 메시지로 보내고, 이진 데이터를 다음 메시지로 보내기로 결정했습니다.서버(MuleSoft)에서 워크로드를 저장하기 위해 미디어타입 및 스토리지 매핑을 확인합니다.레코드 id는 미디어Type "text/plain"과 함께 전송되며 바이너리 데이터는 "application/octet stream"과 함께 전송됩니다.'텍스트/계획' 데이터를 받으면'atteributes.headers.sec 웹소켓 키 '와 유효 부하 (기록 id) 의 맵을 저장합니다.'application/octet stream' 이라면 sec 웹소켓 키가 대상에 저장되어 있다고 가정하고 id 녹음과 관련된 정보를 얻을 수 있습니다.
    이 매핑은 바이너리 데이터를 5MB까지 버퍼링하는 데도 사용됩니다.

    이진 데이터 처리


    AWS S3 멀티 섹션은 업로드가 편리하지만, "각 섹션의 최소 크기는 5MB(마지막 섹션 제외)여야 한다"는 제한이 있습니다.이 "5MB"는 MuleSoft가 실행될 때와 브라우저의 서버입니다...그러나 개체 저장소는 10MB의 기록을 처리할 수 있다.

    이 클라이언트 프로그램은 10초마다 이진 데이터를 보내고 서버 사이드(Mule Soft)는 이를 Object Store에 저장하고 sec 웹소켓 키를 키로 사용합니다.이때 새 바이너리 데이터와 저장된 바이너리 데이터가 연결된 다음 다시 저장됩니다. (만약 <5MB)
    연결은 DataWeave를 사용하는 방법을 찾을 수 없기 때문에 사용자 정의 Java 클래스 "Binary Utils"에서 처리됩니다.자바 매개 변수/되돌림 중, 'Binary' 형식은'byte [] '로 변환되기 때문에, Byte Array Output Stream만 연결하면 간단하고 쉽습니다.
    만약 미디어타입이 '응용 프로그램/java' 라면 사이즈 Of (이진 데이터) 는 작동할 수 있습니다. (실제로는'byte [] '이기 때문입니다.)그러나 왠지 모르게 '응용 프로그램/8바이트 흐름' 은 안 된다.

    API용 RAML


    #%RAML 1.0
    title: skd-piano-lesson
    
    types:
      Error:    
        example:
          {message : "Unknown error"}
        properties:
          message:
            type: string
            description: error message
      User: !include schemas/user-dataType.raml
      Piece:  !include schemas/piece-dataType.raml
      Recording: !include schemas/recording-dataType.raml
      Playlist: !include schemas/playlist-dataType.raml
    
    /me:
      description: Handle login user information
      get:
        displayName: Get login user information
        responses:
          200:
            body:
              application/json:
                type: User
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    /user:
      post:
        displayName: Upsert a User
        description: if matched Id is found, this upserts the given record. if not, inserts. "password" has to be given as "Non-Encrypted" string.
        body:
          application/json:
            type: User
        responses:
          200:
            body:
              application/json:
                type: User
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
      /{user_id}:
        uriParameters:
          user_id:
            displayName: User Id
            type: string
            description: the Id of the User you query
        get:
          displayName: Get a User
          description: Reterns a User if Id matched. "password" is given as "Encrypted" string.
          responses:
            200:
              body:
                application/json:
                  type: User
            401:
              description: Unauthorized error
              body:
                application/json:
                  type: Error
    
    /pieces:
      get:
        displayName: Get all Pieces of the user
        queryParameters:
          inlist:
            description: if true, this returns all "inlist=true" pieces of the user. if false, "inlist=false"
            type: boolean
            default: true
        responses:
          200:
            body:
              application/json:
                type: Piece[]
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    /piece:
      post:
        displayName: Upsert a Piece
        description: if matched Id is found, this upserts the given record. if not, inserts.
        body:
          application/json:
            type: Piece
        responses:
          200:
            body:
              application/json:
                type: Piece
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    
    /playlist:
      get:
        displayName: Get a playlist
        queryParameters:
          datestring:
            description: target datestring (YYYY-MM-DD format
            type: string
            required: true
          piece_id:
            description: if specified, this returns a list with only the piece
            type: string
            required: false
        responses:
          200:
            body:
              application/json:
                type: Playlist
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    /composers:
      get:
        displayName: Get a list of composers the user has
        responses:
          200:
            body:
              application/json:
                type: array
                items:
                  properties:
                    composer:
                      type: string
                      description: name of the composer
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    /recording:
      /{id}:
        uriParameters:
          id:
            type: string
            displayName: Recording Id
            description: Id of the recording you query
        get:
          displayName: Get a recording and the signed url
          responses:
            200:
              body:
                application/json:
                  properties:
                    recording:
                      displayName: the recording info
                      type: Recording
                    signedUrl:
                      displayName: Signed URL for AWS S3
                      type: string
            401:
              description: Unauthorized error
              body:
                application/json:
                  type: Error
    
    /start:
      post:
        displayName: start recording
        description: start recording by creating a recording record and preparing multi-part upload
        body:
          application/json:
            properties:
              piece_id:
                displayName: Piece Id
                description: Id of the piece for the recording
                type: string
                required: true
              datestring:
                displayName: Date String (YYYY-MM-DD)
                description: Lesson date in YYYY-MM-DD format
                type: string
                required: true
        responses:
          200:
            body:
              application/json:
                type: Recording
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    
    /stop:
      post:
        displayName: stop recording
        description: stop recording by completing multi-part upload
        body:
          application/json:
            properties:
              recording_id:
                displayName: Recording Id
                description: Id of the recording to stop
                type: string
                required: true
        responses:
          200:
            body:
              application/json:
                type: object
                properties:
                  Location:
                    description: URL of the recording file
                    type: string
                  Bucket:
                    description: bucket name to be stored 
                    type: string
                  Key:
                    description: Key of the recording file
                    type: string
                  ETag:
                    description: ETag for the upload
                    type: string
          401:
            description: Unauthorized error
            body:
              application/json:
                type: Error
    
    
    
    

    좋은 웹페이지 즐겨찾기