Skip to content

Commit

Permalink
Add support for non-GET requests by @mateusg (#234)
Browse files Browse the repository at this point in the history
Copies changes from this PR with merge conflicts resolved: https://github.com/zombocom/derailed_benchmarks/pull/122/files
  • Loading branch information
nbrookie authored Sep 29, 2024
1 parent a99b8c3 commit c19bd61
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

Gemfile.lock
gemfiles/*.lock
.idea
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
37 changes: 31 additions & 6 deletions lib/derailed_benchmarks/load_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'

Expand All @@ -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."
Expand All @@ -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"
Expand Down
38 changes: 38 additions & 0 deletions test/integration/tasks_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions test/rails_app/app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions test/rails_app/config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/rails_app/config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 2 additions & 0 deletions test/rails_app/config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/rails_app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down

0 comments on commit c19bd61

Please sign in to comment.