12

我们的团队最近遇到了一个最初的边缘案例问题,当用户从具有某种 javascript 呈现(例如 Ajax、选项卡等)的页面单击浏览器的后退按钮时,将显示原始 javascript。要重新创建,我们按照以下步骤操作:

  • 访问用户工作申请索引
  • 单击一个按钮到管理职位发布页面
  • 单击一个选项卡(使用 bettertabs gem)
  • 单击浏览器的返回按钮

前面的步骤将显示:

(function() {

  $(".job_applications").html("<li class=\"job_posting_application\">\n
    ...
    ...
    ...
    ...
  );

}).call(this);

在某些偶然发生的情况下,您无需在返回上一页之前单击选项卡,但它仍会呈现原始 javascript。归根结底,似乎最后渲染的模板正在被缓存,这在浏览器方面是正常的和预期的,但会导致我认为是一个更大的问题。

Rails 指南在Layouts and Rendering部分中指出,特别是关于模板的 MIME 类型:

默认情况下,Rails 将使用 text/html 的 MIME 内容类型(或 application/json 如果您使用 :json 选项,或 application/xml 用于 :xml 选项)提供渲染操作的结果。

基于 Rails 的默认设置,我们的控制器的 index 动作应该会渲染我们的index.html.slim模板。但是,在跟踪服务器日志的同时对该页面进行非远程调用(例如,直接导航到浏览器中的页面)时,我们注意到它实际上呈现index.js.coffee. 下面是我们的控制器操作,请注意我们没有明确响应 html 或 js 格式,因为我们可能应该考虑此页面中的覆盖功能:

def index
  @company_id, @division_id, @job_posting_id = params[:company_id], params[:division_id], params[:job_posting_id]

  # API requests are made here to instantiate @job_posting, et al.,
  # but are not shown for brevity

  authorize! :manage, @job_posting

  @survey = @job_posting.survey
  @job_applications = @job_posting.job_applications(sort_column, sort_direction)
end

但是,鉴于此设置,index.html.slim 根据 Rails 默认值进行渲染。添加respond_to块时,缓存似乎仍然有效,控制器可能不太关心respond_to块的存在:

def index
  ...
  ...
  respond_to do |format|
    format.html
    format.js
  end
end

即使明确地,虽然很臭,告诉每种格式呈现不同的模板,模板似乎js.coffee优先于html.slim模板:

def index
  ...
  ...
  respond_to do |format|
    format.html { render template: "users/job_posting_applications/index" }
    format.js { render template: "users/job_posting_applications/ajax" }
  end
end

在上述情况下,直接导航到浏览器中的页面(换句话说,不进行远程 Ajax 调用),服务器日志将呈现ajax.js.coffee,即使 Rails 默认为 html,除非另有说明。

说了这么多,这里有一些其他的发现:

Started GET "/users/companies/1/divisions/18/job_postings/349421/applications" for 127.0.0.1 at 2012-10-03 19:55:26 -0400
Processing by Users::JobPostingApplicationsController#index as JSON

(您可以在此粘贴中引用上面显示的整个请求

考虑到我们没有在此请求上提供任何 JSON,并且在路由中没有针对:json此路由的默认格式的路由规范,为什么它以 JSON 格式处理超出了我的理解。

此外,在调试request.format此操作中的值时,它会返回application/json.

另一个场景是在另一个users/company_admin_metrics#index仅包含一个index.html.slim模板的控制器 ( ) 中。导航到此页面时,服务器日志显示它users/company_admin_metrics/index.html.slimlayouts/users. 当我创建一个空白 js.coffee 模板时:

$ touch app/views/users/company_admin_metrics/index.js.coffee

并直接导航到该索引页面,服务器日志显示它已渲染users/company_admin_metrics/index.js.coffee,这进一步暴露了某处有关模板渲染优先级的潜在问题。

有没有人遇到过类似的问题,可能会为此提供潜在的解决方案?

我们的堆栈

以下是这个特定问题的基本参与者的分钟列表:

  • 导轨 3.2.6
  • Coffee-Rails 3.2.1
  • 更好的标签 1.2.6

此请求取决于通过解析 JSON 并返回 Ruby 对象的客户端 gem 对我们的职位发布 API 的请求,但这些请求与此特定应用程序的耦合方式不会发生冲突并导致应用程序的内容类型为application/json对于上述请求。

4

3 回答 3

12

我认为您需要将格式扩展附加.js到您的 ajax 请求中的 URL。

可能发生的情况是用户正在通过请求 html 来执行index操作。然后 ajax 访问相同的 URL/动作,但请求 javascript。当用户单击后退按钮时,浏览器无法分辨两者之间的区别,因此使用最新的(javascript 响应)。/job_applicationsGET

于 2012-11-03T08:05:03.710 回答
7

最近开始在带有 Rails 4.2.4 的 Chrome 中看到这个确切的问题。似乎是一个众所周知且备受争议的问题。我们的团队通过在请求中添加Vary标头来解决它:xhr

class ApplicationController < ActionController::Base
  after_action :set_vary_header

  private

  # Fix a bug/issue/by-design(?) of browsers that have a hard time understanding
  # what to do about our ajax search page. This header tells browsers to not cache
  # the current contents of the page because previously when someone filtered results,
  # went to the listing's external site, then hit Back, they'd only see the last
  # ajax response snippet, not the full listings page for their search.
  #
  # Heated multi-year discussion on this issue in Chrome
  # https://code.google.com/p/chromium/issues/detail?id=94369
  #
  # And continued by the Rails team:
  # https://github.com/rails/jquery-ujs/issues/318
  # https://github.com/rails/jquery-rails/issues/121
  def set_vary_header
    if request.xhr?
      response.headers["Vary"] = "accept"
    end
  end
end
于 2015-10-06T02:17:42.120 回答
4

自从提出这个问题以来已经有几个月了,但自从我遇到这个问题以来,我会插话并给我两美分。

正如 carbo 所说,浏览器可以分辨 html 和 js 请求之间的区别,因此当单击后退按钮时,它会看到相同的 url,并且只提供发生在 js 代码中的缓存数据。我遇到了一些问题,但是使用推送状态 javascript 来修改 ajax 请求的浏览器历史记录。我找到的最简单的解决方案是告诉浏览器不要缓存请求。因此,当返回上一页时,它会被迫重新加载它。

您可以全局禁用缓存: $.ajaxSetup({ cache: false });

或者您可以尝试在特定的 ajax 请求上指定 cache false 。

于 2013-02-09T02:06:18.277 回答