-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
1,344 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,100 @@ | ||
# RESTful | ||
|
||
Brief introduction of the feature. | ||
The tRPC framework uses PB to define services, but providing REST-style APIs based on the HTTP protocol is still a widespread demand. Unifying RPC and REST is not an easy task. The HTTP RPC protocol of the tRPC-Go framework hopes to define the same set of PB files, so that services can be called through both RPC (i.e., through the NewXXXClientProxy provided by the stub code) and native HTTP requests. However, such HTTP calls do not comply with RESTful specifications, for example, custom routes cannot be defined, wildcards are not supported, and the response body is empty when errors occur (error information can only be put into the response header). Therefore, we additionally support RESTful protocols, and no longer try to forcibly unify RPC and REST. If the service is specified as RESTful, it is not supported to call it with stub code, but only supports http client call. The benefits of doing so are that you can provide API that complies with the RESTful specifications through protobuf annotation in the same set of PB files, and can use various plugins and filter capabilities of the tRPC framework. | ||
|
||
## Usage | ||
|
||
Steps to use the feature. Typically: | ||
- Define a PB file that contains the service definition and RESTful annotations. | ||
```protobuf | ||
// file : examples/features/restful/server/pb/helloworld.proto | ||
// Greeter service | ||
service Greeter { | ||
rpc SayHello(HelloRequest) returns (HelloReply) { | ||
option (trpc.api.http) = { | ||
// http method is GET and path is /v1/greeter/hello/{name} | ||
// {name} is a path parameter , it will be mapped to HelloRequest.name | ||
get: "/v1/greeter/hello/{name}" | ||
}; | ||
} | ||
............ | ||
............ | ||
} | ||
``` | ||
|
||
- Generate the stub code. | ||
```shell | ||
trpc create -p helloworld.proto --rpconly --gotag --alias -f -o=. | ||
``` | ||
|
||
- Implement the service. | ||
```go | ||
// file : examples/features/restful/server/main.go | ||
type greeterService struct{ | ||
pb.UnimplementedGreeter | ||
} | ||
|
||
func (g greeterService) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { | ||
log.InfoContextf(ctx, "[restful] Received SayHello request with req: %v", req) | ||
// handle request | ||
rsp := &pb.HelloReply{ | ||
Message: "[restful] SayHello Hello " + req.Name, | ||
} | ||
return rsp, nil | ||
} | ||
``` | ||
|
||
- Register the service. | ||
```go | ||
// file : examples/features/restful/server/main.go | ||
// Register Greeter service | ||
pb.RegisterGreeterService(server, new(greeterService)) | ||
``` | ||
|
||
- config | ||
```yaml | ||
# file : examples/features/restful/server/trpc_go.yaml | ||
server: # server configuration. | ||
app: test # business application name. | ||
server: helloworld # service process name. | ||
bin_path: /usr/local/trpc/bin/ # paths to binary executables and framework configuration files. | ||
conf_path: /usr/local/trpc/conf/ # paths to business configuration files. | ||
data_path: /usr/local/trpc/data/ # paths to business data files. | ||
service: # business service configuration,can have multiple. | ||
- name: trpc.test.helloworld.Greeter # the route name of the service. | ||
ip: 127.0.0.1 # the service listening ip address, can use the placeholder ${ip}, choose one of ip and nic, priority ip. | ||
port: 9092 # the service listening port, can use the placeholder ${port}. | ||
network: tcp # the service listening network type, tcp or udp. | ||
protocol: restful # application layer protocol. NOTE restful service this is restful. | ||
``` | ||
* Start server. | ||
```shell | ||
$ go run server/main.go # start server | ||
$ go run server/main.go -conf server/trpc_go.yaml | ||
``` | ||
|
||
* Start client. | ||
|
||
```shell | ||
$ go run client/main.go # start client | ||
$ go run client/main.go -conf client/trpc_go.yaml | ||
``` | ||
|
||
* Server output | ||
|
||
``` | ||
2023-05-10 20:31:11.628 DEBUG maxprocs/maxprocs.go:47 maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined | ||
2023-05-10 20:31:11.629 INFO server/service.go:164 process:2140, restful service:trpc.test.helloworld.Greeter launch success, tcp:127.0.0.1:9092, serving ... | ||
2023-05-10 20:31:23.336 INFO server/main.go:28 [restful] Received SayHello request with req: name:"trpc-restful" | ||
2023-05-10 20:31:23.355 INFO server/main.go:36 [restful] Received Message request with req: name:"messages/trpc-restful-wildcard" sub:{subfield:"wildcard"} | ||
2023-05-10 20:31:23.356 INFO server/main.go:44 [restful] Received UpdateMessage request with req: message_id:"123" message:{message:"trpc-restful-patch"} | ||
2023-05-10 20:31:23.357 INFO server/main.go:52 [restful] Received UpdateMessageV2 request with req: message_id:"123" message:"trpc-restful-patch-v2" | ||
``` | ||
|
||
Then explain the expected result. | ||
* Client output | ||
|
||
``` | ||
2023-05-11 11:09:20.911 INFO client/main.go:55 helloRsp : [restful] SayHello Hello trpc-restful | ||
2023-05-11 11:09:20.912 INFO client/main.go:66 messageWildcardRsp : [restful] Message name:messages/trpc-restful-wildcard,subfield:wildcard | ||
2023-05-11 11:09:20.912 INFO client/main.go:84 updateMessageRsp : [restful] UpdateMessage message_id:123,message:trpc-restful-patch | ||
2023-05-11 11:09:20.914 INFO client/main.go:102 updateMessageV2Rsp : [restful] UpdateMessageV2 message_id:123,message:trpc-restful-patch-v2 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Package main is the main package. | ||
package main | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"trpc.group/trpc-go/trpc-go" | ||
"trpc.group/trpc-go/trpc-go/client" | ||
thttp "trpc.group/trpc-go/trpc-go/http" | ||
"trpc.group/trpc-go/trpc-go/log" | ||
) | ||
|
||
type ( | ||
// greeterMessageReq is request struct. | ||
greeterMessageReq struct { | ||
Message string `json:"message"` | ||
} | ||
// greeterRsp is response struct. | ||
greeterRsp struct { | ||
Message string `json:"message"` | ||
} | ||
) | ||
|
||
var greeterHttpProxy = thttp.NewClientProxy("greeterRestfulService") | ||
|
||
func main() { | ||
// init trpc server | ||
_ = trpc.NewServer() | ||
|
||
// get trpc context | ||
ctx := trpc.BackgroundContext() | ||
|
||
// get /v1/greeter/hello/{name} | ||
callGreeterHello(ctx) | ||
|
||
// get /v1/greeter/message/{name=messages/*} | ||
callGreeterMessageSubfield(ctx) | ||
|
||
// patch /v1/greeter/message/{message_id} | ||
callGreeterUpdateMessageV1(ctx) | ||
|
||
// patch /v2/greeter/message/{message_id} | ||
callGreeterUpdateMessageV2(ctx) | ||
} | ||
|
||
// callGreeterHello restful request greeter service | ||
func callGreeterHello(ctx context.Context) { | ||
var rsp greeterRsp | ||
err := greeterHttpProxy.Get(ctx, "/v1/greeter/hello/trpc-restful", &rsp) | ||
if err != nil { | ||
log.Fatalf("get /v1/greeter/hello/trpc-restful http.err:%s", err.Error()) | ||
} | ||
// want: [restful] SayHello Hello trpc-restful | ||
log.Infof("helloRsp : %v", rsp.Message) | ||
} | ||
|
||
// callGreeterMessageSubfield restful request greeter service | ||
func callGreeterMessageSubfield(ctx context.Context) { | ||
var rsp greeterRsp | ||
err := greeterHttpProxy.Get(ctx, "/v1/greeter/message/messages/trpc-restful-wildcard?sub.subfield=wildcard", &rsp) | ||
if err != nil { | ||
log.Fatalf("get /v1/greeter/message/messages/trpc-restful-wildcard http.err:%s", err.Error()) | ||
} | ||
// want: [restful] Message name:messages/trpc-restful-wildcard,subfield:wildcard | ||
log.Infof("messageWildcardRsp : %v", rsp.Message) | ||
} | ||
|
||
// callGreeterUpdateMessageV1 restful request greeter service | ||
func callGreeterUpdateMessageV1(ctx context.Context) { | ||
var rsp greeterRsp | ||
var reqBody = greeterMessageReq{ | ||
Message: "trpc-restful-patch", | ||
} | ||
header := &thttp.ClientReqHeader{ | ||
Method: http.MethodPatch, | ||
} | ||
header.AddHeader("ContentType", "application/json") | ||
err := greeterHttpProxy.Patch(ctx, "/v1/greeter/message/123", reqBody, &rsp, client.WithReqHead(header)) | ||
if err != nil { | ||
log.Fatalf("patch /v1/greeter/message/123 http.err:%s", err.Error()) | ||
} | ||
// want: [restful] UpdateMessage message_id:123,message:trpc-restful-patch | ||
log.Infof("updateMessageRsp : %v", rsp.Message) | ||
} | ||
|
||
// callGreeterUpdateMessageV2 restful request greeter service | ||
func callGreeterUpdateMessageV2(ctx context.Context) { | ||
var rsp greeterRsp | ||
var reqBody = greeterMessageReq{ | ||
Message: "trpc-restful-patch-v2", | ||
} | ||
header := &thttp.ClientReqHeader{ | ||
Method: http.MethodPatch, | ||
} | ||
header.AddHeader("ContentType", "application/json") | ||
err := greeterHttpProxy.Patch(ctx, "/v2/greeter/message/123", reqBody, &rsp, client.WithReqHead(header)) | ||
if err != nil { | ||
log.Fatalf("patch /v2/greeter/message/123 http.err:%s", err.Error()) | ||
} | ||
// want: [restful] UpdateMessage message_id:123,message:trpc-restful-patch | ||
log.Infof("updateMessageV2Rsp : %v", rsp.Message) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
global: # global config. | ||
namespace: development # environment type, two types: production and development. | ||
env_name: test # environment name, names of multiple environments in informal settings. | ||
container_name: ${container_name} # container name, the placeholder is replaced by the actual container name by platform. | ||
local_ip: ${local_ip} # local ip,it is the container's ip in container and is local ip in physical machine or virtual machine. | ||
|
||
client: # configuration for client calls. | ||
timeout: 1000 # maximum request processing time for all backends. | ||
namespace: development # environment type for all backends. | ||
service: # configuration for a single backend. | ||
- name: greeterRestfulService # backend service name | ||
target: ip://127.0.0.1:9092 # backend service address:ip://ip:port. | ||
network: tcp # backend service network type, tcp or udp, configuration takes precedence. | ||
protocol: http # application layer protocol, trpc or http. | ||
timeout: 800 # maximum request processing time in milliseconds. | ||
|
||
plugins: # configuration for plugins | ||
log: # configuration for log | ||
default: # default configuration for log, and can support multiple output. | ||
- writer: console # console stdout, default. | ||
level: debug # level of stdout. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
all: | ||
trpc create \ | ||
-p helloworld.proto \ | ||
--api-version 2 \ | ||
--rpconly \ | ||
--nogomod \ | ||
--mock=false \ | ||
--domain=trpc.group | ||
|
||
.PHONY: all |
Oops, something went wrong.