mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-21 09:36:34 -05:00
parent
fa0cf6db26
commit
2523d77919
@ -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 {
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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) {};
|
||||
}
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -21,7 +21,7 @@ type Balancer struct {
|
||||
ohm outbound.Manager
|
||||
fallbackTag string
|
||||
|
||||
override overridden
|
||||
override override
|
||||
}
|
||||
|
||||
// PickOutbound picks the tag of a outbound
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 (
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
98
common/units/bytesize.go
Normal 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
|
||||
}
|
66
common/units/bytesize_test.go
Normal file
66
common/units/bytesize_test.go
Normal 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
145
config.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
package json
|
||||
package mergers
|
||||
|
||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||
|
25
infra/conf/mergers/extensions.go
Normal file
25
infra/conf/mergers/extensions.go
Normal 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
|
||||
}
|
40
infra/conf/mergers/formats.go
Normal file
40
infra/conf/mergers/formats.go
Normal 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
|
||||
}
|
95
infra/conf/mergers/merge.go
Normal file
95
infra/conf/mergers/merge.go
Normal 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)
|
||||
}
|
103
infra/conf/mergers/merger_base.go
Normal file
103
infra/conf/mergers/merger_base.go
Normal 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
|
||||
}
|
32
infra/conf/mergers/mergers.go
Normal file
32
infra/conf/mergers/mergers.go
Normal 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,
|
||||
}),
|
||||
)
|
||||
}
|
10
infra/conf/mergers/names.go
Normal file
10
infra/conf/mergers/names.go
Normal 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
|
||||
}
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
83
main/commands/all/api/log.go
Normal file
83
main/commands/all/api/log.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
165
main/commands/all/api/stats.go
Normal file
165
main/commands/all/api/stats.go
Normal 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(®exp, "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())
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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.
|
||||
`,
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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...))
|
||||
|
@ -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.
|
||||
`,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
`,
|
||||
}
|
||||
|
55
main/commands/helpers/config_load.go
Normal file
55
main/commands/helpers/config_load.go
Normal 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
|
||||
}
|
70
main/commands/helpers/fs.go
Normal file
70
main/commands/helpers/fs.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
9
main/formats/errors.generated.go
Normal file
9
main/formats/errors.generated.go
Normal 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
56
main/formats/formats.go
Normal 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()
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user