webcommander.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package webcommander
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. "context"
  6. "io/fs"
  7. "net/http"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/improbable-eng/grpc-web/go/grpcweb"
  12. "google.golang.org/grpc"
  13. core "github.com/v2fly/v2ray-core/v5"
  14. "github.com/v2fly/v2ray-core/v5/app/commander"
  15. "github.com/v2fly/v2ray-core/v5/common"
  16. "github.com/v2fly/v2ray-core/v5/features/outbound"
  17. )
  18. //go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
  19. func newWebCommander(ctx context.Context, config *Config) (*WebCommander, error) {
  20. if config == nil {
  21. return nil, newError("config is nil")
  22. }
  23. if config.Tag == "" {
  24. return nil, newError("config.Tag is empty")
  25. }
  26. var webRootfs fs.FS
  27. if config.WebRoot != nil {
  28. zipReader, err := zip.NewReader(bytes.NewReader(config.WebRoot), int64(len(config.WebRoot)))
  29. if err != nil {
  30. return nil, newError("failed to create zip reader").Base(err)
  31. }
  32. webRootfs = zipReader
  33. }
  34. return &WebCommander{ctx: ctx, config: config, webRootfs: webRootfs}, nil
  35. }
  36. type WebCommander struct {
  37. sync.Mutex
  38. ctx context.Context
  39. ohm outbound.Manager
  40. cm commander.CommanderIfce
  41. server *http.Server
  42. wrappedGrpc *grpcweb.WrappedGrpcServer
  43. webRootfs fs.FS
  44. config *Config
  45. }
  46. func (w *WebCommander) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
  47. apiPath := w.config.ApiMountpoint
  48. if strings.HasPrefix(request.URL.Path, apiPath) {
  49. request.URL.Path = strings.TrimPrefix(request.URL.Path, apiPath)
  50. if w.wrappedGrpc.IsGrpcWebRequest(request) {
  51. w.wrappedGrpc.ServeHTTP(writer, request)
  52. return
  53. }
  54. }
  55. if w.webRootfs != nil {
  56. http.ServeFileFS(writer, request, w.webRootfs, request.URL.Path)
  57. return
  58. }
  59. writer.WriteHeader(http.StatusNotFound)
  60. }
  61. func (w *WebCommander) asyncStart() {
  62. var grpcServer *grpc.Server
  63. for {
  64. grpcServer = w.cm.ExtractGrpcServer()
  65. if grpcServer != nil {
  66. break
  67. }
  68. time.Sleep(time.Second)
  69. }
  70. listener := commander.NewOutboundListener()
  71. wrappedGrpc := grpcweb.WrapServer(grpcServer)
  72. w.server = &http.Server{}
  73. w.wrappedGrpc = wrappedGrpc
  74. w.server.Handler = w
  75. go func() {
  76. err := w.server.Serve(listener)
  77. if err != nil {
  78. newError("failed to serve HTTP").Base(err).WriteToLog()
  79. }
  80. }()
  81. if err := w.ohm.RemoveHandler(context.Background(), w.config.Tag); err != nil {
  82. newError("failed to remove existing handler").WriteToLog()
  83. }
  84. if err := w.ohm.AddHandler(context.Background(), commander.NewOutbound(w.config.Tag, listener)); err != nil {
  85. newError("failed to add handler").Base(err).WriteToLog()
  86. }
  87. }
  88. func (w *WebCommander) Type() interface{} {
  89. return (*WebCommander)(nil)
  90. }
  91. func (w *WebCommander) Start() error {
  92. if err := core.RequireFeatures(w.ctx, func(cm commander.CommanderIfce, om outbound.Manager) {
  93. w.Lock()
  94. defer w.Unlock()
  95. w.cm = cm
  96. w.ohm = om
  97. go w.asyncStart()
  98. }); err != nil {
  99. return err
  100. }
  101. return nil
  102. }
  103. func (w *WebCommander) Close() error {
  104. w.Lock()
  105. defer w.Unlock()
  106. if w.server != nil {
  107. if err := w.server.Close(); err != nil {
  108. return newError("failed to close http server").Base(err)
  109. }
  110. w.server = nil
  111. }
  112. return nil
  113. }
  114. func init() {
  115. common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
  116. return newWebCommander(ctx, config.(*Config))
  117. }))
  118. }