将 gRPC Web 与 Dart 结合使用
Using gRPC Web with Dart
我有一个具有以下堆栈的 Web 应用程序:
- UI:颤动Web/Dart
- 服务器:开始
- 通讯协议:gRPC/gRPC-Web
我已经定义了一些protobufs并成功地将它们编译到Go和Dart中。当我 运行 Go 服务器代码时,我能够成功地使用 Kreya 进行 gRPC 调用,但是当我尝试使用 grpc/grpc_web.dart
从 Flutter 进行相同的调用时,尽管我保持 运行ning以下错误:
gRPC Error (code: 2, codeName: UNKNOWN, message: HTTP request completed without a status
(potential CORS issue), details: null, rawResponse: , trailers: {})
这是我的 UI 代码:
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 网络代理中,但是 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 网络代理,但是如果我这样做,我会将 Envoy 上的端点公开为 REST APIs,然后将请求作为 gRPC 调用转发。据我了解,这需要维护 2 个版本的数据模型——一个用于 UI 和 Envoy 之间的通信(在 JSON 中),另一个用于 Envoy 和服务器之间的通信(如 protobuf ).这是正确的理解吗?我怎样才能克服这个问题?
*** 编辑:***
根据评论中的建议,我尝试使用 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 代理而不是 go-proxy 来解决这个问题,尽管根据链接[=44,该解决方案并不是完全开箱即用的=].
这是工作envoy.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 9000 }
filter_chains:
- filters:
- name: envoy.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_grpc_timeout: 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,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,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
lb_policy: round_robin
http2_protocol_options: {}
load_assignment:
cluster_name: cluster_0
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: host.docker.internal
port_value: 9001
正在工作Dockerfile
FROM envoyproxy/envoy:v1.20-latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml -l debug
以及用于 运行 Envoy
的命令
docker build -t envoy .
前一个命令必须 运行 与 Dockerfile
在同一目录中
docker run -p 9000:9000 -p 9901:9901 envoy
其中 9000
和 9901
是我要公开并能够从外部访问的端口(在 envoy.yaml
中列出)
**注意:**
确保包含 http2_protocol_options: {}
。根据网上的一些可能的解决方案,我将其删除,导致connection reset due to protocol error
。我在这个问题上停留了几个小时,直到我看到 Envoy 可以将请求转发为 HTTP/2
或 HTTP/3
,并决定将其添加回来,这最终让我可以使用gRPC Web 客户端。
希望这对可能遇到此问题的其他人有所帮助。
我有一个具有以下堆栈的 Web 应用程序:
- UI:颤动Web/Dart
- 服务器:开始
- 通讯协议:gRPC/gRPC-Web
我已经定义了一些protobufs并成功地将它们编译到Go和Dart中。当我 运行 Go 服务器代码时,我能够成功地使用 Kreya 进行 gRPC 调用,但是当我尝试使用 grpc/grpc_web.dart
从 Flutter 进行相同的调用时,尽管我保持 运行ning以下错误:
gRPC Error (code: 2, codeName: UNKNOWN, message: HTTP request completed without a status
(potential CORS issue), details: null, rawResponse: , trailers: {})
这是我的 UI 代码:
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 网络代理中,但是 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 网络代理,但是如果我这样做,我会将 Envoy 上的端点公开为 REST APIs,然后将请求作为 gRPC 调用转发。据我了解,这需要维护 2 个版本的数据模型——一个用于 UI 和 Envoy 之间的通信(在 JSON 中),另一个用于 Envoy 和服务器之间的通信(如 protobuf ).这是正确的理解吗?我怎样才能克服这个问题?
*** 编辑:***
根据评论中的建议,我尝试使用 Envoy 代替 go 代理。但是,即使是现在,我也无法让它正常工作。我现在在尝试访问 Envoy 9001
公开的端口时得到 upstream connect error or disconnect/reset before headers. reset reason: overflow
,尽管我可以直接从端口 9000
.
这是我的 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 代理而不是 go-proxy 来解决这个问题,尽管根据链接[=44,该解决方案并不是完全开箱即用的=].
这是工作envoy.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 9000 }
filter_chains:
- filters:
- name: envoy.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_grpc_timeout: 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,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,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
lb_policy: round_robin
http2_protocol_options: {}
load_assignment:
cluster_name: cluster_0
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: host.docker.internal
port_value: 9001
正在工作Dockerfile
FROM envoyproxy/envoy:v1.20-latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml -l debug
以及用于 运行 Envoy
的命令docker build -t envoy .
前一个命令必须 运行 与 Dockerfile
docker run -p 9000:9000 -p 9901:9901 envoy
其中 9000
和 9901
是我要公开并能够从外部访问的端口(在 envoy.yaml
中列出)
**注意:**
确保包含 http2_protocol_options: {}
。根据网上的一些可能的解决方案,我将其删除,导致connection reset due to protocol error
。我在这个问题上停留了几个小时,直到我看到 Envoy 可以将请求转发为 HTTP/2
或 HTTP/3
,并决定将其添加回来,这最终让我可以使用gRPC Web 客户端。
希望这对可能遇到此问题的其他人有所帮助。