denny

package module
v1.0.18 Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2021 License: BSD-3-Clause Imports: 22 Imported by: 3

README

denny

common http/grpc server which simplify request handling and logging by combining libraries, framework to be able to

  • support both http and grpc in one controller, write once, support both protocol. See example
  • support class base request controller, one controller for one handler, or you can describe your service in grpc and impelement grpc service, denny will then support HTTP/gRPC when you start it in brpc mode, see example
  • make cache usage simpler
  • use open tracing
  • make config reader simpler
  • make logger attached in request context, log should be showed as steps and in only one line for every request

denny is not a http/grpc server from scratch, by now it's based on gin framework and google grpc server, with help of Golang reflection to support both protocol while requires user just minimum about of work. Eq: you just need to write your service in grpc, denny will also support HTTP for your implementation.

denny is different from grpc gateway, grpc gateway uses code generation to generate http proxy call, a request to http enpoint of grpc gateway will also trigger another grpc call to your service. it's http proxy to grpc, with denny, a call to http will only invoke the code you wrote, does not trigger grpc call. It applies also for grpc call. Because of that, using grpc your service has to start with 2 services port, 1 for http and 1 for grpc, denny need only one for both protocol.

It also borrow many component from well known libraries (go-config, beego, logrus...).

usage example

Register discoverable grpc service
using etcd as naming storage

//server
func main() {

	server := denny.NewServer(true)
	// setup grpc server

	grpcServer := denny.NewGrpcServer()
	pb.RegisterHelloServiceServer(grpcServer, new(Hello))
	server.WithGrpcServer(grpcServer)
	
	registry := etcd.New("127.0.0.1:7379", "demo.brpc.svc")
	server.WithRegistry(registry)

	// start server in dual mode
	server.GraceFulStart(":8081")
}
// client

package main

import (
	"context"
	"fmt"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/whatvn/denny/example/protobuf"
	"github.com/whatvn/denny/naming/etcd"
	"github.com/whatvn/denny/naming"
	"google.golang.org/grpc"
)

func main() {

	registry := etcd.NewResolver("127.0.0.1:7379", "demo.brpc.svc")
	conn, err := grpc.Dial(registry.SvcName(), naming.DefaultBalancePolicy(), grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	client := pb.NewHelloServiceClient(conn)
	response, err := client.SayHelloAnonymous(context.Background(), &empty.Empty{})
	fmt.Println(response, err)
}
using redis as naming storage

//server
func main() {

	server := denny.NewServer(true)
	// setup grpc server

	grpcServer := denny.NewGrpcServer()
	pb.RegisterHelloServiceServer(grpcServer, new(Hello))
	server.WithGrpcServer(grpcServer)
	
	registry := redis.New("127.0.0.1:6379","",  "demo.brpc.svc")
	server.WithRegistry(registry)

	// start server in dual mode
	server.GraceFulStart(":8081")
}
// client

package main

import (
	"context"
	"fmt"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/whatvn/denny/example/protobuf"
	"github.com/whatvn/denny/naming/redis"
	"github.com/whatvn/denny/naming"
	"google.golang.org/grpc"
)

func main() {

	registry := redis.NewResolver("127.0.0.1:7379", "", "demo.brpc.svc")
	conn, err := grpc.Dial(registry.SvcName(), naming.DefaultBalancePolicy(), grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	client := pb.NewHelloServiceClient(conn)
	response, err := client.SayHelloAnonymous(context.Background(), &empty.Empty{})
	fmt.Println(response, err)
}
Write grpc code but support both http/grpc
package main

import (
	"context"
	"fmt"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/zipkin"
	"github.com/whatvn/denny"
	pb "github.com/whatvn/denny/example/protobuf"
	"github.com/whatvn/denny/middleware/http"
	"github.com/whatvn/denny/naming/etcd"
	"io"
)

// grpc
type Hello struct{}

// groupPath + "/hello/" + "sayhello"
//
func (s *Hello) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
	var (
		logger = denny.GetLogger(ctx)
	)
	response := &pb.HelloResponse{
		Reply: "hi",
	}

	logger.WithField("response", response)
	return response, nil
}

// http get request
// when define grpc method with input is empty.Empty object, denny will consider request as get request
// router will be:
// groupPath + "/hello/" + "sayhelloanonymous"
// rule is rootRoute + "/" lowerCase(serviceName) + "/" lowercase(methodName)

func (s *Hello) SayHelloAnonymous(ctx context.Context, in *empty.Empty) (*pb.HelloResponse, error) {

	var (
		logger = denny.GetLogger(ctx)
	)

	span, ctx := opentracing.StartSpanFromContext(ctx, "sayHello")
	defer span.Finish()
	response := &pb.HelloResponse{
		Reply: "ha",
	}

	logger.WithField("response", response)

	return response, nil
}

type TestController struct {
	denny.Controller
}

func (t *TestController) Handle(ctx *denny.Context) {
	ctx.JSON(200, &pb.HelloResponse{
		Reply: "ha",
	})
}

func newReporterUDP(jaegerAddr string, port int, packetLength int) jaeger.Transport {
	hostString := fmt.Sprintf("%s:%d", jaegerAddr, port)
	transport, err := jaeger.NewUDPTransport(hostString, packetLength)
	if err != nil {
		panic(err)
	}
	return transport
}
func initTracerUDP(jaegerAddr string, port int, packetLength int, serviceName string) (opentracing.Tracer, io.Closer) {
	var (
		propagator = zipkin.NewZipkinB3HTTPHeaderPropagator()
	)

	return jaeger.NewTracer(
		serviceName,
		jaeger.NewConstSampler(true),
		jaeger.NewRemoteReporter(newReporterUDP(jaegerAddr, port, packetLength)),
		jaeger.TracerOptions.Injector(opentracing.HTTPHeaders, propagator),
		jaeger.TracerOptions.Extractor(opentracing.HTTPHeaders, propagator),
		jaeger.TracerOptions.ZipkinSharedRPCSpan(true),
	)
}

func main() {

	// open tracing
	tracer, _ := initTracerUDP(
		"127.0.0.1",
		6831,
		65000,
		"brpc.server.demo",
	)
	opentracing.SetGlobalTracer(tracer)



	server := denny.NewServer(true)
	server.Use(http.Logger())
	group := server.NewGroup("/hi")
	group.Controller("/hi", denny.HttpPost, new(TestController))

	// setup grpc server

	grpcServer := denny.NewGrpcServer()
	pb.RegisterHelloServiceServer(grpcServer, new(Hello))
	server.WithGrpcServer(grpcServer)
	//

	//// then http
	authorized := server.NewGroup("/")
	// http://127.0.0.1:8080/hello/sayhello  (POST)
	// http://127.0.0.1:8080/hello/sayhelloanonymous  (GET)
	authorized.BrpcController(&Hello{})


	// naming registry
	registry := etcd.New("127.0.0.1:7379", "demo.brpc.svc")
	server.WithRegistry(registry)

	// start server in dual mode
	server.GraceFulStart(":8081")
}

setting up simple http request handler

package main

import (
	"github.com/whatvn/denny"
	"github.com/whatvn/denny/middleware/http"
)

type xController struct {
	denny.Controller
}

// define handle function for controller  
func (x xController) Handle(ctx *denny.Context) {
	var (
		logger = denny.GetLogger(ctx)
	)
	logger.AddLog("receive request")
	var str = "hello"
	logger.AddLog("do more thing") // logger middleware will log automatically when request finished
	str += " world"
	ctx.Writer.Write([]byte(str))
}

func main() {
	server := denny.NewServer()
	server.WithMiddleware(http.Logger())
	server.Controller("/", denny.HttpGet, &xController{})
	server.Start()
}



Reading config

package main

import (
	"fmt"
	"github.com/whatvn/denny/config"
	"os"
	"path/filepath"
	"time"
)

func configFile() (*os.File, error) {
	data := []byte(`{"foo": "bar", "denny": {"sister": "jenny"}}`)
	path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
	fh, err := os.Create(path)
	if err != nil {
		return nil, err
	}
	_, err = fh.Write(data)
	if err != nil {
		return nil, err
	}

	return fh, nil
}

func main()  {
	f, err := configFile()
	if err != nil {
		fmt.Println(err)
	}
	// read config from file
	config.New(f.Name())
	fmt.Println(config.GetString("foo"))
	fmt.Println(config.GetString("denny", "sister"))

	// config from evn takes higher priority
	os.Setenv("foo", "barbar")
	os.Setenv("denny_sister", "Jenny")
	config.Reload()
	fmt.Println(config.GetString("foo"))
	fmt.Println(config.GetString("denny", "sister"))
}

limit

Denny uses etcd as naming registry, but etcd packaging is somewhat complicated, new version links to old version, old version links to older version which is very difficult to optimize import, so currently it use a fork version of etcd here. Look at this issue to track

Documentation

Index

Constants

View Source
const (
	SelectCapital  = "([a-z])([A-Z])"
	ReplaceCapital = "$1 $2"
)

Variables

View Source
var Validator = binding.Validator

Functions

func Binding

func Binding(ctx *gin.Context) binding.Binding

func GetLogger

func GetLogger(ctx context.Context) *log.Log

func NewGrpcServer

func NewGrpcServer(interceptors ...grpc.UnaryServerInterceptor) *grpc.Server

Types

type Context

type Context = gin.Context

type Controller

type Controller struct {
	binding.StructValidator
	*log.Log
}

func (*Controller) SetValidator

func (c *Controller) SetValidator(v binding.StructValidator)

type Denny

type Denny struct {
	sync.Mutex
	*log.Log

	*gin.Engine
	// contains filtered or unexported fields
}

func NewServer

func NewServer(debug ...bool) *Denny

NewServer init denny with default parameter

func (*Denny) Controller

func (r *Denny) Controller(path string, method HttpMethod, ctl controller) *Denny

Controller register a controller with given path, method to http routes

func (*Denny) GraceFulStart

func (r *Denny) GraceFulStart(addrs ...string) error

GraceFulStart uses net http standard server it also detect if grpc server and discovery registry are available to start Denny in brpc mode, in this mode, server will support both protocol using same port and register channel listen to os signals to make it graceful stop

func (*Denny) NewGroup

func (r *Denny) NewGroup(path string) *group

NewGroup adds new group into server routes

func (*Denny) ServeHTTP

func (r *Denny) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP conforms to the http.Handler interface.

func (*Denny) SetValidator

func (r *Denny) SetValidator(v binding.StructValidator) *Denny

SetValidator overwrites default gin validate with provides validator we're using v10 validator

func (*Denny) Start

func (r *Denny) Start(addrs ...string) error

Start http server with given address Deprecated: use GraceFulStart(addrs ...string) instead.

func (*Denny) WithGrpcServer

func (r *Denny) WithGrpcServer(server *grpc.Server) *Denny

WithGrpcServer turns Denny into grpc server

func (*Denny) WithMiddleware

func (r *Denny) WithMiddleware(middleware ...HandleFunc)

WithMiddleware registers middleware to http server only gin style middleware is supported

func (*Denny) WithNotFoundHandler added in v1.0.8

func (r *Denny) WithNotFoundHandler(handler HandleFunc)

WithNotFoundHandler registers middleware to http server to serve not found route only gin style middleware is supported

func (*Denny) WithRegistry

func (r *Denny) WithRegistry(registry naming.Registry) *Denny

WithRegistry makes Denny discoverable via naming registry

type HandleFunc

type HandleFunc = gin.HandlerFunc

type HttpMethod

type HttpMethod string
const (
	HttpGet    HttpMethod = "GET"
	HttpPost   HttpMethod = "POST"
	HttpPatch  HttpMethod = "PATCH"
	HttpOption HttpMethod = "OPTION"
	HttpDelete HttpMethod = "DELETE"
)

Directories

Path Synopsis
Package config is an interface for dynamic configuration.
Package config is an interface for dynamic configuration.
encoder
Package encoder handles source encoding formats
Package encoder handles source encoding formats
loader
package loader manages loading from multiple sources
package loader manages loading from multiple sources
options
Package options provides a way to initialise options
Package options provides a way to initialise options
reader
Package reader parses change sets and provides config values
Package reader parses change sets and provides config values
source
Package source is the interface for sources
Package source is the interface for sources
source/file
Package file is a file source.
Package file is a file source.
source/memory
Package memory is a memory source
Package memory is a memory source
middleware contains built-in/necessary middleware (http) and interceptor (grpc)
middleware contains built-in/necessary middleware (http) and interceptor (grpc)
package naming is interface for both registry register/unregister and grpc builder/resolver
package naming is interface for both registry register/unregister and grpc builder/resolver

Jump to

Keyboard shortcuts

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