【问题标题】:How to use typescript protobuf generated files in a gRPC angular application如何在 gRPC Angular 应用程序中使用 typescript protobuf 生成的文件
【发布时间】:2019-10-05 10:56:26
【问题描述】:

总结问题

我正在学习如何使用 gRPC,并想尝试进行客户端-服务器连接。服务器(在 Elixir 中)工作正常,但我遇到了一些问题。但由于我主要是后端开发人员,在 Angular 中实现它时遇到了更多麻烦,希望能得到一些帮助。

我在这个项目中使用 Angular 8.2.9Angular CLI 8.3.8Node 10.16.0

我已经做了什么?

  1. 使用ng new test-grpc 创建了一个新的 Angular cli 项目
  2. 使用ng g... 生成模块和组件,并修改基本路由以具有工作页面和网址。
  3. 已安装 Protobuf 和 gRPC 库npm install @improbable-eng/grpc-web @types/google-protobuf google-protobuf grpc-web-client protoc ts-protoc-gen --save
  4. 从我的 Elixir 代码中获取 .proto 文件并将其复制到文件夹 src/proto
  5. 在 package.json 中添加了 protoccommand 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"
  6. 调用命令生成src/app/proto-tssrc/app/proto-js中的js和ts文件
  7. 将生成文件名称中的任何_ 替换为-
  8. 然后我尝试在 ngOnInit 中添加以下代码:https://github.com/improbable-eng/grpc-web/tree/master/client/grpc-web#usage-overview 如:
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());
    }
  }
});

我试过了:

  • 在构造函数参数中创建请求。
  • 寻找如何添加typescript文件(我只找到在angular项目中添加javascript文件的方法,生成的javascript比typescript重得多,没有任何解释)。
  • 要了解 jspb.Message 是什么(我仍然不知道,但我认为它与此文件相关联:https://github.com/protocolbuffers/protobuf/blob/master/js/message.js
  • 试图找到有关如何在 Angular 应用程序中实现 rGPD 的教程(404 未找到)

.proto 文件、两个 typescript 生成的文件以及尝试使用它们的组件

.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 文件的任何更改都会覆盖在这两个生成的文件中所做的任何修改。

目前,我被这两条错误消息阻止,这取决于我上次保存的文件:TypeError: _proto_ts_user_pb_d__WEBPACK_IMPORTED_MODULE_4__.ListUsersRequest is not a constructor(如果我最后保存 protobuf 生成的文件,则控制台中没有错误)或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.(如果我上次保存了一个角度文件,在控制台中显示相同的错误消息)。

tsconfig.json 中添加“文件”或“包含”(由 Angular CLI 生成,未经任何修改)会产生另一个错误:Refused to load the image 'http://localhost:4200/favicon.ico' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.

【问题讨论】:

  • 您可能也应该显示 tsconfig.json 并以更具体的方式显示错误消息:哪个类被报告为不是构造函数?缺少什么 file_name.ts?
  • 感谢您的评论。我更新了错误消息。 tsconfig.json 是由 CLI 生成的,未经任何修改(我删除了在看到新错误时添加的几行)。
  • TypeScript 对 grpc-web 的支持目前处于相当实验阶段。 protobuf 没有官方的 TypeScript 支持。您现在可以查看github.com/grpc/grpc-web/pull/626。有人试图让 grpc-web 在 Angular 应用程序中工作。
  • 我尝试在一个新的 Angular 应用程序上重现,一切正常。不过没有时间调查它为什么会这样。

标签: angular typescript protocol-buffers protobuf.js grpc-web


【解决方案1】:

我知道这个问题很老了,你已经解决了这个问题。

但我想补充两点:

  1. grpc-web 堆栈过于复杂。 protobuf JavaScript API 的文档很少,grpc-web 也好不到哪里去,在上面生成 TypeScript 声明只会让事情变得更糟。

  2. 官方堆栈有替代品! ts-proto 例如生成纯 TypeScript。

我在 grpc web 和 Angular 方面有非常相似的经历,并出于沮丧开始protobuf-ts。它是一个生成 TypeScript 并支持 grpc-web 的 protoc-plugin。它是从头开始编写的,可能是一个可行的替代方案......

【讨论】:

    【解决方案2】:

    这不是最好的方法。更多的工作(如果你的项目不是很大)。

    我最近回到了这个问题。不想尝试新事物,所以我只是按照以下步骤创建了一个新的 Angular 项目:

    1. 将 gRPC 放在首位
    2. 检查是否一切正常
    3. 从初始项目中添加一个模块
    4. 重复 2 和 3 直到没有更多模块要添加

    现在它对我有用。

    正如我所说,这不是最好的解决方案,因为最初的问题仍然存在于最初的项目中。但这总比没有好。

    【讨论】:

      【解决方案3】:

      此处出错:

      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));
          });
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-02-08
        • 1970-01-01
        • 2021-04-24
        • 1970-01-01
        • 1970-01-01
        • 2021-09-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多