thrift_rack 0.2.2 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 819f79b59cea9fc3dfd86830ce064789fc4c24ae
4
- data.tar.gz: 2ddef4a2067d0c77e7146bd6bcb35adf4d660101
2
+ SHA256:
3
+ metadata.gz: 3f79e2c9d1ce63045d0fb7042f7109911a632fec51c7e09a9c8edde7958447b5
4
+ data.tar.gz: 6f421077a9a66de076519bcfe42beb9f0316e928b36402bad86dea39ba71afee
5
5
  SHA512:
6
- metadata.gz: d4b9dc298861c34bb55ab17f6fc0285d850481a23df99f68338004a770b3e86bf3218a968f8eb6dd0aec19492ddb24e3f33921bbdd8fbce1e986216e7d146ecb
7
- data.tar.gz: 1dfd1db65ea8cc7f7c7f3494050bbf96044a594346a3ed882428dc39d4638654ba31895ec45d76d2cb4587f16c05b5586d023ff8c76c5d74213315d2a0125324
6
+ metadata.gz: 27020172c648b060af7a24b08f23d9ab8a3a1cf6a1b8c24a8b5f9547b2bd3ccbb2be5fdecb2b8caa5b7696800b160e82268f0bfb30ed340a7fa80713b198e638
7
+ data.tar.gz: 49cf0c76cf8f38f4d68ac06584fbe4e9b2e0a778c73926ac345f37885452482fbae10d81724392befe19de170f1021d85e87c0fd54b63d2f9968389b8be379e7
data/Gemfile.lock CHANGED
@@ -1,25 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thrift_rack (0.2.1)
4
+ thrift_rack (0.2.6)
5
5
  net-http-persistent (>= 3.0)
6
- rack (>= 2.0)
7
- thrift (~> 0.10.0)
6
+ rack (>= 2.0.6)
7
+ redis (>= 3.0)
8
+ thrift (~> 0.10)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
13
  coderay (1.1.2)
13
- connection_pool (2.2.1)
14
+ connection_pool (2.2.5)
14
15
  method_source (0.9.0)
15
- net-http-persistent (3.0.0)
16
+ net-http-persistent (4.0.1)
16
17
  connection_pool (~> 2.2)
17
18
  pry (0.11.3)
18
19
  coderay (~> 1.1.0)
19
20
  method_source (~> 0.9.0)
20
- rack (2.0.5)
21
- rake (10.5.0)
22
- thrift (0.10.0.0)
21
+ rack (2.2.3)
22
+ rake (13.0.1)
23
+ redis (4.4.0)
24
+ thrift (0.14.2)
23
25
 
24
26
  PLATFORMS
25
27
  ruby
@@ -27,8 +29,8 @@ PLATFORMS
27
29
  DEPENDENCIES
28
30
  bundler (~> 1.16)
29
31
  pry (~> 0)
30
- rake (~> 10.0)
32
+ rake (~> 13.0)
31
33
  thrift_rack!
32
34
 
33
35
  BUNDLED WITH
34
- 1.16.1
36
+ 1.17.3
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # ThriftRack
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/thrift_rack`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ ThriftRack implements [thrift]([https://thrift.apache.org](https://thrift.apache.org/)) `Compact Protocol + HTTP Transport ` with rack, and makes it easy to write a server with convention.
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,7 +20,207 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ ### Server
24
+
25
+ Assume you use rails only with `activemodel`、`activerecord`、`activesupport`. Because ThriftRack act as controller and view, you do not need `actionview` and `actionpack`
26
+
27
+ You cloud new a rails app use this command
28
+
29
+ ```bash
30
+ rails new demo --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-active-record --skip-active-storage --skip-action-cable --skip-sprockets --skip-javascript --skip-turbolinks --skip-test --skip-system-test --skip-bootsnap --api --skip-webpack-install
31
+ ```
32
+
33
+ Suppose you has a thrift named `math.thrift` to add two number:
34
+
35
+ ```thrift
36
+ # math.thrift
37
+ namespace rb thrift.math # namesapce should same with filename
38
+
39
+ service MathService { # Math is capitalized from Math
40
+ int add(1: int i, 2: int j)
41
+ }
42
+ ```
43
+
44
+ then you should implement this thrift in `math_server.rb`
45
+
46
+ ```ruby
47
+ class MathServer < ThriftRack::Server
48
+ def add(i, j)
49
+ i + j
50
+ end
51
+ end
52
+ ```
53
+
54
+ Then change the `config.ru`
55
+
56
+ ```
57
+ require_relative 'config/environment'
58
+ app = ThriftRack.app(ThriftRack::Server.children)
59
+ run app
60
+ ```
61
+
62
+ Another config under initializers
63
+
64
+ ```ruby
65
+ #initializers/thrift.rb
66
+ Dir["#{Rails.root}/lib/thrift/**/*.rb"].each { |file| require file } # support generate thrift files under lib/thrift
67
+ Dir["#{Rails.root}/app/servers/*.rb"].each { |file| require file } # support servers under app/servers
68
+
69
+ ThriftRack.redis = Redis.new
70
+ ThriftRack::Logger.tag = { key: value} # tag to logs
71
+
72
+ at_exit do
73
+ ThriftRack::Logger.logger.close
74
+ end
75
+ ```
76
+
77
+ Then run this bash
78
+
79
+ ```
80
+ rackup
81
+ ```
82
+
83
+ what happend?
84
+
85
+ * We implement the math.thrift with `POST` method at `/math` path
86
+ * A Server seems like a controller, except that you do not need to write routes.
87
+ * We replace the default rails rack entrance, but it still a rack server. **You could use puma to web server, and enjoy puma's advantage**.
88
+ * Client will auto retry with network jitter, Atom ensure each request at most processed once.
89
+ * `/ping` should be use to health check, all request to `/ping` will not be log
90
+ * `ThriftRack::Logger` log each rpc request with json format. You could use ELK to analyze each request
91
+
92
+ ### Client
93
+
94
+ client cloud write like this
95
+
96
+ ```ruby
97
+ class Math
98
+ attr_accessor :client
99
+ def initialize(request_id = "no-request-id") # web should has one request_id
100
+ @client = ThriftRack::Client.new("http://127.0.0.1:300/math", ThriftClientClass, request_id)
101
+ end
102
+
103
+ def add(i, j)
104
+ self.client.add(i, j)
105
+ end
106
+ end
107
+ ```
108
+
109
+ Another config under initializers
110
+
111
+ ```ruby
112
+ #initializers/thrift.rb
113
+ Dir["#{Rails.root}/lib/thrift/**/*.rb"].each { |file| require file }
114
+
115
+ ThriftRack::Client.config Rails.application.class.parent.name.underscore
116
+ ```
117
+
118
+ what happend?
119
+
120
+ * Client will auto generate a `rpc_id` to tracer rpc request
121
+ * Client will add app_name to header
122
+ * Client use HTTP 1.1, wich persistent tcp connection with multi http requests
123
+ * Client has a connection pool, network error will auto retry
124
+ * Client will log each rpc process time
125
+
126
+ #### Furthermore
127
+
128
+ you chould write a supperclass like under, other client inherit superclass
129
+
130
+ 1. you could write a around_action to set request
131
+
132
+ ```ruby
133
+ class ApplicationController < ActionController::API
134
+ around_action :set_thread_local_request
135
+
136
+ private
137
+
138
+ def set_thread_local_request
139
+ Thread.current["request"] = request
140
+ yield
141
+ ensure
142
+ Thread.current["request"] = nil
143
+ end
144
+ end
145
+ ```
146
+
147
+ 2. write a superclass
148
+
149
+ ```ruby
150
+ class ClientBase
151
+ def initialize(req = nil)
152
+ if req
153
+ @request_id = req.request_id
154
+ end
155
+ end
156
+
157
+ def request_id
158
+ @request_id ||= Thread.current["request"] ? Thread.current["request"].request_id : "no-request-id"
159
+ end
160
+
161
+ def client
162
+ ThriftRack::Client.new("http:127.0.0.1:3000/#{_namespace.underscore}", _client_class, request_id)
163
+ end
164
+
165
+ def respond_to_missing?(method, include_private = false)
166
+ self.client.respond_to?(method)
167
+ end
168
+
169
+ def method_missing(method, *args)
170
+ return super unless self.client.respond_to?(method)
171
+ self.class_eval do
172
+ define_method method.to_sym do |*params|
173
+ self.client.public_send(method, *params)
174
+ end
175
+ end
176
+ self.public_send(method, *args)
177
+ end
178
+
179
+ private
180
+
181
+ def _namespace
182
+ @namespace ||= self.class.name
183
+ end
184
+
185
+ def _client_class
186
+ "Thrift::#{_namespace}::#{_namespace}Service::Client".constantize
187
+ end
188
+
189
+ class << self
190
+ def default
191
+ self.new
192
+ end
193
+
194
+ def respond_to_missing?(method, include_private = false)
195
+ self.default.respond_to?(method)
196
+ end
197
+
198
+ def method_missing(method, *params)
199
+ return super unless self.default.respond_to?(method)
200
+ define_singleton_method method.to_sym do |*args|
201
+ self.default.public_send(method, *args)
202
+ end
203
+ self.public_send(method, *params)
204
+ end
205
+ end
206
+ end
207
+ ```
208
+
209
+ 3. inherit super class
210
+
211
+ ```ruby
212
+ class Math < ClientBase
213
+ def add(i, j)
214
+ self.client.add(i, j)
215
+ end
216
+ end
217
+ ```
218
+
219
+ 4. use
220
+
221
+ ```
222
+ Math.add(1, 2) # will auto assign request_id is with request
223
+ ```
26
224
 
27
225
  ## Development
28
226
 
@@ -0,0 +1,13 @@
1
+ class ThriftRack
2
+ class ActiveRecord
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ ::ActiveRecord::Base.connection_pool.with_connection do
9
+ @app.call(env)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ class ThriftRack
2
+ class Atom
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ req = Rack::Request.new(env)
9
+ rpc_id = req.env["HTTP_X_RPC_ID"]
10
+ if rpc_id
11
+ start_time = Time.now
12
+ valid = ThriftRack.redis.set("thrift_rack:atom:#{rpc_id}", true, nx: true, ex: 180)
13
+ if valid
14
+ env["ATOM_DURATION"] = ((Time.now - start_time) * 1000).round(4)
15
+ @app.call(env)
16
+ else
17
+ [409, {}, ["RPC Request Processed"]]
18
+ end
19
+ else
20
+ @app.call(env)
21
+ end
22
+ end
23
+
24
+ class << self
25
+ # compatibility with old version
26
+ def redis=(r)
27
+ ThriftRack.redis = r
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,67 +1,101 @@
1
1
  require 'securerandom'
2
2
  class ThriftRack
3
3
  class Client
4
- def initialize(url, client_klass, request_id = nil)
5
- @request_id = request_id || "no-request"
4
+ DEFAULT_REQUEST_ID = "no-request".freeze
5
+ def initialize(url, client_klass, request = nil)
6
+ if request.is_a?(ActionDispatch::Request)
7
+ @request = request
8
+ @request_id = request.request_id
9
+ else
10
+ @request_id = request || DEFAULT_REQUEST_ID
11
+ end
6
12
  @url = url
7
13
  @transport = ThriftRack::HttpClientTransport.new(url)
8
14
  protocol = protocol_factory.get_protocol(@transport)
9
15
  @client = client_klass.new(protocol)
10
16
  end
11
17
 
12
- def logger
13
- @logger ||= (defined? Rails) ? rails_logger : std_logger
14
- end
15
-
16
18
  def protocol_factory
17
19
  Thrift::CompactProtocolFactory.new
18
20
  end
19
21
 
22
+ def respond_to_missing?(method, _include_private = false)
23
+ @client.respond_to?(method)
24
+ end
25
+
20
26
  def method_missing(method, *params)
21
27
  return super unless @client.respond_to?(method)
28
+
22
29
  self.class_eval do
23
30
  define_method method.to_sym do |*args|
24
31
  begin
25
32
  rpc_id = SecureRandom.uuid
26
33
  request_at = Time.now
27
- @transport.add_headers({"X-Request-ID" => @request_id, "X-Rpc-ID" => rpc_id, "X-Rpc-Func" => method.to_s, "X-From" => ThriftRack::Client.app_name || "unknown"})
34
+ if Thread.current["RPC_FULL_TRACE"].to_s == "true"
35
+ full_trace = true
36
+ else
37
+ full_trace = @request_id == DEFAULT_REQUEST_ID || @request_id.hash % 8 == 0
38
+ end
39
+ @transport.add_headers("X-Request-ID" => @request_id, "X-Rpc-ID" => rpc_id, "X-Rpc-Func" => method.to_s, "X-From" => ThriftRack::Client.app_name || "unknown", "X-Full-Trace" => full_trace.to_s)
28
40
  @client.send(method, *args)
29
41
  ensure
30
42
  end_time = Time.now
31
- self.logger.info(JSON.dump({
32
- request_at: request_at.iso8601(6),
33
- request_id: @request_id,
34
- rpc_id: rpc_id,
35
- duration: ((end_time - request_at) * 1000).round(4),
36
- path: URI(@url).path,
37
- func: method,
38
- }))
43
+ duration = (end_time - request_at) * 1000
44
+ process_duration = @transport.response_headers["x-server-process-duration"]&.to_f
45
+ if full_trace || duration >= 100
46
+ ThriftRack::Client.logger.info(
47
+ JSON.dump(
48
+ request_at: request_at.iso8601(6),
49
+ request_id: @request_id,
50
+ rpc_id: rpc_id,
51
+ duration: duration.round(4),
52
+ path: URI(@url).path,
53
+ func: method,
54
+ tag: ThriftRack::Client.logger_tag,
55
+ full_trace: full_trace,
56
+ extra_context: @request ? { context: "action_controller", controller: @request.params["controller"], action: @request.params["action"] } : {},
57
+ server: {
58
+ id: @transport.response_headers["x-server-id"],
59
+ private_ip: @transport.response_headers["x-server-private-ip"],
60
+ process_duration: process_duration ? process_duration.round(4) : nil,
61
+ network_duration: process_duration ? (duration - process_duration).round(4) : nil,
62
+ },
63
+ ),
64
+ )
65
+ end
39
66
  end
40
67
  end
41
68
  end
42
69
  self.public_send(method, *params)
43
70
  end
44
71
 
45
- private
72
+ class << self
73
+ attr_writer :app_name, :logger_tag
46
74
 
47
- def rails_logger
48
- file = File.open("#{Rails.root}/log/rpc_client.log", File::WRONLY | File::APPEND | File::CREAT)
49
- file.sync = true
50
- ActiveSupport::Logger.new(file)
51
- end
75
+ def app_name
76
+ @app_name ||= Rails.application.class.parent.name.underscore if defined? Rails
77
+ @app_name
78
+ end
52
79
 
53
- def std_logger
54
- ::Logger.new(STDOUT)
55
- end
80
+ def logger_tag
81
+ @logger_tag || {}
82
+ end
56
83
 
57
- class << self
58
- attr_accessor :app_name, :pool_size
84
+ def logger
85
+ @logger ||= if defined? Rails
86
+ ActiveSupport::Logger.new(File.open("#{Rails.root}/log/rpc_client.log", File::WRONLY | File::APPEND | File::CREAT))
87
+ else
88
+ ::Logger.new(STDOUT)
89
+ end
90
+ end
59
91
 
60
- def pool_size=(p)
61
- http = Net::HTTP::Persistent.new(name: self.app_name, pool_size: p)
62
- http.verify_mode = 0
63
- HttpClientTransport.default = http
64
- @pool_size = p
92
+ def config(app_name, max_requests: 100, logger_tag: {})
93
+ self.app_name = app_name
94
+ self.logger_tag = logger_tag
95
+ HttpClientTransport.default = HttpClientTransport.new_http(app_name, max_requests: max_requests)
96
+ at_exit do
97
+ ThriftRack::Client.logger.close
98
+ end
65
99
  end
66
100
  end
67
101
  end
@@ -0,0 +1,46 @@
1
+ class ThriftRack
2
+ class Downgrade
3
+ def initialize(app)
4
+ @app = app
5
+ @drop_percentage = 0
6
+ @last_sync_drop_percentage_at = Time.at(0)
7
+ end
8
+
9
+ def call(env)
10
+ if rand(100) < drop_percentage
11
+ [509, {}, ["Downgrading"]]
12
+ else
13
+ @app.call(env)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def drop_percentage
20
+ if Time.now - @last_sync_drop_percentage_at > 3
21
+ @last_sync_drop_percentage_at = Time.now
22
+ @drop_percentage = self.class.current_drop_percentage
23
+ else
24
+ @drop_percentage
25
+ end
26
+ end
27
+
28
+ class << self
29
+ def change_downgrade(drop_percentage)
30
+ ThriftRack.redis.set(downgrade_redis_key, drop_percentage)
31
+ end
32
+
33
+ def current_drop_percentage
34
+ ThriftRack.redis.get(downgrade_redis_key).to_f
35
+ end
36
+
37
+ def close_downgrade
38
+ ThriftRack.redis.del(downgrade_redis_key)
39
+ end
40
+
41
+ def downgrade_redis_key
42
+ "thrift_rack:downgrade"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -6,7 +6,7 @@ class ThriftRack
6
6
 
7
7
  def call(env)
8
8
  req = Rack::Request.new(env)
9
- return Rack::Response.new(["Not Valid Thrift Request"], 400, {'Content-Type' => 'text/plain'}) unless req.post? && req.env["CONTENT_TYPE"] == THRIFT_HEADER
9
+ return 400, {'Content-Type' => 'text/plain'}, ["Not Valid Thrift Request"] unless req.post? && req.env["CONTENT_TYPE"] == THRIFT_HEADER
10
10
  @app.call(env)
11
11
  end
12
12
  end
@@ -4,9 +4,14 @@ require 'net/http/persistent'
4
4
  class ThriftRack
5
5
  class HttpClientTransport < Thrift::BaseTransport
6
6
  class RespCodeError < StandardError; end
7
+ class ProcessedRequest < RespCodeError; end
8
+ class ServerDowngradingError < RespCodeError; end
9
+
10
+ attr_accessor :response_headers
7
11
  def initialize(url, opts = {})
8
12
  @headers = {'Content-Type' => 'application/x-thrift'}
9
13
  @outbuf = Thrift::Bytes.empty_byte_buffer
14
+ @response_headers = {}
10
15
  @url = url
11
16
  end
12
17
 
@@ -18,29 +23,55 @@ class ThriftRack
18
23
  @headers = @headers.merge(headers)
19
24
  end
20
25
 
21
-
22
26
  def flush
27
+ self.response_headers = {}
23
28
  uri = URI(@url)
24
29
  post = Net::HTTP::Post.new uri.path
25
30
  post.body = @outbuf
26
31
  post.initialize_http_header(@headers)
27
- resp = ThriftRack::HttpClientTransport.default.request(uri, post)
32
+ resp = retry_request_with_503{ThriftRack::HttpClientTransport.default.request(uri, post)}
28
33
  data = resp.body
29
- raise RespCodeError.new("#{resp.code} on #{@url} with body #{data}") unless resp.code.to_i == 200
34
+ self.response_headers = resp.header
35
+ resp_code = resp.code.to_i
36
+ if resp_code != 200
37
+ if resp_code == 409
38
+ raise ProcessedRequest, @url
39
+ elsif resp_code == 509
40
+ raise ServerDowngradingError, @url
41
+ else
42
+ raise RespCodeError, "#{resp.code} on #{@url} with body #{data}"
43
+ end
44
+ end
30
45
  data = Thrift::Bytes.force_binary_encoding(data)
31
46
  @inbuf = StringIO.new data
32
47
  ensure
33
48
  @outbuf = Thrift::Bytes.empty_byte_buffer
34
49
  end
35
50
 
51
+ def retry_request_with_503
52
+ resp = nil
53
+ 3.times do |i|
54
+ resp = yield
55
+ return resp unless resp.code.to_i == 503
56
+
57
+ sleep(0.1 * i)
58
+ ThriftRack::HttpClientTransport.default.reconnect
59
+ end
60
+ resp
61
+ end
62
+
36
63
  class << self
37
64
  attr_accessor :default
38
65
 
39
66
  def default
40
- return @default if @default
41
- @default = Net::HTTP::Persistent.new
42
- @default.verify_mode = 0
43
- @default
67
+ @default ||= new_http(ThriftRack::Client.app_name || "default")
68
+ end
69
+
70
+ def new_http(name, max_requests: 100)
71
+ http = Net::HTTP::Persistent.new(name: name)
72
+ http.max_requests = max_requests
73
+ http.verify_mode = 0
74
+ http
44
75
  end
45
76
  end
46
77
  end
@@ -0,0 +1,12 @@
1
+ class ThriftRack
2
+ class LaunchTimestamp
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ env['LAUNCH_AT'] = Time.now
9
+ @app.call(env)
10
+ end
11
+ end
12
+ end
@@ -7,37 +7,52 @@ class ThriftRack
7
7
  end
8
8
 
9
9
  def call(env)
10
- request_at = Time.now
10
+ request_at = env['LAUNCH_AT'] || Time.now
11
+ income_middleware_duration = Time.now - request_at
11
12
  req = Rack::Request.new(env)
12
13
  resp = @app.call(env)
13
14
  resp
14
15
  ensure
15
- end_time = Time.now
16
- self.logger.info(JSON.dump({
17
- request_at: request_at.iso8601(6),
18
- request_id: req.env["HTTP_X_REQUEST_ID"],
19
- rpc_id: req.env["HTTP_X_RPC_ID"],
20
- duration: ((end_time - request_at) * 1000).round(4),
21
- path: req.path,
22
- func: req.env["HTTP_X_RPC_FUNC"],
23
- from: req.env["HTTP_X_FROM"],
24
- }))
16
+ duration = ((Time.now - request_at) * 1000).round(4)
17
+ request_id = req.env["HTTP_X_REQUEST_ID"]
18
+ if req.env["HTTP_X_FULL_TRACE"]
19
+ full_trace = req.env["HTTP_X_FULL_TRACE"] == "true"
20
+ else
21
+ full_trace = request_id.hash % 8 == 0
22
+ end
23
+ if full_trace || duration >= 100
24
+ ThriftRack::Logger.logger.info(
25
+ JSON.dump(
26
+ request_at: request_at.iso8601(6),
27
+ request_id: request_id,
28
+ rpc_id: req.env["HTTP_X_RPC_ID"],
29
+ duration: duration,
30
+ income_middleware_duration: (income_middleware_duration * 1000).round(2),
31
+ atom_duration: env["ATOM_DURATION"],
32
+ path: req.path,
33
+ func: req.env["HTTP_X_RPC_FUNC"],
34
+ from: req.env["HTTP_X_FROM"],
35
+ full_trace: full_trace,
36
+ tag: Logger.tag,
37
+ ),
38
+ )
39
+ end
25
40
  end
26
41
 
27
- def logger
28
- @logger ||= (defined? Rails) ? rails_logger : std_logger
29
- end
30
-
31
- private
42
+ class << self
43
+ attr_writer :tag
32
44
 
33
- def rails_logger
34
- file = File.open("#{Rails.root}/log/rpc.log", File::WRONLY | File::APPEND | File::CREAT)
35
- file.sync = true
36
- ActiveSupport::Logger.new(file)
37
- end
45
+ def logger
46
+ @logger ||= if defined? Rails
47
+ ActiveSupport::Logger.new(File.open("#{Rails.root}/log/rpc.log", File::WRONLY | File::APPEND | File::CREAT))
48
+ else
49
+ ::Logger.new(STDOUT)
50
+ end
51
+ end
38
52
 
39
- def std_logger
40
- ::Logger.new(STDOUT)
53
+ def tag
54
+ @tag ||= {}
55
+ end
41
56
  end
42
57
  end
43
58
  end
@@ -6,7 +6,7 @@ class ThriftRack
6
6
 
7
7
  def call(env)
8
8
  req = Rack::Request.new(env)
9
- return Rack::Response.new(["PONG"], 200, {'Content-Type' => 'text/plain'}) if req.path == "/ping"
9
+ return 200, {'Content-Type' => 'text/plain'}, ["PONG"] if req.path == "/ping"
10
10
  @app.call(env)
11
11
  end
12
12
  end
@@ -0,0 +1,13 @@
1
+ class ThriftRack
2
+ class Sentry
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ req = Rack::Request.new(env)
9
+ Raven.extra_context(request_id: req.env["HTTP_X_REQUEST_ID"], rpc_id: req.env["HTTP_X_RPC_ID"], from: req.env["HTTP_X_FROM"])
10
+ @app.call(env)
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,9 @@
1
1
  class ThriftRack
2
2
  class Server
3
+ def initialize(request = nil)
4
+ @_request = request
5
+ end
6
+
3
7
  class << self
4
8
  def inherited(subclass)
5
9
  warn "Your class should end with Server not it is #{subclass}" unless subclass.name.end_with?("Server")
@@ -22,7 +26,7 @@ class ThriftRack
22
26
  if Kernel.const_defined?(promissory_class_name)
23
27
  Kernel.const_get(promissory_class_name)
24
28
  else
25
- raise "You should overwrite processor_class for #{self.class}"
29
+ raise "You should overwrite processor_class for #{self}"
26
30
  end
27
31
  end
28
32
 
@@ -0,0 +1,28 @@
1
+ class ThriftRack
2
+ class ServerMetric
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ request_at = env['LAUNCH_AT'] || Time.now
9
+ status, headers, body = @app.call(env)
10
+ headers["x-server-process-duration"] = ((Time.now - request_at) * 1000).to_s
11
+ headers["x-server-id"] = self.class.server_id
12
+ headers["x-server-private-ip"] = self.class.server_private_ip
13
+ [status, headers, body]
14
+ end
15
+
16
+ class << self
17
+ attr_writer :server_id, :server_private_ip
18
+
19
+ def server_id
20
+ @server_id || "unkonwn"
21
+ end
22
+
23
+ def server_private_ip
24
+ @server_private_ip || "0.0.0.0"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  class ThriftRack
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.6"
3
3
  end
data/lib/thrift_rack.rb CHANGED
@@ -1,16 +1,23 @@
1
1
  require "thrift_rack/version"
2
2
  require "thrift_rack/server"
3
+ require 'thrift_rack/sentry'
3
4
  require "thrift_rack/logger"
4
5
  require "thrift_rack/client"
6
+ require 'thrift_rack/launch_timestamp'
5
7
  require 'thrift_rack/ping'
8
+ require 'thrift_rack/atom'
6
9
  require 'thrift_rack/format_check'
10
+ require 'thrift_rack/server_metric'
7
11
  require 'thrift_rack/http_client_transport'
12
+ require 'thrift_rack/downgrade'
13
+ require 'thrift_rack/active_record'
8
14
 
9
15
  require 'rack'
10
16
  require 'thrift'
17
+ require 'redis'
11
18
 
12
19
  class ThriftRack
13
- THRIFT_HEADER = "application/x-thrift"
20
+ THRIFT_HEADER = "application/x-thrift".freeze
14
21
 
15
22
  def initialize(servers = nil)
16
23
  servers ||= ThriftRack::Server.children
@@ -22,23 +29,41 @@ class ThriftRack
22
29
 
23
30
  def call(env)
24
31
  req = Rack::Request.new(env)
32
+ Thread.current["request"] = req
25
33
  server_class = @maps[req.path]
26
- return Rack::Response.new(["No Thrift Server For #{req.path}"], 404, {'Content-Type' => 'text/plain'}) unless server_class
34
+ return 400, { 'Content-Type' => 'text/plain' }, ["No Thrift Server For #{req.path}"] unless server_class
27
35
 
28
- resp = Rack::Response.new([], 200, {'Content-Type' => THRIFT_HEADER})
36
+ resp = Rack::Response.new([], 200, { 'Content-Type' => THRIFT_HEADER })
29
37
 
30
38
  transport = Thrift::IOStreamTransport.new req.body, resp
31
39
  protocol = server_class.protocol_factory.get_protocol transport
32
- server_class.processor_class.new(server_class.new).process(protocol, protocol)
40
+ server_class.processor_class.new(server_class.new(req)).process(protocol, protocol)
33
41
 
34
- resp
42
+ resp_a = resp.to_a
43
+ [resp_a[0], resp_a[1], [resp_a[2].join]]
44
+ ensure
45
+ Thread.current["request"] = nil
35
46
  end
47
+ class << self
48
+ attr_writer :redis
36
49
 
37
- def self.app(servers = nil)
38
- Rack::Builder.new(ThriftRack.new(servers)) do
39
- use ThriftRack::Ping
40
- use ThriftRack::FormatCheck
41
- use ThriftRack::Logger
50
+ def app(servers = nil)
51
+ Rack::Builder.new(ThriftRack.new(servers)) do
52
+ use ThriftRack::LaunchTimestamp
53
+ use ThriftRack::ActiveRecord if defined? ::ActiveRecord::Base
54
+ # use(ActionDispatch::Executor, ::Rails.application.executor) if defined? ::Rails
55
+ use ThriftRack::Downgrade
56
+ use ThriftRack::Ping
57
+ use ThriftRack::FormatCheck
58
+ use ThriftRack::Atom
59
+ use ThriftRack::Logger
60
+ use ThriftRack::Sentry if defined? Raven
61
+ use ThriftRack::ServerMetric
62
+ end
63
+ end
64
+
65
+ def redis
66
+ @redis ||= Redis.new
42
67
  end
43
68
  end
44
69
  end
data/thrift_rack.gemspec CHANGED
@@ -21,11 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_dependency "rack", ">= 2.0"
25
- spec.add_dependency "thrift", '~> 0.10.0'
24
+ spec.add_dependency "rack", ">= 2.0.6"
25
+ spec.add_dependency "thrift", '~> 0.10'
26
26
  spec.add_dependency 'net-http-persistent', ">= 3.0"
27
+ spec.add_dependency 'redis', '>=3.0'
27
28
 
28
29
  spec.add_development_dependency "bundler", "~> 1.16"
29
- spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rake", "~> 13.0"
30
31
  spec.add_development_dependency "pry", "~> 0"
31
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thrift_rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - xuxiangyang
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-07 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: 2.0.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: 2.0.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: thrift
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.10.0
33
+ version: '0.10'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.10.0
40
+ version: '0.10'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: net-http-persistent
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +86,14 @@ dependencies:
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: '10.0'
89
+ version: '13.0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: '10.0'
96
+ version: '13.0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: pry
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -111,19 +125,25 @@ files:
111
125
  - config.ru
112
126
  - lib/config.ru
113
127
  - lib/thrift_rack.rb
128
+ - lib/thrift_rack/active_record.rb
129
+ - lib/thrift_rack/atom.rb
114
130
  - lib/thrift_rack/client.rb
131
+ - lib/thrift_rack/downgrade.rb
115
132
  - lib/thrift_rack/format_check.rb
116
133
  - lib/thrift_rack/http_client_transport.rb
134
+ - lib/thrift_rack/launch_timestamp.rb
117
135
  - lib/thrift_rack/logger.rb
118
136
  - lib/thrift_rack/ping.rb
137
+ - lib/thrift_rack/sentry.rb
119
138
  - lib/thrift_rack/server.rb
139
+ - lib/thrift_rack/server_metric.rb
120
140
  - lib/thrift_rack/version.rb
121
141
  - thrift_rack.gemspec
122
142
  homepage: https://github.com/xuxiangyang/thrift_rack
123
143
  licenses:
124
144
  - MIT
125
145
  metadata: {}
126
- post_install_message:
146
+ post_install_message:
127
147
  rdoc_options: []
128
148
  require_paths:
129
149
  - lib
@@ -138,9 +158,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
158
  - !ruby/object:Gem::Version
139
159
  version: '0'
140
160
  requirements: []
141
- rubyforge_project:
142
- rubygems_version: 2.6.14
143
- signing_key:
161
+ rubygems_version: 3.0.8
162
+ signing_key:
144
163
  specification_version: 4
145
164
  summary: thrift http rakc server
146
165
  test_files: []