gRPC는 마이크로서비스 아키텍처에서 고성능 통신을 위한 RPC 프레임워크로 널리 사용되고 있다.

하지만 gRPC는 브라우저 환경에서 직접 실행되지 않는다는 한계가 존재한다. 이러한 문제를 해결하고자 gRPC-WEB이 등장했다.

 

이번 글에서는 gRPC-WEB이란 무엇인지, 그리고 RESTful API와 gRPC 간 변환 방법에 대해 알아본다.

 

🤔 gRPC-Web이란?


gRPC-Web은 브라우저에서 gRPC 서비스를 호출할 수 있도록 설계된 확장 기술이다.

기본적인 gRPC는 HTTP/2 기반으로 동작하지만, 브라우저는 HTTP/2 기반의 gRPC를 직접 지원하지 않는다.

이를 해결하기 위해 gRPC-Web이 등장했다.

 

📌 gRPC-WEB의 필요성

기본적인 gRPC는 HTTP/2 스트리밍을 활용하여 빠른 통신을 지원하지만, 대부분의 브라우저에서는 HTTP/2를 통한 gRPC 요청을 직접 처리할 수 없다.

즉, 프론트엔드(React, Vue, Angular)에서 gRPC API를 직접 호출할 수 없다는 문제점이 있다.

 

이를 해결하기 위해 gRPC-Web이 등장했으며, gRPC-Web은 브라우저가 HTTP/1.1을 통해 gRPC 서버와 통신할 수 있도록 변환하는 방식을 사용한다.

 

📌 gRPC-Web의 특징

  • 브라우저에서 gRPC API 호출 가능
  • gRPC 서버와 직접 통신하지 않고, gRPC-Web 프록시(Nginx, Envoy)가 중간에서 변환
  • REST API처럼 사용할 수 있으면서도, gRPC의 효율성을 유지
  • JSON 대신 Protobuf를 사용하여 빠르고 최적화된 데이터 전송

 

📌 gRPC-Web의 동작 방식

기존 gRPC는 클라이언트 <-> gRPC 서버 간 HTTP/2 기반 통신이 필요하다.

그러나 브라우저에서는 이를 직접 지원하지 않으므로 gRPC-Web 프록시(Envoy, Nginx)가 중간에서 변환 역할을 수행한다.

 

1️⃣ 웹 클라이언트(React, Vue, Angular)가 gRPC-Web 요청 전송

2️⃣ gRPC-Web 프록시(Envoy, Nginx)가 HTTP/1.1 요청을 gRPC로 변환

3️⃣ gRPC 서버가 요청을 처리하고 응답 반환

4️⃣ gRPC-Web 프록시가 gRPC 응답을 변환하여 웹 클라이언트에 전달

 

Envoy Proxy를 사용하는 이유
- gRPC-Web 클라이언트가 보낸 HTTP/1.1 요청을 gRPC 서버가 이해할 수 있는 HTTP/2 gRPC 요청으로 변환
- 일반적인 Java gRPC 서버는 gRPC-Web 프로토콜을 지원하지 않기 때문에, Envoy 프록시를 사용하거나, Armeria(JVM) 기반의 서버를 사용하는 것도 방법 중 하나이다.

 

📌  gRPC-Web 아키텍처

[웹 클라이언트 (React, Vue, Angular)]
         |
         v
[gRPC-Web 요청 (HTTP/1.1)]
         |
         v
[gRPC-Web 프록시 (Envoy, Nginx)]
         |
         v
[gRPC 서버 (Java, Go, Python)]
         |
         v
[데이터베이스 (MySQL, MongoDB)]
  • gRPC-Web을 사용하면 브라우저에서 gRPC를 직접 호출할 수 없던 문제를 해결할 수 있다.

 

📌 gRPC-Web 예제 (React + Envoy + gRPC 서버)

1) gRPC 서비스 정의 (user.proto)
syntax = "proto3";

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}

message UserRequest {
  int32 user_id = 1;
}

message UserResponse {
  int32 user_id = 1;
  string name = 2;
  string email = 3;
}

 

 

이 파일을 컴파일하여 gRPC 서버와 gRPC-Web 클라이언트를 생성한다.

 

2) gRPC 서버 구현 (Java)
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
    @Override
    public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
        UserResponse response = UserResponse.newBuilder()
            .setUserId(request.getUserId())
            .setName("Alice")
            .setEmail("alice@example.com")
            .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

 

gRPC 서버가 요청을 받아 사용자 정보를 응답한다.

 

3) Envoy 설정 (gRPC-Web 프록시)

 

gRPC-Web을 사용하려면 Envoy를 프록시 서버로 설정해야 한다.

다음과 같은 envoy.yaml 파일을 작성한다.

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      filter_chains:
        - filters:
            - name: envoy.http_connection_manager
              config:
                codec_type: AUTO
                stat_prefix: ingress_http
                route_config:
                  virtual_hosts:
                    - name: backend
                      domains: ["*"]
                      routes:
                        - match: { prefix: "/user.UserService" }
                          route: { cluster: grpc_backend }
                http_filters:
                  - name: envoy.grpc_web
                  - name: envoy.router
  clusters:
    - name: grpc_backend
      connect_timeout: 0.25s
      type: STATIC
      http2_protocol_options: {}
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: grpc_backend
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: grpc-server, port_value: 9090 }

 

Envoy는 gRPC-Web 요청을 gRPC 서버로 변환하는 역할을 한다.

 

4) React 클라이언트에서 gRPC-Web 호출
import { UserServiceClient } from "./generated/user_pb_service";
import { UserRequest } from "./generated/user_pb";
import { grpc } from "@improbable-eng/grpc-web";

const client = new UserServiceClient("http://localhost:8080", { transport: grpc.WebsocketTransport() });

const request = new UserRequest();
request.setUserId(1);

client.getUser(request, (err, response) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log("User:", response.toObject());
});

 

React 애플리케이션에서 gRPC-Web을 사용하여 서버와 통신한다.

 

📌 gRPC-Web과 gRPC의 차이점

비교 항목 gRPC (HTTP/2) gRPC-Web (HTTP/1.1)
지원 환경 백엔드 서비스, 서버-서버 통신 브라우저 클라이언트 지원
통신 방식 HTTP/2 HTTP/1.1 또는 HTTP/2
프록시 필요 여부 필요 없음 Envoy 또는 Nginx 필요
브라우저 지원 여부 지원 안됨 지원됨

 

📌 gRPC-Web 사용 시 고려해야 할 사항

  1. 프록시(Nginx, Envoy)를 반드시 설정해야 한다.
  2. 브라우저에서는 gRPC-Web 클라이언트 라이브러리를 사용해야 한다.
  3. 기본 gRPC의 모든 기능이 지원되는 것은 아니다.
    • 클라이언트 스트리밍 및 양방향 스트리밍이 제한적일 수 있다.

 

 


💭 RESTful API와 gRPC 간 변환 방법


 

기존 RESTful API를 사용하던 시스템에서는 RESTful API와 gRPC를 함께 사용해야 하는 경우가 많다.

이를 위해 RESTful API를 gRPC로 변환하거나, gRPC를 REST API로 변환하는 방법이 필요하다.

 

📌 gRPC를 RESTful API로 변환하는 이유 및 장점

  • 기존 REST 클라이언트 지원
    • 많은 시스템이 여전히 RESTful API를 사용하고 있으며, 새로운 gRPC 서버로 전환하더라도 기존 REST 클라이언트를 지원해야 할 수 있다.
  • 플랫폼 및 언어 독립성
    • RESTful API는 언어와 플랫폼에 독립적이므로 웹, 모바일, IoT 등 다양한 환경에서 쉽게 사용할 수 있다.
  • 방화벽 및 인터넷 친화력
    • gRPC는 HTTP/2를 기반으로 동작하지만, 일부 네트워크에서는 방화벽이나 프록시가 HTTP/2를 차단할 수 있다.
  • 간단한 디버깅과 모니터링
    • REST API는 JSON 형식을 사용하므로, 개발자가 디버깅하거나 로그를 분석하기 쉽다.

 

📌 RESTful API와 gRPC 간 변환 방법 3가지

RESTful API와 gRPC 간 변환 방법에는 세 가지 주요 방식이 존재한다.

 

1️⃣ gRPC-Gateway (google.api.http 옵션 활용)

2️⃣ Spring을 이용한 Gateway 직접 구현

3️⃣ Armeria Framework 사용

 

 

💡 gRPC-Gateway (google.api.http 옵션 활용)


gRPC-Gateway는 RESTful API 요청을 gRPC 요청으로 변환하는 프록시 역할을 한다.

Google에서 제공하는 google.api.http 옵션을 활용하면 REST API처럼 gRPC 서비스를 호출할 수 있다.

 

예시
syntax = "proto3";

import "google/api/annotations.proto";

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse) {
    option (google.api.http) = {
      get: "/v1/users/{id}"
  }
}

message UserRequest {
  int32 user_id = 1;
}

message UserResponse {
  int32 user_id = 1;
  string name = 2;
  string email = 3;
}
  • option (google.api.http)을 추가하면 REST API 요청을 gRPC 호출로 변환할 수 있다.
  • /v1/users/1 REST 요청을 받으면 -> GetUser gRPC 메서드가 호출된다.

 

 

  • google.api.http option을 사용
    • HTTP GET 요청을 gRPC 메서드에 바인딩
    • gRPC 본문에 있는 데이터를 메시지 필드에 매핑 가능
  • Java 서버는 option을 처리하지 못함
    • Go gRPC Gateway를 사용해야 한다.

 


💡 Spring으로 Gateway 직접 구현하는 방법


Spring Boot를 사용하면 RESTful API와 gRPC를 직접 연결하는 Gateway를 만들 수도 있다.

이 방식은 gRPC-Gateway 없이 Spring 내부에서 변환을 처리하는 방법이다.

 

RESTful API -> gRPC 변환 Gateway (Spring Boot)
@RestController
@RequestMapping("/api/user")
public class UserController {

    private final UserServiceGrpc.UserServiceBlockingStub userServiceStub;

    public UserController(ManagedChannel channel) {
        this.userServiceStub = UserServiceGrpc.newBlockingStub(channel);
    }

    @GetMapping("/{userId}")
    public ResponseEntity<UserResponse> getUser(@PathVariable int userId) {
        UserRequest request = UserRequest.newBuilder().setUserId(userId).build();
        UserResponse response = userServiceStub.getUser(request);
        return ResponseEntity.ok(response);
    }
}
  • Spring Boot를 이용해 RESTful API 요청을 직접 gRPC로 변환한다.
  • gRPC-Gateway가 필요 없이 Spring 내에서 처리할 수 있다.

 


Armerai Framework를 활용한 변환


Armeria는 Spring Boot와 통합이 가능하며, HTTP/2와 gRPC를 함께 지원하는 프레임워크이다.

이를 활용하면 REST API와 gRPC를 하나의 서버에서 제공할 수 있다.

 

Armeria를 활용한 REST <-> gRPC 변환
Server server = Server.builder()
    .http(8080)
    .serviceUnder("/api", new GrpcServiceBuilder()
        .addService(new UserServiceImpl())
        .enableHttpJsonTranscoding(true) // RESTful API 변환 지원
        .build())
    .build();
  • enableHttpJsonTranscoding(true) 옵션을 설정하면 RESTful API 요청을 자동으로 gRPC 요청으로 변환할 수 있다.

 


📌 RESTful API와 gRPC 간 변환 방법 비교

방법 특징 추가 설정 필요
gRPC-Gateway google.api.http 옵션을 활용하여 REST 요청을 gRPC로 변환 Go 서버 필요
Spring Gateway Spring Boot에서 직접 gRPC 요청을 변환 Java 서버 내에서 가능
Armeria Framework HTTP/2 및 RESTful API와 gRPC를 함께 지원 Armeria 설정 필요
  • gRPC-Gateway ➡️ RESTful API와 gRPC를 완전히 분리하고 싶은 경우 사용
  • Spring Gateway ➡️ Spring Boot 기반 애플리케이션에서 직접 변환하고 싶은 경우 사용
  • Armeria ➡️ gRPC와 RESTful API를 동시에 제공하고 싶은 경우 사용