最近在学习ffos的应用开发,需要为每一个应用写一个manifest.webapp文件。这个文件是json格式的,但是不能以application/json类型返回,返回的Content-Type必须是application/x-web-app-manifest+json。
刚开始用了一个笨方法来实现的。通过查看Static模块的代码,看到public下的文件,是通过RenderFile来实现的。就增加了一个filter,如果请求的文件是以.webapp为后缀名的,就通过c.Response.WriteHeader()来设置返回的Content-Type。这样,ffos的模拟器就可以通过这个manifest.webapp来安装这个web应用了。但是这样做有个小问题,当WriteHeader的时候,会出现一个error log:server.go:597: http: multiple response.WriteHeader calls。具体原因待查明。
后来仔细看ffos的文档(链接在此:https://developer.mozilla.org/en-US/docs/Web/Apps/Manifest),发现里面写明了在apache和nginx下的Content-Type配置,在配置文件里增加了对应的MIME类型,web server就可以自动返回对应的Content-Type。以前在读revel源码的时候,好像见到了关于mime的东西。于是一顿“find xxx | grep xxx”。终于找到了revel中mime的配置,代码在github.com/robfig/revel/conf/mime-types.conf。只要增加一行:webapp=application/x-web-app-manifest+json,就可以了。迅速删掉之前非常2b的实现。
后续:
mime配置的地方找到了,那么它是在哪里读取并返回的呢?
1.加载:github.com/robfig/revel/revel/run.go中runApp函数里,调用revel.LoadMimeConfig()。也就是说程序一跑起来,就加载一次。
2.LoadMimeConfig的实现是在github.com/robfig/revel/util.go中,关键一句:
mimeConfig, err = LoadConfig("mime-types.conf")
看到就是我前面修改的那个文件,mime-types.conf。
3.当请求一个static文件时,通过返回一个BinaryResult,来进行response的数据填写。看文件results.go中的一段代码:
func (r *BinaryResult) Apply(req *Request, resp *Response) { disposition := string(r.Delivery) if r.Name != "" { disposition += fmt.Sprintf("; filename=%s", r.Name) } resp.Out.Header().Set("Content-Disposition", disposition) // If we have a ReadSeeker, delegate to http.ServeContent if rs, ok := r.Reader.(io.ReadSeeker); ok { http.ServeContent(resp.Out, req.Request, r.Name, r.ModTime, rs) } else { // Else, do a simple io.Copy. if r.Length != -1 { resp.Out.Header().Set("Content-Length", strconv.FormatInt(r.Length, 10)) } resp.WriteHeader(http.StatusOK, ContentTypeByFilename(r.Name)) io.Copy(resp.Out, r.Reader) } // Close the Reader if we can if v, ok := r.Reader.(io.Closer); ok { v.Close() } }
可以看加黑的一句,通过ContentTypeByFilename函数,从文件名称的名称找到对应的content type。
4.最后看一下ContentTypeByFilename的实现,还是在util.go中:
func ContentTypeByFilename(filename string) string { dot := strings.LastIndex(filename, ".") if dot == -1 || dot+1 >= len(filename) { return DefaultFileContentType } extension := filename[dot+1:] contentType := mimeConfig.StringDefault(extension, "") if contentType == "" { return DefaultFileContentType } if strings.HasPrefix(contentType, "text/") { return contentType + "; charset=utf-8" } return contentType }
很简单,就是截取后缀名,然后通过之前加载的mimeConfig,找到对应的Content-Type。