Rails' `link_to foo, bar, method: "delete" 有 Yesod 替代品吗?

Is there a Yesod alternative to the Rails' `link_to foo, bar, method: "delete"?

有没有一种简单的方法可以生成 link,它使用与 Yesod 中的 GET 不同的方法,例如 Rails 上的 Ruby,其中可以执行 link_to foo, bar, method: "post",这将通过 JavaScript?

提交 link

update: Ruby on Rails 所做的是生成一个 data-method 属性,然后由它自己的 JavaScript图书馆。我不一定要寻找完全相同的解决方案,而是寻找在 Yesod 中创建 POST links 的现有机制。

Is there a simple way to generate a link that uses a different method than GET in Yesod

我不知道。

an existing mechanism for creating POST links in Yesod

要具体生成 POST link,您可以渲染样式看起来像 link 的表单。您可以将其包装在 postLink :: Route -> Widget 帮助器中。

下面是 Rails 的大致实现。这涉及几个部分:生成 link,使用 Javascript 实现方法更改功能,以及后端用于 CSRF 保护的额外代码。这是 linking 方面:

linkToPost :: Route App -> [(Text,Text)] -> Text -> Widget
linkToPost = linkToMethod "POST"

linkToMethod :: Text -> Route App -> [(Text,Text)] -> Text -> Widget
linkToMethod method url attributes content = 
    toWidget [hamlet|
    $newline never
    <a href="@{url}" rel="nofollow" data-method="#{method}" *{attributes}>#{content}</a>
    |]

你可以这样使用:

^{linkToPost CommentR [] "Create comment"}

此时需要Javascript来处理data-method属性。您实际上可以只包含 rails.js 并且上面的代码可以正常工作。以给自己带来相当大的痛苦和许多运行时错误为代价,我提取了相关的 Javascript:

$(document).ready(function() {

  $(document).on("click", "a[data-method]", function() {
    var link = $(this);
    var method = link.data('method');

    handleMethod(link);
    return false;
  });
});

function handleMethod(link) {
  var href = link.attr('href'),
    method = link.data('method'),
    csrfToken = $('meta[name=csrf-token]').attr('content'),
    form = $('<form method="POST" action="' + href + '"></form>'),
    metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';

  if (csrfToken !== undefined) {
    metadataInput += '<input name="_token" value="' + csrfToken + '" type="hidden" />';
  }

  form.hide().append(metadataInput).appendTo('body');

  form.submit();
}

注意,如果表单有POST类型的方法和_method参数,Yesod默认middleware会覆盖方法为_method的值,就像 Rails。上面的 Javascript 假设已启用。

为了利用 Rails.js 正在使用的 CSRF 保护,我们需要将 CSRF 令牌嵌入到我们页面的 <head> 中。首先,我在 defaultLayout 函数中获取 CSRF 令牌:

mCsrfToken <- fmap reqToken getRequest

然后嵌入到页面header里面default-layout-wrapper.hamlet:

$maybe token <- mCsrfToken
  <meta name="csrf-token" content=#{token}>

此时,可以应用用于表单的正常CSRF 保护。对我来说已经晚了,但我想出了这个作为简单请求(如 DELETE /users/1)检查 CSRF 令牌的方法,可能有比这更好的方法:

checkCsrf :: Handler ()
checkCsrf = do
    ((res, _), _) <- runFormPost (renderDivs (mempty :: AForm Handler () ))
    case res of
        FormSuccess _ -> return ()
        _ -> permissionDenied "CSRF failure"

这是一个很长的答案 + 我有点累了,欢迎对上面的代码进行审查。

更新

现在 Yesod 支持 embedding CSRF tokens in cookies,并在请求 header 中验证它们,您可能想要使用它而不是将令牌嵌入 HTML 并制作您的申请表格。推理:

  • 从 cookie 中查找值比 HTML 查询更简单且支持更好。
  • 添加请求 header 与 POSTing JSON/binary 数据等兼容。对我来说,这似乎不那么 hacky。
  • Yesod测试明确支持所有这些