github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/main/commands/all/api/shared.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "os" 11 "reflect" 12 "strings" 13 "time" 14 15 "google.golang.org/grpc/credentials/insecure" 16 "google.golang.org/protobuf/encoding/protojson" 17 18 "github.com/xmplusdev/xmcore/common/buf" 19 "github.com/xmplusdev/xmcore/main/commands/base" 20 "google.golang.org/grpc" 21 "google.golang.org/protobuf/proto" 22 ) 23 24 type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.Command, args []string) string 25 26 var ( 27 apiServerAddrPtr string 28 apiTimeout int 29 apiJSON bool 30 ) 31 32 func setSharedFlags(cmd *base.Command) { 33 cmd.Flag.StringVar(&apiServerAddrPtr, "s", "127.0.0.1:8080", "") 34 cmd.Flag.StringVar(&apiServerAddrPtr, "server", "127.0.0.1:8080", "") 35 cmd.Flag.IntVar(&apiTimeout, "t", 3, "") 36 cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "") 37 cmd.Flag.BoolVar(&apiJSON, "json", false, "") 38 } 39 40 func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) { 41 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(apiTimeout)*time.Second) 42 conn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) 43 if err != nil { 44 base.Fatalf("failed to dial %s", apiServerAddrPtr) 45 } 46 close = func() { 47 cancel() 48 conn.Close() 49 } 50 return 51 } 52 53 // loadArg loads one arg, maybe an remote url, or local file path 54 func loadArg(arg string) (out io.Reader, err error) { 55 var data []byte 56 switch { 57 case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"): 58 data, err = fetchHTTPContent(arg) 59 60 case arg == "stdin:": 61 data, err = io.ReadAll(os.Stdin) 62 63 default: 64 data, err = os.ReadFile(arg) 65 } 66 67 if err != nil { 68 return 69 } 70 out = bytes.NewBuffer(data) 71 return 72 } 73 74 // fetchHTTPContent dials https for remote content 75 func fetchHTTPContent(target string) ([]byte, error) { 76 parsedTarget, err := url.Parse(target) 77 if err != nil { 78 return nil, err 79 } 80 81 if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { 82 return nil, fmt.Errorf("invalid scheme: %s", parsedTarget.Scheme) 83 } 84 85 client := &http.Client{ 86 Timeout: 30 * time.Second, 87 } 88 resp, err := client.Do(&http.Request{ 89 Method: "GET", 90 URL: parsedTarget, 91 Close: true, 92 }) 93 if err != nil { 94 return nil, fmt.Errorf("failed to dial to %s", target) 95 } 96 defer resp.Body.Close() 97 98 if resp.StatusCode != 200 { 99 return nil, fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) 100 } 101 102 content, err := buf.ReadAllToBytes(resp.Body) 103 if err != nil { 104 return nil, fmt.Errorf("failed to read HTTP response") 105 } 106 107 return content, nil 108 } 109 110 func protoToJSONString(m proto.Message, prefix, indent string) (string, error) { 111 return strings.TrimSpace(protojson.MarshalOptions{Indent: indent}.Format(m)), nil 112 } 113 114 func showJSONResponse(m proto.Message) { 115 if isNil(m) { 116 return 117 } 118 output, err := protoToJSONString(m, "", " ") 119 if err != nil { 120 fmt.Fprintf(os.Stdout, "%v\n", m) 121 base.Fatalf("error encode json: %s", err) 122 } 123 fmt.Println(output) 124 } 125 126 func isNil(i interface{}) bool { 127 vi := reflect.ValueOf(i) 128 if vi.Kind() == reflect.Ptr { 129 return vi.IsNil() 130 } 131 return i == nil 132 }