unicorn 5.5.3 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +104 -59
- data/ISSUES +4 -1
- data/README +7 -0
- data/ext/unicorn_http/extconf.rb +5 -0
- data/ext/unicorn_http/unicorn_http.rl +42 -4
- data/lib/unicorn/configurator.rb +10 -0
- data/lib/unicorn/http_request.rb +11 -0
- data/lib/unicorn/http_server.rb +35 -3
- data/t/GNUmakefile +3 -72
- data/test/test_helper.rb +22 -4
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/test/unit/test_server.rb +74 -0
- data/unicorn.gemspec +6 -5
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3166eb1eac760d39cac35a6fc46332d6a6a2ee75bdf594118fddb3c6a959f6d
|
4
|
+
data.tar.gz: 3e18599fef201efdd79c67eb126aab35787c196054313a727d6bb184f830587e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 356f0c951cee2e69de8e49169f2c9560e6fead9fe759bd0af5a51097888a23794eb17175ff5393f07d1fadec5678215b494b688cdeeadb8a95c37201f3808a92
|
7
|
+
data.tar.gz: ffa1f8781231ac559157310d2d235340522c6c1cccf69be3d87d432659d2a2a7a39d744a84306c1f4292a3f6b36141cc1bb80497f1a9047902ec0d592f77871b
|
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -10,6 +10,7 @@ RAGEL = ragel
|
|
10
10
|
RSYNC = rsync
|
11
11
|
OLDDOC = olddoc
|
12
12
|
RDOC = rdoc
|
13
|
+
INSTALL = install
|
13
14
|
|
14
15
|
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
|
15
16
|
@./GIT-VERSION-GEN
|
@@ -25,7 +26,38 @@ endif
|
|
25
26
|
|
26
27
|
RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
|
27
28
|
|
28
|
-
|
29
|
+
# we should never package more than one ext to avoid DSO proliferation:
|
30
|
+
# https://udrepper.livejournal.com/8790.html
|
31
|
+
ext := $(firstword $(wildcard ext/*))
|
32
|
+
|
33
|
+
ragel: $(ext)/unicorn_http.c
|
34
|
+
|
35
|
+
rl_files := $(wildcard $(ext)/*.rl)
|
36
|
+
ragel: $(ext)/unicorn_http.c
|
37
|
+
$(ext)/unicorn_http.c: $(rl_files)
|
38
|
+
cd $(@D) && $(RAGEL) unicorn_http.rl -C $(RLFLAGS) -o $(@F)
|
39
|
+
ext_pfx := test/$(RUBY_ENGINE)-$(RUBY_VERSION)
|
40
|
+
tmp_bin := $(ext_pfx)/bin
|
41
|
+
ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h)
|
42
|
+
ext_src := $(sort $(wildcard $(ext)/*.c) $(ext_h) $(ext)/unicorn_http.c)
|
43
|
+
ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src))
|
44
|
+
ext_dir := $(ext_pfx)/$(ext)
|
45
|
+
$(ext)/extconf.rb: $(wildcard $(ext)/*.h)
|
46
|
+
@>>$@
|
47
|
+
$(ext_dir) $(tmp_bin) man/man1 doc/man1 pkg t/trash:
|
48
|
+
@mkdir -p $@
|
49
|
+
$(ext_pfx)/$(ext)/%: $(ext)/% | $(ext_dir)
|
50
|
+
$(INSTALL) -m 644 $< $@
|
51
|
+
$(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_h) | $(ext_dir)
|
52
|
+
$(RM) -f $(@D)/*.o
|
53
|
+
cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb $(EXTCONF_ARGS)
|
54
|
+
ext_sfx := _ext.$(DLEXT)
|
55
|
+
ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT))
|
56
|
+
$(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile
|
57
|
+
$(MAKE) -C $(@D)
|
58
|
+
lib := $(CURDIR)/lib:$(CURDIR)/$(ext_pfx)/$(ext)
|
59
|
+
http build: $(ext_dl)
|
60
|
+
$(ext_pfx)/$(ext)/unicorn_http.c: ext/unicorn_http/unicorn_http.c
|
29
61
|
|
30
62
|
# dunno how to implement this as concisely in Ruby, and hell, I love awk
|
31
63
|
awk_slow := awk '/def test_/{print FILENAME"--"$$2".n"}' 2>/dev/null
|
@@ -37,44 +69,21 @@ T := $(filter-out $(slow_tests), $(wildcard test/*/test*.rb))
|
|
37
69
|
T_n := $(shell $(awk_slow) $(slow_tests))
|
38
70
|
T_log := $(subst .rb,$(log_suffix),$(T))
|
39
71
|
T_n_log := $(subst .n,$(log_suffix),$(T_n))
|
40
|
-
test_prefix = $(CURDIR)/test/$(RUBY_ENGINE)-$(RUBY_VERSION)
|
41
72
|
|
42
|
-
ext := ext/unicorn_http
|
43
|
-
c_files := $(ext)/unicorn_http.c $(ext)/httpdate.c $(wildcard $(ext)/*.h)
|
44
|
-
rl_files := $(wildcard $(ext)/*.rl)
|
45
73
|
base_bins := unicorn unicorn_rails
|
46
74
|
bins := $(addprefix bin/, $(base_bins))
|
47
75
|
man1_rdoc := $(addsuffix _1, $(base_bins))
|
48
76
|
man1_bins := $(addsuffix .1, $(base_bins))
|
49
77
|
man1_paths := $(addprefix man/man1/, $(man1_bins))
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
ragel: $(ext)/unicorn_http.c
|
54
|
-
$(ext)/unicorn_http.c: $(rl_files)
|
55
|
-
cd $(@D) && $(RAGEL) unicorn_http.rl -C $(RLFLAGS) -o $(@F)
|
56
|
-
$(ext)/Makefile: $(ext)/extconf.rb $(c_files)
|
57
|
-
cd $(@D) && $(RUBY) extconf.rb
|
58
|
-
$(ext)/unicorn_http.$(DLEXT): $(ext)/Makefile
|
59
|
-
$(MAKE) -C $(@D)
|
60
|
-
http: $(ext)/unicorn_http.$(DLEXT)
|
78
|
+
tmp_bins = $(addprefix $(tmp_bin)/, unicorn unicorn_rails)
|
79
|
+
pid := $(shell echo $$PPID)
|
61
80
|
|
62
|
-
|
63
|
-
|
64
|
-
|
81
|
+
$(tmp_bin)/%: bin/% | $(tmp_bin)
|
82
|
+
$(INSTALL) -m 755 $< $@.$(pid)
|
83
|
+
$(MRI) -i -p -e '$$_.gsub!(%r{^#!.*$$},"#!$(ruby_bin)")' $@.$(pid)
|
84
|
+
mv $@.$(pid) $@
|
65
85
|
|
66
|
-
|
67
|
-
$(test_prefix)/.stamp: $(inst_deps)
|
68
|
-
mkdir -p $(test_prefix)/.ccache
|
69
|
-
tar cf - $(inst_deps) GIT-VERSION-GEN | \
|
70
|
-
(cd $(test_prefix) && tar xf -)
|
71
|
-
$(MAKE) -C $(test_prefix) clean
|
72
|
-
$(MAKE) -C $(test_prefix) http-install shebang RUBY="$(RUBY)"
|
73
|
-
> $@
|
74
|
-
|
75
|
-
# this is only intended to be run within $(test_prefix)
|
76
|
-
shebang: $(bins)
|
77
|
-
$(MRI) -i -p -e '$$_.gsub!(%r{^#!.*$$},"#!$(ruby_bin)")' $^
|
86
|
+
bins: $(tmp_bins)
|
78
87
|
|
79
88
|
t_log := $(T_log) $(T_n_log)
|
80
89
|
test: $(T) $(T_n)
|
@@ -83,15 +92,54 @@ test: $(T) $(T_n)
|
|
83
92
|
|
84
93
|
test-exec: $(wildcard test/exec/test_*.rb)
|
85
94
|
test-unit: $(wildcard test/unit/test_*.rb)
|
86
|
-
$(slow_tests): $(
|
95
|
+
$(slow_tests): $(ext_dl)
|
87
96
|
@$(MAKE) $(shell $(awk_slow) $@)
|
88
97
|
|
89
98
|
# ensure we can require just the HTTP parser without the rest of unicorn
|
90
|
-
test-require: $(
|
91
|
-
$(RUBY) --disable-gems -I$(ext) -runicorn_http -e Unicorn
|
99
|
+
test-require: $(ext_dl)
|
100
|
+
$(RUBY) --disable-gems -I$(ext_pfx)/$(ext) -runicorn_http -e Unicorn
|
101
|
+
|
102
|
+
test_prereq := $(tmp_bins) $(ext_dl)
|
103
|
+
|
104
|
+
SH_TEST_OPTS =
|
105
|
+
ifdef V
|
106
|
+
ifeq ($(V),2)
|
107
|
+
SH_TEST_OPTS += --trace
|
108
|
+
else
|
109
|
+
SH_TEST_OPTS += --verbose
|
110
|
+
endif
|
111
|
+
endif
|
92
112
|
|
93
|
-
|
94
|
-
|
113
|
+
# do we trust Ruby behavior to be stable? some tests are
|
114
|
+
# (mostly) POSIX sh (not bash or ksh93, so no "set -o pipefail"
|
115
|
+
# TRACER = strace -f -o $(t_pfx).strace -s 100000
|
116
|
+
# TRACER = /usr/bin/time -o $(t_pfx).time
|
117
|
+
t_pfx = trash/$@-$(RUBY_ENGINE)-$(RUBY_VERSION)
|
118
|
+
T_sh = $(wildcard t/t[0-9][0-9][0-9][0-9]-*.sh)
|
119
|
+
$(T_sh): export RUBY := $(RUBY)
|
120
|
+
$(T_sh): export PATH := $(CURDIR)/$(tmp_bin):$(PATH)
|
121
|
+
$(T_sh): export RUBYLIB := $(lib):$(RUBYLIB)
|
122
|
+
$(T_sh): dep $(test_prereq) t/random_blob t/trash/.gitignore
|
123
|
+
cd t && $(TRACER) $(SHELL) $(SH_TEST_OPTS) $(@F) $(TEST_OPTS)
|
124
|
+
|
125
|
+
t/trash/.gitignore : | t/trash
|
126
|
+
echo '*' >$@
|
127
|
+
|
128
|
+
dependencies := socat curl
|
129
|
+
deps := $(addprefix t/.dep+,$(dependencies))
|
130
|
+
$(deps): dep_bin = $(lastword $(subst +, ,$@))
|
131
|
+
$(deps):
|
132
|
+
@which $(dep_bin) > $@.$(pid) 2>/dev/null || :
|
133
|
+
@test -s $@.$(pid) || \
|
134
|
+
{ echo >&2 "E '$(dep_bin)' not found in PATH=$(PATH)"; exit 1; }
|
135
|
+
@mv $@.$(pid) $@
|
136
|
+
dep: $(deps)
|
137
|
+
|
138
|
+
t/random_blob:
|
139
|
+
dd if=/dev/urandom bs=1M count=30 of=$@.$(pid)
|
140
|
+
mv $@.$(pid) $@
|
141
|
+
|
142
|
+
test-integration: $(T_sh)
|
95
143
|
|
96
144
|
check: test-require test test-integration
|
97
145
|
test-all: check
|
@@ -122,16 +170,16 @@ run_test = $(quiet_pre) \
|
|
122
170
|
|
123
171
|
%.n: arg = $(subst .n,,$(subst --, -n ,$@))
|
124
172
|
%.n: t = $(subst .n,$(log_suffix),$@)
|
125
|
-
%.n: export PATH := $(
|
126
|
-
%.n: export RUBYLIB := $(
|
127
|
-
%.n: $(
|
173
|
+
%.n: export PATH := $(CURDIR)/$(tmp_bin):$(PATH)
|
174
|
+
%.n: export RUBYLIB := $(lib):$(RUBYLIB)
|
175
|
+
%.n: $(test_prereq)
|
128
176
|
$(run_test)
|
129
177
|
|
130
178
|
$(T): arg = $@
|
131
179
|
$(T): t = $(subst .rb,$(log_suffix),$@)
|
132
|
-
$(T): export PATH := $(
|
133
|
-
$(T): export RUBYLIB := $(
|
134
|
-
$(T): $(
|
180
|
+
$(T): export PATH := $(CURDIR)/$(tmp_bin):$(PATH)
|
181
|
+
$(T): export RUBYLIB := $(lib):$(RUBYLIB)
|
182
|
+
$(T): $(test_prereq)
|
135
183
|
$(run_test)
|
136
184
|
|
137
185
|
install: $(bins) $(ext)/unicorn_http.c
|
@@ -152,18 +200,16 @@ clean:
|
|
152
200
|
-$(MAKE) -C $(ext) clean
|
153
201
|
$(RM) $(ext)/Makefile
|
154
202
|
$(RM) $(setup_rb_files) $(t_log)
|
155
|
-
$(RM) -r $(
|
156
|
-
$(RM) $(
|
203
|
+
$(RM) -r $(ext_pfx) man t/trash
|
204
|
+
$(RM) $(html1)
|
157
205
|
|
158
206
|
man1 := $(addprefix Documentation/, unicorn.1 unicorn_rails.1)
|
159
207
|
html1 := $(addsuffix .html, $(man1))
|
160
|
-
man :
|
161
|
-
|
162
|
-
install -m 644 $(man1) man/man1
|
208
|
+
man : $(man1) | man/man1
|
209
|
+
$(INSTALL) -m 644 $(man1) man/man1
|
163
210
|
|
164
|
-
html : $(html1)
|
165
|
-
|
166
|
-
install -m 644 $(html1) doc/man1
|
211
|
+
html : $(html1) | doc/man1
|
212
|
+
$(INSTALL) -m 644 $(html1) doc/man1
|
167
213
|
|
168
214
|
%.1.html: %.1
|
169
215
|
$(OLDDOC) man2html -o $@ ./$<
|
@@ -187,10 +233,10 @@ doc: .document $(ext)/unicorn_http.c man html .olddoc.yml $(PLACEHOLDERS)
|
|
187
233
|
$(OLDDOC) prepare
|
188
234
|
$(RDOC) -f dark216
|
189
235
|
$(OLDDOC) merge
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
236
|
+
$(INSTALL) -m 644 COPYING doc/COPYING
|
237
|
+
$(INSTALL) -m 644 NEWS.atom.xml doc/NEWS.atom.xml
|
238
|
+
$(INSTALL) -m 644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/
|
239
|
+
$(INSTALL) -m 644 $(man1_paths) doc/
|
194
240
|
tar cf - $$(git ls-files examples/) | (cd doc && tar xf -)
|
195
241
|
|
196
242
|
# publishes docs to https://yhbt.net/unicorn/
|
@@ -199,7 +245,8 @@ publish_doc:
|
|
199
245
|
$(MAKE) doc
|
200
246
|
$(MAKE) doc_gz
|
201
247
|
chmod 644 $$(find doc -type f)
|
202
|
-
$(RSYNC) -av doc/ yhbt.net:/srv/yhbt/unicorn/
|
248
|
+
$(RSYNC) -av doc/ yhbt.net:/srv/yhbt/unicorn/ \
|
249
|
+
--exclude index.html* --exclude created.rid*
|
203
250
|
git ls-files | xargs touch
|
204
251
|
|
205
252
|
# Create gzip variants of the same timestamp as the original so nginx
|
@@ -231,9 +278,8 @@ gem: $(pkggem)
|
|
231
278
|
install-gem: $(pkggem)
|
232
279
|
gem install --local $(CURDIR)/$<
|
233
280
|
|
234
|
-
$(pkggem): .manifest fix-perms
|
281
|
+
$(pkggem): .manifest fix-perms | pkg
|
235
282
|
gem build $(rfpackage).gemspec
|
236
|
-
mkdir -p pkg
|
237
283
|
mv $(@F) $@
|
238
284
|
|
239
285
|
$(pkgtgz): distdir = $(basename $@)
|
@@ -264,5 +310,4 @@ check-warnings:
|
|
264
310
|
do $(RUBY) --disable-gems -d -W2 -c \
|
265
311
|
$$i; done) | grep -v '^Syntax OK$$' || :
|
266
312
|
|
267
|
-
.PHONY: .FORCE-GIT-VERSION-FILE doc $(T) $(slow_tests) man
|
268
|
-
.PHONY: test-install
|
313
|
+
.PHONY: .FORCE-GIT-VERSION-FILE doc $(T) $(slow_tests) man $(T_sh) clean
|
data/ISSUES
CHANGED
@@ -76,15 +76,18 @@ document distributed with git) on guidelines for patch submission.
|
|
76
76
|
* public: mailto:unicorn-public@yhbt.net
|
77
77
|
* nntp://news.gmane.io/gmane.comp.lang.ruby.unicorn.general
|
78
78
|
* nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
79
|
+
* imaps://news.public-inbox.org/inbox.comp.lang.ruby.unicorn.0
|
79
80
|
* https://yhbt.net/unicorn-public/
|
80
81
|
* http://ou63pmih66umazou.onion/unicorn-public/
|
81
82
|
|
82
83
|
Mailing list subscription is optional, so Cc: all participants.
|
83
84
|
|
84
|
-
You can follow along via NNTP (read-only):
|
85
|
+
You can follow along via NNTP or IMAP (read-only):
|
85
86
|
|
86
87
|
nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
87
88
|
nntp://news.gmane.io/gmane.comp.lang.ruby.unicorn.general
|
89
|
+
imaps://news.public-inbox.org/inbox.comp.lang.ruby.unicorn.0
|
90
|
+
imap://ou63pmih66umazou.onion/inbox.comp.lang.ruby.unicorn.0
|
88
91
|
|
89
92
|
Or Atom feeds:
|
90
93
|
|
data/README
CHANGED
@@ -136,10 +136,17 @@ requests) go to the mailing list/newsgroup. See the ISSUES document for
|
|
136
136
|
information on the {mailing list}[mailto:unicorn-public@yhbt.net].
|
137
137
|
|
138
138
|
The mailing list is archived at https://yhbt.net/unicorn-public/
|
139
|
+
|
139
140
|
Read-only NNTP access is available at:
|
140
141
|
nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn and
|
141
142
|
nntp://news.gmane.io/gmane.comp.lang.ruby.unicorn.general
|
142
143
|
|
144
|
+
Read-only IMAP access is also avaialble at:
|
145
|
+
imaps://news.public-inbox.org/inbox.comp.lang.ruby.unicorn.0 and
|
146
|
+
imap://ou63pmih66umazou.onion/inbox.comp.lang.ruby.unicorn.0
|
147
|
+
AUTH=ANONYMOUS mechanism is supported, as is any username+password
|
148
|
+
combination.
|
149
|
+
|
143
150
|
For the latest on unicorn releases, you may also finger us at
|
144
151
|
unicorn@yhbt.net or check our NEWS page (and subscribe to our Atom
|
145
152
|
feed).
|
data/ext/unicorn_http/extconf.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
|
+
unless RUBY_VERSION < '3.1'
|
5
|
+
warn "Unicorn was only tested against MRI up to 3.0.\n" \
|
6
|
+
"It might not properly work with #{RUBY_VERSION}"
|
7
|
+
end
|
8
|
+
|
4
9
|
have_macro("SIZEOF_OFF_T", "ruby.h") or check_sizeof("off_t", "sys/types.h")
|
5
10
|
have_macro("SIZEOF_SIZE_T", "ruby.h") or check_sizeof("size_t", "sys/types.h")
|
6
11
|
have_macro("SIZEOF_LONG", "ruby.h") or check_sizeof("long", "sys/types.h")
|
@@ -62,7 +62,8 @@ struct http_parser {
|
|
62
62
|
} len;
|
63
63
|
};
|
64
64
|
|
65
|
-
static ID id_set_backtrace;
|
65
|
+
static ID id_set_backtrace, id_is_chunked_p;
|
66
|
+
static VALUE cHttpParser;
|
66
67
|
|
67
68
|
#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
|
68
69
|
# define my_hash_clear(h) (void)rb_hash_clear(h)
|
@@ -220,6 +221,19 @@ static void write_cont_value(struct http_parser *hp,
|
|
220
221
|
rb_str_buf_cat(hp->cont, vptr, end + 1);
|
221
222
|
}
|
222
223
|
|
224
|
+
static int is_chunked(VALUE v)
|
225
|
+
{
|
226
|
+
/* common case first */
|
227
|
+
if (STR_CSTR_CASE_EQ(v, "chunked"))
|
228
|
+
return 1;
|
229
|
+
|
230
|
+
/*
|
231
|
+
* call Ruby function in unicorn/http_request.rb to deal with unlikely
|
232
|
+
* comma-delimited case
|
233
|
+
*/
|
234
|
+
return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
|
235
|
+
}
|
236
|
+
|
223
237
|
static void write_value(struct http_parser *hp,
|
224
238
|
const char *buffer, const char *p)
|
225
239
|
{
|
@@ -246,7 +260,9 @@ static void write_value(struct http_parser *hp,
|
|
246
260
|
f = uncommon_field(field, flen);
|
247
261
|
} else if (f == g_http_connection) {
|
248
262
|
hp_keepalive_connection(hp, v);
|
249
|
-
} else if (f == g_content_length) {
|
263
|
+
} else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) {
|
264
|
+
if (hp->len.content)
|
265
|
+
parser_raise(eHttpParserError, "Content-Length already set");
|
250
266
|
hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
|
251
267
|
if (hp->len.content < 0)
|
252
268
|
parser_raise(eHttpParserError, "invalid Content-Length");
|
@@ -254,9 +270,30 @@ static void write_value(struct http_parser *hp,
|
|
254
270
|
HP_FL_SET(hp, HASBODY);
|
255
271
|
hp_invalid_if_trailer(hp);
|
256
272
|
} else if (f == g_http_transfer_encoding) {
|
257
|
-
if (
|
273
|
+
if (is_chunked(v)) {
|
274
|
+
if (HP_FL_TEST(hp, CHUNKED))
|
275
|
+
/*
|
276
|
+
* RFC 7230 3.3.1:
|
277
|
+
* A sender MUST NOT apply chunked more than once to a message body
|
278
|
+
* (i.e., chunking an already chunked message is not allowed).
|
279
|
+
*/
|
280
|
+
parser_raise(eHttpParserError, "Transfer-Encoding double chunked");
|
281
|
+
|
258
282
|
HP_FL_SET(hp, CHUNKED);
|
259
283
|
HP_FL_SET(hp, HASBODY);
|
284
|
+
|
285
|
+
/* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */
|
286
|
+
hp->len.content = 0;
|
287
|
+
} else if (HP_FL_TEST(hp, CHUNKED)) {
|
288
|
+
/*
|
289
|
+
* RFC 7230 3.3.3, point 3 states:
|
290
|
+
* If a Transfer-Encoding header field is present in a request and
|
291
|
+
* the chunked transfer coding is not the final encoding, the
|
292
|
+
* message body length cannot be determined reliably; the server
|
293
|
+
* MUST respond with the 400 (Bad Request) status code and then
|
294
|
+
* close the connection.
|
295
|
+
*/
|
296
|
+
parser_raise(eHttpParserError, "invalid Transfer-Encoding");
|
260
297
|
}
|
261
298
|
hp_invalid_if_trailer(hp);
|
262
299
|
} else if (f == g_http_trailer) {
|
@@ -931,7 +968,7 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
931
968
|
|
932
969
|
void Init_unicorn_http(void)
|
933
970
|
{
|
934
|
-
VALUE mUnicorn
|
971
|
+
VALUE mUnicorn;
|
935
972
|
|
936
973
|
mUnicorn = rb_define_module("Unicorn");
|
937
974
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
@@ -991,5 +1028,6 @@ void Init_unicorn_http(void)
|
|
991
1028
|
#ifndef HAVE_RB_HASH_CLEAR
|
992
1029
|
id_clear = rb_intern("clear");
|
993
1030
|
#endif
|
1031
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
994
1032
|
}
|
995
1033
|
#undef SET_GLOBAL
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -53,6 +53,7 @@ class Unicorn::Configurator
|
|
53
53
|
server.logger.info("worker=#{worker.nr} ready")
|
54
54
|
},
|
55
55
|
:pid => nil,
|
56
|
+
:early_hints => false,
|
56
57
|
:worker_exec => false,
|
57
58
|
:preload_app => false,
|
58
59
|
:check_client_connection => false,
|
@@ -276,6 +277,15 @@ def default_middleware(bool)
|
|
276
277
|
set_bool(:default_middleware, bool)
|
277
278
|
end
|
278
279
|
|
280
|
+
# sets whether to enable the proposed early hints Rack API.
|
281
|
+
# If enabled, Rails 5.2+ will automatically send a 103 Early Hint
|
282
|
+
# for all the `javascript_include_tag` and `stylesheet_link_tag`
|
283
|
+
# in your response. See: https://api.rubyonrails.org/v5.2/classes/ActionDispatch/Request.html#method-i-send_early_hints
|
284
|
+
# See also https://tools.ietf.org/html/rfc8297
|
285
|
+
def early_hints(bool)
|
286
|
+
set_bool(:early_hints, bool)
|
287
|
+
end
|
288
|
+
|
279
289
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
280
290
|
# current set. This is for the global listener pool shared by all
|
281
291
|
# worker processes. For per-worker listeners, see the after_fork example
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -188,4 +188,15 @@ def write_http_header(socket) # :nodoc:
|
|
188
188
|
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
189
189
|
end
|
190
190
|
end
|
191
|
+
|
192
|
+
# called by ext/unicorn_http/unicorn_http.rl via rb_funcall
|
193
|
+
def self.is_chunked?(v) # :nodoc:
|
194
|
+
vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
|
195
|
+
if vals.pop == 'chunked'.freeze
|
196
|
+
return true unless vals.include?('chunked'.freeze)
|
197
|
+
raise Unicorn::HttpParserError, 'double chunked', []
|
198
|
+
end
|
199
|
+
return false unless vals.include?('chunked'.freeze)
|
200
|
+
raise Unicorn::HttpParserError, 'chunked not last', []
|
201
|
+
end
|
191
202
|
end
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -15,7 +15,7 @@ class Unicorn::HttpServer
|
|
15
15
|
:before_fork, :after_fork, :before_exec,
|
16
16
|
:listener_opts, :preload_app,
|
17
17
|
:orig_app, :config, :ready_pipe, :user,
|
18
|
-
:default_middleware
|
18
|
+
:default_middleware, :early_hints
|
19
19
|
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
20
20
|
|
21
21
|
attr_reader :pid, :logger
|
@@ -588,6 +588,25 @@ def handle_error(client, e)
|
|
588
588
|
rescue
|
589
589
|
end
|
590
590
|
|
591
|
+
def e103_response_write(client, headers)
|
592
|
+
response = if @request.response_start_sent
|
593
|
+
"103 Early Hints\r\n"
|
594
|
+
else
|
595
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
596
|
+
end
|
597
|
+
|
598
|
+
headers.each_pair do |k, vs|
|
599
|
+
next if !vs || vs.empty?
|
600
|
+
values = vs.to_s.split("\n".freeze)
|
601
|
+
values.each do |v|
|
602
|
+
response << "#{k}: #{v}\r\n"
|
603
|
+
end
|
604
|
+
end
|
605
|
+
response << "\r\n".freeze
|
606
|
+
response << "HTTP/1.1 ".freeze if @request.response_start_sent
|
607
|
+
client.write(response)
|
608
|
+
end
|
609
|
+
|
591
610
|
def e100_response_write(client, env)
|
592
611
|
# We use String#freeze to avoid allocations under Ruby 2.1+
|
593
612
|
# Not many users hit this code path, so it's better to reduce the
|
@@ -602,7 +621,17 @@ def e100_response_write(client, env)
|
|
602
621
|
# once a client is accepted, it is processed in its entirety here
|
603
622
|
# in 3 easy steps: read request, call app, write app response
|
604
623
|
def process_client(client)
|
605
|
-
|
624
|
+
env = @request.read(client)
|
625
|
+
|
626
|
+
if early_hints
|
627
|
+
env["rack.early_hints"] = lambda do |headers|
|
628
|
+
e103_response_write(client, headers)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
env["rack.after_reply"] = []
|
633
|
+
|
634
|
+
status, headers, body = @app.call(env)
|
606
635
|
|
607
636
|
begin
|
608
637
|
return if @request.hijacked?
|
@@ -624,6 +653,8 @@ def process_client(client)
|
|
624
653
|
end
|
625
654
|
rescue => e
|
626
655
|
handle_error(client, e)
|
656
|
+
ensure
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
627
658
|
end
|
628
659
|
|
629
660
|
def nuke_listeners!(readers)
|
@@ -686,6 +717,7 @@ def worker_loop(worker)
|
|
686
717
|
trap(:USR1) { nr = -65536 }
|
687
718
|
|
688
719
|
ready = readers.dup
|
720
|
+
nr_listeners = readers.size
|
689
721
|
@after_worker_ready.call(self, worker)
|
690
722
|
|
691
723
|
begin
|
@@ -708,7 +740,7 @@ def worker_loop(worker)
|
|
708
740
|
# we're probably reasonably busy, so avoid calling select()
|
709
741
|
# and do a speculative non-blocking accept() on ready listeners
|
710
742
|
# before we sleep again in select().
|
711
|
-
|
743
|
+
if nr == nr_listeners
|
712
744
|
tmp = ready.dup
|
713
745
|
redo
|
714
746
|
end
|
data/t/GNUmakefile
CHANGED
@@ -1,74 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# there used to be more, here, but we stopped relying on recursive make
|
2
2
|
all::
|
3
|
+
$(MAKE) -C .. test-integration
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
RUBY = ruby
|
7
|
-
RAKE = rake
|
8
|
-
-include ../local.mk
|
9
|
-
ifeq ($(RUBY_VERSION),)
|
10
|
-
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
11
|
-
endif
|
12
|
-
|
13
|
-
ifeq ($(RUBY_VERSION),)
|
14
|
-
$(error unable to detect RUBY_VERSION)
|
15
|
-
endif
|
16
|
-
|
17
|
-
RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
|
18
|
-
export RUBY_ENGINE
|
19
|
-
|
20
|
-
MYLIBS := $(RUBYLIB)
|
21
|
-
|
22
|
-
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
23
|
-
|
24
|
-
all:: $(T)
|
25
|
-
|
26
|
-
# can't rely on "set -o pipefail" since we don't require bash or ksh93 :<
|
27
|
-
t_pfx = trash/$@-$(RUBY_ENGINE)-$(RUBY_VERSION)
|
28
|
-
TEST_OPTS =
|
29
|
-
# TRACER = strace -f -o $(t_pfx).strace -s 100000
|
30
|
-
# TRACER = /usr/bin/time -o $(t_pfx).time
|
31
|
-
|
32
|
-
ifdef V
|
33
|
-
ifeq ($(V),2)
|
34
|
-
TEST_OPTS += --trace
|
35
|
-
else
|
36
|
-
TEST_OPTS += --verbose
|
37
|
-
endif
|
38
|
-
endif
|
39
|
-
|
40
|
-
random_blob:
|
41
|
-
dd if=/dev/urandom bs=1M count=30 of=$@.$(pid)
|
42
|
-
mv $@.$(pid) $@
|
43
|
-
|
44
|
-
$(T): random_blob
|
45
|
-
|
46
|
-
dependencies := socat curl
|
47
|
-
deps := $(addprefix .dep+,$(dependencies))
|
48
|
-
$(deps): dep_bin = $(lastword $(subst +, ,$@))
|
49
|
-
$(deps):
|
50
|
-
@which $(dep_bin) > $@.$(pid) 2>/dev/null || :
|
51
|
-
@test -s $@.$(pid) || \
|
52
|
-
{ echo >&2 "E '$(dep_bin)' not found in PATH=$(PATH)"; exit 1; }
|
53
|
-
@mv $@.$(pid) $@
|
54
|
-
dep: $(deps)
|
55
|
-
|
56
|
-
test_prefix := $(CURDIR)/../test/$(RUBY_ENGINE)-$(RUBY_VERSION)
|
57
|
-
$(test_prefix)/.stamp:
|
58
|
-
$(MAKE) -C .. test-install
|
59
|
-
|
60
|
-
$(T): export RUBY := $(RUBY)
|
61
|
-
$(T): export RAKE := $(RAKE)
|
62
|
-
$(T): export PATH := $(test_prefix)/bin:$(PATH)
|
63
|
-
$(T): export RUBYLIB := $(test_prefix)/lib:$(MYLIBS)
|
64
|
-
$(T): dep $(test_prefix)/.stamp trash/.gitignore
|
65
|
-
$(TRACER) $(SHELL) $(SH_TEST_OPTS) $@ $(TEST_OPTS)
|
66
|
-
|
67
|
-
trash/.gitignore:
|
68
|
-
mkdir -p $(@D)
|
69
|
-
echo '*' > $@
|
70
|
-
|
71
|
-
clean:
|
72
|
-
$(RM) -r trash/*
|
73
|
-
|
74
|
-
.PHONY: $(T) clean
|
5
|
+
.PHONY: all
|
data/test/test_helper.rb
CHANGED
@@ -34,16 +34,33 @@
|
|
34
34
|
Debugger.start
|
35
35
|
end
|
36
36
|
|
37
|
+
unless RUBY_VERSION < '3.1'
|
38
|
+
warn "Unicorn was only tested against MRI up to 3.0.\n" \
|
39
|
+
"It might not properly work with #{RUBY_VERSION}"
|
40
|
+
end
|
41
|
+
|
37
42
|
def redirect_test_io
|
38
43
|
orig_err = STDERR.dup
|
39
44
|
orig_out = STDOUT.dup
|
40
|
-
|
41
|
-
|
45
|
+
new_out = File.open("test_stdout.#$$.log", "a")
|
46
|
+
new_err = File.open("test_stderr.#$$.log", "a")
|
47
|
+
new_out.sync = new_err.sync = true
|
48
|
+
|
49
|
+
if tail = ENV['TAIL'] # "tail -F" if GNU, "tail -f" otherwise
|
50
|
+
require 'shellwords'
|
51
|
+
cmd = tail.shellsplit
|
52
|
+
cmd << new_out.path
|
53
|
+
cmd << new_err.path
|
54
|
+
pid = Process.spawn(*cmd, { 1 => 2, :pgroup => true })
|
55
|
+
sleep 0.1 # wait for tail(1) to startup
|
56
|
+
end
|
57
|
+
STDERR.reopen(new_err)
|
58
|
+
STDOUT.reopen(new_out)
|
42
59
|
STDERR.sync = STDOUT.sync = true
|
43
60
|
|
44
61
|
at_exit do
|
45
|
-
File.unlink(
|
46
|
-
File.unlink(
|
62
|
+
File.unlink(new_out.path) rescue nil
|
63
|
+
File.unlink(new_err.path) rescue nil
|
47
64
|
end
|
48
65
|
|
49
66
|
begin
|
@@ -51,6 +68,7 @@ def redirect_test_io
|
|
51
68
|
ensure
|
52
69
|
STDERR.reopen(orig_err)
|
53
70
|
STDOUT.reopen(orig_out)
|
71
|
+
Process.kill(:TERM, pid) if pid
|
54
72
|
end
|
55
73
|
end
|
56
74
|
|
@@ -11,6 +11,20 @@ def setup
|
|
11
11
|
@parser = HttpParser.new
|
12
12
|
end
|
13
13
|
|
14
|
+
# RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
|
15
|
+
# but "chunked" must be last if used
|
16
|
+
def test_is_chunked
|
17
|
+
[ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
|
18
|
+
assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
|
19
|
+
end
|
20
|
+
[ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
|
21
|
+
assert HttpParser.is_chunked?(x)
|
22
|
+
end
|
23
|
+
[ 'gzip', 'xhunked', 'xchunked' ].each do |x|
|
24
|
+
assert !HttpParser.is_chunked?(x)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
14
28
|
def test_parser_max_len
|
15
29
|
assert_raises(RangeError) do
|
16
30
|
HttpParser.max_header_len = 0xffffffff + 1
|
@@ -566,6 +580,73 @@ def test_invalid_content_length
|
|
566
580
|
end
|
567
581
|
end
|
568
582
|
|
583
|
+
def test_duplicate_content_length
|
584
|
+
str = "PUT / HTTP/1.1\r\n" \
|
585
|
+
"Content-Length: 1\r\n" \
|
586
|
+
"Content-Length: 9\r\n" \
|
587
|
+
"\r\n"
|
588
|
+
assert_raises(HttpParserError) { @parser.headers({}, str) }
|
589
|
+
end
|
590
|
+
|
591
|
+
def test_chunked_overrides_content_length
|
592
|
+
order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
|
593
|
+
%w(a b).each do |x|
|
594
|
+
str = "PUT /#{x} HTTP/1.1\r\n" \
|
595
|
+
"#{order.join("\r\n")}" \
|
596
|
+
"\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
|
597
|
+
order.reverse!
|
598
|
+
env = @parser.headers({}, str)
|
599
|
+
assert_nil @parser.content_length
|
600
|
+
assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
|
601
|
+
assert_equal '666', env['CONTENT_LENGTH'],
|
602
|
+
'Content-Length logged so the app can log a possible client bug/attack'
|
603
|
+
@parser.filter_body(dst = '', str)
|
604
|
+
assert_equal 'helloworld', dst
|
605
|
+
@parser.parse # handle the non-existent trailer
|
606
|
+
assert @parser.next?
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def test_chunked_order_good
|
611
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
612
|
+
"Transfer-Encoding: gzip\r\n" \
|
613
|
+
"Transfer-Encoding: chunked\r\n" \
|
614
|
+
"\r\n"
|
615
|
+
env = @parser.headers({}, str)
|
616
|
+
assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
|
617
|
+
assert_nil @parser.content_length
|
618
|
+
|
619
|
+
@parser.clear
|
620
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
621
|
+
"Transfer-Encoding: gzip, chunked\r\n" \
|
622
|
+
"\r\n"
|
623
|
+
env = @parser.headers({}, str)
|
624
|
+
assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
|
625
|
+
assert_nil @parser.content_length
|
626
|
+
end
|
627
|
+
|
628
|
+
def test_chunked_order_bad
|
629
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
630
|
+
"Transfer-Encoding: chunked\r\n" \
|
631
|
+
"Transfer-Encoding: gzip\r\n" \
|
632
|
+
"\r\n"
|
633
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
634
|
+
end
|
635
|
+
|
636
|
+
def test_double_chunked
|
637
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
638
|
+
"Transfer-Encoding: chunked\r\n" \
|
639
|
+
"Transfer-Encoding: chunked\r\n" \
|
640
|
+
"\r\n"
|
641
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
642
|
+
|
643
|
+
@parser.clear
|
644
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
645
|
+
"Transfer-Encoding: chunked,chunked\r\n" \
|
646
|
+
"\r\n"
|
647
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
648
|
+
end
|
649
|
+
|
569
650
|
def test_backtrace_is_empty
|
570
651
|
begin
|
571
652
|
@parser.headers({}, "AAADFSFDSFD\r\n\r\n")
|
data/test/unit/test_server.rb
CHANGED
@@ -23,6 +23,34 @@ def call(env)
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
class TestEarlyHintsHandler
|
27
|
+
def call(env)
|
28
|
+
while env['rack.input'].read(4096)
|
29
|
+
end
|
30
|
+
env['rack.early_hints'].call(
|
31
|
+
"Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload"
|
32
|
+
)
|
33
|
+
[200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class TestRackAfterReply
|
38
|
+
def initialize
|
39
|
+
@called = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def call(env)
|
43
|
+
while env['rack.input'].read(4096)
|
44
|
+
end
|
45
|
+
|
46
|
+
env["rack.after_reply"] << -> { @called = true }
|
47
|
+
|
48
|
+
[200, { 'Content-Type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
|
49
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
50
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
26
54
|
|
27
55
|
class WebServerTest < Test::Unit::TestCase
|
28
56
|
|
@@ -84,6 +112,52 @@ def test_preload_app_config
|
|
84
112
|
tmp.close!
|
85
113
|
end
|
86
114
|
|
115
|
+
def test_early_hints
|
116
|
+
teardown
|
117
|
+
redirect_test_io do
|
118
|
+
@server = HttpServer.new(TestEarlyHintsHandler.new,
|
119
|
+
:listeners => [ "127.0.0.1:#@port"],
|
120
|
+
:early_hints => true)
|
121
|
+
@server.start
|
122
|
+
end
|
123
|
+
|
124
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
125
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
126
|
+
|
127
|
+
responses = sock.read(4096)
|
128
|
+
assert_match %r{\AHTTP/1.[01] 103\b}, responses
|
129
|
+
assert_match %r{^Link: </style\.css>}, responses
|
130
|
+
assert_match %r{^Link: </script\.js>}, responses
|
131
|
+
|
132
|
+
assert_match %r{^HTTP/1.[01] 200\b}, responses
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_after_reply
|
136
|
+
teardown
|
137
|
+
|
138
|
+
redirect_test_io do
|
139
|
+
@server = HttpServer.new(TestRackAfterReply.new,
|
140
|
+
:listeners => [ "127.0.0.1:#@port"])
|
141
|
+
@server.start
|
142
|
+
end
|
143
|
+
|
144
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
145
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
146
|
+
|
147
|
+
responses = sock.read(4096)
|
148
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
149
|
+
assert_match %r{^after_reply_called: false}, responses
|
150
|
+
|
151
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
152
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
153
|
+
|
154
|
+
responses = sock.read(4096)
|
155
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
156
|
+
assert_match %r{^after_reply_called: true}, responses
|
157
|
+
|
158
|
+
sock.close
|
159
|
+
end
|
160
|
+
|
87
161
|
def test_broken_app
|
88
162
|
teardown
|
89
163
|
app = lambda { |env| raise RuntimeError, "hello" }
|
data/unicorn.gemspec
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
|
12
12
|
Gem::Specification.new do |s|
|
13
13
|
s.name = %q{unicorn}
|
14
|
-
s.version = (ENV['VERSION'] || '5.
|
14
|
+
s.version = (ENV['VERSION'] || '5.8.0').dup
|
15
15
|
s.authors = ['unicorn hackers']
|
16
16
|
s.summary = 'Rack HTTP server for fast clients and Unix'
|
17
17
|
s.description = File.read('README').split("\n\n")[1]
|
@@ -25,10 +25,11 @@
|
|
25
25
|
s.homepage = 'https://yhbt.net/unicorn/'
|
26
26
|
s.test_files = test_files
|
27
27
|
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
|
28
|
+
# 1.9.3 is the minumum supported version. We don't specify
|
29
|
+
# a maximum version to make it easier to test pre-releases,
|
30
|
+
# but we do warn users if they install unicorn on an untested
|
31
|
+
# version in extconf.rb
|
32
|
+
s.required_ruby_version = ">= 1.9.3"
|
32
33
|
|
33
34
|
# We do not have a hard dependency on rack, it's possible to load
|
34
35
|
# things which respond to #call. HTTP status lines in responses
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unicorn hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -282,9 +282,9 @@ require_paths:
|
|
282
282
|
- lib
|
283
283
|
required_ruby_version: !ruby/object:Gem::Requirement
|
284
284
|
requirements:
|
285
|
-
- - "
|
285
|
+
- - ">="
|
286
286
|
- !ruby/object:Gem::Version
|
287
|
-
version:
|
287
|
+
version: 1.9.3
|
288
288
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
289
289
|
requirements:
|
290
290
|
- - ">="
|