1
0
mirror of https://github.com/kataras/iris.git synced 2026-08-07 04:59:01 +00:00

last version of v12

This commit is contained in:
Gerasimos (Makis) Maropoulos
2024-04-25 01:43:13 +03:00
1443 changed files with 85668 additions and 101900 deletions
@@ -0,0 +1,121 @@
// Package main shows how to use a dependency to check if a user is logged in
// using a special custom Go type `Authenticated`, which when,
// present on a controller's method or a field then
// it limits the visibility to "authenticated" users only.
//
// The same result could be done through a middleware as well, however using a static type
// any person reads your code can see that an "X" controller or method
// should only be used by "authenticated" users.
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"github.com/kataras/iris/v12/sessions"
)
func main() {
app := newApp()
// app.UseRouter(iris.Compression)
app.Logger().SetLevel("debug")
// Open a client, e.g. Postman and visit the below endpoints.
// GET: http://localhost:8080/user (UnauthenticatedUserController.Get)
// POST: http://localhost:8080/user/login (UnauthenticatedUserController.PostLogin)
// GET: http://localhost:8080/user (UserController.Get)
// POST: http://localhost:8080/user/logout (UserController.PostLogout)
app.Listen(":8080")
}
func newApp() *iris.Application {
app := iris.New()
sess := sessions.New(sessions.Config{
Cookie: "myapp_session_id",
AllowReclaim: true,
})
app.Use(sess.Handler())
userRouter := app.Party("/user")
{
// Use that in order to be able to register a route twice,
// last one will be executed if the previous route's handler(s) stopped and the response can be reset-ed.
// See core/router/route_register_rule_test.go#TestRegisterRuleOverlap.
userRouter.SetRegisterRule(iris.RouteOverlap)
// Initialize a new MVC application on top of the "userRouter".
userApp := mvc.New(userRouter)
// Register Dependencies.
userApp.Register(authDependency)
// Register Controllers.
userApp.Handle(new(UserController))
userApp.Handle(new(UnauthenticatedUserController))
}
return app
}
// Authenticated is a custom type used as "annotation" for resources that requires authentication,
// its value should be the logged user ID.
type Authenticated uint64
func authDependency(ctx iris.Context, session *sessions.Session) Authenticated {
userID := session.GetUint64Default("user_id", 0)
if userID == 0 {
// If execution was stopped
// any controller's method will not be executed at all.
//
// Note that, the below will not fire the error to the user:
// ctx.StopWithStatus(iris.StatusUnauthorized)
// because of the imaginary:
// UnauthenticatedUserController.Get() (string, int) {
// return "...", iris.StatusOK
// }
//
// OR
// If you don't want to set a status code at all:
ctx.StopExecution()
return 0
}
return Authenticated(userID)
}
// UnauthenticatedUserController serves the "public" Unauthorized User API.
type UnauthenticatedUserController struct{}
// Get registers a route that will be executed when authentication is not passed
// (see UserController.Get) too.
func (c *UnauthenticatedUserController) Get() string {
return "custom action to redirect on authentication page"
}
// PostLogin serves
// POST: /user/login
func (c *UnauthenticatedUserController) PostLogin(session *sessions.Session) mvc.Response {
session.Set("user_id", 1)
// Redirect (you can still use the Context.Redirect if you want so).
return mvc.Response{
Path: "/user",
Code: iris.StatusFound,
}
}
// UserController serves the "public" User API.
type UserController struct {
CurrentUserID Authenticated
}
// Get returns a message for the sake of the example.
// GET: /user
func (c *UserController) Get() string {
return `UserController.Get: The Authenticated type
can be used to secure a controller's method too.`
}
// PostLogout serves
// POST: /user/logout
func (c *UserController) PostLogout(ctx iris.Context) {
sessions.Get(ctx).Man.Destroy(ctx)
}
@@ -0,0 +1,24 @@
package main
import (
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestMVCOverlapping(t *testing.T) {
app := newApp()
e := httptest.New(t, app, httptest.URL("http://example.com"))
// unauthenticated.
e.GET("/user").Expect().Status(httptest.StatusOK).Body().IsEqual("custom action to redirect on authentication page")
// login.
e.POST("/user/login").Expect().Status(httptest.StatusOK)
// authenticated.
e.GET("/user").Expect().Status(httptest.StatusOK).Body().IsEqual(`UserController.Get: The Authenticated type
can be used to secure a controller's method too.`)
// logout.
e.POST("/user/logout").Expect().Status(httptest.StatusOK)
// unauthenticated.
e.GET("/user").Expect().Status(httptest.StatusOK).Body().IsEqual("custom action to redirect on authentication page")
}
+27 -11
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/accesslog"
"github.com/kataras/iris/v12/middleware/recover"
"github.com/kataras/iris/v12/sessions"
@@ -12,19 +13,32 @@ import (
func main() {
app := iris.New()
app.Use(recover.New())
app.Logger().SetLevel("debug")
mvc.Configure(app.Party("/basic"), basicMVC)
app.Run(iris.Addr(":8080"))
basic := app.Party("/basic")
{
// Register middlewares to run under the /basic path prefix.
ac := accesslog.File("./basic_access.log")
defer ac.Close()
basic.UseRouter(ac.Handler)
basic.UseRouter(recover.New())
mvc.Configure(basic, basicMVC)
}
app.Listen(":8080")
}
func basicMVC(app *mvc.Application) {
// You can use normal middlewares at MVC apps of course.
app.Router.Use(func(ctx iris.Context) {
ctx.Application().Logger().Infof("Path: %s", ctx.Path())
ctx.Next()
})
// Disable verbose logging of controllers for this and its children mvc apps
// when the log level is "debug":
app.SetControllersNoLog(true)
// You can still register middlewares at MVC apps of course.
// The app.Router returns the Party that this MVC
// was registered on.
// app.Router.UseRouter/Use/....
// Register dependencies which will be binding to the controller(s),
// can be either a function which accepts an iris.Context and returns a single value (dynamic binding)
@@ -32,6 +46,7 @@ func basicMVC(app *mvc.Application) {
app.Register(
sessions.New(sessions.Config{}).Start,
&prefixedLogger{prefix: "DEV"},
accesslog.GetFields, // Set custom fields through a controller or controller's methods.
)
// GET: http://localhost:8080/basic
@@ -68,9 +83,9 @@ func (s *prefixedLogger) Log(msg string) {
}
type basicController struct {
Logger LoggerService
Session *sessions.Session
Logger LoggerService // the static logger service attached to this app.
Session *sessions.Session // current HTTP session.
LogFields *accesslog.Fields // accesslog middleware custom fields.
}
func (c *basicController) BeforeActivation(b mvc.BeforeActivation) {
@@ -85,6 +100,7 @@ func (c *basicController) AfterActivation(a mvc.AfterActivation) {
func (c *basicController) Get() string {
count := c.Session.Increment("count", 1)
c.LogFields.Set("count", count)
body := fmt.Sprintf("Hello from basicController\nTotal visits from you: %d", count)
c.Logger.Log(body)
+41
View File
@@ -0,0 +1,41 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
usersRouter := app.Party("/users")
mvc.New(usersRouter).Handle(new(myController))
// Same as:
// usersRouter.Get("/{p:path}", func(ctx iris.Context) {
// wildcardPathParameter := ctx.Params().Get("p")
// ctx.JSON(response{
// Message: "The path parameter is: " + wildcardPathParameter,
// })
// })
/*
curl --location --request GET 'http://localhost:8080/users/path_segment_1/path_segment_2'
Expected Output:
{
"message": "The wildcard is: path_segment_1/path_segment_2"
}
*/
app.Listen(":8080")
}
type myController struct{}
type response struct {
Message string `json:"message"`
}
func (c *myController) GetByWildcard(wildcardPathParameter string) response {
return response{
Message: "The path parameter is: " + wildcardPathParameter,
}
}
@@ -0,0 +1,75 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
m := mvc.New(app)
m.Handle(new(controller))
app.Listen(":8080")
}
type errorResponse struct {
Code int
Message string
}
/*
// Note: if a struct implements the standard go error, so it's an error
// and its Error() is not empty, then its text will be rendered instead,
// override any Dispatch method.
func (e errorResponse) Error() string {
return e.Message
}
*/
// implements mvc.Result.
func (e errorResponse) Dispatch(ctx iris.Context) {
// If u want to use mvc.Result on any method without an output return value
// go for it:
//
view := mvc.View{Code: e.Code, Data: e} // use Code and Message as the template data.
switch e.Code {
case iris.StatusNotFound:
view.Name = "404"
default:
view.Name = "500"
}
view.Dispatch(ctx)
// Otherwise use ctx methods:
//
// ctx.StatusCode(e.Code)
// switch e.Code {
// case iris.StatusNotFound:
// // use Code and Message as the template data.
// if err := ctx.View("404.html", e)
// default:
// if err := ctx.View("500.html", e)
// }
}
type controller struct{}
type user struct {
ID uint64 `json:"id"`
}
func (c *controller) GetBy(userid uint64) mvc.Result {
if userid != 1 {
return errorResponse{
Code: iris.StatusNotFound,
Message: "User Not Found",
}
}
return mvc.Response{
Object: user{ID: userid},
}
}
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Error Page</title>
</head>
<body>
<h2>{{.Code}}</h2>
<h3>{{.Message}}</h3>
</body>
</html>
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Error Page</title>
</head>
<body>
<h2>{{.Code}}</h2>
<h3>{{.Message}}</h3>
</body>
</html>
@@ -0,0 +1,60 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
// Hijack each output value of a method (can be used per-party too).
app.ConfigureContainer().
UseResultHandler(func(next iris.ResultHandler) iris.ResultHandler {
return func(ctx iris.Context, v interface{}) error {
switch val := v.(type) {
case errorResponse:
return next(ctx, errorView(val))
default:
return next(ctx, v)
}
}
})
m := mvc.New(app)
m.Handle(new(controller))
app.Listen(":8080")
}
func errorView(e errorResponse) mvc.Result {
switch e.Code {
case iris.StatusNotFound:
return mvc.View{Code: e.Code, Name: "404.html", Data: e}
default:
return mvc.View{Code: e.Code, Name: "500.html", Data: e}
}
}
type errorResponse struct {
Code int
Message string
}
type controller struct{}
type user struct {
ID uint64 `json:"id"`
}
func (c *controller) GetBy(userid uint64) interface{} {
if userid != 1 {
return errorResponse{
Code: iris.StatusNotFound,
Message: "User Not Found",
}
}
return user{ID: userid}
}
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Error Page</title>
</head>
<body>
<h2>{{.Code}}</h2>
<h3>{{.Message}}</h3>
</body>
</html>
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Error Page</title>
</head>
<body>
<h2>{{.Code}}</h2>
<h3>{{.Message}}</h3>
</body>
</html>
+74
View File
@@ -0,0 +1,74 @@
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := newApp()
app.Logger().SetLevel("debug")
app.Listen(":8080")
}
func newApp() *iris.Application {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
m := mvc.New(app)
m.Handle(new(controller))
return app
}
type controller struct{}
func (c *controller) Get() string {
return "Hello!"
}
func (c *controller) GetError() mvc.Result {
return mvc.View{
// Map to mvc.Code and mvc.Err respectfully on HandleHTTPError method.
Code: iris.StatusBadRequest,
Err: fmt.Errorf("custom error"),
}
}
// The input parameter of mvc.Code is optional but a good practise to follow.
// You could register a Context and get its error code through ctx.GetStatusCode().
//
// This can accept dependencies and output values like any other Controller Method,
// however be careful if your registered dependencies depend only on successful(200...) requests.
//
// Also note that, if you register more than one controller.HandleHTTPError
// in the same Party, you need to use the RouteOverlap feature as shown
// in the "authenticated-controller" example, and a dependency on
// a controller's field (or method's input argument) is required
// to select which, between those two controllers, is responsible
// to handle http errors.
func (c *controller) HandleHTTPError(statusCode mvc.Code, err mvc.Err) mvc.View {
if err != nil {
// Do something with that error,
// e.g. view.Data = MyPageData{Message: err.Error()}
}
code := int(statusCode) // cast it to int.
view := mvc.View{
Code: code,
Name: "unexpected-error.html",
}
switch code {
case 404:
view.Name = "404.html"
// [...]
case 500:
view.Name = "500.html"
}
return view
}
@@ -0,0 +1,20 @@
package main
import (
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestControllerHandleHTTPError(t *testing.T) {
const (
expectedIndex = "Hello!"
expectedNotFound = "<h3>Not Found Custom Page Rendered through Controller's HandleHTTPError</h3>"
)
app := newApp()
e := httptest.New(t, app)
e.GET("/").Expect().Status(httptest.StatusOK).Body().IsEqual(expectedIndex)
e.GET("/a_notefound").Expect().Status(httptest.StatusNotFound).ContentType("text/html").Body().IsEqual(expectedNotFound)
}
@@ -0,0 +1 @@
<h3>Not Found Custom Page Rendered through Controller's HandleHTTPError</h3>
@@ -0,0 +1 @@
<h3>Internal Server Err</h3>
@@ -0,0 +1 @@
<h1>Unexpected Error</h1>
@@ -0,0 +1,101 @@
package main
import (
"fmt"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
m := mvc.New(app)
m.Handle(new(controller))
app.Listen(":8080")
}
type controller struct{}
// Generic response type for JSON results.
type response struct {
ID uint64 `json:"id,omitempty"`
Data interface{} `json:"data,omitempty"` // {data: result } on fetch actions.
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}
func (r *response) Preflight(ctx iris.Context) error {
if r.ID > 0 {
r.Timestamp = time.Now().Unix()
}
if code := r.Code; code > 0 {
// You can call ctx.View or mvc.View{...}.Dispatch
// to render HTML on Code != 200
// but in order to not proceed with the response resulting
// as JSON you MUST return the iris.ErrStopExecution error.
// Example:
if code != 200 {
mvc.View{
/* calls the ctx.StatusCode */
Code: code,
/* use any r.Data as the template data
OR the whole "response" as its data. */
Data: r,
/* automatically pick the template per error (just for the sake of the example) */
Name: fmt.Sprintf("%d", code),
}.Dispatch(ctx)
return iris.ErrStopExecution
}
ctx.StatusCode(r.Code)
}
return nil
}
type user struct {
ID uint64 `json:"id"`
}
func (c *controller) GetBy(userid uint64) *response {
if userid != 1 {
return &response{
Code: iris.StatusNotFound,
Message: "User Not Found",
}
}
return &response{
ID: userid,
Data: user{ID: userid},
}
}
/*
You can use that `response` structure on non-mvc applications too, using handlers:
c := app.ConfigureContainer()
c.Get("/{id:uint64}", getUserByID)
func getUserByID(id uint64) response {
if userid != 1 {
return response{
Code: iris.StatusNotFound,
Message: "User Not Found",
}
}
return response{
ID: userid,
Data: user{ID: userid},
}
}
*/
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Error Page</title>
</head>
<body>
<h2>{{.Code}}</h2>
<h3>{{.Message}}</h3>
</body>
</html>
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Error Page</title>
</head>
<body>
<h2>{{.Code}}</h2>
<h3>{{.Message}}</h3>
</body>
</html>
+1 -1
View File
@@ -23,7 +23,7 @@ func main() {
mvcApp.Handle(new(myController))
// http://localhost:8080
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
type myController struct {
@@ -0,0 +1,49 @@
package main
import (
"context"
"time"
pb "grpcexample/helloworld"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
// Set up a connection to the server.
cred, err := credentials.NewClientTLSFromFile("../server.crt", "localhost")
if err != nil {
panic(err)
}
conn, err := grpc.Dial("localhost:443", grpc.WithTransportCredentials(cred), grpc.WithBlock())
defer conn.Close()
client := pb.NewGreeterBidirectionalStreamClient(conn)
stream, err := client.SayHello(context.Background())
if err != nil {
panic(err)
}
waitCh := make(chan struct{})
// Implement the send channel.
// As an exercise you can implement the read channel one (reading from server, see the server/main.go).
go func() {
for {
println("Sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
println("Sending a <test> msg...")
msg := &pb.HelloRequest{Name: "test"}
err = stream.Send(msg)
if err != nil {
panic("stream.Send: " + err.Error())
}
}
}()
<-waitCh
stream.CloseSend()
}
@@ -0,0 +1,66 @@
module grpcexample
go 1.22
require (
github.com/golang/protobuf v1.5.4
github.com/kataras/iris/v12 v12.2.11-0.20240424221303-df7737ed577a
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.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/blang/semver/v4 v4.0.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // 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/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/websocket v1.5.1 // 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/mediocregopher/radix/v3 v3.8.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // 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/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
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+206
View File
@@ -0,0 +1,206 @@
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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
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/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/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/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.20240424221303-df7737ed577a h1:uaDFlkh2LzTPtoOk/1e6jy/ChoM5/FxTgTiEehlJuD0=
github.com/kataras/iris/v12 v12.2.11-0.20240424221303-df7737ed577a/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.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/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/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-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.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.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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
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,39 @@
syntax = "proto3";
package helloworld;
option go_package = ".;helloworld";
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The greeting service definition. (Server-side streaming RPC)
service GreeterServerSideSStream {
// Sends a greeting
rpc SayHello (HelloRequest) returns (stream HelloReply) {}
}
// The greeting service definition. (Client-side streaming RPC)
service GreeterClientSideStream {
// Sends a greeting
rpc SayHello (stream HelloRequest) returns (HelloReply) {}
}
// The greeting service definition. (Bidirectional streaming RPC)
service GreeterBidirectionalStream {
// Sends a greeting
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}
@@ -0,0 +1,242 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.12.3
// source: helloworld.proto
package helloworld
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// The request message containing the user's name.
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_helloworld_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_helloworld_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
// The response message containing the greetings
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_helloworld_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_helloworld_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_helloworld_proto protoreflect.FileDescriptor
var file_helloworld_proto_rawDesc = []byte{
0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x22,
0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72,
0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48,
0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65,
0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x5c, 0x0a, 0x18, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x53, 0x53, 0x74, 0x72, 0x65, 0x61,
0x6d, 0x12, 0x40, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e,
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x00, 0x30, 0x01, 0x32, 0x5b, 0x0a, 0x17, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x64, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x40,
0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c,
0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c,
0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01,
0x32, 0x60, 0x0a, 0x1a, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x42, 0x69, 0x64, 0x69, 0x72,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x42,
0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c,
0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c,
0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01,
0x30, 0x01, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x3b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_helloworld_proto_rawDescOnce sync.Once
file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc
)
func file_helloworld_proto_rawDescGZIP() []byte {
file_helloworld_proto_rawDescOnce.Do(func() {
file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData)
})
return file_helloworld_proto_rawDescData
}
var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_helloworld_proto_goTypes = []interface{}{
(*HelloRequest)(nil), // 0: helloworld.HelloRequest
(*HelloReply)(nil), // 1: helloworld.HelloReply
}
var file_helloworld_proto_depIdxs = []int32{
0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
0, // 1: helloworld.GreeterServerSideSStream.SayHello:input_type -> helloworld.HelloRequest
0, // 2: helloworld.GreeterClientSideStream.SayHello:input_type -> helloworld.HelloRequest
0, // 3: helloworld.GreeterBidirectionalStream.SayHello:input_type -> helloworld.HelloRequest
1, // 4: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
1, // 5: helloworld.GreeterServerSideSStream.SayHello:output_type -> helloworld.HelloReply
1, // 6: helloworld.GreeterClientSideStream.SayHello:output_type -> helloworld.HelloReply
1, // 7: helloworld.GreeterBidirectionalStream.SayHello:output_type -> helloworld.HelloReply
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_helloworld_proto_init() }
func file_helloworld_proto_init() {
if File_helloworld_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_helloworld_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 4,
},
GoTypes: file_helloworld_proto_goTypes,
DependencyIndexes: file_helloworld_proto_depIdxs,
MessageInfos: file_helloworld_proto_msgTypes,
}.Build()
File_helloworld_proto = out.File
file_helloworld_proto_rawDesc = nil
file_helloworld_proto_goTypes = nil
file_helloworld_proto_depIdxs = nil
}
@@ -0,0 +1,450 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package helloworld
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion7
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type greeterClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer
// for forward compatibility
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServer will
// result in compilation errors.
type UnsafeGreeterServer interface {
mustEmbedUnimplementedGreeterServer()
}
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/helloworld.Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "helloworld.proto",
}
// GreeterServerSideSStreamClient is the client API for GreeterServerSideSStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterServerSideSStreamClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (GreeterServerSideSStream_SayHelloClient, error)
}
type greeterServerSideSStreamClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterServerSideSStreamClient(cc grpc.ClientConnInterface) GreeterServerSideSStreamClient {
return &greeterServerSideSStreamClient{cc}
}
func (c *greeterServerSideSStreamClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (GreeterServerSideSStream_SayHelloClient, error) {
stream, err := c.cc.NewStream(ctx, &_GreeterServerSideSStream_serviceDesc.Streams[0], "/helloworld.GreeterServerSideSStream/SayHello", opts...)
if err != nil {
return nil, err
}
x := &greeterServerSideSStreamSayHelloClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type GreeterServerSideSStream_SayHelloClient interface {
Recv() (*HelloReply, error)
grpc.ClientStream
}
type greeterServerSideSStreamSayHelloClient struct {
grpc.ClientStream
}
func (x *greeterServerSideSStreamSayHelloClient) Recv() (*HelloReply, error) {
m := new(HelloReply)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GreeterServerSideSStreamServer is the server API for GreeterServerSideSStream service.
// All implementations must embed UnimplementedGreeterServerSideSStreamServer
// for forward compatibility
type GreeterServerSideSStreamServer interface {
// Sends a greeting
SayHello(*HelloRequest, GreeterServerSideSStream_SayHelloServer) error
mustEmbedUnimplementedGreeterServerSideSStreamServer()
}
// UnimplementedGreeterServerSideSStreamServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServerSideSStreamServer struct {
}
func (UnimplementedGreeterServerSideSStreamServer) SayHello(*HelloRequest, GreeterServerSideSStream_SayHelloServer) error {
return status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServerSideSStreamServer) mustEmbedUnimplementedGreeterServerSideSStreamServer() {
}
// UnsafeGreeterServerSideSStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServerSideSStreamServer will
// result in compilation errors.
type UnsafeGreeterServerSideSStreamServer interface {
mustEmbedUnimplementedGreeterServerSideSStreamServer()
}
func RegisterGreeterServerSideSStreamServer(s grpc.ServiceRegistrar, srv GreeterServerSideSStreamServer) {
s.RegisterService(&_GreeterServerSideSStream_serviceDesc, srv)
}
func _GreeterServerSideSStream_SayHello_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(HelloRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(GreeterServerSideSStreamServer).SayHello(m, &greeterServerSideSStreamSayHelloServer{stream})
}
type GreeterServerSideSStream_SayHelloServer interface {
Send(*HelloReply) error
grpc.ServerStream
}
type greeterServerSideSStreamSayHelloServer struct {
grpc.ServerStream
}
func (x *greeterServerSideSStreamSayHelloServer) Send(m *HelloReply) error {
return x.ServerStream.SendMsg(m)
}
var _GreeterServerSideSStream_serviceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.GreeterServerSideSStream",
HandlerType: (*GreeterServerSideSStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "SayHello",
Handler: _GreeterServerSideSStream_SayHello_Handler,
ServerStreams: true,
},
},
Metadata: "helloworld.proto",
}
// GreeterClientSideStreamClient is the client API for GreeterClientSideStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClientSideStreamClient interface {
// Sends a greeting
SayHello(ctx context.Context, opts ...grpc.CallOption) (GreeterClientSideStream_SayHelloClient, error)
}
type greeterClientSideStreamClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterClientSideStreamClient(cc grpc.ClientConnInterface) GreeterClientSideStreamClient {
return &greeterClientSideStreamClient{cc}
}
func (c *greeterClientSideStreamClient) SayHello(ctx context.Context, opts ...grpc.CallOption) (GreeterClientSideStream_SayHelloClient, error) {
stream, err := c.cc.NewStream(ctx, &_GreeterClientSideStream_serviceDesc.Streams[0], "/helloworld.GreeterClientSideStream/SayHello", opts...)
if err != nil {
return nil, err
}
x := &greeterClientSideStreamSayHelloClient{stream}
return x, nil
}
type GreeterClientSideStream_SayHelloClient interface {
Send(*HelloRequest) error
CloseAndRecv() (*HelloReply, error)
grpc.ClientStream
}
type greeterClientSideStreamSayHelloClient struct {
grpc.ClientStream
}
func (x *greeterClientSideStreamSayHelloClient) Send(m *HelloRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *greeterClientSideStreamSayHelloClient) CloseAndRecv() (*HelloReply, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(HelloReply)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GreeterClientSideStreamServer is the server API for GreeterClientSideStream service.
// All implementations must embed UnimplementedGreeterClientSideStreamServer
// for forward compatibility
type GreeterClientSideStreamServer interface {
// Sends a greeting
SayHello(GreeterClientSideStream_SayHelloServer) error
mustEmbedUnimplementedGreeterClientSideStreamServer()
}
// UnimplementedGreeterClientSideStreamServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterClientSideStreamServer struct {
}
func (UnimplementedGreeterClientSideStreamServer) SayHello(GreeterClientSideStream_SayHelloServer) error {
return status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterClientSideStreamServer) mustEmbedUnimplementedGreeterClientSideStreamServer() {
}
// UnsafeGreeterClientSideStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterClientSideStreamServer will
// result in compilation errors.
type UnsafeGreeterClientSideStreamServer interface {
mustEmbedUnimplementedGreeterClientSideStreamServer()
}
func RegisterGreeterClientSideStreamServer(s grpc.ServiceRegistrar, srv GreeterClientSideStreamServer) {
s.RegisterService(&_GreeterClientSideStream_serviceDesc, srv)
}
func _GreeterClientSideStream_SayHello_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GreeterClientSideStreamServer).SayHello(&greeterClientSideStreamSayHelloServer{stream})
}
type GreeterClientSideStream_SayHelloServer interface {
SendAndClose(*HelloReply) error
Recv() (*HelloRequest, error)
grpc.ServerStream
}
type greeterClientSideStreamSayHelloServer struct {
grpc.ServerStream
}
func (x *greeterClientSideStreamSayHelloServer) SendAndClose(m *HelloReply) error {
return x.ServerStream.SendMsg(m)
}
func (x *greeterClientSideStreamSayHelloServer) Recv() (*HelloRequest, error) {
m := new(HelloRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _GreeterClientSideStream_serviceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.GreeterClientSideStream",
HandlerType: (*GreeterClientSideStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "SayHello",
Handler: _GreeterClientSideStream_SayHello_Handler,
ClientStreams: true,
},
},
Metadata: "helloworld.proto",
}
// GreeterBidirectionalStreamClient is the client API for GreeterBidirectionalStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterBidirectionalStreamClient interface {
// Sends a greeting
SayHello(ctx context.Context, opts ...grpc.CallOption) (GreeterBidirectionalStream_SayHelloClient, error)
}
type greeterBidirectionalStreamClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterBidirectionalStreamClient(cc grpc.ClientConnInterface) GreeterBidirectionalStreamClient {
return &greeterBidirectionalStreamClient{cc}
}
func (c *greeterBidirectionalStreamClient) SayHello(ctx context.Context, opts ...grpc.CallOption) (GreeterBidirectionalStream_SayHelloClient, error) {
stream, err := c.cc.NewStream(ctx, &_GreeterBidirectionalStream_serviceDesc.Streams[0], "/helloworld.GreeterBidirectionalStream/SayHello", opts...)
if err != nil {
return nil, err
}
x := &greeterBidirectionalStreamSayHelloClient{stream}
return x, nil
}
type GreeterBidirectionalStream_SayHelloClient interface {
Send(*HelloRequest) error
Recv() (*HelloReply, error)
grpc.ClientStream
}
type greeterBidirectionalStreamSayHelloClient struct {
grpc.ClientStream
}
func (x *greeterBidirectionalStreamSayHelloClient) Send(m *HelloRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *greeterBidirectionalStreamSayHelloClient) Recv() (*HelloReply, error) {
m := new(HelloReply)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GreeterBidirectionalStreamServer is the server API for GreeterBidirectionalStream service.
// All implementations must embed UnimplementedGreeterBidirectionalStreamServer
// for forward compatibility
type GreeterBidirectionalStreamServer interface {
// Sends a greeting
SayHello(GreeterBidirectionalStream_SayHelloServer) error
mustEmbedUnimplementedGreeterBidirectionalStreamServer()
}
// UnimplementedGreeterBidirectionalStreamServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterBidirectionalStreamServer struct {
}
func (UnimplementedGreeterBidirectionalStreamServer) SayHello(GreeterBidirectionalStream_SayHelloServer) error {
return status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterBidirectionalStreamServer) mustEmbedUnimplementedGreeterBidirectionalStreamServer() {
}
// UnsafeGreeterBidirectionalStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterBidirectionalStreamServer will
// result in compilation errors.
type UnsafeGreeterBidirectionalStreamServer interface {
mustEmbedUnimplementedGreeterBidirectionalStreamServer()
}
func RegisterGreeterBidirectionalStreamServer(s grpc.ServiceRegistrar, srv GreeterBidirectionalStreamServer) {
s.RegisterService(&_GreeterBidirectionalStream_serviceDesc, srv)
}
func _GreeterBidirectionalStream_SayHello_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GreeterBidirectionalStreamServer).SayHello(&greeterBidirectionalStreamSayHelloServer{stream})
}
type GreeterBidirectionalStream_SayHelloServer interface {
Send(*HelloReply) error
Recv() (*HelloRequest, error)
grpc.ServerStream
}
type greeterBidirectionalStreamSayHelloServer struct {
grpc.ServerStream
}
func (x *greeterBidirectionalStreamSayHelloServer) Send(m *HelloReply) error {
return x.ServerStream.SendMsg(m)
}
func (x *greeterBidirectionalStreamSayHelloServer) Recv() (*HelloRequest, error) {
m := new(HelloRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _GreeterBidirectionalStream_serviceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.GreeterBidirectionalStream",
HandlerType: (*GreeterBidirectionalStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "SayHello",
Handler: _GreeterBidirectionalStream_SayHello_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "helloworld.proto",
}
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICwzCCAaugAwIBAgIJANAsSlhuqzW/MA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
BAMTCWxvY2FsaG9zdDAeFw0yMDEwMzEwMjM4MTNaFw0zMDEwMjkwMjM4MTNaMBQx
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKY8mFh0nvXa8V6Vx5TVVbfq9Zx81hiIKeXN977JaQGrPfCMRU61smaDJAzR
MBEFjHT37Zj5sZr6qvQmt3Dfst8bNcFBsZ7s3bBknuevD3gPJBy1r5KFwSkPZekH
T+0DF0Drq1/iIZQXE13udWzDnzxABUBOmIzEmn+rfp2j3xJJGLoO1O4Wu2YpXhtj
yNJfswoztGGie6u61mAN7Rw4JQUkDKa3hAKkYtfOoGHZBs6Y0RIKZQikjxlVpMMc
fCvUMHkY51GP8ycgZ8+0n/cjm13KkqfvoyRZ1rQqCd24YNIEl18t0FClpUAHkxD/
aUPCqV3bqpFBbDPQcYybIGjfNIMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MA0GCSqGSIb3DQEBBQUAA4IBAQAd6F6tG/jcUu9pm/5XeH1Iof5PB9tFuBvv
w9HFjBI5S7ItnTepneoOP26lx2iOE0GwkvrhseZcHkGDZPdncBn12PuCHUbcO2Ux
bEDyDMsAHooKLvmAfAWeiOmeJAdFl8DOTgD/lTHK1n/mEwCUOtGozmfm/Y0pnfc7
nMgbQ+yNsj1X7HVJHRUdOfOCGjiWofo6v7V7YyZJC2jpn3K7O4126jic2ibWYKQl
fLLjgS0N5Wcun117e1mRYV41jtPOkeAvxJbJGf0IP7Os0VfG1UQhodwjkV4s8GcG
naB5qC4Y508cB7Lq58kbGgZ0nfMCcpnrKobliIZVEpH7aa0Po1lm
-----END CERTIFICATE-----
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEApjyYWHSe9drxXpXHlNVVt+r1nHzWGIgp5c33vslpAas98IxF
TrWyZoMkDNEwEQWMdPftmPmxmvqq9Ca3cN+y3xs1wUGxnuzdsGSe568PeA8kHLWv
koXBKQ9l6QdP7QMXQOurX+IhlBcTXe51bMOfPEAFQE6YjMSaf6t+naPfEkkYug7U
7ha7ZileG2PI0l+zCjO0YaJ7q7rWYA3tHDglBSQMpreEAqRi186gYdkGzpjREgpl
CKSPGVWkwxx8K9QweRjnUY/zJyBnz7Sf9yObXcqSp++jJFnWtCoJ3bhg0gSXXy3Q
UKWlQAeTEP9pQ8KpXduqkUFsM9BxjJsgaN80gwIDAQABAoIBACDVNwHBhuPoKmQU
ESdEO3nn3jraLS8LNbs9wwDbpvG9cK5iBg5VtLaqkCQ37NZv0h4IGdVs+7cwazNt
si2JATsvlJ5m6z4IaoC8XuZDnTqJQwiomdTGti/16prr5s1ZHu6jnWWCtD8bj6et
wWOJ/5lWy7K300l6S0mMBaX9B8IEfGR+YPGF/bfsIlf+9iuVulBjH5NPIozwl4c9
VUafESEZX0DM/KDHqFvvWdMaw9e+3QNi+l0t3VJz6JuEcUfRztT6lbzYbRGBYqKr
DmUNxYUThd717NJklxr9D+GMm5FnfrzY8vcJ16SILBQyDthsz1s6gdT0yeRFgjJf
afDObAECgYEA1WQdayAeqT7sE0T0jHAWHdVhRd9sGUTZ4LjVuGbMjbJWJVUPn/fz
ahBQh77xutmNCbDSUD0srT+zHtRRJUVhl+ojFS1CMyyL/UAfH24gDwUnXIyq/DPA
heXKD8NwGoTPbHKjdmy+89qVbes3oxBc5tDKpzAuBoPY4iBMVwfqSmECgYEAx24Y
i1lhxuG+g2YSjiIhmD/ytZSXamP3+KmnqWWXbEjJd2oYmCPr9yBCfdk9jCUXZJsg
tYsRduMajTjmTNkYmGyZNVtmjXfrq/vaJgf7bZhhLOFxWoNmLlKQfmbuJW5fkV1b
hY8oid+YbBob4xseFzKvm5j0aKwufi3v7XLUEWMCgYEAs7rqKFNaX9SWhDhc/Xhe
uGwDzRU8eCAMnwEvSWyUN3iQpEr7qRHvXFM3cM47zdP0vcfHrDuKSLXRSVMssYa5
h3l2aRzAmFeZ5Qk/7XoU2HHP0FzOmzN/oYeE5DgJUNyx1DbORS2cu8lMeNNX/ikH
BoWvWpfy/BvK7dKkWd1Z0aECgYEAm1+QKcjqX5ty5UZ6AFhhGhAAVS2+Rfo6sHXl
FRn8PjX7GFkFbkrWRUPR6eB9jhk7v3sIocgGRDytbAc/jfG5ss8xEhvyqxcZ+nUO
QYEIhxsn4mKGAMHMsxxKTOB+e5UhScyVSFn/eGNGijpRLb/r0qD/pdcl3AMBefbq
LXG//QcCgYBrYTCaKYDg/TSMt6fTSZgJRaui8PvvlLjZy63vrVCexVUibiCpbUt9
sHe9gbSDPr/uCyNgKnZjDbVabmZboVYqHBDQnXPUYYx9dBSL2DzE3JXA6Q8RVnOY
zwCPzVxYbE9h02ishAgc6k9w61XUaDnASmBZAnlxt9/8cB7+IZAlfg==
-----END RSA PRIVATE KEY-----
@@ -0,0 +1,50 @@
package main
import (
"io"
pb "grpcexample/helloworld"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"google.golang.org/grpc"
)
type Greeter struct {
pb.UnimplementedGreeterBidirectionalStreamServer
}
// SayHello implements the proto Bidirectional Stream Greeter service.
func (g *Greeter) SayHello(stream pb.GreeterBidirectionalStream_SayHelloServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
println("Received input: " + in.Name)
// On client side you can implement the 'read' operation too.
stream.Send(&pb.HelloReply{Message: "Hello " + in.Name})
}
}
func main() {
app := iris.New()
grpcServer := grpc.NewServer()
myService := &Greeter{}
pb.RegisterGreeterBidirectionalStreamServer(grpcServer, myService)
rootApp := mvc.New(app)
rootApp.Handle(myService, mvc.GRPC{
Server: grpcServer, // Required.
ServiceName: "helloworld.GreeterBidirectionalStream", // Required.
Strict: true, // Set it to true on gRPC streaming.
})
app.Run(iris.TLS(":443", "../server.crt", "../server.key"))
}
+20
View File
@@ -0,0 +1,20 @@
# gRPC Iris Example
## Generate TLS Keys
```sh
$ openssl genrsa -out server.key 2048
$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
```
## Install the protoc Go plugin
```sh
$ go get -u github.com/golang/protobuf/protoc-gen-go
```
## Generate proto
```sh
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld
```
@@ -0,0 +1,52 @@
// Package main implements a client for Greeter service.
package main
import (
"context"
"crypto/tls"
"log"
"os"
"time"
pb "github.com/kataras/iris/v12/_examples/mvc/grpc-compatible/helloworld"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
address = "localhost:443"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
// cred, err := credentials.NewClientTLSFromFile("../server.crt", "localhost")
// if err != nil {
// log.Fatal(err)
// }
cred := credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
Renegotiation: tls.RenegotiateNever,
})
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(cred), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
@@ -0,0 +1,3 @@
# Helloworld gRPC Example
https://github.com/grpc/grpc-go/tree/master/examples/helloworld
@@ -0,0 +1,315 @@
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.21.0
// protoc v3.11.1
// source: helloworld.proto
package helloworld
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// The request message containing the user's name.
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_helloworld_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_helloworld_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
// The response message containing the greetings
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_helloworld_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_helloworld_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_helloworld_proto protoreflect.FileDescriptor
var file_helloworld_proto_rawDesc = []byte{
0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x22,
0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72,
0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48,
0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65,
0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x30, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63,
0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
0x6f, 0x72, 0x6c, 0x64, 0x42, 0x0f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_helloworld_proto_rawDescOnce sync.Once
file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc
)
func file_helloworld_proto_rawDescGZIP() []byte {
file_helloworld_proto_rawDescOnce.Do(func() {
file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData)
})
return file_helloworld_proto_rawDescData
}
var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_helloworld_proto_goTypes = []interface{}{
(*HelloRequest)(nil), // 0: helloworld.HelloRequest
(*HelloReply)(nil), // 1: helloworld.HelloReply
}
var file_helloworld_proto_depIdxs = []int32{
0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
1, // 1: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_helloworld_proto_init() }
func file_helloworld_proto_init() {
if File_helloworld_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_helloworld_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_helloworld_proto_goTypes,
DependencyIndexes: file_helloworld_proto_depIdxs,
MessageInfos: file_helloworld_proto_msgTypes,
}.Build()
File_helloworld_proto = out.File
file_helloworld_proto_rawDesc = nil
file_helloworld_proto_goTypes = nil
file_helloworld_proto_depIdxs = nil
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type greeterClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
// UnimplementedGreeterServer can be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/helloworld.Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "helloworld.proto",
}
@@ -0,0 +1,33 @@
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
@@ -0,0 +1,50 @@
package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"log"
"net/http"
"os"
pb "github.com/kataras/iris/v12/_examples/mvc/grpc-compatible/helloworld"
)
func main() {
b, err := os.ReadFile("../server.crt")
if err != nil {
log.Fatal(err)
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
log.Fatal("credentials: failed to append certificates")
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: cp,
},
}
client := http.Client{Transport: transport}
buf := new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(pb.HelloRequest{Name: "world"})
if err != nil {
log.Fatal(err)
}
resp, err := client.Post("https://localhost/helloworld.Greeter/SayHello", "application/json", buf)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var reply pb.HelloReply
err = json.NewDecoder(resp.Body).Decode(&reply)
if err != nil {
log.Fatal(err)
}
log.Printf("Greeting: %s", reply.GetMessage())
}
+65 -37
View File
@@ -2,66 +2,94 @@ package main
import (
"context"
"log"
pb "github.com/kataras/iris/v12/_examples/mvc/grpc-compatible/helloworld"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"google.golang.org/grpc"
)
// See https://github.com/kataras/iris/issues/1449
// for more details but in-short you can convert Iris MVC to gRPC methods by
// binding the `context.Context` from `iris.Context.Request().Context()` and gRPC input and output data.
// Iris automatically binds the standard "context" context.Context to `iris.Context.Request().Context()`
// and any other structure that is not mapping to a registered dependency
// as a payload depends on the request, e.g XML, YAML, Query, Form, JSON.
//
// Useful to use gRPC services as Iris controllers fast and without wrappers.
func main() {
app := newApp()
app.Logger().SetLevel("debug")
// POST: http://localhost:8080/login
// with request data: {"username": "makis"}
// and expected output: {"message": "makis logged"}
app.Listen(":8080")
// The Iris server should ran under TLS (it's a gRPC requirement).
// POST: https://localhost:443/helloworld.Greeter/SayHello
// with request data: {"name": "John"}
// and expected output: {"message": "Hello John"}
app.Run(iris.TLS(":443", "server.crt", "server.key"))
}
func newApp() *iris.Application {
app := iris.New()
// app.Configure(iris.WithLowercaseRouting) // OPTIONAL.
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>Index Page</h1>")
})
ctrl := &myController{}
// Register gRPC server.
grpcServer := grpc.NewServer()
pb.RegisterGreeterServer(grpcServer, ctrl)
// serviceName := pb.File_helloworld_proto.Services().Get(0).FullName()
// Register MVC application controller for gRPC services.
// You can bind as many mvc gRpc services in the same Party or app,
// as the ServiceName differs.
mvc.New(app).
// Request-scope binding for context.Context-type controller's method or field.
// (or import github.com/kataras/iris/v12/hero and hero.Register(...))
Register(func(ctx iris.Context) context.Context {
return ctx.Request().Context()
}).
// Bind loginRequest.
// Register(func(ctx iris.Context) loginRequest {
// var req loginRequest
// ctx.ReadJSON(&req)
// return req
// }).
// OR
// Bind any other structure or pointer to a structure from request's
// XML
// YAML
// Query
// Form
// JSON (default, if not client's "Content-Type" specified otherwise)
Register(mvc.AutoBinding).
Handle(&myController{})
Register(new(myService)).
Handle(ctrl, mvc.GRPC{
Server: grpcServer, // Required.
ServiceName: "helloworld.Greeter", // Required.
Strict: false,
})
return app
}
type myController struct{}
type loginRequest struct {
Username string `json:"username"`
type service interface {
DoSomething() error
}
type loginResponse struct {
Message string `json:"message"`
type myService struct{}
func (s *myService) DoSomething() error {
log.Println("service: DoSomething")
return nil
}
func (c *myController) PostLogin(ctx context.Context, input loginRequest) (loginResponse, error) {
// [use of ctx to call a gRpc method or a database call...]
return loginResponse{
Message: input.Username + " logged",
}, nil
type myController struct {
// Ctx iris.Context
SingletonDependency service
}
// SayHello implements helloworld.GreeterServer.
// See https://github.com/kataras/iris/issues/1449#issuecomment-625570442
// for the comments below (https://github.com/iris-contrib/swagger).
//
// @Description greet service
// @Accept json
// @Produce json
// @Success 200 {string} string "Hello {name}"
// @Router /helloworld.Greeter/SayHello [post]
func (c *myController) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
err := c.SingletonDependency.DoSomething()
if err != nil {
return nil, err
}
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
+2 -2
View File
@@ -10,7 +10,7 @@ func TestGRPCCompatible(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
e.POST("/login").WithJSON(map[string]string{"username": "makis"}).Expect().
e.POST("/helloworld.Greeter/SayHello").WithJSON(map[string]string{"name": "makis"}).Expect().
Status(httptest.StatusOK).
JSON().Equal(map[string]string{"message": "makis logged"})
JSON().IsEqual(map[string]string{"message": "Hello makis"})
}
+19
View File
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDKjCCAhICCQDpz77z0oyjCDANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJH
UjEPMA0GA1UECAwGQXRoZW5zMQ8wDQYDVQQHDAZBdGhlbnMxJjAkBgkqhkiG9w0B
CQEWF2thdGFyYXMyMDA2QGhvdG1haWwuY29tMB4XDTIyMDMwMzExMjczM1oXDTMy
MDIyOTExMjczM1owVzELMAkGA1UEBhMCR1IxDzANBgNVBAgMBkF0aGVuczEPMA0G
A1UEBwwGQXRoZW5zMSYwJAYJKoZIhvcNAQkBFhdrYXRhcmFzMjAwNkBob3RtYWls
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALu4Uqpcf0AnRDwW
QGDh//v2wDNQ2EP/jn2Y4YxYJXtvWZ7dWcoX4fPA03mOAEmEPXWGxkhe8DYFh8PY
0zpZW5sFY7ae0AcpcjvlyxjNvHsqjhnh7M1gKZuhfyxvvLv7afC5Gs64tyg+f4C3
EqvwKHV+fjC8eKiASs4FsEEi89uv2AZdD9Vp0Zvbz6/1mRQQWBMpsPiIO7/WsaW4
DPq/zUZVY15WaX5B0Fg57O5Uv8aHyV4A+AQN7XzadCpYBp8XUb/cVcN9Azj2F6d3
WODyYT7y/RMuuih9HJQMxuAYPNB5UAkW6syTdfjbdyqbXe45z5iKPcotE4KaXYJf
hTkj6osCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAsb3QGQUGNGs8zv2y4QmsDCB9
+mkzwH9BpqSo40Zs19kT35nH0u//UuXiNG3U8d2WH1J7kk64cdrETuVay5TwW9jJ
EagdJl4zAZbfLQuDUCI+cVpjbywbO//GXzi8Q/aO7viboU0OR+MPbabubtKE7f+X
THIjnMj3OhFM4zzrFyEL8gsRMLzRRCV18wTsiCl9bi4bZ1Ssvr6MwqyXHs3uK+fx
JpxQVb3MckoIgUKwwU4MlXwufDkOmslGFsRg4e1iiwlkJ0HgRdf5lSuDmnBjH4T+
fWvRqCeHDTjOI09+7IAK1EF7rw4TZjHv+RbPl5+b1TJcanUC9EDg4z4/rR0XOg==
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAu7hSqlx/QCdEPBZAYOH/+/bAM1DYQ/+OfZjhjFgle29Znt1Z
yhfh88DTeY4ASYQ9dYbGSF7wNgWHw9jTOllbmwVjtp7QBylyO+XLGM28eyqOGeHs
zWApm6F/LG+8u/tp8Lkazri3KD5/gLcSq/AodX5+MLx4qIBKzgWwQSLz26/YBl0P
1WnRm9vPr/WZFBBYEymw+Ig7v9axpbgM+r/NRlVjXlZpfkHQWDns7lS/xofJXgD4
BA3tfNp0KlgGnxdRv9xVw30DOPYXp3dY4PJhPvL9Ey66KH0clAzG4Bg80HlQCRbq
zJN1+Nt3Kptd7jnPmIo9yi0Tgppdgl+FOSPqiwIDAQABAoIBAQCg0XM4cc+uVTV2
yJVUqqjT4fucusjb0EbxQJUR174ctjMwD2/J25X+bhZ9z3JdiQXh9plODM97aFd8
J/glx8Hb180p+Xo8eHxd5iqNUEwFtFpSwCNPeu+KXduGZR9qaCPFT78wlDyNJKW0
zqIXXMI8jiZreDtiF65+O49Y7im97Lqwhw8fEABcr3rpOpjK79nPHkf0X3CKj1TC
IhW5Av4j41uv8+1BgObjVkeYFHK4O+vRWiSId9OiXzZT9ZaG56ntGOap9KuugTym
2ngbIBm2DdUDmbY3vL9eUzhfY75VIjFrgnf/EYN+7kymbymo3vnv5OmB3OByX7iw
bXTmxQgxAoGBAOLLx6EzcQJ83g85Gtd3WNiNW414ynDPyIB/XAEcyygZGYqrVfsH
HfGhxI787WqzjIZxl987HTPOkrX5Xc/GhHNeTVa1+neyUUqx46DxessU3/s53Vg4
X1AmnmL6+Wl1qrlTJiUQINDyOZkHsbycR8okDlrzSS1rwO90wW6K5snDAoGBANPk
a/NH2t+KrAboPtXZk2DCUMkCoyDhzXnPU4HARO0BRa3Qhul8keV8u54MtNsej1y+
TcatUQYpuT4Pgpl1kdtyHrLNiEhd0ThBgy5RmS/rLk9XnuWgfeth5bGxbEIlHXMN
HvoxL34UhGxwWFCHKr5vV2OYVcPLkbdDBhLneweZAoGAaL6tCGp1uyxocqdxGipo
wjsnGYO8G7YbaB1qJKljurU88qqHH1T+I2cPHOr7y9f5Au7bsaHfrtmtMJZnGVsa
OR5Ioc+SSk309YaLFv3wNHMDr0feTqxaeO4dIKHBJ0/M9aLNbzivr1DwARlooS+c
iGN2rdLG7U9i4DUQUTmdtXkCgYEAllEJM8DZyJN7jjrbuKFtJ8sxvCeeygjl12/4
8acQPoIUiEXSL3krlv1xq6Gf+4ImeciXLEZvoEuhGiGuqGb7Xg4LMRUVhSDo91ui
UA2a+p+AbtDd7FB6g60jYXdYMWRbC+9W9m5GHs83UiYwwI/jBs291O2QiiGz8aoe
ePK2GKECgYEAg7PYp1uOWEuA39gqrKmuLA5f/i2bGgBrajk90m+9kgNwZ9RqeZJG
V97hRwO86EwBBqhMUjaFv/zwSjKhCZUE8vOt5DF4sH5Ppa9j/kqrzsZjlKjajj/U
tH1iFSXz/+PwQVeNssfjFMUPwWWsGSN5pvqx/5Ru/DIvPLHOg3YbEQc=
-----END RSA PRIVATE KEY-----
+31 -4
View File
@@ -1,15 +1,16 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"strings"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/logger"
"github.com/kataras/iris/v12/middleware/recover"
"github.com/kataras/iris/v12/mvc"
)
// This example is equivalent to the
// https://github.com/kataras/iris/blob/master/_examples/hello-world/main.go
// https://github.com/kataras/iris/blob/main/_examples/hello-world/main.go
//
// It seems that additional code you
// have to write doesn't worth it
@@ -40,6 +41,16 @@ func newApp() *iris.Application {
// Serve a controller based on the root Router, "/".
mvc.New(app).Handle(new(ExampleController))
// Add custom path func
mvc.New(app).SetCustomPathWordFunc(func(path, w string, wordIndex int) string {
if wordIndex == 0 {
w = strings.ToLower(w)
}
path += w
return path
}).Handle(new(ExampleControllerCustomPath))
return app
}
@@ -50,7 +61,7 @@ func main() {
// http://localhost:8080/ping
// http://localhost:8080/hello
// http://localhost:8080/custom_path
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
// ExampleController serves the "/", "/ping" and "/hello".
@@ -80,6 +91,13 @@ func (c *ExampleController) GetHello() interface{} {
return map[string]string{"message": "Hello Iris!"}
}
// GetHelloWorld serves
// Method: GET
// Resource: http://localhost:8080/hello/world
func (c *ExampleController) GetHelloWorld() interface{} {
return map[string]string{"message": "Hello Iris! DefaultPath"}
}
// BeforeActivation called once, before the controller adapted to the main application
// and of course before the server ran.
// After version 9 you can also add custom routes for a specific controller's methods.
@@ -105,6 +123,15 @@ func (c *ExampleController) CustomHandlerWithoutFollowingTheNamingGuide() string
return "hello from the custom handler without following the naming guide"
}
type ExampleControllerCustomPath struct{}
// GetHelloWorld serves
// Method: GET
// Resource: http://localhost:8080/helloWorld
func (c *ExampleControllerCustomPath) GetHelloWorld() interface{} {
return map[string]string{"message": "Hello Iris! CustomPath"}
}
// GetUserBy serves
// Method: GET
// Resource: http://localhost:8080/user/{username:string}
+3 -3
View File
@@ -10,14 +10,14 @@ func TestMVCHelloWorld(t *testing.T) {
e := httptest.New(t, newApp())
e.GET("/").Expect().Status(httptest.StatusOK).
ContentType("text/html", "utf-8").Body().Equal("<h1>Welcome</h1>")
ContentType("text/html", "utf-8").Body().IsEqual("<h1>Welcome</h1>")
e.GET("/ping").Expect().Status(httptest.StatusOK).
Body().Equal("pong")
Body().IsEqual("pong")
e.GET("/hello").Expect().Status(httptest.StatusOK).
JSON().Object().Value("message").Equal("Hello Iris!")
e.GET("/custom_path").Expect().Status(httptest.StatusOK).
Body().Equal("hello from the custom handler without following the naming guide")
Body().IsEqual("hello from the custom handler without following the naming guide")
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,56 @@
package main
import (
"time"
"github.com/kataras/iris/v12/_examples/mvc/login-mvc-single-responsibility/user"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"github.com/kataras/iris/v12/sessions"
)
func main() {
app := iris.New()
// You got full debug messages, useful when using MVC and you want to make
// sure that your code is aligned with the Iris' MVC Architecture.
app.Logger().SetLevel("debug")
app.RegisterView(iris.HTML("./views", ".html").Layout("shared/layout.html"))
app.HandleDir("/public", iris.Dir("./public"))
userRouter := app.Party("/user")
{
manager := sessions.New(sessions.Config{
Cookie: "sessioncookiename",
Expires: 24 * time.Hour,
})
userRouter.Use(manager.Handler())
mvc.Configure(userRouter, configureUserMVC)
}
// http://localhost:8080/user/register
// http://localhost:8080/user/login
// http://localhost:8080/user/me
// http://localhost:8080/user/logout
// http://localhost:8080/user/1
app.Listen(":8080", configure)
}
func configureUserMVC(userApp *mvc.Application) {
userApp.Register(
user.NewDataSource(),
)
userApp.Handle(new(user.Controller))
}
func configure(app *iris.Application) {
app.Configure(
iris.WithOptimizations,
iris.WithFireMethodNotAllowed,
iris.WithLowercaseRouting,
iris.WithPathIntelligence,
iris.WithTunneling,
)
}
@@ -0,0 +1,61 @@
/* Bordered form */
form {
border: 3px solid #f1f1f1;
}
/* Full-width inputs */
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}
/* Set a style for all buttons */
button {
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
cursor: pointer;
width: 100%;
}
/* Add a hover effect for buttons */
button:hover {
opacity: 0.8;
}
/* Extra style for the cancel button (red) */
.cancelbtn {
width: auto;
padding: 10px 18px;
background-color: #f44336;
}
/* Center the container */
/* Add padding to containers */
.container {
padding: 16px;
}
/* The "Forgot password" text */
span.psw {
float: right;
padding-top: 16px;
}
/* Change styles for span and cancel button on extra small screens */
@media screen and (max-width: 300px) {
span.psw {
display: block;
float: none;
}
.cancelbtn {
width: 100%;
}
}
@@ -0,0 +1,110 @@
package user
import (
"errors"
"strconv"
"strings"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"github.com/kataras/iris/v12/sessions"
)
const sessionIDKey = "UserID"
// paths
var (
PathLogin = mvc.Response{Path: "/user/login"}
PathLogout = mvc.Response{Path: "/user/logout"}
)
// AuthController is the user authentication controller, a custom shared controller.
type AuthController struct {
// context is auto-binded if struct depends on this,
// in this controller we don't we do everything with mvc-style,
// and that's neither the 30% of its features.
// Ctx iris.Context
Source *DataSource
Session *sessions.Session
// the whole controller is request-scoped because we already depend on Session, so
// this will be new for each new incoming request, BeginRequest sets that based on the session.
UserID int64
}
// BeginRequest saves login state to the context, the user id.
func (c *AuthController) BeginRequest(ctx iris.Context) {
c.UserID, _ = c.Session.GetInt64(sessionIDKey)
}
// EndRequest is here just to complete the BaseController
// in order to be tell iris to call the `BeginRequest` before the main method.
func (c *AuthController) EndRequest(ctx iris.Context) {}
func (c *AuthController) fireError(err error) mvc.View {
return mvc.View{
Code: iris.StatusBadRequest,
Name: "shared/error.html",
Data: iris.Map{"Title": "User Error", "Message": strings.ToUpper(err.Error())},
}
}
func (c *AuthController) redirectTo(id int64) mvc.Response {
return mvc.Response{Path: "/user/" + strconv.Itoa(int(id))}
}
func (c *AuthController) createOrUpdate(firstname, username, password string) (user Model, err error) {
username = strings.Trim(username, " ")
if username == "" || password == "" || firstname == "" {
return user, errors.New("empty firstname, username or/and password")
}
userToInsert := Model{
Firstname: firstname,
Username: username,
password: password,
} // password is hashed by the Source.
newUser, err := c.Source.InsertOrUpdate(userToInsert)
if err != nil {
return user, err
}
return newUser, nil
}
func (c *AuthController) isLoggedIn() bool {
// we don't search by session, we have the user id
// already by the `BeginRequest` middleware.
return c.UserID > 0
}
func (c *AuthController) verify(username, password string) (user Model, err error) {
if username == "" || password == "" {
return user, errors.New("please fill both username and password fields")
}
u, found := c.Source.GetByUsername(username)
if !found {
// if user found with that username not found at all.
return user, errors.New("user with that username does not exist")
}
if ok, err := ValidatePassword(password, u.HashedPassword); err != nil || !ok {
// if user found but an error occurred or the password is not valid.
return user, errors.New("please try to login with valid credentials")
}
return u, nil
}
// if logged in then destroy the session
// and redirect to the login page
// otherwise redirect to the registration page.
func (c *AuthController) logout() mvc.Response {
if c.isLoggedIn() {
c.Session.Destroy()
}
return PathLogin
}
@@ -0,0 +1,189 @@
package user
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
var (
// About Code: iris.StatusSeeOther ->
// When redirecting from POST to GET request you -should- use this HTTP status code,
// however there're some (complicated) alternatives if you
// search online or even the HTTP RFC.
// "See Other" RFC 7231
pathMyProfile = mvc.Response{Path: "/user/me", Code: iris.StatusSeeOther}
pathRegister = mvc.Response{Path: "/user/register"}
)
// Controller is responsible to handle the following requests:
// GET /user/register
// POST /user/register
// GET /user/login
// POST /user/login
// GET /user/me
// GET /user/{id:int64}
// All HTTP Methods /user/logout
type Controller struct {
AuthController
}
type formValue func(string) string
// BeforeActivation called once before the server start
// and before the controller's registration, here you can add
// dependencies, to this controller and only, that the main caller may skip.
func (c *Controller) BeforeActivation(b mvc.BeforeActivation) {
// bind the context's `FormValue` as well in order to be
// acceptable on the controller or its methods' input arguments (NEW feature as well).
b.Dependencies().Register(func(ctx iris.Context) formValue { return ctx.FormValue })
}
type page struct {
Title string
}
// GetRegister handles GET:/user/register.
// mvc.Result can accept any struct which contains a `Dispatch(ctx iris.Context)` method.
// Both mvc.Response and mvc.View are mvc.Result.
func (c *Controller) GetRegister() mvc.Result {
if c.isLoggedIn() {
return c.logout()
}
// You could just use it as a variable to win some time in serve-time,
// this is an exersise for you :)
return mvc.View{
Name: pathRegister.Path + ".html",
Data: page{"User Registration"},
}
}
// PostRegister handles POST:/user/register.
func (c *Controller) PostRegister(form formValue) mvc.Result {
// we can either use the `c.Ctx.ReadForm` or read values one by one.
var (
firstname = form("firstname")
username = form("username")
password = form("password")
)
user, err := c.createOrUpdate(firstname, username, password)
if err != nil {
return c.fireError(err)
}
// setting a session value was never easier.
c.Session.Set(sessionIDKey, user.ID)
// succeed, nothing more to do here, just redirect to the /user/me.
return pathMyProfile
}
// with these static views,
// you can use variables-- that are initialized before server start
// so you can win some time on serving.
// You can do it else where as well but I let them as pracise for you,
// essentially you can understand by just looking below.
var userLoginView = mvc.View{
Name: PathLogin.Path + ".html",
Data: page{"User Login"},
}
// GetLogin handles GET:/user/login.
func (c *Controller) GetLogin() mvc.Result {
if c.isLoggedIn() {
return c.logout()
}
return userLoginView
}
// PostLogin handles POST:/user/login.
func (c *Controller) PostLogin(form formValue) mvc.Result {
var (
username = form("username")
password = form("password")
)
user, err := c.verify(username, password)
if err != nil {
return c.fireError(err)
}
c.Session.Set(sessionIDKey, user.ID)
return pathMyProfile
}
// AnyLogout handles any method on path /user/logout.
func (c *Controller) AnyLogout() {
c.logout()
}
// GetMe handles GET:/user/me.
func (c *Controller) GetMe() mvc.Result {
id, err := c.Session.GetInt64(sessionIDKey)
if err != nil || id <= 0 {
// when not already logged in, redirect to login.
return PathLogin
}
u, found := c.Source.GetByID(id)
if !found {
// if the session exists but for some reason the user doesn't exist in the "database"
// then logout him and redirect to the register page.
return c.logout()
}
// set the model and render the view template.
return mvc.View{
Name: pathMyProfile.Path + ".html",
Data: iris.Map{
"Title": "Profile of " + u.Username,
"User": u,
},
}
}
func (c *Controller) renderNotFound(id int64) mvc.View {
return mvc.View{
Code: iris.StatusNotFound,
Name: "user/notfound.html",
Data: iris.Map{
"Title": "User Not Found",
"ID": id,
},
}
}
// Dispatch completes the `mvc.Result` interface
// in order to be able to return a type of `Model`
// as mvc.Result.
// If this function didn't exist then
// we should explicit set the output result to that Model or to an interface{}.
func (u Model) Dispatch(ctx iris.Context) {
ctx.JSON(u)
}
// GetBy handles GET:/user/{id:int64},
// i.e http://localhost:8080/user/1
func (c *Controller) GetBy(userID int64) mvc.Result {
// we have /user/{id}
// fetch and render user json.
user, found := c.Source.GetByID(userID)
if !found {
// not user found with that ID.
return c.renderNotFound(userID)
}
// Q: how the hell Model can be return as mvc.Result?
// A: I told you before on some comments and the docs,
// any struct that has a `Dispatch(ctx iris.Context)`
// can be returned as an mvc.Result(see ~20 lines above),
// therefore we are able to combine many type of results in the same method.
// For example, here, we return either an mvc.View to render a not found custom template
// either a user which returns the Model as JSON via its Dispatch.
//
// We could also return just a struct value that is not an mvc.Result,
// if the output result of the `GetBy` was that struct's type or an interface{}
// and iris would render that with JSON as well, but here we can't do that without complete the `Dispatch`
// function, because we may return an mvc.View which is an mvc.Result.
return user
}
@@ -0,0 +1,114 @@
package user
import (
"errors"
"sync"
"time"
)
// IDGenerator would be our user ID generator
// but here we keep the order of users by their IDs
// so we will use numbers that can be easly written
// to the browser to get results back from the REST API.
// var IDGenerator = func() string {
// return uuid.NewV4().String()
// }
// DataSource is our data store example.
type DataSource struct {
Users map[int64]Model
mu sync.RWMutex
}
// NewDataSource returns a new user data source.
func NewDataSource() *DataSource {
return &DataSource{
Users: make(map[int64]Model),
}
}
// GetBy receives a query function
// which is fired for every single user model inside
// our imaginary database.
// When that function returns true then it stops the iteration.
//
// It returns the query's return last known boolean value
// and the last known user model
// to help callers to reduce the loc.
//
// But be carefully, the caller should always check for the "found"
// because it may be false but the user model has actually real data inside it.
//
// It's actually a simple but very clever prototype function
// I'm think of and using everywhere since then,
// hope you find it very useful too.
func (d *DataSource) GetBy(query func(Model) bool) (user Model, found bool) {
d.mu.RLock()
for _, user = range d.Users {
found = query(user)
if found {
break
}
}
d.mu.RUnlock()
return
}
// GetByID returns a user model based on its ID.
func (d *DataSource) GetByID(id int64) (Model, bool) {
return d.GetBy(func(u Model) bool {
return u.ID == id
})
}
// GetByUsername returns a user model based on the Username.
func (d *DataSource) GetByUsername(username string) (Model, bool) {
return d.GetBy(func(u Model) bool {
return u.Username == username
})
}
func (d *DataSource) getLastID() (lastID int64) {
d.mu.RLock()
for id := range d.Users {
if id > lastID {
lastID = id
}
}
d.mu.RUnlock()
return lastID
}
// InsertOrUpdate adds or updates a user to the (memory) storage.
func (d *DataSource) InsertOrUpdate(user Model) (Model, error) {
// no matter what we will update the password hash
// for both update and insert actions.
hashedPassword, err := GeneratePassword(user.password)
if err != nil {
return user, err
}
user.HashedPassword = hashedPassword
// update
if id := user.ID; id > 0 {
_, found := d.GetByID(id)
if !found {
return user, errors.New("ID should be zero or a valid one that maps to an existing User")
}
d.mu.Lock()
d.Users[id] = user
d.mu.Unlock()
return user, nil
}
// insert
id := d.getLastID() + 1
user.ID = id
d.mu.Lock()
user.CreatedAt = time.Now()
d.Users[id] = user
d.mu.Unlock()
return user, nil
}
@@ -0,0 +1,36 @@
package user
import (
"time"
"golang.org/x/crypto/bcrypt"
)
// Model is our User example model.
type Model struct {
ID int64 `json:"id"`
Firstname string `json:"firstname"`
Username string `json:"username"`
// password is the client-given password
// which will not be stored anywhere in the server.
// It's here only for actions like registration and update password,
// because we caccept a Model instance
// inside the `DataSource#InsertOrUpdate` function.
password string
HashedPassword []byte `json:"-"`
CreatedAt time.Time `json:"created_at"`
}
// 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, error) {
if err := bcrypt.CompareHashAndPassword(hashed, []byte(userPassword)); err != nil {
return false, err
}
return true, nil
}
@@ -0,0 +1,4 @@
<h1>Error.</h1>
<h2>An error occurred while processing your request.</h2>
<h3>{{.Message}}</h3>
@@ -0,0 +1,12 @@
<html>
<head>
<title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/public/css/site.css" />
</head>
<body>
{{ yield . }}
</body>
</html>
@@ -0,0 +1,11 @@
<form action="/user/login" method="POST">
<div class="container">
<label><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>
<label><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Login</button>
</div>
</form>
@@ -0,0 +1,3 @@
<p>
Welcome back <strong>{{.User.Firstname}}</strong>!
</p>
@@ -0,0 +1,3 @@
<p>
User with ID <strong>{{.ID}}</strong> does not exist.
</p>
@@ -0,0 +1,14 @@
<form action="/user/register" method="POST">
<div class="container">
<label><b>Firstname</b></label>
<input type="text" placeholder="Enter Firstname" name="firstname" required>
<label><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>
<label><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Register</button>
</div>
</form>
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// Keep note that the tags for public-use (for our web app)
// should be kept in other file like "web/viewmodels/user.go"
// which could wrap by embedding the datamodels.User or
// define completely new fields instead but for the shake
// define completely new fields instead but for the sake
// of the example, we will use this datamodel
// as the only one User model in our application.
type User struct {
+2 -2
View File
@@ -21,10 +21,10 @@ const (
MySQL
)
// LoadUsers returns all users(empty map) from the memory, for the shake of simplicty.
// LoadUsers returns all users(empty map) from the memory, for the sake of simplicty.
func LoadUsers(engine Engine) (map[int64]datamodels.User, error) {
if engine != Memory {
return nil, errors.New("for the shake of simplicity we're using a simple map as the data source")
return nil, errors.New("for the sake of simplicity we're using a simple map as the data source")
}
return make(map[int64]datamodels.User), nil
+9 -10
View File
@@ -28,12 +28,15 @@ func main() {
Reload(true)
app.RegisterView(tmpl)
app.HandleDir("/public", "./web/public")
app.HandleDir("/public", iris.Dir("./web/public"))
app.OnAnyErrorCode(func(ctx iris.Context) {
ctx.ViewData("Message", ctx.Values().
GetStringDefault("message", "The page you're looking for doesn't exist"))
ctx.View("shared/error.html")
if err := ctx.View("shared/error.html"); err != nil {
ctx.HTML("<h3>%s</h3>", err.Error())
return
}
})
// ---- Serve our controllers. ----
@@ -76,12 +79,8 @@ func main() {
// http://localhost:8080/user/me
// http://localhost:8080/user/logout
// basic auth: "admin", "password", see "./middleware/basicauth.go" source file.
app.Run(
// Starts the web server at localhost:8080
iris.Addr("localhost:8080"),
// Ignores err server closed log when CTRL/CMD+C pressed.
iris.WithoutServerError(iris.ErrServerClosed),
// Enables faster json serialization and more.
iris.WithOptimizations,
)
// Starts the web server at localhost:8080
// Enables faster json serialization and more.
app.Listen(":8080", iris.WithOptimizations)
}
+1 -1
View File
@@ -51,7 +51,7 @@ func (s *userService) GetByID(id int64) (datamodels.User, bool) {
})
}
// GetByUsernameAndPassword returns a user based on its username and passowrd,
// GetByUsernameAndPassword returns a user based on its username and password,
// used for authentication.
func (s *userService) GetByUsernameAndPassword(username, userPassword string) (datamodels.User, bool) {
if username == "" || userPassword == "" {
@@ -9,9 +9,9 @@ import (
// UsersController is our /users API controller.
// GET /users | get all
// GET /users/{id:long} | get by id
// PUT /users/{id:long} | update by id
// DELETE /users/{id:long} | delete by id
// GET /users/{id:int64} | get by id
// PUT /users/{id:int64} | update by id
// DELETE /users/{id:int64} | delete by id
// Requires basic authentication.
type UsersController struct {
// Optionally: context is auto-binded by Iris on each request,
@@ -30,14 +30,16 @@ type UsersController struct {
// curl -i -u admin:password http://localhost:8080/users
//
// The correct way if you have sensitive data:
// func (c *UsersController) Get() (results []viewmodels.User) {
// data := c.Service.GetAll()
//
// for _, user := range data {
// results = append(results, viewmodels.User{user})
// }
// return
// }
// func (c *UsersController) Get() (results []viewmodels.User) {
// data := c.Service.GetAll()
//
// for _, user := range data {
// results = append(results, viewmodels.User{user})
// }
// return
// }
//
// otherwise just return the datamodels.
func (c *UsersController) Get() (results []datamodels.User) {
return c.Service.GetAll()
@@ -5,8 +5,6 @@ package middleware
import "github.com/kataras/iris/v12/middleware/basicauth"
// BasicAuth middleware sample.
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
var BasicAuth = basicauth.Default(map[string]string{
"admin": "password",
})
@@ -6,7 +6,7 @@
</head>
<body>
{{ yield }}
{{ yield . }}
</body>
</html>
+4 -2
View File
@@ -14,20 +14,22 @@ var cacheHandler = cache.Handler(10 * time.Second)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
mvc.Configure(app, configure)
// http://localhost:8080
// http://localhost:8080/other
//
// refresh every 10 seconds and you'll see different time output.
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
func configure(m *mvc.Application) {
m.Router.Use(cacheHandler)
m.Handle(&exampleController{
timeFormat: "Mon, Jan 02 2006 15:04:05",
})
} /* ,mvc.IgnoreEmbedded --- Can be used to ignore any embedded struct method handlers */)
}
type exampleController struct {
+23 -17
View File
@@ -9,9 +9,11 @@ else as well, otherwise I am going with the first one:
```go
// 1
mvc.Configure(app.Party("/user"), func(m *mvc.Application) {
m.Router.Use(cache.Handler(10*time.Second))
})
mvc.Configure(app.Party("/user"), func(m *mvc.Application) {
m.Router.Use(cache.Handler(10*time.Second))
})
```
```go
@@ -32,10 +34,12 @@ mvc.Configure(userRouter, ...)
```go
// 4
// same:
app.PartyFunc("/user", func(r iris.Party){
r.Use(cache.Handler(10*time.Second))
mvc.Configure(r, ...)
})
app.PartyFunc("/user", func(r iris.Party){
r.Use(cache.Handler(10*time.Second))
mvc.Configure(r, ...)
})
```
If you want to use a middleware for a single route,
@@ -48,16 +52,18 @@ then you just call it on the method:
var myMiddleware := myMiddleware.New(...) // this should return an iris/context.Handler
type UserController struct{}
func (c *UserController) GetSomething(ctx iris.Context) {
// ctx.Proceed checks if myMiddleware called `ctx.Next()`
// inside it and returns true if so, otherwise false.
nextCalled := ctx.Proceed(myMiddleware)
if !nextCalled {
return
}
// else do the job here, it's allowed
}
func (c *UserController) GetSomething(ctx iris.Context) {
// ctx.Proceed checks if myMiddleware called `ctx.Next()`
// inside it and returns true if so, otherwise false.
nextCalled := ctx.Proceed(myMiddleware)
if !nextCalled {
return
}
// else do the job here, it's allowed
}
```
And last, if you want to add a middleware on a specific method
@@ -84,7 +90,7 @@ func main() {
m := mvc.New(app)
m.Handle(&exampleController{})
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
type exampleController struct{}
@@ -1,27 +1,10 @@
/*Package main is a simple example of the behavior change of the execution flow of the handlers,
normally we need the `ctx.Next()` to call the next handler in a route's handler chain,
but with the new `ExecutionRules` we can change this default behavior.
Please read below before continue.
/*
Package main shows how to add done handlers in an MVC application without
the necessity of `ctx.Next()` inside the controller's methods.
The `Party#SetExecutionRules` alters the execution flow of the route handlers outside of the handlers themselves.
For example, if for some reason the desired result is the (done or all) handlers to be executed no matter what
even if no `ctx.Next()` is called in the previous handlers, including the begin(`Use`),
the main(`Handle`) and the done(`Done`) handlers themselves, then:
Party#SetExecutionRules(iris.ExecutionRules {
Begin: iris.ExecutionOptions{Force: true},
Main: iris.ExecutionOptions{Force: true},
Done: iris.ExecutionOptions{Force: true},
})
Note that if `true` then the only remained way to "break" the handler chain is by `ctx.StopExecution()` now that `ctx.Next()` does not matter.
These rules are per-party, so if a `Party` creates a child one then the same rules will be applied to that as well.
Reset of these rules (before `Party#Handle`) can be done with `Party#SetExecutionRules(iris.ExecutionRules{})`.
The most common scenario for its use can be found inside Iris MVC Applications;
when we want the `Done` handlers of that specific mvc app's `Party`
to be executed but we don't want to add `ctx.Next()` on the `exampleController#EndRequest`*/
When we want the `Done` handlers of that specific mvc app's `Party`
to be executed but we don't want to add `ctx.Next()` on the `exampleController#EndRequest`
*/
package main
import (
@@ -33,25 +16,19 @@ func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) { ctx.Redirect("/example") })
// example := app.Party("/example")
// example.SetExecutionRules && mvc.New(example) or...
m := mvc.New(app.Party("/example"))
exampleRouter := app.Party("/example")
{
exampleRouter.SetExecutionRules(iris.ExecutionRules{
Done: iris.ExecutionOptions{Force: true},
})
// IMPORTANT
// All options can be filled with Force:true, they all play nice together.
m.Router.SetExecutionRules(iris.ExecutionRules{
// Begin: <- from `Use[all]` to `Handle[last]` future route handlers, execute all, execute all even if `ctx.Next()` is missing.
// Main: <- all `Handle` future route handlers, execute all >> >>.
Done: iris.ExecutionOptions{Force: true}, // <- from `Handle[last]` to `Done[all]` future route handlers, execute all >> >>.
})
m.Router.Done(doneHandler)
// m.Router.Done(...)
// ...
//
exampleRouter.Done(doneHandler)
m.Handle(&exampleController{})
m := mvc.New(exampleRouter)
m.Handle(&exampleController{})
}
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
func doneHandler(ctx iris.Context) {
@@ -64,7 +41,8 @@ func (c *exampleController) Get() string {
return "From Main Handler"
// Note that here we don't binding the `Context`, and we don't call its `Next()`
// function in order to call the `doneHandler`,
// this is done automatically for us because we changed the execution rules with the `SetExecutionRules`.
// this is done automatically for us because we changed the execution rules with the
// `SetExecutionRules`.
//
// Therefore the final output is:
// From Main Handler
+17
View File
@@ -0,0 +1,17 @@
# docker build -t app .
# docker run --rm -it -p 8080:8080 app: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
COPY . .
RUN go install
FROM scratch
COPY --from=builder /go/bin/app .
ENTRYPOINT ["./app"]
+377
View File
@@ -0,0 +1,377 @@
# Quick start
The following guide is just a simple example of usage of some of the **Iris MVC** features. You are not limited to that data structure or code flow.
Create a folder, let's assume its path is `app`. The structure should look like that:
```
│ main.go
│ go.mod
│ go.sum
└───environment
│ environment.go
└───model
│ request.go
│ response.go
└───database
│ database.go
│ mysql.go
│ sqlite.go
└───service
│ greet_service.go
└───controller
greet_controller.go
```
Navigate to that `app` folder and execute the following command:
```sh
$ go init app
$ go get github.com/kataras/iris/v12@latest
```
## Environment
Let's start by defining the available environments that our web-application can behave on.
We'll just work on two available environments, the "development" and the "production", as they define the two most common scenarios. The `ReadEnv` will read from the `Env` type of a system's environment variable (see `main.go` in the end of the page).
Create a `environment/environment.go` file and put the following contents:
```go
package environment
import (
"os"
"strings"
)
const (
PROD Env = "production"
DEV Env = "development"
)
type Env string
func (e Env) String() string {
return string(e)
}
func ReadEnv(key string, def Env) Env {
v := Getenv(key, def.String())
if v == "" {
return def
}
env := Env(strings.ToLower(v))
switch env {
case PROD, DEV: // allowed.
default:
panic("unexpected environment " + v)
}
return env
}
func Getenv(key string, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
```
## Database
We will use two database management systems, the `MySQL` and the `SQLite`. The first one for "production" use and the other for "development".
Create a `database/database.go` file and copy-paste the following:
```go
package database
import "app/environment"
type DB interface {
Exec(q string) error
}
func NewDB(env environment.Env) DB {
switch env {
case environment.PROD:
return &mysql{}
case environment.DEV:
return &sqlite{}
default:
panic("unknown environment")
}
}
```
Let's simulate our MySQL and SQLite `DB` instances. Create a `database/mysql.go` file which looks like the following one:
```go
package database
import "fmt"
type mysql struct{}
func (db *mysql) Exec(q string) error {
return fmt.Errorf("mysql: not implemented <%s>", q)
}
```
And a `database/sqlite.go` file.
```go
package database
type sqlite struct{}
func (db *sqlite) Exec(q string) error { return nil }
```
The `DB` depends on the `Environment.
> A practical and operational database example, including Docker images, can be found at the following guide: https://github.com/kataras/iris/tree/main/_examples/database/mysql
## Service
We'll need a service that will communicate with a database instance in behalf of our Controller(s).
In our case we will only need a single service, the Greet Service.
For the sake of the example, let's use two implementations of a greet service based on the `Environment`. The `GreetService` interface contains a single method of `Say(input string) (output string, err error)`. Create a `./service/greet_service.go` file and write the following code:
```go
package service
import (
"fmt"
"app/database"
"app/environment"
)
// GreetService example service.
type GreetService interface {
Say(input string) (string, error)
}
// NewGreetService returns a service backed with a "db" based on "env".
func NewGreetService(env environment.Env, db database.DB) GreetService {
service := &greeter{db: db, prefix: "Hello"}
switch env {
case environment.PROD:
return service
case environment.DEV:
return &greeterWithLogging{service}
default:
panic("unknown environment")
}
}
type greeter struct {
prefix string
db database.DB
}
func (s *greeter) Say(input string) (string, error) {
if err := s.db.Exec("simulate a query..."); err != nil {
return "", err
}
result := s.prefix + " " + input
return result, nil
}
type greeterWithLogging struct {
*greeter
}
func (s *greeterWithLogging) Say(input string) (string, error) {
result, err := s.greeter.Say(input)
fmt.Printf("result: %s\nerror: %v\n", result, err)
return result, err
}
```
The `greeter` will be used on "production" and the `greeterWithLogging` on "development". The `GreetService` depends on the `Environment` and the `DB`.
## Models
Continue by creating our HTTP request and response models.
Create a `model/request.go` file and copy-paste the following code:
```go
package model
type Request struct {
Name string `url:"name"`
}
```
Same for the `model/response.go` file.
```go
package model
type Response struct {
Message string `json:"msg"`
}
```
The server will accept a URL Query Parameter of `name` (e.g. `/greet?name=kataras`) and will reply back with a JSON message.
## Controller
MVC Controllers are responsible for controlling the flow of the application execution. When you make a request (means request a page) to MVC Application, a controller is responsible for returning the response to that request.
We will only need the `GreetController` for our mini web-application. Create a file at `controller/greet_controller.go` which looks like that:
```go
package controller
import (
"app/model"
"app/service"
)
type GreetController struct {
Service service.GreetService
// Ctx iris.Context
}
func (c *GreetController) Get(req model.Request) (model.Response, error) {
message, err := c.Service.Say(req.Name)
if err != nil {
return model.Response{}, err
}
return model.Response{Message: message}, nil
}
```
The `GreetController` depends on the `GreetService`. It serves the `GET: /greet` index path through its `Get` method. The `Get` method accepts a `model.Request` which contains a single field name of `Name` which will be extracted from the `URL Query Parameter 'name'` (because it's a `GET` requst and its `url:"name"` struct field).
## Wrap up
```sh
+-------------------+
| Env (DEV, PROD) |
+---------+---------+
| | |
| | |
| | |
DEV | | | PROD
-------------------+---------------------+ | +----------------------+-------------------
| | |
| | |
+---+-----+ +----------------v------------------+ +----+----+
| sqlite | | NewDB(Env) DB | | mysql |
+---+-----+ +----------------+---+--------------+ +----+----+
| | | |
| | | |
| | | |
+--------------+-----+ +-------------------v---v-----------------+ +----+------+
| greeterWithLogging | | NewGreetService(Env, DB) GreetService | | greeter |
+--------------+-----+ +---------------------------+-------------+ +----+------+
| | |
| | |
| +-----------------------------------------+ |
| | GreetController | | |
| | | | |
| | - Service GreetService <-- | |
| | | |
| +-------------------+---------------------+ |
| | |
| | |
| | |
| +-----------+-----------+ |
| | HTTP Request | |
| +-----------------------+ |
| | /greet?name=kataras | |
| +-----------+-----------+ |
| | |
+------------------+--------+ +------------+------------+ +-------+------------------+
| model.Response (JSON) | | Response (JSON, error) | | Bad Request |
+---------------------------+ +-------------------------+ +--------------------------+
| { | | mysql: not implemented |
| "msg": "Hello kataras" | +--------------------------+
| } |
+---------------------------+
```
Now it's the time to wrap all the above into our `main.go` file. Copy-paste the following code:
```go
package main
import (
"app/controller"
"app/database"
"app/environment"
"app/service"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.Get("/ping", pong).Describe("healthcheck")
mvc.Configure(app.Party("/greet"), setup)
// http://localhost:8080/greet?name=kataras
app.Listen(":8080", iris.WithLogLevel("debug"))
}
func pong(ctx iris.Context) {
ctx.WriteString("pong")
}
func setup(app *mvc.Application) {
// Register Dependencies.
app.Register(
environment.DEV, // DEV, PROD
database.NewDB, // sqlite, mysql
service.NewGreetService, // greeterWithLogging, greeter
)
// Register Controllers.
app.Handle(new(controller.GreetController))
}
```
The `mvc.Application.Register` method registers one more dependencies, dependencies can depend on previously registered dependencies too. Thats the reason we pass, first, the `Environment(DEV)`, then the `NewDB` which depends on that `Environment`, following by the `NewGreetService` function which depends on both the `Environment(DEV)` and the `DB`.
The `mvc.Application.Handle` registers a new controller, which depends on the `GreetService`, for the targeted sub-router of `Party("/greet")`.
## Run
Install [Go](https://golang.org/dl) and run the application with:
```sh
go run main.go
```
<details><summary>Docker</summary>
Download the [Dockerfile](https://raw.githubusercontent.com/kataras/iris/9b93c0dbb491dcedf49c91e89ca13bab884d116f/_examples/mvc/overview/Dockerfile) and [docker-compose.yml](https://raw.githubusercontent.com/kataras/iris/9b93c0dbb491dcedf49c91e89ca13bab884d116f/_examples/mvc/overview/docker-compose.yml) files to the `app` folder.
Install [Docker](https://www.docker.com/) and execute the following command:
```sh
$ docker-compose up
```
</details>
Visit http://localhost:8080?name=kataras.
Optionally, replace the `main.go`'s `app.Register(environment.DEV` with `environment.PROD`, restart the application and refresh. You will see that a new database (`sqlite`) and another service of (`greeterWithLogging`) will be binded to the `GreetController`. With **a single change** you achieve to completety change the result.
@@ -0,0 +1,23 @@
package controller
import (
"app/model"
"app/service"
)
// GreetController handles the index.
type GreetController struct {
Service service.GreetService
// Ctx iris.Context
}
// Get serves [GET] /.
// Query: name
func (c *GreetController) Get(req model.Request) (model.Response, error) {
message, err := c.Service.Say(req.Name)
if err != nil {
return model.Response{}, err
}
return model.Response{Message: message}, nil
}
@@ -0,0 +1,20 @@
package database
import "app/environment"
// DB example database interface.
type DB interface {
Exec(q string) error
}
// NewDB returns a database based on "env".
func NewDB(env environment.Env) DB {
switch env {
case environment.PROD:
return &mysql{}
case environment.DEV:
return &sqlite{}
default:
panic("unknown environment")
}
}
+10
View File
@@ -0,0 +1,10 @@
package database
import "fmt"
type mysql struct{}
func (db *mysql) Exec(q string) error {
// simulate an error response.
return fmt.Errorf("mysql: not implemented <%s>", q)
}
@@ -0,0 +1,5 @@
package database
type sqlite struct{}
func (db *sqlite) Exec(q string) error { return nil }
+16
View File
@@ -0,0 +1,16 @@
version: '3.1'
services:
app:
build: .
ports:
- 8080:8080
environment:
PORT: 8080
ENVIRONMENT: development
restart: on-failure
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/ping"]
interval: 30s
timeout: 10s
retries: 5
@@ -0,0 +1,50 @@
package environment
import (
"os"
"strings"
)
// Available environments example.
const (
PROD Env = "production"
DEV Env = "development"
)
// Env is the environment type.
type Env string
// String just returns the string representation of the Env.
func (e Env) String() string {
return string(e)
}
// ReadEnv returns the environment of the system environment variable of "key".
// Returns the "def" if not found.
// Reports a panic message if the environment variable found
// but the Env is unknown.
func ReadEnv(key string, def Env) Env {
v := Getenv(key, def.String())
if v == "" {
return def
}
env := Env(strings.ToLower(v))
switch env {
case PROD, DEV: // allowed.
default:
panic("unexpected environment " + v)
}
return env
}
// Getenv returns the value of a system environment variable "key".
// Defaults to "def" if not found.
func Getenv(key string, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
+62
View File
@@ -0,0 +1,62 @@
module app
go 1.22
require github.com/kataras/iris/v12 v12.2.11-0.20240424221303-df7737ed577a
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/blang/semver/v4 v4.0.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // 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-cmp v0.5.9 // 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/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/mediocregopher/radix/v3 v3.8.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // 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/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
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/yaml.v3 v3.0.1 // indirect
)
+200
View File
@@ -0,0 +1,200 @@
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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
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/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.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.20240424221303-df7737ed577a h1:uaDFlkh2LzTPtoOk/1e6jy/ChoM5/FxTgTiEehlJuD0=
github.com/kataras/iris/v12 v12.2.11-0.20240424221303-df7737ed577a/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.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/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/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-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.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.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 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/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=
+74 -44
View File
@@ -1,13 +1,10 @@
// file: main.go
package main
import (
"github.com/kataras/iris/v12/_examples/mvc/overview/datasource"
"github.com/kataras/iris/v12/_examples/mvc/overview/repositories"
"github.com/kataras/iris/v12/_examples/mvc/overview/services"
"github.com/kataras/iris/v12/_examples/mvc/overview/web/controllers"
"github.com/kataras/iris/v12/_examples/mvc/overview/web/middleware"
"app/controller"
"app/database"
"app/environment"
"app/service"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
@@ -17,44 +14,77 @@ func main() {
app := iris.New()
app.Logger().SetLevel("debug")
// Load the template files.
app.RegisterView(iris.HTML("./web/views", ".html"))
app.Get("/ping", pong).Describe("healthcheck")
// Serve our controllers.
mvc.New(app.Party("/hello")).Handle(new(controllers.HelloController))
// You can also split the code you write to configure an mvc.Application
// using the `mvc.Configure` method, as shown below.
mvc.Configure(app.Party("/movies"), movies)
mvc.Configure(app.Party("/greet"), setup)
// http://localhost:8080/hello
// http://localhost:8080/hello/iris
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Run(
// Start the web server at localhost:8080
iris.Addr("localhost:8080"),
// skip err server closed when CTRL/CMD+C pressed:
iris.WithoutServerError(iris.ErrServerClosed),
// enables faster json serialization and more:
iris.WithOptimizations,
// http://localhost:8080/greet?name=kataras
addr := ":" + environment.Getenv("PORT", "8080")
app.Listen(addr)
}
func pong(ctx iris.Context) {
ctx.WriteString("pong")
}
/*
+-------------------+
| Env (DEV, PROD) |
+---------+---------+
| | |
| | |
| | |
DEV | | | PROD
-------------------+---------------------+ | +----------------------+-------------------
| | |
| | |
+---+-----+ +----------------v------------------+ +----+----+
| sqlite | | NewDB(Env) DB | | mysql |
+---+-----+ +----------------+---+--------------+ +----+----+
| | | |
| | | |
| | | |
+--------------+-----+ +-------------------v---v-----------------+ +----+------+
| greeterWithLogging | | NewGreetService(Env, DB) GreetService | | greeter |
+--------------+-----+ +---------------------------+-------------+ +----+------+
| | |
| | |
| +-----------------------------------------+ |
| | GreetController | | |
| | | | |
| | - Service GreetService <-- | |
| | | |
| +-------------------+---------------------+ |
| | |
| | |
| | |
| +-----------+-----------+ |
| | HTTP Request | |
| +-----------------------+ |
| | /greet?name=kataras | |
| +-----------+-----------+ |
| | |
+------------------+--------+ +------------+------------+ +-------+------------------+
| model.Response (JSON) | | Response (JSON, error) | | Bad Request |
+---------------------------+ +-------------------------+ +--------------------------+
| { | | mysql: not implemented |
| "msg": "Hello kataras" | +--------------------------+
| } |
+---------------------------+
*/
func setup(app *mvc.Application) {
// Register Dependencies.
// Tip: A dependency can depend on other dependencies too.
env := environment.ReadEnv("ENVIRONMENT", environment.DEV)
app.Register(
env, // DEV, PROD
database.NewDB, // sqlite, mysql
service.NewGreetService, // greeterWithLogging, greeter
)
}
// note the mvc.Application, it's not iris.Application.
func movies(app *mvc.Application) {
// Add the basic authentication(admin:password) middleware
// for the /movies based requests.
app.Router.Use(middleware.BasicAuth)
// Create our movie repository with some (memory) data from the datasource.
repo := repositories.NewMovieRepository(datasource.Movies)
// Create our movie service, we will bind it to the movie app's dependencies.
movieService := services.NewMovieService(repo)
app.Register(movieService)
// serve our movies controller.
// Note that you can serve more than one controller
// you can also create child mvc apps using the `movies.Party(relativePath)` or `movies.Clone(app.Party(...))`
// if you want.
app.Handle(new(controllers.MovieController))
// Register Controllers.
app.Handle(new(controller.GreetController))
}
+6
View File
@@ -0,0 +1,6 @@
package model
// Request example incoming request.
type Request struct {
Name string `json:"name" url:"name"`
}
+6
View File
@@ -0,0 +1,6 @@
package model
// Response example server's response.
type Response struct {
Message string `json:"msg"`
}
@@ -0,0 +1,51 @@
package service
import (
"fmt"
"app/database"
"app/environment"
)
// GreetService example service.
type GreetService interface {
Say(input string) (string, error)
}
// NewGreetService returns a service backed with a "db" based on "env".
func NewGreetService(env environment.Env, db database.DB) GreetService {
service := &greeter{db: db, prefix: "Hello"}
switch env {
case environment.PROD:
return service
case environment.DEV:
return &greeterWithLogging{service}
default:
panic("unknown environment")
}
}
type greeter struct {
prefix string
db database.DB
}
func (s *greeter) Say(input string) (string, error) {
if err := s.db.Exec("simulate a query..."); err != nil {
return "", err
}
result := s.prefix + " " + input
return result, nil
}
type greeterWithLogging struct {
*greeter
}
func (s *greeterWithLogging) Say(input string) (string, error) {
result, err := s.greeter.Say(input)
fmt.Printf("result: %s\nerror: %v\n", result, err)
return result, err
}
+3 -3
View File
@@ -4,7 +4,7 @@ package main
/*
There is no MVC naming pattern for such these things,you can imagine the limitations of that.
Instead you can use the `BeforeActivation` on your controller to add more advanced routing features
(https://github.com/kataras/iris/tree/master/_examples/routing).
(https://github.com/kataras/iris/tree/main/_examples/routing).
You can also create your own macro,
i.e: /{file:json} or macro function of a specific parameter type i.e: (/{file:string json()}).
@@ -24,13 +24,13 @@ func main() {
// http://localhost:8080/module/xxx.json (OK)
// http://localhost:8080/module/xxx.xml (Not Found)
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
type myController struct{}
func (m *myController) BeforeActivation(b mvc.BeforeActivation) {
// b.Dependencies().Add/Remove
// b.Dependencies().Register
// b.Router().Use/UseGlobal/Done // and any standard API call you already know
// 1-> Method
@@ -8,7 +8,7 @@ package datamodels
// which could wrap by embedding the datamodels.Movie or
// declare new fields instead butwe will use this datamodel
// as the only one Movie model in our application,
// for the shake of simplicty.
// for the sake of simplicty.
type Movie struct {
ID int64 `json:"id"`
Name string `json:"name"`
@@ -2,7 +2,7 @@
package datasource
import "github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
import "github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
// Movies is our imaginary data source.
var Movies = map[int64]datamodels.Movie{

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+53
View File
@@ -0,0 +1,53 @@
// file: main.go
package main
import (
"github.com/kataras/iris/v12/_examples/mvc/repository/datasource"
"github.com/kataras/iris/v12/_examples/mvc/repository/repositories"
"github.com/kataras/iris/v12/_examples/mvc/repository/services"
"github.com/kataras/iris/v12/_examples/mvc/repository/web/controllers"
"github.com/kataras/iris/v12/_examples/mvc/repository/web/middleware"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
// Load the template files.
app.RegisterView(iris.HTML("./web/views", ".html"))
// Serve our controllers.
mvc.New(app.Party("/hello")).Handle(new(controllers.HelloController))
// You can also split the code you write to configure an mvc.Application
// using the `mvc.Configure` method, as shown below.
mvc.Configure(app.Party("/movies"), movies)
// http://localhost:8080/hello
// http://localhost:8080/hello/iris
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Listen(":8080", iris.WithOptimizations)
}
// note the mvc.Application, it's not iris.Application.
func movies(app *mvc.Application) {
// Add the basic authentication(admin:password) middleware
// for the /movies based requests.
app.Router.Use(middleware.BasicAuth)
// Create our movie repository with some (memory) data from the datasource.
repo := repositories.NewMovieRepository(datasource.Movies)
// Create our movie service, we will bind it to the movie app's dependencies.
movieService := services.NewMovieService(repo)
app.Register(movieService)
// serve our movies controller.
// Note that you can serve more than one controller
// you can also create child mvc apps using the `movies.Party(relativePath)` or `movies.Clone(app.Party(...))`
// if you want.
app.Handle(new(controllers.MovieController))
}
@@ -1,20 +1,20 @@
# Domain Models
There should be the domain/business-level models.
Example:
```go
import "github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
type Movie struct {
datamodels.Movie
}
func (m Movie) Validate() (Movie, error) {
/* do some checks and return an error if that Movie is not valid */
}
```
However, we will use the "datamodels" as the only one models package because
# Domain Models
There should be the domain/business-level models.
Example:
```go
import "github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
type Movie struct {
datamodels.Movie
}
func (m Movie) Validate() (Movie, error) {
/* do some checks and return an error if that Movie is not valid */
}
```
However, we will use the "datamodels" as the only one models package because
Movie structure we don't need any extra functionality or validation inside it.
@@ -1,3 +1,3 @@
# Repositories
# Repositories
The package which has direct access to the "datasource" and can manipulate data directly.
@@ -6,7 +6,7 @@ import (
"errors"
"sync"
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
)
// Query represents the visitor and action queries.
@@ -1,3 +1,3 @@
# Service Layer
# Service Layer
The package which has access to call functions from the "repositories" and "models" ("datamodels" only in that simple example). It should contain the domain logic.
@@ -3,8 +3,8 @@
package services
import (
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/overview/repositories"
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/repository/repositories"
)
// MovieService handles some of the CRUID operations of the movie datamodel.
@@ -5,8 +5,8 @@ package controllers
import (
"errors"
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/overview/services"
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/repository/services"
"github.com/kataras/iris/v12"
)
@@ -23,14 +23,16 @@ type MovieController struct {
// curl -i http://localhost:8080/movies
//
// The correct way if you have sensitive data:
// func (c *MovieController) Get() (results []viewmodels.Movie) {
// data := c.Service.GetAll()
//
// for _, movie := range data {
// results = append(results, viewmodels.Movie{movie})
// }
// return
// }
// func (c *MovieController) Get() (results []viewmodels.Movie) {
// data := c.Service.GetAll()
//
// for _, movie := range data {
// results = append(results, viewmodels.Movie{movie})
// }
// return
// }
//
// otherwise just return the datamodels.
func (c *MovieController) Get() (results []datamodels.Movie) {
return c.Service.GetAll()
@@ -5,8 +5,6 @@ package middleware
import "github.com/kataras/iris/v12/middleware/basicauth"
// BasicAuth middleware sample.
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
var BasicAuth = basicauth.Default(map[string]string{
"admin": "password",
})
@@ -1,56 +1,56 @@
# View Models
There should be the view models, the structure that the client will be able to see.
Example:
```go
import (
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
type Movie struct {
datamodels.Movie
}
func (m Movie) IsValid() bool {
/* do some checks and return true if it's valid... */
return m.ID > 0
}
```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoretically, something like the following is permitted if it's really necessary;
```go
// Dispatch completes the `kataras/iris/mvc#Result` interface.
// Sends a `Movie` as a controlled http response.
// If its ID is zero or less then it returns a 404 not found error
// else it returns its json representation,
// (just like the controller's functions do for custom types by default).
//
// Don't overdo it, the application's logic should not be here.
// It's just one more step of validation before the response,
// simple checks can be added here.
//
// It's just a showcase,
// imagine the potentials this feature gives when designing a bigger application.
//
// This is called where the return value from a controller's method functions
// is type of `Movie`.
// For example the `controllers/movie_controller.go#GetBy`.
func (m Movie) Dispatch(ctx iris.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
```
However, we will use the "datamodels" as the only one models package because
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
# View Models
There should be the view models, the structure that the client will be able to see.
Example:
```go
import (
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
type Movie struct {
datamodels.Movie
}
func (m Movie) IsValid() bool {
/* do some checks and return true if it's valid... */
return m.ID > 0
}
```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoretically, something like the following is permitted if it's really necessary;
```go
// Dispatch completes the `kataras/iris/mvc#Result` interface.
// Sends a `Movie` as a controlled http response.
// If its ID is zero or less then it returns a 404 not found error
// else it returns its json representation,
// (just like the controller's functions do for custom types by default).
//
// Don't overdo it, the application's logic should not be here.
// It's just one more step of validation before the response,
// simple checks can be added here.
//
// It's just a showcase,
// imagine the potentials this feature gives when designing a bigger application.
//
// This is called where the return value from a controller's method functions
// is type of `Movie`.
// For example the `controllers/movie_controller.go#GetBy`.
func (m Movie) Dispatch(ctx iris.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
```
However, we will use the "datamodels" as the only one models package because
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
and we don't need any extra functionality or validation inside it.
@@ -1,12 +1,12 @@
<!-- file: web/views/hello/index.html -->
<html>
<head>
<title>{{.Title}} - My App</title>
</head>
<body>
<p>{{.MyMessage}}</p>
</body>
<!-- file: web/views/hello/index.html -->
<html>
<head>
<title>{{.Title}} - My App</title>
</head>
<body>
<p>{{.MyMessage}}</p>
</body>
</html>
@@ -1,12 +1,12 @@
<!-- file: web/views/hello/name.html -->
<html>
<head>
<title>{{.}}' Portfolio - My App</title>
</head>
<body>
<h1>Hello {{.}}</h1>
</body>
<!-- file: web/views/hello/name.html -->
<html>
<head>
<title>{{.}}' Portfolio - My App</title>
</head>
<body>
<h1>Hello {{.}}</h1>
</body>
</html>
@@ -0,0 +1,77 @@
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
// https://github.com/kataras/iris/issues/1706
// When you need that type of logic behind a request input,
// e.g. set default values, the right way to do that is
// to register a request-scoped dependency for that type.
// We have the `Context.ReadQuery(pointer)`
// which you can use to bind a struct value, that value can have default values.
// Here is how you could do that:
func main() {
app := iris.New()
mvcApp := mvc.New(app)
{
mvcApp.Register(paramsDependency)
mvcApp.Handle(new(controller))
}
// http://localhost:8080/records?phone=random&order_by=DESC
// http://localhost:8080/records?phone=random
app.Listen(":8080")
}
type params struct {
CallID string `url:"phone"`
ComDir int `url:"dir"`
CaseUserID string `url:"on"`
StartYear int `url:"sy"`
EndYear int `url:"ey"`
OrderBy string `url:"order_by"`
Offset int `url:"offset"`
}
// As we've read in the previous examples, the paramsDependency
// describes a request-scoped dependency.
// It should accept the iris context (or any previously registered or builtin dependency)
// and it should return the value which will be binded to the
// controller's methods (or fields) - see `GetRecords`.
var paramsDependency = func(ctx iris.Context) params {
p := params{
OrderBy: "ASC", // default value.
}
// Bind the URL values to the "p":
ctx.ReadQuery(&p)
// Or bind a specific URL value by giving a default value:
// p.OrderBy = ctx.URLParamDefault("order_by", "ASC")
//
// OR make checks for default values after ReadXXX,
// e.g. if p.OrderBy == "" {...}
/* More options to read a request:
// Bind the JSON request body to the "p":
ctx.ReadJSON(&p)
// Bind the Form to the "p":
ctx.ReadForm(&p)
// Bind any, based on the client's content-type header:
ctx.ReadBody(&p)
// Bind the http requests to a struct value:
ctx.ReadHeader(&h)
*/
return p
}
type controller struct{}
func (c *controller) GetRecords(stru params) string {
return fmt.Sprintf("%#+v\n", stru)
}
+11 -23
View File
@@ -11,16 +11,14 @@ import (
// VisitController handles the root route.
type VisitController struct {
// the current request session,
// its initialization happens by the dependency function that we've added to the `visitApp`.
// the current request session, automatically binded.
Session *sessions.Session
// A time.time which is binded from the MVC,
// order of binded fields doesn't matter.
// A time.time which is binded from the MVC application manually.
StartTime time.Time
}
// Get handles
// Get handles index
// Method: GET
// Path: http://localhost:8080
func (c *VisitController) Get() string {
@@ -35,24 +33,14 @@ func (c *VisitController) Get() string {
func newApp() *iris.Application {
app := iris.New()
// Configure sessions manager as we used to.
sess := sessions.New(sessions.Config{Cookie: "mysession_cookie_name"})
app.Use(sess.Handler())
visitApp := mvc.New(app)
visitApp.Register(time.Now())
// The `VisitController.Session` is automatically binded to the current `sessions.Session`.
visitApp := mvc.New(app.Party("/"))
// bind the current *session.Session, which is required, to the `VisitController.Session`
// and the time.Now() to the `VisitController.StartTime`.
visitApp.Register(
// if dependency is a function which accepts
// a Context and returns a single value
// then the result type of this function is resolved by the controller
// and on each request it will call the function with its Context
// and set the result(the *sessions.Session here) to the controller's field.
//
// If dependencies are registered without field or function's input arguments as
// consumers then those dependencies are being ignored before the server ran,
// so you can bind many dependecies and use them in different controllers.
sess.Start,
time.Now(),
)
visitApp.Handle(new(VisitController))
return app
@@ -61,10 +49,10 @@ func newApp() *iris.Application {
func main() {
app := newApp()
// 1. open the browser
// 1. Prepare a client, e.g. your browser
// 2. navigate to http://localhost:8080
// 3. refresh the page some times
// 4. close the browser
// 5. re-open the browser (if it wasn't in private mode) and re-play 2.
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
+1 -1
View File
@@ -13,7 +13,7 @@ func main() {
mvc.New(app.Party("/")).Handle(&globalVisitorsController{visits: 0})
// http://localhost:8080
app.Run(iris.Addr(":8080"))
app.Listen(":8080")
}
type globalVisitorsController struct {
@@ -0,0 +1,62 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
// Optional deprecated X-API-XXX headers for version 1.
var opts = mvc.DeprecationOptions{
WarnMessage: "deprecated, see <this link>",
DeprecationDate: time.Now().UTC(),
DeprecationInfo: "a bigger version is available, see <this link> for more information",
}
func main() {
app := newApp()
// See main_test.go for request examples.
app.Listen(":8080")
}
func newApp() *iris.Application {
app := iris.New()
dataRouter := app.Party("/data")
{
m := mvc.New(dataRouter)
m.Handle(new(v1Controller), mvc.Version("1.0.0"), mvc.Deprecated(opts))
m.Handle(new(v2Controller), mvc.Version("2.3.0"))
m.Handle(new(v3Controller), mvc.Version(">=3.0.0 <4.0.0"))
m.Handle(new(noVersionController)) // or if missing it will respond with 501 version not found.
}
return app
}
type v1Controller struct{}
func (c *v1Controller) Get() string {
return "data (v1.x)"
}
type v2Controller struct{}
func (c *v2Controller) Get() string {
return "data (v2.x)"
}
type v3Controller struct{}
func (c *v3Controller) Get() string {
return "data (v3.x)"
}
type noVersionController struct{}
func (c *noVersionController) Get() string {
return "data"
}
@@ -0,0 +1,34 @@
package main
import (
"testing"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/httptest"
"github.com/kataras/iris/v12/versioning"
)
func TestVersionedController(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1.0.0").Expect().
Status(iris.StatusOK).Body().IsEqual("data (v1.x)")
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "2.3.0").Expect().
Status(iris.StatusOK).Body().IsEqual("data (v2.x)")
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "3.1.0").Expect().
Status(iris.StatusOK).Body().IsEqual("data (v3.x)")
// Test invalid version or no version at all.
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "4.0.0").Expect().
Status(iris.StatusOK).Body().IsEqual("data")
e.GET("/data").Expect().
Status(iris.StatusOK).Body().IsEqual("data")
// Test Deprecated (v1)
ex := e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1.0.0").Expect()
ex.Status(iris.StatusOK).Body().IsEqual("data (v1.x)")
ex.Header("X-API-Warn").Equal(opts.WarnMessage)
expectedDateStr := opts.DeprecationDate.Format(app.ConfigurationReadOnly().GetTimeFormat())
ex.Header("X-API-Deprecation-Date").Equal(expectedDateStr)
}

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