hRPC 소개: 사용자를 위한 API의 간단한 RPC 시스템

공동 저자: 우소포 벨라 엘탄(yusdacra@GitHub, 다니엘 크론니크(Bluskript@GitHub, 제니트 브레이크 켈(pontaoski@GitHub)
hRPC는 새로운 RPC 시스템으로 우리atHarmony는 우리의 분산식 채팅 프로토콜을 개발하고 사용해 왔다.프로토콜 버퍼(Protobufs)를 유선 형식으로 사용하고 흐름 전송을 지원합니다.
hRPC는 주로 사용자를 위한 API로 가능한 한 간단하고 쉽게 사용할 수 있도록 설계되었다.
더 많은 정보를 알고 싶으면 hRPC규범here을 보십시오.

What is an RPC system?
If you know traditional API models like REST, then you can think of RPC as a more integrated version of that. Instead of defining requests by endpoint and method, requests are defined as methods on objects or services. With good code generation, an RPC system is often easier and safer to use for both clients and servers.


왜 hRPC입니까?


hRPC는 REST를 사용하여 일반적인 일원 요청을 모의하고, 웹소켓을 사용하여 흐르는 요청을 모의한다.따라서 지원하지 않는 언어를 위한 라이브러리 작성은 쉬울 것이다.
hRPC 기능:
  • 유형 안전
  • 양측은 협의를 엄격히 준수한다
  • 간이 흐르는 미디어 논리
  • 인터페이스/특징과 단점 생성이 있는 더욱 우아한 서버와 클라이언트 코드.
  • 다중 언어 코드 생성
  • 작은 요청 크기
  • 빠른 요청 해결
  • 왜 트와이프가 아니에요?


    Twirp와 hRPC는 공통점이 많지만 Twirp가 Harmony 거래 파괴자가 된 관건적인 차이점은 미디어 RPC에 대한 지원이 부족하다는 데 있다.Harmony의 비전은 Protobuf 형식으로 모든 단점을 표시하는 것이기 때문에 Twirp는 근본적으로 호환되지 않는다.

    왜 gRPC가 아니에요?


    gRPC는 사실상 RPC 시스템으로 사실상 프로토퍼와 gRPC는 자주 결합된다.그래서 문제는, 당신은 왜 hRPC로 대체하려고 합니까?
    불행하게도 gRPC는 한계가 많은데, 대부분 저급 성질 때문이다.

    네트워크 지원 부족


    Harmony에서 웹 기반 클라이언트를 지원하는 것은 필수적이며 이것도 간단하게 실현하는 필수 조건이다.gRPC는 둘 다 없습니다.gRPC의 설명에 따르면:

    It is currently impossible to implement the HTTP/2 gRPC spec in the browser, as there is simply no browser API with enough fine-grained control over the requests.


    gRPC slowloris


    gRPC 흐름은 본질적으로 장기적으로 실행되는 HTTP 요청일 뿐입니다.데이터를 보내야 할 때마다 새 HTTP/2 프레임만 보냅니다.그러나 문제는 대부분의 역방향 에이전트가 gRPC 흐름을 이해하지 못한다는 것이다.Harmony에서는 콘센트가 오랫동안 비어 있기 때문에 연결을 끊는 것이 흔하다.NGINX와 다른 리버스 에이전트는 이러한 유휴 연결을 보고 닫아서 우리의 모든 클라이언트에 문제를 일으킬 것이다.hRPC는 웹소켓의 사용에 대해 이 용례를 해결했다. 왜냐하면 역방향 에이전트가 그것들을 완전히 이해할 수 있기 때문이다.
    전반적으로 말하면 hRPC를 통해 우리는 gRPC의 대부분 장점을 보존하고 업무를 크게 간소화했다.

    왜 푹 쉬지 않습니까?


    Protobuf는 요청을 위해 JSON보다 더 촘촘한 이진 형식을 제공합니다.이것은 사용자가 그들의 메시지와 RPC에 대해 모델을 정의하여 서버와 클라이언트 코드의 생성을 간소화할 수 있도록 한다.Protobuf는 또한 이런 모델(예를 들어 확장)에 매우 유용한 기능을 가지고 있기 때문에 hRPC에 매우 적합하다.

    간단한 채팅 예시


    기본적인 채팅 예시로 hRPC를 시험해 봅시다.이것은 간단한 시스템으로 채팅 정보를 발표하고 모든 클라이언트로 전송하는 것을 지원한다.다음은 계약입니다.
    syntax = "proto3";
    
    package chat;
    
    // Empty object which is used in place of nothing
    message Empty { }
    
    // Object that represents a chat message
    message Message { string content = 1; }
    
    service Chat {
      // Endpoint to send a chat message
      rpc SendMessage(Message) returns (Empty);
      // Endpoint to stream chat messages
      rpc StreamMessages(Empty) returns (stream Message);
    }
    
    마지막으로, Dell은 다음과 같은 이점을 제공합니다.

    개시하다


    참고 계속하지 않으려면 hRPC examples repository에서 전체 서버 예제를 찾을 수 있습니다.
    이를 실현하는 서버를 만드는 것부터 시작합시다.우리는 hrpc-rs를 사용할 것이다. 이것은 hRPC의 한 Rust 실현이다.
    참고: Rust가 설치되어 있지 않으면 the rustup website에서 설치할 수 있습니다.
    우리는 cargo new chat-example --bin로 우리의 프로젝트를 만들기 시작했다.
    현재 우리는 Cargo.toml에 의존항을 추가해야 한다.
    [build-dependencies]
    # `hrpc-build` will handle generating Protobuf code for us
    # The features we enable here matches the ones we enable for `hrpc`
    hrpc-build = { version = "0.29", features = ["server", "recommended"] }
    
    [dependencies]
    # `prost` provides us with protobuf decoding and encoding
    prost = "0.9"
    # `hrpc` is the `hrpc-rs` main crate!
    # Enable hrpc's server features, and the recommended transport
    hrpc = { version = "0.29", features = ["server", "recommended"] }
    # `tokio` is the async runtime we use
    # Enable tokio's macros so we can mark our main function, and enable multi
    # threaded runtime
    tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
    # `tower-http` is a collection of HTTP related middleware
    tower-http = { version = "0.1", features = ["cors"] }
    # Logging utilities
    # `tracing` gives us the ability to log from anywhere we want
    tracing = "0.1"
    # `tracing-subscriber` gives us a terminal logger
    tracing-subscriber = "0.3"
    
    프로젝트의 사용 여부를 확인하는 것을 잊지 마세요. cargo check 컴파일링!

    Protobufs 구축


    이제 기본적인 프로토콜 버프 코드 생성을 시작합시다.
    우선, 채팅 프로토콜을 더 빠른 버전src/chat.proto으로 계속 복사합니다.
    다음에 구축 스크립트가 필요합니다.프로젝트 루트 디렉토리에 build.rs라는 파일을 생성합니다.
    // build.rs
    fn main() {
        // The path here is the path to our protocol file
        // which we copied in the previous step!
        //
        // This will generate Rust code for our protobuf definitions.
        hrpc_build::compile_protos("src/chat.proto")
            .expect("could not compile the proto");
    }
    
    마지막으로 생성된 코드를 가져와야 합니다.
    // src/main.rs
    // Our chat package generated code
    pub mod chat {
        // This imports all the generated code for you
        hrpc::include_proto!("chat");
    }
    
    // This is empty for now!
    fn main() { }
    
    현재 당신은 그것을 컴파일했는지 확인하기 위해 cargo check 를 실행할 수 있습니다.

    실시 의정서


    이 절에서 우리는 협의의 단점을 실현할 것이다.
    우선, 우리가 필요로 하는 것을 가져오는 것부터 시작한다.
    // src/main.rs
    // top of the file
    
    // Import everything from chat package, and the generated
    // server trait
    use chat::{*, chat_server::*};
    // Import the server prelude, which contains
    // often used code that is used to develop servers.
    use hrpc::server::prelude::*;
    
    이제 채팅 서버의 업무 논리를 정의합시다.이것은 간단한 예이기 때문에 우리는 tokio::sync::broadcast의 채널을 사용할 수 있다.이것은 우리가 모든 연결된 클라이언트에게 채팅 정보를 방송할 수 있게 할 것이다.
    // ... other `use` statements
    
    // The channel we will use to broadcast our chat messages
    use tokio::sync::broadcast;
    
    그런 다음 서비스 상태를 정의할 수 있습니다.
    pub struct ChatService {
        // The sender half of our broadcast channel.
        // 
        // We will use it's `.subscribe()` method to get a
        // receiver when a client connects.
        message_broadcast: broadcast::Sender<Message>,
    }
    
    그런 다음 간단한 구조 함수를 정의합니다.
    impl ChatService {
        // Creates a new `ChatService`
        fn new() -> Self {
            // Create a broadcast channel with a maximum 100
            // amount of items that can be pending. This
            // doesn't matter in our case, so the number is
            // arbitrary.
            let (tx, _) = broadcast::channel(100);
            Self {
                message_broadcast: tx,
            }
        }
    }
    
    현재 우리는 우리의 서비스를 위해 생성된 특성을 실현해야 한다.
    impl Chat for ChatService {
        // This corresponds to the SendMessage endpoint
        // 
        // `handler` is a Rust macro that is used to transform
        // an `async fn` into a properly typed hRPC trait method.
        #[handler]
        async fn send_message(&self, request: Request<Message>) -> ServerResult<Response<Empty>> {
            // we will add this in a bit
        }
    
        // This corresponds to the StreamMessages endpoint
        #[handler]
        async fn stream_messages(
            &self,
            // We don't use the request here, so we can just ignore it.
            // The leading `_` stops Rust from complaining about unused
            // variables!
            _request: Request<()>,
            socket: Socket<Message, Empty>,
        ) -> ServerResult<()> {
            // we will add this in a bit
        }
    }
    
    이제 메시지 발송부터 실제 논리에 대해 알아보겠습니다.
    #[handler]
    async fn send_message(&self, request: Request<Message>) -> ServerResult<Response<Empty>> {
        // Extract the chat message from the request
        let message = request.into_message().await?;
    
        // Try to broadcast the chat message across the channel
        // if it fails return an error
        if self.message_broadcast.send(message).is_err() {
            return Err(HrpcError::new_internal_server_error("couldn't broadcast message"));
        }
    
        // Log the message we just got
        tracing::info!("got message: {}", message.content);
    
        Ok((Empty {}).into_response())
    }
    
    흐름 논리는 매우 간단하다.브로드캐스트 채널에 가입한 후 오류가 발생할 때까지 채널의 메시지를 영원히 읽으십시오.
    #[handler]
    async fn stream_messages(
        &self,
        _request: Request<()>,
        socket: Socket<Message, Empty>,
    ) -> ServerResult<()> {
        // Subscribe to the message broadcaster
        let mut message_receiver = self.message_broadcast.subscribe();
    
        // Poll for received messages...
        while let Ok(message) = message_receiver.recv().await {
            // ...and send them to client.
            socket.send_message(message).await?;
        }
    
        Ok(())
    }
    
    이 모든 것을 main 함수에 넣으세요.우리는 새로운 채팅 서버를 만들 것이다. 그곳에서 우리는 우리의 서비스를 통해 실현할 것이다.우리는 서버의 초HTTP 전송을 사용하여 서비스를 제공하지만, 필요하면 다른 전송을 사용하여 교환할 수 있습니다.
    // ...other imports
    
    // Import our CORS middleware
    use tower_http::cors::CorsLayer;
    
    // Import the Hyper HTTP transport for hRPC
    use hrpc::server::transport::http::Hyper;
    
    // `tokio::main` is a Rust macro that converts an `async fn`
    // `main` function into a synchronous `main` function, and enables
    // you to use the `tokio` async runtime. The runtime we use is the
    // multithreaded runtime, which is what we want.
    #[tokio::main]
    async fn main() -> Result<(), BoxError> {
        // Initialize the default logging in `tracing-subscriber`
        // which is logging to the terminal
        tracing_subscriber::fmt().init();
    
        // Create our chat service
        let service = ChatServer::new(ChatService::new());
    
        // Create our transport that we will use to serve our service
        let transport = Hyper::new("127.0.0.1:2289")?;
    
        // Layer our transport for use with CORS.
        // Since this is specific to HTTP, we use the transport's layer method.
        //
        // Note: A "layer" can simply be thought of as a middleware!
        let transport = transport.layer(CorsLayer::permissive());
    
        // Serve our service with our transport
        transport.serve(service).await?;
    
        Ok(())
    }
    
    위 코드에서 CORS 레이어를 지정해야 합니다.물론 절차의 다음 단계는 이를 위해 앞부분을 작성하는 것이다.

    프런트엔드(CLI)


    웹 클라이언트 예제를 사용하지 않으려면 CLI 클라이언트at hRPC examples repository를 시도해 보십시오.이 문서에는 CLI 클라이언트 작성이 포함되지 않습니다.
    실행하려면 저장소에 링크된 후 git clone로 이동하여 실행하십시오chat/tui-client.저장소의 읽어보기 파일에도 설명이 있습니다.

    프런트엔드(Vue 3+Vite+TS)


    주의: 계속하고 싶지 않으면 hRPC examples repository 완전한 웹 클라이언트 예시를 찾을 수 있습니다.
    이 설정은 Vue 템플릿을 사용하는 기본 Vite 항목으로 모든 템플릿 데모 코드를 삭제합니다.프로젝트를 완료하면 다음 패키지를 설치합니다.cargo run npm i @protobuf-ts/runtime @protobuf-ts/runtime-rpc @harmony-dev/transport-hrpcProtobuf가 정상적으로 작동하도록 하기 위해서, 우리는 Buf 을 사용할 것입니다. 이것은 프로토콜 버퍼를 구축하기 위해 구축된 도구입니다.다음부터 시작npm i -D @protobuf-ts/plugin @protobuf-ts/protoc windicss vite-plugin-windicss:
    version: v1
    plugins:
      - name: ts
        out: gen
        opt: generate_dependencies,long_type_string
        path: ./node_modules/@protobuf-ts/plugin/bin/protoc-gen-ts
    
    위의 설정은 우리가 설치한 코드 생성기를 호출하여 롱에 문자열 표시를 사용하고 내장된 구글 형식에 코드를 생성합니다.
    이전 프로토콜을 폴더 루트 디렉터리 buf.gen.yaml 에 붙여넣고 protocol/chat.proto 실행합니다.만약 buf generate ./protocol 폴더가 나타나는 것을 보았다면, 코드 생성에 성공했습니다.✅

    구현


    UI를 구축할 때 웹 사이트를 실시간으로 미리 보는 것이 유용합니다.터미널에서 gen 실행하면 새로운 개발 서버를 시작합니다.
    전체 구현은 사이트의 주요 Vue 구성 요소인 npm run dev에서 수행됩니다.
    비즈니스 논리에 대해서는 새로운 패턴과 빛나는 Vue 3script setup syntax을 사용합니다.정의부터 시작합니다.
    <script setup lang="ts">
    </script>
    
    현재 이 블록에서 우리는 먼저 클라이언트 설정을 HrpcTransport 구조 함수에 전달함으로써 채팅 클라이언트를 만든다.
    import { ChatClient } from "../gen/chat.client";
    import { HrpcTransport } from "@harmony-dev/transport-hrpc";
    
    const client = new ChatClient(
      new HrpcTransport({
        baseUrl: "http://127.0.0.1:2289",
        insecure: true
      })
    );
    
    그런 다음 수동 메시지 목록과 텍스트 입력 내용을 정의합니다.
    const content = ref("");
    const msgs = reactive<string[]>([]);
    
    이러한 참조는 변경 사항을 반영하기 위해 UI에서 사용됩니다.
    이제 API 논리를 추가하겠습니다.
    // when the component mounts (page loads)
    onMounted(() => {
      // start streaming messages
      client.streamMessages({}).responses.onMessage((msg) => {
        // add the message to the list
        msgs.push(msg.content);
      });
    });
    
    // keyboard handler for the input
    const onKey = (ev: KeyboardEvent) => {
      if (ev.key !== "Enter") return; // only send a message on enter
      client.sendMessage({
        content: content.value,
      }); // send a message to the server
      content.value = ""; // clear the textbox later
    };
    
    이제 등록 이벤트 처리 프로그램을 입력하고 src/App.vue 순환을 사용하여 메시지를 표시하기 위해 레이아웃과 스타일을 추가합니다.
    <template>
      <div class="h-100vh w-100vw bg-surface-900 flex flex-col justify-center p-3">
        <div class="flex-1 p-3 flex flex-col gap-2 overflow-auto">
          <p class="p-3 max-w-30ch rounded-md bg-surface-800" v-for="m in msgs" :key="m">{{ m }}</p>
        </div>
        <input
          class="
            p-2
            bg-surface-700
            rounded-md
            focus:outline-none focus:ring-3
            ring-secondary-400
        mt-2
          "
          v-model="content"
          @keydown="send"
        />
      </div>
    </template>
    
    만약 이 과정들이 무슨 뜻인지 확실하지 않다면 WindiCSS에서 더 많은 정보를 보십시오.
    이렇게 해서 저희가 채팅 앱을 완성했습니다!

    기타 실현


    비록 우리는 Rust를 서버로 하고 TypeScript를 클라이언트로 사용하지만 hRPC는 크로스 언어이다.harmony-development organisation on GitHub 또 다른 실현이 있는데 대부분hRPC repo에 있다.

    좋은 웹페이지 즐겨찾기