英文:
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 = {"/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 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 = "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
答案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 -> InterceptorA -> 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 <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
};
}
}
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("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");
}
});
}
You can check the full example (among others) here.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论