golang實(shí)現(xiàn)的文件上傳下載小工具
前言
雖然現(xiàn)在文件上傳下載工具多如牛毛,比如http、ftp、sftp、scp等方案都可以用于文件傳輸,但都是需要安裝服務(wù)器甚至客戶端。
有一種場(chǎng)景是我只需要臨時(shí)上傳或下載一個(gè)文件,完了就不用服務(wù)器運(yùn)行了,如果使用那些文件傳輸工具,不光安裝麻煩,開(kāi)啟關(guān)閉也惱火額。
因此才想搞小工具,不過(guò)Python愛(ài)好者可以用python -m http.server 8080 --bind 192.168.1.100開(kāi)啟文件服務(wù)器,對(duì)我來(lái)說(shuō)還是麻煩。
已經(jīng)上傳到【Github】,隨意鑒賞。
源碼鑒賞
模擬一個(gè)http服務(wù)器,通過(guò)curl和wget命令作為客戶端實(shí)現(xiàn)文件的上傳下載功能。
只是實(shí)現(xiàn)一個(gè)小工具,所以沒(méi)必要使用http庫(kù)了,我也試過(guò)用http庫(kù)來(lái)完成相同的功能,發(fā)現(xiàn)很多東西都用不上。
上傳和下載文件加入了進(jìn)度顯示,方便知道上傳和下載進(jìn)度。本來(lái)想實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能,但比較懶,不想弄,原理很簡(jiǎn)單。
想想還是把認(rèn)證授權(quán)加上去,不然太不安全了,命令行更新了使用用戶名和密碼方式的上傳和下載。
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?/home/tmp.txt"
curl -u user:pass -OJ "http://ip:port?/home/tmp.txt"
post file:
wget -qO - --auth-no-challenge --user=user --password=pass --post-file=C:\tmp.txt "http://ip:port?/home/tmp.txt"
curl -u user:pass --data-binary @C:\tmp.txt "http://ip:port?/home/tmp.txt"
`, 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 // 授權(quán)信息
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
}
// 內(nèi)存復(fù)用,更快速,省內(nèi)存
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列數(shù)據(jù)
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 == "" { // ?號(hào)后面就是文件路徑,需要解碼url一下
return "", "", 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請(qǐng)求提前得到文件大小
} 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 // 遇到空行,之后為請(qǐng)求體
}
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)}
// 之所以這樣做進(jìn)度,是因?yàn)榇蛴『男阅?因此在協(xié)程中打印進(jìn)度
// 在處理數(shù)據(jù)中用非阻塞方式往chan中傳處理字節(jié)數(shù)
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中寫數(shù)據(jù)
case p.rate <- p.cnt:
default:
}
return n, err
}
func (p *progress) Close() error {
close(p.rate) // 關(guān)閉chan,通知打印協(xié)程退出
return nil
}
食用方法
執(zhí)行UpDownFile-h可以查看幫助文檔,里面有wget和curl上傳和下載文件的命令,方便忘記命令的時(shí)候copy一下下。
工具雖小,但確實(shí)解決了我個(gè)人的臨時(shí)上傳下載文件需求,再也不用到處安裝各種服務(wù)器咯,爽爽噠。
usage: UpDownFile ip:port get file: wget --auth-no-challenge --user=user --password=pass --content-disposition "http://ip:port?/home/tmp.txt" curl -u user:pass -OJ "http://ip:port?/home/tmp.txt" post file: wget -qO - --auth-no-challenge --user=user --password=pass --post-file=C:\tmp.txt "http://ip:port?/home/tmp.txt" curl -u user:pass --data-binary @C:\tmp.txt "http://ip:port?/home/tmp.txt"
以上就是golang實(shí)現(xiàn)的文件上傳下載小工具的詳細(xì)內(nèi)容,更多關(guān)于golang實(shí)現(xiàn)文件上傳下載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實(shí)現(xiàn)WebSocket服務(wù)的項(xiàng)目實(shí)踐
本文介紹如何使用Golang實(shí)現(xiàn)實(shí)時(shí)后端WebSocket服務(wù),首先使用Gin框架搭建http服務(wù),然后使用gorilla/websocket庫(kù)實(shí)現(xiàn)簡(jiǎn)單后端WebSocket服務(wù),具有一定的參考價(jià)值,感興趣的可以了解一下2023-05-05
深入探索Go語(yǔ)言中的高效數(shù)據(jù)結(jié)構(gòu)堆
堆,作為一種基本的數(shù)據(jù)結(jié)構(gòu),以其在優(yōu)先隊(duì)列和排序算法中提供高效解決方案的能力而聞名。在本文中,我們將深入探討堆的內(nèi)部工作原理,包括其特性、實(shí)現(xiàn)細(xì)節(jié)以及在現(xiàn)代編程中的應(yīng)用2008-06-06
Golang并發(fā)發(fā)送HTTP請(qǐng)求的各種方法
在 Golang 領(lǐng)域,并發(fā)發(fā)送 HTTP 請(qǐng)求是優(yōu)化 Web 應(yīng)用程序的一項(xiàng)重要技能,本文探討了實(shí)現(xiàn)此目的的各種方法,從基本的 goroutine 到涉及通道和sync.WaitGroup 的高級(jí)技術(shù),需要的朋友可以參考下2024-02-02
四種Golang實(shí)現(xiàn)middleware框架的方式小結(jié)
middleware是一般框架里面常用的形式,比如web框架、rpc框架等,本文為大家詳細(xì)介紹了四種實(shí)現(xiàn)middleawre的方式,感興趣的可以了解一下2024-03-03
Go語(yǔ)言協(xié)程通道使用的問(wèn)題小結(jié)
本文主要介紹了Go語(yǔ)言協(xié)程通道使用的問(wèn)題小結(jié),詳細(xì)的介紹了使用的一些重要問(wèn)題,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
Golang標(biāo)準(zhǔn)庫(kù)time包日常用法小結(jié)
本文主要介紹了Golang標(biāo)準(zhǔn)庫(kù)time包日常用法小結(jié),可以通過(guò)它們來(lái)獲取當(dāng)前時(shí)間、創(chuàng)建指定時(shí)間、解析時(shí)間字符串、控制時(shí)間間隔等操作,感興趣的可以了解一下2023-11-11
在Go語(yǔ)言項(xiàng)目中使用Zap日志庫(kù)的操作過(guò)程
本文將先介紹Go語(yǔ)言原生的日志庫(kù)的使用,然后詳細(xì)介紹非常流行的Uber開(kāi)源的zap日志庫(kù),同時(shí)會(huì)介紹如何搭配·Lumberjack·實(shí)現(xiàn)日志的切割和歸檔,對(duì)Go使用Zap日志庫(kù)相關(guān)知識(shí)感興趣的朋友一起看看吧2024-03-03

