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:
+306
-364
@@ -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)
|
||||
|
||||
@@ -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 模式
|
||||
|
||||

|
||||
|
||||
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) 这个项目会一直跟进最新趋势。
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Swagger 2.0
|
||||
|
||||
Visit https://github.com/iris-contrib/swagger instead.
|
||||
@@ -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
|
||||
)
|
||||
@@ -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"))
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 © 2022</i>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
Generated
+182
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"},
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
+2
-2
@@ -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}}
|
||||
+19
-11
@@ -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}}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
Generated
+180
@@ -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=
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
+1
-1
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
+6
-3
@@ -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 |
@@ -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
|
||||
)
|
||||
Generated
+235
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -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 |
+4
-1
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+4
-4
@@ -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>
|
||||
+22
-22
@@ -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>© 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>© 2017 - {{.AppOwner}}</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Vendored
-80
@@ -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
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
<div>
|
||||
{{.Message}}
|
||||
<div>
|
||||
{{.Message}}
|
||||
</div>
|
||||
+10
-10
@@ -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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
*/
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user