diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..8b3231d --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,5 @@ +*.pb.go +*.pd + +/gateway/gateway +/helloworld/server/server diff --git a/examples/gateway/main.go b/examples/gateway/main.go new file mode 100644 index 0000000..73b3355 --- /dev/null +++ b/examples/gateway/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "encoding/json" + "flag" + "log" + "net/http" + "os" + + "github.com/vizee/gapi/engine" + "github.com/vizee/gapi/handlers/httpview" + "github.com/vizee/gapi/handlers/jsonapi" + "github.com/vizee/gapi/metadata" + "github.com/vizee/gapi/proto/descriptor" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" +) + +func writeJsonResponse(w http.ResponseWriter, o any) { + w.Header().Set("Content-Type", "application/json") + data, _ := json.Marshal(o) + _, _ = w.Write(data) +} + +func newEngine() *engine.Engine { + builder := engine.NewBuilder() + + builder.RegisterHandler("httpview", &httpview.Handler{ + PassPath: true, + PassQuery: true, + PassParams: true, + CopyHeaders: true, + FilterHeaders: []string{"Content-Type", "User-Agent"}, + MaxBodySize: 1 * 1024 * 1024, + }) + builder.RegisterHandler("jsonapi", &jsonapi.Handler{}) + + builder.RegisterMiddleware("sign", func(ctx *engine.Context) error { + sign := ctx.Request().URL.Query().Get("sign") + if sign == "good" { + return ctx.Next() + } else { + http.Error(ctx.Response(), http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return nil + } + }) + + builder.RegisterMiddleware("auth", func(ctx *engine.Context) error { + uid := ctx.Request().URL.Query().Get("uid") + if uid != "" { + ctx.Set("uid", uid) + return ctx.Next() + } else { + http.Error(ctx.Response(), http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return nil + } + }) + + builder.Use(func(ctx *engine.Context) error { + log.Printf("<> %s %s", ctx.Request().Method, ctx.Request().URL.String()) + return ctx.Next() + }) + builder.Use(func(ctx *engine.Context) error { + err := ctx.Next() + if err != nil { + if s, ok := status.FromError(err); ok { + if s.Code() >= 1000 { + writeJsonResponse(ctx.Response(), map[string]any{ + "code": s.Code(), + "message": s.Message(), + }) + return nil + } + } + log.Printf("ERROR %s %v", ctx.Request().RequestURI, err) + http.Error(ctx.Response(), http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return nil + }) + + builder.NotFound(func(ctx *engine.Context) error { + http.NotFound(ctx.Response(), ctx.Request()) + return nil + }) + + return builder.Build() +} + +func loadRoutes(fname string) ([]metadata.Route, error) { + data, err := os.ReadFile(fname) + if err != nil { + return nil, err + } + + var fds descriptorpb.FileDescriptorSet + err = proto.Unmarshal(data, &fds) + if err != nil { + return nil, err + } + p := descriptor.NewParser() + for _, fd := range fds.File { + err := p.AddFile(fd) + if err != nil { + return nil, err + } + } + + return metadata.ResolveRoutes(&metadata.MessageCache{}, p.Services(), false) +} + +func main() { + var ( + pdFile string + listenAddr string + ) + flag.StringVar(&pdFile, "pd", "file.pd", "pd file") + flag.StringVar(&listenAddr, "l", ":8080", "listen address") + flag.Parse() + + engine := newEngine() + routes, err := loadRoutes(pdFile) + if err != nil { + log.Fatalf("loadRoutes: %v", err) + } + err = engine.RebuildRouter(routes, false) + if err != nil { + log.Fatalf("RebuildRouter: %v", err) + } + + log.Printf("listening: %s", listenAddr) + + err = http.ListenAndServe(listenAddr, engine) + if err != nil { + log.Fatalf("srv.ListenAndServe: %v", err) + } +} diff --git a/examples/helloworld/proto/Makefile b/examples/helloworld/proto/Makefile new file mode 100644 index 0000000..86c628c --- /dev/null +++ b/examples/helloworld/proto/Makefile @@ -0,0 +1,6 @@ +GAPI_PROTO ?= . + +proto: + @protoc -I . -I $(GAPI_PROTO) --descriptor_set_out=helloworld.pd \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld.proto diff --git a/examples/helloworld/proto/helloworld.proto b/examples/helloworld/proto/helloworld.proto new file mode 100644 index 0000000..0a350aa --- /dev/null +++ b/examples/helloworld/proto/helloworld.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package gapi.examples.helloworld.proto; + +option go_package = "github.com/vizee/gapi/examples/helloworld/proto"; + +import "gapi/annotation.proto"; + +service Greeter { + option (gapi.server) = "localhost:50051"; + option (gapi.default_handler) = "jsonapi"; + option (gapi.default_timeout) = 5000; + option (gapi.path_prefix) = "/greeter"; + option (gapi.use) = "sign"; + + rpc SayHello (HelloRequest) returns (HelloReply) { + option (gapi.http) = { + post: "/sayHello" + use: "auth" + }; + } +} + +message HelloRequest { + string name = 1; + string userId = 2 [(gapi.alias) = "uid", (gapi.bind) = FROM_CONTEXT]; +} + +message HelloReply { + string message = 1; +} diff --git a/examples/helloworld/server/main.go b/examples/helloworld/server/main.go new file mode 100644 index 0000000..3c401db --- /dev/null +++ b/examples/helloworld/server/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "fmt" + "log" + "net" + + "github.com/vizee/gapi/examples/helloworld/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +type Greeter struct { + proto.UnimplementedGreeterServer +} + +func (*Greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) { + log.Printf("SayHello: %s", req) + + if len(req.Name) < 3 { + return nil, status.Errorf(1000, "name too short") + } + + return &proto.HelloReply{ + Message: fmt.Sprintf("Hello %s(%s)", req.Name, req.UserId), + }, nil +} + +func main() { + const listenAddr = ":50051" + + srv := grpc.NewServer() + proto.RegisterGreeterServer(srv, &Greeter{}) + ln, err := net.Listen("tcp", listenAddr) + if err != nil { + log.Fatalf("listen: %v", err) + } + + log.Printf("listening: %s", listenAddr) + + err = srv.Serve(ln) + if err != nil { + log.Fatalf("serve: %v", err) + } +}