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