From fd1fdb8b4feeeba23a2aa8fdc8319a577a93943e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jochum?= Date: Sat, 17 Sep 2022 06:24:38 +0200 Subject: [PATCH] Fix routes having the same method, update docs, update build --- .drone.yml | 1 + README.md | 29 +++++++++++++++++++++++- Taskfile.yml | 2 ++ cmd/microrouterd/handler/handler.go | 34 +++++++++++++++++------------ docker/router/Dockerfile | 4 +++- go.mod | 2 +- go.sum | 3 ++- handler.go | 7 +++++- internal/logger/logger.go | 27 ++++++++++++++--------- method.go | 24 ++++++++++---------- route.go | 19 +++++++++++----- 11 files changed, 104 insertions(+), 48 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1d9e26d..18385a8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -24,5 +24,6 @@ steps: dockerfile: ./docker/router/Dockerfile repo: registry.fk.jochum.dev/jo-micro/router build_args: + - DOCKER_IO=registry.fk.jochum.dev/docker_hub_cache - VERSION=${DRONE_TAG:1} auto_tag: true diff --git a/README.md b/README.md index 6ab3db3..7cc293c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://drone.fk.jochum.dev/api/badges/jo-micro/router/status.svg)](https://drone.fk.jochum.dev/jo-micro/router) + # router A dynamic router (API Gatway) for go-micro. @@ -8,6 +10,31 @@ It looks for services that host "proto/routerclientpb/routerclientpb.RouterClien - gin doesn't allow to delete routes, so if you want to delete a route you have to restart go-micro/router. +## Usage + +docker-compose: + +```yaml +services: + router: + restart: unless-stopped + image: docker.io/jomicro/router:0.2.3 + environment: + - MICRO_TRANSPORT=grpc + - MICRO_REGISTRY=nats + - MICRO_REGISTRY_ADDRESS=nats:4222 + - MICRO_BROKER=nats + - MICRO_BROKER_ADDRESS=nats:4222 + - SERVER_ADDRESS=:8080 + - LOG_LEVEL=info + ports: + - 8080:8080 + depends_on: + - nats +``` + +See `cmd/microrouterd/plugins.go` for a list of availabel transports, registries and brokers. + ## Todo - Add (more) examples. @@ -15,7 +42,7 @@ It looks for services that host "proto/routerclientpb/routerclientpb.RouterClien - Add support for [debug](https://github.com/asim/go-micro/tree/master/debug). - Maybe add optional support for [auth](https://github.com/asim/go-micro/blob/master/auth/auth.go). -## Examples +## Integration examples Have a look at [internalService](https://jochum.dev/jo-micro/router/blob/master/cmd/microrouterd/main.go#L35) or the author's FOSS project [microlobby](https://github.com/pcdummy/microlobby). diff --git a/Taskfile.yml b/Taskfile.yml index e207b62..3322b88 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -36,6 +36,8 @@ tasks: vars: VOLUME_PATH: sh: podman volume inspect jo_micro-router_go --format "{{"{{"}}.Mountpoint{{"}}"}}" + preconditions: + - test -n "{{.CLI_ARGS}}" protoc: run: "once" diff --git a/cmd/microrouterd/handler/handler.go b/cmd/microrouterd/handler/handler.go index d0a43d8..9ba4fbf 100644 --- a/cmd/microrouterd/handler/handler.go +++ b/cmd/microrouterd/handler/handler.go @@ -30,14 +30,14 @@ type JSONRoute struct { type Handler struct { service micro.Service engine *gin.Engine - routes map[string]bool + routes map[string]*routerclientpb.RoutesReply_Route } func NewHandler(service micro.Service, engine *gin.Engine) (*Handler, error) { return &Handler{ service: service, engine: engine, - routes: make(map[string]bool), + routes: make(map[string]*routerclientpb.RoutesReply_Route), }, nil } @@ -51,16 +51,16 @@ func (h *Handler) Start() error { for { services, err := util.FindByEndpoint(h.service, "RouterClientService.Routes") if err != nil { - iLogger.WithCaller().Error(err) + iLogger.Logrus().Error(err) continue } for _, s := range services { - iLogger.WithCaller().Debug("Found service ", s.Name) + iLogger.Logrus().WithField("service", s.Name).Tracef("Found service") client := routerclientpb.NewRouterClientService(s.Name, h.service.Client()) resp, err := client.Routes(ctx, &emptypb.Empty{}) if err != nil { - iLogger.WithCaller().Error(err) + iLogger.Logrus().Error(err) // failure in getting routes, silently ignore continue } @@ -68,7 +68,6 @@ func (h *Handler) Start() error { serviceGroup := globalGroup.Group(fmt.Sprintf("/%s", resp.GetRouterURI())) for _, route := range resp.Routes { - iLogger.WithCaller().Debug("Found endpoint ", route.Endpoint) var g *gin.RouterGroup = nil if route.IsGlobal { @@ -77,11 +76,20 @@ func (h *Handler) Start() error { g = serviceGroup } - // Calculate the path of the route and register it if it's not registered yet - path := fmt.Sprintf("%s: %s/%s", route.Method, g.BasePath(), route.Path) - if _, ok := h.routes[path]; !ok { + // Calculate the pathMethod of the route and register it if it's not registered yet + pathMethod := fmt.Sprintf("%s:%s%s", route.GetMethod(), g.BasePath(), route.GetPath()) + path := fmt.Sprintf("%s%s", g.BasePath(), route.GetPath()) + if _, ok := h.routes[pathMethod]; !ok { + iLogger.Logrus(). + WithField("service", s.Name). + WithField("endpoint", route.GetEndpoint()). + WithField("method", route.GetMethod()). + WithField("path", path). + Debugf("Found route") + g.Handle(route.GetMethod(), route.GetPath(), h.proxy(s.Name, route)) - h.routes[path] = true + h.routes[pathMethod] = route + h.routes[pathMethod].Path = path } } } @@ -173,7 +181,7 @@ func (h *Handler) proxy(serviceName string, route *routerclientpb.RoutesReply_Ro var response json.RawMessage err := h.service.Client().Call(ctx, req, &response) if err != nil { - iLogger.WithCaller().Error(err) + iLogger.Logrus().Error(err) pErr := errors.FromError(err) code := int(http.StatusInternalServerError) @@ -192,9 +200,7 @@ func (h *Handler) proxy(serviceName string, route *routerclientpb.RoutesReply_Ro } func (h *Handler) Routes(ctx context.Context, in *emptypb.Empty, out *routerserverpb.RoutesReply) error { - ginRoutes := h.engine.Routes() - - for _, route := range ginRoutes { + for _, route := range h.routes { out.Routes = append(out.Routes, &routerserverpb.RoutesReply_Route{ Method: route.Method, Path: route.Path, diff --git a/docker/router/Dockerfile b/docker/router/Dockerfile index a48448e..6914edf 100644 --- a/docker/router/Dockerfile +++ b/docker/router/Dockerfile @@ -1,3 +1,5 @@ +ARG DOCKER_IO=docker.io + # STEP 1 build executable binary FROM registry.fk.jochum.dev/jo-micro/builder:latest AS builder @@ -11,7 +13,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -installsuffix cgo -ldflags=" # STEP 2 build a small image # start from busybox -FROM busybox +FROM ${DOCKER_IO}/library/busybox:latest LABEL maintainer="René Jochum " diff --git a/go.mod b/go.mod index a50afd1..dca2980 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.0 github.com/toorop/gin-logrus v0.0.0-20210225092905-2c785434f26f - github.com/urfave/cli/v2 v2.15.0 + github.com/urfave/cli/v2 v2.16.0 go-micro.dev/v4 v4.8.1 google.golang.org/protobuf v1.28.1 ) diff --git a/go.sum b/go.sum index a88c207..a3972b8 100644 --- a/go.sum +++ b/go.sum @@ -682,7 +682,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -723,6 +722,8 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.15.0 h1:/U7qTMlBYcmo/Z34PaaVY0Gw04xoGJqEdRAiWNHNyy8= github.com/urfave/cli/v2 v2.15.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/urfave/cli/v2 v2.16.0 h1:p0XJ2TBh4AyH7twPdMBPL5OMrd9nxkAOOlTHxT5SNBA= +github.com/urfave/cli/v2 v2.16.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= diff --git a/handler.go b/handler.go index be6e28d..44d7ae5 100644 --- a/handler.go +++ b/handler.go @@ -16,9 +16,14 @@ type Handler struct { } // NewHandler returns a new dynrouterpb Handler -func NewHandler(routerURI string, routes ...Route) *Handler { +func NewHandler(routerURI string, routes ...*Route) *Handler { pbRoutes := []*routerclientpb.RoutesReply_Route{} for _, r := range routes { + // NewRoute returns nil if no Endpoint has been specified, ignore these here + if r == nil { + continue + } + pbRoutes = append(pbRoutes, &routerclientpb.RoutesReply_Route{ IsGlobal: r.IsGlobal, Method: r.Method, diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 4736d19..62c3fd1 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -30,6 +30,14 @@ func Intialized() bool { return initialized } +// caller returns string presentation of log caller which is formatted as +// `/path/to/file.go:line_number`. e.g. `/internal/app/api.go:25` +func caller() func(*runtime.Frame) (function string, file string) { + return func(f *runtime.Frame) (function string, file string) { + return "", fmt.Sprintf("%s:%d", f.File, f.Line) + } +} + func Start(cli *cli.Context) error { if initialized { return nil @@ -44,6 +52,15 @@ func Start(cli *cli.Context) error { myLogger.Out = os.Stdout myLogger.Level = lvl + myLogger.SetReportCaller(true) + + myLogger.SetFormatter(&logrus.JSONFormatter{ + CallerPrettyfier: caller(), + FieldMap: logrus.FieldMap{ + logrus.FieldKeyFile: "caller", + }, + }) + microLogger.DefaultLogger = microLogrus.NewLogger(microLogrus.WithLogger(myLogger)) initialized = true @@ -60,13 +77,3 @@ func Stop() error { func Logrus() *logrus.Logger { return myLogger } - -func WithCaller() *logrus.Entry { - e := logrus.NewEntry(myLogger) - _, file, no, ok := runtime.Caller(1) - if ok { - e.WithField("caller", fmt.Sprintf("%s:%d", file, no)) - } - - return e -} diff --git a/method.go b/method.go index f5170d0..fecab46 100644 --- a/method.go +++ b/method.go @@ -1,17 +1,15 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package router +import "net/http" + const ( - MethodGet = "GET" - MethodHead = "HEAD" - MethodPost = "POST" - MethodPut = "PUT" - MethodPatch = "PATCH" // RFC 5789 - MethodDelete = "DELETE" - MethodConnect = "CONNECT" - MethodOptions = "OPTIONS" - MethodTrace = "TRACE" + MethodGet = http.MethodGet + MethodHead = http.MethodHead + MethodPost = http.MethodPost + MethodPut = http.MethodPut + MethodPatch = http.MethodPatch + MethodDelete = http.MethodDelete + MethodConnect = http.MethodConnect + MethodOptions = http.MethodOptions + MethodTrace = http.MethodTrace ) diff --git a/route.go b/route.go index aa65f37..da94cb7 100644 --- a/route.go +++ b/route.go @@ -1,6 +1,8 @@ package router -import "net/http" +import ( + "log" +) type Route struct { // isGlobal=True == no prefix route @@ -13,17 +15,22 @@ type Route struct { type Option func(*Route) -func NewRoute(endpoint interface{}, opts ...Option) Route { - route := Route{ +func NewRoute(opts ...Option) *Route { + route := &Route{ IsGlobal: false, - Method: http.MethodGet, + Method: MethodGet, Path: "/", - Endpoint: endpoint, + Endpoint: nil, Params: []string{}, } for _, o := range opts { - o(&route) + o(route) + } + + if route.Endpoint == nil { + log.Println("router.Endpoint() is a required argument") + return nil } return route