модуль DRb
Обзор
dRuby — это распределённая система объектов для Ruby. Она написана на чистом Ruby и использует собственный протокол. Не требуются дополнительные службы, помимо тех, которые предоставляют среда выполнения Ruby, такие как сокеты TCP. Она не полагается на другие распределённые системы объектов, такие как CORBA, RMI или .NET и не взаимодействует с ними.
dRuby позволяет вызывать методы в одном процессе Ruby над объектом Ruby, расположенном в другом процессе Ruby, даже на другом компьютере. Ссылки на объекты могут передаваться между процессами. Method аргументы и возвращаемые значения сохраняются и загружаются в сериализованном формате. Всё это происходит прозрачно как для вызывающего метод, так и для объекта, на котором он вызывается.
Объект в удалённом процессе локально представляется экземпляром DRb::DRbObject. Он действует как своего рода прокси для удалённого объекта. Методы, вызываемые на этом экземпляре DRbObject, передаются его удалённому объекту. Это организуется динамически во время выполнения. Нет статически объявленных интерфейсов для удалённых объектов, как в IDL CORBA.
Вызовы dRuby, сделанные в процесс, обрабатываются экземпляром DRb::DRbServer внутри этого процесса. Этот экземпляр восстанавливает вызов метода, вызывает его на указанном локальном объекте и возвращает значение удалённому вызывающему. Любой объект может получать вызовы через dRuby. Нет необходимости реализовывать специальный интерфейс или смешивать специальную функциональность. В общем случае объект также не обязательно явно регистрировать себя в DRbServer для получения вызовов dRuby.
Один процесс, желающий сделать вызовы dRuby на другой процесс, должен каким-то образом получить начальную ссылку на объект в удалённом процессе другими средствами, нежели как возвращаемое значение вызова удалённого метода, так как изначально нет ссылки на удалённый объект, на котором можно вызвать метод. Это делается путём подключения к серверу с помощью URI. Каждый DRbServer связывается с URI, таким как 'druby://example.com:8787'. У DRbServer может быть прикреплён объект, который действует как главный объект сервера. Экземпляр DRbObject может быть явно создан из URI сервера. Удалённый объект этого DRbObject будет главным объектом сервера. Этот главный объект затем может возвращать ссылки на другие объекты Ruby в процессе DRbServer.
Method вызовы, сделанные через dRuby, в основном ведут себя так же, как обычные вызовы методов Ruby, выполняемые внутри процесса. Method вызовы с блоками поддерживаются, как и поднятие исключений. В дополнение к стандартным ошибкам метода вызов dRuby также может вызвать одну из специфичных для dRuby ошибок, все из которых являются подклассами DRb::DRbError.
Любой тип объекта может передаваться как аргумент вызова dRuby или возвращаться как его возвращаемое значение. По умолчанию такие объекты сохраняются или сериализуются на локальном конце, затем загружаются или десериализуются на удалённом конце. Таким образом, удалённый конец получает копию локального объекта, а не распределённую ссылку на него; методы, вызываемые на этой копии, выполняются полностью в удалённом процессе, а не передаются обратно локальному оригинальному. Это имеет семантику, аналогичную передаче по значению.
Однако, если объект не может быть сериализован, вместо этого передаётся или возвращается ссылка на него dRuby. На удалённом конце это отобразится как экземпляр DRbObject. Все методы, вызываемые на этом удалённом прокси, передаются локальному объекту, как описано в обсуждении DRbObjects. Это имеет семантику, аналогичную обычной передаче по ссылке в Ruby.
Самый простой способ указать, что мы хотим, чтобы иначе сериализуемый объект передавался или возвращался как ссылка DRbObject, а не сериализованный и отправленный как копия, состоит в том, чтобы включить модуль смешивания DRb::DRbUndumped.
dRuby поддерживает вызов удалённых методов с блоками. Так как блоки (или скорее представляющие их объекты Proc) не сериализуемы, блок выполняется в локальном, а не в удалённом контексте. Каждое значение, переданное в блок, передаётся из удалённого объекта в локальный блок, затем значение, возвращённое каждым вызовом блока, передаётся обратно в удалённый контекст выполнения для сбора, прежде чем собранные значения будут наконец возвращены в локальный контекст как возвращаемое значение вызова метода.
Примеры использования
Для получения дополнительных примеров dRuby обратитесь к каталогу samples в полном распределении dRuby.
dRuby в режиме клиент/сервер
Это иллюстрирует настройку простой системы клиент-сервер drb. Запустите код сервера и клиента в разных терминалах, сначала запустив код сервера.
Код сервера
require 'drb/drb'
# The URI for the server to connect to
URI="druby://localhost:8787"
class TimeServer
def get_current_time
return Time.now
end
end
# The object that handles requests on the server
FRONT_OBJECT=TimeServer.new
DRb.start_service(URI, FRONT_OBJECT)
# Wait for the drb server thread to finish before exiting.
DRb.thread.join
Код клиента
require 'drb/drb' # The URI to connect to SERVER_URI="druby://localhost:8787" # Start a local DRbServer to handle callbacks. # # Not necessary for this small example, but will be required # as soon as we pass a non-marshallable object as an argument # to a dRuby call. # # Note: this must be called at least once per process to take any effect. # This is particularly important if your application forks. DRb.start_service timeserver = DRbObject.new_with_uri(SERVER_URI) puts timeserver.get_current_time
Удалённые объекты в dRuby
Этот пример иллюстрирует возвращение ссылки на объект из вызова dRuby. Экземпляры Logger находятся в процессе сервера. Ссылки на них возвращаются в процесс клиента, где на них можно вызывать методы. Эти методы выполняются в процессе сервера.
Код сервера
require 'drb/drb'
URI="druby://localhost:8787"
class Logger
# Make dRuby send Logger instances as dRuby references,
# not copies.
include DRb::DRbUndumped
def initialize(n, fname)
@name = n
@filename = fname
end
def log(message)
File.open(@filename, "a") do |f|
f.puts("#{Time.now}: #{@name}: #{message}")
end
end
end
# We have a central object for creating and retrieving loggers.
# This retains a local reference to all loggers created. This
# is so an existing logger can be looked up by name, but also
# to prevent loggers from being garbage collected. A dRuby
# reference to an object is not sufficient to prevent it being
# garbage collected!
class LoggerFactory
def initialize(bdir)
@basedir = bdir
@loggers = {}
end
def get_logger(name)
if !@loggers.has_key? name
# make the filename safe, then declare it to be so
fname = name.gsub(/[.\/\\\:]/, "_")
@loggers[name] = Logger.new(name, @basedir + "/" + fname)
end
return @loggers[name]
end
end
FRONT_OBJECT=LoggerFactory.new("/tmp/dlog")
DRb.start_service(URI, FRONT_OBJECT)
DRb.thread.join
Код клиента
require 'drb/drb'
SERVER_URI="druby://localhost:8787"
DRb.start_service
log_service=DRbObject.new_with_uri(SERVER_URI)
["loga", "logb", "logc"].each do |logname|
logger=log_service.get_logger(logname)
logger.log("Hello, world!")
logger.log("Goodbye, world!")
logger.log("=== EOT ===")
end
Безопасность
Как и во всех сетевых сервисах, безопасность необходимо учитывать при использовании dRuby. Разрешая внешний доступ к объекту Ruby, вы не только позволяете внешним клиентам вызывать определённые вами методы для этого объекта, но и по умолчанию даёте им возможность выполнять произвольный код Ruby на вашем сервере. Рассмотрите следующие моменты:
# !!! UNSAFE CODE !!!
ro = DRbObject::new_with_uri("druby://your.server.com:8989")
class << ro
undef :instance_eval # force call to be passed to remote object
end
ro.instance_eval("`rm -rf *`")
Опасности, связанные с instance_eval и его родственниками, таковы, что DRbServer должен использоваться только тогда, когда клиенты надёжны.
DRbServer можно настроить с помощью списка управления доступом, чтобы выборочно разрешить или запретить доступ с указанных IP-адресов. Основное распределение druby предоставляет класс ACL для этой цели. В общем случае этот механизм следует использовать наряду с, а не вместо, хорошим брандмауэром.
Внутренности dRuby
dRuby реализован с использованием трёх основных компонентов: сериализатор/десериализатор удалённых вызовов методов; протокол передачи данных; и отображение ID-объекта. Последние два можно напрямую, а первый косвенно заменить, чтобы обеспечить различное поведение и возможности.
Сериализация и десериализация удалённых вызовов методов выполняется экземпляром DRb::DRbMessage. Он использует модуль Marshal для сохранения вызова метода перед отправкой его по транспортному слою, а затем восстанавливает его на другом конце. Обычно нет необходимости заменять этот компонент, и нет прямого способа сделать это. Однако можно реализовать альтернативный метод сериализации в рамках реализации транспортного уровня.
Транспортный уровень отвечает за открытие клиентских и серверных сетевых соединений и пересылку запросов dRuby через них. Обычно он использует DRb::DRbMessage для управления сериализацией и десериализацией. Транспортный уровень управляется DRb::DRbProtocol. В DRbProtocol могут быть установлены несколько протоколов одновременно; выбор между ними определяется схемой dRuby URI. Протокол по умолчанию выбирается схемой 'druby:' и реализуется DRb::DRbTCPSocket. Он использует обычные сокеты TCP/IP для связи. Альтернативный протокол, использующий сокеты доменных имён, реализован в файле drb/unix.rb классом DRb::DRbUNIXSocket и выбирается схемой 'drbunix:'. Пример реализации через HTTP можно найти в примерах, прилагаемых к основному распределению dRuby.
Компонент сопоставления ID-объекта сопоставляет идентификаторы объектов dRuby с объектами, которые они ссылаются, и наоборот. Реализацию можно указать в настройках DRb::DRbServer. По умолчанию реализация предоставляется DRb::DRbIdConv. Она использует идентификатор объекта ObjectSpace в качестве его идентификатора dRuby. Это означает, что ссылка dRuby на этот объект остаётся значимой только на протяжении срока существования процесса объекта и срока существования объекта в этом процессе. Модифицированная реализация предоставляется DRb::TimerIdConv в файле drb/timeridconv.rb. Эта реализация сохраняет локальную ссылку на все экспортированные через dRuby объекты на конфигурируемый период времени (по умолчанию 10 минут), чтобы предотвратить их сборку мусора за это время. Другой пример реализации представлен в sample/name.rb в основном распределении dRuby. Это позволяет объектам указывать свой собственный идентификатор или «имя». Ссылка dRuby может быть сделана постоянной через процессы, если каждый процесс зарегистрирует объект с тем же именем dRuby.
Атрибуты
Основной локальный сервер dRuby.
Это сервер, созданный вызовом start_service.
Основной локальный сервер dRuby.
Это сервер, созданный вызовом start_service.
Методы публичного класса
# File lib/drb/drb.rb, line 1835 def config current_server.config rescue DRbServer.make_config end
Получить конфигурацию текущего сервера.
Если текущий сервер отсутствует, возвращается стандартная конфигурация. См. current_server и DRbServer::make_config.
# File lib/drb/drb.rb, line 1792 def current_server drb = Thread.current['DRb'] server = (drb && drb['server']) ? drb['server'] : @primary_server raise DRbServerNotFound unless server return server end
Получить 'текущий' сервер.
В контексте выполнения, происходящего в главном потоке сервера dRuby (как правило, в результате удалённого вызова на сервере или одном из его объектов), текущий сервер — это сам сервер. В противном случае, текущий сервер — это основной сервер.
Если вышеуказальное правило не находит сервер, генерируется ошибка DRbServerNotFound.
# File lib/drb/drb.rb, line 1937 def fetch_server(uri) @server[uri] end
Получает сервер с заданным uri.
См. также regist_server и remove_server.
# File lib/drb/drb.rb, line 1846 def front current_server.front end
Получить объект front текущего сервера.
Возникает ошибка DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1825 def here?(uri) current_server.here?(uri) rescue false # (current_server.uri rescue nil) == uri end
Является ли uri URI текущего локального сервера?
Приватные методы экземпляра
# File lib/drb/drb.rb, line 1835 def config current_server.config rescue DRbServer.make_config end
Получить конфигурацию текущего сервера.
Если текущего сервера нет, возвращается конфигурация по умолчанию. См. current_server и DRbServer::make_config.
# File lib/drb/drb.rb, line 1792 def current_server drb = Thread.current['DRb'] server = (drb && drb['server']) ? drb['server'] : @primary_server raise DRbServerNotFound unless server return server end
Получить текущий сервер.
В контексте выполнения, происходящего в главном потоке сервера dRuby (обычно, в результате удалённого вызова на сервер или один из его объектов), текущий сервер — это тот сервер. В противном случае, текущий сервер — это первичный сервер.
Если по вышеуказанному правилу сервер не найден, генерируется ошибка DRbServerNotFound.
# File lib/drb/drb.rb, line 1937 def fetch_server(uri) @server[uri] end
Возвращает сервер с заданным uri.
См. также regist_server и remove_server.
# File lib/drb/drb.rb, line 1846 def front current_server.front end
Получить объект front текущего сервера.
Если текущего сервера нет, генерируется ошибка DRbServerNotFound. См. current_server.
# File lib/drb/drb.rb, line 1825 def here?(uri) current_server.here?(uri) rescue false # (current_server.uri rescue nil) == uri end
Является ли uri URI для текущего локального сервера?
# File lib/drb/drb.rb, line 1891 def install_acl(acl) DRbServer.default_acl(acl) end
# File lib/drb/drb.rb, line 1883 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) end
Set объект преобразования идентификаторов по умолчанию.
Ожидается, что это экземпляр, такой как DRb::DRbIdConv, который отвечает на to_id и to_obj, для преобразования объектов в и из DRb ссылок.
См. DRbServer#default_id_conv.
# File lib/drb/drb.rb, line 1915
def regist_server(server)
@server[server.uri] = server
mutex.synchronize do
@primary_server = server unless @primary_server
end
end Регистрирует server с DRb.
Вызывается при создании нового DRb::DRbServer.
Если первичного сервера нет, то server становится первичным сервером.
Пример:
require 'drb' s = DRb::DRbServer.new # automatically calls regist_server DRb.fetch_server s.uri #=> #<DRb::DRbServer:0x...>
# File lib/drb/drb.rb, line 1924
def remove_server(server)
@server.delete(server.uri)
mutex.synchronize do
if @primary_server == server
@primary_server = nil
end
end
end Удаляет server из списка зарегистрированных серверов.
# File lib/drb/drb.rb, line 1771 def start_service(uri=nil, front=nil, config=nil) @primary_server = DRbServer.new(uri, front, config) end
Запустить локальный сервер dRuby.
Новый сервер dRuby станет первичным сервером, даже если в настоящее время первичным сервером является другой сервер.
uri — URI для привязки сервера. Если равно nil, сервер будет привязан к случайному порту на имени хоста по умолчанию и использовать протокол dRuby по умолчанию.
front — объект front сервера. Может быть nil.
config — конфигурация нового сервера. Может быть nil.
См. DRbServer::new.
# File lib/drb/drb.rb, line 1804 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
Остановить локальный сервер dRuby.
Действует на первичный сервер. Если в настоящее время первичный сервер не запущен, это ничто.
# File lib/drb/drb.rb, line 1872 def thread @primary_server ? @primary_server.thread : nil end
Получить поток первичного сервера.
Возвращает nil, если первичный сервер отсутствует. См. primary_server.
# File lib/drb/drb.rb, line 1863 def to_id(obj) current_server.to_id(obj) end
Получить идентификатор ссылки для объекта, используя текущий сервер.
Если текущего сервера нет, генерируется ошибка DRbServerNotFound. См. current_server.
# File lib/drb/drb.rb, line 1855 def to_obj(ref) current_server.to_obj(ref) end
Преобразовать ссылку в объект, используя текущий сервер.
Если текущего сервера нет, генерируется ошибка DRbServerNotFound. См. current_server.
# File lib/drb/drb.rb, line 1813
def uri
drb = Thread.current['DRb']
client = (drb && drb['client'])
if client
uri = client.uri
return uri if uri
end
current_server.uri
end Получить URI, определяющий локальное пространство dRuby.
Это URI текущего сервера. См. current_server.
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.