Skip to content

Commit

Permalink
Create a grpc gateway on the same port as the gin server.
Browse files Browse the repository at this point in the history
* Using the same port avoids CORS issues with the client
  • Loading branch information
jlewi committed Apr 5, 2024
1 parent 7f2efc9 commit a9cf704
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 24 deletions.
95 changes: 92 additions & 3 deletions app/pkg/server/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"context"
"encoding/json"
"fmt"
"html/template"
Expand All @@ -15,13 +16,18 @@ import (

"github.com/gin-gonic/gin"
"github.com/go-logr/zapr"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/jlewi/foyle/app/pkg/config"
"github.com/jlewi/foyle/app/pkg/executor"
"github.com/jlewi/foyle/app/pkg/logs"
"github.com/jlewi/foyle/protos/go/foyle/v1alpha1"
"github.com/pkg/errors"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
Expand All @@ -36,6 +42,7 @@ type Server struct {
builtinExtensionPaths []string

executor *executor.Executor
conn *grpc.ClientConn
}

// NewServer creates a new server
Expand All @@ -62,7 +69,7 @@ func (s *Server) createGinEngine() error {
log.Info("Setting up server")

router := gin.Default()
router.GET("/healthz", healthCheck)
router.GET("/healthz", s.healthCheck)

// Serve the static assets for vscode.
// There should be several directories located in ${ASSETS_DIR}/vscode
Expand Down Expand Up @@ -112,6 +119,7 @@ func (s *Server) createGinEngine() error {

// The workbench endpoint serves the workbench.html page which is the main entry point for vscode for web
router.GET("/workbench", s.handleGetWorkbench)

s.engine = router
return nil
}
Expand Down Expand Up @@ -219,6 +227,9 @@ func (s *Server) Run() error {

}()

if err := s.registerGRPCGatewayRoutes(); err != nil {
return err
}
address := fmt.Sprintf("%s:%d", s.config.Server.BindAddress, s.config.Server.HttpPort)
log.Info("Starting http server", "address", address)
if err := http.ListenAndServe(address, s.engine); err != nil {
Expand Down Expand Up @@ -249,6 +260,72 @@ func (s *Server) startGRPCServer(lis net.Listener) error {
return s.grpcServer.Serve(lis)
}

// registerGRPCGateway starts the gRPC gateway which provides a REST proxy to the grpc server.
func (s *Server) registerGRPCGatewayRoutes() error {
// TODO(jeremy): I think we could use a ctx with Cancel and then potentially trigger cancel to shutdown the
// connection.
ctx := context.Background()

// Create a connection to the gRPC server
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}

log := zapr.NewLogger(zap.L())

grpcServerEndpoint := fmt.Sprintf("%s:%d", s.config.Server.BindAddress, s.config.Server.GRPCPort)
log.Info("Dialing grpc server", "endpoint", grpcServerEndpoint)
conn, err := grpc.DialContext(ctx, grpcServerEndpoint, opts...)
if err != nil {
return err
}
go func() {
<-ctx.Done()
if err := conn.Close(); err != nil {
grpclog.Errorf("failed to close connection to the gRPC server: %v", err)
}
}()
s.conn = conn
log.Info("Connected to grpc server", "connectionState", conn.GetState())

// TODO(jeremy): Should we add a handler for openapi spec; e.g.
// https://github.com/grpc-ecosystem/grpc-gateway/blob/10d49ec19ecab090aa3318245e3fe0d5db666c3f/examples/internal/gateway/main.go#L51C2-L51C49

gwMux := runtime.NewServeMux()

if err := v1alpha1.RegisterExecuteServiceHandler(ctx, gwMux, conn); err != nil {
return err
}

// Configure gin to delegate to the grpc gateway
handleFunc := func(c *gin.Context) {
log.V(logs.Debug).Info("Delegating request to grpc gateway")
gwMux.ServeHTTP(c.Writer, c.Request)
}

// N.B since we want to to server our grpc gateway on the same port as our gin server
// we need to configure the gin server to delegate to the gateway mux for the appropriate routes.
// There currently doesn't seem to be anyway to do this programmatically. So if we add new routes we'd
// have to update the code here.
pathPrefix := "/api/v1alpha1"

type method struct {
Method string
Path string
}

methods := []method{
{Method: http.MethodPost, Path: "execute"},
{Method: http.MethodPost, Path: "generate"},
}

for _, m := range methods {
fullPath := pathPrefix + "/" + m.Path
log.Info("configuring gin to delegate to the grpc gateway", "path", fullPath, "methods", m.Method)
s.engine.Handle(m.Method, fullPath, handleFunc)
}

return nil
}

// trapInterrupt shutdowns the server if the appropriate signals are sent
func trapInterrupt(s *grpc.Server) {
log := zapr.NewLogger(zap.L())
Expand All @@ -266,6 +343,18 @@ func trapInterrupt(s *grpc.Server) {
}()
}

func healthCheck(ctx *gin.Context) {
ctx.String(http.StatusOK, "foyle is healthy")
func (s *Server) healthCheck(ctx *gin.Context) {
// TODO(jeremy): We should return the version
connState := s.conn.GetState()
d := gin.H{
"server": "foyle",
"status": "healthy",
"grpcConnectionState": connState.String(),
}
code := http.StatusOK
if connState != connectivity.Ready {
d["status"] = "unhealthy"
code = http.StatusServiceUnavailable
}
ctx.JSON(code, d)
}
9 changes: 7 additions & 2 deletions protos/foyle/v1alpha1/agent.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ option go_package = "github.com/jlewi/foyle/protos/go/v1alpha1";
// https://github.com/grpc-ecosystem/grpc-gateway
import "google/api/annotations.proto";


// IMPORTANT:
// If you update or add any new methods to this file then you need to update
// Server.registerGRPCGatewayRoutes to have gin delegate the appropriate requests to the grpc gateway server.

message GenerateRequest {
Doc doc = 1;
}
Expand All @@ -22,7 +27,7 @@ service GenerateService {
// Generate generates new cells given an existing document.
rpc Generate (GenerateRequest) returns (GenerateResponse) {
option (google.api.http) = {
post: "/v1alpha1/generate"
post: "/api/v1alpha1/generate"
};
}
}
Expand All @@ -40,7 +45,7 @@ service ExecuteService {
// Execute executes a cell in an existing document.
rpc Execute(ExecuteRequest) returns (ExecuteResponse) {
option (google.api.http) = {
post: "/v1alpha1/execute"
post: "/api/v1alpha1/execute"
};
}
}
27 changes: 14 additions & 13 deletions protos/go/foyle/v1alpha1/agent.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions protos/go/foyle/v1alpha1/agent.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a9cf704

Please sign in to comment.