| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame^] | 1 | # Copyright 2014, Google Inc. | 
|  | 2 | # All rights reserved. | 
|  | 3 | # | 
|  | 4 | # Redistribution and use in source and binary forms, with or without | 
|  | 5 | # modification, are permitted provided that the following conditions are | 
|  | 6 | # met: | 
|  | 7 | # | 
|  | 8 | #     * Redistributions of source code must retain the above copyright | 
|  | 9 | # notice, this list of conditions and the following disclaimer. | 
|  | 10 | #     * Redistributions in binary form must reproduce the above | 
|  | 11 | # copyright notice, this list of conditions and the following disclaimer | 
|  | 12 | # in the documentation and/or other materials provided with the | 
|  | 13 | # distribution. | 
|  | 14 | #     * Neither the name of Google Inc. nor the names of its | 
|  | 15 | # contributors may be used to endorse or promote products derived from | 
|  | 16 | # this software without specific prior written permission. | 
|  | 17 | # | 
|  | 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | 19 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | 20 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | 21 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | 22 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | 23 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | 24 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | 25 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | 26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | 27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | 29 |  | 
|  | 30 | #!/usr/bin/env ruby | 
|  | 31 | # | 
|  | 32 | # Sample gRPC Ruby server that implements the Math::Calc service and helps | 
|  | 33 | # validate GRPC::RpcServer as GRPC implementation using proto2 serialization. | 
|  | 34 | # | 
|  | 35 | # Usage: $ path/to/math_server.rb | 
|  | 36 |  | 
|  | 37 | this_dir = File.expand_path(File.dirname(__FILE__)) | 
|  | 38 | lib_dir = File.join(File.dirname(this_dir), 'lib') | 
|  | 39 | $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) | 
|  | 40 | $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) | 
|  | 41 |  | 
|  | 42 | require 'forwardable' | 
|  | 43 | require 'grpc' | 
|  | 44 | require 'grpc/generic/service' | 
|  | 45 | require 'grpc/generic/rpc_server' | 
|  | 46 | require 'math.pb' | 
|  | 47 |  | 
|  | 48 | # Holds state for a fibonacci series | 
|  | 49 | class Fibber | 
|  | 50 |  | 
|  | 51 | def initialize(limit) | 
|  | 52 | raise "bad limit: got #{limit}, want limit > 0" if limit < 1 | 
|  | 53 | @limit = limit | 
|  | 54 | end | 
|  | 55 |  | 
|  | 56 | def generator | 
|  | 57 | return enum_for(:generator) unless block_given? | 
|  | 58 | idx, current, previous = 0, 1, 1 | 
|  | 59 | until idx == @limit | 
|  | 60 | if idx == 0 || idx == 1 | 
|  | 61 | yield Math::Num.new(:num => 1) | 
|  | 62 | idx += 1 | 
|  | 63 | next | 
|  | 64 | end | 
|  | 65 | tmp = current | 
|  | 66 | current = previous + current | 
|  | 67 | previous = tmp | 
|  | 68 | yield Math::Num.new(:num => current) | 
|  | 69 | idx += 1 | 
|  | 70 | end | 
|  | 71 | end | 
|  | 72 | end | 
|  | 73 |  | 
|  | 74 | # A EnumeratorQueue wraps a Queue to yield the items added to it. | 
|  | 75 | class EnumeratorQueue | 
|  | 76 | extend Forwardable | 
|  | 77 | def_delegators :@q, :push | 
|  | 78 |  | 
|  | 79 | def initialize(sentinel) | 
|  | 80 | @q = Queue.new | 
|  | 81 | @sentinel = sentinel | 
|  | 82 | end | 
|  | 83 |  | 
|  | 84 | def each_item | 
|  | 85 | return enum_for(:each_item) unless block_given? | 
|  | 86 | loop do | 
|  | 87 | r = @q.pop | 
|  | 88 | break if r.equal?(@sentinel) | 
|  | 89 | raise r if r.is_a?Exception | 
|  | 90 | yield r | 
|  | 91 | end | 
|  | 92 | end | 
|  | 93 |  | 
|  | 94 | end | 
|  | 95 |  | 
|  | 96 | # The Math::Math:: module occurs because the service has the same name as its | 
|  | 97 | # package. That practice should be avoided by defining real services. | 
|  | 98 | class Calculator < Math::Math::Service | 
|  | 99 |  | 
|  | 100 | def div(div_args, call) | 
|  | 101 | if div_args.divisor == 0 | 
|  | 102 | # To send non-OK status handlers raise a StatusError with the code and | 
|  | 103 | # and detail they want sent as a Status. | 
|  | 104 | raise GRPC::StatusError.new(GRPC::Status::INVALID_ARGUMENT, | 
|  | 105 | 'divisor cannot be 0') | 
|  | 106 | end | 
|  | 107 |  | 
|  | 108 | Math::DivReply.new(:quotient => div_args.dividend/div_args.divisor, | 
|  | 109 | :remainder => div_args.dividend % div_args.divisor) | 
|  | 110 | end | 
|  | 111 |  | 
|  | 112 | def sum(call) | 
|  | 113 | # the requests are accesible as the Enumerator call#each_request | 
|  | 114 | nums = call.each_remote_read.collect { |x| x.num } | 
|  | 115 | sum = nums.inject { |sum,x| sum + x } | 
|  | 116 | Math::Num.new(:num => sum) | 
|  | 117 | end | 
|  | 118 |  | 
|  | 119 | def fib(fib_args, call) | 
|  | 120 | if fib_args.limit < 1 | 
|  | 121 | raise StatusError.new(Status::INVALID_ARGUMENT, 'limit must be >= 0') | 
|  | 122 | end | 
|  | 123 |  | 
|  | 124 | # return an Enumerator of Nums | 
|  | 125 | Fibber.new(fib_args.limit).generator() | 
|  | 126 | # just return the generator, GRPC::GenericServer sends each actual response | 
|  | 127 | end | 
|  | 128 |  | 
|  | 129 | def div_many(requests) | 
|  | 130 | # requests is an lazy Enumerator of the requests sent by the client. | 
|  | 131 | q = EnumeratorQueue.new(self) | 
|  | 132 | t = Thread.new do | 
|  | 133 | begin | 
|  | 134 | requests.each do |req| | 
|  | 135 | logger.info("read #{req.inspect}") | 
|  | 136 | resp = Math::DivReply.new(:quotient => req.dividend/req.divisor, | 
|  | 137 | :remainder => req.dividend % req.divisor) | 
|  | 138 | q.push(resp) | 
|  | 139 | Thread::pass  # let the internal Bidi threads run | 
|  | 140 | end | 
|  | 141 | logger.info('finished reads') | 
|  | 142 | q.push(self) | 
|  | 143 | rescue StandardError => e | 
|  | 144 | q.push(e)  # share the exception with the enumerator | 
|  | 145 | raise e | 
|  | 146 | end | 
|  | 147 | end | 
|  | 148 | t.priority = -2  # hint that the div_many thread should not be favoured | 
|  | 149 | q.each_item | 
|  | 150 | end | 
|  | 151 |  | 
|  | 152 | end | 
|  | 153 |  | 
|  | 154 | def main | 
|  | 155 | host_port = 'localhost:7070' | 
|  | 156 | if ARGV.size > 0 | 
|  | 157 | host_port = ARGV[0] | 
|  | 158 | end | 
|  | 159 |  | 
|  | 160 | s = GRPC::RpcServer.new() | 
|  | 161 | s.add_http2_port(host_port) | 
|  | 162 | s.handle(Calculator) | 
|  | 163 | s.run | 
|  | 164 | end | 
|  | 165 |  | 
|  | 166 | main |