Beego源码解析之Context-Output
BeegoOutput
- 初始化结构体方法
- 写入response body的方法:写入数据类型可以为[]byte,JSON,JSONP,XML,YAML。
- 一个根据header中Accept字段,自动判断用哪种数据类型写入response body。
- 一系列参数判断和设置参数值的方法(具体情况,具体分析,这里不做呈现)。
- 一个下载文件的response
BeegoOutput 结构
BeegoOutput主要用来发送回传的header
type BeegoOutput struct {
Context *Context
Status int
EnableGzip bool
}
初始化Onput
在/beego/context.go中初始化context时被调用,beego的output封装request的一些api,便于使用。Reset在context.Reset()中被调用。reset的作用相当于初始化BeegoOutput。
func NewOutput() *BeegoOutput {
return &BeegoOutput{}
}
// Reset init BeegoOutput
func (output *BeegoOutput) Reset(ctx *Context) {
output.Context = ctx
output.Status = 0
}
根据[]byte设置repsonse的body
EnableGzip为true表示,需根据request的Accept-Encoding值来对content进行压缩。
如果encoding的值不为空,会赋值给b,并且赋值output.Header的Content-Encoding。
如果encoding的值不为空,Content-Length为压缩后的content长度,否则为原始content长度。
可以实现io.Copy的原因见注释
outupt.status是啥和在哪里赋值不清楚
func (output *BeegoOutput) Body(content []byte) error {
var encoding string
var buf = &bytes.Buffer{}
if output.EnableGzip {
encoding = ParseEncoding(output.Context.Request)
}
if b, n, _ := WriteBody(encoding, buf, content); b {
output.Header("Content-Encoding", n)
output.Header("Content-Length", strconv.Itoa(buf.Len()))
} else {
output.Header("Content-Length", strconv.Itoa(len(content)))
}
// Write status code if it has been set manually
// Set it to 0 afterwards to prevent "multiple response.WriteHeader calls"
if output.Status != 0 {
output.Context.ResponseWriter.WriteHeader(output.Status)
output.Status = 0
} else {
output.Context.ResponseWriter.Started = true
}
//output.Context.ResponseWriter继承/net/http/server.go里的ResponseWriter,/net/http/server.go里的Response被同文件里的response结构体实现,在response里有一个类型为*bytes.Writer的字段w,w里面实现继承了io.Writer,所以可以进行如下io.Copy。
io.Copy(output.Context.ResponseWriter, buf)
return nil
}
根据json设置response的body
JSON封装了上面讲到的Body方法,主要是将传进来的json序列化之后写入body。
hasIndent表示需要做缩排。
encoding表示需要将utf-8转成unicode。
stringsToJSON和原来思考的copy方式不太一样,值得研究,说不定后面有用。
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
output.Header("Content-Type", "application/json; charset=utf-8")
var content []byte
var err error
if hasIndent {
content, err = json.MarshalIndent(data, "", " ")
} else {
content, err = json.Marshal(data)
}
if err != nil {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
if encoding {
content = []byte(stringsToJSON(string(content)))
}
return output.Body(content)
}
根据YAML data写入repsonse的body
func (output *BeegoOutput) YAML(data interface{}) error {
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
var content []byte
var err error
content, err = yaml.Marshal(data)
if err != nil {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
return output.Body(content)
}
根据JSONP data写入response的body
Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
为什么我们从不同的域(网站)访问数据需要一个特殊的技术(JSONP )呢?这是因为同源策略。
同源策略,它是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript 的浏览器都会使用这个策略。
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/javascript; charset=utf-8")
var content []byte
var err error
if hasIndent {
content, err = json.MarshalIndent(data, "", " ")
} else {
content, err = json.Marshal(data)
}
if err != nil {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
callback := output.Context.Input.Query("callback")
if callback == "" {
return errors.New(`"callback" parameter required`)
}
callback = template.JSEscapeString(callback)
callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback)
callbackContent.WriteString("(")
callbackContent.Write(content)
callbackContent.WriteString(");\r\n")
return output.Body(callbackContent.Bytes())
}
根据XML data写入response的body
func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/xml; charset=utf-8")
var content []byte
var err error
if hasIndent {
content, err = xml.MarshalIndent(data, "", " ")
} else {
content, err = xml.Marshal(data)
}
if err != nil {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
return output.Body(content)
}
根据header中的Accept字段决定需要用哪个类型写入response的body
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) {
accept := output.Context.Input.Header("Accept")
switch accept {
case ApplicationYAML:
output.YAML(data)
case ApplicationXML, TextXML:
output.XML(data, hasIndent)
default:
output.JSON(data, hasIndent, len(hasEncode) > 0 && hasEncode[0])
}
}
定义一个response为download file
func (output *BeegoOutput) Download(file string, filename ...string) {
// check get file error, file not found or other error.
if _, err := os.Stat(file); err != nil {
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
return
}
var fName string
if len(filename) > 0 && filename[0] != "" {
fName = filename[0]
} else {
fName = filepath.Base(file)
}
output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
output.Header("Content-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream")
output.Header("Content-Transfer-Encoding", "binary")
output.Header("Expires", "0")
output.Header("Cache-Control", "must-revalidate")
output.Header("Pragma", "public")
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
}