grpc-web 1.0.0 → 1.0.1

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 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