自己动手写http服务器 : golang的http服务器file server,代码如下:
package main import ( "fmt" _ "log" "net/http" _ "net/http/pprof" "os" _ "os/user" "path" "path/filepath" "strings" "time" ) var testDir = filepath.Join("D:/golang/project", "testdir") func FileServer(root http.FileSystem, hide []string) http.Handler { return &fileHandler{root: root, hide: hide} } type fileHandler struct { root http.FileSystem hide []string // list of files to treat as "Not Found" } //net/http的handler接口方法:ServeHTTP func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { upath := r.URL.Path if !strings.HasPrefix(upath, "/") { upath = "/" + upath r.URL.Path = upath } //fmt.Printf("url %v \n", r.URL.Path) fh.serveFile(w, r, path.Clean(upath)) } // serveFile writes the specified file to the HTTP response. // name is '/'-separated, not filepath.Separator. func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name string) (int, error) { f, err := fh.root.Open(name) fmt.Printf("path %v err %v \n", name, err) if err != nil { if os.IsNotExist(err) { return http.StatusNotFound, nil } else if os.IsPermission(err) { return http.StatusForbidden, err } // 如果server非常busy或者文件描述符已经占用太多 w.Header().Set("Retry-After", "5") // TODO: 5 seconds enough delay? Or too much? return http.StatusServiceUnavailable, err } defer f.Close() d, err := f.Stat() if err != nil { if os.IsNotExist(err) { return http.StatusNotFound, nil } else if os.IsPermission(err) { return http.StatusForbidden, err } // Return a different status code than above so as to distinguish these cases return http.StatusInternalServerError, err } // redirect to canonical path url := r.URL.Path //fmt.Printf("canonical path %v \n", url) if d.IsDir() { // Ensure / at end of directory url if url[len(url)-1] != '/' { redirect(w, r, path.Base(url)+"/") return http.StatusMovedPermanently, nil } } else { // Ensure no / at end of file url if url[len(url)-1] == '/' { redirect(w, r, "../"+path.Base(url)) return http.StatusMovedPermanently, nil } } // 使用路径下默认的文件,比如index.html if d.IsDir() { for _, indexPage := range IndexPages { index := strings.TrimSuffix(name, "/") + "/" + indexPage ff, err := fh.root.Open(index) if err == nil { defer ff.Close() dd, err := ff.Stat() if err == nil { name = index d = dd f = ff break } } } } // 前面检测条件都过了,如果仍然为目录 if d.IsDir() { return http.StatusNotFound, nil } // 隐藏的文件不能访问 for _, hiddenPath := range fh.hide { if strings.EqualFold(d.Name(), path.Base(hiddenPath)) { return http.StatusNotFound, nil } } //主要实现借助golang的net/http下面的fs.go http.ServeContent(w, r, d.Name(), d.ModTime(), f) return http.StatusOK, nil } // redirect is taken from http.localRedirect of the std lib. // 302重定向 // 简单来说就是 w.Header().Set("Location", urlStr) func redirect(w http.ResponseWriter, r *http.Request, newPath string) { if q := r.URL.RawQuery; q != "" { newPath += "?" + q } http.Redirect(w, r, newPath, http.StatusMovedPermanently) } // 默认文件名 var IndexPages = []string{ "index.html", "index.htm", "index.txt", "default.html", "default.htm", "default.txt", } func main() { server := http.Server{ Addr: ":8080", Handler: FileServer(http.Dir(testDir), []string{""}), ReadTimeout: 10 * time.Second, } server.ListenAndServe() }
一次wireshark的抓包截图:(可以看见客户端发送了两次HTTP的GET请求,/favicon.ico请求了一次资源)