浅谈go语言renderer包代码分析

2019-11-10 11:50:42刘景俊

大致看上去,和我们不使用renderer包的实现基本一样。指定Content-Type, 发送HTTP状态码,然后看JSON前缀是否设置,如果设置,前缀也发送到字节流中。 最后就是发送json字节流。

唯一区别在于,我们使用encoding/json包的Marshal方法来将给定的值转换成二进制序列,而renderer对这个方法进行了包装:

func (r *Render) json(v interface{}) ([]byte, error) {
  var bs []byte
  var err error
  if r.opts.JSONIndent {
    bs, err = json.MarshalIndent(v, "", " ")
  } else {
    bs, err = json.Marshal(v)
  }
  if err != nil {
    return bs, err
  }
  if r.opts.UnEscapeHTML {
    bs = bytes.Replace(bs, []byte("u003c"), []byte("<"), -1)
    bs = bytes.Replace(bs, []byte("u003e"), []byte(">"), -1)
    bs = bytes.Replace(bs, []byte("u0026"), []byte("&"), -1)
  }
  return bs, nil
}

如果有设置JSONIndent, 即JSON缩进,那么使用json.MarshalIndent来将变量转换为json字节流。 这个方法其实就是将JSON格式化,使得结果看起来更好看。

另外这个方法还会根据配置,进行html实体的转义。

因此总体来说原理和开头的代码基本一样。只不过多了一些额外的修饰修补。

JSONP方法

我们理解了JSON方法,理解起JSONP就更加简单了。

JSONP全称为JSON with Padding, 用于解决Ajax跨域问题的一种方案。

它的原理非常简单:

// 客户端代码
var dosomething = function(data) {
  // do something with data
}

var url = "server.jsonp?callback=dosomething";
 // 创建 <script> 标签,设置其 src 属性
 var script = document.createElement('script');
 script.setAttribute('src', url);

 // 把 <script> 标签加入 <body> 尾部,此时调用开始。
 document.getElementsByTagName('body')[0].appendChild(script);
上面server.jsonp是一个后台脚本,访问后立即返回它的输出内容, 这也就是renderer的JSONP要响应的内容。

func (r *Render) JSONP(w http.ResponseWriter, status int, callback string, v interface{}) error {
  w.Header().Set(ContentType, r.opts.ContentJSONP)
  w.WriteHeader(status)

  bs, err := r.json(v)
  if err != nil {
    return err
  }

  if callback == "" {
    return errors.New("renderer: callback can not bet empty")
  }

  w.Write([]byte(callback + "("))
  _, err = w.Write(bs)
  w.Write([]byte(");"))

  return err
}

    设置Content-Type为application/javascript, 非常关键的一点。 想一想html中嵌入的js文件的mime类型是不是也是这个值? 然后同样的设置响应状态码, 这点没有什么特殊的。 将值转换为json字节序列。这个json字节序列还没有向响应写入进去。 这个时候我们检查callback是否存在,不存在报错出去。因为是JSONP, 必须要有callback, 这个callback是请求参数传入的。 然后用"callbak("和")"将json字节序列包围起来,一起输出到响应流中。这样jsonp响应就产生了。