| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- package webcommander
- import (
- "archive/zip"
- "bytes"
- "context"
- "io/fs"
- "net/http"
- "strings"
- "sync"
- "time"
- "github.com/improbable-eng/grpc-web/go/grpcweb"
- "google.golang.org/grpc"
- core "github.com/v2fly/v2ray-core/v5"
- "github.com/v2fly/v2ray-core/v5/app/commander"
- "github.com/v2fly/v2ray-core/v5/common"
- "github.com/v2fly/v2ray-core/v5/features/outbound"
- )
- //go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
- func newWebCommander(ctx context.Context, config *Config) (*WebCommander, error) {
- if config == nil {
- return nil, newError("config is nil")
- }
- if config.Tag == "" {
- return nil, newError("config.Tag is empty")
- }
- var webRootfs fs.FS
- if config.WebRoot != nil {
- zipReader, err := zip.NewReader(bytes.NewReader(config.WebRoot), int64(len(config.WebRoot)))
- if err != nil {
- return nil, newError("failed to create zip reader").Base(err)
- }
- webRootfs = zipReader
- }
- return &WebCommander{ctx: ctx, config: config, webRootfs: webRootfs}, nil
- }
- type WebCommander struct {
- sync.Mutex
- ctx context.Context
- ohm outbound.Manager
- cm commander.CommanderIfce
- server *http.Server
- wrappedGrpc *grpcweb.WrappedGrpcServer
- webRootfs fs.FS
- config *Config
- }
- func (w *WebCommander) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
- api_path := w.config.ApiMountpoint
- if strings.HasPrefix(request.URL.Path, api_path) {
- request.URL.Path = strings.TrimPrefix(request.URL.Path, api_path)
- if w.wrappedGrpc.IsGrpcWebRequest(request) {
- w.wrappedGrpc.ServeHTTP(writer, request)
- return
- }
- }
- if w.webRootfs != nil {
- http.ServeFileFS(writer, request, w.webRootfs, request.URL.Path)
- return
- }
- writer.WriteHeader(http.StatusNotFound)
- }
- func (w *WebCommander) asyncStart() {
- var grpcServer *grpc.Server
- for {
- grpcServer = w.cm.ExtractGrpcServer()
- if grpcServer != nil {
- break
- }
- time.Sleep(time.Second)
- }
- listener := commander.NewOutboundListener()
- wrappedGrpc := grpcweb.WrapServer(grpcServer)
- w.server = &http.Server{}
- w.wrappedGrpc = wrappedGrpc
- w.server.Handler = w
- go func() {
- err := w.server.Serve(listener)
- if err != nil {
- newError("failed to serve HTTP").Base(err).WriteToLog()
- }
- }()
- if err := w.ohm.RemoveHandler(context.Background(), w.config.Tag); err != nil {
- newError("failed to remove existing handler").WriteToLog()
- }
- if err := w.ohm.AddHandler(context.Background(), commander.NewOutbound(w.config.Tag, listener)); err != nil {
- newError("failed to add handler").Base(err).WriteToLog()
- }
- }
- func (w *WebCommander) Type() interface{} {
- return (*WebCommander)(nil)
- }
- func (w *WebCommander) Start() error {
- if err := core.RequireFeatures(w.ctx, func(cm commander.CommanderIfce, om outbound.Manager) {
- w.Lock()
- defer w.Unlock()
- w.cm = cm
- w.ohm = om
- go w.asyncStart()
- }); err != nil {
- return err
- }
- return nil
- }
- func (w *WebCommander) Close() error {
- w.Lock()
- defer w.Unlock()
- if w.server != nil {
- if err := w.server.Close(); err != nil {
- return newError("failed to close http server").Base(err)
- }
- w.server = nil
- }
- return nil
- }
- func init() {
- common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
- return newWebCommander(ctx, config.(*Config))
- }))
- }
|