Apache APISIX와 gRPC-Web 통합하기
Fei Han
January 25, 2022
gRPC 웹 소개
원래 Google에서 개발한 gRPC는 HTTP/2 위에서 구현된 고성능 원격 프로시저 호출 프레임워크입니다. 그러나 브라우저가 HTTP/2를 직접 노출하지 않기 때문에 웹 애플리케이션은 gRPC를 직접 사용할 수 없습니다. gRPC 웹은 이 문제를 해결하기 위한 표준화된 프로토콜입니다.
첫 번째 gRPC-웹 구현은 2018년에 JavaScript 라이브러리로 출시되었으며, 이를 통해 웹 애플리케이션이 gRPC 서비스와 직접 통신할 수 있습니다. 원리는 HTTP/1.1 및 HTTP/2와 호환되는 종단 간 gRPC 파이프라인을 생성하는 것입니다. 그런 다음 브라우저가 일반 HTTP 요청을 보내고, 브라우저와 서버 사이에 위치한 gRPC-웹 프록시가 요청과 응답을 변환합니다. gRPC와 유사하게, gRPC 웹은 웹 클라이언트와 백엔드 gRPC 서비스 간에 미리 정의된 계약을 사용합니다. Protocol Buffers는 메시지를 직렬화하고 인코딩하는 데 사용됩니다.
gRPC 웹을 사용하면 사용자는 브라우저나 Node 클라이언트를 사용하여 백엔드 gRPC 애플리케이션을 직접 호출할 수 있습니다. 그러나 브라우저 측에서 gRPC 서비스를 호출하기 위해 gRPC-웹을 사용하는 데는 몇 가지 제한 사항이 있습니다.
- 클라이언트 측 스트리밍 및 양방향 스트리밍 호출은 지원되지 않습니다.
- 도메인 간 gRPC 서비스 호출을 위해서는 서버 측에서 CORS를 구성해야 합니다.
- gRPC 서버 측은 gRPC-웹을 지원하도록 구성되어야 하거나, 브라우저와 서버 간의 호출을 변환할 수 있는 타사 서비스 에이전트가 있어야 합니다.
Apache APISIX gRPC 웹 프록시
Apache APISIX는 플러그인을 통해 gRPC 웹 프로토콜의 프록시를 지원하며, grpc-web
플러그인에서 gRPC 웹과 gRPC 서버 간의 통신 시 프로토콜 변환 및 데이터 코덱 작업을 수행합니다. 그 통신 과정은 다음과 같습니다.
gRPC 웹 클라이언트 -> Apache APISIX (프로토콜 변환 & 데이터 코덱) -> gRPC 서버
다음은 gRPC 웹 클라이언트를 구축하고 Apache APISIX를 통해 gRPC 웹 요청을 프록시하는 방법을 보여주는 완전한 예제입니다. 아래 예제에서는 Go를 gRPC 서버 서버 핸들러로, Node를 gRPC 웹 클라이언트 요청자로 사용합니다.
Protocol Buffer 구성
첫 번째 단계는 Protocol Buffer 컴파일러 및 관련 플러그인을 설치하는 것입니다.
-
protoc
및proto-grpc-*
설치.Protocol Buffer 컴파일러
protoc
와.proto
에 대한 Go, JavaScript 및 gRPC 웹 인터페이스 코드를 생성하기 위한protoc-gen-go
및protoc-gen-grpc-web
플러그인을 시스템에 설치해야 합니다.다음 스크립트를 실행하여 위 구성 요소를 설치하십시오.
#!/usr/bin/env bash set -ex PROTOBUF_VERSION="3.19.0" wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip unzip protoc-${PROTOBUF_VERSION}-linux-x86_64.zip mv bin/protoc /usr/local/bin/protoc mv include/google /usr/local/include/ chmod +x /usr/local/bin/protoc PROTO_GO_PLUGIN_VER="1.2.0" wget https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTO_GO_PLUGIN_VER}/protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz tar -zxvf protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz mv protoc-gen-go-grpc /usr/local/bin/protoc-gen-go chmod +x /usr/local/bin/protoc-gen-go PROTO_JS_PLUGIN_VER="1.3.0" wget https://github.com/grpc/grpc-web/releases/download/${PROTO_JS_PLUGIN_VER}/protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 mv protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web chmod +x /usr/local/bin/protoc-gen-grpc-web
-
SayHello
예제proto
파일 생성.// a6/echo.proto syntax = "proto3"; package a6; option go_package = "./;a6"; message EchoRequest { string message = 1; } message EchoResponse { string message = 1; } service EchoService { rpc Echo(EchoRequest) returns (EchoResponse); }
서버 측 애플리케이션 구성
-
서버 측 Go 원시 메시지 및 서비스/클라이언트 스텁 생성.
protoc -I./a6 echo.proto --go_out=plugins=grpc:./a6
-
서버 측 핸들러 인터페이스 구현.
// a6/echo.impl.go package a6 import ( "errors" "golang.org/x/net/context" ) type EchoServiceImpl struct { } func (esi *EchoServiceImpl) Echo(ctx context.Context, in *EchoRequest) (*EchoResponse, error) { if len(in.Message) <= 0 { return nil, errors.New("message invalid") } return &EchoResponse{Message: "response: " + in.Message}, nil }
-
서버 측 애플리케이션 런타임 진입 파일.
// server.go package main import ( "fmt" "log" "net" "apisix.apache.org/example/a6" "google.golang.org/grpc" ) func main() { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 50001)) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() a6.RegisterEchoServiceServer(grpcServer, &a6.EchoServiceImpl{}) if err = grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %s", err) } }
-
서버 측 서비스 컴파일 및 시작.
go build -o grpc-server server.go ./grpc-server
클라이언트 프로그램 구성
-
클라이언트 측
proto
코드 생성.클라이언트 측 JavaScript 원시 메시지, 서비스/클라이언트 스텁 및 gRPC 웹의 JavaScript 인터페이스 코드를 생성합니다.
gRPC 웹의
proto
플러그인은 두 가지 모드의 코드 생성을 제공합니다.-
mode=grpcwebtext: 기본 생성 코드는 grpc-web-text 형식으로 페이로드를 전송합니다.
-
Content-type: application/grpc-web-text
-
페이로드는 base64 인코딩 사용
-
단일 및 서버 스트리밍 호출 지원
-
mode=grpcweb: 페이로드를 바이너리 protobuf 형식으로 전송합니다.
- Content-type: application/grpc-web+proto
- 페이로드는 바이너리 protobuf 형식
- 현재는 단일 호출만 지원
protoc -I=./a6 echo.proto --js_out=import_style=commonjs:./a6 --grpc-web_out=import_style=commonjs,mode=grpcweb:./a6
-
-
클라이언트 측 종속성 설치.
npm i grpc-web npm i google-protobuf
-
클라이언트 측 진입 파일 실행.
// client.js const {EchoRequest} = require('./a6/echo_pb'); const {EchoServiceClient} = require('./a6/echo_grpc_web_pb'); // Apache APISIX의 진입점에 연결 let echoService = new EchoServiceClient('http://127.0.0.1:9080'); let request = new EchoRequest(); request.setMessage("hello") echoService.echo(request, {}, function (err, response) { if (err) { console.log(err.code); console.log(err.message); } else { console.log(response.getMessage()); } });
-
최종 프로젝트 구조
$ tree . ├── a6 │ ├── echo.impl.go │ ├── echo.pb.go │ ├── echo.proto │ ├── echo_grpc_web_pb.js │ └── echo_pb.js ├── client.js ├── server.go ├── go.mod ├── go.sum ├── package.json └── package-lock.json
위 단계를 완료하면 gRPC 서버 서버 애플리케이션과 gRPC 웹 클라이언트 애플리케이션을 구성하고, 서버 애플리케이션을 시작하여 50001
포트에서 요청을 수신할 준비가 되었습니다.
Apache APISIX 구성
다음으로, Apache APISIX 라우팅 플러그인 구성에서 grpc-web
플러그인을 활성화하여 gRPC 웹 요청을 프록시합니다.
-
grpc-web
프록시 플러그인 활성화.라우팅은 접두사 일치를 사용해야 합니다 (예:
/* 또는 /grpc/example/*
), 왜냐하면 gRPC 웹 클라이언트는 URI에proto
에 선언된 패키지 이름, 서비스 인터페이스 이름, 메서드 이름 등을 전달하기 때문입니다 (예:/path/a6.EchoService/Echo
), 절대 일치를 사용하면 플러그인이 URI에서proto
정보를 추출할 수 없습니다.$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/*", // 접두사 일치 모드 "plugins":{ "grpc-web":{} // gRPC 웹 플러그인 활성화 }, "upstream":{ "scheme":"grpc", "type":"roundrobin", "nodes":{ "127.0.0.1:50001":1 // gRPC 서버 수신 주소 및 포트 } } }'
-
gRPC 웹 프록시 요청 검증.
Node에서
client.js
를 실행하여 gRPC 웹 프로토콜 요청을 Apache APISIX로 보낼 수 있습니다.위의 클라이언트 측 및 서버 측 처리 로직은 각각 다음과 같습니다: 클라이언트가 서버에
hello
라는 메시지를 보내고, 서버가 메시지를 받아response: hello
로 응답합니다. 실행 결과는 다음과 같습니다.$ node client.js response: hello
-
grpc-web
프록시 플러그인 비활성화.라우팅 플러그인 구성에서 grpc-web 속성을 제거하기만 하면 됩니다.
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/*", "plugins":{ }, "upstream":{ "scheme":"grpc", "type":"roundrobin", "nodes":{ "127.0.0.1:50001":1 } } }'
요약
이 글은 Apache APISIX에서 grpc-web
을 사용하는 방법에 대한 실습 경험을 제공합니다.
GitHub Discussions에서 토론을 시작하거나 메일링 리스트를 통해 소통해 보세요.