likes
comments
collection
share

白嫖gradio连接分享

作者站长头像
站长
· 阅读数 45

背景

这些天出现了不少好玩的ai项目,俺也是折腾的好不快乐。项目玩着玩着,发现清一色前端全是gradio写的,像SoVITS、ChatTTS啥的。gradio开箱即用,虽说和美观

大相径庭南辕北辙,但对于ai项目来说,项目能跑就行,为了前端美观不值当,有万能的社区呢。但咱今天重点不在于此,gradio在启动时如果设置下参数

share=True,浏览器便会打开个形如https://XXX.gradio.live的网址,你第一眼可能可能会有点懵,啥啥啥,这是个啥!鬼使神差的你访问了一下,嗨,这

玩意还真能访问,话说,它是咋把127.0.0.1夺舍的?嘿,这就得说道内网穿透了。

内网穿透

啥叫内网穿透?

我们查下百度百科,内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。

是不是感觉说的不是人话?我们翻译一下,就是在外面能访问家里的电脑。当然,gradio做的还没那么绝,只是原来这个项目只能局域网访问,现在这个限制不存在了。

这么好用的东西只能创建gradio项目使用,太暴殄天物了吧!俺也这么想的,整吧。

解决方案

我在寻求解决方案的时候,偶然发现个这个么项目gradio-tunneling,俺一拍大腿,嚯,还真是俺想要的,思路一看立马清晰明了。

刚出门便到了终点,这文章还写啥啊,按按delete吧。

我说服自己那是个python项目,很多人没python环境。脑袋里就有个人小人鄙视道,你就不能打个包,又没用啥大的库,python环境整进去能有多大,多少人电

脑上几百个浏览器了也没见抱怨。文件洁癖是病得改。

我把小人给摁住了,拿go练练手吧,命令行应用用go合适的不得了。

定义命令行

在go中命令行常用有两种,一种flag,还有一种cobra

flag是标准库,cobra是第三方库,这个项目怎么说呢,cobra是杀鸡用牛刀,flag整呗

// 定义命令行参数
portPtr := flag.Int("port", 8085, "定义要转发的端口")
address := flag.String("address", "https://api.gradio.app/v2/tunnel-request", "分享服务器地址")
binPath := flag.String("binPath", binaryPath, "frpc程序路径,默认查找可执行文件同级的bin目录")

再设置下帮助,就是输入-h时显示一大串参数那功能

flag.Usage = utils.PrintUsage

怎么显示呢,就把参数名是啥,功能是啥列出来

func PrintUsage() {
	fmt.Fprintf(flag.CommandLine.Output(), "使用方法: %s [选项]\n", flag.CommandLine.Name())
	fmt.Fprintln(flag.CommandLine.Output(), "选项:")
	flag.PrintDefaults()
}

设置frpc文件路径

gradio穿透就是利用frpc来实现的,我们需要定位frpc位置,至于为啥要定位,难道只想在windows上跑?那go语言的交叉编译不是白瞎了,frpc不同平台主程序肯定不一样啊,linux的剑斩windows的官,这个想法太大胆了。

frpc存放在同级bin目录之中,我们设置一下规则名称。

func GuessFrpcBinaryName() string {
	//判断当前系统平台
	platform := runtime.GOOS
	//架构
	arch := runtime.GOARCH
	return fmt.Sprintf("frpc_%s_%s", platform, arch)
}

这样我们就获取到了当前系统应该找啥路径。

binaryPath = fmt.Sprintf("bin/%s", fileName)

我们就可以对该路径该判断判断,该干嘛干嘛,当然,手动指定也是可以的

if *binPath != "" {
    binaryPath = *binPath
    //查询是否存在
    if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
        log.Fatalf(fmt.Sprintf("frpc 二进制文件路径不存在: %s\n", binaryPath))
        return
    }
} else {
    // 查询当前目录是否存在bin目录
    if _, err := os.Stat("bin"); os.IsNotExist(err) {
        log.Fatalf("frpc文件不存在: %s\n", binaryPath)
        return
    }
    //查询是否存在
    if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
        log.Fatalf("frpc文件不存在: %s\n", binaryPath)
        return
    }
}
//设置binaryPath为绝对路径
binaryPath, _ = filepath.Abs(binaryPath)

调用frpc还需要生成一个token

import (
	"crypto/rand"
	"encoding/base64"
)

func GenerateSecureToken(length int) (string, error) {
	b := make([]byte, length)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

至此,万事俱备,该进入正题了

调用frpc

首先,定义一个结构体用于存放上述信息

type Tunnel struct {
	proc         *exec.Cmd
	FrpcPath     string
	RemoteHost   string
	RemotePort   int
	LocalHost    string
	LocalPort    int
	ShareToken   string
	stdoutReader io.ReadCloser
	stderrReader io.ReadCloser
}

构建命令

func (t *Tunnel) Start() (string, error) {
	cmdArgs := []string{
		"http",
		"-n",
		t.ShareToken,
		"-l",
		strconv.Itoa(t.LocalPort),
		"-i",
		t.LocalHost,
		"--uc",
		"--sd",
		"random",
		"--ue",
		"--server_addr",
		fmt.Sprintf("%s:%d", t.RemoteHost, t.RemotePort),
		"--disable_log_color",
	}
	t.proc = exec.Command(t.FrpcPath, cmdArgs...)
	log.Printf("cmd:%s\n", t.proc.String())

	...
	return url, nil
}

读取生产的公网地址

func (t *Tunnel) readURLFromTunnelStream(r io.Reader) (string, error) {
	log.Println("Reading from stream...")

	// Compile regex pattern once outside the loop for efficiency.
	re := regexp.MustCompile(`start proxy success: (.+)`)
	reader := bufio.NewReader(r)
	var url string
	var err error

	// Setup a single-use timer for timeout.
	timeout := time.After(30 * time.Second)

	// Read lines from the stream with timeout.
	for {
		select {
		case <-timeout:
			log.Println("Timeout occurred while reading from stream.")
			err = errors.New("read timeout")
			goto exit
		default:
			line, readErr := reader.ReadString('\n')
			if readErr != nil {
				if readErr != io.EOF { // Ignore EOF which can be a normal termination signal.
					err = readErr
				}
				goto exit
			}
			line = strings.TrimSpace(line) // Remove leading/trailing whitespaces.
			if line == "" {
				continue
			}
			log.Println("Read line:", line)
			if strings.Contains(line, "start proxy success") {
				matches := re.FindStringSubmatch(line)
				if len(matches) == 2 {
					url = matches[1]
					goto exit
				}
			} else if strings.Contains(line, "login to server failed") {
				err = errors.New("login to server failed")
				goto exit
			}
		}
	}

exit:
	log.Println("Read operation completed.")
	return url, err
}

打包

当然可以使用go build,简单快捷,但是,不试试更爽的gox

gox

安装

go get github.com/mitchellh/gox

我看很多教程安装完了就直接用了,我试了识别不了,这种情况下咋办

找到gox文件夹,找不到就git clone

C:\Users\{user}\go\pkg\mod\github.com\mitchellh

运行go build,将生成的gox.exe添加到环境变量

gox -output "build/gradio-tunnel_{{.OS}}_{{.Arch}}"

各个平台的包就吭哧吭呲都编译好了,还在程序名上做了区分

自动化打包

但咱还得把bin目录添加进去啊,还得压缩啊,整个全自动化吧

构建这东西还是python写方便,先来个压缩

import zipfile
import os

def zip(zip_path, files):
    """
    压缩文件至zip
    压缩文件至zip
    :param zip_path: [FILE]zip包路径-zip包路径
    :param files: [DIR]压缩目录-待压缩文件的目录 字符串和字符串数组均可
    """
    if isinstance(files, str):
        files = [files]

    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for file in files:
            if os.path.isdir(file):
                for dir_path, dir_names, file_names in os.walk(file):
                    for filename in file_names:
                        file_path = dir_path.replace(file, '')
                        zf.write(os.path.join(dir_path, filename), file_path + '\\'+Path(file).name+"\\" + filename)
            elif os.path.isfile(file):
                path, file_name = os.path.split(file)
                zf.write(file, file_name)
            else:
                raise Exception('请检查路径%s' % file)

打包完将bin目录和主程序一起制作zip压缩包

def main():
    project_home = "D:\xxx"
    build_home = r"D:\build\xxx"
    if Path(build_home).exists():
        log.info("build_home exists")
        # 清空
        for file in Path(build_home).iterdir():
            if file.is_file():
                file.unlink()
                log.info("delete %s success" % file.name)
    else:
        log.info("build_home not exists")
        Path(build_home).mkdir(parents=True,exist_ok=True)

    # 编译gox
    result = subprocess.run(["gox","-output","%s/gradio-tunnel_{{.OS}}_{{.Arch}}" % build_home],cwd=project_home)
    if result.returncode == 0:
        log.info("gox compile success")
    else:
        log.error("gox compile failed")


    # 遍历build_home下的文件
    for file in Path(build_home).iterdir():
        if file.is_file():
            # 压缩文件
            zip_path = file.with_suffix(".zip")
            zip(zip_path, [file,project_home+"\\bin"], "")
            log.info("zip %s success" % file.name)
            # 删除文件
            file.unlink()
            log.info("delete %s success" % file.name)

齐活儿,下面就是各个平台测试了,精力有效,就试了试windows和linux,嗯,效果不错不错,白嫖的代理就是爽。

代码

本文代码已开源,感兴趣可参考,github创建了realease,下载即用。

-github

-gitee

转载自:https://juejin.cn/post/7387038113767735346
评论
请登录