github.com/v2fly/v2ray-core/v4@v4.45.2/infra/control/api.go (about)

     1  package control
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/golang/protobuf/proto"
    12  	"google.golang.org/grpc"
    13  
    14  	logService "github.com/v2fly/v2ray-core/v4/app/log/command"
    15  	statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
    16  	"github.com/v2fly/v2ray-core/v4/common"
    17  )
    18  
    19  type APICommand struct{}
    20  
    21  func (c *APICommand) Name() string {
    22  	return "api"
    23  }
    24  
    25  func (c *APICommand) Description() Description {
    26  	return Description{
    27  		Short: "Call V2Ray API",
    28  		Usage: []string{
    29  			"v2ctl api [--server=127.0.0.1:8080] Service.Method Request",
    30  			"Call an API in an V2Ray process.",
    31  			"The following methods are currently supported:",
    32  			"\tLoggerService.RestartLogger",
    33  			"\tStatsService.GetStats",
    34  			"\tStatsService.QueryStats",
    35  			"API calls in this command have a timeout to the server of 3 seconds.",
    36  			"Examples:",
    37  			"v2ctl api --server=127.0.0.1:8080 LoggerService.RestartLogger '' ",
    38  			"v2ctl api --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: \"\" reset: false'",
    39  			"v2ctl api --server=127.0.0.1:8080 StatsService.GetStats 'name: \"inbound>>>statin>>>traffic>>>downlink\" reset: false'",
    40  			"v2ctl api --server=127.0.0.1:8080 StatsService.GetSysStats ''",
    41  		},
    42  	}
    43  }
    44  
    45  func (c *APICommand) Execute(args []string) error {
    46  	fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
    47  
    48  	serverAddrPtr := fs.String("server", "127.0.0.1:8080", "Server address")
    49  
    50  	if err := fs.Parse(args); err != nil {
    51  		return err
    52  	}
    53  
    54  	unnamedArgs := fs.Args()
    55  	if len(unnamedArgs) < 2 {
    56  		return newError("service name or request not specified.")
    57  	}
    58  
    59  	service, method := getServiceMethod(unnamedArgs[0])
    60  	handler, found := serivceHandlerMap[strings.ToLower(service)]
    61  	if !found {
    62  		return newError("unknown service: ", service)
    63  	}
    64  
    65  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    66  	defer cancel()
    67  
    68  	conn, err := grpc.DialContext(ctx, *serverAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
    69  	if err != nil {
    70  		return newError("failed to dial ", *serverAddrPtr).Base(err)
    71  	}
    72  	defer conn.Close()
    73  
    74  	response, err := handler(ctx, conn, method, unnamedArgs[1])
    75  	if err != nil {
    76  		return newError("failed to call service ", unnamedArgs[0]).Base(err)
    77  	}
    78  
    79  	fmt.Println(response)
    80  	return nil
    81  }
    82  
    83  func getServiceMethod(s string) (string, string) {
    84  	ss := strings.Split(s, ".")
    85  	service := ss[0]
    86  	var method string
    87  	if len(ss) > 1 {
    88  		method = ss[1]
    89  	}
    90  	return service, method
    91  }
    92  
    93  type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error)
    94  
    95  var serivceHandlerMap = map[string]serviceHandler{
    96  	"statsservice":  callStatsService,
    97  	"loggerservice": callLogService,
    98  }
    99  
   100  func callLogService(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) {
   101  	client := logService.NewLoggerServiceClient(conn)
   102  
   103  	switch strings.ToLower(method) {
   104  	case "restartlogger":
   105  		r := &logService.RestartLoggerRequest{}
   106  		if err := proto.UnmarshalText(request, r); err != nil {
   107  			return "", err
   108  		}
   109  		resp, err := client.RestartLogger(ctx, r)
   110  		if err != nil {
   111  			return "", err
   112  		}
   113  		return proto.MarshalTextString(resp), nil
   114  	default:
   115  		return "", errors.New("Unknown method: " + method)
   116  	}
   117  }
   118  
   119  func callStatsService(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) {
   120  	client := statsService.NewStatsServiceClient(conn)
   121  
   122  	switch strings.ToLower(method) {
   123  	case "getstats":
   124  		r := &statsService.GetStatsRequest{}
   125  		if err := proto.UnmarshalText(request, r); err != nil {
   126  			return "", err
   127  		}
   128  		resp, err := client.GetStats(ctx, r)
   129  		if err != nil {
   130  			return "", err
   131  		}
   132  		return proto.MarshalTextString(resp), nil
   133  	case "querystats":
   134  		r := &statsService.QueryStatsRequest{}
   135  		if err := proto.UnmarshalText(request, r); err != nil {
   136  			return "", err
   137  		}
   138  		resp, err := client.QueryStats(ctx, r)
   139  		if err != nil {
   140  			return "", err
   141  		}
   142  		return proto.MarshalTextString(resp), nil
   143  	case "getsysstats":
   144  		// SysStatsRequest is an empty message
   145  		r := &statsService.SysStatsRequest{}
   146  		resp, err := client.GetSysStats(ctx, r)
   147  		if err != nil {
   148  			return "", err
   149  		}
   150  		return proto.MarshalTextString(resp), nil
   151  	default:
   152  		return "", errors.New("Unknown method: " + method)
   153  	}
   154  }
   155  
   156  func init() {
   157  	common.Must(RegisterCommand(&APICommand{}))
   158  }