1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-17 09:57:01 +00:00

New: gRPC MVC features, new WithLowercaseRouting option and add some new context methods

read HISTORY.md


Former-commit-id: 30a16cceb11f754aa32923058abeda1e736350e7
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-04-25 02:30:19 +03:00
parent 0cf5d5a4a3
commit 5d3c96947c
21 changed files with 566 additions and 185 deletions

View File

@@ -2,6 +2,7 @@ package mvc
import (
"fmt"
"os"
"reflect"
"strings"
@@ -81,6 +82,9 @@ type ControllerActivator struct {
// true if this controller listens and serves to websocket events.
servesWebsocket bool
// true to skip the internal "activate".
activated bool
}
// NameOf returns the package name + the struct type's name,
@@ -96,6 +100,14 @@ func NameOf(v interface{}) string {
}
func newControllerActivator(app *Application, controller interface{}) *ControllerActivator {
if controller == nil {
return nil
}
if c, ok := controller.(*ControllerActivator); ok {
return c
}
typ := reflect.TypeOf(controller)
c := &ControllerActivator{
@@ -225,6 +237,11 @@ func (c *ControllerActivator) isReservedMethod(name string) bool {
return false
}
func (c *ControllerActivator) markAsWebsocket() {
c.servesWebsocket = true
c.attachInjector()
}
func (c *ControllerActivator) attachInjector() {
if c.injector == nil {
partyCountParams := macro.CountParams(c.app.Router.GetRelPath(), *c.app.Router.Macros())
@@ -232,12 +249,18 @@ func (c *ControllerActivator) attachInjector() {
}
}
func (c *ControllerActivator) markAsWebsocket() {
c.servesWebsocket = true
c.attachInjector()
// Activated can be called to skip the internal method parsing.
func (c *ControllerActivator) Activated() bool {
b := c.activated
c.activated = true
return b
}
func (c *ControllerActivator) activate() {
if c.Activated() {
return
}
c.parseMethods()
}
@@ -314,10 +337,16 @@ func (c *ControllerActivator) handleMany(method, path, funcName string, override
return nil
}
wd, _ := os.Getwd()
for _, r := range routes {
// change the main handler's name in order to respect the controller's and give
// a proper debug message.
// change the main handler's name and file:line
// in order to respect the controller's and give
// a proper debug/log message.
r.MainHandlerName = fmt.Sprintf("%s.%s", c.fullName, funcName)
if m, ok := c.Type.MethodByName(funcName); ok {
r.SourceFileName, r.SourceLineNumber = context.HandlerFileLineRel(m.Func, wd)
}
}
// add this as a reserved method name in order to

66
mvc/grpc.go Normal file
View File

@@ -0,0 +1,66 @@
package mvc
import (
"net/http"
"path"
"github.com/kataras/iris/v12/context"
)
// GRPC registers a controller which serves gRPC clients.
// It accepts the controller ptr to a struct value,
// the gRPCServer itself, and a strict option which is explained below.
//
// The differences by a common controller are:
// HTTP verb: only POST (Party.AllowMethods can be used for more),
// method parsing is disabled: path is the function name as it is,
// if 'strictMode' option is true then this controller will only serve gRPC-based clients
// and fires 404 on common HTTP clients,
// otherwise HTTP clients can send and receive JSON (protos contain json struct fields by-default).
type GRPC struct {
// Server is required and should be gRPC Server derives from google's grpc package.
Server http.Handler
// ServiceName is required and should be the name of the service (used to build the gRPC route path),
// e.g. "helloworld.Greeter".
// For a controller's method of "SayHello" and ServiceName "helloworld.Greeter",
// both gRPC and common HTTP request path is: "/helloworld.Greeter/SayHello".
//
// Tip: the ServiceName can be fetched through proto's file descriptor, e.g.
// serviceName := pb.File_helloworld_proto.Services().Get(0).FullName().
ServiceName string
// When Strict option is true then this controller will only serve gRPC-based clients
// and fires 404 on common HTTP clients.
Strict bool
}
// Apply parses the controller's methods and registers gRPC handlers to the application.
func (g GRPC) Apply(c *ControllerActivator) {
defer c.Activated()
pre := func(ctx context.Context) {
if ctx.IsGRPC() { // gRPC, consumes and produces protobuf.
g.Server.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
ctx.StopExecution()
return
}
if g.Strict {
ctx.NotFound()
} else {
// Allow common HTTP clients, consumes and produces JSON.
ctx.Next()
}
}
for i := 0; i < c.Type.NumMethod(); i++ {
m := c.Type.Method(i)
path := path.Join(g.ServiceName, m.Name)
if route := c.Handle(http.MethodPost, path, m.Name, pre); route != nil {
route.Description = "gRPC"
if g.Strict {
route.Description = "-only"
}
}
}
}

View File

@@ -111,6 +111,15 @@ func (app *Application) Register(dependencies ...interface{}) *Application {
return app
}
// Option is an interface which does contain a single `Apply` method that accepts
// a `ControllerActivator`. It can be passed on `Application.Handle` method to
// mdoify the behavior right after the `BeforeActivation` state.
//
// See `GRPC` package-level structure too.
type Option interface {
Apply(*ControllerActivator)
}
// Handle serves a controller for the current mvc application's Router.
// It accept any custom struct which its functions will be transformed
// to routes.
@@ -154,9 +163,12 @@ func (app *Application) Register(dependencies ...interface{}) *Application {
// Result or (Result, error)
// where Get is an HTTP Method func.
//
// Default behavior can be changed through second, variadic, variable "options",
// e.g. Handle(controller, GRPC {Server: grpcServer, Strict: true})
//
// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc
func (app *Application) Handle(controller interface{}) *Application {
app.handle(controller)
func (app *Application) Handle(controller interface{}, options ...Option) *Application {
app.handle(controller, options...)
return app
}
@@ -195,7 +207,7 @@ func (app *Application) GetNamespaces() websocket.Namespaces {
return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces()
}
func (app *Application) handle(controller interface{}) *ControllerActivator {
func (app *Application) handle(controller interface{}, options ...Option) *ControllerActivator {
// initialize the controller's activator, nothing too magical so far.
c := newControllerActivator(app, controller)
@@ -208,6 +220,12 @@ func (app *Application) handle(controller interface{}) *ControllerActivator {
before.BeforeActivation(c)
}
for _, opt := range options {
if opt != nil {
opt.Apply(c)
}
}
c.activate()
if after, okAfter := controller.(interface {