iris_guide.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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. // Services registers one or more dependencies that APIs can use.
  225. Services(deps ...interface{}) ApplicationBuilder
  226. }
  227. // ApplicationBuilder is the final step of the Guide.
  228. // It is used to register APIs controllers (PartyConfigurators) and
  229. // its Build, Listen and Run methods configure and build the actual Iris application
  230. // based on the previous steps.
  231. ApplicationBuilder interface {
  232. // Handle registers a simple route on specific method and (dynamic) path.
  233. // It simply calls the Iris Application's Handle method.
  234. // Use the "API" method instead to keep the app organized.
  235. Handle(method, path string, handlers ...Handler) ApplicationBuilder
  236. // API registers a router which is responsible to serve the /api group.
  237. API(pathPrefix string, c ...router.PartyConfigurator) ApplicationBuilder
  238. // Build builds the application with the prior configuration and returns the
  239. // Iris Application instance for further customizations.
  240. //
  241. // Use "Build" before "Listen" or "Run" to apply further modifications
  242. // to the framework before starting the server. Calling "Build" is optional.
  243. Build() *Application // optional call.
  244. // Listen calls the Application's Listen method which is a shortcut of Run(iris.Addr("hostPort")).
  245. // Use "Run" instead if you need to customize the HTTP/2 server itself.
  246. Listen(hostPort string, configurators ...Configurator) error // Listen OR Run.
  247. // Run calls the Application's Run method.
  248. // The 1st argument is a Runner (iris.Listener, iris.Server, iris.Addr, iris.TLS, iris.AutoTLS and iris.Raw).
  249. // The 2nd argument can be used to add custom configuration right before the server is up and running.
  250. Run(runner Runner, configurators ...Configurator) error
  251. }
  252. )
  253. type step1 struct {
  254. originLine string
  255. }
  256. func (s *step1) AllowOrigin(originLine string) CompressionGuide {
  257. s.originLine = originLine
  258. return &step2{
  259. step1: s,
  260. }
  261. }
  262. type step2 struct {
  263. step1 *step1
  264. enableCompression bool
  265. }
  266. func (s *step2) Compression(b bool) HealthGuide {
  267. s.enableCompression = b
  268. return &step3{
  269. step2: s,
  270. }
  271. }
  272. type step3 struct {
  273. step2 *step2
  274. enableHealth bool
  275. env, developer string
  276. }
  277. func (s *step3) Health(b bool, env, developer string) TimeoutGuide {
  278. s.enableHealth = b
  279. s.env, s.developer = env, developer
  280. return &step4{
  281. step3: s,
  282. }
  283. }
  284. type step4 struct {
  285. step3 *step3
  286. handlerTimeout time.Duration
  287. serverTimeoutRead time.Duration
  288. serverTimeoutWrite time.Duration
  289. }
  290. func (s *step4) Timeout(requestResponseLife, read, write time.Duration) MiddlewareGuide {
  291. s.handlerTimeout = requestResponseLife
  292. s.serverTimeoutRead = read
  293. s.serverTimeoutWrite = write
  294. return &step5{
  295. step4: s,
  296. }
  297. }
  298. type step5 struct {
  299. step4 *step4
  300. routerMiddlewares []Handler // top-level router middlewares, fire even on 404s.
  301. middlewares []Handler
  302. }
  303. func (s *step5) RouterMiddlewares(handlers ...Handler) MiddlewareGuide {
  304. s.routerMiddlewares = append(s.routerMiddlewares, handlers...)
  305. return s
  306. }
  307. func (s *step5) Middlewares(handlers ...Handler) ServiceGuide {
  308. s.middlewares = handlers
  309. return &step6{
  310. step5: s,
  311. }
  312. }
  313. type step6 struct {
  314. step5 *step5
  315. deps []interface{}
  316. // derives from "deps".
  317. closers []func()
  318. // derives from "deps".
  319. configuratorsAsDeps []Configurator
  320. }
  321. func (s *step6) Deferrables(closers ...func()) ServiceGuide {
  322. s.closers = append(s.closers, closers...)
  323. return s
  324. }
  325. func (s *step6) Services(deps ...interface{}) ApplicationBuilder {
  326. s.deps = deps
  327. for _, d := range deps {
  328. if d == nil {
  329. continue
  330. }
  331. switch cb := d.(type) {
  332. case func():
  333. s.closers = append(s.closers, cb)
  334. case func() error:
  335. s.closers = append(s.closers, func() { cb() })
  336. case interface{ Close() }:
  337. s.closers = append(s.closers, cb.Close)
  338. case interface{ Close() error }:
  339. s.closers = append(s.closers, func() {
  340. cb.Close()
  341. })
  342. case Configurator:
  343. s.configuratorsAsDeps = append(s.configuratorsAsDeps, cb)
  344. }
  345. }
  346. return &step7{
  347. step6: s,
  348. }
  349. }
  350. type step7 struct {
  351. step6 *step6
  352. app *Application
  353. m map[string][]router.PartyConfigurator
  354. handlers []step7SimpleRoute
  355. }
  356. type step7SimpleRoute struct {
  357. method, path string
  358. handlers []Handler
  359. }
  360. func (s *step7) Handle(method, path string, handlers ...Handler) ApplicationBuilder {
  361. s.handlers = append(s.handlers, step7SimpleRoute{method: method, path: path, handlers: handlers})
  362. return s
  363. }
  364. func (s *step7) API(prefix string, c ...router.PartyConfigurator) ApplicationBuilder {
  365. if s.m == nil {
  366. s.m = make(map[string][]router.PartyConfigurator)
  367. }
  368. s.m[prefix] = append(s.m[prefix], c...)
  369. return s
  370. }
  371. func (s *step7) Build() *Application {
  372. if s.app != nil {
  373. return s.app
  374. }
  375. app := New()
  376. app.SetContextErrorHandler(errors.DefaultContextErrorHandler)
  377. app.Macros().SetErrorHandler(errors.DefaultPathParameterTypeErrorHandler)
  378. app.UseRouter(recover.New())
  379. app.UseRouter(s.step6.step5.routerMiddlewares...)
  380. app.UseRouter(func(ctx Context) {
  381. ctx.Header("Server", "Iris")
  382. if dev := s.step6.step5.step4.step3.developer; dev != "" {
  383. ctx.Header("X-Developer", dev)
  384. }
  385. ctx.Next()
  386. })
  387. if allowOrigin := s.step6.step5.step4.step3.step2.step1.originLine; allowOrigin != "" && allowOrigin != "none" {
  388. app.UseRouter(cors.New().AllowOrigin(allowOrigin).Handler())
  389. }
  390. if s.step6.step5.step4.step3.step2.enableCompression {
  391. app.Use(Compression)
  392. }
  393. for _, middleware := range s.step6.step5.middlewares {
  394. if middleware == nil {
  395. continue
  396. }
  397. app.Use(middleware)
  398. }
  399. if configAsDeps := s.step6.configuratorsAsDeps; len(configAsDeps) > 0 {
  400. app.Configure(configAsDeps...)
  401. }
  402. if s.step6.step5.step4.step3.enableHealth {
  403. app.Get("/health", modrevision.New(modrevision.Options{
  404. ServerName: "Iris Server",
  405. Env: s.step6.step5.step4.step3.env,
  406. Developer: s.step6.step5.step4.step3.developer,
  407. }))
  408. }
  409. if deps := s.step6.deps; len(deps) > 0 {
  410. app.EnsureStaticBindings().RegisterDependency(deps...)
  411. }
  412. for prefix, c := range s.m {
  413. app.PartyConfigure("/api"+prefix, c...)
  414. }
  415. for _, route := range s.handlers {
  416. app.Handle(route.method, route.path, route.handlers...)
  417. }
  418. if readTimeout := s.step6.step5.step4.serverTimeoutRead; readTimeout > 0 {
  419. app.ConfigureHost(func(su *Supervisor) {
  420. su.Server.ReadTimeout = readTimeout
  421. su.Server.IdleTimeout = readTimeout
  422. if v, recommended := readTimeout/4, 5*time.Second; v > recommended {
  423. su.Server.ReadHeaderTimeout = v
  424. } else {
  425. su.Server.ReadHeaderTimeout = recommended
  426. }
  427. })
  428. }
  429. if writeTimeout := s.step6.step5.step4.serverTimeoutWrite; writeTimeout > 0 {
  430. app.ConfigureHost(func(su *Supervisor) {
  431. su.Server.WriteTimeout = writeTimeout
  432. })
  433. }
  434. var defaultConfigurators = []Configurator{
  435. WithoutServerError(ErrServerClosed, ErrURLQuerySemicolon),
  436. WithOptimizations,
  437. WithRemoteAddrHeader(
  438. "X-Real-Ip",
  439. "X-Forwarded-For",
  440. "CF-Connecting-IP",
  441. "True-Client-Ip",
  442. "X-Appengine-Remote-Addr",
  443. ),
  444. WithTimeout(s.step6.step5.step4.handlerTimeout),
  445. }
  446. app.Configure(defaultConfigurators...)
  447. s.app = app
  448. return app
  449. }
  450. func (s *step7) Listen(hostPort string, configurators ...Configurator) error {
  451. return s.Run(Addr(hostPort), configurators...)
  452. }
  453. func (s *step7) Run(runner Runner, configurators ...Configurator) error {
  454. app := s.Build()
  455. defer func() {
  456. // they will be called on interrupt signals too,
  457. // because Iris has a builtin mechanism to call server's shutdown on interrupt.
  458. for _, cb := range s.step6.closers {
  459. cb()
  460. }
  461. }()
  462. return app.Run(runner, configurators...)
  463. }