golang实现的文件上传下载小工具
前言
虽然现在文件上传下载工具多如牛毛,比如http、ftp、sftp、scp等方案都可以用于文件传输,但都是需要安装服务器甚至客户端。
有一种场景是我只需要临时上传或下载一个文件,完了就不用服务器运行了,如果使用那些文件传输工具,不光安装麻烦,开启关闭也恼火额。
因此才想搞小工具,不过Python爱好者可以用python-mhttp.server8080--bind192.168.1.100开启文件服务器,对我来说还是麻烦。
已经上传到【Github】,随意鉴赏。
源码鉴赏
模拟一个http服务器,通过curl和wget命令作为客户端实现文件的上传下载功能。
只是实现一个小工具,所以没必要使用http库了,我也试过用http库来完成相同的功能,发现很多东西都用不上。
上传和下载文件加入了进度显示,方便知道上传和下载进度。本来想实现断点续传功能,但比较懒,不想弄,原理很简单。
想想还是把认证授权加上去,不然太不安全了,命令行更新了使用用户名和密码方式的上传和下载。
packagemain
import(
"bufio"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"unsafe"
)
funcmain(){
iflen(os.Args)!=3{
fmt.Printf(`usage:%sip:portuser:pass
getfile:
wget--auth-no-challenge--user=user--password=pass--content-disposition"http://ip:port?/home/tmp.txt"
curl-uuser:pass-OJ"http://ip:port?/home/tmp.txt"
postfile:
wget-qO---auth-no-challenge--user=user--password=pass--post-file=C:\tmp.txt"http://ip:port?/home/tmp.txt"
curl-uuser:pass--data-binary@C:\tmp.txt"http://ip:port?/home/tmp.txt"
`,os.Args[0])
return
}
addr,err:=net.ResolveTCPAddr("tcp",os.Args[1])
iferr!=nil{
panic(err)
}
ser,err:=net.ListenTCP("tcp",addr)
iferr!=nil{
panic(err)
}
fmt.Printf("Listen:[%s]\n",addr)
authStr="Basic"+base64.StdEncoding.EncodeToString([]byte(os.Args[2]))
for{
ln,err:=ser.AcceptTCP()
iferr!=nil{
panic(err)
}
gofunc(l*net.TCPConn){
err:=handleFile(l)
iferr!=nil{
respData(l,err.Error())
}
l.Close()
}(ln)
}
}
const(
maxMemory=10<<20//缓存10MB
respMsg="HTTP/1.1200OK\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.1200OK\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"
)
varauthStrstring//授权信息
funcrespData(wio.Writer,datastring){
msg:=data+"\r\n"
fmt.Fprintf(w,respMsg,len(msg),msg)
}
funchandleFile(l*net.TCPConn)error{
br:=bufio.NewReaderSize(l,maxMemory)
method,path,length,err:=getHeaderMsg(br)
iferr!=nil{
returnerr
}
fmt.Printf("[%s-%s-%d]\n",method,path,length)
ifmethod=="GET"{
returnhttpGetFile(path,l,length)
}
err=httpPostFile(path,br,length)
iferr!=nil{
returnerr
}
respData(l,"postok")
returnnil
}
//内存复用,更快速,省内存
funcbytesToString(b[]byte)string{
return*(*string)(unsafe.Pointer(&b))
}
funcgetHeaderMsg(r*bufio.Reader)(string,string,int64,error){
//读取第一行,提取有用信息
line,_,err:=r.ReadLine()
iferr!=nil{
return"","",0,err
}
header:=strings.Fields(bytesToString(line))
iflen(header)<3{//首行至少3列数据
return"","",0,errors.New("headererror")
}
method,path:=header[0],""
s:=strings.Index(header[1],"?")
ifs>=0{
path,_=url.QueryUnescape(header[1][s+1:])
}
ifpath==""{//?号后面就是文件路径,需要解码url一下
return"","",0,errors.New("patherror")
}
varlengthint64
ifmethod=="GET"{
fi,err:=os.Stat(path)
iferr!=nil{
return"","",0,err
}
length=fi.Size()//GET请求提前得到文件大小
}elseifmethod!="POST"{
return"","",0,errors.New(method+"notsupport")
}
varauthCheckstring
for{
line,_,err=r.ReadLine()
iferr!=nil{
return"","",0,err
}
iflen(line)==0{
break//遇到空行,之后为请求体
}
header=strings.Split(bytesToString(line),":")
iflen(header)==2{//头部[key:val]解析
header[0]=strings.ToLower(strings.TrimSpace(header[0]))
header[1]=strings.TrimSpace(header[1])
ifmethod=="POST"&&header[0]=="content-length"{
length,_=strconv.ParseInt(header[1],10,64)
}elseifheader[0]=="authorization"{
authCheck=header[1]
}
}
}
ifauthCheck!=authStr{
return"","",0,errors.New("authorizationerror")
}
returnmethod,path,length,nil
}
funchttpPostFile(pathstring,rio.Reader,lengthint64)error{
fw,err:=os.Create(path)
iferr!=nil{
returnerr
}
deferfw.Close()
pr:=newProgress(r,length)
_,err=io.CopyN(fw,pr,length)
pr.Close()
returnerr
}
funchttpGetFile(pathstring,wio.Writer,sizeint64)error{
fr,err:=os.Open(path)
iferr!=nil{
returnerr
}
deferfr.Close()
fmt.Fprintf(w,getHeader,filepath.Base(path),size)
pr:=newProgress(fr,size)
_,err=io.Copy(w,pr)
pr.Close()
returnerr
}
typeprogressstruct{
rio.Reader
cntint64
ratechanint64
}
funcnewProgress(rio.Reader,sizeint64)io.ReadCloser{
p:=&progress{r:r,rate:make(chanint64)}
//之所以这样做进度,是因为打印耗性能,因此在协程中打印进度
//在处理数据中用非阻塞方式往chan中传处理字节数
gofunc(rate<-chanint64,allint64){
forcur:=rangerate{
fmt.Printf("\rhandle:%4d%%",cur*100/all)
}
fmt.Printf("\rhandle:100%%\r\n\r\n")
}(p.rate,size)
returnp
}
func(p*progress)Read(b[]byte)(int,error){
n,err:=p.r.Read(b)
p.cnt+=int64(n)
select{//非阻塞方式往chan中写数据
casep.rate<-p.cnt:
default:
}
returnn,err
}
func(p*progress)Close()error{
close(p.rate)//关闭chan,通知打印协程退出
returnnil
}
食用方法
执行UpDownFile-h可以查看帮助文档,里面有wget和curl上传和下载文件的命令,方便忘记命令的时候copy一下下。
工具虽小,但确实解决了我个人的临时上传下载文件需求,再也不用到处安装各种服务器咯,爽爽哒。
usage:UpDownFileip:port getfile: wget--auth-no-challenge--user=user--password=pass--content-disposition"http://ip:port?/home/tmp.txt" curl-uuser:pass-OJ"http://ip:port?/home/tmp.txt" postfile: wget-qO---auth-no-challenge--user=user--password=pass--post-file=C:\tmp.txt"http://ip:port?/home/tmp.txt" curl-uuser:pass--data-binary@C:\tmp.txt"http://ip:port?/home/tmp.txt"
以上就是golang实现的文件上传下载小工具的详细内容,更多关于golang实现文件上传下载的资料请关注毛票票其它相关文章!