mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add ActionController::HTTP
More info http://edgeguides.rubyonrails.org/api_app.html [Carlos Antonio da Silva & Santiago Pastorino]
This commit is contained in:
parent
cc1c4acc8e
commit
4c16791f35
17 changed files with 383 additions and 9 deletions
|
@ -6,6 +6,7 @@ module ActionController
|
|||
|
||||
autoload :Base
|
||||
autoload :Caching
|
||||
autoload :HTTP
|
||||
autoload :Metal
|
||||
autoload :Middleware
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ module ActionController
|
|||
class Base < Metal
|
||||
abstract!
|
||||
|
||||
# Shortcut helper that returns all the ActionController modules except the ones passed in the argument:
|
||||
# Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument:
|
||||
#
|
||||
# class MetalController
|
||||
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
|
||||
|
|
134
actionpack/lib/action_controller/http.rb
Normal file
134
actionpack/lib/action_controller/http.rb
Normal file
|
@ -0,0 +1,134 @@
|
|||
require "action_controller/log_subscriber"
|
||||
|
||||
module ActionController
|
||||
# HTTP Controller is a lightweight version of <tt>ActionController::Base</tt>,
|
||||
# created for applications that don't require all functionality that a complete
|
||||
# \Rails controller provides, allowing you to create faster controllers. The
|
||||
# main scenario where HTTP Controllers could be used is API only applications.
|
||||
#
|
||||
# An HTTP Controller is different from a normal controller in the sense that
|
||||
# by default it doesn't include a number of features that are usually required
|
||||
# by browser access only: layouts and templates rendering, cookies, sessions,
|
||||
# flash, assets, and so on. This makes the entire controller stack thinner and
|
||||
# faster, suitable for API applications. It doesn't mean you won't have such
|
||||
# features if you need them: they're all available for you to include in
|
||||
# your application, they're just not part of the default HTTP Controller stack.
|
||||
#
|
||||
# By default, only the ApplicationController in a \Rails application inherits
|
||||
# from <tt>ActionController::HTTP</tt>. All other controllers in turn inherit
|
||||
# from ApplicationController.
|
||||
#
|
||||
# A sample controller could look like this:
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# def index
|
||||
# @posts = Post.all
|
||||
# render json: @posts
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Request, response and parameters objects all work the exact same way as
|
||||
# <tt>ActionController::Base</tt>.
|
||||
#
|
||||
# == Renders
|
||||
#
|
||||
# The default HTTP Controller stack includes all renderers, which means you
|
||||
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
|
||||
# in mind that templates are not going to be rendered, so you need to ensure
|
||||
# your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
|
||||
# all actions.
|
||||
#
|
||||
# def show
|
||||
# @post = Post.find(params[:id])
|
||||
# render json: @post
|
||||
# end
|
||||
#
|
||||
# == Redirects
|
||||
#
|
||||
# Redirects are used to move from one action to another. You can use the
|
||||
# <tt>redirect</tt> method in your controllers in the same way as
|
||||
# <tt>ActionController::Base</tt>. For example:
|
||||
#
|
||||
# def create
|
||||
# redirect_to root_url and return if not_authorized?
|
||||
# # do stuff here
|
||||
# end
|
||||
#
|
||||
# == Adding new behavior
|
||||
#
|
||||
# In some scenarios you may want to add back some functionality provided by
|
||||
# <tt>ActionController::Base</tt> that is not present by default in
|
||||
# <tt>ActionController::HTTP</tt>, for instance <tt>MimeResponds</tt>. This
|
||||
# module gives you the <tt>respond_to</tt> and <tt>respond_with</tt> methods.
|
||||
# Adding it is quite simple, you just need to include the module in a specific
|
||||
# controller or in <tt>ApplicationController</tt> in case you want it
|
||||
# available to your entire app:
|
||||
#
|
||||
# class ApplicationController < ActionController::HTTP
|
||||
# include ActionController::MimeResponds
|
||||
# end
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# respond_to :json, :xml
|
||||
#
|
||||
# def index
|
||||
# @posts = Post.all
|
||||
# respond_with @posts
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
|
||||
# available modules if you want to include any other functionality that is
|
||||
# not provided by <tt>ActionController::HTTP</tt> out of the box.
|
||||
class HTTP < Metal
|
||||
abstract!
|
||||
|
||||
# Shortcut helper that returns all the ActionController::HTTP modules except the ones passed in the argument:
|
||||
#
|
||||
# class MetalController
|
||||
# ActionController::HTTP.without_modules(:ParamsWrapper, :Streaming).each do |left|
|
||||
# include left
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This gives better control over what you want to exclude and makes it easier
|
||||
# to create a bare controller class, instead of listing the modules required manually.
|
||||
def self.without_modules(*modules)
|
||||
modules = modules.map do |m|
|
||||
m.is_a?(Symbol) ? ActionController.const_get(m) : m
|
||||
end
|
||||
|
||||
MODULES - modules
|
||||
end
|
||||
|
||||
MODULES = [
|
||||
HideActions,
|
||||
UrlFor,
|
||||
Redirecting,
|
||||
Rendering,
|
||||
Renderers::All,
|
||||
ConditionalGet,
|
||||
RackDelegation,
|
||||
|
||||
ForceSSL,
|
||||
DataStreaming,
|
||||
|
||||
# Before callbacks should also be executed the earliest as possible, so
|
||||
# also include them at the bottom.
|
||||
AbstractController::Callbacks,
|
||||
|
||||
# Append rescue at the bottom to wrap as much as possible.
|
||||
Rescue,
|
||||
|
||||
# Add instrumentations hooks at the bottom, to ensure they instrument
|
||||
# all the methods properly.
|
||||
Instrumentation
|
||||
]
|
||||
|
||||
MODULES.each do |mod|
|
||||
include mod
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:action_controller, self)
|
||||
end
|
||||
end
|
|
@ -44,7 +44,7 @@ module ActionController
|
|||
redirect_options = {:protocol => 'https://', :status => :moved_permanently}
|
||||
redirect_options.merge!(:host => host) if host
|
||||
redirect_options.merge!(:params => request.query_parameters)
|
||||
flash.keep
|
||||
flash.keep if respond_to?(:flash)
|
||||
redirect_to redirect_options
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ module ActionController
|
|||
end
|
||||
|
||||
initializer "action_controller.initialize_framework_caches" do
|
||||
ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache }
|
||||
ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache if respond_to?(:cache_store) }
|
||||
end
|
||||
|
||||
initializer "action_controller.assets_config", :group => :all do |app|
|
||||
|
@ -37,8 +37,15 @@ module ActionController
|
|||
ActiveSupport.on_load(:action_controller) do
|
||||
include app.routes.mounted_helpers
|
||||
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
|
||||
extend ::ActionController::Railties::Paths.with(app)
|
||||
options.each { |k,v| send("#{k}=", v) }
|
||||
extend ::ActionController::Railties::Paths.with(app) if respond_to?(:helpers_path)
|
||||
options.each do |k,v|
|
||||
k = "#{k}="
|
||||
if respond_to?(k)
|
||||
send(k, v)
|
||||
elsif !Base.respond_to?(k)
|
||||
raise "Invalid option key: #{k}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ module ActionController
|
|||
else
|
||||
paths = app.helpers_paths
|
||||
end
|
||||
|
||||
klass.helpers_path = paths
|
||||
|
||||
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
|
||||
|
|
|
@ -51,12 +51,13 @@ module ActionDispatch # :nodoc:
|
|||
# If a character set has been defined for this response (see charset=) then
|
||||
# the character set information will also be included in the content type
|
||||
# information.
|
||||
attr_accessor :charset, :content_type
|
||||
attr_accessor :charset
|
||||
attr_reader :content_type
|
||||
|
||||
CONTENT_TYPE = "Content-Type".freeze
|
||||
SET_COOKIE = "Set-Cookie".freeze
|
||||
LOCATION = "Location".freeze
|
||||
|
||||
|
||||
cattr_accessor(:default_charset) { "utf-8" }
|
||||
|
||||
include Rack::Response::Helpers
|
||||
|
@ -83,6 +84,10 @@ module ActionDispatch # :nodoc:
|
|||
@status = Rack::Utils.status_code(status)
|
||||
end
|
||||
|
||||
def content_type=(content_type)
|
||||
@content_type = content_type.to_s
|
||||
end
|
||||
|
||||
# The response code of the request
|
||||
def response_code
|
||||
@status
|
||||
|
|
|
@ -293,6 +293,10 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
class HTTP
|
||||
include SharedTestRoutes.url_helpers
|
||||
end
|
||||
|
||||
class TestCase
|
||||
include ActionDispatch::TestProcess
|
||||
|
||||
|
|
19
actionpack/test/controller/http/action_methods_test.rb
Normal file
19
actionpack/test/controller/http/action_methods_test.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ActionMethodsHTTPController < ActionController::HTTP
|
||||
def one; end
|
||||
def two; end
|
||||
hide_action :two
|
||||
end
|
||||
|
||||
class ActionMethodsHTTPTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@controller = ActionMethodsHTTPController.new
|
||||
end
|
||||
|
||||
def test_action_methods
|
||||
assert_equal Set.new(%w(one)),
|
||||
@controller.class.action_methods,
|
||||
"#{@controller.controller_path} should not be empty!"
|
||||
end
|
||||
end
|
55
actionpack/test/controller/http/conditional_get_test.rb
Normal file
55
actionpack/test/controller/http/conditional_get_test.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ConditionalGetHTTPController < ActionController::HTTP
|
||||
before_filter :handle_last_modified_and_etags, :only => :two
|
||||
|
||||
def one
|
||||
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123])
|
||||
render :text => "Hi!"
|
||||
end
|
||||
end
|
||||
|
||||
def two
|
||||
render :text => "Hi!"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_last_modified_and_etags
|
||||
fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
|
||||
end
|
||||
end
|
||||
|
||||
class ConditionalGetHTTPTest < ActionController::TestCase
|
||||
tests ConditionalGetHTTPController
|
||||
|
||||
def setup
|
||||
@last_modified = Time.now.utc.beginning_of_day.httpdate
|
||||
end
|
||||
|
||||
def test_request_with_bang_gets_last_modified
|
||||
get :two
|
||||
assert_equal @last_modified, @response.headers['Last-Modified']
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_request_with_bang_obeys_last_modified
|
||||
@request.if_modified_since = @last_modified
|
||||
get :two
|
||||
assert_response :not_modified
|
||||
end
|
||||
|
||||
def test_last_modified_works_with_less_than_too
|
||||
@request.if_modified_since = 5.years.ago.httpdate
|
||||
get :two
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_request_not_modified
|
||||
@request.if_modified_since = @last_modified
|
||||
get :one
|
||||
assert_equal 304, @response.status.to_i
|
||||
assert_blank @response.body
|
||||
assert_equal @last_modified, @response.headers['Last-Modified']
|
||||
end
|
||||
end
|
27
actionpack/test/controller/http/data_streaming_test.rb
Normal file
27
actionpack/test/controller/http/data_streaming_test.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
module TestHTTPFileUtils
|
||||
def file_name() File.basename(__FILE__) end
|
||||
def file_path() File.expand_path(__FILE__) end
|
||||
def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
|
||||
end
|
||||
|
||||
class DataStreamingHTTPController < ActionController::HTTP
|
||||
include TestHTTPFileUtils
|
||||
|
||||
def one; end
|
||||
def two
|
||||
send_data(file_data, {})
|
||||
end
|
||||
end
|
||||
|
||||
class DataStreamingHTTPTest < ActionController::TestCase
|
||||
include TestHTTPFileUtils
|
||||
tests DataStreamingHTTPController
|
||||
|
||||
def test_data
|
||||
response = process('two')
|
||||
assert_kind_of String, response.body
|
||||
assert_equal file_data, response.body
|
||||
end
|
||||
end
|
20
actionpack/test/controller/http/force_ssl_test.rb
Normal file
20
actionpack/test/controller/http/force_ssl_test.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ForceSSLHTTPController < ActionController::HTTP
|
||||
force_ssl
|
||||
|
||||
def one; end
|
||||
def two
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
class ForceSSLHTTPTest < ActionController::TestCase
|
||||
tests ForceSSLHTTPController
|
||||
|
||||
def test_banana_redirects_to_https
|
||||
get :two
|
||||
assert_response 301
|
||||
assert_equal "https://test.host/force_sslhttp/two", redirect_to_url
|
||||
end
|
||||
end
|
19
actionpack/test/controller/http/redirect_to_test.rb
Normal file
19
actionpack/test/controller/http/redirect_to_test.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class RedirectToHTTPController < ActionController::HTTP
|
||||
def one
|
||||
redirect_to :action => "two"
|
||||
end
|
||||
|
||||
def two; end
|
||||
end
|
||||
|
||||
class RedirectToHTTPTest < ActionController::TestCase
|
||||
tests RedirectToHTTPController
|
||||
|
||||
def test_redirect_to
|
||||
get :one
|
||||
assert_response :redirect
|
||||
assert_equal "http://test.host/redirect_to_http/two", redirect_to_url
|
||||
end
|
||||
end
|
37
actionpack/test/controller/http/renderers_test.rb
Normal file
37
actionpack/test/controller/http/renderers_test.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class Model
|
||||
def to_json(options = {})
|
||||
{ :a => 'b' }.to_json(options)
|
||||
end
|
||||
|
||||
def to_xml(options = {})
|
||||
{ :a => 'b' }.to_xml(options)
|
||||
end
|
||||
end
|
||||
|
||||
class RenderersHTTPController < ActionController::HTTP
|
||||
def one
|
||||
render :json => Model.new
|
||||
end
|
||||
|
||||
def two
|
||||
render :xml => Model.new
|
||||
end
|
||||
end
|
||||
|
||||
class RenderersHTTPTest < ActionController::TestCase
|
||||
tests RenderersHTTPController
|
||||
|
||||
def test_render_json
|
||||
get :one
|
||||
assert_response :success
|
||||
assert_equal({ :a => 'b' }.to_json, @response.body)
|
||||
end
|
||||
|
||||
def test_render_xml
|
||||
get :two
|
||||
assert_response :success
|
||||
assert_equal({ :a => 'b' }.to_xml, @response.body)
|
||||
end
|
||||
end
|
20
actionpack/test/controller/http/url_for_test.rb
Normal file
20
actionpack/test/controller/http/url_for_test.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class UrlForHTTPController < ActionController::HTTP
|
||||
def one; end
|
||||
def two; end
|
||||
end
|
||||
|
||||
class UrlForHTTPTest < ActionController::TestCase
|
||||
tests UrlForHTTPController
|
||||
|
||||
def setup
|
||||
super
|
||||
@request.host = 'www.example.com'
|
||||
end
|
||||
|
||||
def test_url_for
|
||||
get :one
|
||||
assert_equal "http://www.example.com/url_for_http/one", @controller.url_for
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
|
||||
ActiveSupport.on_load(:action_controller) do
|
||||
wrap_parameters format: [:json]
|
||||
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
|
||||
end
|
||||
|
||||
<%- unless options.skip_active_record? -%>
|
||||
|
|
|
@ -130,6 +130,33 @@ module ApplicationTests
|
|||
assert_equal "false", last_response.body
|
||||
end
|
||||
|
||||
test "action_controller http initializes successfully" do
|
||||
app_file "app/controllers/application_controller.rb", <<-RUBY
|
||||
class ApplicationController < ActionController::HTTP
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/controllers/omg_controller.rb", <<-RUBY
|
||||
class OmgController < ApplicationController
|
||||
def show
|
||||
render :json => { :omg => 'omg' }
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
AppTemplate::Application.routes.draw do
|
||||
match "/:controller(/:action)"
|
||||
end
|
||||
RUBY
|
||||
|
||||
require 'rack/test'
|
||||
extend Rack::Test::Methods
|
||||
|
||||
get '/omg/show'
|
||||
assert_equal '{"omg":"omg"}', last_response.body
|
||||
end
|
||||
|
||||
# AD
|
||||
test "action_dispatch extensions are applied to ActionDispatch" do
|
||||
add_to_config "config.action_dispatch.tld_length = 2"
|
||||
|
|
Loading…
Reference in a new issue