grpc-web 1.0.0 → 1.0.1

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
2
  SHA256:
3
- metadata.gz: 1e933d5fb31c8a64203e667339b03fe89a5b1bcfb8ddecd66c0271bef8d60f0e
4
- data.tar.gz: af6dca14b9a5ba0ada4c91f1c8cf534f408cf8be77180b2553d5418491b38908
3
+ metadata.gz: 758ff05af92607617e668f99e32a0fb1e39e8795dd99a4d617e7de4d18a593df
4
+ data.tar.gz: 0fc70d8bf94ddb52d1a747590682f56750b2c57f2add9f7206cd4e20138bb998
5
5
  SHA512:
6
- metadata.gz: 1e06359b9f0097781bf63e28b87e0c12746ab6c3e1a80b1d02a971420931f2f62d5d49b78b1629b70ae204e7ed9c5502346176661c4eff5fabcaec724665c602
7
- data.tar.gz: 18cd177d9f793d9006ee23f1a28be8879971db60a1e970d7d77e90bb193ed324279d8de3f1bdd7c58a13e31359e9b95596a54a94f57fe7a2ca18b82930ae3141
6
+ metadata.gz: 4ccebf90c39a63a496dfb7d5f518f5a7edb31c62b13b62e0fd01d6162a9804c81ee1d7032c56fbc822e1b51988339904471c0017966cbc65ef2ff8aefeeccf59
7
+ data.tar.gz: ffce3e4c465840373a5c17ca6eff72056ddd86301e3d5743b126f664e7db51ed185fcfe3468d66cf9bd2e0a26697cab4d7a3fa26c1c926f0b7a905c4b5638e21
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc_web/version'
4
- require 'grpc_web/rack_app'
4
+ require 'grpc_web/server/rack_app'
5
5
 
6
6
  module GRPCWeb
7
7
  class << self
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'grpc_web/client/client_executor'
5
+
6
+ # GRPC Client implementation
7
+ # Example usage:
8
+ #
9
+ # client = GRPCWeb::Client.new("http://localhost:3000/grpc", HelloService::Service)
10
+ # client.say_hello(name: 'James')
11
+ class GRPCWeb::Client
12
+ attr_reader :base_url, :service_interface
13
+
14
+ def initialize(base_url, service_interface)
15
+ self.base_url = base_url
16
+ self.service_interface = service_interface
17
+
18
+ service_interface.rpc_descs.each do |rpc_method, rpc_desc|
19
+ define_rpc_method(rpc_method, rpc_desc)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_writer :base_url, :service_interface
26
+
27
+ def define_rpc_method(rpc_method, rpc_desc)
28
+ ruby_method = ::GRPC::GenericService.underscore(rpc_method.to_s).to_sym
29
+ define_singleton_method(ruby_method) do |params = {}|
30
+ uri = endpoint_uri(rpc_desc)
31
+ ::GRPCWeb::ClientExecutor.request(uri, rpc_desc, params)
32
+ end
33
+ end
34
+
35
+ def endpoint_uri(rpc_desc)
36
+ URI(File.join(base_url, service_interface.service_name, rpc_desc.name.to_s))
37
+ end
38
+ end
@@ -7,6 +7,7 @@ require 'grpc/errors'
7
7
  require 'grpc_web/content_types'
8
8
  require 'grpc_web/message_framing'
9
9
 
10
+ # Client execution concerns
10
11
  module GRPCWeb::ClientExecutor
11
12
  class << self
12
13
  include ::GRPCWeb::ContentTypes
@@ -16,11 +17,13 @@ module GRPCWeb::ClientExecutor
16
17
 
17
18
  def request(uri, rpc_desc, params = {})
18
19
  req_proto = rpc_desc.input.new(params)
19
- request_body = ::GRPCWeb::MessageFraming.frame_content(req_proto.to_proto)
20
+ marshalled_proto = rpc_desc.marshal_proc.call(req_proto)
21
+ frame = ::GRPCWeb::MessageFrame.payload_frame(marshalled_proto)
22
+ request_body = ::GRPCWeb::MessageFraming.pack_frames([frame])
20
23
 
21
24
  resp = post_request(uri, request_body)
22
25
  resp_body = handle_response(resp)
23
- rpc_desc.output.decode(resp_body)
26
+ rpc_desc.unmarshal_proc(:output).call(resp_body)
24
27
  end
25
28
 
26
29
  private
@@ -35,12 +38,9 @@ module GRPCWeb::ClientExecutor
35
38
  def post_request(uri, request_body)
36
39
  request = Net::HTTP::Post.new(uri, request_headers)
37
40
  request.body = request_body
38
- if uri.userinfo
39
- request.basic_auth uri.user, uri.password
40
- end
41
+ request.basic_auth uri.user, uri.password if uri.userinfo
41
42
 
42
- Net::HTTP.start(uri.hostname, uri.port) do |http|
43
- http.use_ssl = (uri.scheme == 'https')
43
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
44
44
  http.request(request)
45
45
  end
46
46
  end
@@ -50,7 +50,7 @@ module GRPCWeb::ClientExecutor
50
50
  raise "Received #{resp.code} #{resp.message} response: #{resp.body}"
51
51
  end
52
52
 
53
- frames = ::GRPCWeb::MessageFraming.unframe_content(resp.body)
53
+ frames = ::GRPCWeb::MessageFraming.unpack_frames(resp.body)
54
54
  header_frame = frames.find(&:header?)
55
55
  headers = parse_headers(header_frame.body) if header_frame
56
56
  raise_on_error(headers)
@@ -15,5 +15,5 @@ module GRPCWeb::ContentTypes
15
15
  TEXT_CONTENT_TYPE,
16
16
  TEXT_PROTO_CONTENT_TYPE,
17
17
  ].freeze
18
- ANY_CONTENT_TYPES = ['*/*', ''].freeze
18
+ UNSPECIFIED_CONTENT_TYPES = ['*/*', '', nil].freeze
19
19
  end
@@ -1,31 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GRPCWeb
4
- class MessageFrame
5
- PAYLOAD_FRAME_TYPE = 0 # String: "\x00"
6
- HEADER_FRAME_TYPE = 128 # String: "\x80"
3
+ # Placeholder
4
+ class GRPCWeb::MessageFrame
5
+ PAYLOAD_FRAME_TYPE = 0 # String: "\x00"
6
+ HEADER_FRAME_TYPE = 128 # String: "\x80"
7
7
 
8
- def self.payload_frame(body)
9
- new(PAYLOAD_FRAME_TYPE, body)
10
- end
8
+ def self.payload_frame(body)
9
+ new(PAYLOAD_FRAME_TYPE, body)
10
+ end
11
+
12
+ def self.header_frame(body)
13
+ new(HEADER_FRAME_TYPE, body)
14
+ end
11
15
 
12
- def self.header_frame(body)
13
- new(HEADER_FRAME_TYPE, body)
14
- end
16
+ attr_accessor :frame_type, :body
15
17
 
16
- attr_accessor :frame_type, :body
18
+ def initialize(frame_type, body)
19
+ self.frame_type = frame_type
20
+ self.body = body.b # treat body as a byte string
21
+ end
17
22
 
18
- def initialize(frame_type, body)
19
- self.frame_type = frame_type
20
- self.body = body
21
- end
23
+ def payload?
24
+ frame_type == PAYLOAD_FRAME_TYPE
25
+ end
22
26
 
23
- def payload?
24
- frame_type == PAYLOAD_FRAME_TYPE
25
- end
27
+ def header?
28
+ frame_type == HEADER_FRAME_TYPE
29
+ end
26
30
 
27
- def header?
28
- frame_type == HEADER_FRAME_TYPE
29
- end
31
+ def ==(other)
32
+ frame_type == other.frame_type && body == other.body
30
33
  end
31
34
  end
@@ -1,50 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grpc_web/grpc_web_response'
4
- require 'grpc_web/grpc_web_request'
5
3
  require 'grpc_web/message_frame'
6
4
 
7
- module GRPCWeb
8
- # GRPC Web uses a simple 5 byte framing scheme. The first byte represents
9
- # flags indicating what type of frame this is. The next 4 bytes indicate the
10
- # byte length of the frame body.
11
- module MessageFraming
12
- class << self
13
- def unframe_request(request)
14
- frames = unframe_content(request.body)
15
- ::GRPCWeb::GRPCWebRequest.new(
16
- request.service, request.service_method, request.content_type, request.accept, frames,
17
- )
18
- end
19
-
20
- def frame_response(response)
21
- framed = response.body.map do |frame|
22
- frame_content(frame.body, frame.frame_type)
23
- end.join
24
- ::GRPCWeb::GRPCWebResponse.new(response.content_type, framed)
25
- end
26
-
27
- def frame_content(content, frame_type = ::GRPCWeb::MessageFrame::PAYLOAD_FRAME_TYPE)
28
- length_bytes = [content.bytesize].pack('N')
29
- "#{frame_type.chr}#{length_bytes}#{content}"
30
- end
5
+ # Placeholder
6
+ module GRPCWeb::MessageFraming
7
+ class << self
8
+ def pack_frames(frames)
9
+ frames.map do |frame|
10
+ length_bytes = [frame.body.bytesize].pack('N')
11
+ "#{frame.frame_type.chr}#{length_bytes}#{frame.body}"
12
+ end.join
13
+ end
31
14
 
32
- def unframe_content(content)
33
- frames = []
34
- remaining_content = content
35
- until remaining_content.empty?
36
- msg_length = remaining_content[1..4].unpack1('N')
37
- raise 'Invalid message length' if msg_length <= 0
15
+ def unpack_frames(content)
16
+ frames = []
17
+ remaining_content = content
18
+ until remaining_content.empty?
19
+ msg_length = remaining_content[1..4].unpack1('N')
20
+ raise 'Invalid message length' if msg_length <= 0
38
21
 
39
- frame_end = 5 + msg_length
40
- frames << ::GRPCWeb::MessageFrame.new(
41
- remaining_content[0].bytes[0],
42
- remaining_content[5...frame_end],
43
- )
44
- remaining_content = remaining_content[frame_end..-1]
45
- end
46
- frames
22
+ frame_end = 5 + msg_length
23
+ frames << ::GRPCWeb::MessageFrame.new(
24
+ remaining_content[0].bytes[0],
25
+ remaining_content[5...frame_end],
26
+ )
27
+ remaining_content = remaining_content[frame_end..-1]
47
28
  end
29
+ frames
48
30
  end
49
31
  end
50
32
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Placeholder
3
4
  module GRPCWeb
4
5
  class << self
5
6
  NOOP_ON_ERROR = proc { |ex, service, service_method| }
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc_web/content_types'
4
- require 'grpc_web/error_callback'
5
4
  require 'grpc_web/grpc_web_response'
6
- require 'grpc_web/message_framing'
7
- require 'grpc_web/message_serialization'
8
- require 'grpc_web/text_coder'
5
+ require 'grpc_web/server/request_framing'
6
+ require 'grpc_web/server/error_callback'
7
+ require 'grpc_web/server/message_serialization'
8
+ require 'grpc_web/server/text_coder'
9
9
 
10
+ # Placeholder
10
11
  module GRPCWeb::GRPCRequestProcessor
11
12
  class << self
12
13
  include ::GRPCWeb::ContentTypes
13
14
 
14
15
  def process(grpc_web_request)
15
16
  text_coder = ::GRPCWeb::TextCoder
16
- framing = ::GRPCWeb::MessageFraming
17
+ framing = ::GRPCWeb::RequestFraming
17
18
  serialization = ::GRPCWeb::MessageSerialization
18
19
 
19
20
  grpc_web_request = text_coder.decode_request(grpc_web_request)
@@ -41,7 +42,7 @@ module GRPCWeb::GRPCRequestProcessor
41
42
 
42
43
  # Use Accept header value if specified, otherwise use request content type
43
44
  def response_content_type(request)
44
- if request.accept.nil? || ANY_CONTENT_TYPES.include?(request.accept)
45
+ if UNSPECIFIED_CONTENT_TYPES.include?(request.accept)
45
46
  request.content_type
46
47
  else
47
48
  request.accept
@@ -7,6 +7,7 @@ require 'grpc_web/grpc_web_response'
7
7
  require 'grpc_web/grpc_web_request'
8
8
  require 'grpc_web/message_frame'
9
9
 
10
+ # Placeholder
10
11
  module GRPCWeb::MessageSerialization
11
12
  class << self
12
13
  include ::GRPCWeb::ContentTypes
@@ -72,7 +73,7 @@ module GRPCWeb::MessageSerialization
72
73
  # If needed, trailers can be appended to the response as a 2nd
73
74
  # base64 encoded string with independent framing.
74
75
  def generate_headers(status, message)
75
- header_str = [
76
+ [
76
77
  "grpc-status:#{status}",
77
78
  "grpc-message:#{message}",
78
79
  'x-grpc-web:1',
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/builder'
4
+ require 'grpc_web/server/rack_handler'
5
+ require 'grpc_web/server/service_class_validator'
6
+
7
+ # Can be given a service class, an instance of a service class, or a
8
+ # service interface class with a block to lazily initialize the service.
9
+ #
10
+ # Example 1:
11
+ # app.handle(TestHelloService)
12
+ #
13
+ # Example 2:
14
+ # app.handle(TestHelloService.new)
15
+ #
16
+ # Example 3:
17
+ # app.handle(HelloService::Service) do
18
+ # require 'test_hello_service'
19
+ # TestHelloService.new
20
+ # end
21
+ #
22
+ # TODO: in-mem cache the result of lazy initialization
23
+ class GRPCWeb::RackApp < ::Rack::Builder
24
+ def handle(service_or_class, &lazy_init_block)
25
+ service_class = service_or_class.is_a?(Class) ? service_or_class : service_or_class.class
26
+ GRPCWeb::ServiceClassValidator.validate(service_class)
27
+ service_config = lazy_init_block || service_or_class
28
+
29
+ service_class.rpc_descs.keys.each do |service_method|
30
+ add_service_method_to_app(service_class.service_name, service_config, service_method)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Map a path with Rack::Builder corresponding to the service method
37
+ def add_service_method_to_app(service_name, service_config, service_method)
38
+ map("/#{service_name}/#{service_method}") do
39
+ run(RouteHandler.new(service_config, service_method))
40
+ end
41
+ end
42
+
43
+ # Handles a single route
44
+ class RouteHandler
45
+ def initialize(service_config, service_method)
46
+ self.service_config = service_config
47
+ self.service_method = service_method
48
+ end
49
+
50
+ def call(env)
51
+ ::GRPCWeb::RackHandler.call(service, service_method, env)
52
+ end
53
+
54
+ private
55
+
56
+ attr_accessor :service_config, :service_method
57
+
58
+ def service
59
+ case service_config
60
+ when Proc
61
+ service_config.call
62
+ when Class
63
+ service_config.new
64
+ else
65
+ service_config
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'google/protobuf'
4
+ require 'rack'
5
+ require 'rack/request'
6
+ require 'grpc_web/content_types'
7
+ require 'grpc_web/grpc_web_request'
8
+ require 'grpc_web/server/error_callback'
9
+ require 'grpc_web/server/grpc_request_processor'
10
+
11
+ # Placeholder
12
+ module GRPCWeb::RackHandler
13
+ NOT_FOUND = 404
14
+ UNSUPPORTED_MEDIA_TYPE = 415
15
+ INTERNAL_SERVER_ERROR = 500
16
+ ACCEPT_HEADER = 'HTTP_ACCEPT'
17
+
18
+ class << self
19
+ include ::GRPCWeb::ContentTypes
20
+
21
+ def call(service, service_method, env)
22
+ rack_request = Rack::Request.new(env)
23
+ return not_found_response(rack_request.path) unless rack_request.post?
24
+ return unsupported_media_type_response unless valid_content_types?(rack_request)
25
+
26
+ content_type = rack_request.content_type
27
+ accept = rack_request.get_header(ACCEPT_HEADER)
28
+ body = rack_request.body.read
29
+ request = GRPCWeb::GRPCWebRequest.new(service, service_method, content_type, accept, body)
30
+ response = GRPCWeb::GRPCRequestProcessor.process(request)
31
+
32
+ [200, { 'Content-Type' => response.content_type }, [response.body]]
33
+ rescue Google::Protobuf::ParseError => e
34
+ invalid_response(e.message)
35
+ rescue StandardError => e
36
+ ::GRPCWeb.on_error.call(e, service, service_method)
37
+ error_response
38
+ end
39
+
40
+ private
41
+
42
+ def valid_content_types?(rack_request)
43
+ return false unless ALL_CONTENT_TYPES.include?(rack_request.content_type)
44
+
45
+ accept = rack_request.get_header(ACCEPT_HEADER)
46
+ return true if UNSPECIFIED_CONTENT_TYPES.include?(accept)
47
+
48
+ ALL_CONTENT_TYPES.include?(accept)
49
+ end
50
+
51
+ def not_found_response(path)
52
+ [
53
+ NOT_FOUND,
54
+ { 'Content-Type' => 'text/plain', 'X-Cascade' => 'pass' },
55
+ ["Not Found: #{path}"],
56
+ ]
57
+ end
58
+
59
+ def unsupported_media_type_response
60
+ [
61
+ UNSUPPORTED_MEDIA_TYPE,
62
+ { 'Content-Type' => 'text/plain' },
63
+ ['Unsupported Media Type: Invalid Content-Type or Accept header'],
64
+ ]
65
+ end
66
+
67
+ def invalid_response(message)
68
+ [422, { 'Content-Type' => 'text/plain' }, ["Invalid request format: #{message}"]]
69
+ end
70
+
71
+ def error_response
72
+ [
73
+ INTERNAL_SERVER_ERROR,
74
+ { 'Content-Type' => 'text/plain' },
75
+ ['Request failed with an unexpected error.'],
76
+ ]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_web/grpc_web_response'
4
+ require 'grpc_web/grpc_web_request'
5
+ require 'grpc_web/message_framing'
6
+
7
+ # Framing concerns for handling a request on the server
8
+ module GRPCWeb::RequestFraming
9
+ class << self
10
+ def unframe_request(request)
11
+ frames = message_framing.unpack_frames(request.body)
12
+ ::GRPCWeb::GRPCWebRequest.new(
13
+ request.service, request.service_method, request.content_type, request.accept, frames,
14
+ )
15
+ end
16
+
17
+ def frame_response(response)
18
+ frames = message_framing.pack_frames(response.body)
19
+ ::GRPCWeb::GRPCWebResponse.new(response.content_type, frames)
20
+ end
21
+
22
+ private
23
+
24
+ def message_framing
25
+ ::GRPCWeb::MessageFraming
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Validates that clazz is a grpc service class
4
+ module GRPCWeb::ServiceClassValidator
5
+ def self.validate(clazz)
6
+ unless clazz.include?(::GRPC::GenericService)
7
+ raise(ArgumentError, "#{clazz} must 'include GenericService'")
8
+ end
9
+ if clazz.rpc_descs.size.zero?
10
+ raise(ArgumentError, "#{clazz} should specify some rpc descriptions")
11
+ end
12
+
13
+ true
14
+ end
15
+ end
@@ -5,6 +5,7 @@ require 'grpc_web/content_types'
5
5
  require 'grpc_web/grpc_web_response'
6
6
  require 'grpc_web/grpc_web_request'
7
7
 
8
+ # Placeholder
8
9
  module GRPCWeb::TextCoder
9
10
  class << self
10
11
  include ::GRPCWeb::ContentTypes
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GRPCWeb
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grpc-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Shkolnik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-10 00:00:00.000000000 Z
11
+ date: 2020-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grpc
@@ -156,6 +156,20 @@ dependencies:
156
156
  - - ">="
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: webmock
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
159
173
  description: Host gRPC-Web endpoints for Ruby gRPC services in a Rack or Rails app(over
160
174
  HTTP/1.1). Client included.
161
175
  email:
@@ -166,19 +180,21 @@ extra_rdoc_files: []
166
180
  files:
167
181
  - lib/grpc-web.rb
168
182
  - lib/grpc_web.rb
169
- - lib/grpc_web/client.rb
170
- - lib/grpc_web/client_executor.rb
183
+ - lib/grpc_web/client/client.rb
184
+ - lib/grpc_web/client/client_executor.rb
171
185
  - lib/grpc_web/content_types.rb
172
- - lib/grpc_web/error_callback.rb
173
- - lib/grpc_web/grpc_request_processor.rb
174
186
  - lib/grpc_web/grpc_web_request.rb
175
187
  - lib/grpc_web/grpc_web_response.rb
176
188
  - lib/grpc_web/message_frame.rb
177
189
  - lib/grpc_web/message_framing.rb
178
- - lib/grpc_web/message_serialization.rb
179
- - lib/grpc_web/rack_app.rb
180
- - lib/grpc_web/rack_handler.rb
181
- - lib/grpc_web/text_coder.rb
190
+ - lib/grpc_web/server/error_callback.rb
191
+ - lib/grpc_web/server/grpc_request_processor.rb
192
+ - lib/grpc_web/server/message_serialization.rb
193
+ - lib/grpc_web/server/rack_app.rb
194
+ - lib/grpc_web/server/rack_handler.rb
195
+ - lib/grpc_web/server/request_framing.rb
196
+ - lib/grpc_web/server/service_class_validator.rb
197
+ - lib/grpc_web/server/text_coder.rb
182
198
  - lib/grpc_web/version.rb
183
199
  homepage: https://github.com/gusto/grpc-web-ruby
184
200
  licenses:
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
- require 'grpc_web/client_executor'
5
-
6
- module GRPCWeb
7
- class Client
8
- attr_reader :base_url, :service_interface
9
-
10
- def initialize(base_url, service_interface)
11
- self.base_url = base_url
12
- self.service_interface = service_interface
13
-
14
- service_interface.rpc_descs.each do |rpc_method, rpc_desc|
15
- define_rpc_method(rpc_method, rpc_desc)
16
- end
17
- end
18
-
19
- private
20
-
21
- attr_writer :base_url, :service_interface
22
-
23
- def define_rpc_method(rpc_method, rpc_desc)
24
- ruby_method = ::GRPC::GenericService.underscore(rpc_method.to_s).to_sym
25
- define_singleton_method(ruby_method) do |params = {}|
26
- uri = endpoint_uri(rpc_desc)
27
- ::GRPCWeb::ClientExecutor.request(uri, rpc_desc, params)
28
- end
29
- end
30
-
31
- def endpoint_uri(rpc_desc)
32
- URI(File.join(base_url, service_interface.service_name, rpc_desc.name.to_s))
33
- end
34
- end
35
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack/builder'
4
- require 'grpc_web/rack_handler'
5
-
6
- module GRPCWeb
7
- class RackApp < ::Rack::Builder
8
- # Can be given a service class, an instance of a service class, or a
9
- # service interface class with a block to lazily initialize the service.
10
- #
11
- # Example 1:
12
- # app.handle(TestHelloService)
13
- #
14
- # Example 2:
15
- # app.handle(TestHelloService.new)
16
- #
17
- # Example 3:
18
- # app.handle(HelloService::Service) do
19
- # require 'test_hello_service'
20
- # TestHelloService.new
21
- # end
22
- #
23
- def handle(service_or_class, &lazy_init_block)
24
- service_class = service_or_class.is_a?(Class) ? service_or_class : service_or_class.class
25
- validate_service_class(service_class)
26
- service_config = lazy_init_block || service_or_class
27
-
28
- service_class.rpc_descs.keys.each do |service_method|
29
- add_service_method_to_app(service_class.service_name, service_config, service_method)
30
- end
31
- end
32
-
33
- private
34
-
35
- def validate_service_class(clazz)
36
- unless clazz.include?(::GRPC::GenericService)
37
- raise(ArgumentError, "#{clazz} must 'include GenericService'")
38
- end
39
- if clazz.rpc_descs.size.zero?
40
- raise(ArgumentError, "#{clazz} should specify some rpc descriptions")
41
- end
42
- end
43
-
44
- # Map a path with Rack::Builder corresponding to the service method
45
- def add_service_method_to_app(service_name, service_config, service_method)
46
- map("/#{service_name}/#{service_method}") do
47
- run(RouteHandler.new(service_config, service_method))
48
- end
49
- end
50
-
51
- class RouteHandler
52
- def initialize(service_config, service_method)
53
- self.service_config = service_config
54
- self.service_method = service_method
55
- end
56
-
57
- def call(env)
58
- ::GRPCWeb::RackHandler.call(service, service_method, env)
59
- end
60
-
61
- private
62
-
63
- attr_accessor :service_config, :service_method
64
-
65
- def service
66
- case service_config
67
- when Proc
68
- service_config.call
69
- when Class
70
- service_config.new
71
- else
72
- service_config
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack/request'
4
- require 'grpc_web/content_types'
5
- require 'grpc_web/error_callback'
6
- require 'grpc_web/grpc_request_processor'
7
- require 'grpc_web/grpc_web_request'
8
-
9
- module GRPCWeb
10
- module RackHandler
11
- NOT_FOUND = 404
12
- UNSUPPORTED_MEDIA_TYPE = 415
13
- INTERNAL_SERVER_ERROR = 500
14
- POST = 'POST'
15
- ACCEPT_HEADER = 'HTTP_ACCEPT'
16
-
17
- class << self
18
- include ::GRPCWeb::ContentTypes
19
-
20
- def call(service, service_method, env)
21
- rack_request = Rack::Request.new(env)
22
- return not_found_response(rack_request.path) unless post?(rack_request)
23
- return unsupported_media_type_response unless valid_content_types?(rack_request)
24
-
25
- request_format = rack_request.content_type
26
- accept = rack_request.get_header(ACCEPT_HEADER)
27
- body = rack_request.body.read
28
- request = GRPCWebRequest.new(service, service_method, request_format, accept, body)
29
- response = GRPCRequestProcessor.process(request)
30
-
31
- [200, { 'Content-Type' => response.content_type }, [response.body]]
32
- rescue Google::Protobuf::ParseError => e
33
- invalid_response(e.message)
34
- rescue StandardError => e
35
- ::GRPCWeb.on_error.call(e, service, service_method)
36
- error_response
37
- end
38
-
39
- private
40
-
41
- def post?(rack_request)
42
- rack_request.request_method == POST
43
- end
44
-
45
- def valid_content_types?(rack_request)
46
- return false unless ALL_CONTENT_TYPES.include?(rack_request.content_type)
47
-
48
- accept = rack_request.get_header(ACCEPT_HEADER)
49
- return true if ANY_CONTENT_TYPES.include?(accept)
50
-
51
- ALL_CONTENT_TYPES.include?(accept)
52
- end
53
-
54
- def not_found_response(path)
55
- [
56
- NOT_FOUND,
57
- { 'Content-Type' => 'text/plain', 'X-Cascade' => 'pass' },
58
- ["Not Found: #{path}"],
59
- ]
60
- end
61
-
62
- def unsupported_media_type_response
63
- [
64
- UNSUPPORTED_MEDIA_TYPE,
65
- { 'Content-Type' => 'text/plain' },
66
- ['Unsupported Media Type: Invalid Content-Type or Accept header'],
67
- ]
68
- end
69
-
70
- def invalid_response(message)
71
- [422, { 'Content-Type' => 'text/plain' }, ["Invalid request format: #{message}"]]
72
- end
73
-
74
- def error_response
75
- [
76
- INTERNAL_SERVER_ERROR,
77
- { 'Content-Type' => 'text/plain' },
78
- ['Request failed with an unexpected error.'],
79
- ]
80
- end
81
- end
82
- end
83
- end