利用golang怎么实现一个文件上传功能
创始人
2024-12-27 00:20:59
0次

具体如下

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"

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...