diff --git a/.gitignore b/.gitignore index 13667fb..f316c60 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Gemfile.lock gemfiles/*.lock +.idea diff --git a/CHANGELOG.md b/CHANGELOG.md index def0efc..37b174d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## HEAD +- Support non-GET requests via REQUEST_METHOD and setting body via REQUEST_BODY +- Add ability to set Content-Type and Content-Length headers via CONTENT_TYPE and CONTENT_LENGTH env vars (they don't work with HTTP_ variables) - Support ruby-statistics 4.x (https://github.com/zombocom/derailed_benchmarks/pull/238, https://github.com/zombocom/derailed_benchmarks/pull/239) - Repair tests, support ruby-statistics in ruby < 3.0 (https://github.com/zombocom/derailed_benchmarks/pull/241) - Test Rails 7.1 and 7.2 (https://github.com/zombocom/derailed_benchmarks/pull/242) diff --git a/README.md b/README.md index 1aff32f..79374b7 100644 --- a/README.md +++ b/README.md @@ -575,6 +575,22 @@ $ HTTP_AUTHORIZATION="Basic YWRtaW46c2VjcmV0\n" \ PATH_TO_HIT=/foo_secret bundle exec derailed exec perf:ips ``` +The `Content-Type` and `Content-Length` headers are a bit different. To set those, ignore the HTTP_ prefix, use the `CONTENT_TYPE` and `CONTENT_LENGTH` variables. + +### Performing non-GET requests + +If the endpoint being tested is not a GET request, you can set the `REQUEST_METHOD` variable with the HTTP method you want (e.g. POST, PUT, PATCH, DELETE). + +To set the request body, you can use the `REQUEST_BODY`. + +``` +$ REQUEST_METHOD=POST \ + REQUEST_BODY="{\"user\":{\"email\":\"foo@bar.com\",\"password\":\"123456\",\"password_confirmation\":\"123456\"}}" \ + CONTENT_TYPE="application/json" \ + PATH_TO_HIT=/users \ + bundle exec derailed exec perf:test +``` + ### Using a real web server with `USE_SERVER` All tests are run without a webserver (directly using `Rack::Mock` by default), if you want to use a webserver set `USE_SERVER` to a Rack::Server compliant server, such as `webrick`. diff --git a/lib/derailed_benchmarks/load_tasks.rb b/lib/derailed_benchmarks/load_tasks.rb index da15eaf..89c578e 100644 --- a/lib/derailed_benchmarks/load_tasks.rb +++ b/lib/derailed_benchmarks/load_tasks.rb @@ -24,6 +24,9 @@ DERAILED_APP = Rails.application + # Disables CSRF protection because of non-GET requests + DERAILED_APP.config.action_controller.allow_forgery_protection = false + if DERAILED_APP.respond_to?(:initialized?) DERAILED_APP.initialize! unless DERAILED_APP.initialized? else @@ -77,20 +80,42 @@ WARM_COUNT = (ENV['WARM_COUNT'] || 0).to_i TEST_COUNT = (ENV['TEST_COUNT'] || ENV['CNT'] || 1_000).to_i PATH_TO_HIT = ENV["PATH_TO_HIT"] || ENV['ENDPOINT'] || "/" + REQUEST_METHOD = ENV["REQUEST_METHOD"] || "GET" + REQUEST_BODY = ENV["REQUEST_BODY"] + puts "Method: #{REQUEST_METHOD}" puts "Endpoint: #{ PATH_TO_HIT.inspect }" + # See https://www.rubydoc.info/github/rack/rack/file/SPEC#The_Environment + # All HTTP_ variables are accepted in the Rack environment hash, except HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH. + # For those, the HTTP_ prefix has to be removed. HTTP_HEADER_PREFIX = "HTTP_".freeze - RACK_HTTP_HEADERS = ENV.select { |key| key.start_with?(HTTP_HEADER_PREFIX) } + HTTP_HEADER_REGEXP = /^#{HTTP_HEADER_PREFIX}.+|CONTENT_(TYPE|LENGTH)$/ + RACK_ENV_HASH = ENV.select { |key| key =~ HTTP_HEADER_REGEXP } - HTTP_HEADERS = RACK_HTTP_HEADERS.keys.inject({}) do |hash, rack_header_name| + HTTP_HEADERS = RACK_ENV_HASH.keys.inject({}) do |hash, rack_header_name| # e.g. "HTTP_ACCEPT_CHARSET" -> "Accept-Charset" - header_name = rack_header_name[HTTP_HEADER_PREFIX.size..-1].split("_").map(&:downcase).map(&:capitalize).join("-") - hash[header_name] = RACK_HTTP_HEADERS[rack_header_name] + upper_case_header_name = + if rack_header_name.start_with?(HTTP_HEADER_PREFIX) + rack_header_name[HTTP_HEADER_PREFIX.size..-1] + else + rack_header_name + end + + header_name = upper_case_header_name.split("_").map(&:downcase).map(&:capitalize).join("-") + + hash[header_name] = RACK_ENV_HASH[rack_header_name] hash end puts "HTTP headers: #{HTTP_HEADERS}" unless HTTP_HEADERS.empty? CURL_HTTP_HEADER_ARGS = HTTP_HEADERS.map { |http_header_name, value| "-H \"#{http_header_name}: #{value}\"" }.join(" ") + CURL_BODY_ARG = REQUEST_BODY ? "-d '#{REQUEST_BODY}'" : nil + + if REQUEST_METHOD != "GET" && REQUEST_BODY + RACK_ENV_HASH["GATEWAY_INTERFACE"] = "CGI/1.1" + RACK_ENV_HASH[:input] = REQUEST_BODY.dup + puts "Body: #{REQUEST_BODY}" + end require 'rack/test' @@ -108,7 +133,7 @@ sleep 1 def call_app(path = File.join("/", PATH_TO_HIT)) - cmd = "curl #{CURL_HTTP_HEADER_ARGS} 'http://localhost:#{@port}#{path}' -s --fail 2>&1" + cmd = "curl -X #{REQUEST_METHOD} #{CURL_HTTP_HEADER_ARGS} #{CURL_BODY_ARG} -s --fail 'http://localhost:#{@port}#{path}' 2>&1" response = `#{cmd}` unless $?.success? STDERR.puts "Couldn't call app." @@ -126,7 +151,7 @@ def call_app(path = File.join("/", PATH_TO_HIT)) @app = Rack::MockRequest.new(DERAILED_APP) def call_app - response = @app.get(PATH_TO_HIT, RACK_HTTP_HEADERS) + response = @app.request(REQUEST_METHOD, PATH_TO_HIT, RACK_ENV_HASH) if response.status != 200 STDERR.puts "Couldn't call app. Bad request to #{PATH_TO_HIT}! Resulted in #{response.status} status." STDERR.puts "\n\n***RESPONSE BODY***\n\n" diff --git a/test/integration/tasks_test.rb b/test/integration/tasks_test.rb index 7305fb4..bb980a6 100644 --- a/test/integration/tasks_test.rb +++ b/test/integration/tasks_test.rb @@ -134,6 +134,44 @@ def rake(cmd, options = {}) assert_match (/"Cache-Control"=>"no-cache"/), result end + test 'CONTENT_TYPE' do + env = { + "REQUEST_METHOD" => "POST", + "PATH_TO_HIT" => "users", + "CONTENT_TYPE" => "application/json", + "REQUEST_BODY" => '{"user":{"email":"foo@bar.com","password":"123456","password_confirmation":"123456"}}', + "TEST_COUNT" => "2" + } + + result = rake "perf:test", env: env + assert_match 'Body: {"user":{"email":"foo@bar.com","password":"123456","password_confirmation":"123456"}}', result + assert_match 'HTTP headers: {"Content-Type"=>"application/json"}', result + + env["USE_SERVER"] = "webrick" + result = rake "perf:test", env: env + assert_match 'Body: {"user":{"email":"foo@bar.com","password":"123456","password_confirmation":"123456"}}', result + assert_match 'HTTP headers: {"Content-Type"=>"application/json"}', result + end + + test 'REQUEST_METHOD and REQUEST_BODY' do + env = { + "REQUEST_METHOD" => "POST", + "PATH_TO_HIT" => "users", + "REQUEST_BODY" => "user%5Bemail%5D=foo%40bar.com&user%5Bpassword%5D=123456&user%5Bpassword_confirmation%5D=123456", + "TEST_COUNT" => "2" + } + + result = rake "perf:test", env: env + assert_match 'Endpoint: "users"', result + assert_match 'Method: POST', result + assert_match 'Body: user%5Bemail%5D=foo%40bar.com&user%5Bpassword%5D=123456&user%5Bpassword_confirmation%5D=123456', result + + env["USE_SERVER"] = "webrick" + result = rake "perf:test", env: env + assert_match 'Method: POST', result + assert_match 'Body: user%5Bemail%5D=foo%40bar.com&user%5Bpassword%5D=123456&user%5Bpassword_confirmation%5D=123456', result + end + test 'USE_SERVER' do result = rake "perf:test", env: { "USE_SERVER" => 'webrick', "TEST_COUNT" => "2" } assert_match 'Server: "webrick"', result diff --git a/test/rails_app/app/controllers/users_controller.rb b/test/rails_app/app/controllers/users_controller.rb new file mode 100644 index 0000000..297eb88 --- /dev/null +++ b/test/rails_app/app/controllers/users_controller.rb @@ -0,0 +1,13 @@ +class UsersController < ApplicationController + def create + User.create!(user_params) + + head :created + end + + private + + def user_params + params.require(:user).permit(:email, :password, :password_confirmation) + end +end \ No newline at end of file diff --git a/test/rails_app/config/environments/development.rb b/test/rails_app/config/environments/development.rb index 6960cd1..58e4678 100644 --- a/test/rails_app/config/environments/development.rb +++ b/test/rails_app/config/environments/development.rb @@ -11,6 +11,8 @@ # Log error messages when you accidentally call methods on nil. config.whiny_nils = true + config.eager_load = false + # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/test/rails_app/config/environments/production.rb b/test/rails_app/config/environments/production.rb index 137f451..7383a2b 100644 --- a/test/rails_app/config/environments/production.rb +++ b/test/rails_app/config/environments/production.rb @@ -11,6 +11,8 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true + config.eager_load = true + # Specifies the header that your server uses for sending files config.action_dispatch.x_sendfile_header = "X-Sendfile" diff --git a/test/rails_app/config/environments/test.rb b/test/rails_app/config/environments/test.rb index 5f25e53..82ad679 100644 --- a/test/rails_app/config/environments/test.rb +++ b/test/rails_app/config/environments/test.rb @@ -12,6 +12,8 @@ # Log error messages when you accidentally call methods on nil. config.whiny_nils = true + config.eager_load = false + # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/test/rails_app/config/routes.rb b/test/rails_app/config/routes.rb index 256d522..9b004e2 100644 --- a/test/rails_app/config/routes.rb +++ b/test/rails_app/config/routes.rb @@ -56,6 +56,7 @@ get "foo", to: "pages#index" get "foo_secret", to: "pages#secret" + post "users", to: "users#create" get "authenticated", to: "authenticated#index"