golang实现的文件上传下载小工具

前言

虽然现在文件上传下载工具多如牛毛,比如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?/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 // 授权信息

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 == "" { // ?号后面就是文件路径,需要解码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请求提前得到文件大小
  } 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?/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实现的文件上传下载小工具的详细内容,更多关于golang实现文件上传下载的资料请关注我们其它相关文章!

(0)

相关推荐

  • golang并发下载多个文件的方法

    背景说明 假设有一个分布式文件系统,现需要从该系统中并发下载一部分文件到本地机器. 已知该文件系统的部分节点ip, 以及需要下载的文件fileID列表,并能通过这些信息来拼接下载地址. 其中节点ip列表保存在xx_node.txt, 要下载的fileID保存在xx_fileID.txt中. 代码示例 package main import ( "bufio" "flag" "fmt" "io" "math/rand&

  • Golang+Android基于HttpURLConnection实现的文件上传功能示例

    本文实例讲述了Golang+Android基于HttpURLConnection实现的文件上传功能.分享给大家供大家参考,具体如下: 这里要演示的是使用Android程序作为客户端(使用HttpURLConnection访问网络),Golang程序作为服务器端,实现文件上传. 客户端代码: public static String uploadFile(String uploadUrl, String filePath) { Log.v(TAG, "url:" + uploadUrl)

  • golang实现http server提供文件下载功能

    简介 Go(又称Golang)是Google开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言. 罗伯特·格瑞史莫(Robert Griesemer),罗勃·派克(Rob Pike)及肯·汤普逊(Ken Thompson)于2007年9月开始设计Go,稍后Ian Lance Taylor.Russ Cox加入项目.Go是基于Inferno操作系统所开发的.Go于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Mac OS X平台上进行了实现,后来追加了Windo

  • golang简单获取上传文件大小的实现代码

    本文实例讲述了golang简单获取上传文件大小的方法.分享给大家供大家参考,具体如下: 复制代码 代码如下: package main import (     "fmt"     "io"     "net/http"     "log"     "os" ) // 获取文件大小的接口 type Size interface {     Size() int64 } // 获取文件信息的接口 type S

  • 下载golang.org/x包的操作方法

    golang.org/x包放到了https://github.com/golang/text中,下载时需要先在本地建立golang.org/x的目录后,再下载. mkdir -p golang.org/x git clone https://github.com/golang/text.git 或 go get github.com/golang/text后将包移到x目录 以上这篇下载golang.org/x包的操作方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们

  • Golang 使用http Client下载文件的实现方法

    之前使用beego的http库的时候,有的情况需要下载文件.beego是能实现,但就是有点问题:不支持回调,没法显示下载速度,这在日常开发中是不可忍受的. 看了下beego的实现主要是使用了io.copy函数,于是就深入的看了下实现原理,发现挺简单的,于是就根据io.copy原理实现的一个简单的下载器 //定义要下载的文件 var durl = "https://dl.google.com/go/go1.10.3.darwin-amd64.pkg"; //解析url uri, err

  • 解决 Golang VS Code 插件下载安装失败的问题

    最近开始学Go语言,但是在使用VS Code 编写Go的时候出现了插件无法下载的问题.最初我的解决办法也是从github下载再安装,但是我并不喜欢这种做法,因为我要在多台pc上使用VS Code编写Go,所以我觉要重复多次很麻烦,而且插件的安装也非常麻烦,我曾经一度想放弃学习Go语言,没错因为安装插件很麻烦.在经过我多次的查找最终找到了http://goproxy.cn/,这是Go的国内镜像,使用了这个就可以结局Go插件无法下载的问题. (http://goproxy.cn/ 文档简介,方便以后

  • golang语言实现的文件上传与文件下载功能示例

    本文实例讲述了golang实现的文件上传与文件下载功能.分享给大家供大家参考,具体如下: upload.go 复制代码 代码如下: package common import (  "io/ioutil"  "os"  "path"  "github.com/gin-gonic/gin"  "googo.co/goo"  "googo.co/utils" ) const (  UPLOA

  • Golang实现异步上传文件支持进度条查询的方法

    业务背景 业务需求要求开发一个异步上传文件的接口,并支持上传进度的查询. 需求分析 ZIP压缩包中,包含一个csv文件和一个图片文件夹,要求:解析csv数据存入mongo,将图片文件夹中的图片信息对应上csv中的人员信息. ZIP压缩包解压 使用golang自带的 "archive/zip" 包解压. func decompressZip(filePath, dest string) (string, string, error) { var csvName string imageF

  • 如何使用大学教育邮箱下载golang等软件(推荐)

    Generally speaking,if you want to download goland for developing block chain要么需要付费要么需要激活码to activate,However,今年的激活码好像还没有被人搞出来,而免费使用的话只能使用30天,因此可以利用学校的教育邮箱下载goland来运行go语言. 1注册一个大学教育账户 登录自己大学的信息服务门户,在右下角处注册大学教育账户. 2注册完大学教育邮箱后,进入goland官网https://www.jetb

随机推荐