sidekiq-alive-next 3.1.1 → 3.2.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/README.md +41 -19
- data/lib/sidekiq_alive/config.rb +12 -4
- data/lib/sidekiq_alive/helpers.rb +53 -0
- data/lib/sidekiq_alive/redis/base.rb +35 -0
- data/lib/sidekiq_alive/redis/redis_client_gem.rb +52 -0
- data/lib/sidekiq_alive/redis/redis_gem.rb +45 -0
- data/lib/sidekiq_alive/redis.rb +15 -0
- data/lib/sidekiq_alive/server/base.rb +52 -0
- data/lib/sidekiq_alive/server/default.rb +79 -0
- data/lib/sidekiq_alive/server/http_server.rb +140 -0
- data/lib/sidekiq_alive/server/rack.rb +67 -0
- data/lib/sidekiq_alive/server.rb +13 -29
- data/lib/sidekiq_alive/version.rb +1 -1
- data/lib/sidekiq_alive/worker.rb +14 -7
- data/lib/sidekiq_alive.rb +174 -0
- metadata +69 -48
- data/lib/sidekiq-alive-next.rb +0 -142
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cbf2184e5b001d067c1a79b65daf927182fd132ca2554177e31a7f070176e12
|
4
|
+
data.tar.gz: e911131239e815e146bc66b6844f092571d07108ff6ae27ed2d730d39a49928a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a0ddb8c2bdd70be93b63659711967f617e7b4b4f84bc695646baa331c7de188ed3993ea30530a54dfb12dfcfbd374dd3b8a0a54e350b038a6e09bb10e18cb47
|
7
|
+
data.tar.gz: 551e3c7c103eb1c3067e1ad8084db084872817f30b14d58e9658b348c220e064b20f5b4352f60507372ace146957765bc2f9c17da6360eb7f20c46aa9b23a507
|
data/README.md
CHANGED
@@ -5,7 +5,9 @@
|
|
5
5
|

|
6
6
|
[](https://snyk.io/test/github/andrcuns/sidekiq-alive)
|
7
7
|
|
8
|
-
**This is the fork of 'arturictus/sidekiq_alive' with a few minor fixes and tweaks,
|
8
|
+
**This is the fork of '[arturictus/sidekiq_alive](https://github.com/arturictus/sidekiq_alive)' with a few minor fixes and tweaks, original implementation credit goes to author of original repo**
|
9
|
+
|
10
|
+
---
|
9
11
|
|
10
12
|
SidekiqAlive offers a solution to add liveness probe for a Sidekiq instance deployed in Kubernetes.
|
11
13
|
This library can be used to check sidekiq health outside kubernetes.
|
@@ -49,11 +51,15 @@ gem 'sidekiq-alive-next'
|
|
49
51
|
|
50
52
|
And then execute:
|
51
53
|
|
52
|
-
|
54
|
+
```console
|
55
|
+
bundle
|
56
|
+
```
|
53
57
|
|
54
58
|
Or install it yourself as:
|
55
59
|
|
56
|
-
|
60
|
+
```console
|
61
|
+
gem install sidekiq-alive-next
|
62
|
+
```
|
57
63
|
|
58
64
|
## Usage
|
59
65
|
|
@@ -61,11 +67,11 @@ SidekiqAlive will start when running `sidekiq` command.
|
|
61
67
|
|
62
68
|
Run `Sidekiq`
|
63
69
|
|
64
|
-
```
|
70
|
+
```console
|
65
71
|
bundle exec sidekiq
|
66
72
|
```
|
67
73
|
|
68
|
-
```
|
74
|
+
```console
|
69
75
|
curl localhost:7433
|
70
76
|
#=> Alive!
|
71
77
|
```
|
@@ -74,7 +80,7 @@ curl localhost:7433
|
|
74
80
|
You can disabled by setting `ENV` variable `DISABLE_SIDEKIQ_ALIVE`
|
75
81
|
example:
|
76
82
|
|
77
|
-
```
|
83
|
+
```console
|
78
84
|
DISABLE_SIDEKIQ_ALIVE=true bundle exec sidekiq
|
79
85
|
```
|
80
86
|
|
@@ -137,8 +143,8 @@ kill -SIGTSTP $SIDEKIQ_PID
|
|
137
143
|
|
138
144
|
Make it executable:
|
139
145
|
|
140
|
-
```
|
141
|
-
|
146
|
+
```console
|
147
|
+
chmod +x kube/sidekiq_quiet
|
142
148
|
```
|
143
149
|
|
144
150
|
Execute it in your deployment preStop:
|
@@ -183,12 +189,12 @@ It's just up to you how you want to use it.
|
|
183
189
|
|
184
190
|
An example in local would be:
|
185
191
|
|
186
|
-
```
|
192
|
+
```console
|
187
193
|
bundle exec sidekiq
|
188
194
|
# let it initialize ...
|
189
195
|
```
|
190
196
|
|
191
|
-
```
|
197
|
+
```console
|
192
198
|
curl localhost:7433
|
193
199
|
#=> Alive!
|
194
200
|
```
|
@@ -248,12 +254,10 @@ SidekiqAlive.setup do |config|
|
|
248
254
|
# config.callback = proc { Net::HTTP.get("https://status.com/ping") }
|
249
255
|
|
250
256
|
# ==> Shutdown callback
|
251
|
-
# When sidekiq process is shutting down, you can perform some action
|
257
|
+
# When sidekiq process is shutting down, you can perform some arbitrary action.
|
252
258
|
# default: proc {}
|
253
259
|
#
|
254
|
-
# config.shutdown_callback = proc
|
255
|
-
# Sidekiq::Queue.all.find { |q| q.name == "#{queue_prefix}-#{SidekiqAlive.hostname}" }&.clear
|
256
|
-
# end
|
260
|
+
# config.shutdown_callback = proc { puts "Sidekiq is shutting down" }
|
257
261
|
|
258
262
|
# ==> Queue Prefix
|
259
263
|
# SidekiqAlive will run in a independent queue for each instance/replica
|
@@ -263,12 +267,32 @@ SidekiqAlive.setup do |config|
|
|
263
267
|
#
|
264
268
|
# config.queue_prefix = :other
|
265
269
|
|
270
|
+
# ==> Concurrency
|
271
|
+
# The maximum number of Redis connections requested for the SidekiqAlive pool.
|
272
|
+
# Can also be set with the environment variable SIDEKIQ_ALIVE_CONCURRENCY.
|
273
|
+
# NOTE: only effects Sidekiq 7 or greater.
|
274
|
+
# default: 2
|
275
|
+
#
|
276
|
+
# config.concurrency = 3
|
277
|
+
|
266
278
|
# ==> Rack server
|
267
|
-
# Web server used to serve an HTTP response.
|
279
|
+
# Web server used to serve an HTTP response. By default simple GServer based http server is used.
|
280
|
+
# To use specific server, rack gem version > 2 is required. For rack version >= 3, rackup gem is required.
|
268
281
|
# Can also be set with the environment variable SIDEKIQ_ALIVE_SERVER.
|
269
|
-
# default:
|
282
|
+
# default: nil
|
270
283
|
#
|
271
284
|
# config.server = 'puma'
|
285
|
+
|
286
|
+
# ==> Quiet mode timeout in seconds
|
287
|
+
# When sidekiq is shutting down, the Sidekiq process stops pulling jobs from the queue. This includes alive key update job. In case of
|
288
|
+
# long running jobs, alive key can expire before the job is finished. To avoid this, web server is set in to quiet mode
|
289
|
+
# and is returning 200 OK for healthcheck requests. To avoid infinite quiet mode in case sidekiq process is stuck in shutdown,
|
290
|
+
# timeout can be set. After timeout is reached, web server resumes normal operations and will return unhealthy status in case
|
291
|
+
# alive key is expired or purged from redis.
|
292
|
+
# default: 180
|
293
|
+
#
|
294
|
+
# config.quiet_timeout = 300
|
295
|
+
|
272
296
|
end
|
273
297
|
```
|
274
298
|
|
@@ -278,11 +302,9 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
278
302
|
|
279
303
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
280
304
|
|
281
|
-
Here is an example [rails app](https://github.com/arturictus/sidekiq_alive_example)
|
282
|
-
|
283
305
|
## Contributing
|
284
306
|
|
285
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/andrcuns/sidekiq-alive
|
307
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/andrcuns/sidekiq-alive>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
286
308
|
|
287
309
|
## License
|
288
310
|
|
data/lib/sidekiq_alive/config.rb
CHANGED
@@ -12,10 +12,12 @@ module SidekiqAlive
|
|
12
12
|
:callback,
|
13
13
|
:registered_instance_key,
|
14
14
|
:queue_prefix,
|
15
|
-
:server,
|
16
15
|
:custom_liveness_probe,
|
17
16
|
:logger,
|
18
|
-
:shutdown_callback
|
17
|
+
:shutdown_callback,
|
18
|
+
:concurrency,
|
19
|
+
:server,
|
20
|
+
:quiet_timeout
|
19
21
|
|
20
22
|
def initialize
|
21
23
|
set_defaults
|
@@ -30,13 +32,19 @@ module SidekiqAlive
|
|
30
32
|
@callback = proc {}
|
31
33
|
@registered_instance_key = "SIDEKIQ_REGISTERED_INSTANCE"
|
32
34
|
@queue_prefix = :"sidekiq-alive"
|
33
|
-
@server = ENV.fetch("SIDEKIQ_ALIVE_SERVER", "webrick")
|
34
35
|
@custom_liveness_probe = proc { true }
|
35
36
|
@shutdown_callback = proc {}
|
37
|
+
@concurrency = Integer(ENV.fetch("SIDEKIQ_ALIVE_CONCURRENCY", 2), exception: false) || 2
|
38
|
+
@server = ENV.fetch("SIDEKIQ_ALIVE_SERVER", nil)
|
39
|
+
@quiet_timeout = Integer(ENV.fetch("SIDEKIQ_ALIVE_QUIET_TIMEOUT", 180), exception: false) || 180
|
36
40
|
end
|
37
41
|
|
38
42
|
def registration_ttl
|
39
|
-
@registration_ttl || time_to_live
|
43
|
+
@registration_ttl || time_to_live * 3
|
44
|
+
end
|
45
|
+
|
46
|
+
def worker_interval
|
47
|
+
time_to_live / 2
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqAlive
|
4
|
+
module Helpers
|
5
|
+
class << self
|
6
|
+
def sidekiq_7
|
7
|
+
current_sidekiq_version >= Gem::Version.new("7")
|
8
|
+
end
|
9
|
+
|
10
|
+
def sidekiq_6
|
11
|
+
current_sidekiq_version >= Gem::Version.new("6") &&
|
12
|
+
current_sidekiq_version < Gem::Version.new("7")
|
13
|
+
end
|
14
|
+
|
15
|
+
def sidekiq_5
|
16
|
+
current_sidekiq_version >= Gem::Version.new("5") &&
|
17
|
+
current_sidekiq_version < Gem::Version.new("6")
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_rack?
|
21
|
+
return @use_rack if defined?(@use_rack)
|
22
|
+
|
23
|
+
require "rack"
|
24
|
+
@use_rack = current_rack_version < Gem::Version.new("3")
|
25
|
+
rescue LoadError
|
26
|
+
# currently this won't happen because rack is a dependency of sidekiq
|
27
|
+
@use_rack = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def use_rackup?
|
31
|
+
return @use_rackup if defined?(@use_rackup)
|
32
|
+
|
33
|
+
require "rackup"
|
34
|
+
@use_rackup = current_rack_version >= Gem::Version.new("3")
|
35
|
+
rescue LoadError
|
36
|
+
if current_rack_version >= Gem::Version.new("3")
|
37
|
+
SidekiqAlive.logger.warn("rackup gem required with rack >= 3, defaulting to default server")
|
38
|
+
end
|
39
|
+
@use_rackup = false
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def current_sidekiq_version
|
45
|
+
Gem.loaded_specs["sidekiq"].version
|
46
|
+
end
|
47
|
+
|
48
|
+
def current_rack_version
|
49
|
+
Gem.loaded_specs["rack"].version
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqAlive
|
4
|
+
module Redis
|
5
|
+
class Base
|
6
|
+
def set(...)
|
7
|
+
raise(NotImplementedError)
|
8
|
+
end
|
9
|
+
|
10
|
+
def zadd(set_key, ex, key)
|
11
|
+
raise(NotImplementedError)
|
12
|
+
end
|
13
|
+
|
14
|
+
def zrange(set_key, start, stop)
|
15
|
+
raise(NotImplementedError)
|
16
|
+
end
|
17
|
+
|
18
|
+
def zrangebyscore(set_key, min, max)
|
19
|
+
raise(NotImplementedError)
|
20
|
+
end
|
21
|
+
|
22
|
+
def zrem(set_key, key)
|
23
|
+
raise(NotImplementedError)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(key)
|
27
|
+
raise(NotImplementedError)
|
28
|
+
end
|
29
|
+
|
30
|
+
def ttl(...)
|
31
|
+
redis { |r| r.ttl(...) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module SidekiqAlive
|
6
|
+
module Redis
|
7
|
+
# Wrapper for `redis-client` gem used by `sidekiq` > 7
|
8
|
+
# https://github.com/redis-rb/redis-client
|
9
|
+
class RedisClientGem < Base
|
10
|
+
def initialize(capsule = nil)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@capsule = Sidekiq.default_configuration.capsules[capsule || CAPSULE_NAME]
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(key, time:, ex:)
|
17
|
+
redis { |r| r.call("SET", key, time, ex: ex) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(key)
|
21
|
+
redis { |r| r.call("GET", key) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def zadd(set_key, ex, key)
|
25
|
+
redis { |r| r.call("ZADD", set_key, ex, key) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def zrange(set_key, start, stop)
|
29
|
+
redis { |r| r.call("ZRANGE", set_key, start, stop) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def zrangebyscore(set_key, min, max)
|
33
|
+
redis { |r| r.call("ZRANGEBYSCORE", set_key, min, max) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def zrem(set_key, key)
|
37
|
+
redis { |r| r.call("ZREM", set_key, key) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(key)
|
41
|
+
redis { |r| r.call("DEL", key) }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def redis(&block)
|
47
|
+
# Default to Sidekiq.redis if capsule is not configured yet but redis adapter is accessed
|
48
|
+
(@capsule || Sidekiq).redis(&block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module SidekiqAlive
|
6
|
+
module Redis
|
7
|
+
# Wrapper for `redis` gem used by sidekiq < 7
|
8
|
+
# https://github.com/redis/redis-rb
|
9
|
+
class RedisGem < Base
|
10
|
+
def set(key, time:, ex:)
|
11
|
+
redis { |r| r.set(key, time, ex: ex) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(key)
|
15
|
+
redis { |r| r.get(key) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def zadd(set_key, ex, key)
|
19
|
+
redis { |r| r.zadd(set_key, ex, key) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def zrange(set_key, start, stop)
|
23
|
+
redis { |r| r.zrange(set_key, start, stop) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def zrangebyscore(set_key, min, max)
|
27
|
+
redis { |r| r.zrangebyscore(set_key, min, max) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def zrem(set_key, key)
|
31
|
+
redis { |r| r.zrem(set_key, key) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(key)
|
35
|
+
redis { |r| r.del(key) }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def redis(&block)
|
41
|
+
Sidekiq.redis(&block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqAlive
|
4
|
+
module Redis
|
5
|
+
class << self
|
6
|
+
def adapter(capsule = nil)
|
7
|
+
Helpers.sidekiq_7 ? Redis::RedisClientGem.new(capsule) : Redis::RedisGem.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require_relative "redis/base"
|
14
|
+
require_relative "redis/redis_client_gem"
|
15
|
+
require_relative "redis/redis_gem"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqAlive
|
4
|
+
module Server
|
5
|
+
module Base
|
6
|
+
SHUTDOWN_SIGNAL = "TERM"
|
7
|
+
QUIET_SIGNAL = "TSTP"
|
8
|
+
|
9
|
+
# set web server to quiet mode
|
10
|
+
def quiet!
|
11
|
+
logger.info("[SidekiqAlive] Setting web server to quiet mode")
|
12
|
+
Process.kill(QUIET_SIGNAL, @server_pid) unless @server_pid.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def configure_shutdown
|
18
|
+
Kernel.at_exit do
|
19
|
+
next if @server_pid.nil?
|
20
|
+
|
21
|
+
logger.info("Shutting down SidekiqAlive web server")
|
22
|
+
Process.kill(SHUTDOWN_SIGNAL, @server_pid)
|
23
|
+
Process.wait(@server_pid)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def configure_shutdown_signal(&block)
|
28
|
+
Signal.trap(SHUTDOWN_SIGNAL, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure_quiet_signal(&block)
|
32
|
+
Signal.trap(QUIET_SIGNAL, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def host
|
36
|
+
SidekiqAlive.config.host
|
37
|
+
end
|
38
|
+
|
39
|
+
def port
|
40
|
+
SidekiqAlive.config.port.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def path
|
44
|
+
SidekiqAlive.config.path
|
45
|
+
end
|
46
|
+
|
47
|
+
def logger
|
48
|
+
SidekiqAlive.logger
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "http_server"
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module SidekiqAlive
|
7
|
+
module Server
|
8
|
+
class Default < HttpServer
|
9
|
+
extend Base
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def run!
|
13
|
+
logger.info("[SidekiqAlive] Starting default healthcheck server on #{host}:#{port}")
|
14
|
+
@server_pid = ::Process.fork do
|
15
|
+
@server = new(port, host, path)
|
16
|
+
# stop is wrapped in a thread because gserver calls synchrnonize which raises an error when in trap context
|
17
|
+
configure_shutdown_signal { Thread.new { @server.stop } }
|
18
|
+
configure_quiet_signal { @server.quiet! }
|
19
|
+
|
20
|
+
@server.start
|
21
|
+
@server.join
|
22
|
+
end
|
23
|
+
configure_shutdown
|
24
|
+
logger.info("[SidekiqAlive] Web server started in subprocess with pid #{@server_pid}")
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(port, host, path, logger = SidekiqAlive.logger)
|
31
|
+
super(self, port, host, logger)
|
32
|
+
|
33
|
+
@path = path
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_handler(req, res)
|
37
|
+
if req.path != path
|
38
|
+
res.status = 404
|
39
|
+
res.body = "Not found"
|
40
|
+
return logger.warn("[SidekiqAlive] Path '#{req.path}' not found")
|
41
|
+
end
|
42
|
+
|
43
|
+
if quiet?
|
44
|
+
res.status = 200
|
45
|
+
res.body = "Server is shutting down"
|
46
|
+
return logger.debug("[SidekiqAlive] Server in quiet mode, skipping alive key lookup!")
|
47
|
+
end
|
48
|
+
|
49
|
+
if SidekiqAlive.alive?
|
50
|
+
res.status = 200
|
51
|
+
res.body = "Alive!"
|
52
|
+
return logger.debug("[SidekiqAlive] Found alive key!")
|
53
|
+
end
|
54
|
+
|
55
|
+
response = "Can't find the alive key"
|
56
|
+
res.status = 404
|
57
|
+
res.body = response
|
58
|
+
logger.error("[SidekiqAlive] #{response}")
|
59
|
+
rescue StandardError => e
|
60
|
+
response = "Internal Server Error"
|
61
|
+
res.status = 500
|
62
|
+
res.body = response
|
63
|
+
logger.error("[SidekiqAlive] #{response} looking for alive key. Error: #{e.message}")
|
64
|
+
end
|
65
|
+
|
66
|
+
def quiet!
|
67
|
+
@quiet = Time.now
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :path
|
73
|
+
|
74
|
+
def quiet?
|
75
|
+
@quiet && (Time.now - @quiet) < SidekiqAlive.config.quiet_timeout
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gserver"
|
4
|
+
|
5
|
+
module SidekiqAlive
|
6
|
+
module Server
|
7
|
+
# Simple HTTP server implementation
|
8
|
+
#
|
9
|
+
class HttpServer < GServer
|
10
|
+
# Request class for HTTP server
|
11
|
+
#
|
12
|
+
class Request
|
13
|
+
attr_reader :data, :header, :method, :path, :proto
|
14
|
+
|
15
|
+
def initialize(data, method = nil, path = nil, proto = nil)
|
16
|
+
@header = {}
|
17
|
+
@data = data
|
18
|
+
@method = method
|
19
|
+
@path = path
|
20
|
+
@proto = proto
|
21
|
+
end
|
22
|
+
|
23
|
+
def content_length
|
24
|
+
len = @header["Content-Length"]
|
25
|
+
return if len.nil?
|
26
|
+
|
27
|
+
len.to_i
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Response class for HTTP server
|
32
|
+
#
|
33
|
+
class Response
|
34
|
+
attr_reader :header
|
35
|
+
attr_accessor :body, :status, :status_message
|
36
|
+
|
37
|
+
def initialize(status = 200)
|
38
|
+
@status = status
|
39
|
+
@status_message = nil
|
40
|
+
@header = {}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(handle_obj, port, host, logger = Logger.new($stdout))
|
45
|
+
@handler = handle_obj
|
46
|
+
@logger = logger
|
47
|
+
|
48
|
+
super(port, host, 1, nil, logger.debug?, logger.debug?)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :handler, :logger
|
54
|
+
|
55
|
+
CRLF = "\r\n"
|
56
|
+
HTTP_PROTO = "HTTP/1.1"
|
57
|
+
SERVER_NAME = "SidekiqAlive/#{SidekiqAlive::VERSION} (Ruby/#{RUBY_VERSION})"
|
58
|
+
|
59
|
+
# Default header for the server name
|
60
|
+
DEFAULT_HEADER = {
|
61
|
+
"Server" => SERVER_NAME,
|
62
|
+
}
|
63
|
+
|
64
|
+
# Mapping of status codes and error messages
|
65
|
+
STATUS_CODE_MAPPING = {
|
66
|
+
200 => "OK",
|
67
|
+
400 => "Bad Request",
|
68
|
+
403 => "Forbidden",
|
69
|
+
404 => "Not Found",
|
70
|
+
405 => "Method Not Allowed",
|
71
|
+
411 => "Length Required",
|
72
|
+
500 => "Internal Server Error",
|
73
|
+
}
|
74
|
+
|
75
|
+
def serve(io)
|
76
|
+
# parse first line
|
77
|
+
if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
|
78
|
+
request = Request.new(io, ::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3))
|
79
|
+
else
|
80
|
+
io << http_resp(status_code: 400)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
# parse HTTP headers
|
85
|
+
while (line = io.gets) !~ /^(\n|\r)/
|
86
|
+
if line =~ /^([\w-]+):\s*(.*)$/
|
87
|
+
request.header[::Regexp.last_match(1)] = ::Regexp.last_match(2).strip
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
io.binmode
|
92
|
+
response = Response.new
|
93
|
+
|
94
|
+
# execute request handler
|
95
|
+
handler.request_handler(request, response)
|
96
|
+
|
97
|
+
http_response = http_resp(
|
98
|
+
status_code: response.status,
|
99
|
+
status_message: response.status_message,
|
100
|
+
header: response.header,
|
101
|
+
body: response.body,
|
102
|
+
)
|
103
|
+
|
104
|
+
# write response back to the client
|
105
|
+
io << http_response
|
106
|
+
rescue StandardError
|
107
|
+
io << http_resp(status_code: 500)
|
108
|
+
end
|
109
|
+
|
110
|
+
def http_header(header = nil)
|
111
|
+
new_header = DEFAULT_HEADER.dup
|
112
|
+
new_header.merge(header) unless header.nil?
|
113
|
+
|
114
|
+
new_header["Connection"] = "Keep-Alive"
|
115
|
+
new_header["Date"] = http_date(Time.now)
|
116
|
+
|
117
|
+
new_header
|
118
|
+
end
|
119
|
+
|
120
|
+
def http_resp(status_code:, status_message: nil, header: nil, body: nil)
|
121
|
+
status_message ||= STATUS_CODE_MAPPING[status_code]
|
122
|
+
status_line = "#{HTTP_PROTO} #{status_code} #{status_message}".rstrip + CRLF
|
123
|
+
|
124
|
+
resp_header = http_header(header)
|
125
|
+
resp_header["Content-Length"] = body.bytesize.to_s unless body.nil?
|
126
|
+
header_lines = resp_header.map { |k, v| "#{k}: #{v}#{CRLF}" }.join
|
127
|
+
|
128
|
+
[status_line, header_lines, CRLF, body].compact.join
|
129
|
+
end
|
130
|
+
|
131
|
+
def http_date(a_time)
|
132
|
+
a_time.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
133
|
+
end
|
134
|
+
|
135
|
+
def log(msg)
|
136
|
+
logger.debug(msg)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|