From b7f2f302441acb203620b68597d70bd2cdeee2fe Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Sun, 8 Apr 2018 23:22:55 +0200 Subject: [PATCH] support fetching config from http --- common/buf/multi_buffer.go | 18 ++++- common/buf/reader.go | 3 +- .../platform/ctlcmd/attr_other.go | 2 +- .../platform/ctlcmd/attr_windows.go | 2 +- common/platform/ctlcmd/ctlcmd.go | 67 +++++++++++++++++++ common/platform/ctlcmd/errors.generated.go | 5 ++ main/json/config_json.go | 62 +---------------- main/main.go | 7 ++ 8 files changed, 103 insertions(+), 63 deletions(-) rename main/json/config_json_other.go => common/platform/ctlcmd/attr_other.go (86%) rename main/json/config_json_windows.go => common/platform/ctlcmd/attr_windows.go (90%) create mode 100644 common/platform/ctlcmd/ctlcmd.go create mode 100644 common/platform/ctlcmd/errors.generated.go diff --git a/common/buf/multi_buffer.go b/common/buf/multi_buffer.go index b4e6d4cf..dd896150 100644 --- a/common/buf/multi_buffer.go +++ b/common/buf/multi_buffer.go @@ -6,6 +6,7 @@ import ( "v2ray.com/core/common" "v2ray.com/core/common/errors" + "v2ray.com/core/common/serial" ) // ReadAllToMultiBuffer reads all content from the reader into a MultiBuffer, until EOF. @@ -102,6 +103,9 @@ func (mb MultiBuffer) Copy(b []byte) int { // Read implements io.Reader. func (mb *MultiBuffer) Read(b []byte) (int, error) { + if mb.Len() == 0 { + return 0, io.EOF + } endIndex := len(*mb) totalBytes := 0 for i, bb := range *mb { @@ -121,7 +125,9 @@ func (mb *MultiBuffer) Read(b []byte) (int, error) { } // Write implements io.Writer. -func (mb *MultiBuffer) Write(b []byte) { +func (mb *MultiBuffer) Write(b []byte) (int, error) { + totalBytes := len(b) + n := len(*mb) if n > 0 && !(*mb)[n-1].IsFull() { nBytes, _ := (*mb)[n-1].Write(b) @@ -134,6 +140,8 @@ func (mb *MultiBuffer) Write(b []byte) { b = b[nBytes:] mb.Append(bb) } + + return totalBytes, nil } // Len returns the total number of bytes in the MultiBuffer. @@ -164,6 +172,14 @@ func (mb *MultiBuffer) Release() { *mb = nil } +func (mb MultiBuffer) String() string { + v := make([]interface{}, len(mb)) + for i, b := range mb { + v[i] = b + } + return serial.Concat(v...) +} + // ToNetBuffers converts this MultiBuffer to net.Buffers. The return net.Buffers points to the same content of the MultiBuffer. func (mb MultiBuffer) ToNetBuffers() net.Buffers { bs := make([][]byte, len(mb)) diff --git a/common/buf/reader.go b/common/buf/reader.go index 28b9fb84..6be917e8 100644 --- a/common/buf/reader.go +++ b/common/buf/reader.go @@ -3,6 +3,7 @@ package buf import ( "io" + "v2ray.com/core/common" "v2ray.com/core/common/errors" ) @@ -52,7 +53,7 @@ func (r *BytesToBufferReader) ReadMultiBuffer() (MultiBuffer, error) { nBytes, err := r.Reader.Read(r.buffer) if nBytes > 0 { mb := NewMultiBufferCap(int32(nBytes/Size) + 1) - mb.Write(r.buffer[:nBytes]) + common.Must2(mb.Write(r.buffer[:nBytes])) if nBytes == len(r.buffer) && nBytes < int(largeSize) { freeBytes(r.buffer) r.buffer = newBytes(int32(nBytes) + 1) diff --git a/main/json/config_json_other.go b/common/platform/ctlcmd/attr_other.go similarity index 86% rename from main/json/config_json_other.go rename to common/platform/ctlcmd/attr_other.go index 2c52fd10..8dfe963d 100644 --- a/main/json/config_json_other.go +++ b/common/platform/ctlcmd/attr_other.go @@ -1,6 +1,6 @@ // +build !windows -package json +package ctlcmd import "syscall" diff --git a/main/json/config_json_windows.go b/common/platform/ctlcmd/attr_windows.go similarity index 90% rename from main/json/config_json_windows.go rename to common/platform/ctlcmd/attr_windows.go index 78f63c79..b4c1c5f8 100644 --- a/main/json/config_json_windows.go +++ b/common/platform/ctlcmd/attr_windows.go @@ -1,6 +1,6 @@ // +build windows -package json +package ctlcmd import "syscall" diff --git a/common/platform/ctlcmd/ctlcmd.go b/common/platform/ctlcmd/ctlcmd.go new file mode 100644 index 00000000..19789d76 --- /dev/null +++ b/common/platform/ctlcmd/ctlcmd.go @@ -0,0 +1,67 @@ +package ctlcmd + +import ( + "context" + "io" + "os" + "os/exec" + + "v2ray.com/core/common/buf" + "v2ray.com/core/common/platform" + "v2ray.com/core/common/signal" +) + +//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg ctlcmd -path Command,Platform,CtlCmd + +func Run(args []string, input io.Reader) (buf.MultiBuffer, error) { + v2ctl := platform.GetToolLocation("v2ctl") + if _, err := os.Stat(v2ctl); err != nil { + return nil, err + } + + errBuffer := &buf.MultiBuffer{} + + cmd := exec.Command(v2ctl, args...) + cmd.Stderr = errBuffer + cmd.SysProcAttr = getSysProcAttr() + if input != nil { + cmd.Stdin = input + } + + stdoutReader, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + defer stdoutReader.Close() + + if err := cmd.Start(); err != nil { + return nil, err + } + + var content buf.MultiBuffer + loadTask := signal.ExecuteAsync(func() error { + c, err := buf.ReadAllToMultiBuffer(stdoutReader) + if err != nil { + return err + } + content = c + return nil + }) + + waitTask := signal.ExecuteAsync(func() error { + if err := cmd.Wait(); err != nil { + msg := "failed to execute v2ctl" + if errBuffer.Len() > 0 { + msg += ": " + errBuffer.String() + } + return newError(msg).Base(err) + } + return nil + }) + + if err := signal.ErrorOrFinish2(context.Background(), loadTask, waitTask); err != nil { + return nil, err + } + + return content, nil +} diff --git a/common/platform/ctlcmd/errors.generated.go b/common/platform/ctlcmd/errors.generated.go new file mode 100644 index 00000000..a406cad5 --- /dev/null +++ b/common/platform/ctlcmd/errors.generated.go @@ -0,0 +1,5 @@ +package ctlcmd + +import "v2ray.com/core/common/errors" + +func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("Command", "Platform", "CtlCmd") } diff --git a/main/json/config_json.go b/main/json/config_json.go index a98e1ca7..a0981487 100644 --- a/main/json/config_json.go +++ b/main/json/config_json.go @@ -3,79 +3,23 @@ package json //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg json -path Main,Json import ( - "context" "io" - "os" - "os/exec" "v2ray.com/core" "v2ray.com/core/common" - "v2ray.com/core/common/platform" - "v2ray.com/core/common/signal" + "v2ray.com/core/common/platform/ctlcmd" ) -type logWriter struct{} - -func (*logWriter) Write(b []byte) (int, error) { - n, err := os.Stderr.Write(b) - if err == nil { - os.Stderr.WriteString(platform.LineSeparator()) - } - return n, err -} - -func jsonToProto(input io.Reader) (*core.Config, error) { - v2ctl := platform.GetToolLocation("v2ctl") - if _, err := os.Stat(v2ctl); err != nil { - return nil, err - } - cmd := exec.Command(v2ctl, "config") - cmd.Stdin = input - cmd.Stderr = &logWriter{} - cmd.SysProcAttr = getSysProcAttr() - - stdoutReader, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - defer stdoutReader.Close() - - if err := cmd.Start(); err != nil { - return nil, err - } - - var config *core.Config - - loadTask := signal.ExecuteAsync(func() error { - c, err := core.LoadConfig("protobuf", "", stdoutReader) - if err != nil { - return err - } - config = c - return nil - }) - - waitTask := signal.ExecuteAsync(func() error { - return cmd.Wait() - }) - - if err := signal.ErrorOrFinish2(context.Background(), loadTask, waitTask); err != nil { - return nil, err - } - - return config, nil -} - func init() { common.Must(core.RegisterConfigLoader(&core.ConfigFormat{ Name: "JSON", Extension: []string{"json"}, Loader: func(input io.Reader) (*core.Config, error) { - config, err := jsonToProto(input) + jsonContent, err := ctlcmd.Run([]string{"config"}, input) if err != nil { return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning() } - return config, nil + return core.LoadConfig("protobuf", "", &jsonContent) }, })) } diff --git a/main/main.go b/main/main.go index e3b323da..55b293d0 100644 --- a/main/main.go +++ b/main/main.go @@ -14,6 +14,7 @@ import ( "v2ray.com/core" "v2ray.com/core/common/platform" + "v2ray.com/core/common/platform/ctlcmd" _ "v2ray.com/core/main/distro/all" ) @@ -63,6 +64,12 @@ func startV2Ray() (core.Server, error) { var configInput io.Reader if configFile == "stdin:" { configInput = os.Stdin + } else if strings.HasPrefix(configFile, "http://") || strings.HasPrefix(configFile, "https://") { + content, err := ctlcmd.Run([]string{"fetch", configFile}, nil) + if err != nil { + return nil, err + } + configInput = &content } else { fixedFile := os.ExpandEnv(configFile) file, err := os.Open(fixedFile)