-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE REQUEST] MVC serving gRPC-compatible controller #1449
Comments
Hello @syklevin,
Yes, that's because you can either declare the The Thanks, |
Thanks for reply, here a more concrete example: first of all, we got a grpc services.proto syntax = "proto3";
package api;
message AcctLoginRequest {
string username = 1;
string password = 2;
}
message AcctLoginResponse {
string sessionId = 1;
}
service AcctService {
rpc PostLogin(AcctLoginRequest) returns (AcctLoginResponse) {}
} and here is the main.go, for convenient, I put all the code in one file package main
import (
"context"
"github.com/go-pg/pg/v9"
uuid "github.com/iris-contrib/go.uuid"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"golang.org/x/crypto/bcrypt"
"github.com/syklevin/mvctest/api"
)
var (
//AcctService is default db conn
// DB *pg.DB = connectDB("postgres://dbuser:dbpass@localhost:5432/dbtest?sslmode=disable", 5)
DB *pg.DB = connectDB("postgres://dbuser:dbpass@localhost:5432/dbtest?sslmode=disable", 5)
//AcctService is default service implemented api.AcctServiceServer
AcctService api.AcctServiceServer = &acctService{}
)
func connectDB(dsn string, pool int) *pg.DB {
opts, err := pg.ParseURL(dsn)
if err != nil {
panic(err)
}
opts.PoolSize = pool
db := pg.Connect(opts)
return db
}
type acctModel struct {
ID int64
Login string
Secret string
}
//acctService implemented grpc generated api.AcctServiceServer
type acctService struct{}
func (s *acctService) PostLogin(ctx context.Context, input *api.AcctLoginRequest) (*api.AcctLoginResponse, error) {
acct := &acctModel{}
err := DB.WithContext(ctx).Model(acct).Where("login = ?", input.Username).Select()
if err != nil {
return nil, err
}
err = bcrypt.CompareHashAndPassword([]byte(acct.Secret), []byte(input.Password))
if err != nil {
return nil, err
}
sid, err := uuid.NewV4()
if err != nil {
return nil, err
}
res := &api.AcctLoginResponse{
SessionId: sid.String(),
}
return res, nil
}
type acctServiceWrapper struct{}
func (c *acctServiceWrapper) PostLogin(ctx iris.Context) (*api.AcctLoginResponse, error) {
input := &api.AcctLoginRequest{}
err := ctx.ReadJSON(input)
if err != nil {
return nil, err
}
return AcctService.PostLogin(ctx.Request().Context(), input)
}
func main() {
app := iris.New()
//currently works like this
mvc.New(app.Party("/acct")).Handle(&acctServiceWrapper{})
//could we serve AcctService directly?
// mvc.New(app.Party("/acct2")).Handle(AcctService)
app.Run(iris.Addr(":8080"))
} It is just works, but required write a controller(aka: wrapper) wrapping each the grpc service funcs. |
OK I see, so the mvc.New(app).Register(func(ctx iris.Context) context.Context {
return ctx.Request().Context()
}).Handle(...yourController) Full example code: package main
import (
"context"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
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
}).
Handle(&myController{})
app.Listen(":8080")
}
type myController struct{}
type loginRequest struct {
Username string `json:"username"`
}
type loginResponse struct {
Message string `json:"message"`
}
func (c *myController) PostLogin(ctx context.Context, input loginRequest) (loginResponse, error) {
if ctx == nil { // ctx == iris.Context.Request().Context()
panic("expected ctx")
}
return loginResponse{
Message: input.Username + " logged",
}, nil
} POST: http://localhost:8080/login With body: {
"username":"syklevin"
} Expected output: {
"message": "syklevin logged"
} If that suits your needs @syklevin please close this issue, otherwise tell me to add a builtin binding for the Thanks, |
Yes, that's what i want to achieve and thanks for the example. Beside context.Context, could we have a way to auto bind (without register) the second parameter struct ( |
You are welcome any time! About your second question, yes I suppose we can, with an option like |
Sounds good!, that would be very helpful to serve existing grpc service directly. |
Hello @syklevin I pushed a new release of This new, minor, release brings support for Example Code (NOTE: OLD CODE, Read below comments for a more up-to-dated and better solution): mvc.New(app).
Register(func(ctx iris.Context) context.Context {
return ctx.Request().Context()
}).
Register(mvc.AutoBinding).
Handle(&myController{}) import "context"
func (s *acctService) PostLogin(ctx context.Context, input *api.AcctLoginRequest) (*api.AcctLoginResponse, error) {
acct := &acctModel{}
err := DB.WithContext(ctx).Model(acct).Where("login = ?", input.Username).Select()
if err != nil {
return nil, err
}
err = bcrypt.CompareHashAndPassword([]byte(acct.Secret), []byte(input.Password))
if err != nil {
return nil, err
}
sid, err := uuid.NewV4()
if err != nil {
return nil, err
}
res := &api.AcctLoginResponse{
SessionId: sid.String(),
}
return res, nil
} Please test it and post your thoughts. Thanks, |
I have tried it out and it works like a charm! everything work as expected. |
You are welcome @syklevin as always! Thanks for the feature request, Iris community is grateful! |
@kataras Does this means write in gRPC and serve it as gRPC and HTTP ? Like gRPC-gateway |
@WLun001 At the upcoming Iris release 12.2 that exactly it does, take a look on the fresh example, it contains both http and gRPC clients for testing too: https://github.com/kataras/iris/tree/master/_examples/mvc/grpc-compatible (fetch iris from master and run it) |
@kataras I should have found out Iris earlier |
That means a lot, thanks! I am here to help you migrate from X framework to Iris. |
@kataras , I am currently using gRPC gateway for my project, does it hard to migration if I wanted to? By the way, does it has to use with MVC package?
How to add multiple services? iris/_examples/mvc/grpc-compatible/main.go Lines 49 to 51 in a524ba1
|
The gRPC in Go requires a struct for its service's methods right?
So, It has to live inside mvc because that package already contains the necessary functionality to bind struct's methods for your HTTP service, when gRPC is requested by user, it's delivered by the given grpcServer so you don't lose any performance benefit from using go-grpc.
And yes, you may not use the grpcServer := grpc.NewServer()
app := iris.New()
app.WrapRouter(grpcWrapper.New(grpcServer))
// [...]
app.Listen(...)
The pb.RegisterGreeterServer(grpcServer, service1)
pb.RegisterOtherServiceServer(grpcServer, service2)
mvc.New(app).Handle(service1, mvc.GRPC{
Server: grpcServer,
ServiceName: "helloworld.Service1",
Strict: false,
})
mvc.New(app).Handle(service2, mvc.GRPC{
Server: grpcServer,
ServiceName: "helloworld.Service2",
Strict: false,
}) |
@kataras, does swagger middleware works with gRPC? we just write swagger docs comments on gRPC top of functions |
Hello @WLun001, yes of course it does work, example: // SayHello implements helloworld.GreeterServer.
//
// @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) {
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
} |
@kataras can't wait for v12.2 release to try out |
Former-commit-id: 0cb5121e7d44644f7f0eb34597ff34274157fe95
as requested at: kataras#1449 Former-commit-id: a0af1a78bcfef85f297c5087c8cbb00124226036
Is your feature request related to a problem? Please describe.
currently iris mvc would accept a controller func like
It just like a grpc service func, but when i change iris.Context to context.Context, it cause panic.
Instead writing a wrapper func, could iris.MVC support context.Context as input parameter?
If ture, then all the grpc services could serve by iris.MVC without adding a line.
Describe the solution you'd like
could accept like this
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
The text was updated successfully, but these errors were encountered: