对使用 http 请求的 GET 和 POST 的一点思考
在 web 开发中,一个 http 请求方法有 GET / HEAD / PUT / DELETE / OPTIONS / CONNECT 几个形式,对此不多究。我今天主要想谈谈常见的 GET 和 POST 两种方法的使用思考。
先来讲一个我最近做的一个 Ruby on Rails 项目,在那个项目中,用户 (User) 可以选择某一个数据集合 (DataSet) 项目参与其中做任务 (Task)。模型关系如下
class User < ActiveRecord::Base
has_many :tasks
end
class DataSet < ActiveRecord::Base
def user_task(user)
user.tasks.where(data_set_id: self.id).first
end
end
class Task < ActiveRecord::Base
belongs_to :user
belongs_to :data_set
end
在数据集合 (DataSet) 列表页面,列出多个集合 (DataSet),用户可以选择其中一个参与任务 (Task)。如果用户还没有参与其中的某个项目,显示“开始工作”,否则显示“继续工作”,页面部分代码如下:
<% @data_sets.each do |data_set| %>
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<%= image_tag data_set.logo.url %>
<div class="caption">
<h3><%= data_set.name %></h3>
<p><%= data_set.description %></p>
<p>
<% if data_set.user_task(current_user).present? %>
<%= link_to '继续工作', workspace_data_set_path(data_set), class: 'btn btn-primary' %>
<% else %>
<%= link_to '开始工作', choose_task_data_set_path(data_set), method: :post, class: 'btn btn-primary' %>
<% end %>
</p>
</div>
</div>
</div>
<% end %>
DataSetsController 中的部分代码如下:
class DataSetsController < ApplicationController
before_action :authenticate_user!
before_action :set_data_set, only: [:show, :edit, :update, :destroy, :choose_task, :workspace, :mark_picture]
# GET /data_sets
# GET /data_sets.json
def index
@data_sets = DataSet.all
end
# POST /data_sets/1/choose_task
def choose_task
if current_user.tasks.where(data_set_id: @data_set.id).first.nil?
current_user.tasks.build(data_set_id: @data_set.id).save
end
redirect_to workspace_data_set_path(@data_set)
end
# GET /data_sets/1/workspace
def workspace
# ...
end
end
当初在设计这个页面上逻辑的时候,一开始以为直接用一个方法请求就搞定了,页面点击“开始工作”或者“继续工作”按钮,直接 GET 请求跳转到用户工作台链接/data_sets/{id}/workspace,在 workspace action 中加入额外的逻辑判断是否要创建用户的任务 (Task)。我接着认真思考了一下,发现这样不妥。原因有是 GET /data_sets/{id}/workspace 中的逻辑不纯粹, 与它的 URL 本身语义不符合。也不利于测试。
那也许有人会说我将 workspace 这个 action 改成 POST 显示可以吗?答案也是不可以的,因为你点击按钮进入这个页面后,你如果刷新当前的 workspace 页面,浏览器会提示是否重复提交请求的提示,给用户的体验也不好。实质是这个请求不可 cache。另外在其它页面地方也不能通过一般的 a link 的方式进入 workspace 页面,不可传播。
现在我将这个逻辑分开写了 POST /data_sets/{id}/choose_task 和 GET /data_sets/{id}/workspace 两个请求,代码逻辑是很合理的。用户可以随意刷新他的工作台,而不用担心刷新会改变什么数据。
啰嗦了这么多,其实这是一个很小的问题,但是容易忽略。之前在一个公司维护一个项目,发现页面中做的一个商品多条件查询表单居然是用 POST 请求的。每次在浏览器中查询商品后,按 F5 刷新,总是提示是否重复提交请求,用户体验很不好。另外我还没有办法把我查询找到的商品结果页面发给我其他的朋友。
现在我来总结一下 http 请求的 GET 和 POST 方法使用。
首先如名词所示,GET 是用来从服务器上请求指定的资源,请求携带的信息都是体现在 URL 参数上面,如/products?category=1&color=red。URL 长度有限制。
POST 是向服务器中提交数据,请求携带的信息一般放在 http 请求的消息体中,上传的数据长度在服务器端可以限制。
从这里应该可以明确一点:GET 请求应该不会带来影响的,重复请求不会改动服务器上的资源,它只负责取数据。POST 请求是想服务器提交数据,重复提交动作对服务器资源是有影响的。那么 GET 请求是可以被前端缓存的,而 POST 请求不行。
在用户的浏览器中,GET 请求的 URL 是可以被保存书签的,POST 请求不行。GET 请求的 URL 是有历史记录的,可以前进 / 后退 / 刷新。POST 请求则不行。
完。