Golang. What to use? http.ServeFile(..) or http.FileServer(..)?

huangapple go评论106阅读模式
英文:

Golang. What to use? http.ServeFile(..) or http.FileServer(..)?

问题

我有点困惑。许多示例展示了http.ServeFile(..)http.FileServer(..)的用法,但它们似乎功能非常相似。而且我没有找到关于如何设置自定义的NotFound处理程序的信息。

// 这个方法可以去掉路径中的"/static/"片段
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))

// 这个方法也可以,但是"/static2/"片段仍然存在,需要手动去掉
http.HandleFunc("/static2/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, r.URL.Path[1:])
})

http.ListenAndServe(":8080", nil)

我尝试阅读源代码,它们都使用了serveFile(ResponseWriter, *Request, FileSystem, string, bool)底层函数。然而,http.FileServer返回了一个带有自己的ServeHTTP()方法的fileHandler,并在提供文件之前进行了一些准备工作(例如path.Clean())。

那么为什么需要这种分离?哪种方法更好?如何设置自定义的NotFound处理程序,例如当请求的文件不存在时?

英文:

I'm a little bit confused. Much of examples shows usage of both: http.ServeFile(..) and http.FileServer(..), but seems they have very close functionality. Also I have found no information about how to set custom NotFound handler.

// This works and strip "/static/" fragment from path
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))

// This works too, but "/static2/" fragment remains and need to be striped manually
http.HandleFunc("/static2/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, r.URL.Path[1:])
})

http.ListenAndServe(":8080", nil)

I've tried to read source code and both of them use serveFile(ResponseWriter, *Request, FileSystem, string, bool) underlying function. However http.FileServer return fileHandler with its own ServeHTTP() method and make some preparation work before serving file (eg path.Clean()).

So why need this separation? Which method better to use? And how can I set custom NotFound handler, for example when requested file not found?

答案1

得分: 97

主要区别在于http.FileServer几乎实现了HTTP前缀与文件系统的一对一映射。简单来说,它可以提供整个目录路径及其子目录的服务。

假设你有一个名为/home/bob/static的目录,并且你有以下设置:

fs := http.FileServer(http.Dir("/home/bob/static"))
http.Handle("/static/", http.StripPrefix("/static", fs))

你的服务器将处理/static/foo/bar这样的请求,并提供/home/bob/static/foo/bar路径下的内容(或者返回404)。

相比之下,ServeFile是一个较低级别的辅助函数,可以用于实现类似于FileServer的功能,或者实现自己的路径处理逻辑等。它简单地将指定的本地文件发送到HTTP连接中。它本身不会提供整个目录前缀的服务(除非你编写了一个类似于FileServer的处理程序进行查找)。

注意,直接提供文件系统的服务可能存在潜在的安全风险(可能存在越过根目录的方式),因此我建议,除非你真的知道自己在做什么,否则使用http.FileServerhttp.Dir,它们包含了检查,以确保用户无法越过文件系统的限制,而ServeFile则没有这些检查。

补充说明
你的第二个问题,如何自定义NotFound处理程序,不幸的是,没有一个简单的答案。正如你注意到的,这是从内部函数serveFile中调用的,所以没有一个非常容易的地方可以插入代码。可能有一些巧妙的方法,比如使用自定义的ResponseWriter拦截404响应代码,但是我将把这个问题留给你自己去尝试解决。

英文:

The main difference is that http.FileServer does effectively almost 1:1 mapping of an HTTP prefix with a filesystem. In plain english, it serves up an entire directory path. and all its children.

Say you had a directory called /home/bob/static and you had this setup:

fs := http.FileServer(http.Dir("/home/bob/static"))
http.Handle("/static/", http.StripPrefix("/static", fs))

Your server would take requests for e.g. /static/foo/bar and serve whatever is at /home/bob/static/foo/bar (or 404)

In contrast, the ServeFile is a lower level helper that can be used to implement something similar to FileServer, or implement your own path munging potentially, and any number of things. It simply takes the named local file and sends it over the HTTP connection. By itself, it won't serve a whole directory prefix (unless you wrote a handler that did some lookup similar to FileServer)

NOTE Serving up a filesystem naively is a potentially dangerous thing (there are potentially ways to break out of the rooted tree) hence I recommend that unless you really know what you're doing, use http.FileServer and http.Dir as they include checks to make sure people can't break out of the FS, which ServeFile doesn't.

Addendum
Your secondary question, how do you do a custom NotFound handler, unfortunately, is not easily answered. Because this is called from internal function serveFile as you noticed, there's no super easy place to break into that. There are potentially some sneaky things like intercepting the response with your own ResponseWriter which intercepts the 404 response code, but I'll leave that exercise to you.

答案2

得分: 7

这是一个处理程序,如果找不到文件,它会发送重定向到"/"。当为Angular应用程序添加回退时,这非常方便,如这里所建议的,该应用程序是从golang服务中提供的。

**注意:**此代码不适用于生产环境。仅供说明(最好是:-)

    package main

    import "net/http"
    
    type (
    	// FallbackResponseWriter包装了一个http.Requesthandler并抑制了404状态码。在这种情况下,将提供给定的本地文件。
    	FallbackResponseWriter struct {
    		WrappedResponseWriter http.ResponseWriter
    		FileNotFound          bool
    	}
    )
    
    // Header返回包装的响应写入器的头部
    func (frw *FallbackResponseWriter) Header() http.Header {
    	return frw.WrappedResponseWriter.Header()
    }
    
    // Write将字节发送到包装的响应写入器,如果找不到文件,则抑制进一步的写入(尽管隐藏了这个事实)
    func (frw *FallbackResponseWriter) Write(b []byte) (int, error) {
    	if frw.FileNotFound {
    		return len(b), nil
    	}
    	return frw.WrappedResponseWriter.Write(b)
    }
    
    // WriteHeader将statusCode发送到包装的响应写入器
    func (frw *FallbackResponseWriter) WriteHeader(statusCode int) {
    	Log.Printf("INFO: WriteHeader called with code %d\n", statusCode)
    	if statusCode == http.StatusNotFound {
    		Log.Printf("INFO: Setting FileNotFound flag\n")
    		frw.FileNotFound = true
    		return
    	}
    	frw.WrappedResponseWriter.WriteHeader(statusCode)
    }
    
    // AddFallbackHandler将处理程序函数包装在另一个处理程序函数中,用于身份验证
    func AddFallbackHandler(handler http.HandlerFunc, filename string) http.HandlerFunc {
    	Log.Printf("INFO: Creating fallback handler")
    	return func(w http.ResponseWriter, r *http.Request) {
    		Log.Printf("INFO: Wrapping response writer in fallback response writer")
    		frw := FallbackResponseWriter{
    			WrappedResponseWriter: w,
    			FileNotFound:          false,
    		}
    		handler(&frw, r)
    		if frw.FileNotFound {
    			Log.Printf("INFO: Serving fallback")
    			http.Redirect(w, r, "/", http.StatusSeeOther)
    		}
    	}
    }

可以按照以下示例添加(使用goji作为mux):

	mux.Handle(pat.Get("/*"),
		AddFallbackHandler(http.FileServer(http.Dir("./html")).ServeHTTP, "/"))

英文:

Here a handler which sends a redirect to "/" if file is not found. This comes in handy when adding a fallback for an Angular application, as suggested here, which is served from within a golang service.

Note: This code is not production ready. Only illustrative (at best Golang. What to use? http.ServeFile(..) or http.FileServer(..)?

    package main

    import "net/http"
    
    type (
    	// FallbackResponseWriter wraps an http.Requesthandler and surpresses
    	// a 404 status code. In such case a given local file will be served.
    	FallbackResponseWriter struct {
    		WrappedResponseWriter http.ResponseWriter
    		FileNotFound          bool
    	}
    )
    
    // Header returns the header of the wrapped response writer
    func (frw *FallbackResponseWriter) Header() http.Header {
    	return frw.WrappedResponseWriter.Header()
    }
    
    // Write sends bytes to wrapped response writer, in case of FileNotFound
    // It surpresses further writes (concealing the fact though)
    func (frw *FallbackResponseWriter) Write(b []byte) (int, error) {
    	if frw.FileNotFound {
    		return len(b), nil
    	}
    	return frw.WrappedResponseWriter.Write(b)
    }
    
    // WriteHeader sends statusCode to wrapped response writer
    func (frw *FallbackResponseWriter) WriteHeader(statusCode int) {
    	Log.Printf("INFO: WriteHeader called with code %d\n", statusCode)
    	if statusCode == http.StatusNotFound {
    		Log.Printf("INFO: Setting FileNotFound flag\n")
    		frw.FileNotFound = true
    		return
    	}
    	frw.WrappedResponseWriter.WriteHeader(statusCode)
    }
    
    // AddFallbackHandler wraps the handler func in another handler func covering authentication
    func AddFallbackHandler(handler http.HandlerFunc, filename string) http.HandlerFunc {
    	Log.Printf("INFO: Creating fallback handler")
    	return func(w http.ResponseWriter, r *http.Request) {
    		Log.Printf("INFO: Wrapping response writer in fallback response writer")
    		frw := FallbackResponseWriter{
    			WrappedResponseWriter: w,
    			FileNotFound:          false,
    		}
    		handler(&frw, r)
    		if frw.FileNotFound {
    			Log.Printf("INFO: Serving fallback")
    			http.Redirect(w, r, "/", http.StatusSeeOther)
    		}
    	}
    }

It can be added as in this example (using goji as mux):

	mux.Handle(pat.Get("/*"),
		AddFallbackHandler(http.FileServer(http.Dir("./html")).ServeHTTP, "/"))

huangapple
  • 本文由 发表于 2015年3月1日 20:38:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/28793619.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定