概述
一個(gè)設(shè)計(jì)的好的 REST API 接口虏缸,需要一個(gè)嚴(yán)格的接口定義。本文試圖使用 Protobuf 作為接口設(shè)計(jì)語(yǔ)言蚁堤,設(shè)計(jì) API悴了。
創(chuàng)建文件,main/proto/Login.proto
syntax = "proto3";
package org.wcy123.api;
option java_outer_classname = "Protos";
message LoginRequest {
string name = 1;
string password = 2;
}
message LoginResponse {
enum LoginResult {
DEFAULT = 0;
OK = 1;
FAIL = 2;
}
LoginResult result = 1;
}
然后創(chuàng)建一個(gè) Bean 用于轉(zhuǎn)換 JSON 到 Java 對(duì)象
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
然后創(chuàng)建 Controller
@RestController
@Slf4j
public class MyRestController {
@RequestMapping(path = "/login", method = POST)
public ResponseEntity<Protos.LoginResponse> login(HttpServletRequest request,
HttpServletResponse response, @RequestBody Protos.LoginRequest command) {
log.info("input is {}", new JsonFormat().printToString(command));
return ResponseEntity.ok().body(Protos.LoginResponse.newBuilder()
.setResult(Protos.LoginResponse.LoginResult.OK)
.build());
}
}
Protos.LoginRequest
和 Protos.LoginResponse
是自動(dòng)生成的
測(cè)試程序
curl -v -s -H 'Content-Type: application/json' -H 'Accept: application/json' http://127.0.0.1:8080/login -d '{"name":"wcy123", "password":"123456"}'
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> POST /login HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.43.0
> Content-Type: application/json
> Accept: application/json
> Content-Length: 39
>
* upload completely sent off: 39 out of 39 bytes
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 22 Nov 2016 10:03:43 GMT
<
* Connection #0 to host 127.0.0.1 left intact
{"result": "OK"}
從外部看開违寿,請(qǐng)求和響應(yīng)都是 JSON湃交,PB 只是內(nèi)部實(shí)現(xiàn)。注意要加 Content-Type:application/json
和 Accept: application/json
兩個(gè) header藤巢。
Protobuf 支持的常用數(shù)據(jù)類型和 JSON之間的轉(zhuǎn)換關(guān)系搞莺。
syntax = "proto3";
package org.wcy123.api;
message Root {
Obj1 obj1 = 1;
bytes base64 = 2;
enum AnEnum { FOO_BAR = 0 ; GOOD = 2; BYE = 3;};
AnEnum anEnum = 3;
repeated string anArray = 4;
bool aBoolean = 5;
string aString = 6;
int32 aInt32 = 7;
uint32 aUint32 = 8;
fixed32 aFixed = 9;
int64 aInt64 = 10;
uint64 aUint64 = 11;
fixed64 aFixed64 = 12;
float aFloat = 13;
double aDouble = 14;
//Timestamp aTimestamp = 15;
//Duration aDuaration = 16;
oneof anOneOf {
string eitherString = 17;
int32 orAnInt = 18;
}
}
message Obj1{
string name = 1;
}
我寫了一個(gè)例子,用于實(shí)驗(yàn)所有的改動(dòng)掂咒,具體文檔才沧,參考 https://developers.google.com/protocol-buffers/docs/proto3#json
這個(gè)轉(zhuǎn)換成 JSON 是下面這個(gè)樣子迈喉。
{
"obj1": {
"name": "YourName"
},
"base64": "abc123!?$*&()'-=@~",
"aBoolean": true,
"aString": "hello world",
"aInt32": -32,
"aUint32": 32,
"aFixed": 64,
"aInt64": -64,
"aUint64": 64,
"aFixed64": 128,
"aFloat": 1,
"aDouble": 2,
"eitherString": "Now It is a String, not an integer"
}
注意到 oneOf
使用字段名稱來表示哪一個(gè)字段被選擇了。
細(xì)節(jié)
依賴關(guān)系
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.0.0-beta-1</version>
</dependency>
需要插件
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocExecutable>/usr/local/bin/protoc</protocExecutable>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
默認(rèn)值的處理
注意到在 OK
和 FAIL
前面温圆,我放了一個(gè)無用的 DEFAULT
值挨摸。如果沒有這個(gè)值,那么 JSON 轉(zhuǎn)換的時(shí)候岁歉,認(rèn)為 result
是默認(rèn)值得运,那么就不會(huì)包含這個(gè)字段。
- 如果要強(qiáng)制包含這個(gè)字段锅移,那么填一個(gè)無用的默認(rèn)值熔掺,占位,永遠(yuǎn)不用這個(gè)默認(rèn)值非剃。
- 如果想節(jié)省帶寬置逻,默認(rèn)值就不傳輸?shù)脑挘敲淳捅A暨@個(gè)行為备绽。接收端需要判斷如果字段不存在券坞,就使用默認(rèn)值。
不能識(shí)別的 JSON 字段
PB 忽略不能識(shí)別的字段肺素。