1
0
mirror of https://github.com/gusaul/grpcox.git synced 2024-12-27 19:30:10 +00:00
grpcox/core/grpcox.go

182 lines
4.2 KiB
Go
Raw Normal View History

2018-11-04 19:56:06 +00:00
package core
import (
"context"
"os"
feat(descriptor): add support for local proto descriptor Currently, grpcox depends on server reflection to get proto descriptor. It has a significant drawback, since not every grpc server support [server reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md#known-implementations). Using local proto files is more feasible, as every grpc server certainly have one. Even though using protofile should be simple enough, there's still a problem regarding this. Some protofile use extra plugins for their proto. i.e. gogoprotobuf is a project that does just that. The problems with plugins are most of them require explicit import to the plugin inside of the protofile. It will break grpcurl proto descriptor extraction. Thus, the plugin proto must be uploaded alongside the protofile. Also, the protofile should be modified automatically to change their import to local import. Given that, I proposed a way for the user to upload multiple protofile to grpcox. Then, use that to get the descriptor. Changelog: - Add `use local proto` checkbox in HTML client. On checked it will show upload button and list of selected proto. - `get-service` ajax will use POST when `use local proto` is checked. The uploaded protofile will be the payload for the ajax request. - Add a new route to handle POST "get-service". It will persist the uploaded protofile to `/tmp/` directory and add protos field in the resource. - Modify `openDescriptor` to use local proto if protos field in the resource is available. - Modify `openDescriptor` to return an error, as opening descriptor from local proto may fail. - Modify the main server so it can be shut down gracefully. This is necessary as grpcox need to remove persisted proto right after the server is turned off. This Pull Request will resolve #16
2020-01-29 12:14:41 +00:00
"reflect"
"strconv"
2018-11-04 19:56:06 +00:00
"time"
"github.com/fullstorydev/grpcurl"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
)
// GrpCox - main object
type GrpCox struct {
KeepAlive float64
2019-03-13 03:38:30 +00:00
activeConn *ConnStore
maxLifeConn time.Duration
2018-11-04 19:56:06 +00:00
// TODO : utilize below args
headers []string
reflectHeaders []string
authority string
insecure bool
cacert string
cert string
key string
serverName string
isUnixSocket func() bool
}
feat(descriptor): add support for local proto descriptor Currently, grpcox depends on server reflection to get proto descriptor. It has a significant drawback, since not every grpc server support [server reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md#known-implementations). Using local proto files is more feasible, as every grpc server certainly have one. Even though using protofile should be simple enough, there's still a problem regarding this. Some protofile use extra plugins for their proto. i.e. gogoprotobuf is a project that does just that. The problems with plugins are most of them require explicit import to the plugin inside of the protofile. It will break grpcurl proto descriptor extraction. Thus, the plugin proto must be uploaded alongside the protofile. Also, the protofile should be modified automatically to change their import to local import. Given that, I proposed a way for the user to upload multiple protofile to grpcox. Then, use that to get the descriptor. Changelog: - Add `use local proto` checkbox in HTML client. On checked it will show upload button and list of selected proto. - `get-service` ajax will use POST when `use local proto` is checked. The uploaded protofile will be the payload for the ajax request. - Add a new route to handle POST "get-service". It will persist the uploaded protofile to `/tmp/` directory and add protos field in the resource. - Modify `openDescriptor` to use local proto if protos field in the resource is available. - Modify `openDescriptor` to return an error, as opening descriptor from local proto may fail. - Modify the main server so it can be shut down gracefully. This is necessary as grpcox need to remove persisted proto right after the server is turned off. This Pull Request will resolve #16
2020-01-29 12:14:41 +00:00
// Proto define protofile uploaded from client
// will be used to be persisted to disk and indicator
// whether connections should reflect from server or local proto
type Proto struct {
Name string
Content []byte
}
2019-03-13 03:38:30 +00:00
// InitGrpCox constructor
func InitGrpCox() *GrpCox {
maxLife, tick := 10, 3
if val, err := strconv.Atoi(os.Getenv("MAX_LIFE_CONN")); err == nil {
maxLife = val
}
if val, err := strconv.Atoi(os.Getenv("TICK_CLOSE_CONN")); err == nil {
tick = val
}
c := NewConnectionStore()
g := &GrpCox{
activeConn: c,
}
if maxLife > 0 && tick > 0 {
g.maxLifeConn = time.Duration(maxLife) * time.Minute
c.StartGC(time.Duration(tick) * time.Second)
2019-03-13 03:38:30 +00:00
}
return g
2019-03-13 03:38:30 +00:00
}
2018-11-04 19:56:06 +00:00
// GetResource - open resource to targeted grpc server
2019-03-13 03:38:30 +00:00
func (g *GrpCox) GetResource(ctx context.Context, target string, plainText, isRestartConn bool) (*Resource, error) {
if conn, ok := g.activeConn.getConnection(target); ok {
if !isRestartConn && conn.isValid() {
2019-03-13 03:38:30 +00:00
return conn, nil
}
g.CloseActiveConns(target)
}
2018-11-04 19:56:06 +00:00
var err error
r := new(Resource)
h := append(g.headers, g.reflectHeaders...)
r.md = grpcurl.MetadataFromHeaders(h)
2019-03-13 03:38:30 +00:00
r.clientConn, err = g.dial(ctx, target, plainText)
2018-11-04 19:56:06 +00:00
if err != nil {
return nil, err
}
r.headers = h
g.activeConn.addConnection(target, r, g.maxLifeConn)
2018-11-04 19:56:06 +00:00
return r, nil
}
feat(descriptor): add support for local proto descriptor Currently, grpcox depends on server reflection to get proto descriptor. It has a significant drawback, since not every grpc server support [server reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md#known-implementations). Using local proto files is more feasible, as every grpc server certainly have one. Even though using protofile should be simple enough, there's still a problem regarding this. Some protofile use extra plugins for their proto. i.e. gogoprotobuf is a project that does just that. The problems with plugins are most of them require explicit import to the plugin inside of the protofile. It will break grpcurl proto descriptor extraction. Thus, the plugin proto must be uploaded alongside the protofile. Also, the protofile should be modified automatically to change their import to local import. Given that, I proposed a way for the user to upload multiple protofile to grpcox. Then, use that to get the descriptor. Changelog: - Add `use local proto` checkbox in HTML client. On checked it will show upload button and list of selected proto. - `get-service` ajax will use POST when `use local proto` is checked. The uploaded protofile will be the payload for the ajax request. - Add a new route to handle POST "get-service". It will persist the uploaded protofile to `/tmp/` directory and add protos field in the resource. - Modify `openDescriptor` to use local proto if protos field in the resource is available. - Modify `openDescriptor` to return an error, as opening descriptor from local proto may fail. - Modify the main server so it can be shut down gracefully. This is necessary as grpcox need to remove persisted proto right after the server is turned off. This Pull Request will resolve #16
2020-01-29 12:14:41 +00:00
// GetResourceWithProto - open resource to targeted grpc server using given protofile
func (g *GrpCox) GetResourceWithProto(ctx context.Context, target string, plainText, isRestartConn bool, protos []Proto) (*Resource, error) {
r, err := g.GetResource(ctx, target, plainText, isRestartConn)
if err != nil {
return nil, err
}
// if given protofile is equal to current, skip adding protos as it's already
// persisted in the harddisk anyway
if reflect.DeepEqual(r.protos, protos) {
return r, nil
}
// add protos property to resource and persist it to harddisk
err = r.AddProtos(protos)
return r, err
}
2019-03-13 03:38:30 +00:00
// GetActiveConns - get all saved active connection
func (g *GrpCox) GetActiveConns(ctx context.Context) []string {
active := g.activeConn.getAllConn()
result := make([]string, len(active))
2019-03-13 03:38:30 +00:00
i := 0
for k := range active {
2019-03-13 03:38:30 +00:00
result[i] = k
i++
}
return result
}
// CloseActiveConns - close conn by host or all
func (g *GrpCox) CloseActiveConns(host string) error {
if host == "all" {
for k := range g.activeConn.getAllConn() {
g.activeConn.delete(k)
2019-03-13 03:38:30 +00:00
}
return nil
}
g.activeConn.delete(host)
2019-03-13 03:38:30 +00:00
return nil
}
// Extend extend connection based on setting max life
func (g *GrpCox) Extend(host string) {
g.activeConn.extend(host, g.maxLifeConn)
}
2019-03-13 03:38:30 +00:00
func (g *GrpCox) dial(ctx context.Context, target string, plainText bool) (*grpc.ClientConn, error) {
2018-11-04 19:56:06 +00:00
dialTime := 10 * time.Second
ctx, cancel := context.WithTimeout(ctx, dialTime)
defer cancel()
var opts []grpc.DialOption
// keep alive
if g.KeepAlive > 0 {
timeout := time.Duration(g.KeepAlive * float64(time.Second))
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: timeout,
Timeout: timeout,
}))
}
if g.authority != "" {
opts = append(opts, grpc.WithAuthority(g.authority))
}
var creds credentials.TransportCredentials
2019-03-13 03:38:30 +00:00
if !plainText {
2018-11-04 19:56:06 +00:00
var err error
creds, err = grpcurl.ClientTransportCredentials(g.insecure, g.cacert, g.cert, g.key)
if err != nil {
return nil, err
}
if g.serverName != "" {
if err := creds.OverrideServerName(g.serverName); err != nil {
return nil, err
}
}
}
network := "tcp"
if g.isUnixSocket != nil && g.isUnixSocket() {
network = "unix"
}
cc, err := grpcurl.BlockingDial(ctx, network, target, creds, opts...)
if err != nil {
return nil, err
}
return cc, nil
}