модуль DRb
Обзор
dRuby — это распределённая система объектов для Ruby. Она написана на чистом Ruby и использует свой собственный протокол. Дополнительные сервисы, помимо тех, что предоставляет среда выполнения Ruby, такие как сокеты TCP, не требуются. Она не полагается на другие распределённые системы объектов, такие как CORBA, RMI или .NET и не взаимодействует с ними.
dRuby позволяет вызывать методы в одном процессе Ruby над объектом Ruby, расположенным в другом процессе Ruby, даже на другом компьютере. Ссылки на объекты могут передаваться между процессами. Аргументы и возвращаемые значения методов передаются в сериализованном формате. Всё это происходит прозрачно как для вызывающей стороны удалённого метода, так и для объекта, на котором он вызывается.
Объект в удалённом процессе локально представляется экземпляром 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.
Вызовы методов через dRuby в основном ведут себя так же, как обычные вызовы методов Ruby в одном процессе. Поддерживаются вызовы методов с блоками, а также обработка исключений. Кроме стандартных ошибок метода, вызов dRuby может также генерировать специфические ошибки dRuby, которые все являются подклассами DRb::DRbError.
Любой тип объекта может быть передан в качестве аргумента вызова dRuby или возвращён в качестве его возвращаемого значения. По умолчанию такие объекты сериализуются на локальном конце, затем загружаются или десериализуются на удалённом конце. Таким образом, удалённый конец получает копию локального объекта, а не распределённую ссылку на него; методы, вызываемые над этой копией, выполняются полностью в удалённом процессе, а не передаются обратно локальному оригинальному. Это соответствует семантике передачи по значению.
Однако, если объект не может быть сериализован, вместо него передаётся или возвращается ссылка dRuby на него. На удалённом конце это будет экземпляр DRbObject. Все вызовы методов к этому удалённому прокси пересылаются локальному объекту, как описано в обсуждении DRbObject. Это соответствует семантике обычной передачи по ссылке в Ruby.
Самый простой способ указать, что мы хотим, чтобы иначе сериализуемый объект передавался или возвращался как ссылка DRb::DRbUndumped, а не сериализованной копией, — это включить модуль миксин DRb::DRbUndumped.
dRuby поддерживает вызов удалённых методов с блоками. Поскольку блоки (или скорее объекты Proc, которые их представляют), не сериализуемы, блок выполняется в локальном, а не в удалённом контексте. Каждое значение, переданное в блок, передаётся из удалённого объекта в локальный блок, затем значение, возвращаемое каждым вызовом блока, передаётся обратно в контекст удалённого выполнения для сбора, прежде чем собранные значения, наконец, возвращаются в локальный контекст в качестве возвращаемого значения вызова метода.
Примеры использования
Для получения дополнительных примеров dRuby см. директорию samples в полном дистрибутиве dRuby.
dRuby в режиме клиент/сервер
Это демонстрирует настройку простой системы dRuby клиент-сервер. Запустите код сервера и клиента в разных терминалах, сначала запустив код сервера.
Код сервера
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 реализован с использованием трёх основных компонентов: маршализации/демаршализации вызовов удалённых методов; транспортного протокола; и сопоставления идентификаторов с объектами. Последние два могут быть непосредственно, а первый косвенно, заменены для предоставления различного поведения и возможностей.
Маршализация и демаршализация вызовов удалённых методов выполняется экземпляром DRb::DRbMessage. Она использует модуль Marshal для сериализации вызова метода перед его отправкой по транспортному слою, а затем для его восстановления на другом конце. Обычно нет необходимости заменять этот компонент, и прямой способ сделать это не предусмотрен. Тем не менее, реализация альтернативной схемы маршализации возможна в рамках реализации транспортного уровня.
Транспортный уровень отвечает за открытие сетевых подключений клиента и сервера и пересылку запросов dRuby через них. Обычно он использует DRb::DRbMessage внутри для управления маршализациями и демаршализациями. Управление транспортным уровнем осуществляется через DRb::DRbProtocol. Несколько протоколов могут быть установлены в DRbProtocol одновременно; выбор между ними определяется схемой URI dRuby. По умолчанию транспортный протокол выбирается схемой 'druby:' и реализуется DRb::DRbTCPSocket. Он использует обычные сокеты TCP/IP для связи. Альтернативный протокол с использованием сокетов доменной области Unix реализован в файле 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 1772 def config current_server.config rescue DRbServer.make_config end
Получить конфигурацию текущего сервера.
Если текущий сервер отсутствует, возвращается конфигурация по умолчанию. См. current_server и DRbServer::make_config.
# File lib/drb/drb.rb, line 1729 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 1869 def fetch_server(uri) @server[uri] end
Получает сервер с заданным uri.
См. также #regist_server и remove_server.
# File lib/drb/drb.rb, line 1783 def front current_server.front end
Получить объект front текущего сервера.
Это вызывает ошибку DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1762 def here?(uri) current_server.here?(uri) rescue false # (current_server.uri rescue nil) == uri end
Является ли uri URI текущего локального сервера?
# File lib/drb/drb.rb, line 1828 def install_acl(acl) DRbServer.default_acl(acl) end
Установить по умолчанию ACL на acl.
# File lib/drb/drb.rb, line 1820 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) end
Установить объект преобразования идентификаторов по умолчанию.
Ожидается экземпляр, такой как DRb::DRbIdConv, который отвечает на to_id и to_obj и может преобразовывать объекты в и из ссылок DRb.
См. DRbServer#default_id_conv.
# File lib/drb/drb.rb, line 1852
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 1861 def remove_server(server) @server.delete(server.uri) end
Удаляет server из списка зарегистрированных серверов.
# File lib/drb/drb.rb, line 1708 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.
См. DRb::DRbServer.new.
# File lib/drb/drb.rb, line 1741 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
Остановить локальный сервер dRuby.
Действует на основной сервер. Если в данный момент основной сервер не запущен, это ничто.
# File lib/drb/drb.rb, line 1809 def thread @primary_server ? @primary_server.thread : nil end
Получить поток основного сервера.
Возвращает nil, если основной сервер отсутствует. См. primary_server.
# File lib/drb/drb.rb, line 1800 def to_id(obj) current_server.to_id(obj) end
Получить идентификатор ссылки для объекта, используя текущий сервер.
Вызывает ошибку DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1792 def to_obj(ref) current_server.to_obj(ref) end
Преобразовать ссылку в объект, используя текущий сервер.
Вызывает ошибку DRbServerNotFound, если текущий сервер отсутствует. См. current_server.
# File lib/drb/drb.rb, line 1750
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 1772 def config current_server.config rescue DRbServer.make_config end
Получить конфигурацию текущего сервера.
Если текущего сервера нет, возвращается конфигурация по умолчанию. См. current_server и DRbServer::make_config.
# File lib/drb/drb.rb, line 1729 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 1869 def fetch_server(uri) @server[uri] end
Получает сервер с заданным uri.
См. также #regist_server и remove_server.
# File lib/drb/drb.rb, line 1783 def front current_server.front end
Получить объект front текущего сервера.
Это вызывает ошибку DRbServerNotFound, если текущего сервера нет. См. current_server.
# File lib/drb/drb.rb, line 1762 def here?(uri) current_server.here?(uri) rescue false # (current_server.uri rescue nil) == uri end
Является ли uri URI текущего локального сервера?
# File lib/drb/drb.rb, line 1828 def install_acl(acl) DRbServer.default_acl(acl) end
Установить по умолчанию ACL в acl.
# File lib/drb/drb.rb, line 1820 def install_id_conv(idconv) DRbServer.default_id_conv(idconv) end
Установить объект преобразования идентификаторов по умолчанию.
Ожидается, что это будет экземпляр, такой как DRb::DRbIdConv, который отвечает на to_id и to_obj, и может преобразовывать объекты в и из ссылок DRb.
См. DRbServer#default_id_conv.
# File lib/drb/drb.rb, line 1852
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 1861 def remove_server(server) @server.delete(server.uri) end
Удаляет server из списка зарегистрированных серверов.
# File lib/drb/drb.rb, line 1708 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.
См. DRb::DRbServer.new.
# File lib/drb/drb.rb, line 1741 def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end
Остановить локальный сервер dRuby.
Действует на основной сервер. Если в настоящее время не работает основной сервер, это ничто.
# File lib/drb/drb.rb, line 1809 def thread @primary_server ? @primary_server.thread : nil end
Получить поток основного сервера.
Возвращает nil, если основной сервер отсутствует. См. primary_server.
# File lib/drb/drb.rb, line 1800 def to_id(obj) current_server.to_id(obj) end
Получить идентификатор ссылки на объект, используя текущий сервер.
Вызывает ошибку DRbServerNotFound, если текущего сервера нет. См. current_server.
# File lib/drb/drb.rb, line 1792 def to_obj(ref) current_server.to_obj(ref) end
Преобразовать ссылку в объект, используя текущий сервер.
Вызывает ошибку DRbServerNotFound, если текущего сервера нет. См. current_server.
# File lib/drb/drb.rb, line 1750
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.