Go实战--golang实现静态文件服务器(文件查看,文件上传,文件下载)
来源:互联网 发布:教育就业前景知乎 编辑:程序博客网 时间:2024/04/28 13:37
生命不止,继续 go go go !!!
之前写过博客介绍net/http包:
《Go语言学习之net/http包(The way to go)》
package net/http
再次温习几个方法:
http.FileServer
FileServer returns a handler that serves HTTP requests with the contents of the file system rooted at root.
func StripPrefix(prefix string, h Handler)
Handler将请求url.path中移出指定的前缀,然后将省下的请求交给handler h来处理,对于那些不是以指定前缀开始的路径请求,该函数返回一个http 404 not found 的错误.
type Dir
A Dir implements FileSystem using the native file system restricted to a specific directory tree.
type Handler
A Handler responds to an HTTP request.
func (*Request) ParseMultipartForm
func (r *Request) ParseMultipartForm(maxMemory int64) error
ParseMultipartForm parses a request body as multipart/form-data. The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files. ParseMultipartForm calls ParseForm if necessary. After one call to ParseMultipartForm, subsequent calls have no effect.
ParseMultipartForm将请求的主体作为multipart/form-data解析。请求的整个主体都会被解析,得到的文件记录最多 maxMemery字节保存在内存,其余部分保存在硬盘的temp文件里。如果必要,ParseMultipartForm会自行调用 ParseForm。重复调用本方法是无意义的。
type Server
type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler Handler // handler to invoke, http.DefaultServeMux if nil TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS // ReadTimeout is the maximum duration for reading the entire // request, including the body. // // Because ReadTimeout does not let Handlers make per-request // decisions on each request body's acceptable deadline or // upload rate, most users will prefer to use // ReadHeaderTimeout. It is valid to use them both. ReadTimeout time.Duration // ReadHeaderTimeout is the amount of time allowed to read // request headers. The connection's read deadline is reset // after reading the headers and the Handler can decide what // is considered too slow for the body. ReadHeaderTimeout time.Duration // WriteTimeout is the maximum duration before timing out // writes of the response. It is reset whenever a new // request's header is read. Like ReadTimeout, it does not // let Handlers make decisions on a per-request basis. WriteTimeout time.Duration // IdleTimeout is the maximum amount of time to wait for the // next request when keep-alives are enabled. If IdleTimeout // is zero, the value of ReadTimeout is used. If both are // zero, there is no timeout. IdleTimeout time.Duration // MaxHeaderBytes controls the maximum number of bytes the // server will read parsing the request header's keys and // values, including the request line. It does not limit the // size of the request body. // If zero, DefaultMaxHeaderBytes is used. MaxHeaderBytes int // TLSNextProto optionally specifies a function to take over // ownership of the provided TLS connection when an NPN/ALPN // protocol upgrade has occurred. The map key is the protocol // name negotiated. The Handler argument should be used to // handle HTTP requests and will initialize the Request's TLS // and RemoteAddr if not already set. The connection is // automatically closed when the function returns. // If TLSNextProto is not nil, HTTP/2 support is not enabled // automatically. TLSNextProto map[string]func(*Server, *tls.Conn, Handler) // ConnState specifies an optional callback function that is // called when a client connection changes state. See the // ConnState type and associated constants for details. ConnState func(net.Conn, ConnState) // ErrorLog specifies an optional logger for errors accepting // connections and unexpected behavior from handlers. // If nil, logging goes to os.Stderr via the log package's // standard logger. ErrorLog *log.Logger // contains filtered or unexported fields}
简单的文件服务器
功能:
1 上传文件
2 显示文件列表
用到的包:
1 package io
io.Copy方法
func Copy(dst Writer, src Reader) (written int64, err error)
Copy 将 src 复制到 dst,直到在 src 上到达 EOF 或发生错误。它返回复制的字节数,如果有的话,还会返回在复制时遇到的第一个错误。
成功的 Copy 返回 err == nil,而非 err == EOF。由于 Copy 被定义为从 src 读取直到 EOF 为止,因此它不会将来自 Read 的 EOF 当做错误来报告。
2 package os
可以参考博客:Go语言学习之os包中文件相关的操作(The way to go)
os.Create方法:
func Create(name string) (*File, error)
Create creates the named file with mode 0666 (before umask), truncating it if it already exists.
完整代码:
package mainimport ( "io" "net/http" "os")func uploadHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { //POST takes the uploaded file(s) and saves it to disk. case "POST": //parse the multipart form in the request err := r.ParseMultipartForm(100000) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } //get a ref to the parsed multipart form m := r.MultipartForm //get the *fileheaders files := m.File["uploadfile"] for i, _ := range files { //for each fileheader, get a handle to the actual file file, err := files[i].Open() defer file.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } //create destination file making sure the path is writeable. dst, err := os.Create("./upload/" + files[i].Filename) defer dst.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } //copy the uploaded file to the destination file if _, err := io.Copy(dst, file); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } default: w.WriteHeader(http.StatusMethodNotAllowed) }}func main() { http.HandleFunc("/upload", uploadHandler) //static file handler. http.Handle("/staticfile/", http.StripPrefix("/staticfile/", http.FileServer(http.Dir("./staticfile")))) //Listen on port 8080 http.ListenAndServe(":8080", nil)}
通过curl上传文件:
curl -F "uploadfile=@C:\Users\wangs\Desktop\xueba_license.txt" localhost:8080/upload
通过postman上传文件:
记得设置header: Content-Type multipart/form-data****
查看静态文件:
浏览器访问localhost:8080/staticfile
可以浏览器上传的简单文件服务器
引用自:http://github.com/widuu/staticserver
功能:
1 上传文件
2 显示文件列表
用到的包:
1 html/template
参考博客:Go语言学习之html/template包(The way to go)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
2 path/filepath
参考博客:Go语言学习之path/filepath包(the way to go)
Ext(path string) string
获取路径字符串中的文件扩展名
func Abs(path string) (string, error)
获取 path 的绝对路径
3 strconv
参考博客:Go语言学习之strconv包(The way to go)
func FormatInt(i int64, base int) string
将整数转换为字符串
4 time
参考博客:Go语言学习之time包(获取当前时间戳等)(the way to go)
完整代码:
view文件夹下:
file.html:
<html><head> <title>{{.}}</title></head><body><form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post"> <input type="file" name="uploadfile" /> <input type="submit" value="upload" /></form></body></html>
index.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><link href="./css/css.css" rel="stylesheet" type="text/css" /><head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>{{.Title}}</title></head><body> <div> <a href="/upload">上传文件</a></p> <a href="/file">查看文件</a></p> </div></body></html>
main.go:
package mainimport ( "fmt" "html/template" "io" "net/http" "os" "path/filepath" "strconv" "time")var mux map[string]func(http.ResponseWriter, *http.Request)type Myhandler struct{}type home struct { Title string}const ( Template_Dir = "./view/" Upload_Dir = "./upload/")func main() { server := http.Server{ Addr: ":9090", Handler: &Myhandler{}, ReadTimeout: 10 * time.Second, } mux = make(map[string]func(http.ResponseWriter, *http.Request)) mux["/"] = index mux["/upload"] = upload mux["/file"] = StaticServer server.ListenAndServe()}func (*Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h, ok := mux[r.URL.String()]; ok { h(w, r) return } http.StripPrefix("/", http.FileServer(http.Dir("./upload/"))).ServeHTTP(w, r)}func upload(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { t, _ := template.ParseFiles(Template_Dir + "file.html") t.Execute(w, "上传文件") } else { r.ParseMultipartForm(32 << 20) file, handler, err := r.FormFile("uploadfile") if err != nil { fmt.Fprintf(w, "%v", "上传错误") return } fileext := filepath.Ext(handler.Filename) if check(fileext) == false { fmt.Fprintf(w, "%v", "不允许的上传类型") return } filename := strconv.FormatInt(time.Now().Unix(), 10) + fileext f, _ := os.OpenFile(Upload_Dir+filename, os.O_CREATE|os.O_WRONLY, 0660) _, err = io.Copy(f, file) if err != nil { fmt.Fprintf(w, "%v", "上传失败") return } filedir, _ := filepath.Abs(Upload_Dir + filename) fmt.Fprintf(w, "%v", filename+"上传完成,服务器地址:"+filedir) }}func index(w http.ResponseWriter, r *http.Request) { title := home{Title: "首页"} t, _ := template.ParseFiles(Template_Dir + "index.html") t.Execute(w, title)}func StaticServer(w http.ResponseWriter, r *http.Request) { http.StripPrefix("/file", http.FileServer(http.Dir("./upload/"))).ServeHTTP(w, r)}func check(name string) bool { ext := []string{".exe", ".js", ".png"} for _, v := range ext { if v == name { return false } } return true}
浏览器访问:
http://localhost:9090/
实现一个共享文件夹服务
出自:Alexis ROBERT alexis.robert@gmail.com的代码.
完整代码:
/* Tiny web server in Golang for sharing a folderCopyright (c) 2010-2014 Alexis ROBERT <alexis.robert@gmail.com>Contains some code from Golang's http.ServeFile method, anduses lighttpd's directory listing HTML template. */package mainimport "net/http"import "net/url"import "io"import "os"import "mime"import "path"import "fmt"import "flag"import "strings"import "strconv"import "text/template"import "container/list"import "compress/gzip"import "compress/zlib"import "time"var root_folder *string // TODO: Find a way to be cleaner !var uses_gzip *boolconst serverUA = "Alexis/0.2"const fs_maxbufsize = 4096 // 4096 bits = default page size on OSX/* Go is the first programming language with a templating engine embeddeed * but with no min function. */func min(x int64, y int64) int64 { if x < y { return x } return y}func main() { // Get current working directory to get the file from it cwd, err := os.Getwd() if err != nil { fmt.Printf("Error while getting current directory.") return } // Command line parsing bind := flag.String("bind", ":1718", "Bind address") root_folder = flag.String("root", cwd, "Root folder") uses_gzip = flag.Bool("gzip", true, "Enables gzip/zlib compression") flag.Parse() http.Handle("/", http.HandlerFunc(handleFile)) fmt.Printf("Sharing %s on %s ...\n", *root_folder, *bind) http.ListenAndServe((*bind), nil)}// Manages directory listingstype dirlisting struct { Name string Children_dir []string Children_files []string ServerUA string}func copyToArray(src *list.List) []string { dst := make([]string, src.Len()) i := 0 for e := src.Front(); e != nil; e = e.Next() { dst[i] = e.Value.(string) i = i + 1 } return dst}func handleDirectory(f *os.File, w http.ResponseWriter, req *http.Request) { names, _ := f.Readdir(-1) // First, check if there is any index in this folder. for _, val := range names { if val.Name() == "index.html" { serveFile(path.Join(f.Name(), "index.html"), w, req) return } } // Otherwise, generate folder content. children_dir_tmp := list.New() children_files_tmp := list.New() for _, val := range names { if val.Name()[0] == '.' { continue } // Remove hidden files from listing if val.IsDir() { children_dir_tmp.PushBack(val.Name()) } else { children_files_tmp.PushBack(val.Name()) } } // And transfer the content to the final array structure children_dir := copyToArray(children_dir_tmp) children_files := copyToArray(children_files_tmp) tpl, err := template.New("tpl").Parse(dirlisting_tpl) if err != nil { http.Error(w, "500 Internal Error : Error while generating directory listing.", 500) fmt.Println(err) return } data := dirlisting{Name: req.URL.Path, ServerUA: serverUA, Children_dir: children_dir, Children_files: children_files} err = tpl.Execute(w, data) if err != nil { fmt.Println(err) }}func serveFile(filepath string, w http.ResponseWriter, req *http.Request) { // Opening the file handle f, err := os.Open(filepath) if err != nil { http.Error(w, "404 Not Found : Error while opening the file.", 404) return } defer f.Close() // Checking if the opened handle is really a file statinfo, err := f.Stat() if err != nil { http.Error(w, "500 Internal Error : stat() failure.", 500) return } if statinfo.IsDir() { // If it's a directory, open it ! handleDirectory(f, w, req) return } if (statinfo.Mode() &^ 07777) == os.ModeSocket { // If it's a socket, forbid it ! http.Error(w, "403 Forbidden : you can't access this resource.", 403) return } // Manages If-Modified-Since and add Last-Modified (taken from Golang code) if t, err := time.Parse(http.TimeFormat, req.Header.Get("If-Modified-Since")); err == nil && statinfo.ModTime().Unix() <= t.Unix() { w.WriteHeader(http.StatusNotModified) return } w.Header().Set("Last-Modified", statinfo.ModTime().Format(http.TimeFormat)) // Content-Type handling query, err := url.ParseQuery(req.URL.RawQuery) if err == nil && len(query["dl"]) > 0 { // The user explicitedly wanted to download the file (Dropbox style!) w.Header().Set("Content-Type", "application/octet-stream") } else { // Fetching file's mimetype and giving it to the browser if mimetype := mime.TypeByExtension(path.Ext(filepath)); mimetype != "" { w.Header().Set("Content-Type", mimetype) } else { w.Header().Set("Content-Type", "application/octet-stream") } } // Manage Content-Range (TODO: Manage end byte and multiple Content-Range) if req.Header.Get("Range") != "" { start_byte := parseRange(req.Header.Get("Range")) if start_byte < statinfo.Size() { f.Seek(start_byte, 0) } else { start_byte = 0 } w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start_byte, statinfo.Size()-1, statinfo.Size())) } // Manage gzip/zlib compression output_writer := w.(io.Writer) is_compressed_reply := false if (*uses_gzip) == true && req.Header.Get("Accept-Encoding") != "" { encodings := parseCSV(req.Header.Get("Accept-Encoding")) for _, val := range encodings { if val == "gzip" { w.Header().Set("Content-Encoding", "gzip") output_writer = gzip.NewWriter(w) is_compressed_reply = true break } else if val == "deflate" { w.Header().Set("Content-Encoding", "deflate") output_writer = zlib.NewWriter(w) is_compressed_reply = true break } } } if !is_compressed_reply { // Add Content-Length w.Header().Set("Content-Length", strconv.FormatInt(statinfo.Size(), 10)) } // Stream data out ! buf := make([]byte, min(fs_maxbufsize, statinfo.Size())) n := 0 for err == nil { n, err = f.Read(buf) output_writer.Write(buf[0:n]) } // Closes current compressors switch output_writer.(type) { case *gzip.Writer: output_writer.(*gzip.Writer).Close() case *zlib.Writer: output_writer.(*zlib.Writer).Close() } f.Close()}func handleFile(w http.ResponseWriter, req *http.Request) { w.Header().Set("Server", serverUA) filepath := path.Join((*root_folder), path.Clean(req.URL.Path)) serveFile(filepath, w, req) fmt.Printf("\"%s %s %s\" \"%s\" \"%s\"\n", req.Method, req.URL.String(), req.Proto, req.Referer(), req.UserAgent()) // TODO: Improve this crappy logging}func parseCSV(data string) []string { splitted := strings.SplitN(data, ",", -1) data_tmp := make([]string, len(splitted)) for i, val := range splitted { data_tmp[i] = strings.TrimSpace(val) } return data_tmp}func parseRange(data string) int64 { stop := (int64)(0) part := 0 for i := 0; i < len(data) && part < 2; i = i + 1 { if part == 0 { // part = 0 <=> equal isn't met. if data[i] == '=' { part = 1 } continue } if part == 1 { // part = 1 <=> we've met the equal, parse beginning if data[i] == ',' || data[i] == '-' { part = 2 // part = 2 <=> OK DUDE. } else { if 48 <= data[i] && data[i] <= 57 { // If it's a digit ... // ... convert the char to integer and add it! stop = (stop * 10) + (((int64)(data[i])) - 48) } else { part = 2 // Parsing error! No error needed : 0 = from start. } } } } return stop}const dirlisting_tpl = `<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><!-- Modified from lighttpd directory listing --><head><title>Index of {{.Name}}</title><style type="text/css">a, a:active {text-decoration: none; color: blue;}a:visited {color: #48468F;}a:hover, a:focus {text-decoration: underline; color: red;}body {background-color: #F5F5F5;}h2 {margin-bottom: 12px;}table {margin-left: 12px;}th, td { font: 90% monospace; text-align: left;}th { font-weight: bold; padding-right: 14px; padding-bottom: 3px;}td {padding-right: 14px;}td.s, th.s {text-align: right;}div.list { background-color: white; border-top: 1px solid #646464; border-bottom: 1px solid #646464; padding-top: 10px; padding-bottom: 14px;}div.foot { font: 90% monospace; color: #787878; padding-top: 4px;}</style></head><body><h2>Index of {{.Name}}</h2><div class="list"><table summary="Directory Listing" cellpadding="0" cellspacing="0"><thead><tr><th class="n">Name</th><th class="t">Type</th><th class="dl">Options</th></tr></thead><tbody><tr><td class="n"><a href="../">Parent Directory</a>/</td><td class="t">Directory</td><td class="dl"></td></tr>{{range .Children_dir}}<tr><td class="n"><a href="{{.}}/">{{.}}/</a></td><td class="t">Directory</td><td class="dl"></td></tr>{{end}}{{range .Children_files}}<tr><td class="n"><a href="{{.}}">{{.}}</a></td><td class="t"> </td><td class="dl"><a href="{{.}}?dl">Download</a></td></tr>{{end}}</tbody></table></div><div class="foot">{{.ServerUA}}</div></body></html>`
- Go实战--golang实现静态文件服务器(文件查看,文件上传,文件下载)
- URL下载文件服务器文件
- js访问文件服务器实现文件下载
- Go实战--golang上传文件到七牛云对象存储(github.com/qiniu/api.v7)
- GO语言实现文件上传
- golang 静态文件隐藏下载地址
- Go实战--golang中读写文件的几种方式
- Go实战--golang中读写文件的几种方式
- 从文件服务器下载图片文件
- golang 上传文件
- golang web 文件上传
- Golang 上传文件
- golang 模拟文件上传
- golang 文件上传
- golang 上传文件
- Golang + HTML5 实现多文件上传
- Golang实现http文件上传小功能
- Golang 实现简单的文件上传
- Android 隐藏虚拟按键或系统上端状态栏
- Java新人入职——配置环境及安装开发工具(完全)
- hive数据倾斜总结
- git安装与使用
- Arctic Network(Prim)
- Go实战--golang实现静态文件服务器(文件查看,文件上传,文件下载)
- git学习六:git提交忽略不必要的文件或文件夹
- Dao接口实现基本的数据库操作。增删改查
- 《西瓜书》笔记03:线性模型之LR、线性判别分析(2)
- 模板方法模式
- zcmu--1958: 机器翻译
- csdn如何转载别人的文章
- 企业网银支付B2B
- 阿里云免费服务器ECS秒杀js脚本