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)
}