github.com/erda-project/erda-infra@v1.0.9/providers/httpserver/provider.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package httpserver
    16  
    17  import (
    18  	"reflect"
    19  	"sync"
    20  
    21  	"github.com/go-playground/validator"
    22  	"github.com/labstack/echo"
    23  
    24  	"github.com/erda-project/erda-infra/base/logs"
    25  	"github.com/erda-project/erda-infra/base/servicehub"
    26  	"github.com/erda-project/erda-infra/providers/httpserver/interceptors"
    27  	"github.com/erda-project/erda-infra/providers/httpserver/server"
    28  )
    29  
    30  // config .
    31  type config struct {
    32  	Addr        string `file:"addr" default:":8080" desc:"http address to listen"`
    33  	PrintRoutes bool   `file:"print_routes" default:"true" desc:"print http routes"`
    34  	AllowCORS   bool   `file:"allow_cors" default:"false" desc:"allow cors"`
    35  	Reloadable  bool   `file:"reloadable" default:"false" desc:"routes reloadable"`
    36  
    37  	Debug bool      `file:"debug" default:"false"`
    38  	Log   LogConfig `file:"log"`
    39  }
    40  
    41  // LogConfig .
    42  type LogConfig struct {
    43  	MaxBodySizeBytes int `file:"max_body_size_bytes" default:"1024" desc:"max body size in bytes"`
    44  }
    45  
    46  type provider struct {
    47  	Cfg *config
    48  	Log logs.Logger
    49  
    50  	server server.Server
    51  	lock   sync.Mutex
    52  	routes map[routeKey]*route
    53  	err    error
    54  
    55  	startedChan chan struct{}
    56  }
    57  
    58  // Init .
    59  func (p *provider) Init(ctx servicehub.Context) error {
    60  	p.server = server.New(p.Cfg.Reloadable, &dataBinder{}, &structValidator{validator: validator.New()})
    61  	p.startedChan = make(chan struct{})
    62  
    63  	//p.server.Use(interceptors.Recover(p.Log).(func(echo.HandlerFunc) echo.HandlerFunc))
    64  	p.server.Use(interceptors.SimpleRecord(p.getInterceptorOption()))
    65  	p.server.Use(interceptors.CORS(p.Cfg.AllowCORS))
    66  	p.server.Use(interceptors.InjectRequestID())
    67  	p.server.Use(interceptors.DetailLog(p.getInterceptorOption()))
    68  	p.server.Use(interceptors.BodyDump(p.getInterceptorOption(), p.Cfg.Log.MaxBodySizeBytes))
    69  	p.server.Use(interceptors.PassThroughDebugFlag())
    70  	p.server.Use(p.wrapContext())
    71  
    72  	return nil
    73  }
    74  
    75  func (p *provider) wrapContext() echo.MiddlewareFunc {
    76  	return func(next echo.HandlerFunc) echo.HandlerFunc {
    77  		return func(c echo.Context) error {
    78  			ctx := &context{Context: c}
    79  			err := next(ctx)
    80  			p.logFailure(c, err)
    81  			return err
    82  		}
    83  	}
    84  }
    85  
    86  func (p *provider) getInterceptorOption() interceptors.Option {
    87  	funcs := []interceptors.EnableFetchFunc{
    88  		func(c echo.Context) bool {
    89  			return p.Cfg.Debug
    90  		},
    91  	}
    92  	return interceptors.NewOption(funcs, p.Log)
    93  }
    94  
    95  func (p *provider) logFailure(c server.Context, err error) {
    96  	if err != nil || c.Response().Status/100 != 2 {
    97  		p.Log.Errorf("(%s) err: %v, status: %d, url method: %s, path: %s, matcherPath: %s, ip: %s, header: %v",
    98  			interceptors.GetRequestID(c), err, c.Response().Status, c.Request().Method, c.Request().URL.Path, c.Path(), c.RealIP(), c.Request().Header)
    99  	}
   100  }
   101  
   102  // Start .
   103  func (p *provider) Start() error {
   104  	if p.err != nil {
   105  		return p.err
   106  	}
   107  	if p.Cfg.PrintRoutes {
   108  		if p.Cfg.Reloadable {
   109  			p.lock.Lock()
   110  		}
   111  		p.printRoutes(p.routes)
   112  		if p.Cfg.Reloadable {
   113  			p.lock.Unlock()
   114  		}
   115  	}
   116  	p.Log.Infof("starting http server at %s", p.Cfg.Addr)
   117  	close(p.startedChan)
   118  	return p.server.Start(p.Cfg.Addr)
   119  }
   120  
   121  // Close .
   122  func (p *provider) Close() error {
   123  	if p.server == nil {
   124  		return nil
   125  	}
   126  	return p.server.Close()
   127  }
   128  
   129  // Provide .
   130  func (p *provider) Provide(ctx servicehub.DependencyContext, args ...interface{}) interface{} {
   131  	if ctx.Service() == "http-router-manager" || ctx.Type() == routerManagerType {
   132  		return p.newRouterManager(true, ctx.Caller(), args...)
   133  	} else if p.Cfg.Reloadable && (ctx.Service() != "http-router-tx" || ctx.Type() == routerType) {
   134  		return &autoCommitRouter{
   135  			tx: p.newRouterManager(false, ctx.Caller(), args...),
   136  		}
   137  	}
   138  	return p.newRouterTx(true, ctx.Caller(), args...)
   139  }
   140  
   141  func (p *provider) newRouterManager(reset bool, group string, opts ...interface{}) RouterManager {
   142  	return &routerManager{
   143  		group: group,
   144  		reset: reset,
   145  		opts:  opts,
   146  		p:     p,
   147  	}
   148  }
   149  
   150  func (p *provider) newRouterTx(reset bool, group string, opts ...interface{}) RouterTx {
   151  	interceptors := getInterceptors(opts)
   152  	r := &router{
   153  		tx:           p.server.NewRouter(),
   154  		group:        group,
   155  		interceptors: interceptors,
   156  	}
   157  	r.pathFormater = r.getPathFormater(opts)
   158  	if p.Cfg.Reloadable {
   159  		r.lock = &p.lock
   160  		r.lock.Lock()
   161  		r.routes = make(map[routeKey]*route)
   162  		for key, route := range p.routes {
   163  			if !reset || route.group != r.group {
   164  				r.routes[key] = route
   165  				if route.handler != nil {
   166  					r.tx.Add(route.method, route.path, route.handler)
   167  				}
   168  			}
   169  		}
   170  		r.reportError = func(err error) {}
   171  		r.updateRoutes = func(routes map[routeKey]*route) {
   172  			p.routes = routes
   173  			diff := make(map[routeKey]*route)
   174  			for key, route := range p.routes {
   175  				if route.group == r.group {
   176  					diff[key] = route
   177  				}
   178  			}
   179  			if p.Cfg.PrintRoutes {
   180  				p.printRoutes(diff)
   181  			}
   182  		}
   183  	} else {
   184  		r.routes = p.routes
   185  		r.reportError = func(err error) {
   186  			p.err = err
   187  		}
   188  	}
   189  	return r
   190  }
   191  
   192  var (
   193  	routerType        = reflect.TypeOf((*Router)(nil)).Elem()
   194  	routerTxType      = reflect.TypeOf((*RouterTx)(nil)).Elem()
   195  	routerManagerType = reflect.TypeOf((*RouterManager)(nil)).Elem()
   196  )
   197  
   198  func init() {
   199  	servicehub.Register("http-server", &servicehub.Spec{
   200  		Services: []string{"http-server", "http-router", "http-router-manager", "http-router-tx"},
   201  		Types: []reflect.Type{
   202  			routerType,
   203  			routerTxType,
   204  			routerManagerType,
   205  		},
   206  		Description: "http server",
   207  		ConfigFunc:  func() interface{} { return &config{} },
   208  		Creator: func() servicehub.Provider {
   209  			return &provider{
   210  				routes: make(map[routeKey]*route),
   211  			}
   212  		},
   213  	})
   214  }