improve commands

(rebased from ebbf31f07e)
This commit is contained in:
Jebbs 2021-02-21 23:02:42 +08:00 committed by Shelikhoo
parent fa0cf6db26
commit 2523d77919
No known key found for this signature in database
GPG Key ID: C4D5E79D22B25316
67 changed files with 1723 additions and 1220 deletions

View File

@ -4,14 +4,17 @@ package command
import (
"context"
"time"
grpc "google.golang.org/grpc"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/app/log"
"github.com/v2fly/v2ray-core/v4/common"
cmlog "github.com/v2fly/v2ray-core/v4/common/log"
)
// LoggerServer is the implemention of LoggerService
type LoggerServer struct {
V *core.Instance
}
@ -31,6 +34,34 @@ func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLogger
return &RestartLoggerResponse{}, nil
}
// FollowLog implements LoggerService.
func (s *LoggerServer) FollowLog(_ *FollowLogRequest, stream LoggerService_FollowLogServer) error {
logger := s.V.GetFeature((*log.Instance)(nil))
if logger == nil {
return newError("unable to get logger instance")
}
follower, ok := logger.(cmlog.Follower)
if !ok {
return newError("logger not support following")
}
var err error
f := func(msg cmlog.Message) {
err = stream.Send(&FollowLogResponse{
Message: msg.String(),
})
}
follower.AddFollower(f)
defer follower.RemoveFollower(f)
ticker := time.NewTicker(time.Second)
for {
<-ticker.C
if err != nil {
ticker.Stop()
return nil
}
}
}
func (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {}
type service struct {

View File

@ -134,6 +134,91 @@ func (*RestartLoggerResponse) Descriptor() ([]byte, []int) {
return file_app_log_command_config_proto_rawDescGZIP(), []int{2}
}
type FollowLogRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *FollowLogRequest) Reset() {
*x = FollowLogRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_app_log_command_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FollowLogRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FollowLogRequest) ProtoMessage() {}
func (x *FollowLogRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_log_command_config_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FollowLogRequest.ProtoReflect.Descriptor instead.
func (*FollowLogRequest) Descriptor() ([]byte, []int) {
return file_app_log_command_config_proto_rawDescGZIP(), []int{3}
}
type FollowLogResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *FollowLogResponse) Reset() {
*x = FollowLogResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_app_log_command_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FollowLogResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FollowLogResponse) ProtoMessage() {}
func (x *FollowLogResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_log_command_config_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FollowLogResponse.ProtoReflect.Descriptor instead.
func (*FollowLogResponse) Descriptor() ([]byte, []int) {
return file_app_log_command_config_proto_rawDescGZIP(), []int{4}
}
func (x *FollowLogResponse) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_app_log_command_config_proto protoreflect.FileDescriptor
var file_app_log_command_config_proto_rawDesc = []byte{
@ -144,23 +229,34 @@ var file_app_log_command_config_proto_rawDesc = []byte{
0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c,
0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15,
0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x74, 0x61,
0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67,
0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c,
0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42,
0x6f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65,
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x4c,
0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2d, 0x0a, 0x11, 0x46, 0x6f, 0x6c,
0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18,
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0xf5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67,
0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x0d, 0x52, 0x65,
0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x76, 0x32,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74,
0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c,
0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61,
0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x6c, 0x0a, 0x09, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x12,
0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x46, 0x6f, 0x6c,
0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c,
0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f,
0x77, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01,
0x42, 0x6f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72,
0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -175,17 +271,21 @@ func file_app_log_command_config_proto_rawDescGZIP() []byte {
return file_app_log_command_config_proto_rawDescData
}
var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_app_log_command_config_proto_goTypes = []interface{}{
(*Config)(nil), // 0: v2ray.core.app.log.command.Config
(*RestartLoggerRequest)(nil), // 1: v2ray.core.app.log.command.RestartLoggerRequest
(*RestartLoggerResponse)(nil), // 2: v2ray.core.app.log.command.RestartLoggerResponse
(*FollowLogRequest)(nil), // 3: v2ray.core.app.log.command.FollowLogRequest
(*FollowLogResponse)(nil), // 4: v2ray.core.app.log.command.FollowLogResponse
}
var file_app_log_command_config_proto_depIdxs = []int32{
1, // 0: v2ray.core.app.log.command.LoggerService.RestartLogger:input_type -> v2ray.core.app.log.command.RestartLoggerRequest
2, // 1: v2ray.core.app.log.command.LoggerService.RestartLogger:output_type -> v2ray.core.app.log.command.RestartLoggerResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
3, // 1: v2ray.core.app.log.command.LoggerService.FollowLog:input_type -> v2ray.core.app.log.command.FollowLogRequest
2, // 2: v2ray.core.app.log.command.LoggerService.RestartLogger:output_type -> v2ray.core.app.log.command.RestartLoggerResponse
4, // 3: v2ray.core.app.log.command.LoggerService.FollowLog:output_type -> v2ray.core.app.log.command.FollowLogResponse
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@ -233,6 +333,30 @@ func file_app_log_command_config_proto_init() {
return nil
}
}
file_app_log_command_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FollowLogRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_app_log_command_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FollowLogResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -240,7 +364,7 @@ func file_app_log_command_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_log_command_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumMessages: 5,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -12,6 +12,13 @@ message RestartLoggerRequest {}
message RestartLoggerResponse {}
message FollowLogRequest {}
message FollowLogResponse {
string message = 1;
}
service LoggerService {
rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {}
rpc FollowLog(FollowLogRequest) returns (stream FollowLogResponse) {};
}

View File

@ -19,6 +19,7 @@ const _ = grpc.SupportPackageIsVersion7
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type LoggerServiceClient interface {
RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error)
FollowLog(ctx context.Context, in *FollowLogRequest, opts ...grpc.CallOption) (LoggerService_FollowLogClient, error)
}
type loggerServiceClient struct {
@ -38,11 +39,44 @@ func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLogg
return out, nil
}
func (c *loggerServiceClient) FollowLog(ctx context.Context, in *FollowLogRequest, opts ...grpc.CallOption) (LoggerService_FollowLogClient, error) {
stream, err := c.cc.NewStream(ctx, &LoggerService_ServiceDesc.Streams[0], "/v2ray.core.app.log.command.LoggerService/FollowLog", opts...)
if err != nil {
return nil, err
}
x := &loggerServiceFollowLogClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type LoggerService_FollowLogClient interface {
Recv() (*FollowLogResponse, error)
grpc.ClientStream
}
type loggerServiceFollowLogClient struct {
grpc.ClientStream
}
func (x *loggerServiceFollowLogClient) Recv() (*FollowLogResponse, error) {
m := new(FollowLogResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// LoggerServiceServer is the server API for LoggerService service.
// All implementations must embed UnimplementedLoggerServiceServer
// for forward compatibility
type LoggerServiceServer interface {
RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)
FollowLog(*FollowLogRequest, LoggerService_FollowLogServer) error
mustEmbedUnimplementedLoggerServiceServer()
}
@ -53,6 +87,9 @@ type UnimplementedLoggerServiceServer struct {
func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RestartLogger not implemented")
}
func (UnimplementedLoggerServiceServer) FollowLog(*FollowLogRequest, LoggerService_FollowLogServer) error {
return status.Errorf(codes.Unimplemented, "method FollowLog not implemented")
}
func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {}
// UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service.
@ -84,6 +121,27 @@ func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _LoggerService_FollowLog_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(FollowLogRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LoggerServiceServer).FollowLog(m, &loggerServiceFollowLogServer{stream})
}
type LoggerService_FollowLogServer interface {
Send(*FollowLogResponse) error
grpc.ServerStream
}
type loggerServiceFollowLogServer struct {
grpc.ServerStream
}
func (x *loggerServiceFollowLogServer) Send(m *FollowLogResponse) error {
return x.ServerStream.SendMsg(m)
}
// LoggerService_ServiceDesc is the grpc.ServiceDesc for LoggerService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -96,6 +154,12 @@ var LoggerService_ServiceDesc = grpc.ServiceDesc{
Handler: _LoggerService_RestartLogger_Handler,
},
},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "FollowLog",
Handler: _LoggerService_FollowLog_Handler,
ServerStreams: true,
},
},
Metadata: "app/log/command/config.proto",
}

View File

@ -4,6 +4,7 @@ package log
import (
"context"
"reflect"
"sync"
"github.com/v2fly/v2ray-core/v4/common"
@ -16,6 +17,7 @@ type Instance struct {
config *Config
accessLogger log.Handler
errorLogger log.Handler
followers map[reflect.Value]func(msg log.Message)
active bool
}
@ -89,6 +91,23 @@ func (g *Instance) Start() error {
return g.startInternal()
}
// AddFollower implements log.Follower.
func (g *Instance) AddFollower(f func(msg log.Message)) {
g.Lock()
defer g.Unlock()
if g.followers == nil {
g.followers = make(map[reflect.Value]func(msg log.Message))
}
g.followers[reflect.ValueOf(f)] = f
}
// RemoveFollower implements log.Follower.
func (g *Instance) RemoveFollower(f func(msg log.Message)) {
g.Lock()
defer g.Unlock()
delete(g.followers, reflect.ValueOf(f))
}
// Handle implements log.Handler.
func (g *Instance) Handle(msg log.Message) {
g.RLock()
@ -98,6 +117,10 @@ func (g *Instance) Handle(msg log.Message) {
return
}
for _, f := range g.followers {
f(msg)
}
switch msg := msg.(type) {
case *log.AccessMessage:
if g.accessLogger != nil {

View File

@ -21,7 +21,7 @@ type Balancer struct {
ohm outbound.Manager
fallbackTag string
override overridden
override override
}
// PickOutbound picks the tag of a outbound

View File

@ -43,39 +43,39 @@ func (r *Router) OverrideSelecting(balancer string, selects []string, validity t
return nil
}
type overriddenSettings struct {
type overrideSettings struct {
selects []string
until time.Time
}
type overridden struct {
type override struct {
access sync.RWMutex
settings overriddenSettings
settings overrideSettings
}
// Get gets the overridden settings
func (o *overridden) Get() *overriddenSettings {
// Get gets the override settings
func (o *override) Get() *overrideSettings {
o.access.RLock()
defer o.access.RUnlock()
if len(o.settings.selects) == 0 || time.Now().After(o.settings.until) {
return nil
}
return &overriddenSettings{
return &overrideSettings{
selects: o.settings.selects,
until: o.settings.until,
}
}
// Put updates the overridden settings
func (o *overridden) Put(selects []string, until time.Time) {
// Put updates the override settings
func (o *override) Put(selects []string, until time.Time) {
o.access.Lock()
defer o.access.Unlock()
o.settings.selects = selects
o.settings.until = until
}
// Clear clears the overridden settings
func (o *overridden) Clear() {
// Clear clears the override settings
func (o *override) Clear() {
o.access.Lock()
defer o.access.Unlock()
o.settings.selects = nil

View File

@ -49,9 +49,20 @@ func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*
}
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
matcher, err := strmatcher.Substr.New(request.Pattern)
if err != nil {
return nil, err
mgroup := &strmatcher.MatcherGroup{}
if request.Pattern != "" {
request.Patterns = append(request.Patterns, request.Pattern)
}
t := strmatcher.Substr
if request.Regexp {
t = strmatcher.Regex
}
for _, p := range request.Patterns {
m, err := t.New(p)
if err != nil {
return nil, err
}
mgroup.Add(m)
}
response := &QueryStatsResponse{}
@ -62,7 +73,7 @@ func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest
}
manager.VisitCounters(func(name string, c feature_stats.Counter) bool {
if matcher.Match(name) {
if mgroup.Size() == 0 || len(mgroup.Match(name)) > 0 {
var value int64
if request.Reset_ {
value = c.Set(0)

View File

@ -184,8 +184,11 @@ type QueryStatsRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
// Deprecated, use Patterns instead
Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"`
Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"`
}
func (x *QueryStatsRequest) Reset() {
@ -234,6 +237,20 @@ func (x *QueryStatsRequest) GetReset_() bool {
return false
}
func (x *QueryStatsRequest) GetPatterns() []string {
if x != nil {
return x.Patterns
}
return nil
}
func (x *QueryStatsRequest) GetRegexp() bool {
if x != nil {
return x.Regexp
}
return false
}
type QueryStatsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -494,66 +511,70 @@ var file_app_stats_command_command_proto_rawDesc = []byte{
0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x43, 0x0a, 0x11, 0x51, 0x75,
0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x77, 0x0a, 0x11, 0x51, 0x75,
0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73,
0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22,
0x4c, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12,
0x1a, 0x0a, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72,
0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67,
0x65, 0x78, 0x70, 0x22, 0x4c, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61,
0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61,
0x74, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d,
0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75,
0x6d, 0x47, 0x43, 0x12, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01,
0x28, 0x04, 0x52, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74,
0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54,
0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73,
0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d,
0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61,
0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07,
0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c,
0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a,
0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e,
0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x32, 0xde, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73,
0x12, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47,
0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x12, 0x71, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51,
0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74,
0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, 0x0a,
0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, 0x6d,
0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, 0x6d,
0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x12,
0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05,
0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c,
0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c,
0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01,
0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f,
0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04,
0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, 0x69,
0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, 0x75,
0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52,
0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a,
0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55,
0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32,
0xde, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x6b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74,
0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53,
0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x76, 0x32,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61,
0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a,
0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2f, 0x2e, 0x76, 0x32,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61,
0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79,
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74,
0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53,
0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e,
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79,
0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x42, 0x75, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63,
0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73,
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1c, 0x56, 0x32, 0x52, 0x61, 0x79,
0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e,
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x42, 0x75, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61,
0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73,
0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1c, 0x56,
0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74,
0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (

View File

@ -23,8 +23,11 @@ message GetStatsResponse {
}
message QueryStatsRequest {
// Deprecated, use Patterns instead
string pattern = 1;
bool reset = 2;
repeated string patterns = 3;
bool regexp = 4;
}
message QueryStatsResponse {

View File

@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"time"
@ -28,8 +27,6 @@ func LoadArgToBytes(arg string) (out []byte, err error) {
switch {
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
out, err = FetchHTTPContent(arg)
case (arg == "stdin:"):
out, err = ioutil.ReadAll(os.Stdin)
default:
out, err = ioutil.ReadFile(arg)
}

View File

@ -16,6 +16,12 @@ type Handler interface {
Handle(msg Message)
}
// Follower is the interface for following logs.
type Follower interface {
AddFollower(func(msg Message))
RemoveFollower(func(msg Message))
}
// GeneralMessage is a general log message that can contain all kind of content.
type GeneralMessage struct {
Severity Severity

98
common/units/bytesize.go Normal file
View File

@ -0,0 +1,98 @@
package units
import (
"errors"
"strconv"
"strings"
"unicode"
)
var errInvalidSize = errors.New("invalid size")
var errInvalidUnit = errors.New("invalid or unsupported unit")
// ByteSize is the size of bytes
type ByteSize uint64
const (
_ = iota
// KB = 1KB
KB ByteSize = 1 << (10 * iota)
// MB = 1MB
MB
// GB = 1GB
GB
// TB = 1TB
TB
// PB = 1PB
PB
// EB = 1EB
EB
)
func (b ByteSize) String() string {
unit := ""
value := float64(0)
switch {
case b == 0:
return "0"
case b < KB:
unit = "B"
value = float64(b)
case b < MB:
unit = "KB"
value = float64(b) / float64(KB)
case b < GB:
unit = "MB"
value = float64(b) / float64(MB)
case b < TB:
unit = "GB"
value = float64(b) / float64(GB)
case b < PB:
unit = "TB"
value = float64(b) / float64(TB)
case b < EB:
unit = "PB"
value = float64(b) / float64(PB)
default:
unit = "EB"
value = float64(b) / float64(EB)
}
result := strconv.FormatFloat(value, 'f', 2, 64)
result = strings.TrimSuffix(result, ".0")
return result + unit
}
// Parse parses ByteSize from string
func (b *ByteSize) Parse(s string) error {
s = strings.TrimSpace(s)
s = strings.ToUpper(s)
i := strings.IndexFunc(s, unicode.IsLetter)
if i == -1 {
return errInvalidUnit
}
bytesString, multiple := s[:i], s[i:]
bytes, err := strconv.ParseFloat(bytesString, 64)
if err != nil || bytes <= 0 {
return errInvalidSize
}
switch multiple {
case "B":
*b = ByteSize(bytes)
case "K", "KB", "KIB":
*b = ByteSize(bytes * float64(KB))
case "M", "MB", "MIB":
*b = ByteSize(bytes * float64(MB))
case "G", "GB", "GIB":
*b = ByteSize(bytes * float64(GB))
case "T", "TB", "TIB":
*b = ByteSize(bytes * float64(TB))
case "P", "PB", "PIB":
*b = ByteSize(bytes * float64(PB))
case "E", "EB", "EIB":
*b = ByteSize(bytes * float64(EB))
default:
return errInvalidUnit
}
return nil
}

View File

@ -0,0 +1,66 @@
package units_test
import (
"testing"
"github.com/v2fly/v2ray-core/v4/common/units"
)
func TestByteSizes(t *testing.T) {
size := units.ByteSize(0)
assertSizeString(t, size, "0")
size++
assertSizeValue(t,
assertSizeString(t, size, "1.00B"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeString(t, size, "1.00KB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeString(t, size, "1.00MB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeString(t, size, "1.00GB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeString(t, size, "1.00TB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeString(t, size, "1.00PB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeString(t, size, "1.00EB"),
size,
)
}
func assertSizeValue(t *testing.T, size string, expected units.ByteSize) {
actual := units.ByteSize(0)
err := actual.Parse(size)
if err != nil {
t.Error(err)
}
if actual != expected {
t.Errorf("expect %s, but got %s", expected, actual)
}
}
func assertSizeString(t *testing.T, size units.ByteSize, expected string) string {
actual := size.String()
if actual != expected {
t.Errorf("expect %s, but got %s", expected, actual)
}
return expected
}

145
config.go
View File

@ -1,8 +1,12 @@
package core
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"google.golang.org/protobuf/proto"
@ -12,6 +16,21 @@ import (
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
)
const (
// FormatAuto represents all available formats by auto selecting
FormatAuto = "auto"
// FormatJSON represents json format
FormatJSON = "json"
// FormatTOML represents toml format
FormatTOML = "toml"
// FormatYAML represents yaml format
FormatYAML = "yaml"
// FormatProtobuf represents protobuf format
FormatProtobuf = "protobuf"
// FormatProtobufShort is the short of FormatProtobuf
FormatProtobufShort = "pb"
)
// ConfigFormat is a configurable format of V2Ray config file.
type ConfigFormat struct {
Name []string
@ -23,6 +42,7 @@ type ConfigFormat struct {
type ConfigLoader func(input interface{}) (*Config, error)
var (
configLoaders = make([]*ConfigFormat, 0)
configLoaderByName = make(map[string]*ConfigFormat)
configLoaderByExt = make(map[string]*ConfigFormat)
)
@ -30,11 +50,10 @@ var (
// RegisterConfigLoader add a new ConfigLoader.
func RegisterConfigLoader(format *ConfigFormat) error {
for _, name := range format.Name {
lname := strings.ToLower(name)
if _, found := configLoaderByName[lname]; found {
if _, found := configLoaderByName[name]; found {
return newError(name, " already registered.")
}
configLoaderByName[lname] = format
configLoaderByName[name] = format
}
for _, ext := range format.Extension {
@ -44,7 +63,7 @@ func RegisterConfigLoader(format *ConfigFormat) error {
}
configLoaderByExt[lext] = format
}
configLoaders = append(configLoaders, format)
return nil
}
@ -53,43 +72,101 @@ func getExtension(filename string) string {
return strings.ToLower(ext)
}
// GetConfigLoader get config loader by name and filename.
// Specify formatName to explicitly select a loader.
// Specify filename to choose loader by detect its extension.
// Leave formatName and filename blank for default loader
func GetConfigLoader(formatName string, filename string) (*ConfigFormat, error) {
if formatName != "" {
// if explicitly specified, we can safely assume that user knows what they are
if f, found := configLoaderByName[formatName]; found {
return f, nil
}
return nil, newError("Unable to load config in ", formatName).AtWarning()
// GetLoaderExtensions get config loader extensions.
func GetLoaderExtensions(formatName string) ([]string, error) {
if formatName == FormatAuto {
return GetAllExtensions(), nil
}
// no explicitly specified loader, extenstion detect first
if ext := getExtension(filename); len(ext) > 0 {
if f, found := configLoaderByExt[ext]; found {
return f, nil
}
if f, found := configLoaderByName[formatName]; found {
return f.Extension, nil
}
// default loader
if f, found := configLoaderByName["json"]; found {
return f, nil
}
panic("default loader not found")
return nil, newError("config loader not found: ", formatName).AtWarning()
}
// LoadConfig loads config with given format from given source.
// input accepts 2 different types:
// GetAllExtensions get all extensions supported
func GetAllExtensions() []string {
extensions := make([]string, 0)
for _, f := range configLoaderByName {
extensions = append(extensions, f.Extension...)
}
return extensions
}
// LoadConfig loads multiple config with given format from given source.
// input accepts:
// * string of a single filename/url(s) to open to read
// * []string slice of multiple filename/url(s) to open to read
// * io.Reader that reads a config content (the original way)
func LoadConfig(formatName string, filename string, input interface{}) (*Config, error) {
f, err := GetConfigLoader(formatName, filename)
if err != nil {
return nil, err
func LoadConfig(formatName string, input interface{}) (*Config, error) {
cnt := getInputCount(input)
if cnt == 0 {
log.Println("Using config from STDIN")
input = os.Stdin
cnt = 1
}
if formatName == FormatAuto && cnt == 1 {
// This ensures only to call auto loader for multiple files,
// so that it can only care about merging scenarios
return loadSingleConfigAutoFormat(input)
}
// if input is a slice with single element, extract it
// so that unmergeable loaders don't need to deal with
// slices
s := reflect.Indirect(reflect.ValueOf(input))
k := s.Kind()
if (k == reflect.Slice || k == reflect.Array) && s.Len() == 1 {
value := reflect.Indirect(s.Index(0))
if value.Kind() == reflect.String {
// string type alias
input = fmt.Sprint(value.Interface())
} else {
input = value.Interface()
}
}
f, found := configLoaderByName[formatName]
if !found {
return nil, newError("config loader not found: ", formatName).AtWarning()
}
return f.Loader(input)
}
// loadSingleConfigAutoFormat loads a single config with from given source.
// input accepts:
// * string of a single filename/url(s) to open to read
// * io.Reader that reads a config content (the original way)
func loadSingleConfigAutoFormat(input interface{}) (*Config, error) {
if file, ok := input.(string); ok {
extension := getExtension(file)
if extension != "" {
lowerName := strings.ToLower(extension)
if f, found := configLoaderByExt[lowerName]; found {
return f.Loader(file)
}
return nil, newError("config loader not found for: ", extension).AtWarning()
}
}
// no extension, try all loaders
for _, f := range configLoaders {
if f.Name[0] == FormatAuto {
continue
}
c, err := f.Loader(input)
if err == nil {
return c, nil
}
}
return nil, newError("tried all loaders but failed for: ", input).AtWarning()
}
func getInputCount(input interface{}) int {
s := reflect.Indirect(reflect.ValueOf(input))
k := s.Kind()
if k == reflect.Slice || k == reflect.Array {
return s.Len()
}
return 1
}
func loadProtobufConfig(data []byte) (*Config, error) {
config := new(Config)
if err := proto.Unmarshal(data, config); err != nil {
@ -100,12 +177,12 @@ func loadProtobufConfig(data []byte) (*Config, error) {
func init() {
common.Must(RegisterConfigLoader(&ConfigFormat{
Name: []string{"Protobuf", "pb"},
Name: []string{FormatProtobuf, FormatProtobufShort},
Extension: []string{".pb"},
Loader: func(input interface{}) (*Config, error) {
switch v := input.(type) {
case cmdarg.Arg:
r, err := cmdarg.LoadArg(v[0])
case string:
r, err := cmdarg.LoadArg(v)
if err != nil {
return nil, err
}

View File

@ -26,7 +26,7 @@ type StrategyInfo struct {
Others []*OutboundInfo // Other outbounds
}
// BalancingOverrideInfo holds balancing overridden information
// BalancingOverrideInfo holds balancing override information
type BalancingOverrideInfo struct {
Until time.Time
Selects []string

View File

@ -24,7 +24,7 @@ func CreateObject(v *Instance, config interface{}) (interface{}, error) {
//
// v2ray:api:stable
func StartInstance(configFormat string, configBytes []byte) (*Instance, error) {
config, err := LoadConfig(configFormat, "", bytes.NewReader(configBytes))
config, err := LoadConfig(configFormat, bytes.NewReader(configBytes))
if err != nil {
return nil, err
}

View File

@ -61,7 +61,7 @@ func TestV2RayDial(t *testing.T) {
cfgBytes, err := proto.Marshal(config)
common.Must(err)
server, err := core.StartInstance("protobuf", cfgBytes)
server, err := core.StartInstance(core.FormatProtobuf, cfgBytes)
common.Must(err)
defer server.Close()
@ -111,7 +111,7 @@ func TestV2RayDialUDPConn(t *testing.T) {
cfgBytes, err := proto.Marshal(config)
common.Must(err)
server, err := core.StartInstance("protobuf", cfgBytes)
server, err := core.StartInstance(core.FormatProtobuf, cfgBytes)
common.Must(err)
defer server.Close()
@ -178,7 +178,7 @@ func TestV2RayDialUDP(t *testing.T) {
cfgBytes, err := proto.Marshal(config)
common.Must(err)
server, err := core.StartInstance("protobuf", cfgBytes)
server, err := core.StartInstance(core.FormatProtobuf, cfgBytes)
common.Must(err)
defer server.Close()

View File

@ -9,6 +9,7 @@ import (
)
// mergeMaps merges source map into target
// it supports only map[string]interface{} type for any children of the map tree
func mergeMaps(target map[string]interface{}, source map[string]interface{}) (err error) {
for key, value := range source {
target[key], err = mergeField(target[key], value)

View File

@ -18,95 +18,46 @@ package merge
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
)
// FilesToJSON merges multiple jsons files into one json, accepts remote url, or local file path
func FilesToJSON(args []string) ([]byte, error) {
m, err := FilesToMap(args)
if err != nil {
return nil, err
}
return json.Marshal(m)
}
// BytesToJSON merges multiple json contents into one json.
func BytesToJSON(args [][]byte) ([]byte, error) {
m, err := BytesToMap(args)
if err != nil {
return nil, err
}
return json.Marshal(m)
}
// FilesToMap merges multiple json files into one map, accepts remote url, or local file path
func FilesToMap(args []string) (m map[string]interface{}, err error) {
m, err = loadFiles(args)
if err != nil {
return nil, err
}
err = applyRules(m)
if err != nil {
return nil, err
}
return m, nil
}
// BytesToMap merges multiple json contents into one map.
func BytesToMap(args [][]byte) (m map[string]interface{}, err error) {
m, err = loadBytes(args)
if err != nil {
return nil, err
}
err = applyRules(m)
if err != nil {
return nil, err
}
return m, nil
}
func loadFiles(args []string) (map[string]interface{}, error) {
c := make(map[string]interface{})
// JSONs merges multiple json contents into one json.
func JSONs(args [][]byte) ([]byte, error) {
m := make(map[string]interface{})
for _, arg := range args {
r, err := cmdarg.LoadArg(arg)
if err != nil {
return nil, fmt.Errorf("fail to load %s: %s", arg, err)
}
m, err := decode(r)
if err != nil {
return nil, fmt.Errorf("fail to decode %s: %s", arg, err)
}
if err = mergeMaps(c, m); err != nil {
return nil, fmt.Errorf("fail to merge %s: %s", arg, err)
}
}
return c, nil
}
func loadBytes(args [][]byte) (map[string]interface{}, error) {
conf := make(map[string]interface{})
for _, arg := range args {
r := bytes.NewReader(arg)
m, err := decode(r)
if err != nil {
return nil, err
}
if err = mergeMaps(conf, m); err != nil {
if _, err := ToMap(arg, m); err != nil {
return nil, err
}
}
return conf, nil
return FromMap(m)
}
func decode(r io.Reader) (map[string]interface{}, error) {
c := make(map[string]interface{})
err := serial.DecodeJSON(r, &c)
// ToMap merges json content to target map and returns it
func ToMap(content []byte, target map[string]interface{}) (map[string]interface{}, error) {
if target == nil {
target = make(map[string]interface{})
}
r := bytes.NewReader(content)
n := make(map[string]interface{})
err := serial.DecodeJSON(r, &n)
if err != nil {
return nil, err
}
return c, nil
if err = mergeMaps(target, n); err != nil {
return nil, err
}
return target, nil
}
// FromMap apply merge rules to map and convert it to json
func FromMap(target map[string]interface{}) ([]byte, error) {
if target == nil {
target = make(map[string]interface{})
}
err := ApplyRules(target)
if err != nil {
return nil, err
}
return json.Marshal(target)
}

View File

@ -5,7 +5,7 @@
package merge_test
import (
"encoding/json"
"bytes"
"reflect"
"strings"
"testing"
@ -50,7 +50,7 @@ func TestMergeV2Style(t *testing.T) {
]}
}
`
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
if err != nil {
t.Error(err)
}
@ -91,7 +91,7 @@ func TestMergeTag(t *testing.T) {
}
}
`
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
if err != nil {
t.Error(err)
}
@ -147,7 +147,7 @@ func TestMergeTagValueTypes(t *testing.T) {
}]
}
`
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
if err != nil {
t.Error(err)
}
@ -187,20 +187,24 @@ func TestMergeTagDeep(t *testing.T) {
}]
}
`
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
if err != nil {
t.Error(err)
}
assertResult(t, m, expected)
}
func assertResult(t *testing.T, value map[string]interface{}, expected string) {
e := make(map[string]interface{})
err := serial.DecodeJSON(strings.NewReader(expected), &e)
func assertResult(t *testing.T, value []byte, expected string) {
v := make(map[string]interface{})
err := serial.DecodeJSON(bytes.NewReader(value), &v)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(value, e) {
bs, _ := json.Marshal(value)
t.Fatalf("expected:\n%s\n\nactual:\n%s", expected, string(bs))
e := make(map[string]interface{})
err = serial.DecodeJSON(strings.NewReader(expected), &e)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(v, e) {
t.Fatalf("expected:\n%s\n\nactual:\n%s", expected, string(value))
}
}

View File

@ -7,7 +7,8 @@ package merge
const priorityKey string = "_priority"
const tagKey string = "_tag"
func applyRules(m map[string]interface{}) error {
// ApplyRules applies merge rules according to _tag, _priority fields, and remove them
func ApplyRules(m map[string]interface{}) error {
err := sortMergeSlices(m)
if err != nil {
return err

View File

@ -1,4 +1,4 @@
package json
package mergers
import "github.com/v2fly/v2ray-core/v4/common/errors"

View File

@ -0,0 +1,25 @@
package mergers
import "strings"
// GetExtensions get extensions of given format
func GetExtensions(formatName string) ([]string, error) {
lowerName := strings.ToLower(formatName)
if lowerName == "auto" {
return GetAllExtensions(), nil
}
f, found := mergeLoaderByName[lowerName]
if !found {
return nil, newError(formatName+" not found", formatName).AtWarning()
}
return f.Extensions, nil
}
// GetAllExtensions get all extensions supported
func GetAllExtensions() []string {
extensions := make([]string, 0)
for _, f := range mergeLoaderByName {
extensions = append(extensions, f.Extensions...)
}
return extensions
}

View File

@ -0,0 +1,40 @@
package mergers
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
import (
"strings"
)
// MergeableFormat is a configurable mergeable format of V2Ray config file.
type MergeableFormat struct {
Name string
Extensions []string
Loader MergeLoader
}
// MergeLoader is a utility to merge V2Ray config from external source into a map and returns it.
type MergeLoader func(input interface{}, m map[string]interface{}) error
var (
mergeLoaderByName = make(map[string]*MergeableFormat)
mergeLoaderByExt = make(map[string]*MergeableFormat)
)
// RegisterMergeLoader add a new MergeLoader.
func RegisterMergeLoader(format *MergeableFormat) error {
if _, found := mergeLoaderByName[format.Name]; found {
return newError(format.Name, " already registered.")
}
mergeLoaderByName[format.Name] = format
for _, ext := range format.Extensions {
lext := strings.ToLower(ext)
if f, found := mergeLoaderByExt[lext]; found {
return newError(ext, " already registered to ", f.Name)
}
mergeLoaderByExt[lext] = format
}
return nil
}

View File

@ -0,0 +1,95 @@
package mergers
import (
"io"
"io/ioutil"
"path/filepath"
"strings"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
)
// MergeAs load input and merge as specified format into m
func MergeAs(formatName string, input interface{}, m map[string]interface{}) error {
f, found := mergeLoaderByName[formatName]
if !found {
return newError("format loader not found for: ", formatName)
}
return f.Loader(input, m)
}
// Merge loads inputs and merges them into m
// it detects extension for loader selecting, or try all loaders
// if no extension found
func Merge(input interface{}, m map[string]interface{}) error {
switch v := input.(type) {
case string:
err := mergeSingleFile(v, m)
if err != nil {
return err
}
case []string:
for _, file := range v {
err := mergeSingleFile(file, m)
if err != nil {
return err
}
}
case cmdarg.Arg:
for _, file := range v {
err := mergeSingleFile(file, m)
if err != nil {
return err
}
}
case []byte:
err := mergeSingleFile(v, m)
if err != nil {
return err
}
case io.Reader:
// read to []byte incase it tries different loaders
bs, err := ioutil.ReadAll(v)
if err != nil {
return err
}
err = mergeSingleFile(bs, m)
if err != nil {
return err
}
default:
return newError("unknow merge input type")
}
return nil
}
func mergeSingleFile(input interface{}, m map[string]interface{}) error {
if file, ok := input.(string); ok {
ext := getExtension(file)
if ext != "" {
lext := strings.ToLower(ext)
f, found := mergeLoaderByExt[lext]
if !found {
return newError("unmergeable format extension: ", ext)
}
return f.Loader(file, m)
}
}
// no extension, try all loaders
for _, f := range mergeLoaderByName {
if f.Name == core.FormatAuto {
continue
}
err := f.Loader(input, m)
if err == nil {
return nil
}
}
return newError("tried all loaders but failed for: ", input).AtWarning()
}
func getExtension(filename string) string {
ext := filepath.Ext(filename)
return strings.ToLower(ext)
}

View File

@ -0,0 +1,103 @@
package mergers
import (
"fmt"
"io"
"io/ioutil"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
)
type jsonConverter func(v []byte) ([]byte, error)
func makeLoader(name string, extensions []string, converter jsonConverter) *MergeableFormat {
return &MergeableFormat{
Name: name,
Extensions: extensions,
Loader: makeConvertToJSONLoader(converter),
}
}
func makeConvertToJSONLoader(converter func(v []byte) ([]byte, error)) MergeLoader {
return func(input interface{}, target map[string]interface{}) error {
if target == nil {
panic("merge target is nil")
}
switch v := input.(type) {
case string:
err := loadFile(v, target, converter)
if err != nil {
return err
}
case []string:
err := loadFiles(v, target, converter)
if err != nil {
return err
}
case cmdarg.Arg:
err := loadFiles(v, target, converter)
if err != nil {
return err
}
case []byte:
err := loadBytes(v, target, converter)
if err != nil {
return err
}
case io.Reader:
err := loadReader(v, target, converter)
if err != nil {
return err
}
default:
return newError("unknow merge input type")
}
return nil
}
}
func loadFiles(files []string, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
for _, file := range files {
err := loadFile(file, target, converter)
if err != nil {
return err
}
}
return nil
}
func loadFile(file string, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
bs, err := cmdarg.LoadArgToBytes(file)
if err != nil {
return fmt.Errorf("fail to load %s: %s", file, err)
}
if converter != nil {
bs, err = converter(bs)
if err != nil {
return fmt.Errorf("error convert to json '%s': %s", file, err)
}
}
_, err = merge.ToMap(bs, target)
return err
}
func loadReader(reader io.Reader, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
bs, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
return loadBytes(bs, target, converter)
}
func loadBytes(bs []byte, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
var err error
if converter != nil {
bs, err = converter(bs)
if err != nil {
return fmt.Errorf("fail to convert to json: %s", err)
}
}
_, err = merge.ToMap(bs, target)
return err
}

View File

@ -0,0 +1,32 @@
package mergers
import (
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
)
func init() {
common.Must(RegisterMergeLoader(makeLoader(
core.FormatJSON,
[]string{".json", ".jsonc"},
nil,
)))
common.Must(RegisterMergeLoader(makeLoader(
core.FormatTOML,
[]string{".toml"},
json.FromTOML,
)))
common.Must(RegisterMergeLoader(makeLoader(
core.FormatYAML,
[]string{".yml", ".yaml"},
json.FromYAML,
)))
common.Must(RegisterMergeLoader(
&MergeableFormat{
Name: core.FormatAuto,
Extensions: nil,
Loader: Merge,
}),
)
}

View File

@ -0,0 +1,10 @@
package mergers
// GetAllNames get names of all formats
func GetAllNames() []string {
names := make([]string, 0)
for _, f := range mergeLoaderByName {
names = append(names, f.Name)
}
return names
}

View File

@ -7,14 +7,12 @@ import (
// CmdAPI calls an API in an V2Ray process
var CmdAPI = &base.Command{
UsageLine: "{{.Exec}} api",
Short: "Call V2Ray API",
Short: "call V2Ray API",
Long: `{{.Exec}} {{.LongName}} provides tools to manipulate V2Ray via its API.
`,
Commands: []*base.Command{
cmdRestartLogger,
cmdGetStats,
cmdQueryStats,
cmdSysStats,
cmdLog,
cmdStats,
cmdBalancerCheck,
cmdBalancerInfo,
cmdBalancerOverride,

View File

@ -18,10 +18,10 @@ of server config.
Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:

View File

@ -10,6 +10,7 @@ import (
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
// TODO: support "-json" flag for json output
var cmdBalancerInfo = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...",
@ -24,10 +25,13 @@ of server config.
Arguments:
-s, -server
-json
Use json output.
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
@ -53,12 +57,17 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
sort.Slice(resp.Balancers, func(i, j int) bool {
return resp.Balancers[i].Tag < resp.Balancers[j].Tag
})
if apiJSON {
showJSONResponse(resp)
return
}
for _, b := range resp.Balancers {
showBalancerInfo(b)
}
}
func showBalancerInfo(b *routerService.BalancerMsg) {
const tableIndent = 4
sb := new(strings.Builder)
// Balancer
sb.WriteString(fmt.Sprintf("Balancer: %s\n", b.Tag))
@ -71,25 +80,28 @@ func showBalancerInfo(b *routerService.BalancerMsg) {
if b.Override != nil {
sb.WriteString(" - Selecting Override:\n")
until := fmt.Sprintf("until: %s", b.Override.Until)
writeRow(sb, 0, nil, nil, until)
writeRow(sb, tableIndent, 0, []string{until}, nil)
for i, s := range b.Override.Selects {
writeRow(sb, i+1, nil, nil, s)
writeRow(sb, tableIndent, i+1, []string{s}, nil)
}
}
b.Titles = append(b.Titles, "Tag")
formats := getColumnFormats(b.Titles)
// Selects
sb.WriteString(" - Selects:\n")
writeRow(sb, 0, b.Titles, formats, "Tag")
writeRow(sb, tableIndent, 0, b.Titles, formats)
for i, o := range b.Selects {
writeRow(sb, i+1, o.Values, formats, o.Tag)
o.Values = append(o.Values, o.Tag)
writeRow(sb, tableIndent, i+1, o.Values, formats)
}
// Others
scnt := len(b.Selects)
if len(b.Others) > 0 {
sb.WriteString(" - Others:\n")
writeRow(sb, 0, b.Titles, formats, "Tag")
writeRow(sb, tableIndent, 0, b.Titles, formats)
for i, o := range b.Others {
writeRow(sb, scnt+i+1, o.Values, formats, o.Tag)
o.Values = append(o.Values, o.Tag)
writeRow(sb, tableIndent, scnt+i+1, o.Values, formats)
}
}
os.Stdout.WriteString(sb.String())
@ -103,12 +115,12 @@ func getColumnFormats(titles []string) []string {
return w
}
func writeRow(sb *strings.Builder, index int, values, formats []string, tag string) {
func writeRow(sb *strings.Builder, indent, index int, values, formats []string) {
if index == 0 {
// title line
sb.WriteString(" ")
sb.WriteString(strings.Repeat(" ", indent+4))
} else {
sb.WriteString(fmt.Sprintf(" %-4d", index))
sb.WriteString(fmt.Sprintf("%s%-4d", strings.Repeat(" ", indent), index))
}
for i, v := range values {
format := "%-14s"
@ -117,6 +129,5 @@ func writeRow(sb *strings.Builder, index int, values, formats []string, tag stri
}
sb.WriteString(fmt.Sprintf(format, v))
}
sb.WriteString(tag)
sb.WriteByte('\n')
}

View File

@ -10,14 +10,14 @@ import (
var cmdBalancerOverride = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> selectors...",
Short: "balancer select override",
Short: "balancer override",
Long: `
Override a balancer's selecting in a duration of time.
Override a balancer's selection in a duration of time.
> Make sure you have "RoutingService" set in "config.api.services"
of server config.
Once a balancer's selecting is overridden:
Once a balancer's selection is overridden:
- The selectors of the balancer won't apply.
- The strategy of the balancer stops selecting qualified nodes
@ -25,19 +25,20 @@ Once a balancer's selecting is overridden:
Arguments:
-b, -balancer <tag>
Tag of the target balancer. Required.
-v, -validity <duration>
Time duration of the validity of override, e.g.: 60s, 60m,
24h, 1m30s. Default 1h.
-r, -remove
Remove the overridden
Remove the override
-b, -balancer
Tag of the balancer. Required
-v, -validity
Time minutes of the validity of overridden. Default 60
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
@ -51,13 +52,13 @@ Example:
func executeBalancerOverride(cmd *base.Command, args []string) {
var (
balancer string
validity int64
validity time.Duration
remove bool
)
cmd.Flag.StringVar(&balancer, "b", "", "")
cmd.Flag.StringVar(&balancer, "balancer", "", "")
cmd.Flag.Int64Var(&validity, "v", 60, "")
cmd.Flag.Int64Var(&validity, "validity", 60, "")
cmd.Flag.DurationVar(&validity, "v", time.Hour, "")
cmd.Flag.DurationVar(&validity, "validity", time.Hour, "")
cmd.Flag.BoolVar(&remove, "r", false, "")
cmd.Flag.BoolVar(&remove, "remove", false, "")
setSharedFlags(cmd)
@ -72,7 +73,7 @@ func executeBalancerOverride(cmd *base.Command, args []string) {
v := int64(0)
if !remove {
v = int64(time.Duration(validity) * time.Minute)
v = int64(validity)
}
client := routerService.NewRoutingServiceClient(conn)
r := &routerService.OverrideSelectingRequest{
@ -82,6 +83,6 @@ func executeBalancerOverride(cmd *base.Command, args []string) {
}
_, err := client.OverrideSelecting(ctx, r)
if err != nil {
base.Fatalf("failed to perform balancer health checks: %s", err)
base.Fatalf("failed to override balancer: %s", err)
}
}

View File

@ -4,25 +4,31 @@ import (
"fmt"
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
)
var cmdAddInbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
Short: "Add inbounds",
Short: "add inbounds",
Long: `
Add inbounds to V2Ray.
Arguments:
-s, -server
-format <format>
Specify the input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-r
Load folders recursively.
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
@ -34,26 +40,13 @@ Example:
func executeAddInbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
setSharedConfigFlags(cmd)
cmd.Flag.Parse(args)
unnamedArgs := cmd.Flag.Args()
if len(unnamedArgs) == 0 {
fmt.Println("reading from stdin:")
unnamedArgs = []string{"stdin:"}
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
}
ins := make([]conf.InboundDetourConfig, 0)
for _, arg := range unnamedArgs {
r, err := cmdarg.LoadArg(arg)
if err != nil {
base.Fatalf("failed to load %s: %s", arg, err)
}
conf, err := serial.DecodeJSONConfig(r)
if err != nil {
base.Fatalf("failed to decode %s: %s", arg, err)
}
ins = append(ins, conf.InboundConfigs...)
}
if len(ins) == 0 {
if len(c.InboundConfigs) == 0 {
base.Fatalf("no valid inbound found")
}
@ -61,7 +54,7 @@ func executeAddInbounds(cmd *base.Command, args []string) {
defer close()
client := handlerService.NewHandlerServiceClient(conn)
for _, in := range ins {
for _, in := range c.InboundConfigs {
fmt.Println("adding:", in.Tag)
i, err := in.Build()
if err != nil {
@ -70,10 +63,9 @@ func executeAddInbounds(cmd *base.Command, args []string) {
r := &handlerService.AddInboundRequest{
Inbound: i,
}
resp, err := client.AddInbound(ctx, r)
_, err = client.AddInbound(ctx, r)
if err != nil {
base.Fatalf("failed to add inbound: %s", err)
}
showResponese(resp)
}
}

View File

@ -4,24 +4,31 @@ import (
"fmt"
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
)
var cmdRemoveInbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
Short: "Remove inbounds",
Short: "remove inbounds",
Long: `
Remove inbounds from V2Ray.
Arguments:
-s, -server
-format <format>
Specify the input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-r
Load folders recursively.
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
@ -33,48 +40,28 @@ Example:
func executeRemoveInbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
setSharedConfigFlags(cmd)
cmd.Flag.Parse(args)
unnamedArgs := cmd.Flag.Args()
if len(unnamedArgs) == 0 {
fmt.Println("reading from stdin:")
unnamedArgs = []string{"stdin:"}
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
}
tags := make([]string, 0)
for _, arg := range unnamedArgs {
if r, err := cmdarg.LoadArg(arg); err == nil {
conf, err := serial.DecodeJSONConfig(r)
if err != nil {
base.Fatalf("failed to decode %s: %s", arg, err)
}
ins := conf.InboundConfigs
for _, i := range ins {
tags = append(tags, i.Tag)
}
} else {
// take request as tag
tags = append(tags, arg)
}
}
if len(tags) == 0 {
if len(c.InboundConfigs) == 0 {
base.Fatalf("no inbound to remove")
}
fmt.Println("removing inbounds:", tags)
conn, ctx, close := dialAPIServer()
defer close()
client := handlerService.NewHandlerServiceClient(conn)
for _, tag := range tags {
fmt.Println("removing:", tag)
for _, c := range c.InboundConfigs {
fmt.Println("removing:", c.Tag)
r := &handlerService.RemoveInboundRequest{
Tag: tag,
Tag: c.Tag,
}
resp, err := client.RemoveInbound(ctx, r)
_, err := client.RemoveInbound(ctx, r)
if err != nil {
base.Fatalf("failed to remove inbound: %s", err)
}
showResponese(resp)
}
}

View File

@ -0,0 +1,83 @@
package api
import (
"io"
"log"
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdLog = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api log [--server=127.0.0.1:8080]",
Short: "log operations",
Long: `
Follow and print logs from v2ray.
> It ignores -timeout flag while following logs
Arguments:
-restart
Restart the logger
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
{{.Exec}} {{.LongName}}
{{.Exec}} {{.LongName}} --restart
`,
Run: executeRestartLogger,
}
func executeRestartLogger(cmd *base.Command, args []string) {
var restart bool
cmd.Flag.BoolVar(&restart, "restart", false, "")
setSharedFlags(cmd)
cmd.Flag.Parse(args)
if restart {
restartLogger()
return
}
followLogger()
}
func restartLogger() {
conn, ctx, close := dialAPIServer()
defer close()
client := logService.NewLoggerServiceClient(conn)
r := &logService.RestartLoggerRequest{}
_, err := client.RestartLogger(ctx, r)
if err != nil {
base.Fatalf("failed to restart logger: %s", err)
}
}
func followLogger() {
conn, ctx, close := dialAPIServerWithoutTimeout()
defer close()
client := logService.NewLoggerServiceClient(conn)
r := &logService.FollowLogRequest{}
stream, err := client.FollowLog(ctx, r)
if err != nil {
base.Fatalf("failed to follow logger: %s", err)
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
base.Fatalf("failed to fetch log: %s", err)
}
log.Print(resp.Message)
}
}

View File

@ -1,40 +0,0 @@
package api
import (
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdRestartLogger = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api restartlogger [--server=127.0.0.1:8080]",
Short: "Restart the logger",
Long: `
Restart the logger of V2Ray.
Arguments:
-s, -server
The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3
`,
Run: executeRestartLogger,
}
func executeRestartLogger(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := logService.NewLoggerServiceClient(conn)
r := &logService.RestartLoggerRequest{}
resp, err := client.RestartLogger(ctx, r)
if err != nil {
base.Fatalf("failed to restart logger: %s", err)
}
showResponese(resp)
}

View File

@ -4,25 +4,31 @@ import (
"fmt"
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
)
var cmdAddOutbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
Short: "Add outbounds",
Short: "add outbounds",
Long: `
Add outbounds to V2Ray.
Arguments:
-s, -server
-format <format>
Specify the input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-r
Load folders recursively.
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
@ -34,26 +40,13 @@ Example:
func executeAddOutbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
setSharedConfigFlags(cmd)
cmd.Flag.Parse(args)
unnamedArgs := cmd.Flag.Args()
if len(unnamedArgs) == 0 {
fmt.Println("Reading from STDIN")
unnamedArgs = []string{"stdin:"}
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
}
outs := make([]conf.OutboundDetourConfig, 0)
for _, arg := range unnamedArgs {
r, err := cmdarg.LoadArg(arg)
if err != nil {
base.Fatalf("failed to load %s: %s", arg, err)
}
conf, err := serial.DecodeJSONConfig(r)
if err != nil {
base.Fatalf("failed to decode %s: %s", arg, err)
}
outs = append(outs, conf.OutboundConfigs...)
}
if len(outs) == 0 {
if len(c.OutboundConfigs) == 0 {
base.Fatalf("no valid outbound found")
}
@ -61,7 +54,7 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
defer close()
client := handlerService.NewHandlerServiceClient(conn)
for _, out := range outs {
for _, out := range c.OutboundConfigs {
fmt.Println("adding:", out.Tag)
o, err := out.Build()
if err != nil {
@ -70,10 +63,9 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
r := &handlerService.AddOutboundRequest{
Outbound: o,
}
resp, err := client.AddOutbound(ctx, r)
_, err = client.AddOutbound(ctx, r)
if err != nil {
base.Fatalf("failed to add outbound: %s", err)
}
showResponese(resp)
}
}

View File

@ -4,24 +4,31 @@ import (
"fmt"
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
)
var cmdRemoveOutbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
Short: "Remove outbounds",
Short: "remove outbounds",
Long: `
Remove outbounds from V2Ray.
Arguments:
-s, -server
-format <format>
Specify the input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-r
Load folders recursively.
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
@ -34,30 +41,12 @@ Example:
func executeRemoveOutbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
unnamedArgs := cmd.Flag.Args()
if len(unnamedArgs) == 0 {
fmt.Println("reading from stdin:")
unnamedArgs = []string{"stdin:"}
setSharedConfigFlags(cmd)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
}
tags := make([]string, 0)
for _, arg := range unnamedArgs {
if r, err := cmdarg.LoadArg(arg); err == nil {
conf, err := serial.DecodeJSONConfig(r)
if err != nil {
base.Fatalf("failed to decode %s: %s", arg, err)
}
outs := conf.OutboundConfigs
for _, o := range outs {
tags = append(tags, o.Tag)
}
} else {
// take request as tag
tags = append(tags, arg)
}
}
if len(tags) == 0 {
if len(c.OutboundConfigs) == 0 {
base.Fatalf("no outbound to remove")
}
@ -65,15 +54,14 @@ func executeRemoveOutbounds(cmd *base.Command, args []string) {
defer close()
client := handlerService.NewHandlerServiceClient(conn)
for _, tag := range tags {
fmt.Println("removing:", tag)
for _, c := range c.OutboundConfigs {
fmt.Println("removing:", c.Tag)
r := &handlerService.RemoveOutboundRequest{
Tag: tag,
Tag: c.Tag,
}
resp, err := client.RemoveOutbound(ctx, r)
_, err := client.RemoveOutbound(ctx, r)
if err != nil {
base.Fatalf("failed to remove outbound: %s", err)
}
showResponese(resp)
}
}

View File

@ -5,18 +5,22 @@ import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"time"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
core "github.com/v2fly/v2ray-core/v4"
)
var (
apiServerAddrPtr string
apiTimeout int
apiServerAddrPtr string
apiTimeout int
apiJSON bool
apiConfigFormat string
apiConfigRecursively bool
)
func setSharedFlags(cmd *base.Command) {
@ -24,14 +28,17 @@ func setSharedFlags(cmd *base.Command) {
cmd.Flag.StringVar(&apiServerAddrPtr, "server", "127.0.0.1:8080", "")
cmd.Flag.IntVar(&apiTimeout, "t", 3, "")
cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "")
cmd.Flag.BoolVar(&apiJSON, "json", false, "")
}
func setSharedConfigFlags(cmd *base.Command) {
cmd.Flag.StringVar(&apiConfigFormat, "format", core.FormatAuto, "")
cmd.Flag.BoolVar(&apiConfigRecursively, "r", false, "")
}
func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(apiTimeout)*time.Second)
conn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
base.Fatalf("failed to dial %s", apiServerAddrPtr)
}
conn = dialAPIServerWithContext(ctx)
close = func() {
cancel()
conn.Close()
@ -39,56 +46,40 @@ func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func())
return
}
func showResponese(m proto.Message) {
if isEmpty(m) {
// avoid outputs like `{}`, `{"key":{}}`
return
func dialAPIServerWithoutTimeout() (conn *grpc.ClientConn, ctx context.Context, close func()) {
ctx = context.Background()
conn = dialAPIServerWithContext(ctx)
close = func() {
conn.Close()
}
return
}
func dialAPIServerWithContext(ctx context.Context) (conn *grpc.ClientConn) {
conn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
base.Fatalf("failed to dial %s", apiServerAddrPtr)
}
return
}
func protoToJSONString(m proto.Message, prefix, indent string) (string, error) {
b := new(strings.Builder)
e := json.NewEncoder(b)
e.SetIndent("", " ")
e.SetIndent(prefix, indent)
e.SetEscapeHTML(false)
err := e.Encode(m)
if err != nil {
fmt.Fprintf(os.Stdout, "%v\n", m)
base.Fatalf("error encode json: %s", err)
return
return "", err
}
fmt.Println(strings.TrimSpace(b.String()))
return strings.TrimSpace(b.String()), nil
}
// isEmpty checks if the response is empty (all zero values).
// proto.Message types always "omitempty" on fields,
// there's no chance for a response to show zero-value messages,
// so we can perform isZero test here
func isEmpty(response interface{}) bool {
s := reflect.Indirect(reflect.ValueOf(response))
if s.Kind() == reflect.Invalid {
return true
func showJSONResponse(m proto.Message) {
output, err := protoToJSONString(m, "", "")
if err != nil {
fmt.Fprintf(os.Stdout, "%v\n", m)
base.Fatalf("error encode json: %s", err)
}
switch s.Kind() {
case reflect.Struct:
for i := 0; i < s.NumField(); i++ {
f := s.Type().Field(i)
if f.Name[0] < 65 || f.Name[0] > 90 {
// continue if not exported.
continue
}
field := s.Field(i)
if !isEmpty(field.Interface()) {
return false
}
}
case reflect.Array, reflect.Slice:
for i := 0; i < s.Len(); i++ {
if !isEmpty(s.Index(i).Interface()) {
return false
}
}
default:
if !s.IsZero() {
return false
}
}
return true
fmt.Println(output)
}

View File

@ -1,140 +0,0 @@
package api
import (
"testing"
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
)
func TestEmptyResponese_0(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Name: "1>>2",
Value: 1,
},
{
Name: "1>>2>>3",
Value: 2,
},
},
}
assert(t, isEmpty(r), false)
}
func TestEmptyResponese_1(t *testing.T) {
r := (*statsService.QueryStatsResponse)(nil)
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_2(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: nil,
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_3(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_4(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Name: "",
Value: 0,
},
},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_5(t *testing.T) {
type test struct {
Value *statsService.QueryStatsResponse
}
r := &test{
Value: &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Name: "",
},
},
},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_6(t *testing.T) {
type test struct {
Value *statsService.QueryStatsResponse
}
r := &test{
Value: &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Value: 1,
},
},
},
}
assert(t, isEmpty(r), false)
}
func TestEmptyResponese_7(t *testing.T) {
type test struct {
Value *int
}
v := 1
r := &test{
Value: &v,
}
assert(t, isEmpty(r), false)
}
func TestEmptyResponese_8(t *testing.T) {
type test struct {
Value *int
}
v := 0
r := &test{
Value: &v,
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_9(t *testing.T) {
assert(t, isEmpty(0), true)
}
func TestEmptyResponese_10(t *testing.T) {
assert(t, isEmpty(1), false)
}
func TestEmptyResponese_11(t *testing.T) {
r := []*statsService.Stat{
{
Name: "",
},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_12(t *testing.T) {
r := []*statsService.Stat{
{
Value: 1,
},
}
assert(t, isEmpty(r), false)
}
func assert(t *testing.T, value, expected bool) {
if value != expected {
t.Fatalf("Expected: %v, actual: %v", expected, value)
}
}

View File

@ -0,0 +1,165 @@
package api
import (
"fmt"
"os"
"sort"
"strings"
"time"
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
"github.com/v2fly/v2ray-core/v4/common/units"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdStats = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [pattern]...",
Short: "query statistics",
Long: `
Query statistics from V2Ray.
Arguments:
-regexp
The patterns are using regexp.
-reset
Fetch values then reset statistics counters to 0.
-runtime
Get runtime statistics.
-json
Use json output.
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout <seconds>
Timeout seconds to call API. Default 3
Example:
{{.Exec}} {{.LongName}} -runtime
{{.Exec}} {{.LongName}} node1
{{.Exec}} {{.LongName}} -json node1 node2
{{.Exec}} {{.LongName}} -regexp 'node1.+downlink'
`,
Run: executeStats,
}
func executeStats(cmd *base.Command, args []string) {
setSharedFlags(cmd)
var (
runtime bool
regexp bool
reset bool
)
cmd.Flag.BoolVar(&runtime, "runtime", false, "")
cmd.Flag.BoolVar(&regexp, "regexp", false, "")
cmd.Flag.BoolVar(&reset, "reset", false, "")
cmd.Flag.Parse(args)
unnamed := cmd.Flag.Args()
if runtime {
getRuntimeStats(apiJSON)
return
}
getStats(unnamed, regexp, reset, apiJSON)
}
func getRuntimeStats(jsonOutput bool) {
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.SysStatsRequest{}
resp, err := client.GetSysStats(ctx, r)
if err != nil {
base.Fatalf("failed to get sys stats: %s", err)
}
if jsonOutput {
showJSONResponse(resp)
return
}
showRuntimeStats(resp)
}
func showRuntimeStats(s *statsService.SysStatsResponse) {
formats := []string{"%-22s", "%-10s"}
rows := [][]string{
{"Up time", (time.Duration(s.Uptime) * time.Second).String()},
{"Memory obtained", units.ByteSize(s.Sys).String()},
{"Number of goroutines", fmt.Sprintf("%d", s.NumGoroutine)},
{"Heap allocated", units.ByteSize(s.Alloc).String()},
{"Live objects", fmt.Sprintf("%d", s.LiveObjects)},
{"Heap allocated total", units.ByteSize(s.TotalAlloc).String()},
{"Heap allocate count", fmt.Sprintf("%d", s.Mallocs)},
{"Heap free count", fmt.Sprintf("%d", s.Frees)},
{"Number of GC", fmt.Sprintf("%d", s.NumGC)},
{"Time of GC pause", (time.Duration(s.PauseTotalNs) * time.Nanosecond).String()},
}
sb := new(strings.Builder)
writeRow(sb, 0, 0,
[]string{"Item", "Value"},
formats,
)
for i, r := range rows {
writeRow(sb, 0, i+1, r, formats)
}
os.Stdout.WriteString(sb.String())
}
func getStats(patterns []string, regexp, reset, jsonOutput bool) {
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.QueryStatsRequest{
Patterns: patterns,
Regexp: regexp,
Reset_: reset,
}
resp, err := client.QueryStats(ctx, r)
if err != nil {
base.Fatalf("failed to query stats: %s", err)
}
if jsonOutput {
showJSONResponse(resp)
return
}
sort.Slice(resp.Stat, func(i, j int) bool {
return resp.Stat[i].Name < resp.Stat[j].Name
})
showStats(resp.Stat)
}
func showStats(stats []*statsService.Stat) {
if len(stats) == 0 {
return
}
formats := []string{"%-12s", "%s"}
sum := int64(0)
sb := new(strings.Builder)
idx := 0
writeRow(sb, 0, 0,
[]string{"Value", "Name"},
formats,
)
for _, stat := range stats {
// if stat.Value == 0 {
// continue
// }
idx++
sum += stat.Value
writeRow(
sb, 0, idx,
[]string{units.ByteSize(stat.Value).String(), stat.Name},
formats,
)
}
sb.WriteString(
fmt.Sprintf("\nTotal: %s\n", units.ByteSize(sum)),
)
os.Stdout.WriteString(sb.String())
}

View File

@ -1,55 +0,0 @@
package api
import (
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdGetStats = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [-name '']",
Short: "Get statistics",
Long: `
Get statistics from V2Ray.
Arguments:
-s, -server
The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3
-name
Name of the stat counter.
-reset
Reset the counter to fetching its value.
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -name "inbound>>>statin>>>traffic>>>downlink"
`,
Run: executeGetStats,
}
func executeGetStats(cmd *base.Command, args []string) {
setSharedFlags(cmd)
statName := cmd.Flag.String("name", "", "")
reset := cmd.Flag.Bool("reset", false, "")
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.GetStatsRequest{
Name: *statName,
Reset_: *reset,
}
resp, err := client.GetStats(ctx, r)
if err != nil {
base.Fatalf("failed to get stats: %s", err)
}
showResponese(resp)
}

View File

@ -1,55 +0,0 @@
package api
import (
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdQueryStats = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api statsquery [--server=127.0.0.1:8080] [-pattern '']",
Short: "Query statistics",
Long: `
Query statistics from V2Ray.
Arguments:
-s, -server
The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3
-pattern
Pattern of the query.
-reset
Reset the counter to fetching its value.
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -pattern "counter_"
`,
Run: executeQueryStats,
}
func executeQueryStats(cmd *base.Command, args []string) {
setSharedFlags(cmd)
pattern := cmd.Flag.String("pattern", "", "")
reset := cmd.Flag.Bool("reset", false, "")
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.QueryStatsRequest{
Pattern: *pattern,
Reset_: *reset,
}
resp, err := client.QueryStats(ctx, r)
if err != nil {
base.Fatalf("failed to query stats: %s", err)
}
showResponese(resp)
}

View File

@ -1,40 +0,0 @@
package api
import (
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdSysStats = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api statssys [--server=127.0.0.1:8080]",
Short: "Get system statistics",
Long: `
Get system statistics from V2Ray.
Arguments:
-s, -server
The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3
`,
Run: executeSysStats,
}
func executeSysStats(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.SysStatsRequest{}
resp, err := client.GetSysStats(ctx, r)
if err != nil {
base.Fatalf("failed to get sys stats: %s", err)
}
showResponese(resp)
}

View File

@ -3,38 +3,42 @@ package all
import (
"bytes"
"encoding/json"
"github.com/pelletier/go-toml"
"google.golang.org/protobuf/proto"
"os"
"strings"
"github.com/pelletier/go-toml"
"google.golang.org/protobuf/proto"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
"gopkg.in/yaml.v2"
)
var cmdConvert = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} convert [c1.json] [<url>.json] [dir1] ...",
Short: "Convert config files",
Short: "convert config files",
Long: `
Convert config files between different formats. Files are merged
before convert if multiple assigned.
Arguments:
-i, -input
-i, -input <format>
Specify the input format.
Available values: "json", "toml", "yaml"
Default: "json"
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-o, -output
-o, -output <format>
Specify the output format
Available values: "json", "toml", "yaml", "protobuf" / "pb"
Default: "json"
-r
Load confdir recursively.
Load folders recursively.
Examples:
@ -61,15 +65,10 @@ var (
outputFormat string
confDirRecursively bool
)
var formatExtensions = map[string][]string{
"json": {".json", ".jsonc"},
"toml": {".toml"},
"yaml": {".yaml", ".yml"},
}
func setConfArgs(cmd *base.Command) {
cmd.Flag.StringVar(&inputFormat, "input", "json", "")
cmd.Flag.StringVar(&inputFormat, "i", "json", "")
cmd.Flag.StringVar(&inputFormat, "input", core.FormatAuto, "")
cmd.Flag.StringVar(&inputFormat, "i", core.FormatAuto, "")
cmd.Flag.StringVar(&outputFormat, "output", "json", "")
cmd.Flag.StringVar(&outputFormat, "o", "json", "")
cmd.Flag.BoolVar(&confDirRecursively, "r", false, "")
@ -77,37 +76,36 @@ func setConfArgs(cmd *base.Command) {
func executeConvert(cmd *base.Command, args []string) {
setConfArgs(cmd)
cmd.Flag.Parse(args)
unnamed := cmd.Flag.Args()
inputFormat = strings.ToLower(inputFormat)
outputFormat = strings.ToLower(outputFormat)
files := resolveFolderToFiles(unnamed, formatExtensions[inputFormat], confDirRecursively)
if len(files) == 0 {
base.Fatalf("empty config list")
m, err := helpers.LoadConfigToMap(cmd.Flag.Args(), inputFormat, confDirRecursively)
if err != nil {
base.Fatalf(err.Error())
}
err = merge.ApplyRules(m)
if err != nil {
base.Fatalf(err.Error())
}
m := mergeConvertToMap(files, inputFormat)
var (
out []byte
err error
)
var out []byte
switch outputFormat {
case "json":
case core.FormatJSON:
out, err = json.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
}
case "toml":
case core.FormatTOML:
out, err = toml.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
}
case "yaml":
case core.FormatYAML:
out, err = yaml.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
}
case "pb", "protobuf":
case core.FormatProtobuf, core.FormatProtobufShort:
data, err := json.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
@ -132,6 +130,6 @@ func executeConvert(cmd *base.Command, args []string) {
}
if _, err := os.Stdout.Write(out); err != nil {
base.Fatalf("failed to write proto config: %s", err)
base.Fatalf("failed to write stdout: %s", err)
}
}

View File

@ -1,138 +0,0 @@
package all
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
func mergeConvertToMap(files []string, format string) map[string]interface{} {
var (
m map[string]interface{}
err error
)
switch inputFormat {
case "json":
m, err = merge.FilesToMap(files)
if err != nil {
base.Fatalf("failed to load json: %s", err)
}
case "toml":
bs, err := tomlsToJSONs(files)
if err != nil {
base.Fatalf("failed to convert toml to json: %s", err)
}
m, err = merge.BytesToMap(bs)
if err != nil {
base.Fatalf("failed to merge converted json: %s", err)
}
case "yaml":
bs, err := yamlsToJSONs(files)
if err != nil {
base.Fatalf("failed to convert yaml to json: %s", err)
}
m, err = merge.BytesToMap(bs)
if err != nil {
base.Fatalf("failed to merge converted json: %s", err)
}
default:
base.Errorf("invalid input format: %s", format)
base.Errorf("Run '%s help %s' for details.", base.CommandEnv.Exec, cmdConvert.LongName())
base.Exit()
}
return m
}
// resolveFolderToFiles expands folder path (if any and it exists) to file paths.
// Any other paths, like file, even URL, it returns them as is.
func resolveFolderToFiles(paths []string, extensions []string, recursively bool) []string {
dirReader := readConfDir
if recursively {
dirReader = readConfDirRecursively
}
files := make([]string, 0)
for _, p := range paths {
i, err := os.Stat(p)
if err == nil && i.IsDir() {
files = append(files, dirReader(p, extensions)...)
continue
}
files = append(files, p)
}
return files
}
func readConfDir(dirPath string, extensions []string) []string {
confs, err := ioutil.ReadDir(dirPath)
if err != nil {
base.Fatalf("failed to read dir %s: %s", dirPath, err)
}
files := make([]string, 0)
for _, f := range confs {
ext := filepath.Ext(f.Name())
for _, e := range extensions {
if strings.EqualFold(ext, e) {
files = append(files, filepath.Join(dirPath, f.Name()))
break
}
}
}
return files
}
// getFolderFiles get files in the folder and it's children
func readConfDirRecursively(dirPath string, extensions []string) []string {
files := make([]string, 0)
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
ext := filepath.Ext(path)
for _, e := range extensions {
if strings.EqualFold(ext, e) {
files = append(files, path)
break
}
}
return nil
})
if err != nil {
base.Fatalf("failed to read dir %s: %s", dirPath, err)
}
return files
}
func yamlsToJSONs(files []string) ([][]byte, error) {
jsons := make([][]byte, 0)
for _, file := range files {
bs, err := cmdarg.LoadArgToBytes(file)
if err != nil {
return nil, err
}
j, err := json.FromYAML(bs)
if err != nil {
return nil, err
}
jsons = append(jsons, j)
}
return jsons, nil
}
func tomlsToJSONs(files []string) ([][]byte, error) {
jsons := make([][]byte, 0)
for _, file := range files {
bs, err := cmdarg.LoadArgToBytes(file)
if err != nil {
return nil, err
}
j, err := json.FromTOML(bs)
if err != nil {
return nil, err
}
jsons = append(jsons, j)
}
return jsons, nil
}

View File

@ -10,44 +10,41 @@ var docFormat = &base.Command{
Long: `
{{.Exec}} supports different config formats:
* auto
The default loader, supports all extensions below.
It loads config by format detecting, with mixed
formats support.
* json (.json, .jsonc)
The default loader, multiple config files support.
The json loader, multiple files support, mergeable.
* toml (.toml)
The toml loader, multiple config files support.
The toml loader, multiple files support, mergeable.
* yaml (.yml)
The yaml loader, multiple config files support.
The yaml loader, multiple files support, mergeable.
* protobuf / pb (.pb)
Single conifg file support. If multiple files assigned,
only the first one is loaded.
Single file support, unmergeable.
If "-format" is not explicitly specified, {{.Exec}} will choose
a loader by detecting the extension of the first config file, or
use the default loader.
The following explains how format loaders behave with examples.
Examples:
{{.Exec}} run -d dir (1)
{{.Exec}} run -format=protobuf -d dir (2)
{{.Exec}} test -c c1.yml -d dir (3)
{{.Exec}} test -format=pb -c c1.json (4)
{{.Exec}} run -d dir (1)
{{.Exec}} run -c c1.json -c c2.yaml (2)
{{.Exec}} run -format=json -d dir (3)
{{.Exec}} test -c c1.yml -c c2.pb (4)
{{.Exec}} test -format=pb -d dir (5)
{{.Exec}} test -format=protobuf -c c1.json (6)
(1) The default json loader is used, {{.Exec}} will try to load all
json files in the "dir".
(2) The protobuf loader is specified, {{.Exec}} will try to find
all protobuf files in the "dir", but only the the first
.pb file is loaded.
(3) The yaml loader is selected because of the "c1.yml" file,
{{.Exec}} will try to load "c1.yml" and all yaml files in
the "dir".
(4) The protobuf loader is specified, {{.Exec}} will load
"c1.json" as protobuf, no matter its extension.
(1) Load all supported files in the "dir".
(2) JSON and YAML are merged and loaded.
(3) Load all JSON files in the "dir".
(4) Goes error since .pb is not mergeable to others
(5) Works only when single .pb file found, if not, failed due to
unmergeable.
(6) Force load "c1.json" as protobuf, no matter its extension.
`,
}

View File

@ -13,6 +13,9 @@ Merging of config files is applied in following commands:
{{.Exec}} run -c c1.json -c c2.json ...
{{.Exec}} test -c c1.yaml -c c2.yaml ...
{{.Exec}} convert c1.json dir1 ...
{{.Exec}} api ado c1.json dir1 ...
{{.Exec}} api rmi c1.json dir1 ...
... and more ...
Support of toml and yaml is implemented by converting them to json,
both merge and load. So we take json as example here.

View File

@ -16,30 +16,30 @@ import (
// cmdCert is the tls cert command
var cmdCert = &base.Command{
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=v2ray.com] [--expire=240h]",
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=v2fly.org] [--expire=240h]",
Short: "Generate TLS certificates",
Long: `
Generate TLS certificates.
Arguments:
-domain=domain_name
-domain <domain_name>
The domain name for the certificate.
-org=organization
-org <organization>
The organization name for the certificate.
-ca
Whether this certificate is a CA
The certificate is a CA
-json
The output of certificate to JSON
To output certificate to JSON
-file
-file <path>
The certificate path to save.
-expire
Expire time of the certificate. Default value 3 months.
-expire <days>
Expire days of the certificate. Default 90 days.
`,
}
@ -54,12 +54,12 @@ var (
return true
}()
certCommonName = cmdCert.Flag.String("name", "V2Ray Inc", "The common name of this certificate")
certOrganization = cmdCert.Flag.String("org", "V2Ray Inc", "Organization of the certificate")
certIsCA = cmdCert.Flag.Bool("ca", false, "Whether this certificate is a CA")
certJSONOutput = cmdCert.Flag.Bool("json", true, "Print certificate in JSON format")
certFileOutput = cmdCert.Flag.String("file", "", "Save certificate in file.")
certExpire = cmdCert.Flag.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
certCommonName = cmdCert.Flag.String("name", "V2Ray Inc", "")
certOrganization = cmdCert.Flag.String("org", "V2Ray Inc", "")
certIsCA = cmdCert.Flag.Bool("ca", false, "")
certJSONOutput = cmdCert.Flag.Bool("json", true, "")
certFileOutput = cmdCert.Flag.String("file", "", "")
certExpire = cmdCert.Flag.Uint("expire", 90, "")
)
func executeCert(cmd *base.Command, args []string) {
@ -69,7 +69,7 @@ func executeCert(cmd *base.Command, args []string) {
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
}
opts = append(opts, cert.NotAfter(time.Now().Add(*certExpire)))
opts = append(opts, cert.NotAfter(time.Now().Add(time.Duration(*certExpire)*time.Hour*24)))
opts = append(opts, cert.CommonName(*certCommonName))
if len(certDomainNames) > 0 {
opts = append(opts, cert.DNSNames(certDomainNames...))

View File

@ -14,13 +14,13 @@ import (
// cmdPing is the tls ping command
var cmdPing = &base.Command{
UsageLine: "{{.Exec}} tls ping [-ip <ip>] <domain>",
Short: "Ping the domain with TLS handshake",
Short: "ping the domain with TLS handshake",
Long: `
Ping the domain with TLS handshake.
Arguments:
-ip
-ip <ip>
The IP address of the domain.
`,
}

View File

@ -9,7 +9,7 @@ import (
var cmdUUID = &base.Command{
UsageLine: "{{.Exec}} uuid",
Short: "Generate new UUIDs",
Short: "generate new UUIDs",
Long: `Generate new UUIDs.
`,
Run: executeUUID,

View File

@ -9,13 +9,13 @@ import (
var cmdVerify = &base.Command{
UsageLine: "{{.Exec}} verify [--sig=sig-file] file",
Short: "Verify if a binary is officially signed",
Short: "verify if a binary is officially signed",
Long: `
Verify if a binary is officially signed.
Arguments:
-sig
-sig <signature_file>
The path to the signature file
`,
}

View File

@ -0,0 +1,55 @@
package helpers
import (
"bytes"
"os"
"github.com/v2fly/v2ray-core/v4/infra/conf"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/infra/conf/mergers"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
)
// LoadConfig load config files to *conf.Config, it will:
// - resolve folder to files
// - try to read stdin if no file specified
func LoadConfig(files []string, format string, recursively bool) (*conf.Config, error) {
m, err := LoadConfigToMap(files, format, recursively)
if err != nil {
return nil, err
}
bs, err := merge.FromMap(m)
if err != nil {
return nil, err
}
r := bytes.NewReader(bs)
return serial.DecodeJSONConfig(r)
}
// LoadConfigToMap load config files to map, it will:
// - resolve folder to files
// - try to read stdin if no file specified
func LoadConfigToMap(files []string, format string, recursively bool) (map[string]interface{}, error) {
var err error
if len(files) > 0 {
var extensions []string
extensions, err := mergers.GetExtensions(format)
if err != nil {
return nil, err
}
files, err = ResolveFolderToFiles(files, extensions, recursively)
if err != nil {
return nil, err
}
}
m := make(map[string]interface{})
if len(files) == 0 {
err = mergers.MergeAs(format, os.Stdin, m)
} else {
err = mergers.MergeAs(format, files, m)
}
if err != nil {
return nil, err
}
return m, nil
}

View File

@ -0,0 +1,70 @@
package helpers
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// ReadDir finds files according to extensions in the dir
func ReadDir(dir string, extensions []string) ([]string, error) {
confs, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
files := make([]string, 0)
for _, f := range confs {
ext := filepath.Ext(f.Name())
for _, e := range extensions {
if strings.EqualFold(ext, e) {
files = append(files, filepath.Join(dir, f.Name()))
break
}
}
}
return files, nil
}
// ReadDirRecursively finds files according to extensions in the dir recursively
func ReadDirRecursively(dir string, extensions []string) ([]string, error) {
files := make([]string, 0)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
ext := filepath.Ext(path)
for _, e := range extensions {
if strings.EqualFold(ext, e) {
files = append(files, path)
break
}
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}
// ResolveFolderToFiles expands folder path (if any and it exists) to file paths.
// Any other paths, like file, even URL, it returns them as is.
func ResolveFolderToFiles(paths []string, extensions []string, recursively bool) ([]string, error) {
dirReader := ReadDir
if recursively {
dirReader = ReadDirRecursively
}
files := make([]string, 0)
for _, p := range paths {
i, err := os.Stat(p)
if err == nil && i.IsDir() {
fs, err := dirReader(p, extensions)
if err != nil {
return nil, fmt.Errorf("failed to read dir %s: %s", p, err)
}
files = append(files, fs...)
continue
}
files = append(files, p)
}
return files, nil
}

View File

@ -1,6 +1,7 @@
package commands
import (
"fmt"
"io/ioutil"
"log"
"os"
@ -20,22 +21,22 @@ import (
var CmdRun = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} run [-c config.json] [-d dir]",
Short: "Run V2Ray with config",
Short: "run V2Ray with config",
Long: `
Run V2Ray with config.
Arguments:
-c, -config
-c, -config <file>
Config file for V2Ray. Multiple assign is accepted.
-d, -confdir
-d, -confdir <dir>
A dir with config files. Multiple assign is accepted.
-r
Load confdir recursively.
-format
-format <format>
Format of input files. (default "json")
Examples:
@ -56,7 +57,7 @@ var (
)
func setConfigFlags(cmd *base.Command) {
configFormat = cmd.Flag.String("format", "", "")
configFormat = cmd.Flag.String("format", core.FormatAuto, "")
configDirRecursively = cmd.Flag.Bool("r", false, "")
cmd.Flag.Var(&configFiles, "config", "")
@ -69,6 +70,7 @@ func executeRun(cmd *base.Command, args []string) {
setConfigFlags(cmd)
cmd.Flag.Parse(args)
printVersion()
configFiles = getConfigFilePath()
server, err := startV2Ray()
if err != nil {
base.Fatalf("Failed to start: %s", err)
@ -139,20 +141,8 @@ func readConfDirRecursively(dirPath string, extension []string) cmdarg.Arg {
return files
}
func getLoaderExtension() ([]string, error) {
firstFile := ""
if len(configFiles) > 0 {
firstFile = configFiles[0]
}
loader, err := core.GetConfigLoader(*configFormat, firstFile)
if err != nil {
return nil, err
}
return loader.Extension, nil
}
func getConfigFilePath() cmdarg.Arg {
extension, err := getLoaderExtension()
extension, err := core.GetLoaderExtensions(*configFormat)
if err != nil {
base.Fatalf(err.Error())
}
@ -190,16 +180,18 @@ func getConfigFilePath() cmdarg.Arg {
return cmdarg.Arg{configFile}
}
log.Println("Using config from STDIN")
return cmdarg.Arg{"stdin:"}
return nil
}
func startV2Ray() (core.Server, error) {
configFiles := getConfigFilePath()
config, err := core.LoadConfig(*configFormat, configFiles[0], configFiles)
config, err := core.LoadConfig(*configFormat, configFiles)
if err != nil {
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
if len(configFiles) == 0 {
err = newError("failed to load config").Base(err)
} else {
err = newError(fmt.Sprintf("failed to load config: %s", configFiles)).Base(err)
}
return nil, err
}
server, err := core.New(config)

View File

@ -12,22 +12,22 @@ import (
var CmdTest = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} test [-format=json] [-c config.json] [-d dir]",
Short: "Test config files",
Short: "test config files",
Long: `
Test config files, without launching V2Ray server.
Arguments:
-c, -config
-c, -config <file>
Config file for V2Ray. Multiple assign is accepted.
-d, -confdir
-d, -confdir <dir>
A dir with config files. Multiple assign is accepted.
-r
Load confdir recursively.
-format
-format <format>
Format of input files. (default "json")
Examples:
@ -44,7 +44,7 @@ func executeTest(cmd *base.Command, args []string) {
setConfigFlags(cmd)
cmd.Flag.Parse(args)
extension, err := getLoaderExtension()
extension, err := core.GetLoaderExtensions(*configFormat)
if err != nil {
base.Fatalf(err.Error())
}
@ -59,32 +59,10 @@ func executeTest(cmd *base.Command, args []string) {
configFiles = append(configFiles, dirReader(d, extension)...)
}
}
if len(configFiles) == 0 {
if len(configDirs) == 0 {
cmd.Flag.Usage()
base.SetExitStatus(1)
base.Exit()
}
base.Fatalf("no config file found with extension: %s", extension)
}
printVersion()
_, err = startV2RayTesting()
_, err = startV2Ray()
if err != nil {
base.Fatalf("Test failed: %s", err)
}
fmt.Println("Configuration OK.")
}
func startV2RayTesting() (core.Server, error) {
config, err := core.LoadConfig(*configFormat, configFiles[0], configFiles)
if err != nil {
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
}
server, err := core.New(config)
if err != nil {
return nil, newError("failed to create server").Base(err)
}
return server, nil
}

View File

@ -10,7 +10,7 @@ import (
// CmdVersion prints V2Ray Versions
var CmdVersion = &base.Command{
UsageLine: "{{.Exec}} version",
Short: "Print V2Ray Versions",
Short: "print V2Ray version",
Long: `Prints the build information for V2Ray.
`,
Run: executeVersion,

View File

@ -70,17 +70,8 @@ import (
_ "github.com/v2fly/v2ray-core/v4/infra/conf/geodata/memconservative"
_ "github.com/v2fly/v2ray-core/v4/infra/conf/geodata/standard"
// JSON config support. Choose only one from the two below.
// The following line loads JSON from v2ctl
// _ "github.com/v2fly/v2ray-core/v4/main/json"
// The following line loads JSON internally
_ "github.com/v2fly/v2ray-core/v4/main/json"
// TOML config support.
_ "github.com/v2fly/v2ray-core/v4/main/toml"
// YAML config support.
_ "github.com/v2fly/v2ray-core/v4/main/yaml"
// JSON, TOML, YAML config support.
_ "github.com/v2fly/v2ray-core/v4/main/formats"
// commands
_ "github.com/v2fly/v2ray-core/v4/main/commands/all"

View File

@ -0,0 +1,9 @@
package formats
import "github.com/v2fly/v2ray-core/v4/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

56
main/formats/formats.go Normal file
View File

@ -0,0 +1,56 @@
package formats
import (
"bytes"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/infra/conf/mergers"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
)
func init() {
for _, formatName := range mergers.GetAllNames() {
loader, err := makeMergeLoader(formatName)
if err != nil {
panic(err)
}
if formatName == core.FormatAuto {
loader.Extension = nil
}
common.Must(core.RegisterConfigLoader(loader))
}
}
func makeMergeLoader(formatName string) (*core.ConfigFormat, error) {
extenstoins, err := mergers.GetExtensions(formatName)
if err != nil {
return nil, err
}
return &core.ConfigFormat{
Name: []string{formatName},
Extension: extenstoins,
Loader: makeLoaderFunc(formatName),
}, nil
}
func makeLoaderFunc(formatName string) core.ConfigLoader {
return func(input interface{}) (*core.Config, error) {
m := make(map[string]interface{})
err := mergers.MergeAs(formatName, input, m)
if err != nil {
return nil, err
}
data, err := merge.FromMap(m)
if err != nil {
return nil, err
}
r := bytes.NewReader(data)
cf, err := serial.DecodeJSONConfig(r)
if err != nil {
return nil, err
}
return cf.Build()
}
}

View File

@ -1,38 +0,0 @@
package json
import (
"bytes"
"io"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
)
func init() {
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
Name: []string{"JSON"},
Extension: []string{".json", ".jsonc"},
Loader: func(input interface{}) (*core.Config, error) {
switch v := input.(type) {
case cmdarg.Arg:
data, err := merge.FilesToJSON(v)
if err != nil {
return nil, err
}
r := bytes.NewReader(data)
cf, err := serial.DecodeJSONConfig(r)
if err != nil {
return nil, err
}
return cf.Build()
case io.Reader:
return serial.LoadJSONConfig(v)
default:
return nil, newError("unknow type")
}
},
}))
}

View File

@ -1,69 +0,0 @@
package toml
import (
"bytes"
"errors"
"io"
"io/ioutil"
"github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
)
func init() {
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
Name: []string{"TOML"},
Extension: []string{".toml"},
Loader: func(input interface{}) (*core.Config, error) {
switch v := input.(type) {
case cmdarg.Arg:
bs, err := tomlsToJSONs(v)
if err != nil {
return nil, err
}
data, err := merge.BytesToJSON(bs)
if err != nil {
return nil, err
}
r := bytes.NewReader(data)
cf, err := serial.DecodeJSONConfig(r)
if err != nil {
return nil, err
}
return cf.Build()
case io.Reader:
bs, err := ioutil.ReadAll(v)
if err != nil {
return nil, err
}
bs, err = json.FromTOML(bs)
if err != nil {
return nil, err
}
return serial.LoadJSONConfig(bytes.NewBuffer(bs))
default:
return nil, errors.New("unknow type")
}
},
}))
}
func tomlsToJSONs(files []string) ([][]byte, error) {
jsons := make([][]byte, 0)
for _, file := range files {
bs, err := cmdarg.LoadArgToBytes(file)
if err != nil {
return nil, err
}
j, err := json.FromTOML(bs)
if err != nil {
return nil, err
}
jsons = append(jsons, j)
}
return jsons, nil
}

View File

@ -1,69 +0,0 @@
package yaml
import (
"bytes"
"errors"
"io"
"io/ioutil"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
)
func init() {
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
Name: []string{"YAML"},
Extension: []string{".yml", ".yaml"},
Loader: func(input interface{}) (*core.Config, error) {
switch v := input.(type) {
case cmdarg.Arg:
bs, err := yamlsToJSONs(v)
if err != nil {
return nil, err
}
data, err := merge.BytesToJSON(bs)
if err != nil {
return nil, err
}
r := bytes.NewReader(data)
cf, err := serial.DecodeJSONConfig(r)
if err != nil {
return nil, err
}
return cf.Build()
case io.Reader:
bs, err := ioutil.ReadAll(v)
if err != nil {
return nil, err
}
bs, err = json.FromYAML(bs)
if err != nil {
return nil, err
}
return serial.LoadJSONConfig(bytes.NewBuffer(bs))
default:
return nil, errors.New("unknow type")
}
},
}))
}
func yamlsToJSONs(files []string) ([][]byte, error) {
jsons := make([][]byte, 0)
for _, file := range files {
bs, err := cmdarg.LoadArgToBytes(file)
if err != nil {
return nil, err
}
j, err := json.FromYAML(bs)
if err != nil {
return nil, err
}
jsons = append(jsons, j)
}
return jsons, nil
}

View File

@ -28,7 +28,7 @@ func RunV2RayProtobuf(config []byte) *exec.Cmd {
os.MkdirAll(covDir, os.ModeDir)
randomID := uuid.New()
profile := randomID.String() + ".out"
proc := exec.Command(testBinaryPath, "run", "-config=stdin:", "-format=pb", "-test.run", "TestRunMainForCoverage", "-test.coverprofile", profile, "-test.outputdir", covDir)
proc := exec.Command(testBinaryPath, "run", "-format=pb", "-test.run", "TestRunMainForCoverage", "-test.coverprofile", profile, "-test.outputdir", covDir)
proc.Stdin = bytes.NewBuffer(config)
proc.Stderr = os.Stderr
proc.Stdout = os.Stdout

View File

@ -23,7 +23,7 @@ func BuildV2Ray() error {
func RunV2RayProtobuf(config []byte) *exec.Cmd {
genTestBinaryPath()
proc := exec.Command(testBinaryPath, "run", "-config=stdin:", "-format=pb")
proc := exec.Command(testBinaryPath, "run", "-format=pb")
proc.Stdin = bytes.NewBuffer(config)
proc.Stderr = os.Stderr
proc.Stdout = os.Stdout

View File

@ -85,7 +85,7 @@ func TestV2RayClose(t *testing.T) {
cfgBytes, err := proto.Marshal(config)
common.Must(err)
server, err := StartInstance("protobuf", cfgBytes)
server, err := StartInstance(FormatProtobuf, cfgBytes)
common.Must(err)
server.Close()
}