I created a grpc with spring boot using libraries from the "io.grpc" repository to generate my classes, I just wanted your opinion

huangapple go评论63阅读模式
英文:

I created a grpc with spring boot using libraries from the "io.grpc" repository to generate my classes, I just wanted your opinion

问题

I created a grpc with spring boot using libraries from the "io.grpc" repository to generate my classes, I just wanted your opinion am I doing it the right way? I'll post my classes and you guys give me feedback. OK?

My Controller:

@GetMapping(path = {"/item"}, produces = MediaType.APPLICATION_JSON_VALUE)
public String printMessage(@RequestParam("name") String name) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("springboot", 31217)
            .usePlaintext()
            .build();

    HelloServiceGrpc.HelloServiceBlockingStub stub
            = HelloServiceGrpc.newBlockingStub(channel);

    HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
            .setFirstName("Rafael")
            .setLastName("Fernando")
            .build());


    channel.shutdown();
    return helloResponse.getGreeting();
}

My service:

@Service
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
    private static final Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);

    @Override
    public void hello(
            HelloRequest request, StreamObserver<HelloResponse> responseObserver) {

        Map<String, Object> map = new HashMap<>();
        map.put("name", request.getFirstName());
        map.put("lastName", request.getLastName());
        ObjectMapper objectMapper = new ObjectMapper();

        String jsonString;

        try {
            jsonString = objectMapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }


        HelloResponse response = HelloResponse.newBuilder()
                .setGreeting(jsonString)
                .build();

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

    }
}

My spring boot application:

@SpringBootApplication
public class DemoApplication implements ApplicationRunner {

    public static void main(String[] args){

        SpringApplication.run(DemoApplication.class, args);

    }

    @Override
    public void run(ApplicationArguments args) throws InterruptedException, IOException {
        Server server = ServerBuilder
                .forPort(31217)
                .addService(new HelloServiceImpl()).build();

        server.start();
        server.awaitTermination();


    }
}

My class configuration:

@Configuration
public class AppConfig {
    @Bean
    public ProtobufJsonFormatHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufJsonFormatHttpMessageConverter(
                JsonFormat.parser().ignoringUnknownFields(),
                JsonFormat.printer().omittingInsignificantWhitespace()
        );
    }
}

My HelloService.proto:

syntax = "proto3";
option java_multiple_files = true;
package com.example.demo;

message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

message HelloResponse {
    string greeting = 1;
}

service HelloService {
    rpc hello(HelloRequest) returns (HelloResponse);
}

My spring project works fine on Kubernetes. In your opinion, is the structure right? Another thing I wanted to know more about is the interceptors.

英文:

I created a grpc with spring boot using libraries from the "io.grpc" repository to generate my classes, I just wanted your opinion am I doing it the right way? I'll post my classes and you guys give me feedback. OK?

My Controller:

@GetMapping(path = {&quot;/item&quot;}, produces = MediaType.APPLICATION_JSON_VALUE)
public String printMessage(@RequestParam(&quot;name&quot;) String name) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress(&quot;springboot&quot;, 31217)
            .usePlaintext()
            .build();

    HelloServiceGrpc.HelloServiceBlockingStub stub
            = HelloServiceGrpc.newBlockingStub(channel);

    HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
            .setFirstName(&quot;Rafael&quot;)
            .setLastName(&quot;Fernando&quot;)
            .build());


    channel.shutdown();
    return helloResponse.getGreeting();
}

My service:

@Service
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
    private static final Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);

    @Override
    public void hello(
            HelloRequest request, StreamObserver&lt;HelloResponse&gt; responseObserver) {

        Map&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;name&quot;, request.getFirstName());
        map.put(&quot;lastName&quot;, request.getLastName());
        ObjectMapper objectMapper = new ObjectMapper();

        String jsonString;

        try {
            jsonString = objectMapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }


        HelloResponse response = HelloResponse.newBuilder()
                .setGreeting(jsonString)
                .build();

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

    }
}

My spring boot aplication:

  @SpringBootApplication
    public class DemoApplication implements ApplicationRunner {
    
    	public static void main(String[] args){
    
    		SpringApplication.run(DemoApplication.class, args);
    
    	}
    
    	@Override
    	public void run(ApplicationArguments args) throws InterruptedException, IOException {
    		Server server = ServerBuilder
    				.forPort(31217)
    				.addService(new HelloServiceImpl()).build();
    
    		server.start();
    		server.awaitTermination();
    	

    }
}

My class configuration:

@Configuration
public class AppConfig {
    @Bean
    public ProtobufJsonFormatHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufJsonFormatHttpMessageConverter(
                JsonFormat.parser().ignoringUnknownFields(),
                JsonFormat.printer().omittingInsignificantWhitespace()
        );
    }
}

My HelloService.proto:

syntax = &quot;proto3&quot;;
option java_multiple_files = true;
package com.example.demo;

message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

message HelloResponse {
    string greeting = 1;
}

service HelloService {
    rpc hello(HelloRequest) returns (HelloResponse);
}

My spring project works fine on kubernetes. In your opinion, is the structure right? another thing i wanted to know more about is the interceptors

答案1

得分: 1

When working with gRPC, there is no such thing as an endpoint, so you don't need to declare a mapping. The equivalent to a REST endpoint in gRPC is the Remote Procedure itself.

所以,在你的 @SpringBootApplication 类中有这样的代码:

Server server = ServerBuilder
    .forPort(31217)
    .addService(new HelloServiceImpl()).build();

server.start();
server.awaitTermination();

你已经拥有一个提供调用的 gRPC 服务。

关于拦截器,从服务器的角度来看,你可以将它们类比于 Spring Boot 中的 REST 拦截器,但不同之处在于这些拦截器需要实现 gRPC 的 ServerInterceptor 而不是 HandlerInterceptor。你可以拦截响应和请求。

你也可以通过实现 ClientInterceptor 接口来为客户端添加拦截器。服务器和客户端拦截器都可以全局注册(所有传入/传出的调用都会经过它),或者你也可以以每个服务/客户端的方式注册,但这需要一些技巧。

无论如何,拦截器会以它们被添加的相反顺序进行链接和运行。因此,intercept(InterceptorA.class, InterceptorB.class) 将按以下顺序运行:InterceptorB -> InterceptorA -> Service

以下是官方 gRPC 示例存储库中展示的一个JWT身份验证拦截器示例:

public class JwtServerInterceptor implements ServerInterceptor {
  
  private JwtParser parser = Jwts.parser().setSigningKey(Constant.JWT_SIGNING_KEY);

  @Override
  public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
      Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
    String value = metadata.get(Constant.AUTHORIZATION_METADATA_KEY);

    Status status = Status.OK;
    if (value == null) {
      status = Status.UNAUTHENTICATED.withDescription("Authorization token is missing");
    } else if (!value.startsWith(Constant.BEARER_TYPE)) {
      status = Status.UNAUTHENTICATED.withDescription("Unknown authorization type");
    } else {
      Jws<Claims> claims = null;
      // remove authorization type prefix
      String token = value.substring(Constant.BEARER_TYPE.length()).trim();
      try {
        // verify token signature and parse claims
        claims = parser.parseClaimsJws(token);
      } catch (JwtException e) {
        status = Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e);
      }
      if (claims != null) {
        // set client id into current context
        Context ctx = Context.current()
            .withValue(Constant.CLIENT_ID_CONTEXT_KEY, claims.getBody().getSubject());
        return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
      }
    }

    serverCall.close(status, new Metadata());
    return new ServerCall.Listener<ReqT>() {
      // noop
    };
  }

}

以下是如何全局附加它(对所有已注册的服务)的服务器启动方法:

private void start() throws IOException {
  server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
      .addService(new GreeterImpl())
      .intercept(new JwtServerInterceptor())  // 添加 JwtServerInterceptor
      .build()
      .start();
  logger.info("Server started, listening on " + port);
  Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
      // Use stderr here since the logger may have been reset by its JVM shutdown hook.
      System.err.println("*** shutting down gRPC server since JVM is shutting down");
      AuthServer.this.stop();
      System.err.println("*** server shut down");
    }
  });
}

你可以在这里查看完整示例(以及其他示例)。

英文:

When working with gRPC there is no such thing as an endpoint so you don't need to declare a mapping. The equivalent to a REST endpoint in gRPC is the Remote Procedure itself.

So, by having this in your @SpringBootApplication class:

    Server server = ServerBuilder
.forPort(31217)
.addService(new HelloServiceImpl()).build();
server.start();
server.awaitTermination();

You already have a gRPC service serving calls.


Concerning interceptors, from a server point of view, you can think of them similarly to a REST interceptor in Spring-boot but instead of implementing HandlerInterceptor these need to implement gRPC's ServerInterceptor. You can intercept both responses and requests.

You can also add interceptors to your clients by having classes implementing ClientInterceptor. Both server/client interceptors can be registered globally (all incoming/outgoing calls go through it) or you can achieve a per-service/client style too but it requires some sort of a trick.

In any case, interceptors are chained and run in reverse order in which they were added. So intercept(InterceptorA.class, InterceptorB.class) will run in this order: InterceptorB -&gt; InterceptorA -&gt; Service

Here's an example from the official gRPC examples repository showcasing a JWT authentication interceptor:

public class JwtServerInterceptor implements ServerInterceptor {
private JwtParser parser = Jwts.parser().setSigningKey(Constant.JWT_SIGNING_KEY);
@Override
public &lt;ReqT, RespT&gt; ServerCall.Listener&lt;ReqT&gt; interceptCall(ServerCall&lt;ReqT, RespT&gt; serverCall,
Metadata metadata, ServerCallHandler&lt;ReqT, RespT&gt; serverCallHandler) {
String value = metadata.get(Constant.AUTHORIZATION_METADATA_KEY);
Status status = Status.OK;
if (value == null) {
status = Status.UNAUTHENTICATED.withDescription(&quot;Authorization token is missing&quot;);
} else if (!value.startsWith(Constant.BEARER_TYPE)) {
status = Status.UNAUTHENTICATED.withDescription(&quot;Unknown authorization type&quot;);
} else {
Jws&lt;Claims&gt; claims = null;
// remove authorization type prefix
String token = value.substring(Constant.BEARER_TYPE.length()).trim();
try {
// verify token signature and parse claims
claims = parser.parseClaimsJws(token);
} catch (JwtException e) {
status = Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e);
}
if (claims != null) {
// set client id into current context
Context ctx = Context.current()
.withValue(Constant.CLIENT_ID_CONTEXT_KEY, claims.getBody().getSubject());
return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
}
}
serverCall.close(status, new Metadata());
return new ServerCall.Listener&lt;ReqT&gt;() {
// noop
};
}
}

and here how it's being attached globally (to all registered services) in the server start method:

  private void start() throws IOException {
server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.addService(new GreeterImpl())
.intercept(new JwtServerInterceptor())  // add the JwtServerInterceptor
.build()
.start();
logger.info(&quot;Server started, listening on &quot; + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println(&quot;*** shutting down gRPC server since JVM is shutting down&quot;);
AuthServer.this.stop();
System.err.println(&quot;*** server shut down&quot;);
}
});
}

You can check the full example (among others) here.

huangapple
  • 本文由 发表于 2023年6月6日 06:28:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76410367.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定