trpc
A collection of helpers & simple foundation framework for working with Twirp. Twirp
is a rpc framework on top of HTTP 1.1 and ProtocolBuffers which provide simplicity. Working with Twirp
is almost like
working with HTTP REST JSON API, but it's more structured via ProtocolBuffers, but still simpler rather than gRPC
.
Adapters
This repo provides a simple framework which handle pluggable adapters which is basically used to muxing http requests. Available adapters are:
Usage
Use http.ServeMux
:
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
engine := adapter.NewDefault()
trpc := framework.New(engine)
trpc.Register(twirpHandler)
trpc.Run(":8080")
}
Use gorilla
:
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
engine := adapter.NewGorilla()
trpc := framework.New(engine)
trpc.Register(twirpHandler)
trpc.Run(":8080")
}
Use fiber
:
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
engine := adapter.NewFiber()
trpc := framework.New(engine)
trpc.Register(twirpHandler)
trpc.Run(":8080")
}
Use go-chi
:
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
engine := adapter.NewGochi()
engine.RegisterMiddlewares()
trpc := framework.New(engine)
trpc.Register(twirpHandler)
trpc.Run(":8080")
}
Use gin
:
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
engine := adapter.NewGin()
engine.RegisterMiddlewares()
trpc := framework.New(engine)
trpc.Register(twirpHandler)
trpc.Run(":8080")
}
You can play with example, for client/server example usages.
If we need to send some custom header data to our server, we can use pkg/header
.
Client's implementations (sending the header):
func main() {
client := haberdasher.NewHaberdasherProtobufClient("http://0.0.0.0:8080", &http.Client{}, twirp.WithClientPathPrefix("/twirp"))
h := header.New()
h.Set("test", "testing")
ctx := context.Background()
ctx, err := h.ToContext(ctx)
if err != nil {
panic(err)
}
hat, err := client.MakeHat(ctx, &haberdasher.Size{Inches: 12})
if err != nil {
fmt.Printf("oh no: %v", err)
os.Exit(1)
}
fmt.Printf("I have a nice new hat: %+v", hat)
}
Server's implementation (fetch the header):
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
// setup service middleware handler
svc := handler.New(twirpHandler)
svc.Use(handler.WithHeaderParser, handler.WithRecovery)
engine := adapter.NewGin()
engine.RegisterMiddlewares()
trpc := framework.New(engine)
trpc.Register(svc)
trpc.Run(":8080")
}
We need to do all of these server's implementations on their service init.
Inside our internal service handler, it will looks like this:
// Server implements the Haberdasher service
type Server struct{}
func (s *Server) MakeHat(ctx context.Context, size *pb.Size) (hat *pb.Hat, err error) {
val, err := header.ParseFromContext(ctx)
if err != nil {
log.Printf("Error parse header: %v\n", err)
panic(err)
}
log.Printf("Val from header: %v\n", val.Get("test"))
if size.Inches <= 0 {
return nil, twirp.InvalidArgumentError("inches", "I can't make a hat that small!")
}
return &pb.Hat{
Inches: size.Inches,
Color: []string{"white", "black", "brown", "red", "blue"}[rand.Intn(4)],
Name: []string{"bowler", "baseball cap", "top hat", "derby"}[rand.Intn(3)],
}, nil
}
Handler
I create new package pkg/handler
used to wrap our internal TwirpServer
implementation. This pkg/handler
is compatible
with framework.TwirpServiceBuilder
interface.
You need to use this package if your service need to attach with
our internal custom middleware such as header.WithHeaderParser
Example:
func main() {
server := &service.Server{} // implements Haberdasher interface
twirpHandler := haberdasher.NewHaberdasherServer(server)
// setup service middleware handler
svc := handler.New(twirpHandler)
svc.Use(handler.WithHeaderParser, handler.WithRecovery)
engine := adapter.NewGin()
engine.RegisterMiddlewares()
trpc := framework.New(engine)
trpc.Register(svc)
trpc.Run(":8080")
}
HTTPMiddleware
If you need to create your own custom middleware, you have to make sure
that your middleware follow this contract:
type HTTPMiddleware func(http.Handler) http.Handler
Example implementation:
func WithHeaderParser(base http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hcopied := make(http.Header)
for k, v := range r.Header {
// all of these header keys are forbidden, used by internal Twirp implementations
// we should not do anything to these keys, including overwrite it
if k == "Accept" || k == "Content-Type" || k == "Twirp-Version" {
continue
}
hcopied[k] = v
}
client := header.New()
client.Replace(hcopied)
ctx, err := client.ToContext(ctx)
if err == nil {
r = r.WithContext(ctx)
}
base.ServeHTTP(w, r)
})
}
Request Context
Please remember, that when using Twirp
, your service handle doesn't have any access to incoming request's data. So if you need to passed something from http.Request
to your service handler, you need to put the data into context.Context
.