为什么自定义方法不应该使用 URL 来传输数据?

Why shouldn't custom methods use the URL for transferring data?

TL,DR; 执行custom methods时,"the HTTP configuration [...] must use the body:* clause and all remaining request message fields shall map to the HTTP request body."为什么?

我对 Google 的 API Design Guide which I'm attempting to follow with gRPC with Cloud Endpoints 有疑问。

HttpRule用于transcode HTTP/JSON to gRPC. The HttpRule reference状态:

Note that when using * in the body mapping, it is not possible to have HTTP parameters, as all fields not bound by the path end in the body.

[...] The common usage of * is in custom methods which don't use the URL at all for transferring data.

...在 Google 的 Custom Methods documentation and reinforced with Google's API Linter,

中也重复了一个观点

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

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

这允许向 /v1/messages/123456?format=true&revision=2 发送带有主体

的 HTTP PUT 请求
foo="I am foo"
bar="I am bar"
baz="I am baz"

由于映射将 body 绑定到类型 UpdateMessageRequest.Data,其余字段最终出现在查询字符串中。这是 standard methods, but not with custom) 方法中使用的方法。

自定义方法必须将 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;
}

如果 相同的元数据 在两个 standard and custom) 方法中使用,它必须作为查询字符串参数添加,或者放在正文中。

例如,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);

问题:这是什么原因?

好问题。

作为参考,我写了 the AIP on this topic 以及 lint 规则,我也是您引用的设计指南的当前维护者。

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

我能想到一些不错的 "exception cases",其中 * 以外的机构可能有意义。第一个是自定义方法,它以标准方法之一为模型,但出于某种原因应该是自定义的。第二种情况是自定义方法接受完整资源,并希望将正文设置为该资源。这将使该方法与 CreateUpdate 一致,这显然对 API 的用户有价值。

如果你有一个明确的论据来使用其他东西作为主体(特别是如果那个东西是资源本身),无论如何,使用不同的主体并告诉 linter 安静。我们写 "should" 是有原因的。

您还问过:为什么我们首先会有这个建议?

有几个原因。最大的一个是上述例外情况很少见。我在内部进行了数百次 API 评论,但实际上我想不出其中之一(这并不是说它们不存在)。大多数时候,对用户来说最好的事情是请求消息反映 HTTP 负载。

另一个原因是关键限制:将特定字段指定为正文会限制您可以在该字段外部 添加的内容,因为查询字符串在它们可以表示的内容上受到限制在类型(只是原语)和数量(URI 长度限制)方面。因为稍后更改 body 构成了重大更改,所以这在一定程度上束缚了您的手脚。显然,这可能适合您的用例,但请务必注意。

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