我正在学习如何使用gRPC,并想尝试建立客户端-服务器连接。服务器(在Elixir中)可以工作,尽管我有一些问题。但是由于我主要是后端开发人员,因此在Angular中实施它会遇到更多麻烦,并且希望获得一些帮助。
我正在为此项目使用 Angular 8.2.9 , Angular CLI 8.3.8 和 Node 10.16.0 。
ng new test-grpc
创建了一个新的angular cli项目ng g...
生成一个模块和一个组件,并修改基本路由以使其具有工作页面和url。npm install @improbable-eng/grpc-web @types/google-protobuf google-protobuf grpc-web-client protoc ts-protoc-gen --save
.proto
文件,并将其复制到了文件夹src/proto
protoc
中添加了scripts
命令:
"protoc": "protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts.cmd --js_out=import_style=commonjs,binary:src/app/proto-gen --ts_out=service=true:src/app/proto-ts -I ./src/proto/ ./src/proto/*.proto"
src/app/proto-ts
和src/app/proto-js
中找到的js和ts文件_
替换任何-
。const getBookRequest = new GetBookRequest();
getBookRequest.setIsbn(60929871);
grpc.unary(BookService.GetBook, {
request: getBookRequest,
host: host,
onEnd: res => {
const { status, statusMessage, headers, message, trailers } = res;
if (status === grpc.Code.OK && message) {
console.log("all ok. got book: ", message.toObject());
}
}
});
我尝试过:
.proto文件():
// src/proto/user.proto
syntax = "proto3";
service UserService {
rpc ListUsers (ListUsersRequest) returns (ListUsersReply);
}
message ListUsersRequest {
string message = 1;
}
message ListUsersReply {
repeated User users = 1;
}
message User {
int32 id = 1;
string firstname = 2;
}
打字稿生成的代码(2个文件):
// src/app/proto-ts/user-pb.d.ts
import * as jspb from "google-protobuf";
export class ListUsersRequest extends jspb.Message {
getMessage(): string;
setMessage(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ListUsersRequest.AsObject;
static toObject(includeInstance: boolean, msg: ListUsersRequest): ListUsersRequest.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: ListUsersRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): ListUsersRequest;
static deserializeBinaryFromReader(message: ListUsersRequest, reader: jspb.BinaryReader): ListUsersRequest;
}
export namespace ListUsersRequest {
export type AsObject = {
message: string,
}
}
export class ListUsersReply extends jspb.Message {
clearUsersList(): void;
getUsersList(): Array<User>;
setUsersList(value: Array<User>): void;
addUsers(value?: User, index?: number): User;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ListUsersReply.AsObject;
static toObject(includeInstance: boolean, msg: ListUsersReply): ListUsersReply.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: ListUsersReply, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): ListUsersReply;
static deserializeBinaryFromReader(message: ListUsersReply, reader: jspb.BinaryReader): ListUsersReply;
}
export namespace ListUsersReply {
export type AsObject = {
usersList: Array<User.AsObject>,
}
}
export namespace User {
export type AsObject = {
id: number,
firstname: string,
}
}
// src/app/proto-ts/user-pb-service.d.ts
import * as user_pb from "./user-pb";
import {grpc} from "@improbable-eng/grpc-web";
type UserServiceListUsers = {
readonly methodName: string;
readonly service: typeof UserService;
readonly requestStream: false;
readonly responseStream: false;
readonly requestType: typeof user_pb.ListUsersRequest;
readonly responseType: typeof user_pb.ListUsersReply;
};
export class UserService {
static readonly serviceName: string;
static readonly ListUsers: UserServiceListUsers;
}
export type ServiceError = { message: string, code: number; metadata: grpc.Metadata }
export type Status = { details: string, code: number; metadata: grpc.Metadata }
interface UnaryResponse {
cancel(): void;
}
interface ResponseStream<T> {
cancel(): void;
on(type: 'data', handler: (message: T) => void): ResponseStream<T>;
on(type: 'end', handler: (status?: Status) => void): ResponseStream<T>;
on(type: 'status', handler: (status: Status) => void): ResponseStream<T>;
}
interface RequestStream<T> {
write(message: T): RequestStream<T>;
end(): void;
cancel(): void;
on(type: 'end', handler: (status?: Status) => void): RequestStream<T>;
on(type: 'status', handler: (status: Status) => void): RequestStream<T>;
}
interface BidirectionalStream<ReqT, ResT> {
write(message: ReqT): BidirectionalStream<ReqT, ResT>;
end(): void;
cancel(): void;
on(type: 'data', handler: (message: ResT) => void): BidirectionalStream<ReqT, ResT>;
on(type: 'end', handler: (status?: Status) => void): BidirectionalStream<ReqT, ResT>;
on(type: 'status', handler: (status: Status) => void): BidirectionalStream<ReqT, ResT>;
}
export class UserServiceClient {
readonly serviceHost: string;
constructor(serviceHost: string, options?: grpc.RpcOptions);
listUsers(
requestMessage: user_pb.ListUsersRequest,
metadata: grpc.Metadata,
callback: (error: ServiceError|null, responseMessage: user_pb.ListUsersReply|null) => void
): UnaryResponse;
listUsers(
requestMessage: user_pb.ListUsersRequest,
callback: (error: ServiceError|null, responseMessage: user_pb.ListUsersReply|null) => void
): UnaryResponse;
}
尝试在组件文件中使用它们:
// src/app/modules/user/pages/list/list.component.ts
import { Component, OnInit } from '@angular/core';
import { grpc } from "@improbable-eng/grpc-web";
import { UserService } from '../../../../proto-ts/user-pb-service.d'
import { ListUsersRequest } from '../../../../proto-ts/user-pb.d'
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.scss']
})
export class ListComponent implements OnInit {
constructor() { }
ngOnInit() {
const listUsersRequest = new ListUsersRequest();
listUsersRequest.setMessage("Hello world");
grpc.unary(UserService.ListUsers, {
request: listUsersRequest,
host: "0.0.0.0:50051",
onEnd: res => {
const { status, statusMessage, headers, message, trailers } = res;
if (status === grpc.Code.OK && message) {
console.log("all ok. got the user list: ", message.toObject());
} else {
console.log("error");
}
}
});
}
}
我希望能够在组件(或服务)中使用非角度打字稿代码。
在生成上面的代码(但在组件文件中)时,不应对其进行修改,因为.proto
文件上的任何更改都将覆盖在这两个生成的文件中所做的任何修改。< / p>
目前,我受到这两个错误消息的阻止,这两个错误消息的显示取决于我最后保存的文件:TypeError: _proto_ts_user_pb_d__WEBPACK_IMPORTED_MODULE_4__.ListUsersRequest is not a constructor
(如果我最后保存了一个protobuf生成的文件,则在控制台中没有错误)或{{1 }}(如果我最后保存了一个角度文件,则在控制台中会显示相同的错误消息。)
在src\app\proto-ts\user-pb-service.d.ts and src\app\proto-ts\user-pb.d.ts is missing from the TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property.
中添加“文件”或“包含”(这是由角度CLI生成的,未经任何修改)会产生另一个错误:tsconfig.json
答案 0 :(得分:0)
这不是最好的方法。可以做更多的工作(如果您的项目不大就已经很大)。
我最近又回到了这个问题。不想尝试新事物,所以我按照以下步骤创建了一个新的角度项目:
现在它对我有用。
正如我所说,这不是最佳解决方案,因为最初的问题仍然存在于最初的项目中。但这总比没有好。
答案 1 :(得分:0)
我知道这个问题很旧,您已经解决了这个问题。
但是我想补充两点:
grpc-web堆栈过于复杂。 protobuf JavaScript API的文档很少,grpc-web也不是更好,并且在顶部生成TypeScript声明只会使情况变得更糟。
官方堆栈还有其他替代品!例如ts-proto生成纯TypeScript。
我在使用grpc web和Angular方面经历了非常相似的经历,protobuf-ts出于挫败感而开始。这是一个生成TypeScript并支持grpc-web的协议插件。它是从头开始编写的,可能是可行的替代方法...
答案 2 :(得分:0)
这里的错误:
import { UserService } from '../../../../proto-ts/user-pb-service.d'
import { ListUsersRequest } from '../../../../proto-ts/user-pb.d'
正确导入:(删除模块名称末尾的“.d”)
注意 *.js 和 *.d.ts 必须是同一个文件夹。
import { UserService } from '../../../../proto-ts/user-pb-service'
import { ListUsersRequest } from '../../../../proto-ts/user-pb'
package.json: 选项编译 ts_out=service=grpc-web:
"scripts": {
"grpc:all": "protoc --plugin=protoc-gen-ts=node_modules\\.bin\\protoc-gen-ts.cmd --js_out=import_style=commonjs,binary:./src/app/proto/generated --ts_out=service=grpc-web:./src/app/proto/generated -I ./src/app/proto ./src/app/proto/*.proto"
},
上述编译选项适用于带有 lib "@improbable-eng/grpc-web" 的 grpc 请参阅指南:ts-protoc-gen
你的代码应该是这样的:
// src/app/modules/user/pages/list/list.component.ts
import { Component, OnInit } from '@angular/core';
import { grpc } from "@improbable-eng/grpc-web";
import { UserService } from '../../../../proto-ts/user-pb-service'
import { ListUsersRequest } from '../../../../proto-ts/user-pb'
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.scss']
})
export class ListComponent implements OnInit {
constructor() { }
ngOnInit() {
// build ProtoBuf Message to send
const listUsersRequest = new ListUsersRequest();
listUsersRequest.setMessage("Hello world");
// ClientName = ServiceName + "Client"
const service = new UserServiceClient('http://localhost:8080');
// build headers of httpRequest
// gRPC base on http2 protocol
let metadata = new grpc.Metadata();
metadata.set('header1', 'headerValue1');
metadata.set('header2', 'headerValue2');
// call gRPC service method
service.ListUsers(listUsersRequest , metadata, (error, reply) => {
if (error) {
console.log(error)
return;
}
console.log(reply));
});
}
}