Skip to content

Commit

Permalink
pick missing RESTful example.
Browse files Browse the repository at this point in the history
  • Loading branch information
tocrafty committed Aug 22, 2023
1 parent d82ac5c commit c171de1
Show file tree
Hide file tree
Showing 9 changed files with 1,344 additions and 5 deletions.
93 changes: 88 additions & 5 deletions examples/features/restful/README.md
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
```
103 changes: 103 additions & 0 deletions examples/features/restful/client/main.go
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)
}
21 changes: 21 additions & 0 deletions examples/features/restful/client/trpc_go.yaml
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.
10 changes: 10 additions & 0 deletions examples/features/restful/pb/Makefile
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
Loading

0 comments on commit c171de1

Please sign in to comment.