Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ jobs:

- name: Upload ruby precross artifacts
# has to match the version used in cross-test
if: matrix.ruby-version == '2.7' && matrix.skip-build-ext == false
if: matrix.ruby-version == '4.0' && matrix.skip-build-ext == false
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: rb-precross
Expand All @@ -980,10 +980,12 @@ jobs:
strategy:
matrix:
# kotlin cross test are failing -> see THRIFT-5879
server_lang: ['java', 'go', 'rs', 'cpp', 'py', 'rb', 'php', 'nodejs', 'nodets']
server_lang: ['java', 'go', 'rs', 'cpp', 'py', 'rb', 'rb.thin', 'rb.puma', 'rb.falcon', 'php', 'nodejs', 'nodets']
# we always use comma join as many client langs as possible, to reduce the number of jobs
client_lang: ['java,kotlin', 'go,rs,cpp,py,php,nodejs,nodets,rb']
fail-fast: false
env:
BUNDLE_WITH: falcon_http_server
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down Expand Up @@ -1019,7 +1021,7 @@ jobs:

- uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314.0
with:
ruby-version: "2.7"
ruby-version: "4.0"
bundler-cache: true
working-directory: test/rb

Expand Down
40 changes: 40 additions & 0 deletions lib/rb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ server = Thrift::ThreadedServer.new(processor, server_transport,
server.serve
```

## Rack HTTP Endpoint

Ruby HTTP transport can be mounted as a Rack application, so applications can
run Thrift on an existing Rack server such as Puma or Falcon.
The examples below use the generated `Calculator::Processor` and the
`CalculatorHandler` from [Basic Server Usage](#basic-server-usage).

```ruby
# config.ru
require 'thrift'
require 'thrift/server/rack_application'

processor = Calculator::Processor.new(CalculatorHandler.new)
run Thrift::RackApplication.new(processor)
```

For Rails or another Rack router, mount the endpoint at the route that should
receive Thrift HTTP requests:

```ruby
# config/routes.rb
processor = Calculator::Processor.new(CalculatorHandler.new)
mount Thrift::RackApplication.new(processor) => "/thrift"
```

## Development and Tests

- `bundle exec rake spec` runs the Ruby specs. It expects a built Thrift
Expand All @@ -117,6 +142,21 @@ server.serve

## Breaking Changes

### 0.25.0

Ruby HTTP servers now share one Rack endpoint. Run `Thrift::RackApplication`
as a Rack app, or mount it at one path in an app such as Rails. The
cross-language HTTP tests now cover Puma and Falcon.

`Thrift::MongrelHTTPServer` has been removed because Mongrel no longer works
with supported Ruby versions. To migrate, mount `Thrift::RackApplication` in
a supported Rack server. See [Rack HTTP Endpoint](#rack-http-endpoint).

`Thrift::ThinHTTPServer` is deprecated because Thin is no longer maintained.
Its EventMachine dependency also does not build on new Ruby development
versions. Creating a Thin server now prints a warning. Move to
`Thrift::RackApplication` with a supported Rack server such as Puma or Falcon.

### 0.24.0

Connect timeout handling changed for both `Thrift::Socket` and
Expand Down
61 changes: 0 additions & 61 deletions lib/rb/lib/thrift/server/mongrel_http_server.rb

This file was deleted.

69 changes: 69 additions & 0 deletions lib/rb/lib/thrift/server/rack_application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

require 'rack'
require 'thrift/transport/io_stream_transport'

module Thrift
class RackApplication
THRIFT_HEADER = "application/x-thrift"

def initialize(processor, protocol_factory = BinaryProtocolFactory.new)
@processor = processor
@protocol_factory = protocol_factory
end

def call(env)
request = Rack::Request.new(env)

if self.class.valid_thrift_request?(request)
self.class.successful_request(request, @processor, @protocol_factory).finish
else
self.class.failed_request.finish
end
end

def self.mapped(path, processor, protocol_factory = BinaryProtocolFactory.new)
Rack::Builder.new do
use Rack::ContentLength
map path do
run RackApplication.new(processor, protocol_factory)
end
end
end

def self.successful_request(rack_request, processor, protocol_factory)
response = Rack::Response.new([], 200, {Rack::CONTENT_TYPE => THRIFT_HEADER})
transport = IOStreamTransport.new(rack_request.body, response)
protocol = protocol_factory.get_protocol(transport)
processor.process protocol, protocol

response
end

def self.failed_request
Rack::Response.new(['Not Found'], 404, {Rack::CONTENT_TYPE => THRIFT_HEADER})
end

def self.valid_thrift_request?(rack_request)
rack_request.post? && rack_request.media_type == THRIFT_HEADER
end
end
end
59 changes: 18 additions & 41 deletions lib/rb/lib/thrift/server/thin_http_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@

require 'rack'
require 'thin'
require 'thrift/server/rack_application'

##
# Wraps the Thin web server to provide a Thrift server over HTTP.
# <b>DEPRECATED:</b> Use <tt>Thrift::RackApplication</tt> with a maintained Rack
# server instead.
module Thrift
class ThinHTTPServer < BaseServer

Expand All @@ -33,13 +36,26 @@ class ThinHTTPServer < BaseServer
# * :ip
# * :path
# * :protocol_factory
# * :ssl
# * :ssl_options
def initialize(processor, options = {})
Kernel.warn "[DEPRECATION WARNING] `Thrift::ThinHTTPServer` is deprecated because Thin is no longer maintained. Please use `Thrift::RackApplication` with a maintained Rack server instead."
port = options[:port] || 80
ip = options[:ip] || "0.0.0.0"
path = options[:path] || "/"
protocol_factory = options[:protocol_factory] || BinaryProtocolFactory.new
app = RackApplication.for(path, processor, protocol_factory)
endpoint = RackApplication.mapped(path, processor, protocol_factory)
app = Rack::Builder.new do
use Rack::CommonLogger
use Rack::ShowExceptions
use Rack::Lint
run endpoint
end
@server = Thin::Server.new(ip, port, app)
if options[:ssl]
@server.ssl = true
@server.ssl_options = options[:ssl_options] || {}
end
end

##
Expand All @@ -48,45 +64,6 @@ def serve
@server.start
end

class RackApplication

THRIFT_HEADER = "application/x-thrift"

def self.for(path, processor, protocol_factory)
Rack::Builder.new do
use Rack::CommonLogger
use Rack::ShowExceptions
use Rack::Lint
map path do
run lambda { |env|
request = Rack::Request.new(env)
if RackApplication.valid_thrift_request?(request)
RackApplication.successful_request(request, processor, protocol_factory).finish
else
RackApplication.failed_request.finish
end
}
end
end
end

def self.successful_request(rack_request, processor, protocol_factory)
response = Rack::Response.new([], 200, {'Content-Type' => THRIFT_HEADER})
transport = IOStreamTransport.new rack_request.body, response
protocol = protocol_factory.get_protocol transport
processor.process protocol, protocol
response
end

def self.failed_request
Rack::Response.new(['Not Found'], 404, {'Content-Type' => THRIFT_HEADER})
end

def self.valid_thrift_request?(rack_request)
rack_request.post? && rack_request.env["CONTENT_TYPE"] == THRIFT_HEADER
end

end

RackApplication = Thrift::RackApplication
end
end
6 changes: 5 additions & 1 deletion lib/rb/lib/thrift/transport/http_client_transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def initialize(url, opts = {})
@headers = {'Content-Type' => 'application/x-thrift'}
@outbuf = Bytes.empty_byte_buffer
@ssl_verify_mode = opts.fetch(:ssl_verify_mode, OpenSSL::SSL::VERIFY_PEER)
@ssl_ca_file = opts[:ssl_ca_file]
end

def open?; true end
Expand All @@ -46,7 +47,10 @@ def add_headers(headers)
def flush
http = Net::HTTP.new @url.host, @url.port
http.use_ssl = @url.scheme == 'https'
http.verify_mode = @ssl_verify_mode if @url.scheme == 'https'
if @url.scheme == 'https'
http.verify_mode = @ssl_verify_mode
http.ca_file = @ssl_ca_file if @ssl_ca_file
end
resp = http.post(@url.request_uri, @outbuf, @headers)
raise TransportException.new(TransportException::UNKNOWN, "#{self.class.name} Could not connect to #{@url}, HTTP status code #{resp.code.to_i}") unless (200..299).include?(resp.code.to_i)

Expand Down
23 changes: 23 additions & 0 deletions lib/rb/spec/http_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,28 @@
client.flush
expect(client.read(4)).to eq("data")
end

it "should set the SSL CA file when specified" do
client = Thrift::HTTPClientTransport.new("#{@server_uri}#{@service_path}",
:ssl_ca_file => "/path/to/ca.pem")

client.write "test"
expect(Net::HTTP).to receive(:new).with("my.domain.com", 443) do
double("Net::HTTP").tap do |http|
expect(http).to receive(:use_ssl=).with(true)
expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
expect(http).to receive(:ca_file=).with("/path/to/ca.pem")
expect(http).to receive(:post).with(@service_path, "test",
{"Content-Type" => "application/x-thrift"}) do
double("Net::HTTPOK").tap do |response|
expect(response).to receive(:body).and_return "data"
expect(response).to receive(:code).and_return "200"
end
end
end
end
client.flush
expect(client.read(4)).to eq("data")
end
end
end
Loading
Loading