自己动手写HTTP服务器(1)

自己动手写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请求了一次资源)

自己动手写HTTP服务器(1)_第1张图片


你可能感兴趣的:(自己动手写HTTP服务器(1))