Express中method-override模块详解(一): 源码

本文详细解析了Express中的method-override模块,重点探讨了HTTP VARY头的作用。内容协商机制在HTTP中用于选择最合适的内容版本,VARY头在内容依赖于非标准请求头如User-Agent时至关重要,防止缓存错误。method-override允许修改HTTP头部,使缓存服务器能正确处理请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// gettet 可以是一个自定义的function 或者是string
// function : 可以获取req.body中的数据 找到需要重写的方法(key--value),返回value。
// string : 以X-开头,那么这个key--value在req.header中设置
// string : 其他,那么这个key--value在req.url中设置
// 由于以上原因,method-override 需要放在 body-parser 之后
// options指定需要被改写的方法 比如 {method:['DELETE','POST']}等等
module.exports = function methodOverride(getter, options){
  options = options || {}

  // get the getter fn
  var get = typeof getter === 'function'
    ? getter
    : createGetter(getter || 'X-HTTP-Method-Override')

  // get allowed request methods to examine
  var methods = options.methods === undefined
    ? ['POST']
    : options.methods

  return function methodOverride(req, res, next) {
    var method
    var val

    req.originalMethod = req.originalMethod || req.method
    //

    // validate request is an allowed method
    // 用来判断这个请求是否属于需要被改写的范畴
    if (methods && methods.indexOf(req.originalMethod) === -1) {
      return next()
    }
    // 获得改写的目的方法
    val = get(req, res)
    method = Array.isArray(val)
      ? val[0]
      : val

    // replace
    if (method !== undefined && supports(method)) {
      req.method = method.toUpperCase()
      debug('override %s as %s', req.originalMethod, req.method)
    }

    next()
  }
}

/**
 * Create a getter for the given string.
 */

function createGetter(str) {
  if (str.substr(0, 2).toUpperCase() === 'X-') {
    // header getter
    return createHeaderGetter(str)
  }

  return createQueryGetter(str)
}

/**
 * Create a getter for the given query key name.
 */
//从url里获取改写的目的方法 比如?_method=DELETE等
function createQueryGetter(key) {
  return function(req, res) {
    var url = parseurl(req)
    var query = querystring.parse(url.query || '')
    return query[key]
  }
}

/**
 * Create a getter for the given header name.
 */
//从http头部获取改写的目的方法
function createHeaderGetter(str) {
  var header = str.toLowerCase()

  return function(req, res) {
    // set appropriate Vary header
    vary(res, str)

    // multiple headers get joined with comma by node.js core
    return (req.headers[header] || '').split(/ *, */)
  }
}

/**
 * Check if node supports `method`.
 */
//并不能改写成任意方法 改写的目的方法必须是node支持的方法
function supports(method) {
  return method
    && typeof method === 'string'
    && methods.indexOf(method.toLowerCase()) !== -1
}

可以参考里面的中文注释,或者点击这里在github查看

其中createHeaderGetter方法非常有意思,多了一个vary(res,str). 按照注释是设置了HTTP VARY 头部。但是vary是什么,为什么要设置呢?

一下内容来自博文

要了解 Vary 的作用,先得了解 HTTP 的内容协商机制。有时候,同一个 URL 可以提供多份不同的文档,这就要求服务端和客户端之间有一个选择最合适版本的机制,这就是内容协商。

协商方式有两种,一种是服务端把文档可用版本列表发给客户端让用户选,这可以使用 300 Multiple Choices 状态码来实现。这种方案有不少问题,首先多一次网络往返;其次服务端同一文档的某些版本可能是为拥有某些技术特征的客户端准备的,而普通用户不一定了解这些细节。举个例子,服务端通常可以将静态资源输出为压缩和未压缩两个版本,压缩版显然是为支持压缩的客户端而准备的,但如果让普通用户选,很可能选择错误的版本。

所以 HTTP 的内容协商通常使用另外一种方案:服务端根据客户端发送的请求头中某些字段自动发送最合适的版本。可以用于这个机制的请求头字段又分两种:内容协商专用字段(Accept 字段)、其他字段。

首先来看 Accept 字段,详见下表:


请求头字段说明响应头字段
Accept告知服务器发送何种媒体类型Content-Type
Accept-Language告知服务器发送何种语言Content-Language
Accept-Charset告知服务器发送何种字符集Content-Type
Accept-Encoding告知服务器采用何种压缩方式Content-Encoding

例如客户端发送以下请求头:

Accept:*/*
Accept-Encoding: gzip ,deflate,sdch
Accept-Language:zh-CN,en-US;q=0.8,en;q=0.6

表示它可以接受任何 MIME 类型的资源;支持采用 gzip、deflate 或 sdch 压缩过的资源;可以接受 zh-CN、en-US 和 en 三种语言,并且 zh-CN 的权重最高(q 取值 0 - 1,最高为 1,最低为 0,默认为 1),服务端应该优先返回语言等于 zh-CN 的版本。

浏览器的响应头可能是这样的:

Content-Type: text /javascript
Content-Encoding: gzip

表示这个文档确切的 MIME 类型是 text/javascript;文档内容进行了 gzip 压缩;响应头没有 Content-Language 字段,通常说明返回版本的语言正好是请求头 Accept-Language 中权重最高的那个。

有时候,上面四个 Accept 字段并不够用,例如要针对特定浏览器如 IE6 输出不一样的内容,就需要用到请求头中的 User-Agent 字段。类似的,请求头中的 Cookie 也可能被服务端用做输出差异化内容的依据。

由于客户端和服务端之间可能存在一个或多个中间实体(如缓存服务器),而缓存服务最基本的要求是给用户返回正确的文档。如果服务端根据不同 User-Agent 返回不同内容,而缓存服务器把 IE6 用户的响应缓存下来,并返回给使用其他浏览器的用户,肯定会出问题 。

所以 HTTP 协议规定,如果服务端提供的内容取决于 User-Agent 这样「常规 Accept 协商字段之外」的请求头字段,那么响应头中必须包含 Vary 字段,且 Vary 的内容必须包含 User-Agent。同理,如果服务端同时使用请求头中 User-Agent 和 Cookie 这两个字段来生成内容,那么响应中的 Vary 字段看上去应该是这样的:


Vary: User-Agent, Cookie


也就是说 Vary 字段用于列出一个响应字段列表,告诉缓存服务器遇到同一个 URL 对应着不同版本文档的情况时,如何缓存和筛选合适的版本。


好了,知道了Vary头的含义,就不难理解源码中vary的用处了。

由于我们对HTTP进行了方法重写,那么存在这样的可能,对同一种URL,服务器的相应是不一样的。

比如这样一个POST http://localhost/users

然而HTTP的头部确不一样。可以通过http头X-HTTP-HEADER-REWRITE将其改写成 DELETE,PUT,或者POST等。而此时如果在客户端和服务器之间存在着缓存服务器,我们就需要缓存服务器按照改写的目的方法分类缓存,而不是仅仅只针对URL保存一份缓存。

通过vary,就可以将X-HTTP-HEADER-REWRITE添加到Vary中。


多读源代码,才是进步的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值