iris_guide.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. package iris
  2. import (
  3. "time"
  4. "github.com/kataras/iris/v12/core/router"
  5. "github.com/kataras/iris/v12/middleware/cors"
  6. "github.com/kataras/iris/v12/middleware/modrevision"
  7. "github.com/kataras/iris/v12/middleware/recover"
  8. "github.com/kataras/iris/v12/x/errors"
  9. )
  10. // NewGuide returns a simple Iris API builder.
  11. //
  12. // Example Code:
  13. /*
  14. package main
  15. import (
  16. "context"
  17. "database/sql"
  18. "time"
  19. "github.com/kataras/iris/v12"
  20. "github.com/kataras/iris/v12/x/errors"
  21. )
  22. func main() {
  23. iris.NewGuide().
  24. AllowOrigin("*").
  25. Compression(true).
  26. Health(true, "development", "kataras").
  27. Timeout(0, 20*time.Second, 20*time.Second).
  28. Middlewares().
  29. Services(
  30. // openDatabase(),
  31. // NewSQLRepoRegistry,
  32. NewMemRepoRegistry,
  33. NewTestService,
  34. ).
  35. API("/tests", new(TestAPI)).
  36. Listen(":80")
  37. }
  38. // Recommendation: move it to /api/tests/api.go file.
  39. type TestAPI struct {
  40. TestService *TestService
  41. }
  42. func (api *TestAPI) Configure(r iris.Party) {
  43. r.Get("/", api.listTests)
  44. }
  45. func (api *TestAPI) listTests(ctx iris.Context) {
  46. tests, err := api.TestService.ListTests(ctx)
  47. if err != nil {
  48. errors.Internal.LogErr(ctx, err)
  49. return
  50. }
  51. ctx.JSON(tests)
  52. }
  53. // Recommendation: move it to /pkg/storage/sql/db.go file.
  54. type DB struct {
  55. *sql.DB
  56. }
  57. func openDatabase( your database configuration... ) *DB {
  58. conn, err := sql.Open(...)
  59. // handle error.
  60. return &DB{DB: conn}
  61. }
  62. func (db *DB) Close() error {
  63. return nil
  64. }
  65. // Recommendation: move it to /pkg/repository/registry.go file.
  66. type RepoRegistry interface {
  67. Tests() TestRepository
  68. InTransaction(ctx context.Context, fn func(RepoRegistry) error) error
  69. }
  70. // Recommendation: move it to /pkg/repository/registry/memory.go file.
  71. type repoRegistryMem struct {
  72. tests TestRepository
  73. }
  74. func NewMemRepoRegistry() RepoRegistry {
  75. return &repoRegistryMem{
  76. tests: NewMemTestRepository(),
  77. }
  78. }
  79. func (r *repoRegistryMem) Tests() TestRepository {
  80. return r.tests
  81. }
  82. func (r *repoRegistryMem) InTransaction(ctx context.Context, fn func(RepoRegistry) error) error {
  83. return nil
  84. }
  85. // Recommendation: move it to /pkg/repository/registry/sql.go file.
  86. type repoRegistrySQL struct {
  87. db *DB
  88. tests TestRepository
  89. }
  90. func NewSQLRepoRegistry(db *DB) RepoRegistry {
  91. return &repoRegistrySQL{
  92. db: db,
  93. tests: NewSQLTestRepository(db),
  94. }
  95. }
  96. func (r *repoRegistrySQL) Tests() TestRepository {
  97. return r.tests
  98. }
  99. func (r *repoRegistrySQL) InTransaction(ctx context.Context, fn func(RepoRegistry) error) error {
  100. return nil
  101. // your own database transaction code, may look something like that:
  102. // tx, err := r.db.BeginTx(ctx, nil)
  103. // if err != nil {
  104. // return err
  105. // }
  106. // defer tx.Rollback()
  107. // newRegistry := NewSQLRepoRegistry(tx)
  108. // if err := fn(newRegistry);err!=nil{
  109. // return err
  110. // }
  111. // return tx.Commit()
  112. }
  113. // Recommendation: move it to /pkg/test/test.go
  114. type Test struct {
  115. Name string `db:"name"`
  116. }
  117. // Recommendation: move it to /pkg/test/repository.go
  118. type TestRepository interface {
  119. ListTests(ctx context.Context) ([]Test, error)
  120. }
  121. type testRepositoryMem struct {
  122. tests []Test
  123. }
  124. func NewMemTestRepository() TestRepository {
  125. list := []Test{
  126. {Name: "test1"},
  127. {Name: "test2"},
  128. {Name: "test3"},
  129. }
  130. return &testRepositoryMem{
  131. tests: list,
  132. }
  133. }
  134. func (r *testRepositoryMem) ListTests(ctx context.Context) ([]Test, error) {
  135. return r.tests, nil
  136. }
  137. type testRepositorySQL struct {
  138. db *DB
  139. }
  140. func NewSQLTestRepository(db *DB) TestRepository {
  141. return &testRepositorySQL{db: db}
  142. }
  143. func (r *testRepositorySQL) ListTests(ctx context.Context) ([]Test, error) {
  144. query := `SELECT * FROM tests ORDER BY created_at;`
  145. rows, err := r.db.QueryContext(ctx, query)
  146. if err != nil {
  147. return nil, err
  148. }
  149. defer rows.Close()
  150. tests := make([]Test, 0)
  151. for rows.Next() {
  152. var t Test
  153. if err := rows.Scan(&t.Name); err != nil {
  154. return nil, err
  155. }
  156. tests = append(tests, t)
  157. }
  158. if err := rows.Err(); err != nil {
  159. return nil, err
  160. }
  161. return tests, nil
  162. }
  163. // Recommendation: move it to /pkg/service/test_service.go file.
  164. type TestService struct {
  165. repos RepoRegistry
  166. }
  167. func NewTestService(registry RepoRegistry) *TestService {
  168. return &TestService{
  169. repos: registry,
  170. }
  171. }
  172. func (s *TestService) ListTests(ctx context.Context) ([]Test, error) {
  173. return s.repos.Tests().ListTests(ctx)
  174. }
  175. */
  176. func NewGuide() Guide {
  177. return &step1{}
  178. }
  179. type (
  180. // Guide is the simplify API builder.
  181. // It's a step-by-step builder which can be used to build an Iris Application
  182. // with the most common features.
  183. Guide interface {
  184. // AllowOrigin defines the CORS allowed domains.
  185. // Many can be splitted by comma.
  186. // If "*" is provided then all origins are accepted (use it for public APIs).
  187. AllowOrigin(originLine string) CompressionGuide
  188. }
  189. // CompressionGuide is the 2nd step of the Guide.
  190. // Compression (gzip or any other client requested) can be enabled or disabled.
  191. CompressionGuide interface {
  192. // Compression enables or disables the gzip (or any other client-preferred) compression algorithm
  193. // for response writes.
  194. Compression(b bool) HealthGuide
  195. }
  196. // HealthGuide is the 3rd step of the Guide.
  197. // Health enables the /health route.
  198. HealthGuide interface {
  199. // Health enables the /health route.
  200. // If "env" and "developer" are given, these fields will be populated to the client
  201. // through headers and environment on health route.
  202. Health(b bool, env, developer string) TimeoutGuide
  203. }
  204. // TimeoutGuide is the 4th step of the Guide.
  205. // Timeout defines the http timeout, server read & write timeouts.
  206. TimeoutGuide interface {
  207. // Timeout defines the http timeout, server read & write timeouts.
  208. Timeout(requestResponseLife, read time.Duration, write time.Duration) MiddlewareGuide
  209. }
  210. // MiddlewareGuide is the 5th step of the Guide.
  211. // It registers one or more handlers to run before everything else (RouterMiddlewares) or
  212. // before registered routes (Middlewares).
  213. MiddlewareGuide interface {
  214. // RouterMiddlewares registers one or more handlers to run before everything else.
  215. RouterMiddlewares(handlers ...Handler) MiddlewareGuide
  216. // Middlewares registers one or more handlers to run before the requested route's handler.
  217. Middlewares(handlers ...Handler) ServiceGuide
  218. }
  219. // ServiceGuide is the 6th step of the Guide.
  220. // It is used to register deferrable functions and, most importantly, dependencies that APIs can use.
  221. ServiceGuide interface {
  222. // Deferrables registers one or more functions to be ran when the server is terminated.
  223. Deferrables(closers ...func()) ServiceGuide
  224. // Prefix sets the API Party prefix path.
  225. // Usage: WithPrefix("/api").
  226. WithPrefix(prefixPath string) ServiceGuide
  227. // WithoutPrefix disables the API Party prefix path.
  228. // Usage: WithoutPrefix(), same as WithPrefix("").
  229. WithoutPrefix() ServiceGuide
  230. // Services registers one or more dependencies that APIs can use.
  231. Services(deps ...interface{}) ApplicationBuilder
  232. }
  233. // ApplicationBuilder is the final step of the Guide.
  234. // It is used to register APIs controllers (PartyConfigurators) and
  235. // its Build, Listen and Run methods configure and build the actual Iris application
  236. // based on the previous steps.
  237. ApplicationBuilder interface {
  238. // Handle registers a simple route on specific method and (dynamic) path.
  239. // It simply calls the Iris Application's Handle method.
  240. // Use the "API" method instead to keep the app organized.
  241. Handle(method, path string, handlers ...Handler) ApplicationBuilder
  242. // API registers a router which is responsible to serve the /api group.
  243. API(pathPrefix string, c ...router.PartyConfigurator) ApplicationBuilder
  244. // Build builds the application with the prior configuration and returns the
  245. // Iris Application instance for further customizations.
  246. //
  247. // Use "Build" before "Listen" or "Run" to apply further modifications
  248. // to the framework before starting the server. Calling "Build" is optional.
  249. Build() *Application // optional call.
  250. // Listen calls the Application's Listen method which is a shortcut of Run(iris.Addr("hostPort")).
  251. // Use "Run" instead if you need to customize the HTTP/2 server itself.
  252. Listen(hostPort string, configurators ...Configurator) error // Listen OR Run.
  253. // Run calls the Application's Run method.
  254. // The 1st argument is a Runner (iris.Listener, iris.Server, iris.Addr, iris.TLS, iris.AutoTLS and iris.Raw).
  255. // The 2nd argument can be used to add custom configuration right before the server is up and running.
  256. Run(runner Runner, configurators ...Configurator) error
  257. }
  258. )
  259. type step1 struct {
  260. originLine string
  261. }
  262. func (s *step1) AllowOrigin(originLine string) CompressionGuide {
  263. s.originLine = originLine
  264. return &step2{
  265. step1: s,
  266. }
  267. }
  268. type step2 struct {
  269. step1 *step1
  270. enableCompression bool
  271. }
  272. func (s *step2) Compression(b bool) HealthGuide {
  273. s.enableCompression = b
  274. return &step3{
  275. step2: s,
  276. }
  277. }
  278. type step3 struct {
  279. step2 *step2
  280. enableHealth bool
  281. env, developer string
  282. }
  283. func (s *step3) Health(b bool, env, developer string) TimeoutGuide {
  284. s.enableHealth = b
  285. s.env, s.developer = env, developer
  286. return &step4{
  287. step3: s,
  288. }
  289. }
  290. type step4 struct {
  291. step3 *step3
  292. handlerTimeout time.Duration
  293. serverTimeoutRead time.Duration
  294. serverTimeoutWrite time.Duration
  295. }
  296. func (s *step4) Timeout(requestResponseLife, read, write time.Duration) MiddlewareGuide {
  297. s.handlerTimeout = requestResponseLife
  298. s.serverTimeoutRead = read
  299. s.serverTimeoutWrite = write
  300. return &step5{
  301. step4: s,
  302. }
  303. }
  304. type step5 struct {
  305. step4 *step4
  306. routerMiddlewares []Handler // top-level router middlewares, fire even on 404s.
  307. middlewares []Handler
  308. }
  309. func (s *step5) RouterMiddlewares(handlers ...Handler) MiddlewareGuide {
  310. s.routerMiddlewares = append(s.routerMiddlewares, handlers...)
  311. return s
  312. }
  313. func (s *step5) Middlewares(handlers ...Handler) ServiceGuide {
  314. s.middlewares = handlers
  315. return &step6{
  316. step5: s,
  317. prefix: getDefaultAPIPrefix(),
  318. }
  319. }
  320. type step6 struct {
  321. step5 *step5
  322. deps []interface{}
  323. // derives from "deps".
  324. closers []func()
  325. // derives from "deps".
  326. configuratorsAsDeps []Configurator
  327. // API Party optional prefix path.
  328. // If this is nil then it defaults to "/api" in order to keep backwards compatibility,
  329. // otherwise can be set to empty or a custom one.
  330. prefix *string
  331. }
  332. func (s *step6) Deferrables(closers ...func()) ServiceGuide {
  333. s.closers = append(s.closers, closers...)
  334. return s
  335. }
  336. var defaultAPIPrefix = "/api"
  337. func getDefaultAPIPrefix() *string {
  338. return &defaultAPIPrefix
  339. }
  340. // WithPrefix sets the API Party prefix path.
  341. // Usage: WithPrefix("/api").
  342. func (s *step6) WithPrefix(prefixPath string) ServiceGuide {
  343. if prefixPath == "" {
  344. return s.WithoutPrefix()
  345. }
  346. *s.prefix = prefixPath
  347. return s
  348. }
  349. // WithoutPrefix disables the API Party prefix path, same as WithPrefix("").
  350. // Usage: WithoutPrefix()
  351. func (s *step6) WithoutPrefix() ServiceGuide {
  352. s.prefix = nil
  353. return s
  354. }
  355. func (s *step6) getPrefix() string {
  356. if s.prefix == nil { // if WithoutPrefix called then API has no prefix.
  357. return ""
  358. }
  359. apiPrefix := *s.prefix
  360. if apiPrefix == "" { // if not nil but empty (this shouldn't happen) then it defaults to "/api".
  361. apiPrefix = defaultAPIPrefix
  362. }
  363. return apiPrefix
  364. }
  365. func (s *step6) Services(deps ...interface{}) ApplicationBuilder {
  366. s.deps = deps
  367. for _, d := range deps {
  368. if d == nil {
  369. continue
  370. }
  371. switch cb := d.(type) {
  372. case func():
  373. s.closers = append(s.closers, cb)
  374. case func() error:
  375. s.closers = append(s.closers, func() { cb() })
  376. case interface{ Close() }:
  377. s.closers = append(s.closers, cb.Close)
  378. case interface{ Close() error }:
  379. s.closers = append(s.closers, func() {
  380. cb.Close()
  381. })
  382. case Configurator:
  383. s.configuratorsAsDeps = append(s.configuratorsAsDeps, cb)
  384. }
  385. }
  386. return &step7{
  387. step6: s,
  388. }
  389. }
  390. type step7 struct {
  391. step6 *step6
  392. app *Application
  393. m map[string][]router.PartyConfigurator
  394. handlers []step7SimpleRoute
  395. }
  396. type step7SimpleRoute struct {
  397. method, path string
  398. handlers []Handler
  399. }
  400. func (s *step7) Handle(method, path string, handlers ...Handler) ApplicationBuilder {
  401. s.handlers = append(s.handlers, step7SimpleRoute{method: method, path: path, handlers: handlers})
  402. return s
  403. }
  404. func (s *step7) API(prefix string, c ...router.PartyConfigurator) ApplicationBuilder {
  405. if s.m == nil {
  406. s.m = make(map[string][]router.PartyConfigurator)
  407. }
  408. s.m[prefix] = append(s.m[prefix], c...)
  409. return s
  410. }
  411. func (s *step7) Build() *Application {
  412. if s.app != nil {
  413. return s.app
  414. }
  415. app := New()
  416. app.SetContextErrorHandler(errors.DefaultContextErrorHandler)
  417. app.Macros().SetErrorHandler(errors.DefaultPathParameterTypeErrorHandler)
  418. app.UseRouter(recover.New())
  419. app.UseRouter(s.step6.step5.routerMiddlewares...)
  420. app.UseRouter(func(ctx Context) {
  421. ctx.Header("Server", "Iris")
  422. if dev := s.step6.step5.step4.step3.developer; dev != "" {
  423. ctx.Header("X-Developer", dev)
  424. }
  425. ctx.Next()
  426. })
  427. if allowOrigin := s.step6.step5.step4.step3.step2.step1.originLine; allowOrigin != "" && allowOrigin != "none" {
  428. app.UseRouter(cors.New().AllowOrigin(allowOrigin).Handler())
  429. }
  430. if s.step6.step5.step4.step3.step2.enableCompression {
  431. app.Use(Compression)
  432. }
  433. for _, middleware := range s.step6.step5.middlewares {
  434. if middleware == nil {
  435. continue
  436. }
  437. app.Use(middleware)
  438. }
  439. if configAsDeps := s.step6.configuratorsAsDeps; len(configAsDeps) > 0 {
  440. app.Configure(configAsDeps...)
  441. }
  442. if s.step6.step5.step4.step3.enableHealth {
  443. app.Get("/health", modrevision.New(modrevision.Options{
  444. ServerName: "Iris Server",
  445. Env: s.step6.step5.step4.step3.env,
  446. Developer: s.step6.step5.step4.step3.developer,
  447. }))
  448. }
  449. if deps := s.step6.deps; len(deps) > 0 {
  450. app.EnsureStaticBindings().RegisterDependency(deps...)
  451. }
  452. apiPrefix := s.step6.getPrefix()
  453. for prefix, c := range s.m {
  454. app.PartyConfigure(apiPrefix+prefix, c...)
  455. }
  456. for _, route := range s.handlers {
  457. app.Handle(route.method, route.path, route.handlers...)
  458. }
  459. if readTimeout := s.step6.step5.step4.serverTimeoutRead; readTimeout > 0 {
  460. app.ConfigureHost(func(su *Supervisor) {
  461. su.Server.ReadTimeout = readTimeout
  462. su.Server.IdleTimeout = readTimeout
  463. if v, recommended := readTimeout/4, 5*time.Second; v > recommended {
  464. su.Server.ReadHeaderTimeout = v
  465. } else {
  466. su.Server.ReadHeaderTimeout = recommended
  467. }
  468. })
  469. }
  470. if writeTimeout := s.step6.step5.step4.serverTimeoutWrite; writeTimeout > 0 {
  471. app.ConfigureHost(func(su *Supervisor) {
  472. su.Server.WriteTimeout = writeTimeout
  473. })
  474. }
  475. var defaultConfigurators = []Configurator{
  476. WithoutServerError(ErrServerClosed, ErrURLQuerySemicolon),
  477. WithOptimizations,
  478. WithRemoteAddrHeader(
  479. "X-Real-Ip",
  480. "X-Forwarded-For",
  481. "CF-Connecting-IP",
  482. "True-Client-Ip",
  483. "X-Appengine-Remote-Addr",
  484. ),
  485. WithTimeout(s.step6.step5.step4.handlerTimeout),
  486. }
  487. app.Configure(defaultConfigurators...)
  488. s.app = app
  489. return app
  490. }
  491. func (s *step7) Listen(hostPort string, configurators ...Configurator) error {
  492. return s.Run(Addr(hostPort), configurators...)
  493. }
  494. func (s *step7) Run(runner Runner, configurators ...Configurator) error {
  495. app := s.Build()
  496. defer func() {
  497. // they will be called on interrupt signals too,
  498. // because Iris has a builtin mechanism to call server's shutdown on interrupt.
  499. for _, cb := range s.step6.closers {
  500. if cb == nil {
  501. continue
  502. }
  503. cb()
  504. }
  505. }()
  506. return app.Run(runner, configurators...)
  507. }