mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
introduce iris.NewGuide()
This commit is contained in:
parent
d8af2a1e14
commit
288a67bc58
|
@ -28,7 +28,8 @@ The codebase for Dependency Injection, Internationalization and localization and
|
||||||
|
|
||||||
## Fixes and Improvements
|
## Fixes and Improvements
|
||||||
|
|
||||||
- Make `Context.Domain()` customizable by letting developers to set the custom `Context.GetDomain` package-level function.
|
- Add new `iris.NewGuide` which helps you build a simple and nice JSON API with services as dependencies and better design pattern.
|
||||||
|
- Make `Context.Domain()` customizable by letting developers to modify the `Context.GetDomain` package-level function.
|
||||||
- Remove Request Context-based Transaction feature as its usage can be replaced with just the Iris Context (as of go1.7+) and better [project](_examples/project) structure.
|
- Remove Request Context-based Transaction feature as its usage can be replaced with just the Iris Context (as of go1.7+) and better [project](_examples/project) structure.
|
||||||
- Fix [#1882](https://github.com/kataras/iris/issues/1882)
|
- Fix [#1882](https://github.com/kataras/iris/issues/1882)
|
||||||
- Fix [#1877](https://github.com/kataras/iris/issues/1877)
|
- Fix [#1877](https://github.com/kataras/iris/issues/1877)
|
||||||
|
|
34
README.md
34
README.md
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
Iris is a fast, simple yet fully featured and very efficient web framework for Go.
|
Iris is a fast, simple yet fully featured and very efficient web framework for Go.
|
||||||
|
|
||||||
It provides a beautifully expressive and easy to use foundation for your next website or API.
|
It provides a [beautifully](iris_guide.go#L31-L44) expressive and easy to use foundation for your next website or API.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
@ -38,7 +38,37 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<details><summary>Simple Handler</summary>
|
<details><summary>API Guide</summary>
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
// [other packages...]
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.NewGuide().
|
||||||
|
AllowOrigin("*").
|
||||||
|
Compression(true).
|
||||||
|
Health(true, "development", "kataras").
|
||||||
|
Timeout(0, 20*time.Second, 20*time.Second).
|
||||||
|
Middlewares(basicauth.New(...)).
|
||||||
|
Services(
|
||||||
|
// NewDatabase(),
|
||||||
|
// NewPostgresRepositoryRegistry,
|
||||||
|
// NewUserService,
|
||||||
|
).
|
||||||
|
API("/users", new(UsersAPI)).
|
||||||
|
Listen(":80")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>More with simple Handler</summary>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
|
@ -32,7 +32,7 @@ func newApp() *iris.Application {
|
||||||
// party or subdomain:
|
// party or subdomain:
|
||||||
// Get all routes that are registered so far, including all "Parties" and subdomains:
|
// Get all routes that are registered so far, including all "Parties" and subdomains:
|
||||||
currentRoutes := app.GetRoutes()
|
currentRoutes := app.GetRoutes()
|
||||||
// Register them to the www subdomain/vhost as well:
|
// Register them to the www subdomain/VHost as well:
|
||||||
for _, r := range currentRoutes {
|
for _, r := range currentRoutes {
|
||||||
www.Handle(r.Method, r.Tmpl().Src, r.Handlers...)
|
www.Handle(r.Method, r.Tmpl().Src, r.Handlers...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -613,9 +613,10 @@ type (
|
||||||
// app.Configure(iris.WithConfiguration(conf)) OR
|
// app.Configure(iris.WithConfiguration(conf)) OR
|
||||||
// app.Run/Listen(..., iris.WithConfiguration(conf)).
|
// app.Run/Listen(..., iris.WithConfiguration(conf)).
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
// vhost is private and set only with .Run/Listen methods, it cannot be changed after the first set.
|
// VHost lets you customize the trusted domain this server should run on.
|
||||||
|
// Its value will be used as the return value of Context.Domain() too.
|
||||||
// It can be retrieved by the context if needed (i.e router for subdomains)
|
// It can be retrieved by the context if needed (i.e router for subdomains)
|
||||||
vhost string
|
VHost string `ini:"v_host" json:"vHost" yaml:"VHost" toml:"VHost" env:"V_HOST"`
|
||||||
|
|
||||||
// LogLevel is the log level the application should use to output messages.
|
// LogLevel is the log level the application should use to output messages.
|
||||||
// Logger, by default, is mostly used on Build state but it is also possible
|
// Logger, by default, is mostly used on Build state but it is also possible
|
||||||
|
@ -941,7 +942,7 @@ var _ context.ConfigurationReadOnly = (*Configuration)(nil)
|
||||||
|
|
||||||
// GetVHost returns the non-exported vhost config field.
|
// GetVHost returns the non-exported vhost config field.
|
||||||
func (c *Configuration) GetVHost() string {
|
func (c *Configuration) GetVHost() string {
|
||||||
return c.vhost
|
return c.VHost
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogLevel returns the LogLevel field.
|
// GetLogLevel returns the LogLevel field.
|
||||||
|
|
|
@ -157,7 +157,16 @@ func fromFunc(v reflect.Value, dest *Dependency) bool {
|
||||||
numOut := typ.NumOut()
|
numOut := typ.NumOut()
|
||||||
|
|
||||||
if numIn == 0 {
|
if numIn == 0 {
|
||||||
panic("bad value: function has zero inputs")
|
// it's an empty function, that must return a structure.
|
||||||
|
if numOut != 1 {
|
||||||
|
firstOutType := indirectType(typ.Out(0))
|
||||||
|
if firstOutType.Kind() != reflect.Struct && firstOutType.Kind() != reflect.Interface {
|
||||||
|
panic(fmt.Sprintf("bad value: function has zero inputs: empty input function must output a single value but got: length=%v, type[0]=%s", numOut, firstOutType.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to structure.
|
||||||
|
return fromStructValue(v.Call(nil)[0], dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if numOut == 0 {
|
if numOut == 0 {
|
||||||
|
|
23
iris.go
23
iris.go
|
@ -545,17 +545,21 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
|
||||||
// bind the constructed server and return it
|
// bind the constructed server and return it
|
||||||
su := host.New(srv)
|
su := host.New(srv)
|
||||||
|
|
||||||
if app.config.vhost == "" { // vhost now is useful for router subdomain on wildcard subdomains,
|
if app.config.VHost == "" { // vhost now is useful for router subdomain on wildcard subdomains,
|
||||||
// in order to correct decide what to do on:
|
// in order to correct decide what to do on:
|
||||||
// mydomain.com -> invalid
|
// mydomain.com -> invalid
|
||||||
// localhost -> invalid
|
// localhost -> invalid
|
||||||
// sub.mydomain.com -> valid
|
// sub.mydomain.com -> valid
|
||||||
// sub.localhost -> valid
|
// sub.localhost -> valid
|
||||||
// we need the host (without port if 80 or 443) in order to validate these, so:
|
// we need the host (without port if 80 or 443) in order to validate these, so:
|
||||||
app.config.vhost = netutil.ResolveVHost(srv.Addr)
|
app.config.VHost = netutil.ResolveVHost(srv.Addr)
|
||||||
|
} else {
|
||||||
|
context.GetDomain = func(_ string) string { // #1886
|
||||||
|
return app.config.VHost
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// app.logger.Debugf("Host: virtual host is %s", app.config.vhost)
|
// app.logger.Debugf("Host: virtual host is %s", app.config.VHost)
|
||||||
|
|
||||||
// the below schedules some tasks that will run among the server
|
// the below schedules some tasks that will run among the server
|
||||||
|
|
||||||
|
@ -604,6 +608,15 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
|
||||||
return su
|
return su
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (app *Application) OnShutdown(closers ...func()) {
|
||||||
|
// for _,cb := range closers {
|
||||||
|
// if cb == nil {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// RegisterOnInterrupt(cb)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Shutdown gracefully terminates all the application's server hosts and any tunnels.
|
// Shutdown gracefully terminates all the application's server hosts and any tunnels.
|
||||||
// Returns an error on the first failure, otherwise nil.
|
// Returns an error on the first failure, otherwise nil.
|
||||||
func (app *Application) Shutdown(ctx stdContext.Context) error {
|
func (app *Application) Shutdown(ctx stdContext.Context) error {
|
||||||
|
@ -795,7 +808,7 @@ type Runner func(*Application) error
|
||||||
// See `Run` for more.
|
// See `Run` for more.
|
||||||
func Listener(l net.Listener, hostConfigs ...host.Configurator) Runner {
|
func Listener(l net.Listener, hostConfigs ...host.Configurator) Runner {
|
||||||
return func(app *Application) error {
|
return func(app *Application) error {
|
||||||
app.config.vhost = netutil.ResolveVHost(l.Addr().String())
|
app.config.VHost = netutil.ResolveVHost(l.Addr().String())
|
||||||
return app.NewHost(&http.Server{Addr: l.Addr().String()}).
|
return app.NewHost(&http.Server{Addr: l.Addr().String()}).
|
||||||
Configure(hostConfigs...).
|
Configure(hostConfigs...).
|
||||||
Serve(l)
|
Serve(l)
|
||||||
|
@ -1059,7 +1072,7 @@ func (app *Application) tryStartTunneling() {
|
||||||
|
|
||||||
publicAddr := publicAddrs[0]
|
publicAddr := publicAddrs[0]
|
||||||
// to make subdomains resolution still based on this new remote, public addresses.
|
// to make subdomains resolution still based on this new remote, public addresses.
|
||||||
app.config.vhost = publicAddr[strings.Index(publicAddr, "://")+3:]
|
app.config.VHost = publicAddr[strings.Index(publicAddr, "://")+3:]
|
||||||
|
|
||||||
directLog := []byte(fmt.Sprintf("• Public Address: %s\n", publicAddr))
|
directLog := []byte(fmt.Sprintf("• Public Address: %s\n", publicAddr))
|
||||||
app.logger.Printer.Write(directLog) // nolint:errcheck
|
app.logger.Printer.Write(directLog) // nolint:errcheck
|
||||||
|
|
530
iris_guide.go
Normal file
530
iris_guide.go
Normal file
|
@ -0,0 +1,530 @@
|
||||||
|
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() Step1 {
|
||||||
|
return &step1{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Step1 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) Step2
|
||||||
|
}
|
||||||
|
|
||||||
|
Step2 interface {
|
||||||
|
// Compression enables or disables the gzip (or any other client-preferred) compression algorithm
|
||||||
|
// for response writes.
|
||||||
|
Compression(b bool) Step3
|
||||||
|
}
|
||||||
|
|
||||||
|
Step3 interface {
|
||||||
|
// Health enables the /health route.
|
||||||
|
// If "env" and "developer" are given, these fields will be populated to the client
|
||||||
|
// through headers and environemnt on health route.
|
||||||
|
Health(b bool, env, developer string) Step4
|
||||||
|
}
|
||||||
|
|
||||||
|
Step4 interface {
|
||||||
|
// Timeout defines the http timeout, server read & write timeouts.
|
||||||
|
Timeout(requestResponseLife, read time.Duration, write time.Duration) Step5
|
||||||
|
}
|
||||||
|
|
||||||
|
Step5 interface {
|
||||||
|
// Middlewares registers one or more handlers to run before the requested route's handler.
|
||||||
|
Middlewares(handlers ...Handler) Step6
|
||||||
|
}
|
||||||
|
|
||||||
|
Step6 interface {
|
||||||
|
// Services registers one or more dependencies that APIs can use.
|
||||||
|
Services(deps ...interface{}) Step7
|
||||||
|
}
|
||||||
|
|
||||||
|
Step7 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) Step7
|
||||||
|
// API registers a router which is responsible to serve the /api group.
|
||||||
|
API(pathPrefix string, c ...router.PartyConfigurator) Step7
|
||||||
|
// 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.
|
||||||
|
// 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.
|
||||||
|
Run(runner Runner, configurators ...Configurator) error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type step1 struct {
|
||||||
|
originLine string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *step1) AllowOrigin(originLine string) Step2 {
|
||||||
|
s.originLine = originLine
|
||||||
|
return &step2{
|
||||||
|
step1: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type step2 struct {
|
||||||
|
step1 *step1
|
||||||
|
|
||||||
|
enableCompression bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *step2) Compression(b bool) Step3 {
|
||||||
|
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) Step4 {
|
||||||
|
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) Step5 {
|
||||||
|
s.handlerTimeout = requestResponseLife
|
||||||
|
|
||||||
|
s.serverTimeoutRead = read
|
||||||
|
s.serverTimeoutWrite = write
|
||||||
|
return &step5{
|
||||||
|
step4: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type step5 struct {
|
||||||
|
step4 *step4
|
||||||
|
|
||||||
|
middlewares []Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *step5) Middlewares(handlers ...Handler) Step6 {
|
||||||
|
s.middlewares = handlers
|
||||||
|
|
||||||
|
return &step6{
|
||||||
|
step5: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type step6 struct {
|
||||||
|
step5 *step5
|
||||||
|
|
||||||
|
deps []interface{}
|
||||||
|
// derives from "deps".
|
||||||
|
closers []func()
|
||||||
|
// derives from "deps".
|
||||||
|
configuratorsAsDeps []Configurator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *step6) Services(deps ...interface{}) Step7 {
|
||||||
|
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) Step7 {
|
||||||
|
s.handlers = append(s.handlers, step7SimpleRoute{method: method, path: path, handlers: handlers})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *step7) API(prefix string, c ...router.PartyConfigurator) Step7 {
|
||||||
|
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(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...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for prefix, c := range s.m {
|
||||||
|
app.PartyConfigure("/api"+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 {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return app.Run(runner, configurators...)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user