世界上最伟大的投资就是投资自己的教育

首页Ruby
随风 · 练气

gem 介绍及源码分析之 http_accept_language (四)

随风发布于1773 次阅读

1.介绍

之所以要介绍这个http_accept_language,是我在翻看ruby-china源码中无意中看到的,它可以让页面根据浏览器所使用的语言来自动调整网站的语言,比如你的浏览器的语言是 en,网站就会自动用 en 这个 locale。把它拿来介绍,也是因为它简单,并且里面涉及到一些有意义的知识,比如中间件 (middleware)、HTTP 协议等。

2. 安装及使用

添加下面这行到 Gemfile 文件中:

gem 'http_accept_language'

在使用之前,来看看我的应用的一些关于 locale 的配置。

# config/application.rb
config.i18n.default_locale = :'zh-CN'

我们试着先在浏览器发送一个请求,然后用 pry 来调试。

我的浏览器所使用的语言是 en。

这是 HTTP 请求的头部信息,显示的正是 en 语言。然后用 pry 来得到这个 HTTP 的请求头部,一共有两种方法:

[1] pry(#<HomeController>)> request.headers["HTTP_ACCEPT_LANGUAGE"]
"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4"
[2] pry(#<HomeController>)> env["HTTP_ACCEPT_LANGUAGE"]
"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4"

http_accept_language正是利用这个en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4信息来处理的,它就是解析这个字符串来得到真正的 locale,再去 rails 中找到所有可用的 locale 进行匹配,并加载最终的 locale。

[3] pry(#<HomeController>)> http_accept_language.compatible_language_from(I18n.available_locales)
:en

果然,输出的就是正确的en

关于具体的使用方法,就得查看官方的 readme 文档了。

3. 源码解析

在看源码之前,先来看rake middleware指令的输出:

  rails365 git:(master)  rake middleware
...
use HttpAcceptLanguage::Middleware
use ExceptionNotification::Rack
run Rails365::Application.routes

可以发现多了一个 middleware。

来看下源码:

# https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/railtie.rb
module HttpAcceptLanguage
  class Railtie < ::Rails::Railtie
    initializer "http_accept_language.add_middleware" do |app|
      app.middleware.use Middleware

      ActiveSupport.on_load :action_controller do
        include EasyAccess
      end
    end
  end

  module EasyAccess
    def http_accept_language
      @http_accept_language ||= request.env["http_accept_language.parser"] || Parser.new(request.env["HTTP_ACCEPT_LANGUAGE"])
    end
  end
end

Railtie是 rails 的一个组件,是在启动的时候加载和扩展各种组件,比如 active_record, active_view 等,rails 的源码到处都有它的身影,在这里理解为启动的时候,就能加载组件就可以了。app.middleware.use Middleware表示的是使用了这个Middleware的 rack 中间件。

先来看下这个Middleware中间件的源码:

# https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/middleware.rb
module HttpAcceptLanguage
  class Middleware
    def initialize(app)
      @app = app
    end

    def call(env)
      env["http_accept_language.parser"] = Parser.new(env["HTTP_ACCEPT_LANGUAGE"])

      def env.http_accept_language
        self["http_accept_language.parser"]
      end

      @app.call(env)
    end
  end
end

其实整个 rails 应用就是实现的 rack 中间件,默认情况下就自动加载了各种中间件的,在这里只不过插件多了一个,中间件就是得到 HTTP 请求,再把请求的数据,得到再处理,再按照规定的格式响应,比如 rack 规定了,响应必须包含状态码,头部信息,和响应的内容。

具体的关于 rack 中间件的内容可以参考下面两个链接:

上面中间件的内容就是把env["http_accept_language.parser"]的信息读取了,之后进行解析,我们来看下是如何解析的。

def compatible_language_from(available_languages)
  user_preferred_languages.map do |preferred| #en-US
    preferred = preferred.downcase
    preferred_language = preferred.split('-', 2).first

    available_languages.find do |available| # en
      available = available.to_s.downcase
      preferred == available || preferred_language == available.split('-', 2).first
    end
  end.compact.first
end

def user_preferred_languages
  @user_preferred_languages ||= begin
    header.to_s.gsub(/\s+/, '').split(',').map do |language|
      locale, quality = language.split(';q=')
      raise ArgumentError, 'Not correctly formatted' unless locale =~ /^[a-z\-0-9]+|\*$/i

      locale  = locale.downcase.gsub(/-[a-z0-9]+$/i, &:upcase) # Uppercase territory
      locale  = nil if locale == '*' # Ignore wildcards

      quality = quality ? quality.to_f : 1.0

      [locale, quality]
    end.sort do |(_, left), (_, right)|
      right <=> left
    end.map(&:first).compact
  rescue ArgumentError # Just rescue anything if the browser messed up badly.
    []
  end
end

所有相关的代码都在https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb这个文件里。

具体的代码就不分析了,只要原理懂了,代码层次的东西也是比较简单的。

完结。

本站文章均为原创内容,如需转载请注明出处,谢谢。

0 条回复
暂无回复~~
喜欢
我的微信官网服务号精品文章订阅号微信视频号
程序员随风
统计信息
    学员: 22744
    视频数量: 1492
    文章数量: 466

© 汕尾市求知科技有限公司 | 专业版网站 | 关于我们 | 在线学员:1151

粤公网安备 44152102000088号粤公网安备 44152102000088号 | 粤ICP备19038915号

Top