首页 > 解决方案 > 为什么自定义方法不应该使用 URL 来传输数据?

问题描述

TL,博士;在实现自定义方法时,“HTTP 配置 [...] 必须使用该body:*子句,并且所有剩余的请求消息字段都应映射到 HTTP 请求正文。” . 为什么?

我对 Google 的API 设计指南有疑问 ,我正在尝试使用带有 Cloud Endpoints 的 gRPC来遵循该指南。

HttpRule用于将HTTP /JSON 转码为 gRPC。HttpRule参考指出:

请注意,在正文映射中使用*时,不可能有 HTTP 参数,因为所有不受路径绑定的字段都以正文结尾。

[...]的常见用法*是在根本不使用URL传输数据的自定义方法中。

...谷歌的自定义方法文档中也重复了一个观点,并在谷歌的 API Linter中得到了加强,

在映射中使用命名表示body时,会留下一个定义明确的空间来以查询字符串参数的形式添加元数据;例如分页、链接、弃用警告、错误消息)。

service Messaging {
  rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
    option (google.api.http) = {
      put: "/v1/messages/{message_id}"

      // A named reference makes it possible to use querystring params
      // and the HTTP body.
      body: "data"
    };
  }
}
message UpdateMessageRequest {
  message Data {
    string foo = 1;
    string bar = 2;
    string baz = 3;
  }

  // mapped to the URL as querystring params
  bool format = 1;
  string revision = 2;

  // mapped to the body
  Data data = 3;
}

这允许 HTTP PUT 请求/v1/messages/123456?format=true&revision=2与正文

foo="I am foo"
bar="I am bar"
baz="I am baz"

由于映射绑定body到 type UpdateMessageRequest.Data,其余字段最终在查询字符串中。这是标准方法中使用的方法,但不是自定义) 方法。

自定义方法必须映射body*. 具有自定义方法的相同 API 将是

service Messaging {
  rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
    option (google.api.http) = {
      put: "/v1/messages/{message_id}"

      // Every field not bound by the path template should be
      // mapped to the request body.
      body: "*"
    };
  }
}
message UpdateMessageRequest {
  message Data {
    string foo = 1;
    string bar = 2;
    string baz = 3;
  }

  // mapped to the body
  bool format = 1;
  string revision = 2;
  Data data = 3;
}

如果在标准自定义) 方法中使用相同的元数据,则必须将其添加为查询字符串参数,或放置在正文中。

例如,一个 Angular 应用程序会使用HttpParams

// standard method
const params = new HttpParams().append('format', true).append('revision', 2);
const request = {
  foo: "I am foo",
  bar: "I am bar",
  baz: "I am baz",
}
this.http.post<Document>(url, request, {params});

但是,自定义方法需要客户端将所有内容放在正文中:

// custom method
const request = {
  format: true,
  revision: 2,
  data: {
    foo: "I am foo",
    bar: "I am bar",
    baz: "I am baz",
  },
}
this.http.post<Document>(url, request);

问:这是什么原因?

标签: grpcgoogle-cloud-endpointsapi-designgoogle-api-linter

解决方案


好问题。

作为参考,我编写了关于这个主题的 AIP以及 lint 规则,并且我也是您所引用的设计指南的当前维护者。

首先,我会提到我们的最新指南(上面链接)特别说明了应该而不是必须。换句话说,大多数时候做正确的事,但也可能有例外。gRPC 转码实现中的任何内容都不会阻止您使用不同的方法body——我们告诉您使用*自定义方法,但我们不会对做其他事情设置任何技术障碍。

我可以想到几个很好的“例外情况”,其中一个主体*可能是有意义的。第一个是自定义方法,它以标准方法之一为模型,但出于某种原因应该是自定义的。第二个是如果自定义方法接受了完整的资源,并且想要将主体设置为该资源。这将使该方法与Createand一致Update,这显然对 API 的用户有价值。

如果你有一个明确的论据来使用其他东西作为主体(特别是如果那是资源本身),请务必使用不同的主体并告诉 linter 保持安静。我们写“应该”是有原因的。

您还问:为什么我们首先提出该建议?

有几个原因。最大的一点是,上述的例外情况很少见。我在内部进行了数百次 API 审查,但我真的想不出一个(这并不是说它们不存在)。大多数时候,对用户来说最好的事情是让请求消息镜像 HTTP 有效负载。

另一个原因是一个关键限制:将特定字段指定为正文会限制您可以在该字段之外添加的内容,因为查询字符串在类型(仅原语)和数量(URI 长度约束)中可以表示的内容受到限制。因为更改body后者会构成重大更改,所以这会在某种程度上束缚您的手。显然,这对您的用例可能没问题,但请务必注意。

无论如何,我希望这会有所帮助——哦,感谢您使用我的东西。:-)


推荐阅读