【发布时间】:2022-01-20 21:50:29
【问题描述】:
我有一个具有以下堆栈的 Web 应用程序:
- UI:Flutter Web/Dart
- 服务器:开始
- 通信协议:gRPC/gRPC-Web
我已经定义了一些 protobuf 并成功地将它们编译到 Go 和 Dart 中。当我运行 Go 服务器代码时,我能够成功地使用 Kreya 进行 gRPC 调用,但是当我尝试使用 grpc/grpc_web.dart 从 Flutter 进行相同的调用时,尽管我一直遇到以下错误:
gRPC Error (code: 2, codeName: UNKNOWN, message: HTTP request completed without a status
(potential CORS issue), details: null, rawResponse: , trailers: {})
这是我的用户界面代码:
class FiltersService {
static ResponseFuture<Filters> getFilters() {
GrpcWebClientChannel channel =
GrpcWebClientChannel.xhr(Uri.parse('http://localhost:9000'));
FiltersServiceClient clientStub = FiltersServiceClient(
channel,
);
return clientStub.getFilters(Void());
}
}
后端代码:
func StartServer() {
log.Println("Starting server")
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
log.Fatalf("Unable to listen to port %v\n%v\n", port, err)
}
repositories.ConnectToMongoDB()
grpcServer = grpc.NewServer()
registerServices()
if err = grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve gRPC\n%v\n", err)
}
}
// Register services defined in protobufs to call from UI
func registerServices() {
cardsService := &services.CardsService{}
protos.RegisterCardsServiceServer(grpcServer, cardsService)
filtersService := &services.FiltersService{}
protos.RegisterFiltersServiceServer(grpcServer, filtersService)
}
如前所述,当使用 Kreya 进行调用时,API 调用成功,但 Dart 代码一直失败。
我曾尝试将 gRPC 服务器封装在 gRPC Web 代理中,但是 Dart 和 Kreya 也都失败了。这是我尝试过的代码:
func StartProxy() {
log.Println("Starting server")
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
log.Fatalf("Unable to listen to port %v\n%v\n", port, err)
}
repositories.ConnectToMongoDB()
grpcServer = grpc.NewServer()
registerServices()
grpcWebServer := grpcweb.WrapServer(grpcServer)
httpServer := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 {
grpcWebServer.ServeHTTP(w, r)
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-User-Agent, X-Grpc-Web")
w.Header().Set("grpc-status", "")
w.Header().Set("grpc-message", "")
if grpcWebServer.IsGrpcWebRequest(r) {
grpcWebServer.ServeHTTP(w, r)
}
}
}),
}
httpServer.Serve(listener)
}
func StartServer() {
StartProxy()
}
我也知道 Envoy 代理可以用来代替这个 gRPC Web 代理,但是如果我这样做,我会将 Envoy 上的端点公开为 REST API,然后它将请求作为 gRPC 调用转发.据我了解,这需要维护 2 个版本的数据模型——一个用于 UI 和 Envoy 之间的通信(以 JSON 格式),另一个用于 Envoy 和服务器之间的通信(作为 protobuf)。这是正确的理解吗?我怎样才能超越这个?
*** 编辑:***
根据 cmets 中的建议,我尝试使用 Envoy 代替 go 代理。但是,即使是现在,我也无法让它发挥作用。我现在在尝试访问 Envoy 9001 公开的端口时得到 upstream connect error or disconnect/reset before headers. reset reason: overflow,尽管我可以直接从端口 9000 上的 kreya 成功调用后端服务。
这是我的envoy.yaml:
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: host.docker.internal, port_value: 9001 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: greeter_service
max_stream_duration:
grpc_timeout_header_max: 0s
cors:
allow_origin_string_match:
- prefix: "*"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: id,token,grpc-status,grpc-message
http_filters:
- name: envoy.filters.http.grpc_web
- name: envoy.filters.http.cors
- name: envoy.filters.http.router
clusters:
- name: greeter_service
connect_timeout: 0.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
# win/mac hosts: Use address: host.docker.internal instead of address: localhost in the line below
load_assignment:
cluster_name: cluster_0
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: host.docker.internal
port_value: 9000
【问题讨论】:
-
你使用 envoy 作为你的 grpc 调用的网关代理。您不需要为它创建另一个数据模型。在这里查看我的答案:stackoverflow.com/questions/59558293/…
-
而且不需要添加http handler,使用grpc server即可。
-
@ישואוהבאותך 感谢您的回复。我一直在考虑您的建议,能够在 docker 容器中本地运行特使,但是我无法将请求转发到我的服务器。我不断收到
upstream connect error or disconnect/reset before headers. reset reason: overflow。我已经更新了 OP 以显示 Envoy 配置 -
socket_address: { address: host.docker.internal, port_value: 9001 }必须是您的 Flutter 客户端从 Web 访问的地址。尝试将其更改为socket_address: { address: 0.0.0.0, port_value: 9000 }。 p.s 我还没有测试容器化部分。 -
在你的
envoy.yaml、address: host.docker.internal和port_value: 9000末尾应该是你的grpc服务器地址。
标签: dart grpc envoyproxy grpc-go grpc-web