grpc-java 라이브러리를 사용해보자
일단 grpc 용어좀 짚고가자
채널 channel
- 네트워크 연결 관리 : 클라이언트와 서버간의 연결
- 보안 : TLS/SSL 을 통한 보안 통신 가능
- 상태 모니터링 : 채널은 서버 연결 상태(예: IDLE, CONNECTING, READY, TRANSIENT_FAILURE, SHUTDOWN)를 관리하고, 클라이언트가 이 상태를 추적할 수 있게 해줌
채널의 생성과 관리
채널은 한번 생성하면 재사용하는게 일반적임, 재사용도 다 하고 이제 쓸일 없으면 닫아야함
채널의 생성
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext() // TLS를 적용하려면 .useTransportSecurity()를 사용
.build();
채널 닫기
channel.shutdown();
스텁 stub
**"메소드 스텁(method stub) 혹은 간단히 스텁은 소프트웨어 개발에 쓰이고 다른 프로그래밍 기능을 대리하는 코드이다"
서버에 있는 함수가 Client에는 없으니까 서버의 함수를 호출하듯이 서버 함수를 대신해주는 대신맨
**
동기 스텁 (blocking stub)
동기 방식의 호출을 수행하며, 서버의 응답을 받을 때까지 클라이언트는 호출이 완료될 때까지 대기합니다.
예: HelloServiceGrpc.HelloServiceBlockingStub
비동기 스텁 (future stub)
비동기 방식으로 Future 객체를 반환합니다. 응답을 즉시 기다릴 필요 없이 비동기 작업으로 처리할 수 있습니다.
예: HelloServiceGrpc.HelloServiceFutureStub
콜백 스텁 (async stub)
비동기 방식으로 Future 객체를 반환합니다. 응답을 즉시 기다릴 필요 없이 비동기 작업으로 처리할 수 있습니다.
예: HelloServiceGrpc.HelloServiceFutureStub
스텁의 생성 및 사용
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder()
.setName("ChatGPT")
.build();
HelloResponse response = stub.sayHello(request);
System.out.println("서버 응답: " + response.getMessage());
grpc 에서 jwt 포함하여 호출하기
CallCredentials
을 확장하여 Request 마다 자동으로 Authorization Header
가 추가되도록 코드를 수정한다
grpc clinet
public class JwtCallCredentials extends CallCredentials {
private final String jwt;
public JwtCallCredentials(String jwt) {
this.jwt = jwt;
}
@Override
public void applyRequestMetadata(RequestInfo requestInfo,
Executor appExecutor,
MetadataApplier applier) {
appExecutor.execute(() -> {
try {
Metadata headers = new Metadata();
headers.put(
Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER),
"Bearer " + jwt);
applier.apply(headers);
} catch (Throwable e) {
applier.fail(Status.UNAUTHENTICATED.withCause(e));
}
});
}
}
blockingStub
를 생성할때 withCallCredentials(callCredentials);
과 같이 callCredentials 확장 객체를 넣을 수 있다
client 코드는 이게 다다
public HelloWorldClient(Channel channel, CallCredentials callCredentials) {
blockingStub = GreeterGrpc.newBlockingStub(channel).withCallCredentials(callCredentials);
}
HelloWorldClient client = new HelloWorldClient(channel, new JwtCallCredentials("asdadsad"));
client.greet(user);
grpc server
서버의 경우 ServerInterceptor
를 구현한 JwtServerInterceptor
를 생성 후 Server
객체에 등록하여 Metadata
인자에서 Authorization
헤더를 꺼내올 수 있다
public class JwtServerInterceptor implements ServerInterceptor {
private static final Logger logger = Logger.getLogger(JwtServerInterceptor.class.getName());
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
// 헤더에서 JWT 토큰 추출
String jwt = headers.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER));
if (jwt == null) {
logger.warning("JWT is null");
throw new IllegalArgumentException("jwt is null");
}
if (!jwt.startsWith("Bearer ")) {
logger.warning("not startsWith Bearer");
throw new IllegalArgumentException("not startsWith Bearer");
}
jwt = jwt.substring(7); // 'Bearer ' 제거
logger.info("Received JWT: " + jwt);
// 다음 핸들러 호출
return next.startCall(call, headers);
}
}
이런식으로 구현된 JwtServerInterceptor
를 ServerBuilder
를 통하여 등록할 수 있는데
server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.addService(ServerInterceptors.intercept(new GreeterImpl(), new JwtServerInterceptor()))
.build()
.start();
이런식으로 등록 가능하다