123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- package iris
- import (
- "time"
- "github.com/kataras/iris/v12/core/router"
- "github.com/kataras/iris/v12/middleware/cors"
- "github.com/kataras/iris/v12/middleware/modrevision"
- "github.com/kataras/iris/v12/middleware/recover"
- "github.com/kataras/iris/v12/x/errors"
- )
- // NewGuide returns a simple Iris API builder.
- //
- // Example Code:
- /*
- package main
- import (
- "context"
- "database/sql"
- "time"
- "github.com/kataras/iris/v12"
- "github.com/kataras/iris/v12/x/errors"
- )
- func main() {
- iris.NewGuide().
- AllowOrigin("*").
- Compression(true).
- Health(true, "development", "kataras").
- Timeout(0, 20*time.Second, 20*time.Second).
- Middlewares().
- Services(
- // openDatabase(),
- // NewSQLRepoRegistry,
- NewMemRepoRegistry,
- NewTestService,
- ).
- API("/tests", new(TestAPI)).
- Listen(":80")
- }
- // Recommendation: move it to /api/tests/api.go file.
- type TestAPI struct {
- TestService *TestService
- }
- func (api *TestAPI) Configure(r iris.Party) {
- r.Get("/", api.listTests)
- }
- func (api *TestAPI) listTests(ctx iris.Context) {
- tests, err := api.TestService.ListTests(ctx)
- if err != nil {
- errors.Internal.LogErr(ctx, err)
- return
- }
- ctx.JSON(tests)
- }
- // Recommendation: move it to /pkg/storage/sql/db.go file.
- type DB struct {
- *sql.DB
- }
- func openDatabase( your database configuration... ) *DB {
- conn, err := sql.Open(...)
- // handle error.
- return &DB{DB: conn}
- }
- func (db *DB) Close() error {
- return nil
- }
- // Recommendation: move it to /pkg/repository/registry.go file.
- type RepoRegistry interface {
- Tests() TestRepository
- InTransaction(ctx context.Context, fn func(RepoRegistry) error) error
- }
- // Recommendation: move it to /pkg/repository/registry/memory.go file.
- type repoRegistryMem struct {
- tests TestRepository
- }
- func NewMemRepoRegistry() RepoRegistry {
- return &repoRegistryMem{
- tests: NewMemTestRepository(),
- }
- }
- func (r *repoRegistryMem) Tests() TestRepository {
- return r.tests
- }
- func (r *repoRegistryMem) InTransaction(ctx context.Context, fn func(RepoRegistry) error) error {
- return nil
- }
- // Recommendation: move it to /pkg/repository/registry/sql.go file.
- type repoRegistrySQL struct {
- db *DB
- tests TestRepository
- }
- func NewSQLRepoRegistry(db *DB) RepoRegistry {
- return &repoRegistrySQL{
- db: db,
- tests: NewSQLTestRepository(db),
- }
- }
- func (r *repoRegistrySQL) Tests() TestRepository {
- return r.tests
- }
- func (r *repoRegistrySQL) InTransaction(ctx context.Context, fn func(RepoRegistry) error) error {
- return nil
- // your own database transaction code, may look something like that:
- // tx, err := r.db.BeginTx(ctx, nil)
- // if err != nil {
- // return err
- // }
- // defer tx.Rollback()
- // newRegistry := NewSQLRepoRegistry(tx)
- // if err := fn(newRegistry);err!=nil{
- // return err
- // }
- // return tx.Commit()
- }
- // Recommendation: move it to /pkg/test/test.go
- type Test struct {
- Name string `db:"name"`
- }
- // Recommendation: move it to /pkg/test/repository.go
- type TestRepository interface {
- ListTests(ctx context.Context) ([]Test, error)
- }
- type testRepositoryMem struct {
- tests []Test
- }
- func NewMemTestRepository() TestRepository {
- list := []Test{
- {Name: "test1"},
- {Name: "test2"},
- {Name: "test3"},
- }
- return &testRepositoryMem{
- tests: list,
- }
- }
- func (r *testRepositoryMem) ListTests(ctx context.Context) ([]Test, error) {
- return r.tests, nil
- }
- type testRepositorySQL struct {
- db *DB
- }
- func NewSQLTestRepository(db *DB) TestRepository {
- return &testRepositorySQL{db: db}
- }
- func (r *testRepositorySQL) ListTests(ctx context.Context) ([]Test, error) {
- query := `SELECT * FROM tests ORDER BY created_at;`
- rows, err := r.db.QueryContext(ctx, query)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- tests := make([]Test, 0)
- for rows.Next() {
- var t Test
- if err := rows.Scan(&t.Name); err != nil {
- return nil, err
- }
- tests = append(tests, t)
- }
- if err := rows.Err(); err != nil {
- return nil, err
- }
- return tests, nil
- }
- // Recommendation: move it to /pkg/service/test_service.go file.
- type TestService struct {
- repos RepoRegistry
- }
- func NewTestService(registry RepoRegistry) *TestService {
- return &TestService{
- repos: registry,
- }
- }
- func (s *TestService) ListTests(ctx context.Context) ([]Test, error) {
- return s.repos.Tests().ListTests(ctx)
- }
- */
- func NewGuide() Guide {
- return &step1{}
- }
- type (
- // Guide is the simplify API builder.
- // It's a step-by-step builder which can be used to build an Iris Application
- // with the most common features.
- Guide interface {
- // AllowOrigin defines the CORS allowed domains.
- // Many can be splitted by comma.
- // If "*" is provided then all origins are accepted (use it for public APIs).
- AllowOrigin(originLine string) CompressionGuide
- }
- // CompressionGuide is the 2nd step of the Guide.
- // Compression (gzip or any other client requested) can be enabled or disabled.
- CompressionGuide interface {
- // Compression enables or disables the gzip (or any other client-preferred) compression algorithm
- // for response writes.
- Compression(b bool) HealthGuide
- }
- // HealthGuide is the 3rd step of the Guide.
- // Health enables the /health route.
- HealthGuide interface {
- // Health enables the /health route.
- // If "env" and "developer" are given, these fields will be populated to the client
- // through headers and environment on health route.
- Health(b bool, env, developer string) TimeoutGuide
- }
- // TimeoutGuide is the 4th step of the Guide.
- // Timeout defines the http timeout, server read & write timeouts.
- TimeoutGuide interface {
- // Timeout defines the http timeout, server read & write timeouts.
- Timeout(requestResponseLife, read time.Duration, write time.Duration) MiddlewareGuide
- }
- // MiddlewareGuide is the 5th step of the Guide.
- // It registers one or more handlers to run before everything else (RouterMiddlewares) or
- // before registered routes (Middlewares).
- MiddlewareGuide interface {
- // RouterMiddlewares registers one or more handlers to run before everything else.
- RouterMiddlewares(handlers ...Handler) MiddlewareGuide
- // Middlewares registers one or more handlers to run before the requested route's handler.
- Middlewares(handlers ...Handler) ServiceGuide
- }
- // ServiceGuide is the 6th step of the Guide.
- // It is used to register deferrable functions and, most importantly, dependencies that APIs can use.
- ServiceGuide interface {
- // Deferrables registers one or more functions to be ran when the server is terminated.
- Deferrables(closers ...func()) ServiceGuide
- // Prefix sets the API Party prefix path.
- // Usage: WithPrefix("/api").
- WithPrefix(prefixPath string) ServiceGuide
- // WithoutPrefix disables the API Party prefix path.
- // Usage: WithoutPrefix(), same as WithPrefix("").
- WithoutPrefix() ServiceGuide
- // Services registers one or more dependencies that APIs can use.
- Services(deps ...interface{}) ApplicationBuilder
- }
- // ApplicationBuilder is the final step of the Guide.
- // It is used to register APIs controllers (PartyConfigurators) and
- // its Build, Listen and Run methods configure and build the actual Iris application
- // based on the previous steps.
- ApplicationBuilder interface {
- // Handle registers a simple route on specific method and (dynamic) path.
- // It simply calls the Iris Application's Handle method.
- // Use the "API" method instead to keep the app organized.
- Handle(method, path string, handlers ...Handler) ApplicationBuilder
- // API registers a router which is responsible to serve the /api group.
- API(pathPrefix string, c ...router.PartyConfigurator) ApplicationBuilder
- // Build builds the application with the prior configuration and returns the
- // Iris Application instance for further customizations.
- //
- // Use "Build" before "Listen" or "Run" to apply further modifications
- // to the framework before starting the server. Calling "Build" is optional.
- Build() *Application // optional call.
- // Listen calls the Application's Listen method which is a shortcut of Run(iris.Addr("hostPort")).
- // Use "Run" instead if you need to customize the HTTP/2 server itself.
- Listen(hostPort string, configurators ...Configurator) error // Listen OR Run.
- // Run calls the Application's Run method.
- // The 1st argument is a Runner (iris.Listener, iris.Server, iris.Addr, iris.TLS, iris.AutoTLS and iris.Raw).
- // The 2nd argument can be used to add custom configuration right before the server is up and running.
- Run(runner Runner, configurators ...Configurator) error
- }
- )
- type step1 struct {
- originLine string
- }
- func (s *step1) AllowOrigin(originLine string) CompressionGuide {
- s.originLine = originLine
- return &step2{
- step1: s,
- }
- }
- type step2 struct {
- step1 *step1
- enableCompression bool
- }
- func (s *step2) Compression(b bool) HealthGuide {
- s.enableCompression = b
- return &step3{
- step2: s,
- }
- }
- type step3 struct {
- step2 *step2
- enableHealth bool
- env, developer string
- }
- func (s *step3) Health(b bool, env, developer string) TimeoutGuide {
- s.enableHealth = b
- s.env, s.developer = env, developer
- return &step4{
- step3: s,
- }
- }
- type step4 struct {
- step3 *step3
- handlerTimeout time.Duration
- serverTimeoutRead time.Duration
- serverTimeoutWrite time.Duration
- }
- func (s *step4) Timeout(requestResponseLife, read, write time.Duration) MiddlewareGuide {
- s.handlerTimeout = requestResponseLife
- s.serverTimeoutRead = read
- s.serverTimeoutWrite = write
- return &step5{
- step4: s,
- }
- }
- type step5 struct {
- step4 *step4
- routerMiddlewares []Handler // top-level router middlewares, fire even on 404s.
- middlewares []Handler
- }
- func (s *step5) RouterMiddlewares(handlers ...Handler) MiddlewareGuide {
- s.routerMiddlewares = append(s.routerMiddlewares, handlers...)
- return s
- }
- func (s *step5) Middlewares(handlers ...Handler) ServiceGuide {
- s.middlewares = handlers
- return &step6{
- step5: s,
- prefix: getDefaultAPIPrefix(),
- }
- }
- type step6 struct {
- step5 *step5
- deps []interface{}
- // derives from "deps".
- closers []func()
- // derives from "deps".
- configuratorsAsDeps []Configurator
- // API Party optional prefix path.
- // If this is nil then it defaults to "/api" in order to keep backwards compatibility,
- // otherwise can be set to empty or a custom one.
- prefix *string
- }
- func (s *step6) Deferrables(closers ...func()) ServiceGuide {
- s.closers = append(s.closers, closers...)
- return s
- }
- var defaultAPIPrefix = "/api"
- func getDefaultAPIPrefix() *string {
- return &defaultAPIPrefix
- }
- // WithPrefix sets the API Party prefix path.
- // Usage: WithPrefix("/api").
- func (s *step6) WithPrefix(prefixPath string) ServiceGuide {
- if prefixPath == "" {
- return s.WithoutPrefix()
- }
- *s.prefix = prefixPath
- return s
- }
- // WithoutPrefix disables the API Party prefix path, same as WithPrefix("").
- // Usage: WithoutPrefix()
- func (s *step6) WithoutPrefix() ServiceGuide {
- s.prefix = nil
- return s
- }
- func (s *step6) getPrefix() string {
- if s.prefix == nil { // if WithoutPrefix called then API has no prefix.
- return ""
- }
- apiPrefix := *s.prefix
- if apiPrefix == "" { // if not nil but empty (this shouldn't happen) then it defaults to "/api".
- apiPrefix = defaultAPIPrefix
- }
- return apiPrefix
- }
- func (s *step6) Services(deps ...interface{}) ApplicationBuilder {
- s.deps = deps
- for _, d := range deps {
- if d == nil {
- continue
- }
- switch cb := d.(type) {
- case func():
- s.closers = append(s.closers, cb)
- case func() error:
- s.closers = append(s.closers, func() { cb() })
- case interface{ Close() }:
- s.closers = append(s.closers, cb.Close)
- case interface{ Close() error }:
- s.closers = append(s.closers, func() {
- cb.Close()
- })
- case Configurator:
- s.configuratorsAsDeps = append(s.configuratorsAsDeps, cb)
- }
- }
- return &step7{
- step6: s,
- }
- }
- type step7 struct {
- step6 *step6
- app *Application
- m map[string][]router.PartyConfigurator
- handlers []step7SimpleRoute
- }
- type step7SimpleRoute struct {
- method, path string
- handlers []Handler
- }
- func (s *step7) Handle(method, path string, handlers ...Handler) ApplicationBuilder {
- s.handlers = append(s.handlers, step7SimpleRoute{method: method, path: path, handlers: handlers})
- return s
- }
- func (s *step7) API(prefix string, c ...router.PartyConfigurator) ApplicationBuilder {
- if s.m == nil {
- s.m = make(map[string][]router.PartyConfigurator)
- }
- s.m[prefix] = append(s.m[prefix], c...)
- return s
- }
- func (s *step7) Build() *Application {
- if s.app != nil {
- return s.app
- }
- app := New()
- app.SetContextErrorHandler(errors.DefaultContextErrorHandler)
- app.Macros().SetErrorHandler(errors.DefaultPathParameterTypeErrorHandler)
- app.UseRouter(recover.New())
- app.UseRouter(s.step6.step5.routerMiddlewares...)
- app.UseRouter(func(ctx Context) {
- ctx.Header("Server", "Iris")
- if dev := s.step6.step5.step4.step3.developer; dev != "" {
- ctx.Header("X-Developer", dev)
- }
- ctx.Next()
- })
- if allowOrigin := s.step6.step5.step4.step3.step2.step1.originLine; allowOrigin != "" && allowOrigin != "none" {
- app.UseRouter(cors.New().AllowOrigin(allowOrigin).Handler())
- }
- if s.step6.step5.step4.step3.step2.enableCompression {
- app.Use(Compression)
- }
- for _, middleware := range s.step6.step5.middlewares {
- if middleware == nil {
- continue
- }
- app.Use(middleware)
- }
- if configAsDeps := s.step6.configuratorsAsDeps; len(configAsDeps) > 0 {
- app.Configure(configAsDeps...)
- }
- if s.step6.step5.step4.step3.enableHealth {
- app.Get("/health", modrevision.New(modrevision.Options{
- ServerName: "Iris Server",
- Env: s.step6.step5.step4.step3.env,
- Developer: s.step6.step5.step4.step3.developer,
- }))
- }
- if deps := s.step6.deps; len(deps) > 0 {
- app.EnsureStaticBindings().RegisterDependency(deps...)
- }
- apiPrefix := s.step6.getPrefix()
- for prefix, c := range s.m {
- app.PartyConfigure(apiPrefix+prefix, c...)
- }
- for _, route := range s.handlers {
- app.Handle(route.method, route.path, route.handlers...)
- }
- if readTimeout := s.step6.step5.step4.serverTimeoutRead; readTimeout > 0 {
- app.ConfigureHost(func(su *Supervisor) {
- su.Server.ReadTimeout = readTimeout
- su.Server.IdleTimeout = readTimeout
- if v, recommended := readTimeout/4, 5*time.Second; v > recommended {
- su.Server.ReadHeaderTimeout = v
- } else {
- su.Server.ReadHeaderTimeout = recommended
- }
- })
- }
- if writeTimeout := s.step6.step5.step4.serverTimeoutWrite; writeTimeout > 0 {
- app.ConfigureHost(func(su *Supervisor) {
- su.Server.WriteTimeout = writeTimeout
- })
- }
- var defaultConfigurators = []Configurator{
- WithoutServerError(ErrServerClosed, ErrURLQuerySemicolon),
- WithOptimizations,
- WithRemoteAddrHeader(
- "X-Real-Ip",
- "X-Forwarded-For",
- "CF-Connecting-IP",
- "True-Client-Ip",
- "X-Appengine-Remote-Addr",
- ),
- WithTimeout(s.step6.step5.step4.handlerTimeout),
- }
- app.Configure(defaultConfigurators...)
- s.app = app
- return app
- }
- func (s *step7) Listen(hostPort string, configurators ...Configurator) error {
- return s.Run(Addr(hostPort), configurators...)
- }
- func (s *step7) Run(runner Runner, configurators ...Configurator) error {
- app := s.Build()
- defer func() {
- // they will be called on interrupt signals too,
- // because Iris has a builtin mechanism to call server's shutdown on interrupt.
- for _, cb := range s.step6.closers {
- if cb == nil {
- continue
- }
- cb()
- }
- }()
- return app.Run(runner, configurators...)
- }
|