trpc

module
v1.0.0-alpha.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2021 License: Apache-2.0

README

trpc

Go Reference

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.


Headers

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL