mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-24 08:09:13 -05:00
850b042cab
under rack cache we are able to serve 620reqs a second per thin (on my machine) before it 12 (on my machine) reorganised so mini profilers can be cleanly disabled from config file added caching for categories index move production.rb to production.sample.rb
266 lines
7.5 KiB
Ruby
266 lines
7.5 KiB
Ruby
require 'current_user'
|
|
require 'canonical_url'
|
|
require_dependency 'discourse'
|
|
require_dependency 'custom_renderer'
|
|
require 'archetype'
|
|
require_dependency 'rate_limiter'
|
|
|
|
class ApplicationController < ActionController::Base
|
|
include CurrentUser
|
|
|
|
include CanonicalURL::ControllerExtensions
|
|
|
|
serialization_scope :guardian
|
|
|
|
protect_from_forgery
|
|
|
|
before_filter :inject_preview_style
|
|
before_filter :block_if_maintenance_mode
|
|
before_filter :check_restricted_access
|
|
before_filter :authorize_mini_profiler
|
|
before_filter :store_incoming_links
|
|
before_filter :preload_json
|
|
before_filter :check_xhr
|
|
before_filter :set_locale
|
|
|
|
rescue_from Exception do |exception|
|
|
unless [ ActiveRecord::RecordNotFound, ActionController::RoutingError,
|
|
ActionController::UnknownController, AbstractController::ActionNotFound].include? exception.class
|
|
begin
|
|
ErrorLog.report_async!(exception, self, request, current_user)
|
|
rescue
|
|
# dont care give up
|
|
end
|
|
end
|
|
raise
|
|
end
|
|
|
|
|
|
# Some exceptions
|
|
class RenderEmpty < Exception; end
|
|
class NotLoggedIn < Exception; end
|
|
|
|
# Render nothing unless we are an xhr request
|
|
rescue_from RenderEmpty do
|
|
render 'default/empty'
|
|
end
|
|
|
|
# If they hit the rate limiter
|
|
rescue_from RateLimiter::LimitExceeded do |e|
|
|
|
|
time_left = ""
|
|
if e.available_in < 1.minute.to_i
|
|
time_left = I18n.t("rate_limiter.seconds", count: e.available_in)
|
|
elsif e.available_in < 1.hour.to_i
|
|
time_left = I18n.t("rate_limiter.minutes", count: (e.available_in / 1.minute.to_i))
|
|
else
|
|
time_left = I18n.t("rate_limiter.hours", count: (e.available_in / 1.hour.to_i))
|
|
end
|
|
|
|
render json: {errors: [I18n.t("rate_limiter.too_many_requests", time_left: time_left)]}, status: 429
|
|
end
|
|
|
|
rescue_from Discourse::NotLoggedIn do |e|
|
|
raise e if Rails.env.test?
|
|
redirect_to root_path
|
|
end
|
|
|
|
rescue_from Discourse::NotFound do
|
|
if request.format.html?
|
|
# for now do a simple remap, we may look at cleaner ways of doing the render
|
|
raise ActiveRecord::RecordNotFound
|
|
else
|
|
render file: 'public/404', formats: [:html], layout: false, status: 404
|
|
end
|
|
end
|
|
|
|
rescue_from Discourse::InvalidAccess do
|
|
render file: 'public/403', formats: [:html], layout: false, status: 403
|
|
end
|
|
|
|
|
|
def set_locale
|
|
I18n.locale = SiteSetting.default_locale
|
|
end
|
|
|
|
def store_preloaded(key, json)
|
|
@preloaded ||= {}
|
|
# I dislike that there is a gsub as opposed to a gsub!
|
|
# but we can not be mucking with user input, I wonder if there is a way
|
|
# to inject this safty deeper in the library or even in AM serializer
|
|
@preloaded[key] = json.gsub("</", "<\\/")
|
|
end
|
|
|
|
# If we are rendering HTML, preload the session data
|
|
def preload_json
|
|
if request.format && request.format.html?
|
|
if guardian.current_user
|
|
guardian.current_user.sync_notification_channel_position
|
|
end
|
|
|
|
store_preloaded("site", Site.cached_json)
|
|
|
|
if current_user.present?
|
|
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, root: false)))
|
|
end
|
|
store_preloaded("siteSettings", SiteSetting.client_settings_json)
|
|
end
|
|
end
|
|
|
|
|
|
def inject_preview_style
|
|
style = request['preview-style']
|
|
session[:preview_style] = style if style
|
|
end
|
|
|
|
def guardian
|
|
@guardian ||= Guardian.new(current_user)
|
|
end
|
|
|
|
# This is odd, but it seems that in Rails `render json: obj` is about
|
|
# 20% slower than calling MultiJSON.dump ourselves. I'm not sure why
|
|
# Rails doesn't call MultiJson.dump when you pass it json: obj but
|
|
# it seems we don't need whatever Rails is doing.
|
|
def render_serialized(obj, serializer, opts={})
|
|
|
|
# If it's an array, apply the serializer as an each_serializer to the elements
|
|
serializer_opts = {scope: guardian}.merge!(opts)
|
|
if obj.is_a?(Array)
|
|
serializer_opts[:each_serializer] = serializer
|
|
render_json_dump(ActiveModel::ArraySerializer.new(obj, serializer_opts).as_json)
|
|
else
|
|
render_json_dump(serializer.new(obj, serializer_opts).as_json)
|
|
end
|
|
|
|
end
|
|
|
|
def render_json_dump(obj)
|
|
render json: MultiJson.dump(obj)
|
|
end
|
|
|
|
def can_cache_content?
|
|
# Don't cache unless we're in production mode
|
|
return false unless Rails.env.production? || Rails.env == "profile"
|
|
|
|
# Don't cache logged in users
|
|
return false if current_user.present?
|
|
|
|
# Don't cache if there's restricted access
|
|
return false if SiteSetting.access_password.present?
|
|
|
|
true
|
|
end
|
|
|
|
# Our custom cache method
|
|
def discourse_expires_in(time_length)
|
|
return unless can_cache_content?
|
|
expires_in time_length, public: true
|
|
end
|
|
|
|
# Helper method - if no logged in user (anonymous), use Rails' conditional GET
|
|
# support. Should be very fast behind a cache.
|
|
def anonymous_etag(*args)
|
|
if can_cache_content?
|
|
yield if stale?(*args)
|
|
|
|
# Add a one minute expiry
|
|
expires_in 1.minute, public: true
|
|
else
|
|
yield
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def render_json_error(obj)
|
|
if obj.present?
|
|
render json: MultiJson.dump(errors: obj.errors.full_messages), status: 422
|
|
else
|
|
render json: MultiJson.dump(errors: [I18n.t('js.generic_error')]), status: 422
|
|
end
|
|
end
|
|
|
|
def success_json
|
|
{success: 'OK'}
|
|
end
|
|
|
|
def failed_json
|
|
{failed: 'FAILED'}
|
|
end
|
|
|
|
def json_result(obj, opts={})
|
|
if yield(obj)
|
|
|
|
json = success_json
|
|
|
|
# If we were given a serializer, add the class to the json that comes back
|
|
if opts[:serializer].present?
|
|
json[obj.class.name.underscore] = opts[:serializer].new(obj).serializable_hash
|
|
end
|
|
|
|
render json: MultiJson.dump(json)
|
|
else
|
|
render_json_error(obj)
|
|
end
|
|
end
|
|
|
|
def block_if_maintenance_mode
|
|
if Discourse.maintenance_mode?
|
|
if request.format.json?
|
|
render status: 503, json: failed_json.merge(message: I18n.t('site_under_maintenance'))
|
|
else
|
|
render status: 503, file: File.join( Rails.root, 'public', '503.html' ), layout: false
|
|
end
|
|
end
|
|
end
|
|
|
|
def check_restricted_access
|
|
# note current_user is defined in the CurrentUser mixin
|
|
if SiteSetting.access_password.present? && cookies[:_access] != SiteSetting.access_password
|
|
redirect_to request_access_path(return_path: request.fullpath)
|
|
return false
|
|
end
|
|
end
|
|
|
|
def mini_profiler_enabled?
|
|
defined?(Rack::MiniProfiler) && current_user.try(:admin?)
|
|
end
|
|
|
|
def authorize_mini_profiler
|
|
return unless mini_profiler_enabled?
|
|
Rack::MiniProfiler.authorize_request
|
|
end
|
|
|
|
def requires_parameters(*required)
|
|
required.each do |p|
|
|
raise Discourse::InvalidParameters.new(p) unless params.has_key?(p)
|
|
end
|
|
end
|
|
|
|
alias :requires_parameter :requires_parameters
|
|
|
|
def store_incoming_links
|
|
if request.referer.present?
|
|
parsed = URI.parse(request.referer)
|
|
if parsed.host != request.host
|
|
IncomingLink.create(url: request.url, referer: request.referer[0..999])
|
|
end
|
|
end
|
|
end
|
|
|
|
def check_xhr
|
|
unless (controller_name == 'forums' || controller_name == 'user_open_ids')
|
|
# bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying
|
|
if !request.get? && request["api_key"]
|
|
return
|
|
end
|
|
|
|
raise RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?)
|
|
end
|
|
end
|
|
|
|
def ensure_logged_in
|
|
raise Discourse::NotLoggedIn.new unless current_user.present?
|
|
end
|
|
|
|
end
|