Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
MSP-Greg | f323d129bc | |
Juanito Fatas | 70600bf261 | |
MSP-Greg | 3d33475dd4 | |
James Prior | 9e131b6098 | |
Memuna Haruna | d27820ff3f | |
Nate Berkopec | 1a3a46aba2 | |
MSP-Greg | 7dba0746d7 | |
Patrik Ragnarsson | 03ed6c8e78 | |
Juanito Fatas | de5bb82227 | |
MSP-Greg | 1520f6bfb0 | |
Andrey Novikov | c975c096f6 | |
Nate Berkopec | a9f659ffee | |
Nate Berkopec | 13ee96d138 | |
Elia Schito | 325ad31ac6 | |
Patrik Ragnarsson | cfb0477350 |
|
@ -1,21 +1,18 @@
|
|||
name: Rack_v2
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
skip_duplicate_runs:
|
||||
uses: ./.github/workflows/skip_duplicate_workflow_runs.yaml
|
||||
|
||||
rack_v2:
|
||||
name: >-
|
||||
Rack_v2: ${{ matrix.os }} ${{ matrix.ruby }}
|
||||
needs: skip_duplicate_runs
|
||||
env:
|
||||
CI: true
|
||||
TESTOPTS: -v
|
||||
|
@ -35,9 +32,11 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: load ruby
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: ruby/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
@ -50,18 +49,23 @@ jobs:
|
|||
|
||||
# fixes 'has a bug that prevents `required_ruby_version`'
|
||||
- name: update rubygems for Ruby 2.4 - 2.5
|
||||
if: contains('2.4 2.5', matrix.ruby)
|
||||
if: |
|
||||
contains('2.4 2.5', matrix.ruby) &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
run: gem update --system 3.3.14 --no-document
|
||||
continue-on-error: true
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: set WERRORFLAG
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
shell: bash
|
||||
run: echo 'PUMA_MAKE_WARNINGS_INTO_ERRORS=true' >> $GITHUB_ENV
|
||||
|
||||
- name: compile
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
run: bundle exec rake compile
|
||||
|
||||
- name: test
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
timeout-minutes: 10
|
||||
run: bundle exec rake test:all
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
name: ragel
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'ext/**'
|
||||
- '.github/workflows/ragel.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ext/**'
|
||||
- '.github/workflows/ragel.yml'
|
||||
workflow_dispatch:
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
skip_duplicate_runs:
|
||||
uses: ./.github/workflows/skip_duplicate_workflow_runs.yaml
|
||||
with:
|
||||
paths: '["ext/**", ".github/workflows/ragel.yml"]'
|
||||
|
||||
ragel:
|
||||
name: >-
|
||||
ragel ${{ matrix.os }} ${{ matrix.ruby }}
|
||||
needs: skip_duplicate_runs
|
||||
env:
|
||||
PUMA_NO_RUBOCOP: true
|
||||
PUMA_TEST_DEBUG: true
|
||||
|
@ -37,15 +34,19 @@ jobs:
|
|||
steps:
|
||||
# windows git will convert \n to \r\n
|
||||
- name: git config
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
if: |
|
||||
startsWith(matrix.os, 'windows') &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: repo checkout
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: load ruby
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: ruby/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
@ -55,6 +56,7 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
|
||||
- name: check ragel generation
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
ragel --version
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
name: Skip Duplicate Workflow Runs
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
paths:
|
||||
description: 'A JSON-array with path patterns'
|
||||
default: '[]'
|
||||
required: false
|
||||
type: string
|
||||
outputs:
|
||||
should_skip:
|
||||
description: "The output from the skip_duplicate_runs job"
|
||||
value: ${{ jobs.skip_duplicate_runs.outputs.should_skip }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
skip_duplicate_runs:
|
||||
name: 'Skip Duplicate Runs'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||
with:
|
||||
paths_ignore: '["**.md"]'
|
||||
paths: ${{ inputs.paths }}
|
||||
concurrent_skipping: 'same_content_newer' # skip newer runs with same content
|
||||
skip_after_successful_duplicate: 'true'
|
|
@ -1,20 +1,18 @@
|
|||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
skip_duplicate_runs:
|
||||
uses: ./.github/workflows/skip_duplicate_workflow_runs.yaml
|
||||
|
||||
rubocop:
|
||||
name: 'Rubocop linting'
|
||||
needs: skip_duplicate_runs
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -28,7 +26,7 @@ jobs:
|
|||
test_mri:
|
||||
name: >-
|
||||
MRI: ${{ matrix.os }} ${{ matrix.ruby }}${{ matrix.no-ssl }}${{ matrix.yjit }}
|
||||
needs: rubocop
|
||||
needs: [rubocop, skip_duplicate_runs]
|
||||
env:
|
||||
CI: true
|
||||
PUMA_TEST_DEBUG: true
|
||||
|
@ -42,7 +40,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-20.04, ubuntu-18.04, macos-10.15, macos-11, macos-12, windows-2022 ]
|
||||
os: [ ubuntu-20.04, ubuntu-18.04, macos-11, macos-12, windows-2022 ]
|
||||
ruby: [ 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, head ]
|
||||
no-ssl: ['']
|
||||
yjit: ['']
|
||||
|
@ -57,21 +55,17 @@ jobs:
|
|||
|
||||
exclude:
|
||||
- { os: windows-2022 , ruby: head }
|
||||
- { os: macos-10.15 , ruby: 2.7 }
|
||||
- { os: macos-10.15 , ruby: '3.0'}
|
||||
- { os: macos-10.15 , ruby: 3.1 }
|
||||
- { os: macos-10.15 , ruby: head }
|
||||
- { os: macos-11 , ruby: 2.4 }
|
||||
- { os: macos-11 , ruby: 2.5 }
|
||||
- { os: macos-12 , ruby: 2.4 }
|
||||
- { os: macos-12 , ruby: 2.5 }
|
||||
- { os: macos-12 , ruby: 2.6 }
|
||||
- { os: macos-11 , ruby: head }
|
||||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: load ruby
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: ruby/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
@ -84,35 +78,45 @@ jobs:
|
|||
|
||||
# fixes 'has a bug that prevents `required_ruby_version`'
|
||||
- name: update rubygems for Ruby 2.4 - 2.5
|
||||
if: contains('2.4 2.5', matrix.ruby)
|
||||
if: |
|
||||
contains('2.4 2.5', matrix.ruby) &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
run: gem update --system 3.3.14 --no-document
|
||||
continue-on-error: true
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Compile Puma without SSL support
|
||||
if: matrix.no-ssl == ' no SSL'
|
||||
if: |
|
||||
(matrix.no-ssl == ' no SSL') &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
shell: bash
|
||||
run: echo 'PUMA_DISABLE_SSL=true' >> $GITHUB_ENV
|
||||
|
||||
- name: set WERRORFLAG
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
shell: bash
|
||||
run: echo 'PUMA_MAKE_WARNINGS_INTO_ERRORS=true' >> $GITHUB_ENV
|
||||
|
||||
- name: compile
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
run: bundle exec rake compile
|
||||
|
||||
- name: Use yjit
|
||||
if: matrix.yjit == ' yjit'
|
||||
if: |
|
||||
(matrix.yjit == ' yjit') &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
shell: bash
|
||||
run: echo 'RUBYOPT=--yjit' >> $GITHUB_ENV
|
||||
|
||||
- name: test
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
timeout-minutes: 10
|
||||
run: bundle exec rake test:all
|
||||
|
||||
test_non_mri:
|
||||
name: >-
|
||||
NON-MRI: ${{ matrix.os }} ${{ matrix.ruby }}${{ matrix.no-ssl }}
|
||||
needs: rubocop
|
||||
needs: [rubocop, skip_duplicate_runs]
|
||||
env:
|
||||
CI: true
|
||||
TESTOPTS: -v
|
||||
|
@ -131,22 +135,27 @@ jobs:
|
|||
- { os: ubuntu-20.04 , ruby: jruby-head, allow-failure: true }
|
||||
- { os: ubuntu-20.04 , ruby: truffleruby, allow-failure: true } # Until https://github.com/oracle/truffleruby/issues/2700 is solved
|
||||
- { os: ubuntu-20.04 , ruby: truffleruby-head, allow-failure: true }
|
||||
- { os: macos-10.15 , ruby: jruby }
|
||||
- { os: macos-10.15 , ruby: truffleruby, allow-failure: true }
|
||||
- { os: macos-11 , ruby: jruby }
|
||||
- { os: macos-11 , ruby: truffleruby, allow-failure: true }
|
||||
- { os: macos-12 , ruby: jruby }
|
||||
- { os: macos-12 , ruby: truffleruby, allow-failure: true }
|
||||
|
||||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: set JAVA_HOME
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
if: |
|
||||
startsWith(matrix.os, 'macos') &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
shell: bash
|
||||
run: |
|
||||
echo JAVA_HOME=$JAVA_HOME_11_X64 >> $GITHUB_ENV
|
||||
|
||||
- name: load ruby, ragel
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
uses: ruby/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
@ -157,22 +166,28 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
|
||||
- name: Compile Puma without SSL support
|
||||
if: matrix.no-ssl == ' no SSL'
|
||||
if: |
|
||||
(matrix.no-ssl == ' no SSL') &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
shell: bash
|
||||
run: echo 'PUMA_DISABLE_SSL=true' >> $GITHUB_ENV
|
||||
|
||||
- name: set WERRORFLAG
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
shell: bash
|
||||
run: echo 'PUMA_MAKE_WARNINGS_INTO_ERRORS=true' >> $GITHUB_ENV
|
||||
|
||||
- name: compile
|
||||
if: ${{ needs.skip_duplicate_runs.outputs.should_skip != 'true' }}
|
||||
run: bundle exec rake compile
|
||||
|
||||
- name: test
|
||||
id: test
|
||||
timeout-minutes: 12
|
||||
continue-on-error: ${{ matrix.allow-failure || false }}
|
||||
if: success() # only run if previous steps have succeeded
|
||||
if: | # only run if previous steps have succeeded
|
||||
success() &&
|
||||
(needs.skip_duplicate_runs.outputs.should_skip != 'true')
|
||||
run: bundle exec rake test:all
|
||||
|
||||
- name: >-
|
||||
|
|
|
@ -37,6 +37,7 @@ Sidekiq 7 (releasing soon) introduces Capsules, which allows you to run a Sideki
|
|||
|
||||
Check the following list to see if you're depending on any of these behaviors:
|
||||
|
||||
1. Configuration constants like `DefaultRackup` removed, see [#2928](https://github.com/puma/puma/pull/2928/files#diff-2dc4e3e83be7fd97cebc482ae07d6a8216944003de82458783fb00b5ae9524c8) for the full list.
|
||||
1. We have changed the names of the following environment variables: `DISABLE_SSL` is now `PUMA_DISABLE_SSL`, `MAKE_WARNINGS_INTO_ERRORS` is now `PUMA_MAKE_WARNINGS_INTO_ERRORS`, and `WAIT_FOR_LESS_BUSY_WORKERS` is now `PUMA_WAIT_FOR_LESS_BUSY_WORKERS`.
|
||||
1. Nakayoshi GC (`nakayoshi_fork` option in config) has been removed without replacement.
|
||||
1. `wait_for_less_busy_worker` is now on by default. If you don't want to use this feature, you must add `wait_for_less_busy_worker false` in your config.
|
||||
|
@ -44,7 +45,6 @@ Check the following list to see if you're depending on any of these behaviors:
|
|||
1. We've removed the following constants: `Puma::StateFile::FIELDS`, `Puma::CLI::KEYS_NOT_TO_PERSIST_IN_STATE` and `Puma::Launcher::KEYS_NOT_TO_PERSIST_IN_STATE`, and `Puma::ControlCLI::COMMANDS`.
|
||||
1. We no longer support Ruby 2.2, 2.3, or JRuby on Java 1.7 or below.
|
||||
1. The behavior of `remote_addr` has changed. When using the set_remote_address header: "header_name" functionality, if the header is not passed, REMOTE_ADDR is now set to the physical peeraddr instead of always being set to 127.0.0.1. When an error occurs preventing the physical peeraddr from being fetched, REMOTE_ADDR is now set to the unspecified source address ('0.0.0.0') instead of to '127.0.0.1'
|
||||
1. If you are creating your own Puma::Server objects, it's initialize signature has changed. In 5.x and below, it was `def initialize(app, events=Events.stdio, options={})`. Now it is `def initialize(app, log_writer=LogWriter.stdio, events=Events.new, options = {})`.
|
||||
|
||||
Then, update your Gemfile:
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ Newbies welcome! We would be happy to help you make your first contribution to a
|
|||
|
||||
Any questions about contributing may be asked in our [Discussions](https://github.com/puma/puma/discussions).
|
||||
|
||||
**If you're nervous, get stuck, need help, or want to know where to start and where you can help**, please don't hesitate to [book 30 minutes with maintainer @nateberkopec here](https://fantastical.app/nateberkopec/weekdays). He is happy to help!
|
||||
|
||||
#### Clone the repo
|
||||
|
||||
Clone the Puma repository:
|
||||
|
@ -152,7 +154,7 @@ We find that values of 4000 or more work well. [Learn more about your file limit
|
|||
|
||||
Puma could use your help in several areas!
|
||||
|
||||
**Don't worry about "claiming an issue". No issues are "claimed" in Puma.** Just start working on it. Once you have a few lines of code, post a draft PR. We are more than happy to help once you have a draft PR up.
|
||||
**Don't worry about "claiming an issue". No issues are "claimed" in Puma.** Just start working on it. The issue tracker is almost always kept updated, so if there is an open issue, it is ready for you to contribute (unless you have questions about how to close issue - then please ask!). Once you have a few lines of code, post a draft PR. We are more than happy to help once you have a draft PR up.
|
||||
|
||||
**New to systems programming? That's ok!** Puma deals with concepts you may not have been familiar with before, like sockets, TCP, UDP, SSL, and Threads. That's ok! You can learn by contributing. Also, see the "Bibliography" section at the end of this document.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## 6.0.0 / 2022-10-XX
|
||||
## 6.0.0 / 2022-10-14
|
||||
|
||||
* Breaking Changes
|
||||
* Dropping Ruby 2.2 and 2.3 support (now 2.4+) ([#2919])
|
||||
|
@ -9,6 +9,9 @@
|
|||
* Prefix all environment variables with `PUMA_` ([#2924], [#2853])
|
||||
* Removed some constants ([#2957], [#2958], [#2959], [#2960])
|
||||
* The following classes are now part of Puma's private API: `Client`, `Cluster::Worker`, `Cluster::Worker`, `HandleRequest`. ([#2988])
|
||||
* Configuration constants like `DefaultRackup` removed ([#2928])
|
||||
* Extracted `LogWriter` from `Events` ([#2798])
|
||||
|
||||
|
||||
* Features
|
||||
* Increase throughput on large (100kb+) response bodies by 3-10x ([#2896], [#2892])
|
||||
|
@ -34,7 +37,6 @@
|
|||
|
||||
* Refactor
|
||||
* log_writer.rb - add internal_write method ([#2888])
|
||||
* [WIP] Refactor: Split out LogWriter from Events (no logic change) ([#2798])
|
||||
* Extract prune_bundler code into it's own class. ([#2797])
|
||||
* Refactor Launcher#run to increase readability (no logic change) ([#2795])
|
||||
* Ruby 3.2 will have native IO#wait_* methods, don't require io/wait ([#2903])
|
||||
|
@ -1915,6 +1917,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|||
* Bugfixes
|
||||
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
|
||||
|
||||
[#2928]:https://github.com/puma/puma/pull/2928 "PR by @nateberkopec, merged 2022-09-10"
|
||||
[#2919]:https://github.com/puma/puma/pull/2919 "PR by @MSP-Greg, merged 2022-08-30"
|
||||
[#2652]:https://github.com/puma/puma/issues/2652 "Issue by @Roguelazer, closed 2022-09-04"
|
||||
[#2653]:https://github.com/puma/puma/pull/2653 "PR by @Roguelazer, closed 2022-03-07"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
Using "3.7.1" as a version example.
|
||||
|
||||
1. `bundle exec rake release`
|
||||
2. `gem push --key github --host https://rubygems.pkg.github.com/puma pkg/puma-VERSION.gem`
|
||||
3. Switch to latest JRuby version
|
||||
4. `rake java gem`
|
||||
5. `gem push pkg/puma-VERSION-java.gem`
|
||||
1. Switch to latest JRuby version
|
||||
1. `rake java gem`
|
||||
1. `gem push pkg/puma-VERSION-java.gem`
|
||||
1. Add release on Github at https://github.com/puma/puma/releases/new
|
||||
|
|
|
@ -250,6 +250,7 @@ module Puma
|
|||
#
|
||||
# * Set the socket backlog depth with +backlog+, default is 1024.
|
||||
# * Set up an SSL certificate with +key+ & +cert+.
|
||||
# * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
|
||||
# * Set whether to optimize for low latency instead of throughput with
|
||||
# +low_latency+, default is to not optimize for low latency. This is done
|
||||
# via +Socket::TCP_NODELAY+.
|
||||
|
@ -259,6 +260,8 @@ module Puma
|
|||
# bind 'unix:///var/run/puma.sock?backlog=512'
|
||||
# @example SSL cert
|
||||
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
|
||||
# @example SSL cert for mutual TLS (mTLS)
|
||||
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
|
||||
# @example Disable optimization for low latency
|
||||
# bind 'tcp://0.0.0.0:9292?low_latency=false'
|
||||
# @example Socket permissions
|
||||
|
|
|
@ -50,7 +50,7 @@ module Puma
|
|||
@input << client
|
||||
@selector.wakeup
|
||||
true
|
||||
rescue ClosedQueueError
|
||||
rescue ClosedQueueError, IOError # Ignore if selector is already closed
|
||||
false
|
||||
end
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ module Puma
|
|||
def handle_request(client, io_buffer, requests)
|
||||
env = client.env
|
||||
socket = client.io # io may be a MiniSSL::Socket
|
||||
app_body = nil
|
||||
|
||||
return false if closed_socket?(socket)
|
||||
|
||||
|
@ -85,14 +86,18 @@ module Puma
|
|||
|
||||
begin
|
||||
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
||||
status, headers, res_body = @thread_pool.with_force_shutdown do
|
||||
status, headers, app_body = @thread_pool.with_force_shutdown do
|
||||
@app.call(env)
|
||||
end
|
||||
else
|
||||
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
||||
status, headers, res_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
||||
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
||||
end
|
||||
|
||||
# app_body needs to always be closed, hold value in case lowlevel_error
|
||||
# is called
|
||||
res_body = app_body
|
||||
|
||||
return :async if client.hijacked
|
||||
|
||||
status = status.to_i
|
||||
|
@ -115,6 +120,17 @@ module Puma
|
|||
status, headers, res_body = lowlevel_error(e, env, 500)
|
||||
end
|
||||
prepare_response(status, headers, res_body, io_buffer, requests, client)
|
||||
ensure
|
||||
io_buffer.reset
|
||||
uncork_socket client.io
|
||||
app_body.close if app_body.respond_to? :close
|
||||
client.tempfile&.unlink
|
||||
after_reply = env[RACK_AFTER_REPLY] || []
|
||||
begin
|
||||
after_reply.each { |o| o.call }
|
||||
rescue StandardError => e
|
||||
@log_writer.debug_error e
|
||||
end unless after_reply.empty?
|
||||
end
|
||||
|
||||
# Assembles the headers and prepares the body for actually sending the
|
||||
|
@ -122,52 +138,50 @@ module Puma
|
|||
#
|
||||
# @param status [Integer] the status returned by the Rack application
|
||||
# @param headers [Hash] the headers returned by the Rack application
|
||||
# @param app_body [Array] the body returned by the Rack application or
|
||||
# @param res_body [Array] the body returned by the Rack application or
|
||||
# a call to `lowlevel_error`
|
||||
# @param io_buffer [Puma::IOBuffer] modified in place
|
||||
# @param requests [Integer] number of inline requests handled
|
||||
# @param client [Puma::Client]
|
||||
# @return [Boolean,:async]
|
||||
def prepare_response(status, headers, app_body, io_buffer, requests, client)
|
||||
def prepare_response(status, headers, res_body, io_buffer, requests, client)
|
||||
env = client.env
|
||||
socket = client.io
|
||||
|
||||
after_reply = env[RACK_AFTER_REPLY] || []
|
||||
socket = client.io
|
||||
|
||||
return false if closed_socket?(socket)
|
||||
|
||||
resp_info = str_headers(env, status, headers, app_body, io_buffer, requests, client)
|
||||
resp_info = str_headers(env, status, headers, res_body, io_buffer, requests, client)
|
||||
|
||||
# below converts app_body into body, dependent on app_body's characteristics, and
|
||||
# resp_info[:content_length] will be set if it can be determined
|
||||
if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
|
||||
if app_body.respond_to?(:to_ary)
|
||||
if res_body.respond_to?(:to_ary)
|
||||
length = 0
|
||||
if array_body = app_body.to_ary
|
||||
if array_body = res_body.to_ary
|
||||
body = array_body.map { |part| length += part.bytesize; part }
|
||||
elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
|
||||
length = app_body.size
|
||||
elsif app_body.respond_to?(:each)
|
||||
elsif res_body.is_a?(::File) && res_body.respond_to?(:size)
|
||||
length = res_body.size
|
||||
elsif res_body.respond_to?(:each)
|
||||
body = []
|
||||
app_body.each { |part| length += part.bytesize; body << part }
|
||||
res_body.each { |part| length += part.bytesize; body << part }
|
||||
end
|
||||
resp_info[:content_length] = length
|
||||
elsif app_body.is_a?(File) && app_body.respond_to?(:size)
|
||||
resp_info[:content_length] = app_body.size
|
||||
body = app_body
|
||||
elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
||||
File.readable?(fn = app_body.to_path)
|
||||
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
||||
resp_info[:content_length] = res_body.size
|
||||
body = res_body
|
||||
elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
||||
File.readable?(fn = res_body.to_path)
|
||||
body = File.open fn, 'rb'
|
||||
resp_info[:content_length] = body.size
|
||||
else
|
||||
body = app_body
|
||||
body = res_body
|
||||
end
|
||||
elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
||||
File.readable?(fn = app_body.to_path)
|
||||
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
||||
File.readable?(fn = res_body.to_path)
|
||||
body = File.open fn, 'rb'
|
||||
resp_info[:content_length] = body.size
|
||||
else
|
||||
body = app_body
|
||||
body = res_body
|
||||
end
|
||||
|
||||
line_ending = LINE_END
|
||||
|
@ -175,8 +189,8 @@ module Puma
|
|||
content_length = resp_info[:content_length]
|
||||
keep_alive = resp_info[:keep_alive]
|
||||
|
||||
if app_body && !app_body.respond_to?(:each)
|
||||
response_hijack = app_body
|
||||
if res_body && !res_body.respond_to?(:each)
|
||||
response_hijack = res_body
|
||||
else
|
||||
response_hijack = resp_info[:response_hijack]
|
||||
end
|
||||
|
@ -210,18 +224,6 @@ module Puma
|
|||
|
||||
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
||||
keep_alive
|
||||
ensure
|
||||
io_buffer.reset
|
||||
resp_info = nil
|
||||
uncork_socket socket
|
||||
app_body.close if app_body.respond_to? :close
|
||||
client.tempfile&.unlink
|
||||
|
||||
begin
|
||||
after_reply.each { |o| o.call }
|
||||
rescue StandardError => e
|
||||
@log_writer.debug_error e
|
||||
end unless after_reply.empty?
|
||||
end
|
||||
|
||||
# @param env [Hash] see Puma::Client#env, from request
|
||||
|
|
|
@ -184,7 +184,7 @@ Minitest::Test.include TestSkips
|
|||
|
||||
class Minitest::Test
|
||||
|
||||
REPO_NAME = ENV['GITHUB_REPOSITORY'] ? ENV['GITHUB_REPOSITORY'][/[^\/]+\z/] : 'puma'
|
||||
PROJECT_ROOT = File.dirname(__dir__)
|
||||
|
||||
def self.run(reporter, options = {}) # :nodoc:
|
||||
prove_it!
|
||||
|
@ -259,3 +259,16 @@ module AggregatedResults
|
|||
end
|
||||
end
|
||||
Minitest::SummaryReporter.prepend AggregatedResults
|
||||
|
||||
module TestTempFile
|
||||
require "tempfile"
|
||||
def tempfile_create(basename, data, mode: File::BINARY)
|
||||
fio = Tempfile.create(basename, mode: mode)
|
||||
fio.write data
|
||||
fio.flush
|
||||
fio.rewind
|
||||
@ios << fio
|
||||
fio
|
||||
end
|
||||
end
|
||||
Minitest::Test.include TestTempFile
|
||||
|
|
|
@ -10,7 +10,7 @@ class TestBundlePruner < Minitest::Test
|
|||
dirs = bundle_pruner.send(:paths_to_require_after_prune)
|
||||
|
||||
assert_equal(2, dirs.length)
|
||||
assert_match(%r{#{REPO_NAME}/lib$}, dirs[0]) # lib dir
|
||||
assert_equal(File.join(PROJECT_ROOT, "lib"), dirs[0]) # lib dir
|
||||
assert_match(%r{puma-#{Puma::Const::PUMA_VERSION}$}, dirs[1]) # native extension dir
|
||||
refute_match(%r{gems/minitest-[\d.]+/lib$}, dirs[2])
|
||||
end
|
||||
|
@ -21,7 +21,7 @@ class TestBundlePruner < Minitest::Test
|
|||
dirs = bundle_pruner([], ['minitest']).send(:paths_to_require_after_prune)
|
||||
|
||||
assert_equal(3, dirs.length)
|
||||
assert_match(%r{#{REPO_NAME}/lib$}, dirs[0]) # lib dir
|
||||
assert_equal(File.join(PROJECT_ROOT, "lib"), dirs[0]) # lib dir
|
||||
assert_match(%r{puma-#{Puma::Const::PUMA_VERSION}$}, dirs[1]) # native extension dir
|
||||
assert_match(%r{gems/minitest-[\d.]+/lib$}, dirs[2]) # minitest dir
|
||||
end
|
||||
|
|
|
@ -112,7 +112,7 @@ class TestIntegrationSingle < TestIntegration
|
|||
rejected_curl_wait_thread.join
|
||||
|
||||
assert_match(/Slept 10/, curl_stdout.read)
|
||||
assert_match(/Connection refused/, rejected_curl_stderr.read)
|
||||
assert_match(/Connection refused|Couldn't connect to server/, rejected_curl_stderr.read)
|
||||
|
||||
Process.wait(@server.pid)
|
||||
@server.close unless @server.closed?
|
||||
|
|
|
@ -5,6 +5,11 @@ require "net/http"
|
|||
require "nio"
|
||||
require "ipaddr"
|
||||
|
||||
class WithoutBacktraceError < StandardError
|
||||
def backtrace; nil; end
|
||||
def message; "no backtrace error"; end
|
||||
end
|
||||
|
||||
class TestPumaServer < Minitest::Test
|
||||
parallelize_me!
|
||||
|
||||
|
@ -26,6 +31,7 @@ class TestPumaServer < Minitest::Test
|
|||
@ios.each do |io|
|
||||
begin
|
||||
io.close if io.respond_to?(:close) && !io.closed?
|
||||
File.unlink io.path if io.is_a? File
|
||||
rescue Errno::EBADF
|
||||
ensure
|
||||
io = nil
|
||||
|
@ -53,6 +59,11 @@ class TestPumaServer < Minitest::Test
|
|||
header
|
||||
end
|
||||
|
||||
# only for shorter bodies!
|
||||
def send_http_and_sysread(req)
|
||||
send_http(req).sysread 2_048
|
||||
end
|
||||
|
||||
def send_http_and_read(req)
|
||||
send_http(req).read
|
||||
end
|
||||
|
@ -140,28 +151,33 @@ class TestPumaServer < Minitest::Test
|
|||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
|
||||
|
||||
assert_equal "Hello World", data.split("\n").last
|
||||
assert_equal "Hello World", data.split("\r\n\r\n", 2).last
|
||||
end
|
||||
|
||||
def test_file_body
|
||||
random_bytes = SecureRandom.random_bytes(4096 * 32)
|
||||
path = Tempfile.open { |f| f.path }
|
||||
File.binwrite path, random_bytes
|
||||
|
||||
server_run { |env| [200, {}, File.open(path, 'rb')] }
|
||||
tf = tempfile_create("test_file_body", random_bytes)
|
||||
|
||||
server_run { |env| [200, {}, tf] }
|
||||
|
||||
data = +''
|
||||
skt = send_http("GET / HTTP/1.1\r\nHost: [::ffff:127.0.0.1]:#{@port}\r\n\r\n")
|
||||
data << skt.sysread(65_536) while skt.wait_readable(0.1)
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\nHost: [::ffff:127.0.0.1]:9292\r\n\r\n"
|
||||
ary = data.split("\r\n\r\n", 2)
|
||||
|
||||
assert_equal random_bytes.bytesize, ary.last.bytesize
|
||||
assert_equal random_bytes, ary.last
|
||||
ensure
|
||||
File.delete(path) if File.exist?(path)
|
||||
tf.close
|
||||
end
|
||||
|
||||
def test_file_to_path
|
||||
random_bytes = SecureRandom.random_bytes(4096 * 32)
|
||||
path = Tempfile.open { |f| f.path }
|
||||
File.binwrite path, random_bytes
|
||||
|
||||
tf = tempfile_create("test_file_to_path", random_bytes)
|
||||
path = tf.path
|
||||
|
||||
obj = Object.new
|
||||
obj.singleton_class.send(:define_method, :to_path) { path }
|
||||
|
@ -169,16 +185,17 @@ class TestPumaServer < Minitest::Test
|
|||
|
||||
server_run { |env| [200, {}, obj] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\nHost: [::ffff:127.0.0.1]:9292\r\n\r\n"
|
||||
data = +''
|
||||
skt = send_http("GET / HTTP/1.1\r\nHost: [::ffff:127.0.0.1]:#{@port}\r\n\r\n")
|
||||
data << skt.sysread(65_536) while skt.wait_readable(0.1)
|
||||
ary = data.split("\r\n\r\n", 2)
|
||||
|
||||
assert_equal random_bytes.bytesize, ary.last.bytesize
|
||||
assert_equal random_bytes, ary.last
|
||||
ensure
|
||||
File.delete(path) if File.exist?(path)
|
||||
tf.close
|
||||
end
|
||||
|
||||
|
||||
|
||||
def test_proper_stringio_body
|
||||
data = nil
|
||||
|
||||
|
@ -407,36 +424,54 @@ EOF
|
|||
assert_match(/{}\n$/, data)
|
||||
end
|
||||
|
||||
def test_lowlevel_error_message
|
||||
@server = Puma::Server.new @app, @events, {log_writer: @log_writer, :force_shutdown_after => 2}
|
||||
class ArrayClose < Array
|
||||
attr_reader :is_closed
|
||||
def closed?
|
||||
@is_closed
|
||||
end
|
||||
|
||||
server_run do
|
||||
def close
|
||||
@is_closed = true
|
||||
end
|
||||
end
|
||||
|
||||
# returns status as an array, which throws lowlevel error
|
||||
def test_lowlevel_error_body_close
|
||||
app_body = ArrayClose.new(['lowlevel_error'])
|
||||
|
||||
server_run(log_writer: @log_writer, :force_shutdown_after => 2) do
|
||||
[[0,1], {}, app_body]
|
||||
end
|
||||
|
||||
data = send_http_and_sysread "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
assert_includes data, 'HTTP/1.0 500 Internal Server Error'
|
||||
assert_includes data, "Puma caught this error: undefined method `to_i' for [0, 1]:Array"
|
||||
refute_includes data, 'lowlevel_error'
|
||||
sleep 0.1 unless ::Puma::IS_MRI
|
||||
assert app_body.closed?
|
||||
end
|
||||
|
||||
def test_lowlevel_error_message
|
||||
server_run(log_writer: @log_writer, :force_shutdown_after => 2) do
|
||||
raise NoMethodError, "Oh no an error"
|
||||
end
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
data = send_http_and_sysread "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
assert_match(/HTTP\/1.0 500 Internal Server Error/, data)
|
||||
assert_includes data, 'HTTP/1.0 500 Internal Server Error'
|
||||
assert_match(/Puma caught this error: Oh no an error.*\(NoMethodError\).*test\/test_puma_server.rb/m, data)
|
||||
end
|
||||
|
||||
class WithoutBacktraceError < StandardError
|
||||
def backtrace; nil; end
|
||||
def message; "no backtrace error"; end
|
||||
def class; "WithoutBacktraceError"; end
|
||||
end
|
||||
|
||||
def test_lowlevel_error_message_without_backtrace
|
||||
@server = Puma::Server.new @app, @events, {log_writer: @log_writer, :force_shutdown_after => 2}
|
||||
|
||||
server_run do
|
||||
server_run(log_writer: @log_writer, :force_shutdown_after => 2) do
|
||||
raise WithoutBacktraceError.new
|
||||
end
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.1\r\n\r\n"
|
||||
assert_match(/HTTP\/1.1 500 Internal Server Error/, data)
|
||||
assert_match(/Puma caught this error: no backtrace error.*\(WithoutBacktraceError\)/, data)
|
||||
assert_match(/<no backtrace available>/, data)
|
||||
data = send_http_and_sysread "GET / HTTP/1.1\r\n\r\n"
|
||||
assert_includes data, 'HTTP/1.1 500 Internal Server Error'
|
||||
assert_includes data, 'Puma caught this error: no backtrace error (WithoutBacktraceError)'
|
||||
assert_includes data, '<no backtrace available>'
|
||||
end
|
||||
|
||||
def test_force_shutdown_error_default
|
||||
|
@ -1433,7 +1468,7 @@ EOF
|
|||
|
||||
# TODO: it would be great to test a connection from a non-localhost IP, but we can't really do that. For
|
||||
# now, at least test that it doesn't return garbage.
|
||||
remote_addr = send_http_and_read("GET / HTTP/1.1\r\n\r\n").split("\r\n").last
|
||||
remote_addr = send_http_and_sysread("GET / HTTP/1.1\r\n\r\n").split("\r\n").last
|
||||
assert_equal @host, remote_addr
|
||||
end
|
||||
end
|
||||
|
|
|
@ -210,6 +210,43 @@ class TestRackServer < Minitest::Test
|
|||
assert_equal str_ary_bytes, content_length
|
||||
end
|
||||
|
||||
def test_hijack_body_close
|
||||
available = true
|
||||
@server.app = ->(env) {
|
||||
if available
|
||||
available = false
|
||||
hijack_lambda = ->(io) {
|
||||
io.syswrite 'hijacked'
|
||||
io.close
|
||||
}
|
||||
[200, { 'Content-Type' => 'text/plain', 'rack.hijack' => hijack_lambda},
|
||||
::Rack::BodyProxy.new([]) { available = true }]
|
||||
else
|
||||
[500, { 'Content-Type' => 'text/plain' }, ['incorrect']]
|
||||
end
|
||||
}
|
||||
|
||||
@server.run
|
||||
|
||||
socket1 = TCPSocket.new "127.0.0.1", @port
|
||||
socket1.syswrite "GET / HTTP/1.1\r\n\r\n"
|
||||
sleep 0.25 if Puma::IS_WINDOWS || !Puma::IS_MRI
|
||||
resp1 = socket1.sysread 1_024
|
||||
|
||||
sleep 0.01 # time for close block to be called ?
|
||||
|
||||
socket2 = TCPSocket.new "127.0.0.1", @port
|
||||
socket2.syswrite "GET / HTTP/1.1\r\n\r\n"
|
||||
sleep 0.25 if Puma::IS_WINDOWS || !Puma::IS_MRI
|
||||
resp2 = socket2.sysread 1_024
|
||||
|
||||
assert_operator resp1, :end_with?, 'hijacked'
|
||||
assert_operator resp2, :end_with?, 'hijacked'
|
||||
|
||||
socket1.close
|
||||
socket2.close
|
||||
end
|
||||
|
||||
def test_common_logger
|
||||
log = StringIO.new
|
||||
|
||||
|
@ -259,5 +296,4 @@ class TestRackServer < Minitest::Test
|
|||
assert_includes headers.downcase, TRANSFER_ENCODING_CHUNKED
|
||||
assert_equal STR_1KB * 10, resp_body
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue