модуль 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
$SAFE = 1 # disable eval() and friends
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(/[.\/\\\:]/, "_").untaint
@loggers[name] = Logger.new(name, @basedir + "/" + fname)
end
return @loggers[name]
end
end
FRONT_OBJECT=LoggerFactory.new("/tmp/dlog")
$SAFE = 1 # disable eval() and friends
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 обычно следует запускать с $SAFE, установленным по крайней мере на уровне 1. Это отключит eval() и родственные вызовы для строк, передаваемых по сети. Приведенный выше код примера использования следует этой практике.
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 в течение настраиваемого периода времени (по умолчанию десять минут), чтобы предотвратить их сборку мусора за это время. Ещё один пример реализации находится в sample/name.rb в основном распределении dRuby. Это позволяет объектам указывать свой собственный идентификатор или «имя». Ссылка dRuby может быть сохранена в разных процессах путём регистрации каждого процесса с использованием того же имени dRuby.
Атрибуты
Основной локальный сервер dRuby.
Это сервер, созданный вызовом start_service.
Основной локальный сервер dRuby.
Это сервер, созданный вызовом start_service.
Открытые методы класса
# File lib/drb/drb.rb, line 1778 def config current_server.config rescue DRbServer.make_config end
Получить конфигурацию текущего сервера.
Если текущего сервера нет, возвращается конфигурация по умолчанию. См. current_server и DRbServer::make_config.
# File lib/drb/drb.rb, line 1735 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 1880 def fetch_server(uri) @server[uri] end
Извлекает сервер с заданным uri.
См. также regist_server и remove_server.
# File lib/drb/drb.rb, line 1789 def front current_server.front end
Получить передний объект текущего сервера.
Возникает ошибка DRbServerNotFound, если текущего сервера нет. См. current_server.
# File lib/drb/drb.rb, line 1768 def here?(uri) current_server.here?(uri) rescue false # (current_server.uri rescue nil) == uri end
Является ли uri URI для текущего локального сервера?
# File lib/drb/drb.rb, line 1826 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 1858
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 1867
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 1714 def start_service(uri=nil, front=nil, config=nil) @primary_server = DRbServer.new(uri, front, config) end
Запустить сервер dRuby локально.
Новый сервер dRuby станет основным сервером, даже если в настоящее время основным сервером является другой сервер.
uri — это URI для сервера, к которому необходимо подключиться. Если значение равно nil, сервер будет подключаться к случайному порту на имени локального хоста по умолчанию и использовать протокол dRuby по умолчанию.
front — это передний объект сервера. Это может быть nil.
config — это конфигурация для нового сервера. Это может быть nil.
См. DRbServer::new.
# File lib/drb/drb.rb, line 1747 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
Остановить локальный сервер dRuby.
Эта операция выполняется на основном сервере. Если в настоящее время не работает основной сервер, это операция без действия.
# File lib/drb/drb.rb, line 1815 def thread @primary_server ? @primary_server.thread : nil end
Получить поток основного сервера.
Возвращает nil, если основного сервера нет. См. primary_server.
# File lib/drb/drb.rb, line 1806 def to_id(obj) current_server.to_id(obj) end
Получить идентификатор ссылки для объекта, используя текущий сервер.
Возникает ошибка DRbServerNotFound, если текущего сервера нет. См. current_server.
# File lib/drb/drb.rb, line 1798 def to_obj(ref) current_server.to_obj(ref) end
Преобразовать ссылку в объект, используя текущий сервер.
Возникает ошибка DRbServerNotFound, если текущего сервера нет. См. current_server.
# File lib/drb/drb.rb, line 1756
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.
Приватные методы экземпляра
# File lib/drb/drb.rb, line 1778 def config current_server.config rescue DRbServer.make_config end
Получить конфигурацию текущего сервера.
Если текущий сервер отсутствует, возвращается конфигурация по умолчанию. См. current_server и DRbServer::make_config.
# File lib/drb/drb.rb, line 1735 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 1880 def fetch_server(uri) @server[uri] end
Получает сервер с заданным uri.
См. также regist_server и remove_server.
# File lib/drb/drb.rb, line 1789 def front current_server.front end
Получить объект front текущего сервера.
Возникает ошибка DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1768 def here?(uri) current_server.here?(uri) rescue false # (current_server.uri rescue nil) == uri end
Является ли uri URI для текущего локального сервера?
# File lib/drb/drb.rb, line 1834 def install_acl(acl) DRbServer.default_acl(acl) end
# File lib/drb/drb.rb, line 1826 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 1858
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 1867
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 1714 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 1747 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
Остановить локальный сервер dRuby.
Действует на основной сервер. Если основной сервер в данный момент не запущен, это ничто.
# File lib/drb/drb.rb, line 1815 def thread @primary_server ? @primary_server.thread : nil end
Получить поток основного сервера.
Возвращает nil, если основной сервер отсутствует. См. primary_server.
# File lib/drb/drb.rb, line 1806 def to_id(obj) current_server.to_id(obj) end
Получить идентификатор ссылки на объект, используя текущий сервер.
Возникает ошибка DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1798 def to_obj(ref) current_server.to_obj(ref) end
Преобразовать ссылку в объект, используя текущий сервер.
Возникает ошибка DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1756
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.