pitchfork 0.14.0 → 0.15.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/.github/workflows/ci.yml +16 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +3 -0
- data/docs/CONFIGURATION.md +32 -0
- data/docs/FORK_SAFETY.md +2 -1
- data/ext/pitchfork_http/common_field_optimization.h +8 -5
- data/ext/pitchfork_http/extconf.rb +3 -0
- data/ext/pitchfork_http/pitchfork_http.c +292 -468
- data/ext/pitchfork_http/pitchfork_http.rl +32 -24
- data/lib/pitchfork/chunked.rb +6 -3
- data/lib/pitchfork/configurator.rb +1 -1
- data/lib/pitchfork/http_parser.rb +0 -1
- data/lib/pitchfork/http_response.rb +3 -1
- data/lib/pitchfork/http_server.rb +40 -20
- data/lib/pitchfork/listeners.rb +65 -0
- data/lib/pitchfork/socket_helper.rb +12 -2
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +0 -5
- data/pitchfork.gemspec +2 -1
- metadata +18 -4
- data/Gemfile.lock +0 -32
@@ -36,12 +36,6 @@ void init_pitchfork_memory_page(VALUE);
|
|
36
36
|
|
37
37
|
static unsigned int MAX_HEADER_LEN = 1024 * (80 + 32); /* same as Mongrel */
|
38
38
|
|
39
|
-
/* this is only intended for use with Rainbows! */
|
40
|
-
static VALUE set_maxhdrlen(VALUE self, VALUE len)
|
41
|
-
{
|
42
|
-
return UINT2NUM(MAX_HEADER_LEN = NUM2UINT(len));
|
43
|
-
}
|
44
|
-
|
45
39
|
/* keep this small for other servers (e.g. yahns) since every client has one */
|
46
40
|
struct http_parser {
|
47
41
|
int cs; /* Ragel internal state */
|
@@ -95,7 +89,7 @@ static inline unsigned int ulong2uint(unsigned long n)
|
|
95
89
|
#define LEN(AT, FPC) (ulong2uint(FPC - buffer) - hp->AT)
|
96
90
|
#define MARK(M,FPC) (hp->M = ulong2uint((FPC) - buffer))
|
97
91
|
#define PTR_TO(F) (buffer + hp->F)
|
98
|
-
#define STR_NEW(M,FPC)
|
92
|
+
#define STR_NEW(M,FPC) str_new(PTR_TO(M), LEN(M, FPC))
|
99
93
|
#define STRIPPED_STR_NEW(M,FPC) stripped_str_new(PTR_TO(M), LEN(M, FPC))
|
100
94
|
|
101
95
|
#define HP_FL_TEST(hp,fl) ((hp)->flags & (UH_FL_##fl))
|
@@ -108,13 +102,29 @@ static int is_lws(char c)
|
|
108
102
|
return (c == ' ' || c == '\t');
|
109
103
|
}
|
110
104
|
|
111
|
-
static VALUE
|
105
|
+
static inline VALUE str_new(const char *str, long len)
|
106
|
+
{
|
107
|
+
VALUE rb_str = rb_str_new(str, len);
|
108
|
+
/* The Rack spec states:
|
109
|
+
> If the string values for CGI keys contain non-ASCII characters, they should use ASCII-8BIT encoding.
|
110
|
+
If they are ASCII, only the server is free to encode them as it wishes.
|
111
|
+
We chose to encode them as UTF-8 as any reasonable application would expect that today, and having the
|
112
|
+
same encoding as literal strings makes for slightly faster comparisons.
|
113
|
+
We'd like to also encode other strings that would be valid UTF-8 into UTF-8, but that would
|
114
|
+
violate the spec, so we leave them in BINARY aka ASCII-8BIT. */
|
115
|
+
if (rb_enc_str_asciionly_p(rb_str)) {
|
116
|
+
RB_ENCODING_SET_INLINED(rb_str, rb_utf8_encindex());
|
117
|
+
}
|
118
|
+
return rb_str;
|
119
|
+
}
|
120
|
+
|
121
|
+
static inline VALUE stripped_str_new(const char *str, long len)
|
112
122
|
{
|
113
123
|
long end;
|
114
124
|
|
115
125
|
for (end = len - 1; end >= 0 && is_lws(str[end]); end--);
|
116
126
|
|
117
|
-
return
|
127
|
+
return str_new(str, end + 1);
|
118
128
|
}
|
119
129
|
|
120
130
|
/*
|
@@ -330,24 +340,18 @@ static void write_value(VALUE self, struct http_parser *hp,
|
|
330
340
|
}
|
331
341
|
action host { rb_hash_aset(hp->env, g_http_host, STR_NEW(mark, fpc)); }
|
332
342
|
action request_uri {
|
333
|
-
VALUE str;
|
334
|
-
|
335
343
|
VALIDATE_MAX_URI_LENGTH(LEN(mark, fpc), REQUEST_URI);
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
344
|
+
rb_hash_aset(hp->env, g_request_uri, STR_NEW(mark, fpc));
|
345
|
+
}
|
346
|
+
action fragment {
|
347
|
+
VALIDATE_MAX_URI_LENGTH(LEN(mark, fpc), FRAGMENT);
|
348
|
+
VALUE str = rb_hash_aset(hp->env, g_fragment, STR_NEW(mark, fpc));
|
341
349
|
if (STR_CSTR_EQ(str, "*")) {
|
342
|
-
str = rb_str_new(
|
350
|
+
VALUE str = rb_str_new("*", 1);
|
343
351
|
rb_hash_aset(hp->env, g_path_info, str);
|
344
352
|
rb_hash_aset(hp->env, g_request_path, str);
|
345
353
|
}
|
346
354
|
}
|
347
|
-
action fragment {
|
348
|
-
VALIDATE_MAX_URI_LENGTH(LEN(mark, fpc), FRAGMENT);
|
349
|
-
rb_hash_aset(hp->env, g_fragment, STR_NEW(mark, fpc));
|
350
|
-
}
|
351
355
|
action start_query {MARK(start.query, fpc); }
|
352
356
|
action query_string {
|
353
357
|
VALIDATE_MAX_URI_LENGTH(LEN(start.query, fpc), QUERY_STRING);
|
@@ -612,6 +616,12 @@ static VALUE HttpParser_alloc(VALUE klass)
|
|
612
616
|
return TypedData_Make_Struct(klass, struct http_parser, &hp_type, hp);
|
613
617
|
}
|
614
618
|
|
619
|
+
#ifndef HAVE_RB_HASH_NEW_CAPA
|
620
|
+
static inline VALUE rb_hash_new_capa(long capa) {
|
621
|
+
return rb_hash_new();
|
622
|
+
}
|
623
|
+
#endif
|
624
|
+
|
615
625
|
/**
|
616
626
|
* call-seq:
|
617
627
|
* parser.new => parser
|
@@ -624,7 +634,7 @@ static VALUE HttpParser_init(VALUE self)
|
|
624
634
|
|
625
635
|
http_parser_init(hp);
|
626
636
|
RB_OBJ_WRITE(self, &hp->buf, rb_str_new(NULL, 0));
|
627
|
-
RB_OBJ_WRITE(self, &hp->env,
|
637
|
+
RB_OBJ_WRITE(self, &hp->env, rb_hash_new_capa(32)); // Even the simplest request will have 10 keys
|
628
638
|
|
629
639
|
return self;
|
630
640
|
}
|
@@ -1010,8 +1020,6 @@ RUBY_FUNC_EXPORTED void Init_pitchfork_http(void)
|
|
1010
1020
|
*/
|
1011
1021
|
rb_define_const(cHttpParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX));
|
1012
1022
|
|
1013
|
-
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
1014
|
-
|
1015
1023
|
init_common_fields();
|
1016
1024
|
SET_GLOBAL(g_http_host, "HOST");
|
1017
1025
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
data/lib/pitchfork/chunked.rb
CHANGED
@@ -104,12 +104,15 @@ module Pitchfork
|
|
104
104
|
def call(env)
|
105
105
|
status, headers, body = response = @app.call(env)
|
106
106
|
|
107
|
-
if
|
107
|
+
if !env.key?('rack.hijack_io') && # full highjack
|
108
|
+
!headers['rack.hijack'] && # partial hijack
|
109
|
+
body.respond_to?(:each) && # body must be enumerable (i.e. non-streaming)
|
110
|
+
chunkable_version?(env[Rack::SERVER_PROTOCOL]) &&
|
108
111
|
!STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
|
109
112
|
!headers[Rack::CONTENT_LENGTH] &&
|
110
|
-
!headers[
|
113
|
+
!headers["transfer-encoding"]
|
111
114
|
|
112
|
-
headers[
|
115
|
+
headers["transfer-encoding"] = 'chunked'
|
113
116
|
if headers['trailer']
|
114
117
|
response[2] = TrailerBody.new(body)
|
115
118
|
else
|
@@ -230,7 +230,7 @@ module Pitchfork
|
|
230
230
|
def listen(address, options = {})
|
231
231
|
address = expand_addr(address)
|
232
232
|
if String === address
|
233
|
-
[ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
|
233
|
+
[ :umask, :backlog, :sndbuf, :rcvbuf, :tries, :queues, :queues_per_worker].each do |key|
|
234
234
|
value = options[key] or next
|
235
235
|
Integer === value or
|
236
236
|
raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'pitchfork/pitchfork_http'
|
5
|
+
require 'pitchfork/listeners'
|
5
6
|
require 'pitchfork/flock'
|
6
7
|
require 'pitchfork/soft_timeout'
|
7
8
|
require 'pitchfork/shared_memory'
|
@@ -79,8 +80,7 @@ module Pitchfork
|
|
79
80
|
attr_accessor :app, :timeout, :timeout_signal, :soft_timeout, :cleanup_timeout, :spawn_timeout, :worker_processes,
|
80
81
|
:before_fork, :after_worker_fork, :after_mold_fork, :before_service_worker_ready, :before_service_worker_exit,
|
81
82
|
:listener_opts, :children,
|
82
|
-
:orig_app, :config, :ready_pipe,
|
83
|
-
:default_middleware, :early_hints
|
83
|
+
:orig_app, :config, :ready_pipe, :early_hints
|
84
84
|
attr_writer :after_worker_exit, :before_worker_exit, :after_worker_ready, :after_request_complete,
|
85
85
|
:refork_condition, :after_worker_timeout, :after_worker_hard_timeout, :after_monitor_ready
|
86
86
|
|
@@ -91,7 +91,7 @@ module Pitchfork
|
|
91
91
|
# all bound listener sockets
|
92
92
|
# note: this is public used by raindrops, but not recommended for use
|
93
93
|
# in new projects
|
94
|
-
LISTENERS =
|
94
|
+
LISTENERS = Listeners.new
|
95
95
|
|
96
96
|
NOOP = '.'
|
97
97
|
|
@@ -196,6 +196,10 @@ module Pitchfork
|
|
196
196
|
# replaces current listener set with +listeners+. This will
|
197
197
|
# close the socket if it will not exist in the new listener set
|
198
198
|
def listeners=(listeners)
|
199
|
+
unless LISTENERS.empty?
|
200
|
+
raise "Listeners can only be initialized once"
|
201
|
+
end
|
202
|
+
|
199
203
|
cur_names, dead_names = [], []
|
200
204
|
listener_names.each do |name|
|
201
205
|
if name.start_with?('/')
|
@@ -205,19 +209,7 @@ module Pitchfork
|
|
205
209
|
cur_names << name
|
206
210
|
end
|
207
211
|
end
|
208
|
-
|
209
|
-
dead_names.concat(cur_names - set_names).uniq!
|
210
|
-
|
211
|
-
LISTENERS.delete_if do |io|
|
212
|
-
if dead_names.include?(sock_name(io))
|
213
|
-
(io.close rescue nil).nil? # true
|
214
|
-
else
|
215
|
-
set_server_sockopt(io, listener_opts[sock_name(io)])
|
216
|
-
false
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
(set_names - cur_names).each { |addr| listen(addr) }
|
212
|
+
listener_names(listeners).each { |addr| listen(addr) }
|
221
213
|
end
|
222
214
|
|
223
215
|
def logger=(obj)
|
@@ -233,12 +225,16 @@ module Pitchfork
|
|
233
225
|
# A negative value for +:tries+ indicates the listen will be
|
234
226
|
# retried indefinitely, this is useful when workers belonging to
|
235
227
|
# different masters are spawned during a transparent upgrade.
|
236
|
-
def listen(address, opt =
|
228
|
+
def listen(address, opt = listener_opts[address] || {})
|
237
229
|
address = config.expand_addr(address)
|
238
230
|
return if String === address && listener_names.include?(address)
|
239
231
|
|
232
|
+
opt = opt.dup
|
240
233
|
delay = opt[:delay] || 0.5
|
241
234
|
tries = opt[:tries] || 5
|
235
|
+
queues = opt[:queues] ||= 1
|
236
|
+
opt[:reuseport] = true if queues > 1
|
237
|
+
|
242
238
|
begin
|
243
239
|
io = bind_listen(address, opt)
|
244
240
|
unless TCPServer === io || UNIXServer === io
|
@@ -247,7 +243,7 @@ module Pitchfork
|
|
247
243
|
end
|
248
244
|
logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
|
249
245
|
Info.keep_io(io)
|
250
|
-
LISTENERS << io
|
246
|
+
LISTENERS << io unless queues > 1
|
251
247
|
io
|
252
248
|
rescue Errno::EADDRINUSE => err
|
253
249
|
logger.error "adding listener failed addr=#{address} (in use)"
|
@@ -261,6 +257,29 @@ module Pitchfork
|
|
261
257
|
logger.fatal "error adding listener addr=#{address}"
|
262
258
|
raise err
|
263
259
|
end
|
260
|
+
|
261
|
+
if queues > 1
|
262
|
+
ios = [io]
|
263
|
+
|
264
|
+
(queues - 1).times do
|
265
|
+
io = bind_listen(address, opt)
|
266
|
+
unless TCPServer === io || UNIXServer === io
|
267
|
+
io.autoclose = false
|
268
|
+
io = server_cast(io)
|
269
|
+
end
|
270
|
+
logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno} (SO_REUSEPORT)"
|
271
|
+
Info.keep_io(io)
|
272
|
+
ios << io
|
273
|
+
rescue => err
|
274
|
+
logger.fatal "error adding listener addr=#{address}"
|
275
|
+
raise err
|
276
|
+
end
|
277
|
+
|
278
|
+
io = Listeners::Group.new(ios, queues_per_worker: opt[:queues_per_worker] || queues - 1)
|
279
|
+
LISTENERS << io
|
280
|
+
end
|
281
|
+
|
282
|
+
io
|
264
283
|
end
|
265
284
|
|
266
285
|
# monitors children and receives signals forever
|
@@ -371,7 +390,8 @@ module Pitchfork
|
|
371
390
|
@respawn = false
|
372
391
|
SharedMemory.shutting_down!
|
373
392
|
wait_for_pending_workers
|
374
|
-
|
393
|
+
LISTENERS.each(&:close).clear
|
394
|
+
|
375
395
|
limit = Pitchfork.time_now + timeout
|
376
396
|
until @children.empty? || Pitchfork.time_now > limit
|
377
397
|
if graceful
|
@@ -897,7 +917,7 @@ module Pitchfork
|
|
897
917
|
|
898
918
|
@config = nil
|
899
919
|
@listener_opts = @orig_app = nil
|
900
|
-
readers = LISTENERS.
|
920
|
+
readers = LISTENERS.for_worker(worker.nr)
|
901
921
|
readers << worker
|
902
922
|
trap(:QUIT) { nuke_listeners!(readers) }
|
903
923
|
trap(:TERM) { nuke_listeners!(readers) }
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pitchfork
|
4
|
+
|
5
|
+
class Listeners
|
6
|
+
class Group
|
7
|
+
def initialize(listeners, queues_per_worker:)
|
8
|
+
@listeners = listeners
|
9
|
+
@queues_per_worker = queues_per_worker
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
@listeners.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def for_worker(nr)
|
17
|
+
index = nr % @listeners.size
|
18
|
+
|
19
|
+
listeners = @listeners.slice(index..-1) + @listeners.slice(0...index)
|
20
|
+
listeners.take(@queues_per_worker)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
include Enumerable
|
25
|
+
|
26
|
+
def initialize(listeners = [])
|
27
|
+
@listeners = listeners
|
28
|
+
end
|
29
|
+
|
30
|
+
def for_worker(nr)
|
31
|
+
ios = []
|
32
|
+
@listeners.each do |listener|
|
33
|
+
if listener.is_a?(Group)
|
34
|
+
ios += listener.for_worker(nr)
|
35
|
+
else
|
36
|
+
ios << listener
|
37
|
+
end
|
38
|
+
end
|
39
|
+
ios
|
40
|
+
end
|
41
|
+
|
42
|
+
def each(&block)
|
43
|
+
@listeners.each do |listener|
|
44
|
+
if listener.is_a?(Group)
|
45
|
+
listener.each(&block)
|
46
|
+
else
|
47
|
+
yield listener
|
48
|
+
end
|
49
|
+
end
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def clear
|
54
|
+
@listeners.clear
|
55
|
+
end
|
56
|
+
|
57
|
+
def <<(listener)
|
58
|
+
@listeners << listener
|
59
|
+
end
|
60
|
+
|
61
|
+
def empty?
|
62
|
+
@listeners.empty?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -26,6 +26,8 @@ module Pitchfork
|
|
26
26
|
:tcp_nodelay => true,
|
27
27
|
}
|
28
28
|
|
29
|
+
private
|
30
|
+
|
29
31
|
# configure platform-specific options (only tested on Linux 2.6 so far)
|
30
32
|
def accf_arg(af_name)
|
31
33
|
[ af_name, nil ].pack('a16a240')
|
@@ -64,7 +66,7 @@ module Pitchfork
|
|
64
66
|
elsif respond_to?(:accf_arg)
|
65
67
|
name = opt[:accept_filter]
|
66
68
|
name = DEFAULTS[:accept_filter] if name.nil?
|
67
|
-
sock.listen(opt
|
69
|
+
sock.listen(compute_backlog(opt))
|
68
70
|
got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
|
69
71
|
arg = accf_arg(name)
|
70
72
|
begin
|
@@ -89,11 +91,19 @@ module Pitchfork
|
|
89
91
|
sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
|
90
92
|
log_buffer_sizes(sock, " after: ")
|
91
93
|
end
|
92
|
-
sock.listen(opt
|
94
|
+
sock.listen(compute_backlog(opt))
|
93
95
|
rescue => e
|
94
96
|
Pitchfork.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
95
97
|
end
|
96
98
|
|
99
|
+
def compute_backlog(opt)
|
100
|
+
backlog = opt[:backlog]
|
101
|
+
if backlog > 0 && opt[:queues]
|
102
|
+
return backlog / opt[:queues]
|
103
|
+
end
|
104
|
+
backlog
|
105
|
+
end
|
106
|
+
|
97
107
|
def log_buffer_sizes(sock, pfx = '')
|
98
108
|
rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
|
99
109
|
sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
|
data/lib/pitchfork/version.rb
CHANGED
data/lib/pitchfork/worker.rb
CHANGED
data/pitchfork.gemspec
CHANGED
@@ -19,7 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
|
20
20
|
s.required_ruby_version = ">= 2.5.0"
|
21
21
|
|
22
|
-
s.add_dependency(
|
22
|
+
s.add_dependency('rack', '>= 2.0')
|
23
|
+
s.add_dependency('logger')
|
23
24
|
|
24
25
|
# Note: To avoid ambiguity, we intentionally avoid the SPDX-compatible
|
25
26
|
# 'Ruby' here since Ruby 1.9.3 switched to BSD-2-Clause, but we
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pitchfork
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: logger
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description: |-
|
28
42
|
`pitchfork` is a preforking HTTP server for Rack applications designed
|
29
43
|
to minimize memory usage by maximizing Copy-on-Write performance.
|
@@ -45,7 +59,6 @@ files:
|
|
45
59
|
- COPYING
|
46
60
|
- Dockerfile
|
47
61
|
- Gemfile
|
48
|
-
- Gemfile.lock
|
49
62
|
- LICENSE
|
50
63
|
- README.md
|
51
64
|
- Rakefile
|
@@ -94,6 +107,7 @@ files:
|
|
94
107
|
- lib/pitchfork/http_server.rb
|
95
108
|
- lib/pitchfork/info.rb
|
96
109
|
- lib/pitchfork/launcher.rb
|
110
|
+
- lib/pitchfork/listeners.rb
|
97
111
|
- lib/pitchfork/mem_info.rb
|
98
112
|
- lib/pitchfork/message.rb
|
99
113
|
- lib/pitchfork/refork_condition.rb
|
@@ -127,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
141
|
- !ruby/object:Gem::Version
|
128
142
|
version: '0'
|
129
143
|
requirements: []
|
130
|
-
rubygems_version: 3.5.
|
144
|
+
rubygems_version: 3.5.11
|
131
145
|
signing_key:
|
132
146
|
specification_version: 4
|
133
147
|
summary: Rack HTTP server for fast clients and Unix
|
data/Gemfile.lock
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
pitchfork (0.14.0)
|
5
|
-
rack (>= 2.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
minitest (5.22.2)
|
11
|
-
nio4r (2.7.0)
|
12
|
-
puma (6.4.2)
|
13
|
-
nio4r (~> 2.0)
|
14
|
-
rack (3.0.11)
|
15
|
-
rake (13.0.6)
|
16
|
-
rake-compiler (1.2.1)
|
17
|
-
rake
|
18
|
-
|
19
|
-
PLATFORMS
|
20
|
-
aarch64-linux
|
21
|
-
arm64-darwin
|
22
|
-
x86_64-linux
|
23
|
-
|
24
|
-
DEPENDENCIES
|
25
|
-
minitest
|
26
|
-
pitchfork!
|
27
|
-
puma
|
28
|
-
rake
|
29
|
-
rake-compiler
|
30
|
-
|
31
|
-
BUNDLED WITH
|
32
|
-
2.3.27
|