Beego源碼解析之Context-Output

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)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章