前言
虽然现在文件上传下载工具多如牛毛,比如http、ftp、sftp、scp等方案都可以用于文件传输,但都是需要安装服务器甚至客户端。
有一种场景是我只需要临时上传或下载一个文件,完了就不用服务器运行了,如果使用那些文件传输工具,不光安装麻烦,开启关闭也恼火额。
因此才想搞小工具,不过Python爱好者可以用python -m http.server 8080 --bind 192.168.1.100
开启文件服务器,对我来说还是麻烦。
已经上传到【Github】,随意鉴赏。
源码鉴赏
模拟一个http服务器,通过curl和wget命令作为客户端实现文件的上传下载功能。
只是实现一个小工具,所以没必要使用http库了,我也试过用http库来完成相同的功能,发现很多东西都用不上。
上传和下载文件加入了进度显示,方便知道上传和下载进度。本来想实现断点续传功能,但比较懒,不想弄,原理很简单。
想想还是把认证授权加上去,不然太不安全了,命令行更新了使用用户名和密码方式的上传和下载。
package main import ( "bufio" "encoding/base64" "errors" "fmt" "io" "net" "net/url" "os" "path/filepath" "strconv" "strings" "unsafe" ) func main() { if len(os.Args) != 3 { fmt.Printf(`usage: %s ip:port user:pass get file: wget --auth-no-challenge --user=user --password=pass --content-disposition "http://ip:port" curl -u user:pass -OJ "http://ip:port" post file: wget -qO - --auth-no-challenge --user=user --password=pass --post-file=C:\tmp.txt "http://ip:port" curl -u user:pass --data-binary @C:\tmp.txt "http://ip:port" `, os.Args[0]) return } addr, err := net.ResolveTCPAddr("tcp", os.Args[1]) if err != nil { panic(err) } ser, err := net.ListenTCP("tcp", addr) if err != nil { panic(err) } fmt.Printf("Listen: [%s]\n", addr) authStr = "Basic " + base64.StdEncoding.EncodeToString([]byte(os.Args[2])) for { ln, err := ser.AcceptTCP() if err != nil { panic(err) } go func(l *net.TCPConn) { err := handleFile(l) if err != nil { respData(l, err.Error()) } l.Close() }(ln) } } const ( maxMemory = 10 << 20 // 缓存10MB respMsg = "HTTP/1.1 200 OK\r\nContent-Type:text/plain;charset=utf-8\r\nContent-Disposition:attachment;filename=resp.txt\r\nContent-Length:%d\r\n\r\n%s" getHeader = "HTTP/1.1 200 OK\r\nContent-Type:application/octet-stream\r\nContent-Disposition:attachment;filename=%s\r\nContent-Length:%d\r\nContent-Transfer-Encoding:binary\r\n\r\n" ) var authStr string // 授权信息 func respData(w io.Writer, data string) { msg := data + "\r\n" fmt.Fprintf(w, respMsg, len(msg), msg) } func handleFile(l *net.TCPConn) error { br := bufio.NewReaderSize(l, maxMemory) method, path, length, err := getHeaderMsg(br) if err != nil { return err } fmt.Printf("[%s - %s - %d]\n", method, path, length) if method == "GET" { return httpGetFile(path, l, length) } err = httpPostFile(path, br, length) if err != nil { return err } respData(l, "post ok") return nil } // 内存复用,更快速,省内存 func bytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func getHeaderMsg(r *bufio.Reader) (string, string, int64, error) { // 读取第一行,提取有用信息 line, _, err := r.ReadLine() if err != nil { return "", "", 0, err } header := strings.Fields(bytesToString(line)) if len(header) < 3 { // 首行至少3列数据 return "", "", 0, errors.New("header error") } method, path := header[0], "" s := strings.Index(header[1], "") if s >= 0 { path, _ = url.QueryUnescape(header[1][s+1:]) } if path == "" { // "", "", 0, errors.New("path error") } var length int64 if method == "GET" { fi, err := os.Stat(path) if err != nil { return "", "", 0, err } length = fi.Size() // GET请求提前得到文件大小 } else if method != "POST" { return "", "", 0, errors.New(method + " not support") } var authCheck string for { line, _, err = r.ReadLine() if err != nil { return "", "", 0, err } if len(line) == 0 { break // 遇到空行,之后为请求体 } header = strings.Split(bytesToString(line), ":") if len(header) == 2 { // 头部[key: val]解析 header[0] = strings.ToLower(strings.TrimSpace(header[0])) header[1] = strings.TrimSpace(header[1]) if method == "POST" && header[0] == "content-length" { length, _ = strconv.ParseInt(header[1], 10, 64) } else if header[0] == "authorization" { authCheck = header[1] } } } if authCheck != authStr { return "", "", 0, errors.New("authorization error") } return method, path, length, nil } func httpPostFile(path string, r io.Reader, length int64) error { fw, err := os.Create(path) if err != nil { return err } defer fw.Close() pr := newProgress(r, length) _, err = io.CopyN(fw, pr, length) pr.Close() return err } func httpGetFile(path string, w io.Writer, size int64) error { fr, err := os.Open(path) if err != nil { return err } defer fr.Close() fmt.Fprintf(w, getHeader, filepath.Base(path), size) pr := newProgress(fr, size) _, err = io.Copy(w, pr) pr.Close() return err } type progress struct { r io.Reader cnt int64 rate chan int64 } func newProgress(r io.Reader, size int64) io.ReadCloser { p := &progress{r: r, rate: make(chan int64)} // 之所以这样做进度,是因为打印耗性能,因此在协程中打印进度 // 在处理数据中用非阻塞方式往chan中传处理字节数 go func(rate <-chan int64, all int64) { for cur := range rate { fmt.Printf("\rhandle:%4d%%", cur*100/all) } fmt.Printf("\rhandle: 100%%\r\n\r\n") }(p.rate, size) return p } func (p *progress) Read(b []byte) (int, error) { n, err := p.r.Read(b) p.cnt += int64(n) select { // 非阻塞方式往chan中写数据 case p.rate <- p.cnt: default: } return n, err } func (p *progress) Close() error { close(p.rate) // 关闭chan,通知打印协程退出 return nil }
食用方法
执行UpDownFile-h可以查看帮助文档,里面有wget和curl上传和下载文件的命令,方便忘记命令的时候copy一下下。
工具虽小,但确实解决了我个人的临时上传下载文件需求,再也不用到处安装各种服务器咯,爽爽哒。
usage: UpDownFile ip:port get file: wget --auth-no-challenge --user=user --password=pass --content-disposition "http://ip:port" curl -u user:pass -OJ "http://ip:port" post file: wget -qO - --auth-no-challenge --user=user --password=pass --post-file=C:\tmp.txt "http://ip:port" curl -u user:pass --data-binary @C:\tmp.txt "http://ip:port"
以上就是golang实现的文件上传下载小工具的详细内容,更多关于golang实现文件上传下载的资料请关注其它相关文章!
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]