Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Updated] Add support for non-GET requests #234

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## HEAD

## 2.2.0 - unreleased
- 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)

## 2.1.2

- Support rails 7 for bundle exec derailed exec mem (https://github.com/zombocom/derailed_benchmarks/pull/212)
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
2 changes: 1 addition & 1 deletion derailed_benchmarks.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "webrick", ">= 0"
gem.add_development_dependency "capybara", "~> 2"
gem.add_development_dependency "m"
gem.add_development_dependency "rails", "> 3", "<= 7"
gem.add_development_dependency "rails", "> 3", "<= 7.0.1"
gem.add_development_dependency "devise", "> 3", "< 6"
end
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 @@ -74,20 +77,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'
require 'rack/file'
Expand All @@ -103,7 +128,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 @@ -121,7 +146,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