X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/ac4cdfc2577b9d25ccbc9ac5d8f0333a81102367..de8324b3fbbf3f67f0f61c162f5895e8dcd3142d:/services/api/app/controllers/application_controller.rb diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb index 27c4bc852f..3a3f6d3345 100644 --- a/services/api/app/controllers/application_controller.rb +++ b/services/api/app/controllers/application_controller.rb @@ -27,6 +27,7 @@ class ApplicationController < ActionController::Base ERROR_ACTIONS = [:render_error, :render_not_found] + before_filter :set_cors_headers before_filter :respond_with_json_by_default before_filter :remote_ip before_filter :load_read_auths @@ -76,7 +77,7 @@ class ApplicationController < ActionController::Base end def show - render json: @object.as_api_response(nil, select: @select) + send_json @object.as_api_response(nil, select: @select) end def create @@ -178,12 +179,30 @@ class ApplicationController < ActionController::Base err[:error_token] = [Time.now.utc.to_i, "%08x" % rand(16 ** 8)].join("+") status = err.delete(:status) || 422 logger.error "Error #{err[:error_token]}: #{status}" - render json: err, status: status + send_json err, status: status + end + + def send_json response, opts={} + # The obvious render(json: ...) forces a slow JSON encoder. See + # #3021 and commit logs. Might be fixed in Rails 4.1. + render({ + text: Oj.dump(response, mode: :compat).html_safe, + content_type: 'application/json' + }.merge opts) + end + + def self.limit_index_columns_read + # This method returns a list of column names. + # If an index request reads that column from the database, + # find_objects_for_index will only fetch objects until it reads + # max_index_database_read bytes of data from those columns. + [] end def find_objects_for_index @objects ||= model_class.readable_by(*@read_users) apply_where_limit_order_params + limit_database_read if (action_name == "index") end def apply_filters model_class=nil @@ -195,8 +214,9 @@ class ApplicationController < ActionController::Base end end - def apply_where_limit_order_params *args - apply_filters *args + def apply_where_limit_order_params model_class=nil + model_class ||= self.model_class + apply_filters model_class ar_table_name = @objects.table_name if @where.is_a? Hash and @where.any? @@ -257,11 +277,8 @@ class ApplicationController < ActionController::Base # Map attribute names in @select to real column names, resolve # those to fully-qualified SQL column names, and pass the # resulting string to the select method. - api_column_map = model_class.attributes_required_columns - columns_list = @select. - flat_map { |attr| api_column_map[attr] }. - uniq. - map { |s| "#{table_name}.#{ActiveRecord::Base.connection.quote_column_name s}" } + columns_list = model_class.columns_for_attributes(@select). + map { |s| "#{ar_table_name}.#{ActiveRecord::Base.connection.quote_column_name s}" } @objects = @objects.select(columns_list.join(", ")) end @@ -278,6 +295,30 @@ class ApplicationController < ActionController::Base @objects = @objects.uniq(@distinct) if not @distinct.nil? end + def limit_database_read + limit_columns = self.class.limit_index_columns_read + limit_columns &= model_class.columns_for_attributes(@select) if @select + return if limit_columns.empty? + model_class.transaction do + limit_query = @objects. + except(:select). + select("(%s) as read_length" % + limit_columns.map { |s| "octet_length(#{s})" }.join(" + ")) + new_limit = 0 + read_total = 0 + limit_query.find_each do |record| + new_limit += 1 + read_total += record.read_length.to_i + break if ((read_total >= Rails.configuration.max_index_database_read) or + (new_limit >= @limit)) + end + @limit = new_limit + @objects = @objects.limit(@limit) + # Force @objects to run its query inside this transaction. + @objects.each { |_| break } + end + end + def resource_attrs return @attrs if @attrs @attrs = params[resource_name] @@ -346,6 +387,13 @@ class ApplicationController < ActionController::Base end end + def set_cors_headers + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, PUT, POST, DELETE' + response.headers['Access-Control-Allow-Headers'] = 'Authorization' + response.headers['Access-Control-Max-Age'] = '86486400' + end + def respond_with_json_by_default html_index = request.accepts.index(Mime::HTML) if html_index.nil? or request.accepts[0...html_index].include?(Mime::JSON) @@ -419,8 +467,8 @@ class ApplicationController < ActionController::Base end accept_param_as_json :reader_tokens, Array - def render_list - @object_list = { + def object_list + list = { :kind => "arvados##{(@response_resource_name || resource_name).camelize(:lower)}List", :etag => "", :self_link => "", @@ -429,11 +477,15 @@ class ApplicationController < ActionController::Base :items => @objects.as_api_response(nil, {select: @select}) } if @objects.respond_to? :except - @object_list[:items_available] = @objects. + list[:items_available] = @objects. except(:limit).except(:offset). count(:id, distinct: true) end - render json: @object_list + list + end + + def render_list + send_json object_list end def remote_ip @@ -451,18 +503,18 @@ class ApplicationController < ActionController::Base (self.class.send "_#{params[:action]}_requires_parameters" rescue {}). each do |key, info| if info[:required] and not params.include?(key) - raise ArgumentError("#{key} parameter is required") + raise ArgumentError.new("#{key} parameter is required") elsif info[:type] == 'boolean' # Make sure params[key] is either true or false -- not a # string, not nil, etc. if not params.include?(key) params[key] = info[:default] - elsif [false, 'false'].include? params[key] + elsif [false, 'false', '0', 0].include? params[key] params[key] = false - elsif [true, 'true'].include? params[key] + elsif [true, 'true', '1', 1].include? params[key] params[key] = true else - raise TypeError("#{key} parameter must be a boolean, true or false") + raise TypeError.new("#{key} parameter must be a boolean, true or false") end end end @@ -475,6 +527,7 @@ class ApplicationController < ActionController::Base type: "boolean", description: "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.", location: "query", + required: false, default: false } }