cmd_tools_fetch.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "net"
  7. "net/http"
  8. "net/url"
  9. "os"
  10. C "github.com/sagernet/sing-box/constant"
  11. "github.com/sagernet/sing-box/log"
  12. "github.com/sagernet/sing/common/bufio"
  13. E "github.com/sagernet/sing/common/exceptions"
  14. M "github.com/sagernet/sing/common/metadata"
  15. "github.com/spf13/cobra"
  16. )
  17. var commandFetch = &cobra.Command{
  18. Use: "fetch",
  19. Short: "Fetch an URL",
  20. Args: cobra.MinimumNArgs(1),
  21. Run: func(cmd *cobra.Command, args []string) {
  22. err := fetch(args)
  23. if err != nil {
  24. log.Fatal(err)
  25. }
  26. },
  27. }
  28. func init() {
  29. commandTools.AddCommand(commandFetch)
  30. }
  31. var (
  32. httpClient *http.Client
  33. http3Client *http.Client
  34. )
  35. func fetch(args []string) error {
  36. instance, err := createPreStartedClient()
  37. if err != nil {
  38. return err
  39. }
  40. defer instance.Close()
  41. httpClient = &http.Client{
  42. Transport: &http.Transport{
  43. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  44. dialer, err := createDialer(instance, commandToolsFlagOutbound)
  45. if err != nil {
  46. return nil, err
  47. }
  48. return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
  49. },
  50. ForceAttemptHTTP2: true,
  51. },
  52. }
  53. defer httpClient.CloseIdleConnections()
  54. if C.WithQUIC {
  55. err = initializeHTTP3Client(instance)
  56. if err != nil {
  57. return err
  58. }
  59. defer http3Client.CloseIdleConnections()
  60. }
  61. for _, urlString := range args {
  62. var parsedURL *url.URL
  63. parsedURL, err = url.Parse(urlString)
  64. if err != nil {
  65. return err
  66. }
  67. switch parsedURL.Scheme {
  68. case "":
  69. parsedURL.Scheme = "http"
  70. fallthrough
  71. case "http", "https":
  72. err = fetchHTTP(httpClient, parsedURL)
  73. if err != nil {
  74. return err
  75. }
  76. case "http3":
  77. if !C.WithQUIC {
  78. return C.ErrQUICNotIncluded
  79. }
  80. parsedURL.Scheme = "https"
  81. err = fetchHTTP(http3Client, parsedURL)
  82. if err != nil {
  83. return err
  84. }
  85. default:
  86. return E.New("unsupported scheme: ", parsedURL.Scheme)
  87. }
  88. }
  89. return nil
  90. }
  91. func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
  92. request, err := http.NewRequest("GET", parsedURL.String(), nil)
  93. if err != nil {
  94. return err
  95. }
  96. request.Header.Add("User-Agent", "curl/7.88.0")
  97. response, err := httpClient.Do(request)
  98. if err != nil {
  99. return err
  100. }
  101. defer response.Body.Close()
  102. _, err = bufio.Copy(os.Stdout, response.Body)
  103. if errors.Is(err, io.EOF) {
  104. return nil
  105. }
  106. return err
  107. }