helidon-io / helidon

Java libraries for writing microservices
https://helidon.io
Apache License 2.0
3.52k stars 565 forks source link

No grpc reponse from Javascript client request #9452

Open DaveO-Home opened 1 day ago

DaveO-Home commented 1 day ago

The Helidon gRPC correctly accepts the Javascript gRPC request. However, there is no or just null response from the server.

Environment Details

build.date 2023-08-03 16:19:35 UTC build.version 3.0.6 build.revision 0c0d6790 project.version 1.0.0 project.helidon.version 4.1.2 project.flavor MP latest.helidon.version 4.1.3 default.helidon.version 4.1.3 Java(TM) SE Runtime Environment Oracle GraalVM 21.0.2+13.1 (build 21.0.2+13-LTS-jvmci-23.1-b30) Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 21.0.2+13.1 (build 21.0.2+13-LTS-jvmci-23.1-b30, mixed mode, sharing)


Sample Code:


import com.google.protobuf.Descriptors;
import golf.handicap.Golfer;
import handicap.grpc.HandicapData;
import handicap.grpc.HandicapProto;
import handicap.grpc.HandicapSetup;
import io.grpc.stub.StreamObserver;
import io.helidon.grpc.core.ResponseHelper;
import io.helidon.webserver.grpc.GrpcService;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.bind.JsonbBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class HandicapService implements GrpcService {
    private static final Logger logger = LogManager.getLogger(HandicapService.class.getName());

    public HandicapService() {
    }

    public void getGolfer(HandicapSetup request, StreamObserver<HandicapData> observer) {
        logger.info("Doing GetGolfer***********************: {}", request);
        JsonObject requestJson = Json.createObjectBuilder().add("data", request.getJson()).build();
        Golfer currentGolfer = JsonbBuilder.create().fromJson(requestJson.getString("data"), Golfer.class);
        /*
            Call logic with (currentGolfer) not implemented - sending back requested data
        */
        logger.info("Returning Data: {}", requestJson.getJsonString("data").getString());
        HandicapData handicapData = HandicapData.newBuilder()
          .setMessage("No Golfer Found")
          .setCmd(request.getCmd())
          .setJson(requestJson.getJsonString("data").getString())
          .build();
        ResponseHelper.complete(observer, handicapData);
    }

    @Override
    public Descriptors.FileDescriptor proto() {
        return HandicapProto.getDescriptor();
    }

    @Override
    public void update(Routing routing) {
        routing.unary(
          "GetGolfer", this::getGolfer
        );
    }
}

The proto:

// import "google/protobuf/struct.proto";

option java_multiple_files = true;
option java_package = "handicap.grpc";
option java_outer_classname = "HandicapProto";

package handicap.grpc;

service HandicapService {
  rpc ListCourses(Command) returns (ListCoursesResponse) {}
  rpc AddRating(Command) returns (HandicapData) {}
  rpc AddScore(Command) returns (HandicapData) {}
  rpc RemoveScore(Command) returns (HandicapData) {}
  rpc GolferScores(Command) returns (HandicapData) {}
  rpc GetGolfer (HandicapSetup) returns (HandicapData) {}
  rpc ListGolfers (Command) returns (ListPublicGolfers) {}
}

message HandicapSetup {
  int32 cmd = 1;
  string json = 2;
  string message = 3;
}

message HandicapData {
  int32 cmd = 1;
  string json = 2;
  string message = 3;
}

message Rating {
  int32 tee = 1;
  string rating = 2;
  int32 slope = 3;
  int32 par = 4;
  string color = 5;
  int32 seq = 6;
}
message Course {
  int32 id = 1;
  string name = 2;
  repeated Rating ratings = 3;
}

message ListCoursesResponse {
  repeated Course courses = 1;
}

message Golfer {
  string name = 1;
}

message ListPublicGolfers {
  repeated Golfer golfer = 1;
}

message Command {
  int32 cmd = 1;
  string key = 2;
  string json = 3;
}

The data the Helidon server accepts:

2024-10-31 17:09:11 (HandicapService:25) - INFO Doing GetGolfer***********************: cmd: 3
json: "{\"pin\":\"xx1234\",\"firstName\":\"\",\"lastName\":\"\",\"country\":\"US\",\"state\":\"AL\",\"overlap\":false,\"public\":false,\"status\":0,\"lastLogin\":\"1730419751853\",\"course\":\"\",\"tee\":\"2\",\"teeDate\":\"1730419751853\"}"
message: "Golfer Data"  #105  

Data the Server responses with:

2024-10-31 17:09:11 (HandicapService:29) - INFO Returning Data: 2024-10-31 17:35:23 (HandicapService:36) - INFO Returning Data:
 cmd: 3
json: "{\"pin\":\"xx1234\",\"firstName\":\"\",\"lastName\":\"\",\"country\":\"US\",\"state\":\"AL\",\"overlap\":false,\"public\":false,\"status\":0,\"lastLogin\":\"1730421323634\",\"course\":\"\",\"tee\":\"2\",\"teeDate\":\"1730421323634\"}"
message: "No Golfer Found" #105

Note: There is an Envoy proxy between the client/server. Does CORS and HTTP: 2

DaveO-Home commented 1 day ago

"Is your Javascript client assuming dynamic resolution of the proto file?" The client only posts the application data, it might be passing meta data under the covers. I don't see that the poxy is an issue because the grpcurl query works.

grpcurl -plaintext -d '{"cmd":3,"json":"{\"pin\":\"do1234\"}","message":"get golfer"}'
 -vv -proto src/main/proto/handicap.proto localhost:8071 handicap.grpc.HandicapService/GetGolfer

port 8071 is the proxy. The query also works via port 8061 the helidon webserver port since grpcurl works with http2. But again the proto file is required.

spericas commented 1 day ago

@DaveO-Home Could provide more info about the JS client part? The library and the JS client code that gets a null response?

DaveO-Home commented 1 day ago

You can find the code at: https://github.com/DaveO-Home/dodex-quarkus/tree/master/src/grpc/client This is a Quarkus framework that has the same code. The only difference is that in the handiap.proto file the Service was changed for HandicapIndex to HandicapService. Files of interest: proto bash script that generates the code in ./js/handicap, the gRPC javascript client. The proto is in ./protos/handicap/ This same setup on Quarkus works without the -proto parameter.

spericas commented 1 day ago

Related to issue #9454

DaveO-Home commented 1 day ago

Thanks, - Question, why does the request work and the response fails. Doesn't the request require reflection?