1
0
mirror of https://github.com/kataras/iris.git synced 2026-09-22 19:52:38 +00:00

last version of v12

This commit is contained in:
Gerasimos (Makis) Maropoulos
2024-04-25 01:43:13 +03:00
1443 changed files with 85668 additions and 101900 deletions
+306 -364
View File
@@ -1,364 +1,306 @@
# Examples
Please do learn how [net/http](https://golang.org/pkg/net/http/) std package works, first.
This folder provides easy to understand code snippets on how to get started with [iris](https://github.com/kataras/iris) web framework.
It doesn't always contain the "best ways" but it does cover each important feature that will make you so excited to GO with iris!
## Running the examples
1. Install the Go Programming Language, version 1.12+ from https://golang.org/dl.
2. [Install Iris](https://github.com/kataras/iris/wiki/installation)
3. Install any external packages that required by the examples
<details>
<summary>External packages</summary>
```sh
cd _examples && go get ./...
```
</details>
And run each example you wanna see, e.g.
```sh
$ cd $GOPATH/src/github.com/kataras/iris/_examples/overview
$ go run main.go
```
> Test the examples by opening a terminal window and execute: `go test -v ./...`
### Overview
- [Hello world!](hello-world/main.go)
- [Docker](docker/README.md)
- [Hello WebAssembly!](webassembly/basic/main.go)
- [Glimpse](overview/main.go)
- [Tutorial: Online Visitors](tutorial/online-visitors/main.go)
- [Tutorial: A Todo MVC Application using Iris and Vue.js](https://hackernoon.com/a-todo-mvc-application-using-iris-and-vue-js-5019ff870064)
- [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
- [Tutorial: How to turn your Android Device into a fully featured Web Server (**MUST**)](https://twitter.com/ThePracticalDev/status/892022594031017988)
- [POC: Convert the medium-sized project "Parrot" from native to Iris](https://github.com/iris-contrib/parrot)
- [POC: Isomorphic react/hot reloadable/redux/css-modules starter kit](https://github.com/kataras/iris-starter-kit)
- [Tutorial: DropzoneJS Uploader](tutorial/dropzonejs)
- [Tutorial: Caddy](tutorial/caddy)
- [Tutorial:Iris Go Framework + MongoDB](https://medium.com/go-language/iris-go-framework-mongodb-552e349eab9c)
- [Tutorial: API for Apache Kafka](tutorial/api-for-apache-kafka)
### Structuring
Nothing stops you from using your favorite folder structure. Iris is a low level web framework, it has got MVC first-class support but it doesn't limit your folder structure, this is your choice.
Structuring depends on your own needs. We can't tell you how to design your own application for sure but you're free to take a closer look to the examples below; you may find something useful that you can borrow for your app;
- [Bootstrapper](structuring/bootstrap)
- [MVC with Repository and Service layer Overview](structuring/mvc-plus-repository-and-service-layers)
- [Login (MVC with Single Responsibility package)](structuring/login-mvc-single-responsibility-package)
- [Login (MVC with Datamodels, Datasource, Repository and Service layer)](structuring/login-mvc)
### HTTP Listening
- [Common, with address](http-listening/listen-addr/main.go)
* [public domain address](http-listening/listen-addr-public/main.go)
* [omit server errors](http-listening/listen-addr/omit-server-errors/main.go)
- [UNIX socket file](http-listening/listen-unix/main.go)
- [TLS](http-listening/listen-tls/main.go)
- [Letsencrypt (Automatic Certifications)](http-listening/listen-letsencrypt/main.go)
- [Notify on shutdown](http-listening/notify-on-shutdown/main.go)
- Custom TCP Listener
* [common net.Listener](http-listening/custom-listener/main.go)
* [SO_REUSEPORT for unix systems](http-listening/custom-listener/unix-reuseport/main.go)
- Custom HTTP Server
* [HTTP/3 Quic](http-listening/http3-quic)
* [easy way](http-listening/custom-httpserver/easy-way/main.go)
* [std way](http-listening/custom-httpserver/std-way/main.go)
* [multi server instances](http-listening/custom-httpserver/multi/main.go)
- Graceful Shutdown
* [using the `RegisterOnInterrupt`](http-listening/graceful-shutdown/default-notifier/main.go)
* [using a custom notifier](http-listening/graceful-shutdown/custom-notifier/main.go)
### Configuration
- [Functional](configuration/functional/main.go)
- [From Configuration Struct](configuration/from-configuration-structure/main.go)
- [Import from YAML file](configuration/from-yaml-file/main.go)
* [Share Configuration between multiple instances](configuration/from-yaml-file/shared-configuration/main.go)
- [Import from TOML file](configuration/from-toml-file/main.go)
### Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context
* `app.Get("{userid:int min(1)}", myHandler)`
* `app.Post("{asset:path}", myHandler)`
* `app.Put("{custom:string regexp([a-z]+)}", myHandler)`
Note: unlike other routers you'd seen, iris' router can handle things like these:
```go
// Matches all GET requests prefixed with "/assets/"
app.Get("/assets/{asset:path}", assetsWildcardHandler)
// Matches only GET "/"
app.Get("/", indexHandler)
// Matches only GET "/about"
app.Get("/about", aboutHandler)
// Matches all GET requests prefixed with "/profile/"
// and followed by a single path part
app.Get("/profile/{username:string}", userHandler)
// Matches only GET "/profile/me" because
// it does not conflict with /profile/{username:string}
// or the root wildcard {root:path}
app.Get("/profile/me", userHandler)
// Matches all GET requests prefixed with /users/
// and followed by a number which should be equal or bigger than 1
app.Get("/user/{userid:int min(1)}", getUserHandler)
// Matches all requests DELETE prefixed with /users/
// and following by a number which should be equal or bigger than 1
app.Delete("/user/{userid:int min(1)}", deleteUserHandler)
// Matches all GET requests except "/", "/about", anything starts with "/assets/" etc...
// because it does not conflict with the rest of the routes.
app.Get("{root:path}", rootWildcardHandler)
```
Navigate through examples for a better understanding.
- [Overview](routing/overview/main.go)
- [Basic](routing/basic/main.go)
- [Controllers](mvc)
- [Custom HTTP Errors](routing/http-errors/main.go)
- [Not Found - Suggest Closest Paths](routing/not-found-suggests/main.go) **NEW**
- [Dynamic Path](routing/dynamic-path/main.go)
* [root level wildcard path](routing/dynamic-path/root-wildcard/main.go)
- [Write your own custom parameter types](routing/macros/main.go)
- [Reverse routing](routing/reverse/main.go)
- [Custom Router (high-level)](routing/custom-high-level-router/main.go)
- [Custom Wrapper](routing/custom-wrapper/main.go)
- Custom Context
* [method overriding](routing/custom-context/method-overriding/main.go)
* [new implementation](routing/custom-context/new-implementation/main.go)
- [Route State](routing/route-state/main.go)
- [Writing a middleware](routing/writing-a-middleware)
* [per-route](routing/writing-a-middleware/per-route/main.go)
* [globally](routing/writing-a-middleware/globally/main.go)
- [Route Register Rule](routing/route-register-rule/main.go) **NEW**
### Versioning
- [How it works](https://github.com/kataras/iris/blob/master/versioning/README.md)
- [Example](versioning/main.go)
### Dependency Injection
- [Basic](hero/basic/main.go)
- [Overview](hero/overview)
- [Sessions](hero/sessions)
- [Yet another dependency injection example and good practises at general](hero/smart-contract/main.go)
### MVC
- [Hello world](mvc/hello-world/main.go)
- [Regexp](mvc/regexp/main.go)
- [Session Controller](mvc/session-controller/main.go)
- [Overview - Plus Repository and Service layers](mvc/overview)
- [Login showcase - Plus Repository and Service layers](mvc/login)
- [Singleton](mvc/singleton)
- [Websocket Controller](mvc/websocket)
- [Register Middleware](mvc/middleware)
- [Vue.js Todo MVC](tutorial/vuejs-todo-mvc)
- [gRPC-compatible controller](mvc/grpc-compatible/main.go) **NEW**
### Subdomains
- [Single](subdomains/single/main.go)
- [Multi](subdomains/multi/main.go)
- [Wildcard](subdomains/wildcard/main.go)
- [WWW](subdomains/www/main.go)
- [Redirect fast](subdomains/redirect/main.go)
### Convert `http.Handler/HandlerFunc`
- [From func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)](convert-handlers/negroni-like/main.go)
- [From http.Handler or http.HandlerFunc](convert-handlers/nethttp/main.go)
- [From func(http.HandlerFunc) http.HandlerFunc](convert-handlers/real-usecase-raven/writing-middleware/main.go)
### View
- [Overview](view/overview/main.go)
- [Hi](view/template_html_0/main.go)
- [A simple Layout](view/template_html_1/main.go)
- [Layouts: `yield` and `render` tmpl funcs](view/template_html_2/main.go)
- [The `urlpath` tmpl func](view/template_html_3/main.go)
- [The `url` tmpl func](view/template_html_4/main.go)
- [Inject Data Between Handlers](view/context-view-data/main.go)
- [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
- [Write to a custom `io.Writer`](view/write-to)
- [Greeting with Pug (Jade)`](view/template_pug_0)
- [Pug (Jade) Actions`](view/template_pug_1)
- [Pug (Jade) Includes`](view/template_pug_2)
- [Pug (Jade) Extends`](view/template_pug_3)
- [Jet](/view/template_jet_0)
- [Jet Embedded](view/template_jet_1_embedded)
- [Jet 'urlpath' tmpl func](/view/template_jet_2)
- [Jet template funcs from structure](/view/template_jet_3)
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [hero templates](https://github.com/shiyanhui/hero/hero) files too, simply by using the `context#ResponseWriter`, take a look at the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate) and [http_responsewriter/herotemplate](http_responsewriter/herotemplate) examples.
### Localization and Internationalization
- [I18n](i18n/main.go) **NEW**
### Sitemap
- [Sitemap](sitemap/main.go) **NEW**
### Desktop App
- [Using blink package](desktop-app/blink) **NEW**
- [Using lorca package](desktop-app/lorca) **NEW**
- [Using webview package](desktop-app/webview) **NEW**
### Authentication
- [Basic Authentication](authentication/basicauth/main.go)
- [OAUth2](authentication/oauth2/main.go)
- [Request Auth(JWT)](experimental-handlers/jwt/main.go)
- [Sessions](#sessions)
### File Server
- [Favicon](file-server/favicon/main.go)
- [Basic](file-server/basic/main.go)
- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)
- [Embedding Gziped Files Into App Executable File](file-server/embedding-gziped-files-into-app/main.go)
- [Send/Force-Download Files](file-server/send-files/main.go)
- Single Page Applications
* [single Page Application](file-server/single-page-application/basic/main.go)
* [embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go)
* [embedded Single Page Application with other routes](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go)
### How to Read from `context.Request() *http.Request`
- [Read JSON](http_request/read-json/main.go)
* [Struct Validation](http_request/read-json-struct-validation/main.go)
- [Read XML](http_request/read-xml/main.go)
- [Read YAML](http_request/read-yaml/main.go)
- [Read Form](http_request/read-form/main.go)
- [Read Query](http_request/read-query/main.go)
- [Read Custom per type](http_request/read-custom-per-type/main.go)
- [Read Custom via Unmarshaler](http_request/read-custom-via-unmarshaler/main.go)
- [Read Many times](http_request/read-many/main.go)
- [Upload/Read File](http_request/upload-file/main.go)
- [Upload multiple files with an easy way](http_request/upload-files/main.go)
- [Extract referrer from "referer" header or URL query parameter](http_request/extract-referer/main.go)
> The `context.Request()` returns the same *http.Request you already know, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris.
### How to Write to `context.ResponseWriter() http.ResponseWriter`
- [Content Negotiation](http_responsewriter/content-negotiation)
- [Write `valyala/quicktemplate` templates](http_responsewriter/quicktemplate)
- [Write `shiyanhui/hero` templates](http_responsewriter/herotemplate)
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)
- [Write Gzip](http_responsewriter/write-gzip/main.go)
- [Stream Writer](http_responsewriter/stream-writer/main.go)
- [Transactions](http_responsewriter/transactions/main.go)
- [SSE](http_responsewriter/sse/main.go)
- [SSE (third-party package usage for server sent events)](http_responsewriter/sse-third-party/main.go)
> The `context/context#ResponseWriter()` returns an enchament version of a http.ResponseWriter, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris.
### ORM
- [Using xorm(Mysql, MyMysql, Postgres, Tidb, **SQLite**, MsSql, MsSql, Oracle)](orm/xorm/main.go)
- [Using gorm](orm/gorm/main.go)
### Miscellaneous
- [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go)
- [Request Logger](http_request/request-logger/main.go)
* [log requests to a file](http_request/request-logger/request-logger-file/main.go)
- [Recovery](miscellaneous/recover/main.go)
- [Profiling (pprof)](miscellaneous/pprof/main.go)
- [Internal Application File Logger](miscellaneous/file-logger/main.go)
- [Google reCAPTCHA](miscellaneous/recaptcha/main.go)
### Community-based Handlers
- [Casbin wrapper](experimental-handlers/casbin/wrapper/main.go)
- [Casbin middleware](experimental-handlers/casbin/middleware/main.go)
- [Cloudwatch](experimental-handlers/cloudwatch/simple/main.go)
- [CORS](experimental-handlers/cors/simple/main.go)
- [JWT](experimental-handlers/jwt/main.go)
- [Newrelic](experimental-handlers/newrelic/simple/main.go)
- [Prometheus](experimental-handlers/prometheus/simple/main.go)
- [Secure](experimental-handlers/secure/simple/main.go)
- [Tollboothic](experimental-handlers/tollboothic/limit-handler/main.go)
- [Cross-Site Request Forgery Protection](experimental-handlers/csrf/main.go)
#### More
https://github.com/kataras/iris/tree/master/middleware#third-party-handlers
### Automated API Documentation
- [yaag](apidoc/yaag/main.go)
### Testing
The `httptest` package is your way for end-to-end HTTP testing, it uses the httpexpect library created by our friend, [gavv](https://github.com/gavv).
[Example](testing/httptest/main_test.go)
### Caching
iris cache library lives on its own [package](https://github.com/kataras/iris/tree/master/cache).
- [Simple](cache/simple/main.go)
- [Client-Side (304)](cache/client-side/main.go) - part of the iris context core
> You're free to use your own favourite caching package if you'd like so.
### Cookies
- [Basic](cookies/basic/main.go)
- [Encode/Decode (securecookie)](cookies/securecookie/main.go)
### Sessions
iris session manager lives on its own [package](https://github.com/kataras/iris/tree/master/sessions).
- [Overview](sessions/overview/main.go)
- [Middleware](sessions/middleware/main.go)
- [Secure Cookie](sessions/securecookie/main.go)
- [Flash Messages](sessions/flash-messages/main.go)
- [Databases](sessions/database)
* [Badger](sessions/database/badger/main.go)
* [BoltDB](sessions/database/boltdb/main.go)
* [Redis](sessions/database/redis/main.go)
> You're free to use your own favourite sessions package if you'd like so.
### Websockets
- [Basic](websocket/basic)
* [Server](websocket/basic/server.go)
* [Go Client](websocket/basic/go-client/client.go)
* [Browser Client](websocket/basic/browser/index.html)
* [Browser NPM Client (browserify)](websocket/basic/browserify/app.js)
- [Native Messages](websocket/native-messages/main.go)
- [TLS Enabled](websocket/secure/README.md)
### Typescript Automation Tools
typescript automation tools have their own repository: [https://github.com/kataras/iris/tree/master/typescript](https://github.com/kataras/iris/tree/master/typescript) **it contains examples**
> I'd like to tell you that you can use your favourite but I don't think you will find such a thing anywhere else.
### Hey, You
Developers should read the [godocs](https://godoc.org/github.com/kataras/iris) and https://docs.iris-go.com for a better understanding.
Psst, I almost forgot; do not forget to [star or watch](https://github.com/kataras/iris/stargazers) the project in order to stay updated with the latest tech trends, it never takes more than a second!
# Table of Contents <a href="./README_ZH_HANT.md"> <img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /> </a>
* [Serverless](https://github.com/iris-contrib/gateway#netlify)
* [REST API for Apache Kafka](kafka-api)
* [URL Shortener](url-shortener)
* [Dropzone.js](dropzonejs)
* [Caddy](caddy)
* [Bootstrapper](bootstrapper)
* [Project Structure](project) :fire:
* Monitor
* [Simple Process Monitor (includes UI)](monitor/monitor-middleware/main.go) **NEW**
* [Heap, MSpan/MCache, Size Classes, Objects, Goroutines, GC/CPU fraction (includes UI)](monitor/statsviz/main.go) **NEW**
* Database
* [MySQL, Groupcache & Docker](database/mysql)
* [MongoDB](database/mongodb)
* [Sqlx](database/orm/sqlx/main.go)
* [Gorm](database/orm/gorm/main.go)
* [Reform](database/orm/reform/main.go)
* [x/sqlx](database/sqlx/main.go) **NEW**
* GraphQL
* [GraphQL: schema-first](graphql/schema-first) **NEW**
* HTTP Server
* [HOST:PORT](http-server/listen-addr/main.go)
* [Public Test Domain](http-server/listen-addr-public/main.go)
* [UNIX socket file](http-server/listen-unix/main.go)
* [TLS](http-server/listen-tls/main.go)
* [Letsencrypt (Automatic Certifications)](http-server/listen-letsencrypt/main.go)
* [Socket Sharding (SO_REUSEPORT)](http-server/socket-sharding/main.go)
* [Graceful Shutdown](http-server/graceful-shutdown/default-notifier/main.go)
* [Notify on shutdown](http-server/notify-on-shutdown/main.go)
* Custom TCP Listener
* [Common net.Listener](http-server/custom-listener/main.go)
* Custom HTTP Server
* [Pass a custom Server](http-server/custom-httpserver/easy-way/main.go)
* [Use Iris as a single http.Handler](http-server/custom-httpserver/std-way/main.go)
* [Multi Instances](http-server/custom-httpserver/multi/main.go)
* [HTTP/3 Quic](http-server/http3-quic)
* [H2C](http-server/h2c/main.go) **NEW**
* [Timeout](http-server/timeout/main.go)
* HTTP Client
* [Weather Client](http-client/weatherapi)
* Configuration
* [Functional](configuration/functional/main.go)
* [Configuration Struct](configuration/from-configuration-structure/main.go)
* [Using Viper](configuration/viper)
* [Import from YAML](configuration/from-yaml-file/main.go)
* [Share Configuration across instances](configuration/from-yaml-file/shared-configuration/main.go)
* [Import from TOML](configuration/from-toml-file/main.go)
* [Multi Environment Configuration](configuration/multi-environments) **NEW**
* Routing
* [Custom Context](routing/custom-context/main.go) **HOT/NEW**
* [Party Controller](routing/party-controller) **NEW**
* [Overview](routing/overview/main.go)
* [Basic](routing/basic/main.go)
* [Custom HTTP Errors](routing/http-errors/main.go)
* [HTTP Wire Errors](routing/http-wire-errors/main.go) **NEW**
* [Service and Validation](routing/http-wire-errors/service/main.go) **NEW**
* [Not Found - Intelligence](routing/intelligence/main.go)
* [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
* [Dynamic Path](routing/dynamic-path/main.go)
* [At-username](routing/dynamic-path/at-username/main.go)
* [Root Wildcard](routing/dynamic-path/root-wildcard/main.go)
* [Implement a Parameter Type](routing/macros/main.go)
* [Same Path Pattern but Func](routing/dynamic-path/same-pattern-different-func/main.go)
* Middleware
* [Per Route](routing/writing-a-middleware/per-route/main.go)
* [Globally](routing/writing-a-middleware/globally/main.go)
* [Remove a Handler](routing/remove-handler/main.go)
* Share Values
* [Share Services](routing/writing-a-middleware/share-services/main.go)
* [Share Functions](routing/writing-a-middleware/share-funcs/main.go)
* [Handlers Execution Rule](routing/route-handlers-execution-rules/main.go)
* [Route Register Rule](routing/route-register-rule/main.go)
* Convert net/http Handlers
* [From func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)](convert-handlers/negroni-like/main.go)
* [From http.Handler or http.HandlerFunc](convert-handlers/nethttp/main.go)
* [From func(http.Handler) http.Handler](convert-handlers/wrapper/main.go)
* [Convert by your own: sentry/raven middleware](convert-handlers/real-usecase-raven/writing-middleware/main.go)
* [Rewrite Middleware](routing/rewrite/main.go)
* [Route State](routing/route-state/main.go)
* [Remove Route](routing/remove-route/main.go)
* [Reverse Routing](routing/reverse/main.go)
* [Router Wrapper](routing/custom-wrapper/main.go)
* [Custom Router](routing/custom-router/main.go)
* Subdomains
* [Single](routing/subdomains/single/main.go)
* [Multi](routing/subdomains/multi/main.go)
* [Wildcard](routing/subdomains/wildcard/main.go)
* [WWW](routing/subdomains/www/main.go)
* [WWW Method](routing/subdomains/www/www-method/main.go)
* [Redirection](routing/subdomains/redirect/main.go)
* [Multi Instances](routing/subdomains/redirect/multi-instances/main.go)
* [HTTP Errors View](routing/subdomains/http-errors-view/main.go)
* [HTTP Method Override](https://github.com/kataras/iris/blob/main/middleware/methodoverride/methodoverride_test.go)
* [API Versioning](routing/versioning/main.go)
* [Sitemap](routing/sitemap/main.go)
* Logging
* [Application File Logger](logging/file-logger/main.go)
* [Application JSON Logger](logging/json-logger/main.go)
* [Rollbar](logging/rollbar/main.go)
* AccessLog
* [Log Requests to a JSON File](logging/request-logger/accesslog-simple/main.go)
* [Using Log Rotation and more](logging/request-logger/accesslog)
* [Custom Fields and Template](logging/request-logger/accesslog-template/main.go)
* [Listen and render Logs to a Client](logging/request-logger/accesslog-broker/main.go)
* [The CSV Formatter](logging/request-logger/accesslog-csv/main.go)
* [Create your own Formatter](logging/request-logger/accesslog-formatter/main.go)
* [Root and Proxy AccessLog instances](logging/request-logger/accesslog-proxy/main.go)
* [Slack integration example](logging/request-logger/accesslog-slack/main.go)
* API Documentation
* [Swagger](https://github.com/iris-contrib/swagger/tree/master/_examples/basic)
* Testing
* [Testing with httptest](testing/httptest/main_test.go)
* [Testing with ginkgo](testing/ginkgotest)
* [Recovery](recover/main.go)
* [Panic and custom Error Handler with Compression](recover/panic-and-custom-error-handler-with-compression/main.go)
* [Profiling](pprof/main.go)
* File Server
* [File Server](file-server/file-server/main.go)
* [HTTP/2 Push Targets](file-server/http2push/main.go)
* [HTTP/2 Push Targets (Embedded)](file-server/http2push-embedded/main.go)
* [HTTP/2 Push Targets (Gzipped Embedded)](file-server/http2push-embedded-gzipped/main.go)
* [Favicon](file-server/favicon/main.go)
* [Basic](file-server/basic/main.go)
* [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)
* [Embedding Files Into App Executable File (Bindata)](file-server/embedding-files-into-app-bindata/main.go)
* [Embedding Gzipped Files Into App Executable File (Bindata)](file-server/embedding-gzipped-files-into-app-bindata/main.go)
* [Send Files (rate limiter included)](file-server/send-files/main.go)
* Single Page Applications
* [Vue Router](file-server/spa-vue-router)
* [Basic SPA](file-server/single-page-application/basic/main.go)
* [Embedded Single Page Application and `iris.PrefixDir`](file-server/single-page-application/embedded-single-page-application/main.go)
* [Embedded Single Page Application with other routes](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go)
* [Upload File](file-server/upload-file/main.go)
* [Upload Multiple Files](file-server/upload-files/main.go)
* [WebDAV](file-server/webdav/main.go)
* View
* [Overview](view/overview/main.go)
* [Layout](view/layout)
* [Ace](view/layout/ace)
* [Blocks](view/layout/blocks)
* [Django](view/layout/django)
* [Handlebars](view/layout/handlebars)
* [HTML](view/layout/html)
* [Jet](view/layout/jet)
* [Pug](view/layout/pug)
* [Basic](view/template_html_0/main.go)
* [A simple Layout](view/template_html_1/main.go)
* [Layouts: `yield` and `render` tmpl funcs](view/template_html_2/main.go)
* The `urlpath` template func
* [HTML](view/template_html_3/main.go)
* [Django](view/template_django_1/main.go)
* [The `url` template func](view/template_html_4/main.go)
* [Inject Data Between Handlers](view/context-view-data/main.go)
* [Inject Engine Between Handlers](view/context-view-engine/main.go)
* [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
* [Embedding Templates Into App Executable File (Bindata)](view/embedding-templates-into-app-bindata/main.go)
* [Write to a custom `io.Writer`](view/write-to)
* Parse a Template from Text
* [HTML, Pug and Ace](view/parse-parse/main.go)
* [Django](view/parse-parse/django/main.go)
* [Jet](view/parse-parse/jet/main.go)
* [Handlebars](view/parse-parse/handlebars/main.go)
* [Blocks](view/template_blocks_0)
* [Blocks Embedded](view/template_blocks_1_embedded)
* [Pug: `Actions`](view/template_pug_0)
* [Pug: `Includes`](view/template_pug_1)
* [Pug Embedded`](view/template_pug_2_embedded)
* [Ace](view/template_ace_0)
* [Django](view/template_django_0)
* [Jet](view/template_jet_0)
* [Jet Embedded](view/template_jet_1_embedded)
* [Jet 'urlpath' tmpl func](view/template_jet_2)
* [Jet Template Funcs from Struct](view/template_jet_3)
* [Handlebars](view/template_handlebars_0)
* Third-Parties
* [Render `valyala/quicktemplate` templates](view/quicktemplate)
* [Render `shiyanhui/hero` templates](view/herotemplate)
* [Render `a-h/templ` templates](view/templ) **NEW**
* [Request ID](https://github.com/kataras/iris/blob/main/middleware/requestid/requestid_test.go)
* [Request Rate Limit](request-ratelimit/main.go)
* [Request Referrer](request-referrer/main.go)
* [Webassembly](webassembly/main.go)
* Request Body
* [Bind JSON](request-body/read-json/main.go)
* * [JSON Stream and disable unknown fields](request-body/read-json-stream/main.go)
* * [Struct Validation](request-body/read-json-struct-validation/main.go)
* [Bind XML](request-body/read-xml/main.go)
* [Bind MsgPack](request-body/read-msgpack/main.go)
* [Bind YAML](request-body/read-yaml/main.go)
* [Bind Form](request-body/read-form/main.go)
* [Checkboxes](request-body/read-form/checkboxes/main.go)
* [Bind Query](request-body/read-query/main.go)
* [Bind Params](request-body/read-params/main.go)
* [Bind URL](request-body/read-url/main.go)
* [Bind Headers](request-body/read-headers/main.go)
* [Bind Body](request-body/read-body/main.go)
* [Add Converter](request-body/form-query-headers-params-decoder/main.go)
* [Bind Custom per type](request-body/read-custom-per-type/main.go)
* [Bind Custom via Unmarshaler](request-body/read-custom-via-unmarshaler/main.go)
* [Bind Many times](request-body/read-many/main.go)
* Response Writer
* [Content Negotiation](response-writer/content-negotiation)
* [Text, Markdown, YAML, HTML, JSON, JSONP, Msgpack, XML and Binary](response-writer/write-rest/main.go)
* [Third-party JSON Encoder](response-writer/json-third-party/main.go)
* [Protocol Buffers](response-writer/protobuf/main.go)
* [HTTP/2 Server Push](response-writer/http2push/main.go)
* [Stream Writer](response-writer/stream-writer/main.go)
* [Server-Sent Events](response-writer/sse/main.go)
* [SSE 3rd-party (r3labs/sse)](response-writer/sse-third-party/main.go)
* [SSE 3rd-party (alexandrevicenzi/go-sse)](response-writer/sse-third-party-2/main.go)
* Cache
* [Simple](response-writer/cache/simple/main.go)
* [Client-Side (304)](response-writer/cache/client-side/main.go)
* Compression
* [Server-Side](compression/main.go)
* [Client-Side](compression/client/main.go)
* [Client-Side (using Iris)](compress/client-using-iris/main.go)
* Localization and Internationalization
* [Basic](i18n/basic)
* [Ttemplates and Functions](i18n/template)
* [Ttemplates and Functions (Embedded)](i18n/template-embedded)
* [Pluralization and Variables](i18n/plurals)
* Authentication, Authorization & Bot Detection
* [Recommended: Auth package and Single-Sign-On](auth/auth) **NEW (GO 1.18 Generics required)**
* Basic Authentication
* [Basic](auth/basicauth/basic)
* [Load from a slice of Users](auth/basicauth/users_list)
* [Load from a file & encrypted passwords](auth/basicauth/users_file_bcrypt)
* [Fetch & validate a User from a Database (MySQL)](auth/basicauth/database)
* [CORS](auth/cors)
* JSON Web Tokens
* [Basic](auth/jwt/basic/main.go)
* [Middleware](auth/jwt/midleware/main.go)
* [Blocklist](auth/jwt/blocklist/main.go)
* [Refresh Token](auth/jwt/refresh-token/main.go)
* [Tutorial](auth/jwt/tutorial)
* [JWT (community edition)](https://github.com/iris-contrib/middleware/tree/v12/jwt/_example/main.go)
* [OAUth2](auth/goth/main.go)
* [Manage Permissions](auth/permissions/main.go)
* [Google reCAPTCHA](auth/recaptcha/main.go)
* [hCaptcha](auth/hcaptcha/main.go)
* Cookies
* [Basic](cookies/basic/main.go)
* [Options](cookies/options/main.go)
* [Encode/Decode (with `securecookie`)](cookies/securecookie/main.go)
* Sessions
* [Overview: Config](sessions/overview/main.go)
* [Overview: Routes](sessions/overview/example/example.go)
* [Basic](sessions/basic/main.go)
* [Secure Cookie](sessions/securecookie/main.go)
* [Flash Messages](sessions/flash-messages/main.go)
* [Databases](sessions/database)
* [Badger](sessions/database/badger/main.go)
* [BoltDB](sessions/database/boltdb/main.go)
* [Redis](sessions/database/redis/main.go)
* [View Data](sessions/viewdata)
* Websocket
* [Gorilla FileWatch (3rd-party)](websocket/gorilla-filewatch/main.go)
* [Basic](websocket/basic)
* [Server](websocket/basic/server.go)
* [Go Client](websocket/basic/go-client/client.go)
* [Browser Client](websocket/basic/browser/index.html)
* [Browser NPM Client (browserify)](websocket/basic/browserify/app.js)
* [Native Messages](websocket/native-messages/main.go)
* [TLS](websocket/secure/README.md)
* [Online Visitors](websocket/online-visitors/main.go)
* Dependency Injection
* [Overview (Movies Service)](ependency-injection/overview/main.go)
* [Basic](dependency-injection/basic/main.go)
* [Middleware](dependency-injection/basic/middleware/main.go)
* [Sessions](dependency-injection/sessions/main.go)
* [Smart Contract](dependency-injection/smart-contract/main.go)
* [JWT](dependency-injection/jwt/main.go)
* [JWT (iris-contrib)](dependency-injection/jwt/contrib/main.go)
* [Register Dependency from Context](dependency-injection/context-register-dependency/main.go)
* MVC
* [Overview](mvc/overview)
* [Repository and Service layers](mvc/repository)
* [Hello world](mvc/hello-world/main.go)
* [Basic](mvc/basic/main.go)
* [Wildcard](mvc/basic/wildcard/main.go)
* [Default request values](mvc/request-default-values/main.go)
* [Singleton](mvc/singleton)
* [Regexp](mvc/regexp/main.go)
* [Session Controller](mvc/session-controller/main.go)
* [Authenticated Controller](mvc/authenticated-controller/main.go)
* [Versioned Controller](mvc/versioned-controller/main.go)
* [Websocket Controller](mvc/websocket)
* [Websocket + Authentication (Single-Sign-On)](mvc/websocket-auth) **NEW (GO 1.18 Generics required)**
* [Register Middleware](mvc/middleware)
* [gRPC](mvc/grpc-compatible)
* [gRPC Bidirectional Stream](mvc/grpc-compatible-bidirectional-stream)
* [Login (Repository and Service layers)](mvc/login)
* [Login (Single Responsibility)](mvc/login-mvc-single-responsibility)
* [Vue.js Todo App](mvc/vuejs-todo-mvc)
* [HTTP Error Handler](mvc/error-handler-http)
* [Error Handler](mvc/error-handler)
* [Handle errors using mvc.Result](mvc/error-handler-custom-result)
* [Handle errors using PreflightResult](mvc/error-handler-preflight)
* [Handle errors by hijacking the result](mvc/error-handler-hijack)
* Desktop Applications
* [The blink package](desktop/blink)
* [The lorca package](desktop/lorca)
* [The webview package](desktop/webview)
* Middlewares [(Community)](https://github.com/iris-contrib/middleware)
-452
View File
@@ -1,452 +0,0 @@
# 示例
请先学习如何使用 [net/http](https://golang.org/pkg/net/http/)
这里包含大部分 [iris](https://github.com/kataras/iris) 网络微框架的简单使用示例
这些示例不一定是最优解,但涵盖了 Iris 的大部分重要功能。
### 概览
- [Hello world!](hello-world/main.go)
- [Hello WebAssemply!](webassembly/basic/main.go)
- [基础](overview/main.go)
- [教程: 在线人数](tutorial/online-visitors/main.go)
- [教程: 一个“待完成”MVC Application基于Iris和Vue.js](https://hackernoon.com/a-todo-mvc-application-using-iris-and-vue-js-5019ff870064)
- [教程: 结合 BoltDB 生成短网址](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
- [教程: 用安卓设备搭建服务器 (**MUST**)](https://twitter.com/ThePracticalDev/status/892022594031017988)
- [POC: 把中等项目"Parrot"从原生转换到Iris](https://github.com/iris-contrib/parrot)
- [POC: 同构react/hot reloadable/redux/css-modules的起始工具包](https://github.com/kataras/iris-starter-kit)
- [教程: DropzoneJS 上传](tutorial/dropzonejs)
- [教程: Caddy 服务器使用](tutorial/caddy)
- [教程: Iris + MongoDB](https://medium.com/go-language/iris-go-framework-mongodb-552e349eab9c)
- [教程: Apache Kafka的API](tutorial/api-for-apache-kafka)
### 目录结构
Iris 是个底层框架, 对 MVC 模式有很好的支持,但不限制文件夹结构,你可以随意组织你的代码。
如何组织代码取决于你的需求. 我们无法告诉你如何设计程序,但你可以仔细查看下面的示例,也许有些片段可以直接放到你的程序里。
- [引导模式架构](structuring/bootstrap)
- [MVC 存储层与服务层](structuring/mvc-plus-repository-and-service-layers)
- [登录演示 (MVC 使用独立包组织)](structuring/login-mvc-single-responsibility-package)
- [登录演示 (MVC 数据模型, 数据源, 存储 和 服务层)](structuring/login-mvc)
### HTTP 监听
- [基础用法](http-listening/listen-addr/main.go)
   * [忽略错误信息](http-listening/listen-addr/omit-server-errors/main.go)
- [UNIX socket文件](http-listening/listen-unix/main.go)
- [TLS](http-listening/listen-tls/main.go)
- [Letsencrypt (自动认证)](http-listening/listen-letsencrypt/main.go)
- [进程关闭通知](http-listening/notify-on-shutdown/main.go)
- 自定义 TCP 监听器
   * [通用 net.Listener](http-listening/custom-listener/main.go)
* [unix系统的SO_REUSEPORT](http-listening/custom-listener/unix-reuseport/main.go)
- 自定义 HTTP 服务
* [HTTP/3 Quic](http-listening/http3-quic) **凊**
   * [简单方式](http-listening/custom-httpserver/easy-way/main.go)
* [标准方式](http-listening/custom-httpserver/std-way/main.go)
   * [多个服务示例](http-listening/custom-httpserver/multi/main.go)
- 优雅关闭
   * [使用 `RegisterOnInterrupt`](http-listening/graceful-shutdown/default-notifier/main.go)
   * [自定义通知](http-listening/graceful-shutdown/custom-notifier/main.go)
### 配置
- [基本配置方式](configuration/functional/main.go)
- [Struct 方式配置](configuration/from-configuration-structure/main.go)
- [导入 YAML 配置文件](configuration/from-yaml-file/main.go)
   * [多实例共享配置](configuration/from-yaml-file/shared-configuration/main.go)
- [导入 TOML 配置文件](configuration/from-toml-file/main.go)
### 路由、路由分组、路径动态参数、路由参数处理宏 、 自定义上下文
* `app.Get("{userid:int min(1)}", myHandler)`
* `app.Post("{asset:path}", myHandler)`
* `app.Put("{custom:string regexp([a-z]+)}", myHandler)`
提示: 不同于其他路由处理, iris 路由可以处理以下各种情况:
```go
// 匹配静态前缀 "/assets/" 的各种请求
app.Get("/assets/{asset:path}", assetsWildcardHandler)
// 只匹配 GET "/"
app.Get("/", indexHandler)
// 只匹配 GET "/about"
app.Get("/about", aboutHandler)
// 匹配前缀为 "/profile/" 的所有 GET 请求
// 接着是其余部分的匹配
app.Get("/profile/{username:string}", userHandler)
// 只匹配 "/profile/me" GET 请求,
// 这和 /profile/{username:string}
// 或跟通配符 {root:path} 不冲突
app.Get("/profile/me", userHandler)
// 匹配所有前缀为 /users/ 的 GET 请求
// 参数为数字,且 >= 1
app.Get("/user/{userid:int min(1)}", getUserHandler)
// 匹配所有前缀为 /users/ 的 DELETE 请求
// 参数为数字,且 >= 1
app.Delete("/user/{userid:int min(1)}", deleteUserHandler)
// 匹配所有 GET 请求,除了 "/", "/about", 或其他以 "/assets/" 开头
// 因为它不会与其他路线冲突。
app.Get("{root:path}", rootWildcardHandler)
```
可以浏览以下示例,以便更好理解
- [概览](routing/overview/main.go)
- [基本使用](routing/basic/main.go)
- [控制器](mvc)
- [自定义 HTTP 错误](routing/http-errors/main.go)
- [动态路径](routing/dynamic-path/main.go)
* [根级通配符路径](routing/dynamic-path/root-wildcard/main.go)
- [编写你自己的参数类型](routing/macros/main.go)
- [反向路由](routing/reverse/main.go)
- [自定义路由(高层级)](routing/custom-high-level-router/main.go)
- [自定义包装](routing/custom-wrapper/main.go) **更新**
- 自定义上下文
   * [方法重写](routing/custom-context/method-overriding/main.go)
   * [新实现方式](routing/custom-context/new-implementation/main.go)
- [路由状态](routing/route-state/main.go)
- [中间件定义](routing/writing-a-middleware)
   * [路由前](routing/writing-a-middleware/per-route/main.go)
   * [全局](routing/writing-a-middleware/globally/main.go)
### hero (输出的一种高效包装模式)
- [基础](hero/basic/main.go)
- [概览](hero/overview)
- [Sessions](hero/sessions)
- [另一种依赖注入的例子和通常的较好实践](hero/smart-contract/main.go) **新**
### i18n
- [本地化和多语言支持](i18n/main.go)
### MVC 模式
![](mvc/web_mvc_diagram.png)
Iris **对 MVC (Model View Controller) 有一流的支持**, 在 Go 社区里是独一无二的。
Iris 支持快速的请求数据,模型,持久性数据和绑定。
**特点**
支持所有的HTTP访问方式,例如,如果想要处理`GET`请求,那么控制器应该有一个叫做`Get()`的函数
你可以在同一个控制器上面定义不止一个请求处理函数(method function).
通过`BeforeActivation`对每个控制器自定义事件回调,使自定义控制器的结构方法(struct's methods)处理自定义路径(甚至是包含参数是正则表达式的路径),例如:
```go
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
mvc.Configure(app.Party("/root"), myMVC)
app.Run(iris.Addr(":8080"))
}
func myMVC(app *mvc.Application) {
// app.Register(...)
// app.Router.Use/UseGlobal/Done(...)
app.Handle(new(MyController))
}
type MyController struct {}
func (m *MyController) BeforeActivation(b mvc.BeforeActivation) {
// b.Dependencies().Add/Remove
// b.Router().Use/UseGlobal/Done // 以及任何你已经知道的标准API调用
// 1-> 方法
// 2-> 路径
// 3-> 被解释成handler的控制器函数名称
// 4-> 在MyCustomHandler之前需要执行的其他handler
b.Handle("GET", "/something/{id:long}", "MyCustomHandler", anyMiddleware...)
}
// GET: http://localhost:8080/root
func (m *MyController) Get() string { return "Hey" }
// GET: http://localhost:8080/root/something/{id:long}
func (m *MyController) MyCustomHandler(id int64) string { return "MyCustomHandler says Hey" }
```
通过定义依赖项服务或者有一个`单一(Singleton)`控制器范畴来持久化你控制器结构中的数据(多次请求间共享的数据)
在控制器间共享依赖或者把他们注册到上层MVC应用,以及
在一个控制器内部可以修改每一个控制器在`BeforeActivation`可选事件回调上的依赖项的能力。
`func(c *MyController) BeforeActivation(b mvc.BeforeActivation) { b.Dependencies().Add/Remove(...) }`.
访问`Context`作为一个控制器的域(field)(不需要手动绑定)即`Ctx iris.Context`,或者通过一个方法的输入参数,即`func(ctx iris.Context, otherArguments...)`
你控制器结构中的模型(在模型函数中设置并且由视图来渲染)
你可以通过一个控制器的方法来返回模型或者设置一个请求生命周期的域
并且在同一个生命周期中把这个域返回给另一个方法。
正如你之前所熟知的流程,mvc应用程序有它自己的以标准iris API `iris/router.Party`作为类型的`路由(router)`
`控制器`可以被注册到任意`集合(party)`,包括子域名,这个集合会像所预料那样开始完成处理器工作。
可额外调用`BeginRequest(ctx)`在任何方法被执行前进行初始化,对于调用中间层(middlewares)或者使用同一组数据的多个方法而言十分有效
同样可调用`EndRequest(ctx)`在任何方法执行后做完成工作(finalization)
递归继承参考我们`mvc.SessionController`的例子,它以`Session *sessions.Session``Manager *sessions.Sessions`作为嵌入域,由它的`BeginRequest`来传递,看[这里](https://github.com/kataras/iris/blob/master/mvc/session_controller.go)
这只是一个例子,你可以使用`sessions.Session`,它作为一个MVC应用的动态依赖从管理者的`Start`返回,即
`mvcApp.Register(sessions.New(sessions.Config{Cookie: "iris_session_id"}).Start)`.
通过控制器方法的输入参数来访问动态路径参数,不需要绑定。
当你需要使用Iris的默认语法从一个控制器里来解析一个handler,你需要在这个方法前加上`By` 大写表示一个新的子路径。例如:
如果是 `mvc.New(app.Party("/user")).Handle(new(user.Controller))`
- `func(*Controller) Get()` - `GET:/user`.
- `func(*Controller) Post()` - `POST:/user`.
- `func(*Controller) GetLogin()` - `GET:/user/login`
- `func(*Controller) PostLogin()` - `POST:/user/login`
- `func(*Controller) GetProfileFollowers()` - `GET:/user/profile/followers`
- `func(*Controller) PostProfileFollowers()` - `POST:/user/profile/followers`
- `func(*Controller) GetBy(id int64)` - `GET:/user/{param:long}`
- `func(*Controller) PostBy(id int64)` - `POST:/user/{param:long}`
如果是 `mvc.New(app.Party("/profile")).Handle(new(profile.Controller))`
- `func(*Controller) GetBy(username string)` - `GET:/profile/{param:string}`
如果是 `mvc.New(app.Party("/assets")).Handle(new(file.Controller))`
- `func(*Controller) GetByWildard(path string)` - `GET:/assets/{param:path}`
支持的函数接收者类型是:int, int64, bool and string。
可以通过输出参数来响应,即
```go
func(c *ExampleController) Get() string |
(string, string) |
(string, int) |
int |
(int, string) |
(string, error) |
error |
(int, error) |
(any, bool) |
(customStruct, error) |
customStruct |
(customStruct, int) |
(customStruct, string) |
mvc.Result or (mvc.Result, error)
```
其中[mvc.Result](https://github.com/kataras/iris/blob/master/mvc/func_result.go)是一个仅包含`Dispatch(ctx iris.Context)`的接口
## Iris MVC 模式代码复用
通过创建互相独立的组建,开发者可以简单快捷地在别的应用里面复用组建。 一个应用同样的(或相似的)视图使用不同的数据可以被重构给另一个应用,因为视图仅仅处理数据怎么展示给用户。
如果你是一个新的web后端开发者,请先阅读MVC架构模式,一个不错的入门是[wikipedia article](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
参考下面的示例
- [Hello world](mvc/hello-world/main.go) **更新**
- [Session Controller](mvc/session-controller/main.go) **更新**
- [Overview - Plus Repository and Service layers](mvc/overview) **更新**
- [Login showcase - Plus Repository and Service layers](mvc/login) **更新**
- [Singleton](mvc/singleton) **新**
- [Websocket Controller](mvc/websocket) **新**
- [Register Middleware](mvc/middleware) **新**
- [Vue.js Todo MVC](tutorial/vuejs-todo-mvc) **新**
### 子域名
- [单域名](subdomains/single/main.go)
- [多域名](subdomains/multi/main.go)
- [通配符](subdomains/wildcard/main.go)
- [WWW](subdomains/www/main.go)
- [快速跳转](subdomains/redirect/main.go)
### 改造 `http.Handler/HandlerFunc`
- [From func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)](convert-handlers/negroni-like/main.go)
- [From http.Handler or http.HandlerFunc](convert-handlers/nethttp/main.go)
- [From func(http.HandlerFunc) http.HandlerFunc](convert-handlers/real-usecase-raven/writing-middleware/main.go)
### 视图
| 模板引擎 | 调用声明 |
| -----------|-------------|
| template/html | `iris.HTML(...)` |
| django | `iris.Django(...)` |
| handlebars | `iris.Handlebars(...)` |
| amber | `iris.Amber(...)` |
| pug(jade) | `iris.Pug(...)` |
- [Overview概览](view/overview/main.go)
- [Hi](view/template_html_0/main.go)
- [A simple Layout简单层](view/template_html_1/main.go)
- [Layouts: `yield` and `render` tmpl funcs 视图层`生产`以及`渲染`模版函数](view/template_html_2/main.go)
- [The `urlpath` tmpl func`urlpath`模版函数](view/template_html_3/main.go)
- [The `url` tmpl func`url`模版函数](view/template_html_4/main.go)
- [Inject Data Between Handlers在处理器间注入数据](view/context-view-data/main.go)
- [Embedding Templates Into App Executable File在应用程序可执行文件中嵌入模版](view/embedding-templates-into-app/main.go)
- [Write to a custom `io.Writer`自定义`io.Writer`](view/write-to)
- [Greeting with `Pug (Jade)`使用`Pug (Jade)`](view/template_pug_0)
- [`Pug (Jade) Actions`](view/template_pug_1)
- [`Pug (Jade) Includes`](view/template_pug_2)
- [`Pug (Jade) Extends`](view/template_pug_3)
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [hero templates](https://github.com/shiyanhui/hero/hero) files too, simply by using the `context#ResponseWriter`, take a look at the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate) and [http_responsewriter/herotemplate](http_responsewriter/herotemplate) examples.
只要使用`context#ResponseWriter`,你可以服务[quicktemplate](https://github.com/valyala/quicktemplate)和[hero templates](https://github.com/shiyanhui/hero/hero)文件。
看这the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate)和[http_responsewriter/herotemplate](http_responsewriter/herotemplate)的例子
### 认证
- [Basic Authentication](authentication/basicauth/main.go)
- [OAUth2](authentication/oauth2/main.go)
- [JWT](experimental-handlers/jwt/main.go)
- [Sessions](#sessions)
### 文件服务器
- [Favicon](file-server/favicon/main.go)
- [基础操作](file-server/basic/main.go) **更新**
- [把文件嵌入应用的可执行文件](file-server/embedding-files-into-app/main.go) **更新**
- [嵌入Gzip压缩的文件到可咨询文件](file-server/embedding-gziped-files-into-app/main.go) **更新**
- [上传/(强制)下载文件](file-server/send-files/main.go)
- 单页面应用(Single Page Applications)
* [单页面应用](file-server/single-page-application/basic/main.go) **更新**
* [嵌入式(embedded)单页面应用](file-server/single-page-application/embedded-single-page-application/main.go) **更新**
* [使用额外路由的嵌入式单页面应用](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go) **更新**
### 如何读取`context.Request() *http.Request`
- [读取JSON](http_request/read-json/main.go)
- [读取XML](http_request/read-xml/main.go)
- [读取YAML](http_request/read-yaml/main.go) **更新**
- [读取Form](http_request/read-form/main.go)
- [读取Query](http_request/read-query/main.go) **更新**
- [读取每个类型的自定义结果Custom per type](http_request/read-custom-per-type/main.go)
- [通过Unmarshaler读取Custom](http_request/read-custom-via-unmarshaler/main.go)
- [上传/读取文件Upload/Read File](http_request/upload-file/main.go)
- [简单上传多个文件Upload multiple files with an easy way](http_request/upload-files/main.go)
> The `context.Request()` returns the same *http.Request you already know, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris.
> `context.Request()`返回你已知的同一*http.Request 这些例子给出了Context使用这个对象的地方。 除此以外你可以在使用iris之前那样子使用它
### 如何写入`context.ResponseWriter() http.ResponseWriter`
- [Content Negotiation](http_responsewriter/content-negotiation) **更新**
- [`valyala/quicktemplate`模版](http_responsewriter/quicktemplate)
- [`shiyanhui/hero`模版](http_responsewriter/herotemplate)
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)
- [写入Gzip压缩](http_responsewriter/write-gzip/main.go)
- [流输出Stream Writer](http_responsewriter/stream-writer/main.go)
- [数据传递Transactions](http_responsewriter/transactions/main.go)
- [SSE](http_responsewriter/sse/main.go)
- [SSE (third-party package usage for server sent events第三方库SSE)](http_responsewriter/sse-third-party/main.go)
> The `context/context#ResponseWriter()` returns an enchament version of a http.ResponseWriter, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris.
> `context.Request()`返回了一个http.ResponseWriter的迷醉(魔幻)版本, 这些例子给出了Context使用这个对象的地方。 除此以外你可以在使用iris之前那样子使用它
### ORM
- [使用 xorm(Mysql, MyMysql, Postgres, Tidb, **SQLite**, MsSql, MsSql, Oracle)](orm/xorm/main.go)
### 其他
- [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go) **更新**
- [请求记录器](http_request/request-logger/main.go)
* [将请求记录到文件](http_request/request-logger/request-logger-file/main.go)
- [恢复](miscellaneous/recover/main.go)
- [性能报告Profiling (pprof)](miscellaneous/pprof/main.go)
- [内部文件记录Internal Application File Logger](miscellaneous/file-logger/main.go)
- [Google验证码Google reCAPTCHA](miscellaneous/recaptcha/main.go)
### 试验性质处理器
- [Casbin wrapper](experimental-handlers/casbin/wrapper/main.go)
- [Casbin middleware](experimental-handlers/casbin/middleware/main.go)
- [Cloudwatch](experimental-handlers/cloudwatch/simple/main.go)
- [CORS](experimental-handlers/cors/simple/main.go)
- [JWT](experimental-handlers/jwt/main.go)
- [Newrelic](experimental-handlers/newrelic/simple/main.go)
- [Prometheus](experimental-handlers/prometheus/simple/main.go)
- [安全](experimental-handlers/secure/simple/main.go)
- [Tollboothic](experimental-handlers/tollboothic/limit-handler/main.go)
- [跨站点伪造请求(CSRF)防护](experimental-handlers/csrf/main.go)
#### 更多
https://github.com/kataras/iris/tree/master/middleware#third-party-handlers
### 自动 API 文档
- [yaag](apidoc/yaag/main.go)
### 测试
The `httptest` package is your way for end-to-end HTTP testing, it uses the httpexpect library created by our friend, [gavv](https://github.com/gavv).
`httptest`包是你用于端对端HTTP测试的,它使用我们朋友[gavv](https://github.com/gavv)创建的httpexpect库
[例子](testing/httptest/main_test.go)
### 缓存
Iris 独立缓存包 [package](https://github.com/kataras/iris/tree/master/cache).
- [简单示例](cache/simple/main.go)
- [客户端 (304)](cache/client-side/main.go) - context 方法
> 可以随意使用自定义的缓存包。
### Cookies
- [基础](cookies/basic/main.go)
- [加密/解密 (安全cookie)](cookies/securecookie/main.go)
### Sessions
Iris session 管理独立包 [package](https://github.com/kataras/iris/tree/master/sessions).
- [概览](sessions/overview/main.go)
- [中间件](sessions/middleware/main.go)
- [安全cookie](sessions/securecookie/main.go)
- [临时消息](sessions/flash-messages/main.go)
- [数据库](sessions/database)
* [Badger](sessions/database/badger/main.go)
* [Redis](sessions/database/redis/main.go)
> 可以随意使用自定义的 Session 管理包。
### Websockets
- [Basic](websocket/basic) **新**
* [Server](websocket/basic/server.go)
* [Go Client](websocket/basic/go-client/client.go)
* [Browser Client](websocket/basic/browser/index.html)
* [Browser NPM Client (browserify)](websocket/basic/browserify/app.js)
- [原生消息](websocket/native-messages/main.go) **更新**
- [TLS支持](websocket/secure/README.md)
### Typescript 自动化工具
Typescript 自动化工具独立库: [https://github.com/kataras/iris/tree/master/typescript](https://github.com/kataras/iris/tree/master/typescript) **包含相关示例**
### 大兄弟
进一步学习可通过 [godocs](https://godoc.org/github.com/kataras/iris) 和 https://docs.iris-go.com
不要忘记点赞 [star or watch](https://github.com/kataras/iris/stargazers) 这个项目会一直跟进最新趋势。
+301
View File
@@ -0,0 +1,301 @@
# 章節目錄
- [無伺服器 (Serverless)](https://github.com/iris-contrib/gateway#netlify)
- [適用 Apache Kafka 的 REST API](kafka-api)
- [縮網址服務](url-shortener)
- [Dropzone.js](dropzonejs)
- [Caddy](caddy)
- [初始化工具](bootstrapper)
- [專案結構](project) :fire:
- 監控
- [簡易處理程序監控工具 (含 UI)](monitor/monitor-middleware/main.go) **新範例**
- [堆積、MSpan/MCache、Size Classes、物件、Goroutines、GC/CPU 分數 (含 UI)](monitor/statsviz/main.go) **新範例**
- 資料庫
- [MySQL、Groupcache 和 Docker](database/mysql)
- [MongoDB](database/mongodb)
- [Sqlx](database/orm/sqlx/main.go)
- [Gorm](database/orm/gorm/main.go)
- [Reform](database/orm/reform/main.go)
- [x/sqlx](database/sqlx/main.go) **新範例**
- HTTP 伺服器
- [主機:連線埠](http-server/listen-addr/main.go)
- [公開測試網域](http-server/listen-addr-public/main.go)
- [UNIX 通訊端 (socket) 檔案](http-server/listen-unix/main.go)
- [TLS](http-server/listen-tls/main.go)
- [Let's Encrypt(自動發證)](http-server/listen-letsencrypt/main.go)
- [通訊端切分 (SO_REUSEPORT)](http-server/socket-sharding/main.go)
- [優雅關閉伺服器](http-server/graceful-shutdown/default-notifier/main.go)
- [關閉伺服器時通知](http-server/notify-on-shutdown/main.go)
- 自訂 TCP 監聽
- [通用 net.Listener](http-server/custom-listener/main.go)
- 自訂 HTTP 伺服器
- [傳入自訂 Server 實體](http-server/custom-httpserver/easy-way/main.go)
- [將 Iris 用作單一 http.Handler](http-server/custom-httpserver/std-way/main.go)
- [多實體](http-server/custom-httpserver/multi/main.go)
- [HTTP/3 QUIC](http-server/http3-quic)
- [H2C](http-server/h2c/main.go) **新範例**
- [延時 (timeout)](http-server/timeout/main.go)
- HTTP 用戶端
- [天氣用戶端](http-client/weatherapi)
- 設定
- [Functional](configuration/functional/main.go)
- [Configuration Struct](configuration/from-configuration-structure/main.go)
- [Using Viper](configuration/viper)
- [Import from YAML](configuration/from-yaml-file/main.go)
- [Share Configuration across instances](configuration/from-yaml-file/shared-configuration/main.go)
- [Import from TOML](configuration/from-toml-file/main.go)
- [Multi Environment Configuration](configuration/multi-environments) **新範例**
- 路由
- [Party Controller](routing/party-controller) **新範例**
- [Overview](routing/overview/main.go)
- [Basic](routing/basic/main.go)
- [Custom HTTP Errors](routing/http-errors/main.go)
- [HTTP Wire Errors](routing/http-wire-errors/main.go) **新範例**
- [Not Found - Intelligence](routing/intelligence/main.go)
- [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
- [Dynamic Path](routing/dynamic-path/main.go)
- [At-username](routing/dynamic-path/at-username/main.go)
- [Root Wildcard](routing/dynamic-path/root-wildcard/main.go)
- [Implement a Parameter Type](routing/macros/main.go)
- [Same Path Pattern but Func](routing/dynamic-path/same-pattern-different-func/main.go)
- Middleware
- [Per Route](routing/writing-a-middleware/per-route/main.go)
- [Globally](routing/writing-a-middleware/globally/main.go)
- [Remove a Handler](routing/remove-handler/main.go)
- Share Values
- [Share Services](routing/writing-a-middleware/share-services/main.go)
- [Share Functions](routing/writing-a-middleware/share-funcs/main.go)
- [Handlers Execution Rule](routing/route-handlers-execution-rules/main.go)
- [Route Register Rule](routing/route-register-rule/main.go)
- Convert net/http Handlers
- [From func(w http.ResponseWriter, r \*http.Request, next http.HandlerFunc)](convert-handlers/negroni-like/main.go)
- [From http.Handler or http.HandlerFunc](convert-handlers/nethttp/main.go)
- [From func(http.Handler) http.Handler](convert-handlers/wrapper/main.go)
- [Convert by your own: sentry/raven middleware](convert-handlers/real-usecase-raven/writing-middleware/main.go)
- [Rewrite Middleware](routing/rewrite/main.go)
- [Route State](routing/route-state/main.go)
- [Remove Route](routing/remove-route/main.go)
- [Reverse Routing](routing/reverse/main.go)
- [Router Wrapper](routing/custom-wrapper/main.go)
- [Custom Router](routing/custom-router/main.go)
- Subdomains
- [Single](routing/subdomains/single/main.go)
- [Multi](routing/subdomains/multi/main.go)
- [Wildcard](routing/subdomains/wildcard/main.go)
- [WWW](routing/subdomains/www/main.go)
- [WWW Method](routing/subdomains/www/www-method/main.go)
- [Redirection](routing/subdomains/redirect/main.go)
- [Multi Instances](routing/subdomains/redirect/multi-instances/main.go)
- [HTTP Errors View](routing/subdomains/http-errors-view/main.go)
- [HTTP Method Override](https://github.com/kataras/iris/blob/main/middleware/methodoverride/methodoverride_test.go)
- [API Versioning](routing/versioning/main.go)
- [Sitemap](routing/sitemap/main.go)
- 日誌
- [Application File Logger](logging/file-logger/main.go)
- [Application JSON Logger](logging/json-logger/main.go)
- [Rollbar](logging/rollbar/main.go)
- AccessLog
- [Log Requests to a JSON File](logging/request-logger/accesslog-simple/main.go)
- [Using Log Rotation and more](logging/request-logger/accesslog)
- [Custom Fields and Template](logging/request-logger/accesslog-template/main.go)
- [Listen and render Logs to a Client](logging/request-logger/accesslog-broker/main.go)
- [The CSV Formatter](logging/request-logger/accesslog-csv/main.go)
- [Create your own Formatter](logging/request-logger/accesslog-formatter/main.go)
- [Root and Proxy AccessLog instances](logging/request-logger/accesslog-proxy/main.go)
- [Slack integration example](logging/request-logger/accesslog-slack/main.go)
- API 文件
- [Swagger](https://github.com/iris-contrib/swagger/tree/master/_examples/basic)
- 測試
- [Testing with httptest](testing/httptest/main_test.go)
- [Testing with ginkgo](testing/ginkgotest)
- [救援錯誤](recover/main.go)
- [Panic and custom Error Handler with Compression](recover/panic-and-custom-error-handler-with-compression/main.go)
- [效能分析 (Profiling)](pprof/main.go)
- 檔案伺服器
- [檔案伺服器](file-server/file-server/main.go)
- [HTTP/2 Push Targets](file-server/http2push/main.go)
- [HTTP/2 Push Targets (Embedded)](file-server/http2push-embedded/main.go)
- [HTTP/2 Push Targets (Gzipped Embedded)](file-server/http2push-embedded-gzipped/main.go)
- [Favicon](file-server/favicon/main.go)
- [Basic](file-server/basic/main.go)
- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)
- [Embedding Files Into App Executable File (Bindata)](file-server/embedding-files-into-app-bindata/main.go)
- [Embedding Gzipped Files Into App Executable File (Bindata)](file-server/embedding-gzipped-files-into-app-bindata/main.go)
- [Send Files (rate limiter included)](file-server/send-files/main.go)
- 單頁面應用程式
- [Vue Router](file-server/spa-vue-router)
- [Basic SPA](file-server/single-page-application/basic/main.go)
- [Embedded Single Page Application and `iris.PrefixDir`](file-server/single-page-application/embedded-single-page-application/main.go)
- [Embedded Single Page Application with other routes](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go)
- [Upload File](file-server/upload-file/main.go)
- [Upload Multiple Files](file-server/upload-files/main.go)
- [WebDAV](file-server/webdav/main.go)
- 檢視
- [概覽](view/overview/main.go)
- [排版引擎](view/layout)
- [Ace](view/layout/ace)
- [Blocks](view/layout/blocks)
- [Django](view/layout/django)
- [Handlebars](view/layout/handlebars)
- [HTML](view/layout/html)
- [Jet](view/layout/jet)
- [Pug](view/layout/pug)
- [基礎](view/template_html_0/main.go)
- [A simple Layout](view/template_html_1/main.go)
- [Layouts: `yield` and `render` tmpl funcs](view/template_html_2/main.go)
- `urlpath` 樣板函式
- [HTML](view/template_html_3/main.go)
- [Django](view/template_django_1/main.go)
- [`url` 樣板函式](view/template_html_4/main.go)
- [Inject Data Between Handlers](view/context-view-data/main.go)
- [Inject Engine Between Handlers](view/context-view-engine/main.go)
- [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
- [Embedding Templates Into App Executable File (Bindata)](view/embedding-templates-into-app-bindata/main.go)
- [Write to a custom `io.Writer`](view/write-to)
- 從文字解析樣板
- [HTML, Pug and Ace](view/parse-parse/main.go)
- [Django](view/parse-parse/django/main.go)
- [Jet](view/parse-parse/jet/main.go)
- [Handlebars](view/parse-parse/handlebars/main.go)
- [Blocks](view/template_blocks_0)
- [Blocks Embedded](view/template_blocks_1_embedded)
- [Pug: `Actions`](view/template_pug_0)
- [Pug: `Includes`](view/template_pug_1)
- [Pug Embedded`](view/template_pug_2_embedded)
- [Ace](view/template_ace_0)
- [Django](view/template_django_0)
- [Jet](view/template_jet_0)
- [Jet Embedded](view/template_jet_1_embedded)
- [Jet 'urlpath' tmpl func](view/template_jet_2)
- [Jet Template Funcs from Struct](view/template_jet_3)
- [Handlebars](view/template_handlebars_0)
- 第三方引擎
- [Render `valyala/quicktemplate` templates](view/quicktemplate)
- [Render `shiyanhui/hero` templates](view/herotemplate)
- [請求 ID](https://github.com/kataras/iris/blob/main/middleware/requestid/requestid_test.go)
- [請求速率限制](request-ratelimit/main.go)
- [請求 Referrer](request-referrer/main.go)
- [Webassembly](webassembly/main.go)
- 請求本文
- [綁定 JSON](request-body/read-json/main.go)
- [JSON Stream and disable unknown fields](request-body/read-json-stream/main.go)
- [Struct Validation](request-body/read-json-struct-validation/main.go)
- [Bind XML](request-body/read-xml/main.go)
- [Bind MsgPack](request-body/read-msgpack/main.go)
- [Bind YAML](request-body/read-yaml/main.go)
- [Bind Form](request-body/read-form/main.go)
- [Checkboxes](request-body/read-form/checkboxes/main.go)
- [Bind Query](request-body/read-query/main.go)
- [Bind Params](request-body/read-params/main.go)
- [Bind URL](request-body/read-url/main.go)
- [Bind Headers](request-body/read-headers/main.go)
- [Bind Body](request-body/read-body/main.go)
- [Add Converter](request-body/form-query-headers-params-decoder/main.go)
- [Bind Custom per type](request-body/read-custom-per-type/main.go)
- [Bind Custom via Unmarshaler](request-body/read-custom-via-unmarshaler/main.go)
- [Bind Many times](request-body/read-many/main.go)
- 請求寫入器
- [Content Negotiation](response-writer/content-negotiation)
- [Text, Markdown, YAML, HTML, JSON, JSONP, Msgpack, XML and Binary](response-writer/write-rest/main.go)
- [Third-party JSON Encoder](response-writer/json-third-party/main.go)
- [Protocol Buffers](response-writer/protobuf/main.go)
- [HTTP/2 Server Push](response-writer/http2push/main.go)
- [Stream Writer](response-writer/stream-writer/main.go)
- [Server-Sent Events](response-writer/sse/main.go)
- [SSE 3rd-party (r3labs/sse)](response-writer/sse-third-party/main.go)
- [SSE 3rd-party (alexandrevicenzi/go-sse)](response-writer/sse-third-party-2/main.go)
- 快取
- [Simple](response-writer/cache/simple/main.go)
- [Client-Side (304)](response-writer/cache/client-side/main.go)
- 壓縮
- [Server-Side](compression/main.go)
- [Client-Side](compression/client/main.go)
- [Client-Side (using Iris)](compress/client-using-iris/main.go)
- 本地化與國際化
- [基礎](i18n/basic)
- [樣板與函式](i18n/template)
- [樣板與函式 (嵌入式)](i18n/template-embedded)
- [複數形與變數](i18n/plurals)
- 認證、授權與機器人偵測
- [推薦:Auth 套件與單點登入](auth/auth) **新範例(需要 GO 1.18 的泛型功能)**
- 基礎認證
- [Basic](auth/basicauth/basic)
- [Load from a slice of Users](auth/basicauth/users_list)
- [Load from a file & encrypted passwords](auth/basicauth/users_file_bcrypt)
- [Fetch & validate a User from a Database (MySQL)](auth/basicauth/database)
- [CORS](auth/cors)
- JSON Web Tokens
- [Basic](auth/jwt/basic/main.go)
- [Middleware](auth/jwt/midleware/main.go)
- [Blocklist](auth/jwt/blocklist/main.go)
- [Refresh Token](auth/jwt/refresh-token/main.go)
- [Tutorial](auth/jwt/tutorial)
- [JWT (community edition)](https://github.com/iris-contrib/middleware/tree/v12/jwt/_example/main.go)
- [OAUth2](auth/goth/main.go)
- [Manage Permissions](auth/permissions/main.go)
- [Google reCAPTCHA](auth/recaptcha/main.go)
- [hCaptcha](auth/hcaptcha/main.go)
- Cookies
- [Basic](cookies/basic/main.go)
- [Options](cookies/options/main.go)
- [Encode/Decode (with `securecookie`)](cookies/securecookie/main.go)
- 連線階段
- [概觀:組態設定](sessions/overview/main.go)
- [概觀:路由](sessions/overview/example/example.go)
- [Basic](sessions/basic/main.go)
- [Secure Cookie](sessions/securecookie/main.go)
- [Flash Messages](sessions/flash-messages/main.go)
- [Databases](sessions/database)
- [Badger](sessions/database/badger/main.go)
- [BoltDB](sessions/database/boltdb/main.go)
- [Redis](sessions/database/redis/main.go)
- [View Data](sessions/viewdata)
- Websocket
- [Gorilla FileWatch (3rd-party)](websocket/gorilla-filewatch/main.go)
- [Basic](websocket/basic)
- [Server](websocket/basic/server.go)
- [Go Client](websocket/basic/go-client/client.go)
- [Browser Client](websocket/basic/browser/index.html)
- [Browser NPM Client (browserify)](websocket/basic/browserify/app.js)
- [Native Messages](websocket/native-messages/main.go)
- [TLS](websocket/secure/README.md)
- [Online Visitors](websocket/online-visitors/main.go)
- 依賴注入
- [概觀 (電影服務)](ependency-injection/overview/main.go)
- [Basic](dependency-injection/basic/main.go)
- [Middleware](dependency-injection/basic/middleware/main.go)
- [Sessions](dependency-injection/sessions/main.go)
- [Smart Contract](dependency-injection/smart-contract/main.go)
- [JWT](dependency-injection/jwt/main.go)
- [JWT (iris-contrib)](dependency-injection/jwt/contrib/main.go)
- [Register Dependency from Context](dependency-injection/context-register-dependency/main.go)
- MVC
- [Overview](mvc/overview)
- [Repository and Service layers](mvc/repository)
- [Hello world](mvc/hello-world/main.go)
- [Basic](mvc/basic/main.go)
- [Wildcard](mvc/basic/wildcard/main.go)
- [Default request values](mvc/request-default-values/main.go)
- [Singleton](mvc/singleton)
- [Regexp](mvc/regexp/main.go)
- [Session Controller](mvc/session-controller/main.go)
- [Authenticated Controller](mvc/authenticated-controller/main.go)
- [Versioned Controller](mvc/versioned-controller/main.go)
- [Websocket Controller](mvc/websocket)
- [Websocket + Authentication (Single-Sign-On)](mvc/websocket-auth) **新範例(需要 GO 1.18 的泛型功能)**
- [Register Middleware](mvc/middleware)
- [gRPC](mvc/grpc-compatible)
- [gRPC Bidirectional Stream](mvc/grpc-compatible-bidirectional-stream)
- [Login (Repository and Service layers)](mvc/login)
- [Login (Single Responsibility)](mvc/login-mvc-single-responsibility)
- [Vue.js Todo App](mvc/vuejs-todo-mvc)
- [HTTP Error Handler](mvc/error-handler-http)
- [Error Handler](mvc/error-handler)
- [Handle errors using mvc.Result](mvc/error-handler-custom-result)
- [Handle errors using PreflightResult](mvc/error-handler-preflight)
- [Handle errors by hijacking the result](mvc/error-handler-hijack)
- 桌面應用程式
- [blink 套件](desktop/blink)
- [lorca 套件](desktop/lorca)
- [webview 套件](desktop/webview)
- 中介模組 [(社群)](https://github.com/iris-contrib/middleware)
+3
View File
@@ -0,0 +1,3 @@
# Swagger 2.0
Visit https://github.com/iris-contrib/swagger instead.
-8
View File
@@ -1,8 +0,0 @@
module github.com/kataras/iris/_examples/apidoc/yaag
go 1.13
require (
github.com/betacraft/yaag v1.0.1-0.20191027021412-565f65e36090
github.com/kataras/iris/v12 v12.1.5
)
-55
View File
@@ -1,55 +0,0 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/betacraft/yaag/irisyaag"
"github.com/betacraft/yaag/yaag"
)
type myXML struct {
Result string `xml:"result"`
}
func main() {
app := iris.New()
yaag.Init(&yaag.Config{ // <- IMPORTANT, init the middleware.
On: true,
DocTitle: "Iris",
DocPath: "apidoc.html",
BaseUrls: map[string]string{"Production": "", "Staging": ""},
})
app.Use(irisyaag.New()) // <- IMPORTANT, register the middleware.
app.Get("/json", func(ctx iris.Context) {
ctx.JSON(iris.Map{"result": "Hello World!"})
})
app.Get("/plain", func(ctx iris.Context) {
ctx.Text("Hello World!")
})
app.Get("/xml", func(ctx iris.Context) {
ctx.XML(myXML{Result: "Hello World!"})
})
app.Get("/complex", func(ctx iris.Context) {
value := ctx.URLParam("key")
ctx.JSON(iris.Map{"value": value})
})
// Run our HTTP Server.
//
// Documentation of "yaag" doesn't note the follow, but in Iris we are careful on what
// we provide to you.
//
// Each incoming request results on re-generation and update of the "apidoc.html" file.
// Recommentation:
// Write tests that calls those handlers, save the generated "apidoc.html".
// Turn off the yaag middleware when in production.
//
// Example usage:
// Visit all paths and open the generated "apidoc.html" file to see the API's automated docs.
app.Run(iris.Addr(":8080"))
}
+12
View File
@@ -0,0 +1,12 @@
# Auth Package (+ Single Sign On)
```sh
$ go run .
```
1. GET/POST: http://localhost:8080/signin
2. GET: http://localhost:8080/member
3. GET: http://localhost:8080/owner
4. POST: http://localhost:8080/refresh
5. GET: http://localhost:8080/signout
6. GET: http://localhost:8080/signout-all
+36
View File
@@ -0,0 +1,36 @@
Headers: # required.
- "Authorization"
- "X-Authorization"
Cookie: # optional.
Name: "iris_auth_cookie"
Secure: false
Hash: "D*G-KaPdSgUkXp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$C&F)J@NcRfUjXn2r4u7x" # length of 64 characters (512-bit).
Block: "VkYp3s6v9y$B&E)H@McQfTjWmZq4t7w!" # length of 32 characters (256-bit).
Keys:
- ID: IRIS_AUTH_ACCESS # required.
Alg: EdDSA
MaxAge: 2h # 2 hours lifetime for access tokens.
Private: |+
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIFdZWoDdFny5SMnP9Fyfr8bafi/B527EVZh8JJjDTIFO
-----END PRIVATE KEY-----
Public: |+
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAzpgjKSr9E032DX+foiOxq1QDsbzjLxagTN+yVpGWZB4=
-----END PUBLIC KEY-----
- ID: IRIS_AUTH_REFRESH # optional. Good practise to have it though.
Alg: EdDSA
# 1 month lifetime for refresh tokens,
# after that period the user has to signin again.
MaxAge: 720h
Private: |+
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIHJ1aoIjA2sRp5eqGjGR3/UMucrHbBdBv9p8uwfzZ1KZ
-----END PRIVATE KEY-----
Public: |+
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAsKKAr+kDtfAqwG7cZdoEAfh9jHt9W8qi9ur5AA1KQAQ=
-----END PUBLIC KEY-----
# Example of setting a binary form of the encryption key for refresh tokens,
# it could be a "string" as well.
EncryptionKey: !!binary stSNLTu91YyihPxzeEOXKwGVMG00CjcC/68G8nMgmqA=
+139
View File
@@ -0,0 +1,139 @@
//go:build go1.18
// +build go1.18
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/auth"
)
func allowRole(role AccessRole) auth.VerifyUserFunc[User] {
return func(u User) error {
if !u.Role.Allow(role) {
return fmt.Errorf("invalid role")
}
return nil
}
}
const configFilename = "./auth.yml"
func main() {
app := iris.New()
app.RegisterView(iris.Blocks(iris.Dir("./views"), ".html").
LayoutDir("layouts").
Layout("main"))
/*
// Easiest 1-liner way, load from configuration and initialize a new auth instance:
s := auth.MustLoad[User]("./auth.yml")
// Bind a configuration from file:
var c auth.Configuration
c.BindFile("./auth.yml")
s, err := auth.New[User](c)
// OR create new programmatically configuration:
config := auth.Configuration{
...fields
}
s, err := auth.New[User](config)
// OR generate a new configuration:
config := auth.MustGenerateConfiguration()
s, err := auth.New[User](config)
// OR generate a new config and save it if cannot open the config file.
if _, err := os.Stat(configFilename); err != nil {
generatedConfig := auth.MustGenerateConfiguration()
configContents, err := generatedConfig.ToYAML()
if err != nil {
panic(err)
}
err = os.WriteFile(configFilename, configContents, 0600)
if err != nil {
panic(err)
}
}
*/
// 1. Load configuration from a file.
authConfig, err := auth.LoadConfiguration(configFilename)
if err != nil {
panic(err)
}
// 2. Initialize a new auth instance for "User" claims (generics: go1.18 +).
s, err := auth.New[User](authConfig)
if err != nil {
panic(err)
}
// 3. Add a custom provider, in our case is just a memory-based one.
s.AddProvider(NewProvider())
// 3.1. Optionally set a custom error handler.
// s.SetErrorHandler(new(auth.DefaultErrorHandler))
app.Get("/signin", renderSigninForm)
// 4. generate token pairs.
app.Post("/signin", s.SigninHandler)
// 5. refresh token pairs.
app.Post("/refresh", s.RefreshHandler)
// 6. calls the provider's InvalidateToken method.
app.Get("/signout", s.SignoutHandler)
// 7. calls the provider's InvalidateTokens method.
app.Get("/signout-all", s.SignoutAllHandler)
// 8.1. allow access for users with "Member" role.
app.Get("/member", s.VerifyHandler(allowRole(Member)), renderMemberPage(s))
// 8.2. allow access for users with "Owner" role.
app.Get("/owner", s.VerifyHandler(allowRole(Owner)), renderOwnerPage(s))
/* Subdomain user verify:
app.Subdomain("owner", s.VerifyHandler(allowRole(Owner))).Get("/", renderOwnerPage(s))
*/
app.Listen(":8080", iris.WithOptimizations) // Setup HTTPS/TLS for production instead.
/* Test subdomain user verify, one way is ingrok,
add the below to the arguments above:
, iris.WithConfiguration(iris.Configuration{
EnableOptmizations: true,
Tunneling: iris.TunnelingConfiguration{
AuthToken: "YOUR_AUTH_TOKEN",
Region: "us",
Tunnels: []tunnel.Tunnel{
{
Name: "Iris Auth (Test)",
Addr: ":8080",
Hostname: "YOUR_DOMAIN",
},
{
Name: "Iris Auth (Test Subdomain)",
Addr: ":8080",
Hostname: "owner.YOUR_DOMAIN",
},
},
},
})*/
}
func renderSigninForm(ctx iris.Context) {
if err := ctx.View("signin", iris.Map{"Title": "Signin Page"}); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
}
func renderMemberPage(s *auth.Auth[User]) iris.Handler {
return func(ctx iris.Context) {
user := s.GetUser(ctx)
ctx.Writef("Hello member: %s\n", user.Email)
}
}
func renderOwnerPage(s *auth.Auth[User]) iris.Handler {
return func(ctx iris.Context) {
user := s.GetUser(ctx)
ctx.Writef("Hello owner: %s\n", user.Email)
}
}
+34
View File
@@ -0,0 +1,34 @@
//go:build go1.18
// +build go1.18
package main
type AccessRole uint16
func (r AccessRole) Is(v AccessRole) bool {
return r&v != 0
}
func (r AccessRole) Allow(v AccessRole) bool {
return r&v >= v
}
const (
InvalidAccessRole AccessRole = 1 << iota
Read
Write
Delete
Owner = Read | Write | Delete
Member = Read | Write
)
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Role AccessRole `json:"role"`
}
func (u User) GetID() string {
return u.ID
}
+101
View File
@@ -0,0 +1,101 @@
//go:build go1.18
// +build go1.18
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/kataras/iris/v12/auth"
)
type Provider struct {
dataset []User
invalidated map[string]struct{} // key = token. Entry is blocked.
invalidatedAll map[string]int64 // key = user id, value = timestamp. Issued before is consider invalid.
mu sync.RWMutex
}
func NewProvider() *Provider {
return &Provider{
dataset: []User{
{
ID: "id-1",
Email: "kataras2006@hotmail.com",
Role: Owner,
},
{
ID: "id-2",
Email: "example@example.com",
Role: Member,
},
},
invalidated: make(map[string]struct{}),
invalidatedAll: make(map[string]int64),
}
}
func (p *Provider) Signin(ctx context.Context, username, password string) (User, error) { // fired on SigninHandler.
// your database...
for _, user := range p.dataset {
if user.Email == username {
return user, nil
}
}
return User{}, fmt.Errorf("user not found")
}
func (p *Provider) ValidateToken(ctx context.Context, standardClaims auth.StandardClaims, u User) error { // fired on VerifyHandler.
// your database and checks of blocked tokens...
// check for specific token ids.
p.mu.RLock()
_, tokenBlocked := p.invalidated[standardClaims.ID]
if !tokenBlocked {
// this will disallow refresh tokens with origin jwt token id as the blocked access token as well.
if standardClaims.OriginID != "" {
_, tokenBlocked = p.invalidated[standardClaims.OriginID]
}
}
p.mu.RUnlock()
if tokenBlocked {
return fmt.Errorf("token was invalidated")
}
//
// check all tokens issuet before the "InvalidateToken" method was fired for this user.
p.mu.RLock()
ts, oldUserBlocked := p.invalidatedAll[u.ID]
p.mu.RUnlock()
if oldUserBlocked && standardClaims.IssuedAt <= ts {
return fmt.Errorf("token was invalidated")
}
//
return nil // else valid.
}
func (p *Provider) InvalidateToken(ctx context.Context, standardClaims auth.StandardClaims, u User) error { // fired on SignoutHandler.
// invalidate this specific token.
p.mu.Lock()
p.invalidated[standardClaims.ID] = struct{}{}
p.mu.Unlock()
return nil
}
func (p *Provider) InvalidateTokens(ctx context.Context, u User) error { // fired on SignoutAllHandler.
// invalidate all previous tokens came from "u".
p.mu.Lock()
p.invalidatedAll[u.ID] = time.Now().Unix()
p.mu.Unlock()
return nil
}
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ if .Title }}{{ .Title }}{{ else }}Default Main Title{{ end }}</title>
</head>
<style>
body {
margin: 0;
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
display: block;
flex: 1 0 auto;
}
.container {
max-width: 500px;
margin: auto;
}
</style>
<body>
<div class="container">
<main>{{ template "content" . }}</main>
<footer style="position: fixed; bottom: 0; width: 100%;">{{ partial "partials/footer" . }}</footer>
</div>
</body>
</html>
@@ -0,0 +1 @@
<i>Iris Web Framework &copy; 2022</i>
+9
View File
@@ -0,0 +1,9 @@
<div class="user_signin">
<form action="" method="post">
<label for="username">Email:</label>
<input name="username" type="email" />
<label for="password">Password:</label>
<input name="password" type="password" />
<input type="submit" value="Sign in" />
</form>
</div>
+99
View File
@@ -0,0 +1,99 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/x/errors"
"github.com/kataras/iris/v12/middleware/basicauth"
)
func newApp() *iris.Application {
app := iris.New()
/*
opts := basicauth.Options{
Realm: "Authorization Required",
MaxAge: 30 * time.Minute,
GC: basicauth.GC{
Every: 2 * time.Hour,
},
Allow: basicauth.AllowUsers(map[string]string{
"myusername": "mypassword",
"mySecondusername": "mySecondpassword",
}),
MaxTries: 2,
}
auth := basicauth.New(opts)
OR simply:
*/
auth := basicauth.Default(map[string]string{
"myusername": "mypassword",
"mySecondusername": "mySecondpassword",
})
// To the next routes of a party (group of routes):
/*
app.Use(auth)
*/
// For global effect, including not founds:
/*
app.UseRouter(auth)
*/
// For global effect, excluding http errors such as not founds:
/*
app.UseGlobal(auth) or app.Use(auth) before any route registered.
*/
// For single/per routes:
/*
app.Get("/mysecret", auth, h)
*/
app.Get("/", func(ctx iris.Context) { ctx.Redirect("/admin") })
// to party
needAuth := app.Party("/admin", auth)
{
//http://localhost:8080/admin
needAuth.Get("/", handler)
// http://localhost:8080/admin/profile
needAuth.Get("/profile", handler)
// http://localhost:8080/admin/settings
needAuth.Get("/settings", handler)
needAuth.Get("/logout", logout)
}
return app
}
func main() {
app := newApp()
// open http://localhost:8080/admin
app.Listen(":8080")
}
func handler(ctx iris.Context) {
// user := ctx.User().(*myUserType)
// or ctx.User().GetRaw().(*myUserType)
// ctx.Writef("%s %s:%s", ctx.Path(), user.Username, user.Password)
// OR if you don't have registered custom User structs:
username, password, _ := ctx.Request().BasicAuth()
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
}
func logout(ctx iris.Context) {
// fires 401, invalidates the basic auth,
// logout through javascript and ajax is a better solution though.
err := ctx.Logout()
if err != nil {
errors.Internal.Err(ctx, err)
return
}
}
@@ -17,11 +17,11 @@ func TestBasicAuth(t *testing.T) {
// with valid basic auth
e.GET("/admin").WithBasicAuth("myusername", "mypassword").Expect().
Status(httptest.StatusOK).Body().Equal("/admin myusername:mypassword")
Status(httptest.StatusOK).Body().IsEqual("/admin myusername:mypassword")
e.GET("/admin/profile").WithBasicAuth("myusername", "mypassword").Expect().
Status(httptest.StatusOK).Body().Equal("/admin/profile myusername:mypassword")
Status(httptest.StatusOK).Body().IsEqual("/admin/profile myusername:mypassword")
e.GET("/admin/settings").WithBasicAuth("myusername", "mypassword").Expect().
Status(httptest.StatusOK).Body().Equal("/admin/settings myusername:mypassword")
Status(httptest.StatusOK).Body().IsEqual("/admin/settings myusername:mypassword")
// with invalid basic auth
e.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword").
@@ -0,0 +1,18 @@
# docker build -t myapp .
# docker run --rm -it -p 8080:8080 myapp:latest
FROM golang:latest AS builder
RUN apt-get update
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /go/src/app
COPY go.mod .
RUN go mod download
# cache step
COPY . .
RUN go install
FROM scratch
COPY --from=builder /go/bin/myapp .
ENTRYPOINT ["./myapp"]
@@ -0,0 +1,44 @@
# BasicAuth + MySQL & Docker Example
## ⚡ Get Started
Download the folder.
### Install (Docker)
Install [Docker](https://www.docker.com/) and execute the command below
```sh
$ docker-compose up --build
```
### Install (Manually)
Run `go build -mod=mod` or `go run -mod=mod main.go` and read below.
#### MySQL
Environment variables:
```sh
MYSQL_USER=user_myapp
MYSQL_PASSWORD=dbpassword
MYSQL_HOST=localhost
MYSQL_DATABASE=myapp
```
Download the schema from [migration/db.sql](migration/db.sql) and execute it against your MySQL server instance.
<http://localhost:8080>
```sh
username: admin
password: admin
```
```sh
username: iris
password: iris_password
```
The example does not contain code to add a user to the database, as this is out of the scope of this middleware. More features can be implemented by end-developers.
@@ -0,0 +1,32 @@
version: '3.1'
services:
db:
image: mysql
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: dbpassword
MYSQL_DATABASE: myapp
MYSQL_USER: user_myapp
MYSQL_PASSWORD: dbpassword
tty: true
volumes:
- ./migration:/docker-entrypoint-initdb.d
app:
build: .
ports:
- 8080:8080
environment:
PORT: 8080
MYSQL_USER: user_myapp
MYSQL_PASSWORD: dbpassword
MYSQL_DATABASE: myapp
MYSQL_HOST: db
restart: on-failure
healthcheck:
test: ["CMD", "curl", "-f", "tcp://db:3306"]
interval: 30s
timeout: 10s
retries: 10
depends_on:
- db
+55
View File
@@ -0,0 +1,55 @@
module myapp
go 1.22
require (
github.com/go-sql-driver/mysql v1.8.1
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/blocks v0.0.8 // indirect
github.com/kataras/golog v0.1.11 // indirect
github.com/kataras/pio v0.0.13 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tdewolff/minify/v2 v2.20.19 // indirect
github.com/tdewolff/parse/v2 v2.7.12 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yosssi/ace v0.0.5 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+182
View File
@@ -0,0 +1,182 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU=
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go=
github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE=
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20=
github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A=
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad h1:oWfB7/JUb6RU6wwCMMNh1e7p307bJjKWgX6R4oazL1A=
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w=
github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
+114
View File
@@ -0,0 +1,114 @@
package main // Look README.md
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
_ "github.com/go-sql-driver/mysql" // lint: mysql driver.
)
// User is just an example structure of a user,
// it MUST contain a Username and Password exported fields
// or/and complete the basicauth.User interface.
type User struct {
ID int64 `db:"id" json:"id"`
Username string `db:"username" json:"username"`
Password string `db:"password" json:"password"`
Email string `db:"email" json:"email"`
}
// GetUsername returns the Username field.
func (u User) GetUsername() string {
return u.Username
}
// GetPassword returns the Password field.
func (u User) GetPassword() string {
return u.Password
}
func main() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
getenv("MYSQL_USER", "user_myapp"),
getenv("MYSQL_PASSWORD", "dbpassword"),
getenv("MYSQL_HOST", "localhost"),
getenv("MYSQL_DATABASE", "myapp"),
)
db, err := connect(dsn)
if err != nil {
panic(err)
}
// Validate a user from database.
allowFunc := func(ctx iris.Context, username, password string) (interface{}, bool) {
user, err := db.getUserByUsernameAndPassword(context.Background(), username, password)
return user, err == nil
}
opts := basicauth.Options{
Realm: basicauth.DefaultRealm,
ErrorHandler: basicauth.DefaultErrorHandler,
Allow: allowFunc,
}
auth := basicauth.New(opts)
app := iris.New()
app.Use(auth)
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
user, _ := ctx.User().GetRaw()
// user is a type of main.User
ctx.JSON(user)
}
func getenv(key string, def string) string {
v := os.Getenv(key)
if v == "" {
return def
}
return v
}
type database struct {
*sql.DB
}
func connect(dsn string) (*database, error) {
conn, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
err = conn.Ping()
if err != nil {
conn.Close()
return nil, err
}
return &database{conn}, nil
}
func (db *database) getUserByUsernameAndPassword(ctx context.Context, username, password string) (User, error) {
query := fmt.Sprintf("SELECT * FROM %s WHERE %s = ? AND %s = ? LIMIT 1", "users", "username", "password")
rows, err := db.QueryContext(ctx, query, username, password)
if err != nil {
return User{}, err
}
defer rows.Close()
if !rows.Next() {
return User{}, sql.ErrNoRows
}
var user User
err = rows.Scan(&user.ID, &user.Username, &user.Password, &user.Email)
return user, err
}
@@ -0,0 +1,22 @@
CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE myapp;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
email varchar(255) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO users (username,password,email)
VALUES
('admin', 'admin', 'kataras2006@hotmail.com'),
("iris", 'iris_password', 'iris-go@outlook.com');
SET FOREIGN_KEY_CHECKS = 1;
@@ -0,0 +1,30 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
)
func main() {
auth := basicauth.Load("users.yml", basicauth.BCRYPT)
/* Same as:
opts := basicauth.Options{
Realm: basicauth.DefaultRealm,
Allow: basicauth.AllowUsersFile("users.yml", basicauth.BCRYPT),
}
auth := basicauth.New(opts)
*/
app := iris.New()
app.Use(auth)
app.Get("/", index)
// kataras:kataras_pass
// makis:makis_pass
app.Listen(":8080")
}
func index(ctx iris.Context) {
user := ctx.User()
ctx.JSON(user)
}
@@ -0,0 +1,12 @@
# The file cannot be modified during the serve time.
# To support real-time users changes please use the Options.Allow instead,
# (see the database example for that).
#
# Again, the username and password (or capitalized) fields are required,
# the rest are optional, depending on your application needs.
- username: kataras
password: $2a$10$Irg8k8HWkDlvL0YDBKLCYee6j6zzIFTplJcvZYKA.B8/clHPZn2Ey # encrypted of kataras_pass
age: 27
role: admin
- username: makis
password: $2a$10$3GXzp3J5GhHThGisbpvpZuftbmzPivDMo94XPnkTnDe7254x7sJ3O # encrypted of makis_pass
@@ -0,0 +1,58 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
)
// User is just an example structure of a user,
// it MUST contain a Username and Password exported fields
// or complete the basicauth.User interface.
type User struct {
Username string `json:"username"`
Password string `json:"password"`
Roles []string `json:"roles"`
}
var users = []User{
{"admin", "admin", []string{"admin"}},
{"kataras", "kataras_pass", []string{"manager", "author"}},
{"george", "george_pass", []string{"member"}},
{"john", "john_pass", []string{}},
}
func main() {
opts := basicauth.Options{
Realm: basicauth.DefaultRealm,
// Defaults to 0, no expiration.
// Prompt for new credentials on a client's request
// made after 10 minutes the user has logged in:
MaxAge: 10 * time.Minute,
// Clear any expired users from the memory every one hour,
// note that the user's expiration time will be
// reseted on the next valid request (when Allow passed).
GC: basicauth.GC{
Every: 2 * time.Hour,
},
// The users can be a slice of custom users structure
// or a map[string]string (username:password)
// or []map[string]interface{} with username and passwords required fields,
// read the godocs for more.
Allow: basicauth.AllowUsers(users),
}
auth := basicauth.New(opts)
// OR: basicauth.Default(users)
app := iris.New()
app.Use(auth)
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
user, _ := ctx.User().GetRaw()
ctx.JSON(user)
}
+63
View File
@@ -0,0 +1,63 @@
// Package main integrates the "rs/cors" net/http middleware into Iris.
// That cors third-party middleware cannot be registered through `iris.FromStd`
// as a common middleware because it should be injected before the Iris Router itself,
// it allows/dissallows HTTP Methods too.
//
// This is just an example you can use to run something, based on custom logic,
// before the Iris Router itself.
//
// In the "routing/custom-wrapper" example
// we learn how we can acquire and release an Iris context to fire an Iris Handler
// based on custom logic, before the Iris Router itself. In that example
// we will fire a net/http handler (the "rs/cors" handler one) instead.
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
package main
import (
"github.com/kataras/iris/v12"
"github.com/rs/cors"
)
func main() {
app := iris.New()
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowCredentials: true,
// Enable Debugging for testing, consider disabling in production
Debug: true,
})
// app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
// [custom logic...]
// if shouldFireNetHTTPHandler {
// ...ServeHTTP(w,r)
// return
// }
// router(w,r)
// })
// In our case, the cors package has a ServeHTTP
// of the same form of app.WrapRouter's accept input argument,
// so we can just do:
app.WrapRouter(c.ServeHTTP)
// Serve ./public/index.html, main.js.
app.HandleDir("/", iris.Dir("./public"))
// Register routes here...
app.Get("/data", listData)
// http://localhost:8080 and click the "fetch data" button.
app.Listen(":8080")
}
type item struct {
Title string `json:"title"`
}
func listData(ctx iris.Context) {
ctx.JSON([]item{
{"Item 1"},
{"Item 2"},
{"Item 3"},
})
}
+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Iris Cors Example</title>
</head>
<body>
<ul id="list">
</ul>
<input type="button" value="Fetch Data" id="fetchBtn" />
<script src="main.js"></script>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
async function doRequest(method = 'GET', url = '', data = {}) {
// Default options are marked with *
const request = {
method: method, // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
};
if (data !== undefined && method !== 'GET' && method !== 'HEAD') {
request.headers = {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
};
// body data type must match "Content-Type" header.
request.body = JSON.stringify(data);
}
const response = await fetch(url, request);
return response.json(); // parses JSON response into native JavaScript objects.
}
const ul = document.getElementById("list");
function fetchData() {
console.log("sending request...")
doRequest('GET', '/data').then(data => {
data.forEach(item => {
var li = document.createElement("li");
li.appendChild(document.createTextNode(item.title));
ul.appendChild(li);
});
console.log(data); // JSON data parsed by `response.json()` call.
});
}
document.getElementById("fetchBtn").onclick = fetchData;
@@ -74,16 +74,14 @@ var sessionsManager *sessions.Sessions
func init() {
// attach a session manager
cookieName := "mycustomsessionid"
// AES only supports key sizes of 16, 24 or 32 bytes.
// You either need to provide exactly that amount or you derive the key from what you type in.
hashKey := []byte("the-big-and-secret-fash-key-here")
blockKey := []byte("lot-secret-of-characters-big-too")
hashKey := securecookie.GenerateRandomKey(64)
blockKey := securecookie.GenerateRandomKey(32)
secureCookie := securecookie.New(hashKey, blockKey)
sessionsManager = sessions.New(sessions.Config{
Cookie: cookieName,
Encode: secureCookie.Encode,
Decode: secureCookie.Decode,
Cookie: cookieName,
Encoding: secureCookie,
AllowReclaim: true,
})
}
@@ -126,8 +124,7 @@ See https://github.com/markbates/goth/examples/main.go to see this in action.
func BeginAuthHandler(ctx iris.Context) {
url, err := GetAuthURL(ctx)
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.Writef("%v", err)
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
@@ -193,7 +190,7 @@ CompleteUserAuth does what it says on the tin. It completes the authentication
process and fetches all of the basic information about the user from the provider.
It expects to be able to get the name of the provider from the query parameters
as either "provider" or ":provider".
as either "provider" or "{provider}" path parameter.
See https://github.com/markbates/goth/examples/main.go to see this in action.
*/
@@ -304,45 +301,46 @@ func main() {
goth.UseProviders(openidConnect)
}
m := make(map[string]string)
m["amazon"] = "Amazon"
m["bitbucket"] = "Bitbucket"
m["box"] = "Box"
m["dailymotion"] = "Dailymotion"
m["deezer"] = "Deezer"
m["digitalocean"] = "Digital Ocean"
m["discord"] = "Discord"
m["dropbox"] = "Dropbox"
m["facebook"] = "Facebook"
m["fitbit"] = "Fitbit"
m["github"] = "Github"
m["gitlab"] = "Gitlab"
m["soundcloud"] = "SoundCloud"
m["spotify"] = "Spotify"
m["steam"] = "Steam"
m["stripe"] = "Stripe"
m["twitch"] = "Twitch"
m["uber"] = "Uber"
m["wepay"] = "Wepay"
m["yahoo"] = "Yahoo"
m["yammer"] = "Yammer"
m["gplus"] = "Google Plus"
m["heroku"] = "Heroku"
m["instagram"] = "Instagram"
m["intercom"] = "Intercom"
m["lastfm"] = "Last FM"
m["linkedin"] = "Linkedin"
m["onedrive"] = "Onedrive"
m["paypal"] = "Paypal"
m["twitter"] = "Twitter"
m["salesforce"] = "Salesforce"
m["slack"] = "Slack"
m["meetup"] = "Meetup.com"
m["auth0"] = "Auth0"
m["openid-connect"] = "OpenID Connect"
m["xero"] = "Xero"
m := map[string]string{
"amazon": "Amazon",
"bitbucket": "Bitbucket",
"box": "Box",
"dailymotion": "Dailymotion",
"deezer": "Deezer",
"digitalocean": "Digital Ocean",
"discord": "Discord",
"dropbox": "Dropbox",
"facebook": "Facebook",
"fitbit": "Fitbit",
"github": "Github",
"gitlab": "Gitlab",
"soundcloud": "SoundCloud",
"spotify": "Spotify",
"steam": "Steam",
"stripe": "Stripe",
"twitch": "Twitch",
"uber": "Uber",
"wepay": "Wepay",
"yahoo": "Yahoo",
"yammer": "Yammer",
"gplus": "Google Plus",
"heroku": "Heroku",
"instagram": "Instagram",
"intercom": "Intercom",
"lastfm": "Last FM",
"linkedin": "Linkedin",
"onedrive": "Onedrive",
"paypal": "Paypal",
"twitter": "Twitter",
"salesforce": "Salesforce",
"slack": "Slack",
"meetup": "Meetup.com",
"auth0": "Auth0",
"openid-connect": "OpenID Connect",
"xero": "Xero",
}
var keys []string
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
@@ -355,6 +353,7 @@ func main() {
// set sessions
// and setup the router for the showcase
app := iris.New()
app.Logger().SetLevel("debug")
// attach and build our templates
app.RegisterView(iris.HTML("./templates", ".html"))
@@ -364,13 +363,25 @@ func main() {
app.Get("/auth/{provider}/callback", func(ctx iris.Context) {
user, err := CompleteUserAuth(ctx)
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Writef("%v", err)
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.ViewData("", user)
if err := ctx.View("user.html"); err != nil {
ctx.Writef("%v", err)
// Between handlers (user as root .):
// ctx.ViewData("", user)
//
// Between handlers (user as .user variable):
// ctx.ViewData("user", user)
// ----
// Directly (user as root):
// ctx.View("user.html", user)
//
// Directly (user as .user variable):
if err := ctx.View("user.html", iris.Map{
"user": user,
}); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
@@ -381,26 +392,27 @@ func main() {
app.Get("/auth/{provider}", func(ctx iris.Context) {
// try to get the user without re-authenticating
if gothUser, err := CompleteUserAuth(ctx); err == nil {
ctx.ViewData("", gothUser)
if err := ctx.View("user.html"); err != nil {
ctx.Writef("%v", err)
}
} else {
gothUser, err := CompleteUserAuth(ctx)
if err != nil {
BeginAuthHandler(ctx)
return
}
if err := ctx.View("user.html", gothUser); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
app.Get("/", func(ctx iris.Context) {
ctx.ViewData("", providerIndex)
if err := ctx.View("index.html"); err != nil {
ctx.Writef("%v", err)
if err := ctx.View("index.html", providerIndex); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
// http://localhost:3000
app.Run(iris.Addr("localhost:3000"))
app.Listen("localhost:3000")
}
type ProviderIndex struct {
@@ -1,3 +1,3 @@
{{range $key,$value:=.Providers}}
<p><a href="/auth/{{$value}}">Log in with {{index $.ProvidersMap $value}}</a></p>
{{range $key,$value:=.Providers}}
<p><a href="/auth/{{$value}}">Log in with {{index $.ProvidersMap $value}}</a></p>
{{end}}
@@ -1,11 +1,19 @@
<p><a href="/logout/{{.Provider}}">logout</a></p>
<p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p>
<p>Email: {{.Email}}</p>
<p>NickName: {{.NickName}}</p>
<p>Location: {{.Location}}</p>
<p>AvatarURL: {{.AvatarURL}} <img src="{{.AvatarURL}}"></p>
<p>Description: {{.Description}}</p>
<p>UserID: {{.UserID}}</p>
<p>AccessToken: {{.AccessToken}}</p>
<p>ExpiresAt: {{.ExpiresAt}}</p>
<p>RefreshToken: {{.RefreshToken}}</p>
<p><a href="/logout/{{.Provider}}">logout</a></p>
<p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p>
<p>Email: {{.Email}}</p>
<p>NickName: {{.NickName}}</p>
<p>Location: {{.Location}}</p>
<p>AvatarURL: {{.AvatarURL}} <img src="{{.AvatarURL}}"></p>
<p>Description: {{.Description}}</p>
<p>UserID: {{.UserID}}</p>
<p>AccessToken: {{.AccessToken}}</p>
<p>ExpiresAt: {{.ExpiresAt}}</p>
<p>RefreshToken: {{.RefreshToken}}</p>
<hr/>
<h3>Iterate all properties</h3>
{{range $key, $value := .RawData}}
{{ $key }} => {{ $value }} <br/>
{{end}}
+3
View File
@@ -0,0 +1,3 @@
# https://docs.hcaptcha.com/#localdev
# Add to the end of your hosts file, e.g. on windows: C:/windows/system32/drivers/etc/hosts
127.0.0.1 yourdomain.com
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"os"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/hcaptcha"
)
// Get the following values from: https://dashboard.hcaptcha.com
// Also, check: https://docs.hcaptcha.com/#localdev to test on local environment.
var (
siteKey = os.Getenv("HCAPTCHA-SITE-KEY")
secretKey = os.Getenv("HCAPTCHA-SECRET-KEY")
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./templates", ".html"))
hCaptcha := hcaptcha.New(secretKey)
app.Get("/register", registerForm)
app.Post("/register", hCaptcha, register) // See `hcaptcha.SiteVerify` for manual validation too.
app.Logger().Infof("SiteKey = %s\tSecretKey = %s",
siteKey, secretKey)
// GET: http://yourdomain.com/register
app.Listen(":80")
}
func register(ctx iris.Context) {
hcaptchaResp, ok := hcaptcha.Get(ctx)
if !ok {
ctx.StatusCode(iris.StatusUnauthorized)
ctx.WriteString("Are you a bot?")
return
}
ctx.Writef("Register action here...action was asked by a Human.\nResponse value is: %#+v", hcaptchaResp)
}
func registerForm(ctx iris.Context) {
ctx.ViewData("SiteKey", siteKey)
if err := ctx.View("register_form.html"); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
}
@@ -0,0 +1,18 @@
<html>
<head>
<title>hCaptcha Demo</title>
<script src="https://hcaptcha.com/1/api.js" async defer></script>
</head>
<body>
<form action="/register" method="POST">
<input type="text" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<div class="h-captcha" data-sitekey="{{ .SiteKey }}"></div>
<br />
<input type="submit" value="Submit" />
</form>
</body>
</html>
+73
View File
@@ -0,0 +1,73 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
/*
Documentation:
https://github.com/kataras/jwt#table-of-contents
*/
// Replace with your own key and keep them secret.
// The "signatureSharedKey" is used for the HMAC(HS256) signature algorithm.
var signatureSharedKey = []byte("sercrethatmaycontainch@r32length")
func main() {
app := iris.New()
app.Get("/", generateToken)
app.Get("/protected", protected)
app.Listen(":8080")
}
type fooClaims struct {
Foo string `json:"foo"`
}
func generateToken(ctx iris.Context) {
claims := fooClaims{
Foo: "bar",
}
// Sign and generate compact form token.
token, err := jwt.Sign(jwt.HS256, signatureSharedKey, claims, jwt.MaxAge(10*time.Minute))
if err != nil {
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
tokenString := string(token) // or jwt.BytesToString
ctx.HTML(`Token: ` + tokenString + `<br/><br/>
<a href="/protected?token=` + tokenString + `">/protected?token=` + tokenString + `</a>`)
}
func protected(ctx iris.Context) {
// Extract the token, e.g. cookie, Authorization: Bearer $token
// or URL query.
token := ctx.URLParam("token")
// Verify the token.
verifiedToken, err := jwt.Verify(jwt.HS256, signatureSharedKey, []byte(token))
if err != nil {
ctx.StopWithStatus(iris.StatusUnauthorized)
return
}
ctx.Writef("This is an authenticated request.\n\n")
// Decode the custom claims.
var claims fooClaims
verifiedToken.Claims(&claims)
// Just an example on how you can retrieve all the standard claims (set by jwt.MaxAge, "exp").
standardClaims := jwt.GetVerifiedToken(ctx).StandardClaims
expiresAtString := standardClaims.ExpiresAt().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
timeLeft := standardClaims.Timeleft()
ctx.Writef("foo=%s\nexpires at: %s\ntime left: %s\n", claims.Foo, expiresAtString, timeLeft)
}
+101
View File
@@ -0,0 +1,101 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
"github.com/kataras/iris/v12/middleware/jwt/blocklist/redis"
// Optionally to set token identifier.
"github.com/google/uuid"
)
var (
signatureSharedKey = []byte("sercrethatmaycontainch@r32length")
signer = jwt.NewSigner(jwt.HS256, signatureSharedKey, 15*time.Minute)
verifier = jwt.NewVerifier(jwt.HS256, signatureSharedKey)
)
type userClaims struct {
Username string `json:"username"`
}
func main() {
app := iris.New()
// IMPORTANT
//
// To use the in-memory blocklist just:
// verifier.WithDefaultBlocklist()
// To use a persistence blocklist, e.g. redis,
// start your redis-server and:
blocklist := redis.NewBlocklist()
// To configure single client or a cluster one:
// blocklist.ClientOptions.Addr = "127.0.0.1:6379"
// blocklist.ClusterOptions.Addrs = []string{...}
// To set a prefix for jwt ids:
// blocklist.Prefix = "myapp-"
//
// To manually connect and check its error before continue:
// err := blocklist.Connect()
// By default the verifier will try to connect, if failed then it will throw http error.
//
// And then register it:
verifier.Blocklist = blocklist
verifyMiddleware := verifier.Verify(func() interface{} {
return new(userClaims)
})
app.Get("/", authenticate)
protectedAPI := app.Party("/protected", verifyMiddleware)
protectedAPI.Get("/", protected)
protectedAPI.Get("/logout", logout)
// http://localhost:8080
// http://localhost:8080/protected?token=$token
// http://localhost:8080/logout?token=$token
// http://localhost:8080/protected?token=$token (401)
app.Listen(":8080")
}
func authenticate(ctx iris.Context) {
claims := userClaims{
Username: "kataras",
}
// Generate JWT ID.
random, err := uuid.NewRandom()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
id := random.String()
// Set the ID with the jwt.ID.
token, err := signer.Sign(claims, jwt.ID(id))
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.Write(token)
}
func protected(ctx iris.Context) {
claims := jwt.Get(ctx).(*userClaims)
// To the standard claims, e.g. the generated ID:
// jwt.GetVerifiedToken(ctx).StandardClaims.ID
ctx.WriteString(claims.Username)
}
func logout(ctx iris.Context) {
ctx.Logout()
ctx.Redirect("/", iris.StatusTemporaryRedirect)
}
+91
View File
@@ -0,0 +1,91 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
var (
sigKey = []byte("signature_hmac_secret_shared_key")
// encKey = []byte("GCM_AES_256_secret_shared_key_32")
)
type fooClaims struct {
Foo string `json:"foo"`
}
/*
In this example you will learn the essentials
of the Iris builtin JWT middleware based on the github.com/kataras/jwt package.
*/
func main() {
app := iris.New()
signer := jwt.NewSigner(jwt.HS256, sigKey, 10*time.Minute)
// Enable payload encryption with:
// signer.WithEncryption(encKey, nil)
app.Get("/", generateToken(signer))
verifier := jwt.NewVerifier(jwt.HS256, sigKey)
// Enable server-side token block feature (even before its expiration time):
verifier.WithDefaultBlocklist()
// Enable payload decryption with:
// verifier.WithDecryption(encKey, nil)
verifyMiddleware := verifier.Verify(func() interface{} {
return new(fooClaims)
})
protectedAPI := app.Party("/protected")
// Register the verify middleware to allow access only to authorized clients.
protectedAPI.Use(verifyMiddleware)
// ^ or UseRouter(verifyMiddleware) to disallow unauthorized http error handlers too.
protectedAPI.Get("/", protected)
// Invalidate the token through server-side, even if it's not expired yet.
protectedAPI.Get("/logout", logout)
// http://localhost:8080
// http://localhost:8080/protected?token=$token (or Authorization: Bearer $token)
// http://localhost:8080/protected/logout?token=$token
// http://localhost:8080/protected?token=$token (401)
app.Listen(":8080")
}
func generateToken(signer *jwt.Signer) iris.Handler {
return func(ctx iris.Context) {
claims := fooClaims{Foo: "bar"}
token, err := signer.Sign(claims)
if err != nil {
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
ctx.Write(token)
}
}
func protected(ctx iris.Context) {
// Get the verified and decoded claims.
claims := jwt.Get(ctx).(*fooClaims)
// Optionally, get token information if you want to work with them.
// Just an example on how you can retrieve all the standard claims (set by signer's max age, "exp").
standardClaims := jwt.GetVerifiedToken(ctx).StandardClaims
expiresAtString := standardClaims.ExpiresAt().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
timeLeft := standardClaims.Timeleft()
ctx.Writef("foo=%s\nexpires at: %s\ntime left: %s\n", claims.Foo, expiresAtString, timeLeft)
}
func logout(ctx iris.Context) {
err := ctx.Logout()
if err != nil {
ctx.WriteString(err.Error())
} else {
ctx.Writef("token invalidated, a new token is required to access the protected API")
}
}
+183
View File
@@ -0,0 +1,183 @@
package main
import (
"fmt"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
const (
accessTokenMaxAge = 10 * time.Minute
refreshTokenMaxAge = time.Hour
)
var (
privateKey, publicKey = jwt.MustLoadRSA("rsa_private_key.pem", "rsa_public_key.pem")
signer = jwt.NewSigner(jwt.RS256, privateKey, accessTokenMaxAge)
verifier = jwt.NewVerifier(jwt.RS256, publicKey)
)
// UserClaims a custom access claims structure.
type UserClaims struct {
ID string `json:"user_id"`
// Do: `json:"username,required"` to have this field required
// or see the Validate method below instead.
Username string `json:"username"`
}
// GetID implements the partial context user's ID interface.
// Note that if claims were a map then the claims value converted to UserClaims
// and no need to implement any method.
//
// This is useful when multiple auth methods are used (e.g. basic auth, jwt)
// but they all share a couple of methods.
func (u *UserClaims) GetID() string {
return u.ID
}
// GetUsername implements the partial context user's Username interface.
func (u *UserClaims) GetUsername() string {
return u.Username
}
// Validate completes the middleware's custom ClaimsValidator.
// It will not accept a token which its claims missing the username field
// (useful to not accept refresh tokens generated by the same algorithm).
func (u *UserClaims) Validate() error {
if u.Username == "" {
return fmt.Errorf("username field is missing")
}
return nil
}
// For refresh token, we will just use the jwt.Claims
// structure which contains the standard JWT fields.
func main() {
app := iris.New()
app.OnErrorCode(iris.StatusUnauthorized, handleUnauthorized)
app.Get("/authenticate", generateTokenPair)
app.Get("/refresh", refreshToken)
protectedAPI := app.Party("/protected")
{
verifyMiddleware := verifier.Verify(func() interface{} {
return new(UserClaims)
})
protectedAPI.Use(verifyMiddleware)
protectedAPI.Get("/", func(ctx iris.Context) {
// Access the claims through: jwt.Get:
// claims := jwt.Get(ctx).(*UserClaims)
// ctx.Writef("Username: %s\n", claims.Username)
//
// OR through context's user (if at least one method was implement by our UserClaims):
user := ctx.User()
id, _ := user.GetID()
username, _ := user.GetUsername()
ctx.Writef("ID: %s\nUsername: %s\n", id, username)
})
}
// http://localhost:8080/protected (401)
// http://localhost:8080/authenticate (200) (response JSON {access_token, refresh_token})
// http://localhost:8080/protected?token={access_token} (200)
// http://localhost:8080/protected?token={refresh_token} (401)
// http://localhost:8080/refresh?refresh_token={refresh_token}
// OR http://localhost:8080/refresh (request JSON{refresh_token = {refresh_token}}) (200) (response JSON {access_token, refresh_token})
// http://localhost:8080/refresh?refresh_token={access_token} (401)
app.Listen(":8080")
}
func generateTokenPair(ctx iris.Context) {
// Simulate a user...
userID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
// Map the current user with the refresh token,
// so we make sure, on refresh route, that this refresh token owns
// to that user before re-generate.
refreshClaims := jwt.Claims{Subject: userID}
accessClaims := UserClaims{
ID: userID,
Username: "kataras",
}
// Generates a Token Pair, long-live for refresh tokens, e.g. 1 hour.
// First argument is the access claims,
// second argument is the refresh claims,
// third argument is the refresh max age.
tokenPair, err := signer.NewTokenPair(accessClaims, refreshClaims, refreshTokenMaxAge)
if err != nil {
ctx.Application().Logger().Errorf("token pair: %v", err)
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
// Send the generated token pair to the client.
// The tokenPair looks like: {"access_token": $token, "refresh_token": $token}
ctx.JSON(tokenPair)
}
// There are various methods of refresh token, depending on the application requirements.
// In this example we will accept a refresh token only, we will verify only a refresh token
// and we re-generate a whole new pair. An alternative would be to accept a token pair
// of both access and refresh tokens, verify the refresh, verify the access with a Leeway time
// and check if its going to expire soon, then generate a single access token.
func refreshToken(ctx iris.Context) {
// Assuming you have access to the current user, e.g. sessions.
//
// Simulate a database call against our jwt subject
// to make sure that this refresh token is a pair generated by this user.
// * Note: You can remove the ExpectSubject and do this validation later on by yourself.
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
// Get the refresh token from ?refresh_token=$token OR
// the request body's JSON{"refresh_token": "$token"}.
refreshToken := []byte(ctx.URLParam("refresh_token"))
if len(refreshToken) == 0 {
// You can read the whole body with ctx.GetBody/ReadBody too.
var tokenPair jwt.TokenPair
if err := ctx.ReadJSON(&tokenPair); err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
refreshToken = tokenPair.RefreshToken
}
// Verify the refresh token, which its subject MUST match the "currentUserID".
_, err := verifier.VerifyToken(refreshToken, jwt.Expected{Subject: currentUserID})
if err != nil {
ctx.Application().Logger().Errorf("verify refresh token: %v", err)
ctx.StatusCode(iris.StatusUnauthorized)
return
}
/* Custom validation checks can be performed after Verify calls too:
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
userID := verifiedToken.StandardClaims.Subject
if userID != currentUserID {
ctx.StopWithStatus(iris.StatusUnauthorized)
return
}
*/
// All OK, re-generate the new pair and send to client,
// we could only generate an access token as well.
generateTokenPair(ctx)
}
func handleUnauthorized(ctx iris.Context) {
if err := ctx.GetErr(); err != nil {
ctx.Application().Logger().Errorf("unauthorized: %v", err)
}
ctx.WriteString("Unauthorized")
}
@@ -0,0 +1,27 @@
-----BEGIN PRIVATE KEY-----
MIIEowIBAAKCAQEArwO0q8WbBvrplz3lTQjsWu66HC7M3mVAjmjLq8Wj/ipqVtiJ
MrUL9t/0q9PNO/KX9u+HayFNYM4TnYkXVZX3M5E31W8fPPy74D/XpqFwrwT7bAEw
pT51JJyxkoBAyOh08lmR2EYvpGF7qErra7qbkk4LGFbhoFCXdMLXguT4rPymkzFH
dQrmGYOBS+v9imSuJddCZpXyv6Ko7AKB4mhzg4RC5RJZO5GEHVUrSMHxZB0syF8c
U+28iL8A7SlGKTNZPZiHmCQVRqA6WlllL/YV/t6p24kaNZBUp9JGbAzOeKuVUv2u
vfNKwB/aBwnFKauM9I6RmC4bnI1nGHjETlNNWwIDAQABAoIBAHBPKHmybTGlgpET
nzo4J7SSzcuYHM/6mdrJVSn9wqcwAN2KR0DK/cqHHTPGz0VRAEPuojAVRtqAZAYM
G3VIr0HgRrwoextf9BCL549+uhkWUWGVwenIktPT2f/xXaGPyrxazkTDhX8vL3Nn
4HtZXMweWPBdkJyYGxlKj5Hn7czTpG3VKpvpHeFlY4caF+FT2as1jcQ1MjPnGslH
Ss+sYPBp/70w2T114Z4wlR4OryI1LeuFeje9obrn0HAmJd0ZKYM21awp/YWJ/y8J
wIH6XQ4AGR9iTRhuffK1XRM/Iec3K/YhOn4PtKdT7OsIujAKY7A9WcqSFif+/E1g
jom3eMECgYEAw5Zdqt2uZ19FuDlDTW4Kw8Z2NyXgWp33LkAXG1mJw7bqDhfPeB1c
xTPs4i4RubGuDusygxZ3GgJAO7tLGzNQfWNoi03mM7Q/BJGkA9VZr+U28zsSRQOQ
+J9xNsdgUMP1js7X/NNM2bxTC8zy9wEsWr9JwNo1C7uHTE9WXAumBI8CgYEA5RKV
niSbyko36W3Vi0ZnGBrRhy0Eiq85V2mhWzHN+txcv+8aISow2wioTUzrpR0aVZ4j
v9+siJENlALVzdUFihy0lPxHqLJT746Cixz95WRTLkdHeNllV0DMfOph2x3j1Hjd
3PgTv+jqb6npY0/2Vb2pp4t/zVikGaObsAalSHUCgYBne8B1bjMfqI3n6gxNBIMX
kILtrNGmwFuPEgPnyZkVf0sZR8nSwJ5cDJwyE7P3LyZr6E9igllj3nsD35Xef2j/
3r/qrL2275BEJ5bDHHgGk91eFgwVjcx/b0TkedrhAL2E4LXwpA/OSFEcNkT7IZjJ
Ltqj+hAE9CSi4HtN2i/tywKBgBotKn28zzSpkIQTMgDNVcCSZ/kbctZqOZI8lty1
70TIY6znJMQ/bv/ImHrk3FSs47J+9LTbWXrtoHCWdlokCpMCvrv7rDCh2Cea0F4X
PQg2k67JJGix5vu2guePXQlN/Bfui+PRUWhvtEJ4VxwrKgoYN0fXEA6mH3JymLrf
t4l1AoGBALk4o9swGjw7MnByYJmOidlJ0p9Wj1BWWJJYoYX2VfjIuvZj6BNxkEb0
aVmYRC+40e9L1rOyrlyaO/TiQaIPE4ljVs/AmMKGz8sIcVfwdyERH3nDrXxvlAav
lSvfKoYM3J+5c63CDuU45gztpmavNerzCczqYTLOEMx1eCLHOQlx
-----END PRIVATE KEY-----
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwO0q8WbBvrplz3lTQjs
Wu66HC7M3mVAjmjLq8Wj/ipqVtiJMrUL9t/0q9PNO/KX9u+HayFNYM4TnYkXVZX3
M5E31W8fPPy74D/XpqFwrwT7bAEwpT51JJyxkoBAyOh08lmR2EYvpGF7qErra7qb
kk4LGFbhoFCXdMLXguT4rPymkzFHdQrmGYOBS+v9imSuJddCZpXyv6Ko7AKB4mhz
g4RC5RJZO5GEHVUrSMHxZB0syF8cU+28iL8A7SlGKTNZPZiHmCQVRqA6WlllL/YV
/t6p24kaNZBUp9JGbAzOeKuVUv2uvfNKwB/aBwnFKauM9I6RmC4bnI1nGHjETlNN
WwIDAQAB
-----END PUBLIC KEY-----
+62
View File
@@ -0,0 +1,62 @@
# Iris JWT Tutorial
This example show how to use JWT with domain-driven design pattern with Iris. There is also a simple Go client which describes how you can use Go to authorize a user and use the server's API.
## Run the server
```sh
$ go run main.go
```
## Authenticate, get the token
```sh
$ curl --location --request POST 'http://localhost:8080/signin' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=admin' \
--data-urlencode 'password=admin'
> $token
```
## Get all TODOs for this User
```sh
$ curl --location --request GET 'http://localhost:8080/todos' \
--header 'Authorization: Bearer $token'
> $todos
```
## Get a specific User's TODO
```sh
$ curl --location --request GET 'http://localhost:8080/todos/$id' \
--header 'Authorization: Bearer $token'
> $todo
```
## Get all TODOs for all Users (admin role)
```sh
$ curl --location --request GET 'http://localhost:8080/admin/todos' \
--header 'Authorization: Bearer $token'
> $todos
```
## Create a new TODO
```sh
$ curl --location --request POST 'http://localhost:8080/todos' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data-raw '{
"title": "test titlte",
"body": "test body"
}'
> Status Created
> $todo
```
+140
View File
@@ -0,0 +1,140 @@
package api
import (
"fmt"
"os"
"time"
"myapp/domain/model"
"myapp/domain/repository"
"myapp/util"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
const defaultSecretKey = "sercrethatmaycontainch@r$32chars"
func getSecretKey() string {
secret := os.Getenv(util.AppName + "_SECRET")
if secret == "" {
return defaultSecretKey
}
return secret
}
// UserClaims represents the user token claims.
type UserClaims struct {
UserID string `json:"user_id"`
Roles []model.Role `json:"roles"`
}
// Validate implements the custom struct claims validator,
// this is totally optionally and maybe unnecessary but good to know how.
func (u *UserClaims) Validate() error {
if u.UserID == "" {
return fmt.Errorf("%w: %s", jwt.ErrMissingKey, "user_id")
}
return nil
}
// Verify allows only authorized clients.
func Verify() iris.Handler {
secret := getSecretKey()
verifier := jwt.NewVerifier(jwt.HS256, []byte(secret), jwt.Expected{Issuer: util.AppName})
verifier.Extractors = []jwt.TokenExtractor{jwt.FromHeader} // extract token only from Authorization: Bearer $token
return verifier.Verify(func() interface{} {
return new(UserClaims)
})
}
// AllowAdmin allows only authorized clients with "admin" access role.
// Should be registered after Verify.
func AllowAdmin(ctx iris.Context) {
if !IsAdmin(ctx) {
ctx.StopWithText(iris.StatusForbidden, "admin access required")
return
}
ctx.Next()
}
// SignIn accepts the user form data and returns a token to authorize a client.
func SignIn(repo repository.UserRepository) iris.Handler {
secret := getSecretKey()
signer := jwt.NewSigner(jwt.HS256, []byte(secret), 15*time.Minute)
return func(ctx iris.Context) {
/*
type LoginForm struct {
Username string `form:"username"`
Password string `form:"password"`
}
and ctx.ReadForm OR use the ctx.FormValue(s) method.
*/
var (
username = ctx.FormValue("username")
password = ctx.FormValue("password")
)
user, ok := repo.GetByUsernameAndPassword(username, password)
if !ok {
ctx.StopWithText(iris.StatusBadRequest, "wrong username or password")
return
}
claims := UserClaims{
UserID: user.ID,
Roles: user.Roles,
}
// Optionally, generate a JWT ID.
jti, err := util.GenerateUUID()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
token, err := signer.Sign(claims, jwt.Claims{
ID: jti,
Issuer: util.AppName,
})
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.Write(token)
}
}
// SignOut invalidates a user from server-side using the jwt Blocklist.
func SignOut(ctx iris.Context) {
ctx.Logout() // this is automatically binded to a function which invalidates the current request token by the JWT Verifier above.
}
// GetClaims returns the current authorized client claims.
func GetClaims(ctx iris.Context) *UserClaims {
claims := jwt.Get(ctx).(*UserClaims)
return claims
}
// GetUserID returns the current authorized client's user id extracted from claims.
func GetUserID(ctx iris.Context) string {
return GetClaims(ctx).UserID
}
// IsAdmin reports whether the current client has admin access.
func IsAdmin(ctx iris.Context) bool {
for _, role := range GetClaims(ctx).Roles {
if role == model.Admin {
return true
}
}
return false
}
+23
View File
@@ -0,0 +1,23 @@
package api
import (
"myapp/domain/repository"
"github.com/kataras/iris/v12"
)
// NewRouter accepts some dependencies
// and returns a function which returns the routes on the given Iris Party (group of routes).
func NewRouter(userRepo repository.UserRepository, todoRepo repository.TodoRepository) func(iris.Party) {
return func(router iris.Party) {
router.Post("/signin", SignIn(userRepo))
router.Use(Verify()) // protect the next routes with JWT.
router.Post("/todos", CreateTodo(todoRepo))
router.Get("/todos", ListTodos(todoRepo))
router.Get("/todos/{id}", GetTodo(todoRepo))
router.Get("/admin/todos", AllowAdmin, ListAllTodos(todoRepo))
}
}
+119
View File
@@ -0,0 +1,119 @@
package api
import (
"errors"
"myapp/domain/repository"
"github.com/kataras/iris/v12"
)
// TodoRequest represents a Todo HTTP request.
type TodoRequest struct {
Title string `json:"title" form:"title" url:"title"`
Body string `json:"body" form:"body" url:"body"`
}
// CreateTodo handles the creation of a Todo entry.
func CreateTodo(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
var req TodoRequest
err := ctx.ReadBody(&req) // will bind the "req" to a JSON, form or url query request data.
if err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
userID := GetUserID(ctx)
todo, err := repo.Create(userID, req.Title, req.Body)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.StatusCode(iris.StatusCreated)
ctx.JSON(todo)
}
}
// GetTodo lists all users todos.
// Parameter: {id}.
func GetTodo(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
id := ctx.Params().Get("id")
userID := GetUserID(ctx)
todo, err := repo.GetByID(id)
if err != nil {
code := iris.StatusInternalServerError
if errors.Is(err, repository.ErrNotFound) {
code = iris.StatusNotFound
}
ctx.StopWithError(code, err)
return
}
if !IsAdmin(ctx) { // admin can access any user's todos.
if todo.UserID != userID {
ctx.StopWithStatus(iris.StatusForbidden)
return
}
}
ctx.JSON(todo)
}
}
// ListTodos lists todos of the current user.
func ListTodos(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
userID := GetUserID(ctx)
todos, err := repo.GetAllByUser(userID)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
// if len(todos) == 0 {
// ctx.StopWithError(iris.StatusNotFound, fmt.Errorf("no entries found"))
// return
// }
// Or let the client decide what to do on empty list.
ctx.JSON(todos)
}
}
// ListAllTodos lists all users todos.
// Access: admin.
// Middleware: AllowAdmin.
func ListAllTodos(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
todos, err := repo.GetAll()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.JSON(todos)
}
}
/* Leave as exercise: use filtering instead...
// ListTodosByUser lists all todos by a specific user.
// Access: admin.
// Middleware: AllowAdmin.
// Parameter: {id}.
func ListTodosByUser(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
userID := ctx.Params().Get("id")
todos, err := repo.GetAllByUser(userID)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.JSON(todos)
}
}
*/
@@ -0,0 +1,9 @@
package model
// Role represents a role.
type Role string
const (
// Admin represents the Admin access role.
Admin Role = "admin"
)
@@ -0,0 +1,10 @@
package model
// Todo represents the Todo model.
type Todo struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Title string `json:"title"`
Body string `json:"body"`
CreatedAt int64 `json:"created_at"` // unix seconds.
}
@@ -0,0 +1,9 @@
package model
// User represents our User model.
type User struct {
ID string `json:"id"`
Username string `json:"username"`
HashedPassword []byte `json:"-"`
Roles []Role `json:"roles"`
}
@@ -0,0 +1,45 @@
package repository
import (
"fmt"
"myapp/domain/model"
)
// GenerateSamples generates data samples.
func GenerateSamples(userRepo UserRepository, todoRepo TodoRepository) error {
// Create users.
for _, username := range []string{"vasiliki", "george", "kwstas"} {
// My grandmother.
// My young brother.
// My youngest brother.
password := fmt.Sprintf("%s_pass", username)
if _, err := userRepo.Create(username, password); err != nil {
return err
}
}
// Create a user with admin role.
if _, err := userRepo.Create("admin", "admin", model.Admin); err != nil {
return err
}
// Create two todos per user.
users, err := userRepo.GetAll()
if err != nil {
return err
}
for i, u := range users {
for j := 0; j < 2; j++ {
title := fmt.Sprintf("%s todo %d:%d title", u.Username, i, j)
body := fmt.Sprintf("%s todo %d:%d body", u.Username, i, j)
_, err := todoRepo.Create(u.ID, title, body)
if err != nil {
return err
}
}
}
return nil
}
@@ -0,0 +1,94 @@
package repository
import (
"errors"
"sync"
"myapp/domain/model"
"myapp/util"
)
// ErrNotFound indicates that an entry was not found.
// Usage: errors.Is(err, ErrNotFound)
var ErrNotFound = errors.New("not found")
// TodoRepository is responsible for Todo CRUD operations,
// however, for the sake of the example we only implement the Create and Read ones.
type TodoRepository interface {
Create(userID, title, body string) (model.Todo, error)
GetByID(id string) (model.Todo, error)
GetAll() ([]model.Todo, error)
GetAllByUser(userID string) ([]model.Todo, error)
}
var (
_ TodoRepository = (*memoryTodoRepository)(nil)
)
type memoryTodoRepository struct {
todos []model.Todo // map[string]model.Todo
mu sync.RWMutex
}
// NewMemoryTodoRepository returns the default in-memory todo repository.
func NewMemoryTodoRepository() TodoRepository {
r := new(memoryTodoRepository)
return r
}
func (r *memoryTodoRepository) Create(userID, title, body string) (model.Todo, error) {
id, err := util.GenerateUUID()
if err != nil {
return model.Todo{}, err
}
todo := model.Todo{
ID: id,
UserID: userID,
Title: title,
Body: body,
CreatedAt: util.Now().Unix(),
}
r.mu.Lock()
r.todos = append(r.todos, todo)
r.mu.Unlock()
return todo, nil
}
func (r *memoryTodoRepository) GetByID(id string) (model.Todo, error) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, todo := range r.todos {
if todo.ID == id {
return todo, nil
}
}
return model.Todo{}, ErrNotFound
}
func (r *memoryTodoRepository) GetAll() ([]model.Todo, error) {
r.mu.RLock()
tmp := make([]model.Todo, len(r.todos))
copy(tmp, r.todos)
r.mu.RUnlock()
return tmp, nil
}
func (r *memoryTodoRepository) GetAllByUser(userID string) ([]model.Todo, error) {
// initialize a slice, so we don't have "null" at empty response.
todos := make([]model.Todo, 0)
r.mu.RLock()
for _, todo := range r.todos {
if todo.UserID == userID {
todos = append(todos, todo)
}
}
r.mu.RUnlock()
return todos, nil
}
@@ -0,0 +1,82 @@
package repository
import (
"sync"
"myapp/domain/model"
"myapp/util"
)
// UserRepository is responsible for User CRUD operations,
// however, for the sake of the example we only implement the Read one.
type UserRepository interface {
Create(username, password string, roles ...model.Role) (model.User, error)
// GetByUsernameAndPassword should return a User based on the given input.
GetByUsernameAndPassword(username, password string) (model.User, bool)
GetAll() ([]model.User, error)
}
var (
_ UserRepository = (*memoryUserRepository)(nil)
)
type memoryUserRepository struct {
// Users represents a user database.
// For the sake of the tutorial we use a simple slice of users.
users []model.User
mu sync.RWMutex
}
// NewMemoryUserRepository returns the default in-memory user repository.
func NewMemoryUserRepository() UserRepository {
r := new(memoryUserRepository)
return r
}
func (r *memoryUserRepository) Create(username, password string, roles ...model.Role) (model.User, error) {
id, err := util.GenerateUUID()
if err != nil {
return model.User{}, err
}
hashedPassword, err := util.GeneratePassword(password)
if err != nil {
return model.User{}, err
}
user := model.User{
ID: id,
Username: username,
HashedPassword: hashedPassword,
Roles: roles,
}
r.mu.Lock()
r.users = append(r.users, user)
r.mu.Unlock()
return user, nil
}
// GetByUsernameAndPassword returns a user from the storage based on the given "username" and "password".
func (r *memoryUserRepository) GetByUsernameAndPassword(username, password string) (model.User, bool) {
for _, u := range r.users { // our example uses a static slice.
if u.Username == username {
// we compare the user input and the stored hashed password.
ok := util.ValidatePassword(password, u.HashedPassword)
if ok {
return u, true
}
}
}
return model.User{}, false
}
func (r *memoryUserRepository) GetAll() ([]model.User, error) {
r.mu.RLock()
tmp := make([]model.User, len(r.users))
copy(tmp, r.users)
r.mu.RUnlock()
return tmp, nil
}
@@ -0,0 +1,12 @@
# Go Client
```sh
$ go run .
```
```sh
2020/11/04 21:08:40 Access Token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYTAwYzI3ZDEtYjVhYS00NjU0LWFmMTYtYjExNzNkZTY1NjI5Iiwicm9sZXMiOlsiYWRtaW4iXSwiaWF0IjoxNjA0NTE2OTIwLCJleHAiOjE2MDQ1MTc4MjAsImp0aSI6IjYzNmVmMDc0LTE2MzktNGJhZi1hNGNiLTQ4ZDM4NGMxMzliYSIsImlzcyI6Im15YXBwIn0.T9B0zG0AHShO5JfQgrMQBlToH33KHgp8nLMPFpN6QmM"
2020/11/04 21:08:40 Todo Created:
model.Todo{ID:"cfa38d7a-c556-4301-ae1f-fb90f705071c", UserID:"a00c27d1-b5aa-4654-af16-b1173de65629", Title:"test todo title", Body:"test todo body contents", CreatedAt:1604516920}
```
@@ -0,0 +1,108 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
)
// Client is the default http client instance used by the following methods.
var Client = http.DefaultClient
// RequestOption is a function which can be used to modify
// a request instance before Do.
type RequestOption func(*http.Request) error
// WithAccessToken sets the given "token" to the authorization request header.
func WithAccessToken(token []byte) RequestOption {
bearer := "Bearer " + string(token)
return func(req *http.Request) error {
req.Header.Add("Authorization", bearer)
return nil
}
}
// WithContentType sets the content-type request header.
func WithContentType(cType string) RequestOption {
return func(req *http.Request) error {
req.Header.Set("Content-Type", cType)
return nil
}
}
// WithContentLength sets the content-length request header.
func WithContentLength(length int) RequestOption {
return func(req *http.Request) error {
req.Header.Set("Content-Length", strconv.Itoa(length))
return nil
}
}
// Do fires a request to the server.
func Do(method, url string, body io.Reader, opts ...RequestOption) (*http.Response, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
for _, opt := range opts {
if err = opt(req); err != nil {
return nil, err
}
}
return Client.Do(req)
}
// JSON fires a request with "v" as client json data.
func JSON(method, url string, v interface{}, opts ...RequestOption) (*http.Response, error) {
buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(v)
if err != nil {
return nil, err
}
opts = append(opts, WithContentType("application/json; charset=utf-8"))
return Do(method, url, buf, opts...)
}
// Form fires a request with "formData" as client form data.
func Form(method, url string, formData url.Values, opts ...RequestOption) (*http.Response, error) {
encoded := formData.Encode()
body := strings.NewReader(encoded)
opts = append([]RequestOption{
WithContentType("application/x-www-form-urlencoded"),
WithContentLength(len(encoded)),
}, opts...)
return Do(method, url, body, opts...)
}
// BindResponse binds a response body to the "dest" pointer and closes the body.
func BindResponse(resp *http.Response, dest interface{}) error {
contentType := resp.Header.Get("Content-Type")
if idx := strings.IndexRune(contentType, ';'); idx > 0 {
contentType = contentType[0:idx]
}
switch contentType {
case "application/json":
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(dest)
default:
return fmt.Errorf("unsupported content type: %s", contentType)
}
}
// RawResponse simply returns the raw response body.
func RawResponse(resp *http.Response) ([]byte, error) {
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
@@ -0,0 +1,69 @@
package main
import (
"fmt"
"log"
"net/http"
"net/url"
"myapp/api"
"myapp/domain/model"
)
const base = "http://localhost:8080"
func main() {
accessToken, err := authenticate("admin", "admin")
if err != nil {
log.Fatal(err)
}
log.Printf("Access Token:\n%q", accessToken)
todo, err := createTodo(accessToken, "test todo title", "test todo body contents")
if err != nil {
log.Fatal(err)
}
log.Printf("Todo Created:\n%#+v", todo)
}
func authenticate(username, password string) ([]byte, error) {
endpoint := base + "/signin"
data := make(url.Values)
data.Set("username", username)
data.Set("password", password)
resp, err := Form(http.MethodPost, endpoint, data)
if err != nil {
return nil, err
}
accessToken, err := RawResponse(resp)
return accessToken, err
}
func createTodo(accessToken []byte, title, body string) (model.Todo, error) {
var todo model.Todo
endpoint := base + "/todos"
req := api.TodoRequest{
Title: title,
Body: body,
}
resp, err := JSON(http.MethodPost, endpoint, req, WithAccessToken(accessToken))
if err != nil {
return todo, err
}
if resp.StatusCode != http.StatusCreated {
rawData, _ := RawResponse(resp)
return todo, fmt.Errorf("failed to create a todo: %s", string(rawData))
}
err = BindResponse(resp, &todo)
return todo, err
}
+54
View File
@@ -0,0 +1,54 @@
module myapp
go 1.22
require (
github.com/google/uuid v1.6.0
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad
golang.org/x/crypto v0.22.0
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/blocks v0.0.8 // indirect
github.com/kataras/golog v0.1.11 // indirect
github.com/kataras/jwt v0.1.12 // indirect
github.com/kataras/pio v0.0.13 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tdewolff/minify/v2 v2.20.19 // indirect
github.com/tdewolff/parse/v2 v2.7.12 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yosssi/ace v0.0.5 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+180
View File
@@ -0,0 +1,180 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU=
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go=
github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE=
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20=
github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A=
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad h1:oWfB7/JUb6RU6wwCMMNh1e7p307bJjKWgX6R4oazL1A=
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w=
github.com/kataras/jwt v0.1.12 h1:FHPgTTj5UqjlBye4PA4/oxknCY+kQ9K34XAi8d37glA=
github.com/kataras/jwt v0.1.12/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo=
github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"myapp/api"
"myapp/domain/repository"
"github.com/kataras/iris/v12"
)
var (
userRepo = repository.NewMemoryUserRepository()
todoRepo = repository.NewMemoryTodoRepository()
)
func main() {
if err := repository.GenerateSamples(userRepo, todoRepo); err != nil {
panic(err)
}
app := iris.New()
app.PartyFunc("/", api.NewRouter(userRepo, todoRepo))
// POST http://localhost:8080/signin (Form: username, password)
// GET http://localhost:8080/todos
// GET http://localhost:8080/todos/{id}
// POST http://localhost:8080/todos (JSON, Form or URL: title, body)
// GET http://localhost:8080/admin/todos
app.Listen(":8080")
}
+7
View File
@@ -0,0 +1,7 @@
package util
// Constants for the application.
const (
Version = "0.0.1"
AppName = "myapp"
)
@@ -0,0 +1,7 @@
package util
import "time"
// Now is the default current time for the whole application.
// Can be modified for testing or custom timezone.
var Now = time.Now
@@ -0,0 +1,25 @@
package util
import "golang.org/x/crypto/bcrypt"
// MustGeneratePassword same as GeneratePassword but panics on errors.
func MustGeneratePassword(userPassword string) []byte {
hashed, err := GeneratePassword(userPassword)
if err != nil {
panic(err)
}
return hashed
}
// GeneratePassword will generate a hashed password for us based on the
// user's input.
func GeneratePassword(userPassword string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(userPassword), bcrypt.DefaultCost)
}
// ValidatePassword will check if passwords are matched.
func ValidatePassword(userPassword string, hashed []byte) bool {
err := bcrypt.CompareHashAndPassword(hashed, []byte(userPassword))
return err == nil
}
+23
View File
@@ -0,0 +1,23 @@
package util
import "github.com/google/uuid"
// MustGenerateUUID returns a new v4 UUID or panics.
func MustGenerateUUID() string {
id, err := GenerateUUID()
if err != nil {
panic(err)
}
return id
}
// GenerateUUID returns a new v4 UUID.
func GenerateUUID() (string, error) {
id, err := uuid.NewRandom()
if err != nil {
return "", err
}
return id.String(), nil
}
+118
View File
@@ -0,0 +1,118 @@
package main
import (
"fmt"
"log"
"strings"
"github.com/kataras/iris/v12"
permissions "github.com/xyproto/permissionbolt"
// * PostgreSQL support:
// permissions "github.com/xyproto/pstore" and
// perm, err := permissions.New(...)
//
// * MariaDB/MySQL support:
// permissions "github.com/xyproto/permissionsql" and
// perm, err := permissions.New/NewWithDSN(...)
// * Redis support:
// permissions "github.com/xyproto/permissions2"
// perm, err := permissions.New2()
// * Bolt support (this one):
// permissions "github.com/xyproto/permissionbolt" and
// perm, err := permissions.New(...)
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
// New permissions middleware.
perm, err := permissions.New()
if err != nil {
log.Fatalln(err)
}
// Blank slate, no default permissions
// perm.Clear()
// Set up a middleware handler for Iris, with a custom "permission denied" message.
permissionHandler := func(ctx iris.Context) {
// Check if the user has the right admin/user rights
if perm.Rejected(ctx.ResponseWriter(), ctx.Request()) {
// Deny the request, don't call other middleware handlers
ctx.StopWithText(iris.StatusForbidden, "Permission denied!")
return
}
// Call the next middleware handler
ctx.Next()
}
// Register the permissions middleware
app.Use(permissionHandler)
// Get the userstate, used in the handlers below
userstate := perm.UserState()
app.Get("/", func(ctx iris.Context) {
msg := ""
msg += fmt.Sprintf("Has user bob: %v\n", userstate.HasUser("bob"))
msg += fmt.Sprintf("Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
msg += fmt.Sprintf("Is confirmed: %v\n", userstate.IsConfirmed("bob"))
msg += fmt.Sprintf("Username stored in cookies (or blank): %v\n", userstate.Username(ctx.Request()))
msg += fmt.Sprintf("Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(ctx.Request()))
msg += fmt.Sprintf("Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(ctx.Request()))
msg += fmt.Sprintln("\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
ctx.WriteString(msg)
})
app.Get("/register", func(ctx iris.Context) {
userstate.AddUser("bob", "hunter1", "bob@zombo.com")
ctx.Writef("User bob was created: %v\n", userstate.HasUser("bob"))
})
app.Get("/confirm", func(ctx iris.Context) {
userstate.MarkConfirmed("bob")
ctx.Writef("User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
})
app.Get("/remove", func(ctx iris.Context) {
userstate.RemoveUser("bob")
ctx.Writef("User bob was removed: %v\n", !userstate.HasUser("bob"))
})
app.Get("/login", func(ctx iris.Context) {
// Headers will be written, for storing a cookie
userstate.Login(ctx.ResponseWriter(), "bob")
ctx.Writef("bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
})
app.Get("/logout", func(ctx iris.Context) {
userstate.Logout("bob")
ctx.Writef("bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
})
app.Get("/makeadmin", func(ctx iris.Context) {
userstate.SetAdminStatus("bob")
ctx.Writef("bob is now administrator: %v\n", userstate.IsAdmin("bob"))
})
app.Get("/clear", func(ctx iris.Context) {
userstate.ClearCookie(ctx.ResponseWriter())
ctx.WriteString("Clearing cookie")
})
app.Get("/data", func(ctx iris.Context) {
ctx.WriteString("user page that only logged in users must see!")
})
app.Get("/admin", func(ctx iris.Context) {
ctx.WriteString("super secret information that only logged in administrators must see!\n\n")
if usernames, err := userstate.AllUsernames(); err == nil {
ctx.Writef("list of all users: %s" + strings.Join(usernames, ", "))
}
})
// Serve
app.Listen(":8080")
}
@@ -24,7 +24,7 @@ func main() {
// pass the middleware before the main handler or use the `recaptcha.SiteVerify`.
app.Post("/comment", r, postComment)
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
var htmlForm = `<form action="/comment" method="POST">
@@ -26,7 +26,7 @@ func main() {
return
}
result := recaptcha.SiteFerify(ctx, recaptchaSecret)
result := recaptcha.SiteVerify(ctx, recaptchaSecret)
if !result.Success {
/* redirect here if u want or do nothing */
ctx.HTML("<b> failed please try again </b>")
@@ -36,5 +36,5 @@ func main() {
ctx.Writef("succeed.")
})
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
-6
View File
@@ -1,6 +0,0 @@
# Authentication
- [Basic Authentication](basicauth/main.go)
- [OAUth2](oauth2/main.go)
- [Request Auth(JWT)](https://github.com/iris-contrib/middleware/blob/master/jwt)
- [Sessions](https://github.com/kataras/iris/tree/master/_examples/#sessions)
@@ -61,7 +61,7 @@ func (b *Bootstrapper) SetupWebsockets(endpoint string, handler websocket.ConnHa
}
// SetupErrorHandlers prepares the http error handlers
// `(context.StatusCodeNotSuccessful`, which defaults to < 200 || >= 400 but you can change it).
// `(context.StatusCodeNotSuccessful`, which defaults to >=400 (but you can change it).
func (b *Bootstrapper) SetupErrorHandlers() {
b.OnAnyErrorCode(func(ctx iris.Context) {
err := iris.Map{
@@ -77,7 +77,10 @@ func (b *Bootstrapper) SetupErrorHandlers() {
ctx.ViewData("Err", err)
ctx.ViewData("Title", "Error")
ctx.View("shared/error.html")
if err := ctx.View("shared/error.html"); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
}
@@ -108,7 +111,7 @@ func (b *Bootstrapper) Bootstrap() *Bootstrapper {
// static files
b.Favicon(StaticAssets + Favicon)
b.HandleDir(StaticAssets[1:len(StaticAssets)-1], StaticAssets)
b.HandleDir("/public", iris.Dir(StaticAssets))
// middleware, after static files
b.Use(recover.New())

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+85
View File
@@ -0,0 +1,85 @@
module github.com/kataras/iris/v12/_examples/bootstrapper
go 1.22
require (
github.com/gorilla/securecookie v1.1.2
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/iris-contrib/httpexpect/v2 v2.15.2 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/blocks v0.0.8 // indirect
github.com/kataras/golog v0.1.11 // indirect
github.com/kataras/neffos v0.0.24-0.20240408172741-99c879ba0ede // indirect
github.com/kataras/pio v0.0.13 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mediocregopher/radix/v3 v3.8.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/nats-io/nats.go v1.34.1 // indirect
github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sanity-io/litter v1.5.5 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sergi/go-diff v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tdewolff/minify/v2 v2.20.19 // indirect
github.com/tdewolff/parse/v2 v2.7.12 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yosssi/ace v0.0.5 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
moul.io/http2curl/v2 v2.3.0 // indirect
)
+235
View File
@@ -0,0 +1,235 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU=
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go=
github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE=
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20=
github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A=
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad h1:oWfB7/JUb6RU6wwCMMNh1e7p307bJjKWgX6R4oazL1A=
github.com/kataras/iris/v12 v12.2.11-0.20240424154124-4e90cd4e4dad/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w=
github.com/kataras/neffos v0.0.24-0.20240408172741-99c879ba0ede h1:ZnSJQ+ri9x46Yz15wHqSb93Q03yY12XMVLUFDJJ0+/g=
github.com/kataras/neffos v0.0.24-0.20240408172741-99c879ba0ede/go.mod h1:i0dtcTbpnw1lqIbojYtGtZlu6gDWPxJ4Xl2eJ6oQ1bE=
github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mediocregopher/radix/v3 v3.8.1 h1:rOkHflVuulFKlwsLY01/M2cM2tWCjDoETcMqKbAWu1M=
github.com/mediocregopher/radix/v3 v3.8.1/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4=
github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
@@ -1,9 +1,9 @@
package main
import (
"github.com/kataras/iris/v12/_examples/structuring/bootstrap/bootstrap"
"github.com/kataras/iris/v12/_examples/structuring/bootstrap/middleware/identity"
"github.com/kataras/iris/v12/_examples/structuring/bootstrap/routes"
"github.com/kataras/iris/v12/_examples/bootstrapper/bootstrap"
"github.com/kataras/iris/v12/_examples/bootstrapper/middleware/identity"
"github.com/kataras/iris/v12/_examples/bootstrapper/routes"
)
func newApp() *bootstrap.Bootstrapper {
@@ -14,11 +14,11 @@ func TestApp(t *testing.T) {
// test our routes
e.GET("/").Expect().Status(httptest.StatusOK)
e.GET("/follower/42").Expect().Status(httptest.StatusOK).
Body().Equal("from /follower/{id:long} with ID: 42")
Body().IsEqual("from /follower/{id:int64} with ID: 42")
e.GET("/following/52").Expect().Status(httptest.StatusOK).
Body().Equal("from /following/{id:long} with ID: 52")
Body().IsEqual("from /following/{id:int64} with ID: 52")
e.GET("/like/64").Expect().Status(httptest.StatusOK).
Body().Equal("from /like/{id:long} with ID: 64")
Body().IsEqual("from /like/{id:int64} with ID: 64")
// test not found
e.GET("/notfound").Expect().Status(httptest.StatusNotFound)
@@ -28,5 +28,5 @@ func TestApp(t *testing.T) {
"message": "",
}
e.GET("/anotfoundwithjson").WithQuery("json", nil).
Expect().Status(httptest.StatusNotFound).JSON().Equal(expectedErr)
Expect().Status(httptest.StatusNotFound).JSON().IsEqual(expectedErr)
}
@@ -5,7 +5,7 @@ import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/_examples/structuring/bootstrap/bootstrap"
"github.com/kataras/iris/v12/_examples/bootstrapper/bootstrap"
)
// New returns a new handler which adds some headers and view data

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -7,5 +7,8 @@ import (
// GetIndexHandler handles the GET: /
func GetIndexHandler(ctx iris.Context) {
ctx.ViewData("Title", "Index Page")
ctx.View("index.html")
if err := ctx.View("index.html"); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
}
+13
View File
@@ -0,0 +1,13 @@
package routes
import (
"github.com/kataras/iris/v12/_examples/bootstrapper/bootstrap"
)
// Configure registers the necessary routes to the app.
func Configure(b *bootstrap.Bootstrapper) {
b.Get("/", GetIndexHandler)
b.Get("/follower/{id:int64}", GetFollowerHandler)
b.Get("/following/{id:int64}", GetFollowingHandler)
b.Get("/like/{id:int64}", GetLikeHandler)
}
@@ -1,5 +1,5 @@
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<h3>{{.Err.status}}</h3>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<h3>{{.Err.status}}</h3>
<h4>{{.Err.message}}</h4>
@@ -1,23 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<title>{{.Title}} - {{.AppName}}</title>
</head>
<body>
<div>
<!-- Render the current template here -->
{{ yield }}
<hr />
<footer>
<p>&copy; 2017 - {{.AppOwner}}</p>
</footer>
</div>
</body>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<title>{{.Title}} - {{.AppName}}</title>
</head>
<body>
<div>
<!-- Render the current template here -->
{{ yield . }}
<hr />
<footer>
<p>&copy; 2017 - {{.AppOwner}}</p>
</footer>
</div>
</body>
</html>
-80
View File
@@ -1,80 +0,0 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/cache"
)
var markdownContents = []byte(`## Hello Markdown
This is a sample of Markdown contents
Features
--------
All features of Sundown are supported, including:
* **Compatibility**. The Markdown v1.0.3 test suite passes with
the --tidy option. Without --tidy, the differences are
mostly in whitespace and entity escaping, where blackfriday is
more consistent and cleaner.
* **Common extensions**, including table support, fenced code
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
* **Safety**. Blackfriday is paranoid when parsing, making it safe
to feed untrusted user input without fear of bad things
happening. The test suite stress tests this and there are no
known inputs that make it crash. If you find one, please let me
know and send me the input that does it.
NOTE: "safety" in this context means *runtime safety only*. In order to
protect yourself against JavaScript injection in untrusted content, see
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
* **Fast processing**. It is fast enough to render on-demand in
most web applications without having to cache the output.
* **Routine safety**. You can run multiple parsers in different
goroutines without ill effect. There is no dependence on global
shared state.
* **Minimal dependencies**. Blackfriday only depends on standard
library packages in Go. The source code is pretty
self-contained, so it is easy to add to any project, including
Google App Engine projects.
* **Standards compliant**. Output successfully validates using the
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
[this is a link](https://github.com/kataras/iris) `)
// Cache should not be used on handlers that contain dynamic data.
// Cache is a good and a must-feature on static content, i.e "about page" or for a whole blog site.
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
app.Get("/", cache.Handler(10*time.Second), writeMarkdown)
// saves its content on the first request and serves it instead of re-calculating the content.
// After 10 seconds it will be cleared and reset.
app.Run(iris.Addr(":8080"))
}
func writeMarkdown(ctx iris.Context) {
// tap multiple times the browser's refresh button and you will
// see this println only once every 10 seconds.
println("Handler executed. Content refreshed.")
ctx.Markdown(markdownContents)
}
/* Note that `HandleDir` does use the browser's disk caching by-default
therefore, register the cache handler AFTER any HandleDir calls,
for a faster solution that server doesn't need to keep track of the response
navigate to https://github.com/kataras/iris/blob/master/_examples/cache/client-side/main.go */
@@ -1,9 +1,9 @@
example.com {
header / Server "Iris"
proxy / example.com:9091 # localhost:9091
}
api.example.com {
header / Server "Iris"
proxy / api.example.com:9092 # localhost:9092
example.com {
header / Server "Iris"
proxy / example.com:9091 # localhost:9091
}
api.example.com {
header / Server "Iris"
proxy / api.example.com:9092 # localhost:9092
}
@@ -1,24 +1,24 @@
# Caddy loves Iris
The `Caddyfile` shows how you can use caddy to listen on ports 80 & 443 and sit in front of iris webserver(s) that serving on a different port (9091 and 9092 in this case; see Caddyfile).
## Running our two web servers
1. Go to `$GOPATH/src/github.com/kataras/iris/_examples/tutorial/caddy/server1`
2. Open a terminal window and execute `go run main.go`
3. Go to `$GOPATH/src/github.com/kataras/iris/_examples/tutorial/caddy/server2`
4. Open a new terminal window and execute `go run main.go`
## Caddy installation
1. Download caddy: https://caddyserver.com/download
2. Extract its contents where the `Caddyfile` is located, the `$GOPATH/src/github.com/kataras/iris/_examples/tutorial/caddy` in this case
3. Open, read and modify the `Caddyfile` to see by yourself how easy it is to configure the servers
4. Run `caddy` directly or open a terminal window and execute `caddy`
5. Go to `https://example.com` and `https://api.example.com/user/42`
## Notes
Iris has the `app.Run(iris.AutoTLS(":443", "example.com", "mail@example.com"))` which does
# Caddy loves Iris
The `Caddyfile` shows how you can use caddy to listen on ports 80 & 443 and sit in front of iris webserver(s) that serving on a different port (9091 and 9092 in this case; see Caddyfile).
## Running our two web servers
1. Go to `$GOPATH/src/github.com/kataras/iris/_examples/caddy/server1`
2. Open a terminal window and execute `go run main.go`
3. Go to `$GOPATH/src/github.com/kataras/iris/_examples/caddy/server2`
4. Open a new terminal window and execute `go run main.go`
## Caddy installation
1. Download caddy: https://caddyserver.com/download
2. Extract its contents where the `Caddyfile` is located, the `$GOPATH/src/github.com/kataras/iris/_examples/caddy` in this case
3. Open, read and modify the `Caddyfile` to see by yourself how easy it is to configure the servers
4. Run `caddy` directly or open a terminal window and execute `caddy`
5. Go to `https://example.com` and `https://api.example.com/user/42`
## Notes
Iris has the `app.Run(iris.AutoTLS(":443", "example.com", "mail@example.com"))` which does
the exactly same thing but caddy is a great tool that helps you when you run multiple web servers from one host machine, i.e iris, apache, tomcat.
@@ -14,7 +14,7 @@ func main() {
mvc.New(app).Handle(new(Controller))
// http://localhost:9091
app.Run(iris.Addr(":9091"))
app.Listen(":9091")
}
// Layout contains all the binding properties for the shared/layout.html
@@ -1,3 +1,3 @@
<div>
{{.Message}}
<div>
{{.Message}}
</div>
@@ -1,11 +1,11 @@
<html>
<head>
<title>{{.Layout.Title}}</title>
</head>
<body>
{{ yield }}
</body>
<html>
<head>
<title>{{.Layout.Title}}</title>
</head>
<body>
{{ yield . }}
</body>
</html>
@@ -21,7 +21,7 @@ func main() {
// PUT http://localhost:9092/user/42
// DELETE http://localhost:9092/user/42
// GET http://localhost:9092/user/followers/42
app.Run(iris.Addr(":9092"))
app.Listen(":9092")
}
// UserController is our user example controller.
@@ -35,7 +35,7 @@ func (c *UserController) Get() string {
// User is our test User model, nothing tremendous here.
type User struct{ ID int64 }
// GetBy handles GET /user/42, equal to .Get("/user/{id:long}")
// GetBy handles GET /user/42, equal to .Get("/user/{id:int64}")
func (c *UserController) GetBy(id int64) User {
// Select User by ID == $id.
return User{id}
@@ -0,0 +1,112 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/kataras/iris/v12/context"
)
const baseURL = "http://localhost:8080"
// Available options:
// - "gzip",
// - "deflate",
// - "br" (for brotli),
// - "snappy" and
// - "s2"
const encoding = context.BROTLI
var client = http.DefaultClient
func main() {
fmt.Printf("Running client example on: %s\n", baseURL)
getExample()
postExample()
}
func getExample() {
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
panic(err)
}
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", encoding)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// decompress server's compressed reply.
cr, err := context.NewCompressReader(resp.Body, encoding)
if err != nil {
panic(err)
}
defer cr.Close()
body, err := io.ReadAll(cr)
if err != nil {
panic(err)
}
fmt.Printf("Received from server: %s", string(body))
}
type payload struct {
Username string `json:"username"`
}
func postExample() {
buf := new(bytes.Buffer)
// Compress client's data.
cw, err := context.NewCompressWriter(buf, encoding, -1)
if err != nil {
panic(err)
}
json.NewEncoder(cw).Encode(payload{Username: "Edward"})
// `Close` or `Flush` required before `NewRequest` call.
cw.Close()
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodPost, endpoint, buf)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
// Required to send gzip compressed data to the server.
req.Header.Set("Content-Encoding", encoding)
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", encoding)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Decompress server's compressed reply.
cr, err := context.NewCompressReader(resp.Body, encoding)
if err != nil {
panic(err)
}
defer cr.Close()
body, err := io.ReadAll(cr)
if err != nil {
panic(err)
}
fmt.Printf("Server replied with: %s", string(body))
}
+102
View File
@@ -0,0 +1,102 @@
package main
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"net/http"
)
var client = http.DefaultClient
const baseURL = "http://localhost:8080"
func main() {
fmt.Printf("Running client example on: %s\n", baseURL)
getExample()
postExample()
}
func getExample() {
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
panic(err)
}
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// decompress server's compressed reply.
r, err := gzip.NewReader(resp.Body)
if err != nil {
panic(err)
}
defer r.Close()
body, err := io.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Printf("Received from server: %s", string(body))
}
type payload struct {
Username string `json:"username"`
}
func postExample() {
buf := new(bytes.Buffer)
// Compress client's data.
w := gzip.NewWriter(buf)
b, err := json.Marshal(payload{Username: "Edward"})
if err != nil {
panic(err)
}
w.Write(b)
w.Close()
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodPost, endpoint, buf)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
// Required to send gzip compressed data to the server.
req.Header.Set("Content-Encoding", "gzip")
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Decompress server's compressed reply.
r, err := gzip.NewReader(resp.Body)
if err != nil {
panic(err)
}
defer r.Close()
body, err := io.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Printf("Server replied with: %s", string(body))
}
+64
View File
@@ -0,0 +1,64 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := newApp()
app.Logger().SetLevel("debug")
app.Listen(":8080")
}
func newApp() *iris.Application {
app := iris.New()
// HERE and you are ready to GO:
app.Use(iris.Compression)
app.Get("/", send)
app.Post("/", receive)
return app
}
type payload struct {
Username string `json:"username"`
}
func send(ctx iris.Context) {
ctx.JSON(payload{
Username: "Makis",
})
}
func receive(ctx iris.Context) {
var p payload
if err := ctx.ReadJSON(&p); err != nil {
ctx.Application().Logger().Debugf("ReadJSON: %v", err)
}
ctx.WriteString(p.Username)
}
/* Manually:
func enableCompression(ctx iris.Context) {
// Enable writing using compression (deflate, gzip, brotli, snappy, s2):
err := ctx.CompressWriter(true)
if err != nil {
ctx.Application().Logger().Debugf("writer: %v", err)
// if you REQUIRE server to SEND compressed data then `return` here.
// return
}
// Enable reading and binding request's compressed data:
err = ctx.CompressReader(true)
if err != nil &&
// on GET we don't expect writing with gzip from client
ctx.Method() != iris.MethodGet {
ctx.Application().Logger().Debugf("reader: %v", err)
// if you REQUIRE server to RECEIVE only
// compressed data then `return` here.
// return
}
ctx.Next()
}
*/
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"encoding/json"
"reflect"
"strings"
"testing"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/httptest"
)
func TestCompression(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
var expectedReply = payload{Username: "Makis"}
testBody(t, e.GET("/"), expectedReply)
}
func TestCompressionAfterRecorder(t *testing.T) {
var expectedReply = payload{Username: "Makis"}
app := iris.New()
app.Use(func(ctx iris.Context) {
ctx.Record()
ctx.Next()
})
app.Use(iris.Compression)
app.Get("/", func(ctx iris.Context) {
ctx.JSON(expectedReply)
})
e := httptest.New(t, app)
testBody(t, e.GET("/"), expectedReply)
}
func TestCompressionBeforeRecorder(t *testing.T) {
var expectedReply = payload{Username: "Makis"}
app := iris.New()
app.Use(iris.Compression)
app.Use(func(ctx iris.Context) {
ctx.Record()
ctx.Next()
})
app.Get("/", func(ctx iris.Context) {
ctx.JSON(expectedReply)
})
e := httptest.New(t, app)
testBody(t, e.GET("/"), expectedReply)
}
func testBody(t *testing.T, req *httptest.Request, expectedReply interface{}) {
t.Helper()
body := req.WithHeader(context.AcceptEncodingHeaderKey, context.GZIP).Expect().
Status(httptest.StatusOK).
ContentEncoding(context.GZIP).
ContentType(context.ContentJSONHeaderValue).Body().Raw()
// Note that .Expect() consumes the response body
// and stores it to unexported "contents" field
// therefore, we retrieve it as string and put it to a new buffer.
r := strings.NewReader(body)
cr, err := context.NewCompressReader(r, context.GZIP)
if err != nil {
t.Fatal(err)
}
defer cr.Close()
var got payload
if err = json.NewDecoder(cr).Decode(&got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expectedReply, got) {
t.Fatalf("expected %#+v but got %#+v", expectedReply, got)
}
}
-283
View File
@@ -1,283 +0,0 @@
# Configuration
All configuration's values have default values, things will work as you expected with `iris.New()`.
Configuration is useless before listen functions, so it should be passed on `Application#Run/2` (second argument(s)).
Iris has a type named `Configurator` which is a `func(*iris.Application)`, any function
which completes this can be passed at `Application#Configure` and/or `Application#Run/2`.
`Application#ConfigurationReadOnly()` returns the configuration values.
`.Run` **by `Configuration` struct**
```go
package main
import (
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<b>Hello!</b>")
})
// [...]
// Good when you want to modify the whole configuration.
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.Configuration{
DisableStartupLog: false,
DisableInterruptHandler: false,
DisablePathCorrection: false,
EnablePathEscape: false,
FireMethodNotAllowed: false,
DisableBodyConsumptionOnUnmarshal: false,
DisableAutoFireStatusCode: false,
TimeFormat: "Mon, 02 Jan 2006 15:04:05 GMT",
Charset: "UTF-8",
}))
}
```
`.Run` **by options**
```go
package main
import (
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<b>Hello!</b>")
})
// [...]
// Good when you want to change some of the configuration's field.
// Prefix: "With", code editors will help you navigate through all
// configuration options without even a glitch to the documentation.
app.Run(iris.Addr(":8080"), iris.WithoutStartupLog, iris.WithCharset("UTF-8"))
// or before run:
// app.Configure(iris.WithoutStartupLog, iris.WithCharset("UTF-8"))
// app.Run(iris.Addr(":8080"))
}
```
`.Run` **by TOML config file**
```tml
DisablePathCorrection = false
EnablePathEscape = false
FireMethodNotAllowed = true
DisableBodyConsumptionOnUnmarshal = false
TimeFormat = "Mon, 01 Jan 2006 15:04:05 GMT"
Charset = "UTF-8"
[Other]
MyServerName = "iris"
```
```go
package main
import (
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<b>Hello!</b>")
})
// [...]
// Good when you have two configurations, one for development and a different one for production use.
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.TOML("./configs/iris.tml")))
}
```
`.Run` **by YAML config file**
```yml
DisablePathCorrection: false
EnablePathEscape: false
FireMethodNotAllowed: true
DisableBodyConsumptionOnUnmarshal: true
TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT
Charset: UTF-8
```
```go
package main
import (
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<b>Hello!</b>")
})
// [...]
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.YAML("./configs/iris.yml")))
}
```
## Builtin Configurators
```go
// WithoutServerError will cause to ignore the matched "errors"
// from the main application's `Run` function.
//
// Usage:
// err := app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
// will return `nil` if the server's error was `http/iris#ErrServerClosed`.
//
// See `Configuration#IgnoreServerErrors []string` too.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/http-listening/listen-addr/omit-server-errors
func WithoutServerError(errors ...error) Configurator
// WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
var WithoutStartupLog
// WithoutInterruptHandler disables the automatic graceful server shutdown
// when control/cmd+C pressed.
var WithoutInterruptHandler
// WithoutPathCorrection disables the PathCorrection setting.
//
// See `Configuration`.
var WithoutPathCorrection
// WithoutBodyConsumptionOnUnmarshal disables BodyConsumptionOnUnmarshal setting.
//
// See `Configuration`.
var WithoutBodyConsumptionOnUnmarshal
// WithoutAutoFireStatusCode disables the AutoFireStatusCode setting.
//
// See `Configuration`.
var WithoutAutoFireStatusCode
// WithPathEscape enanbles the PathEscape setting.
//
// See `Configuration`.
var WithPathEscape
// WithOptimizations can force the application to optimize for the best performance where is possible.
//
// See `Configuration`.
var WithOptimizations
// WithFireMethodNotAllowed enanbles the FireMethodNotAllowed setting.
//
// See `Configuration`.
var WithFireMethodNotAllowed
// WithTimeFormat sets the TimeFormat setting.
//
// See `Configuration`.
func WithTimeFormat(timeformat string) Configurator
// WithCharset sets the Charset setting.
//
// See `Configuration`.
func WithCharset(charset string) Configurator
// WithRemoteAddrHeader enables or adds a new or existing request header name
// that can be used to validate the client's real IP.
//
// Existing values are:
// "X-Real-Ip": false,
// "X-Forwarded-For": false,
// "CF-Connecting-IP": false
//
// Look `context.RemoteAddr()` for more.
func WithRemoteAddrHeader(headerName string) Configurator
// WithoutRemoteAddrHeader disables an existing request header name
// that can be used to validate the client's real IP.
//
// Existing values are:
// "X-Real-Ip": false,
// "X-Forwarded-For": false,
// "CF-Connecting-IP": false
//
// Look `context.RemoteAddr()` for more.
func WithoutRemoteAddrHeader(headerName string) Configurator
// WithOtherValue adds a value based on a key to the Other setting.
//
// See `Configuration`.
func WithOtherValue(key string, val interface{}) Configurator
```
## Custom Configurator
With the `Configurator` developers can modularize their applications with ease.
Example Code:
```go
// file counter/counter.go
package counter
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/core/host"
)
func Configurator(app *iris.Application) {
counterValue := 0
go func() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
counterValue++
}
app.ConfigureHost(func(h *host.Supervisor) { // <- HERE: IMPORTANT
h.RegisterOnShutdown(func() {
ticker.Stop()
})
})
}()
app.Get("/counter", func(ctx iris.Context) {
ctx.Writef("Counter value = %d", counterValue)
})
}
```
```go
// file: main.go
package main
import (
"counter"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Configure(counter.Configurator)
app.Run(iris.Addr(":8080"))
}
```
@@ -12,7 +12,7 @@ func main() {
// [...]
// Good when you want to modify the whole configuration.
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.Configuration{ // default configuration:
app.Listen(":8080", iris.WithConfiguration(iris.Configuration{ // default configuration:
DisableStartupLog: false,
DisableInterruptHandler: false,
DisablePathCorrection: false,
@@ -21,7 +21,7 @@ func main() {
DisableBodyConsumptionOnUnmarshal: false,
DisableAutoFireStatusCode: false,
TimeFormat: "Mon, 02 Jan 2006 15:04:05 GMT",
Charset: "UTF-8",
Charset: "utf-8",
}))
// or before Run:
@@ -3,7 +3,7 @@ EnablePathEscape = false
FireMethodNotAllowed = true
DisableBodyConsumptionOnUnmarshal = false
TimeFormat = "Mon, 01 Jan 2006 15:04:05 GMT"
Charset = "UTF-8"
Charset = "utf-8"
RemoteAddrHeaders = ["X-Real-Ip", "X-Forwarded-For", "CF-Connecting-IP"]
[Other]
MyServerName = "iris"
@@ -13,9 +13,9 @@ func main() {
// [...]
// Good when you have two configurations, one for development and a different one for production use.
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.TOML("./configs/iris.tml")))
app.Listen(":8080", iris.WithConfiguration(iris.TOML("./configs/iris.tml")))
// or before run:
// app.Configure(iris.WithConfiguration(iris.TOML("./configs/iris.tml")))
// app.Run(iris.Addr(":8080"))
// app.Listen(":8080")
}
@@ -3,4 +3,14 @@ EnablePathEscape: false
FireMethodNotAllowed: true
DisableBodyConsumptionOnUnmarshal: true
TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT
Charset: UTF-8
Charset: UTF-8
SSLProxyHeaders:
X-Forwarded-Proto: https
HostProxyHeaders:
X-Host: true
RemoteAddrHeaders:
- X-Real-Ip
- X-Forwarded-For
- CF-Connecting-IP
Other:
Addr: :8080
@@ -14,9 +14,11 @@ func main() {
// Good when you have two configurations, one for development and a different one for production use.
// If iris.YAML's input string argument is "~" then it loads the configuration from the home directory
// and can be shared between many iris instances.
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.YAML("./configs/iris.yml")))
cfg := iris.YAML("./configs/iris.yml")
addr := cfg.Other["Addr"].(string)
app.Listen(addr, iris.WithConfiguration(cfg))
// or before run:
// app.Configure(iris.WithConfiguration(iris.YAML("./configs/iris.yml")))
// app.Run(iris.Addr(":8080"))
// app.Listen(":8080")
}
@@ -14,8 +14,8 @@ func main() {
// Good when you share configuration between multiple iris instances.
// This configuration file lives in your $HOME/iris.yml for unix hosts
// or %HOMEDRIVE%+%HOMEPATH%/iris.yml for windows hosts, and you can modify it.
app.Run(iris.Addr(":8080"), iris.WithGlobalConfiguration)
app.Listen(":8080", iris.WithGlobalConfiguration)
// or before run:
// app.Configure(iris.WithGlobalConfiguration)
// app.Run(iris.Addr(":8080"))
// app.Listen(":8080")
}
+3 -3
View File
@@ -15,9 +15,9 @@ func main() {
// Prefix: "With", code editors will help you navigate through all
// configuration options without even a glitch to the documentation.
app.Run(iris.Addr(":8080"), iris.WithoutStartupLog, iris.WithCharset("UTF-8"))
app.Listen(":8080", iris.WithoutStartupLog, iris.WithCharset("utf-8"))
// or before run:
// app.Configure(iris.WithoutStartupLog, iris.WithCharset("UTF-8"))
// app.Run(iris.Addr(":8080"))
// app.Configure(iris.WithoutStartupLog, iris.WithCharset("utf-8"))
// app.Listen(":8080")
}

Some files were not shown because too many files have changed in this diff Show More