barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

четверг, 31 мая 2012 г.

Декодирование HTML-сущностей на JavaScript

Чтобы преобразовать HTML-сущности в строке в соответствующие символы, можно воспользоваться родными возможностями браузера с помощью следующей функции:

function htmlDecode(input){
  var e = document.createElement('div');
  e.innerHTML = input;
  return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
или, с использованием jQuery:
function htmlDecode(value){
  return $('<div/>').html(value).text();

среда, 30 мая 2012 г.

FTP сервер на Amazon EC2 (Ubuntu 12.04)

Развертывание ftp-сервера (в моем случае - pure-ftpd, хотя описанное здесь справедливо даже для виндового сервера) на Amazon EC2 имеет несколько особенностей. Если просто установить сервер и открыть в амазоновском файрволе порты 20 и 21, клиент, обращающийся к серверу через WAN, выдаст ошибку (текст приведен для клиента FileZilla):
Сервер отправил пассивный ответ с неопределяемым адресом. Использую существующий адрес сервера.
После чего вывалится ошибка, что невозможно соединиться с сервером. Причина следующая: во-первых, при инициализации пассивного соединения сервер кидает клиенту, по-видимому, свой IP-адрес внутри амазоновской локалки, когда надо бы кидать внешний IP. Эта проблема лечится созданием файла /etc/pure-ftpd/conf/ForcePassiveIP и записью в него внешнего IP-адреса сервера. Но ошибку подключения вызывает вовсе не это (т.к. эту проблему обходит сама FileZilla, используя для пассивного соединение тотже адрес, который был использован для инициализации соединения с сервером). Проблема тут в амазоновском файрволе, в котором не открыты порты для пассивных соединений. Какие порты используется pure-ftpd для пассивных соединений по-умолчанию, я искать не стал, а задал их явно, создав файл /etc/pure-ftpd/conf/PassivePortRange и записав в него:
40000 41000
Это укажет ftp-серверу на использование портов с 40000 по 41000 для пассивных соединений. Я задал такой узкий диапазон сознательно, по той причине, что мой сервер отнюдь не для массового использования ;) Теперь этот диапазон портов нужно открыть в амазоновском файрволе, и будет счастье =)

Изменение enum-типа в PostgreSQL 8.3

Задача:

Изменить пользовательский перечислимый тип, используемый в одной из колонок БД (переименовать одно из значений типа).

Решение:

Сделать это оказалось не очень-то просто. В интернете нашел следующее решение (правда, для случая добавления нового значения в enum-тип, но не в этом суть):
-- 1. Переименуйте тот тип ENUM который вы хотите дополнить
ALTER TYPE b_status RENAME TO old_b_status;
-- 2. Создайте новый тип
CREATE TYPE b_status AS ENUM ('exposed','paid','canceled');
-- 3. Переименуйте столбец, который использует ваш старый тип
ALTER TABLE bills RENAME COLUMN status TO old_status;
-- 4. Создайте новый столбец с новым типом
ALTER TABLE bills ADD status b_status NOT NULL DEFAULT 'exposed';
-- 5. Скопируйте значения в новый столбец
UPDATE bills SET status = old_status::text::b_status;
-- 6. Удалите старый столбец и тип
ALTER TABLE bills DROP COLUMN old_status;
DROP TYPE old_b_status;
Проблема только в том, что на моем серваке - PostgreSQL 8.3, и команды ALTER TYPE ... RENAME в нём нет (http://www.postgresql.org/docs/8.3/static/sql-altertype.html), т.к. эта фича появилась только в 8.4. Так что последовательность получилась другая (часть действий делал в GUI pgAdmin, поэтому приведу лишь порядок действий, а не сами SQL-команды):
  1. Создаем временную колонку с типом "text", и переносим в неё данные из колонки enum-типа, используя ::text:
  2. UPDATE mytable SET mycol_tmp = mycol::text
  3. Удаляем старую колонку и старый enum-тип
  4. Создаем новый тип, называя его также, как только что удаленный
  5. Создаем колонку взамен удаленной, задав её типом только что созданный тип
  6. Переносим в эту колонку данные из временной колонки,  преобразуя их при необходимости (например, переименовывая старые значения enum`а в новые) и приводя их к перечислимому типу с помощью ::myenum.
  7. Удаляем временную колонку

вторник, 29 мая 2012 г.

Spring Security: java.lang.IllegalArgumentException: Unsupported configuration attributes: [isAuthenticated()]

Проблема:

При использовании в xml-конфигурации следующего тега:
<intercept-url pattern="/**" access="isAuthenticated()" />
при инициализации Spring Security в логи валится ошибка:
java.lang.IllegalArgumentException: Unsupported configuration attributes: [isAuthenticated()]
Решение:

У родительского тега <http> установить атрибут use-expressions:

<http auto-config="true" access-denied-page="/login.jsp?accessDeniedError=1" use-expressions="true">  
        <intercept-url pattern="/**" access="isAuthenticated()" />
...

Включение логирования в Spring Security

Чтобы включить логирование в Spring Security, необходимо настроить commons-logging и log4j (наверное, можно и без log4j, воспользовавшись другими возможностями commons-logging, но я сделал так). Для этого нужно (на примере проекта веб-приложения в NetBeans):

  1. Во-первых, подключить к проекту jar-ник commons-logging и log4j (в моему случае это были commons-logging-1.1.1.jar и log4j-1.2.17.jar). 
  2. Добавить в проект properties-файл commons-logging.properties, поместив его в src/java, со следующим содержанием:
  3. org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
  4. Добавить в проект properties-файл настройки log4j. Я положил его в src/java, назвав log4j.properties. Содержимое файла следующее (для Debug-уровня логирования):
    log4j.rootLogger=DEBUG, console
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.conversionPattern=%-4r [%t] %-5p %c %x - %m%n
    В форматах паттернов особенно не разбирался, используется паттенн из мануала log4j (http://logging.apache.org/log4j/1.2/manual.html). В принципе, можно выполнить настройку отдельно для разных логгеров, используемых спрингом, заменив строку:
    log4j.rootLogger=DEBUG, console
    на строки
    log4j.logger.org.springframework.web.context.support.StandardServletEnvironment=DEBUG, console
    и др. для остальных логгеров. Какие именно логгеры использует спринг, станет понятно из лога log4j, который будет ругаться на ненастроенные для какого-либо логгера аппендеры:
    log4j:WARN No appenders could be found for logger (org.springframework.web.context.support.StandardServletEnvironment).
    log4j:WARN Please initialize the log4j system properly.
    log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
    Но я это делать не стал.
  5. Добавить в web.xml приложения настройку пути к файлу конфигурации log4j и listener, инициализирующий log4j:
    <context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>/WEB-INF/classes/log4j.properties</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    Всё, теперь логов будет хоть отбавляй =).
UPDATE:
Если валится исключение org.apache.commons.logging.LogConfigurationException, возможно, поможет это: http://barbitoff.blogspot.com/2012/07/orgapachecommonslogginglogconfiguration.html.

пятница, 25 мая 2012 г.

Tomcat: получение URL`а оригинальной запрошенной страницы со страницы-обработчика ошибки 404

Задача:

Установлена кастомная 404-страница для веб-приложения:
<error-page>
<error-code>404</error-code>
<location>/errorPages/404.jsp</location>
</error-page> 
В теле страницы необходимо определить, какой именно URL привел к ошибке (как минимум, это необходимо, чтобы корректно подключить стили на этой странице, т.к. для этого нужно знать уровень вложенности).

Решение:

При возникновении ошибки 404 Tomcat делает forward на установленную 404-страницу, поэтому определить, какой именно URL пользователь запросил, не так просто (request.getRequestURI() возвращает /errorPages/404.jsp). Определение именно запрошенного пользователем URL`а выполняется так:
request.getAttribute("javax.servlet.error.request_uri")
Аналогичным образом можно узнать и другие параметры ошибки через следующие атрибуты:
javax.servlet.error.status_code
javax.servlet.error.exception_type
javax.servlet.error.message
javax.servlet.error.exception 
Такой подход работает не только на JSP-страницах, но и в сервлетах (см. http://java.sun.com/developer/technicalArticles/Servlets/servletapi2.3/).
Теперь уровень вложенности страницы, вызвавшей 404 ошибку, относительно контекста сервлета определяется следующим образом:
request.getAttribute("javax.servlet.error.request_uri").toString().split("/").length-3

четверг, 24 мая 2012 г.

CodeGear C++ Builder 2007: подключение к БД MS Access 2010

Один из возможных вариантов:
1) Добавляем компонент "TADOConnection"
2) Правой кнопкой -> "Edit ConectionString"
3) Жмем "Build"
4) Т.к. "Microsoft Jet 4.0 OLE DB Provider" умеет есть только .mdb, а у нас - .accdb, выбираем поставщика данных "Microsoft Office 12.0 ...", жмем "Далее"
5) В "Источник данных" вводим имя файл БД Access, который должен лежать в папке "Debug". Жмем "Проверить соединение", проверка должна пройти успешно (настройки входа в БД не трогаем, оставляя "Admin" и пустой пароль).
6) Выставляем нужные права доступа на вкладке "Дополнительно", жмем "Ок". Получится ConnectionString вроде:
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=database.accdb;Mode=ReadWrite;Persist Security Info=False
7) Далее в коде открываем коннект:
ADOConnection1->Open("Admin","");
и работаем с ним (ну или используем это соединение в объектах TADOQuery и пр.).

Отладка конфигурации mod_rewrite


Такой конфиг включает максимальный уровень логирования mod_rewrite, что значительно упрощает отладку конфига:
RewriteLog /var/log/apache2/rewrite.log
RewriteLogLevel 9

вторник, 22 мая 2012 г.

java.io.FileNotFoundException: (Too many open files)

Проблема:

Беспроблемно работающее ранее Java-приложение стало вдруг валиться с исключениями:
java.io.FileNotFoundException: (Too many open files)
При этом файл, на отсутствие которого программа ругается, на месте, и необходимые права на него есть.

Причина:

Срабатывают ограничения ОС на число открытых пользователем файлов, либо ограничения на число открытых процессом файлов. В Linux за это отвечает PAM, а точнее его модуль pam_limits. Текущие ограничения можно посмотреть с помощью:
ulimit -n
Посмотреть число файлов, открытых процессом можно так:
lsof -p <PID> | grep / | sort -k9 -u |wc -l
"lsof -p <PID>" выдаст все используемые указанным процессом дескрипторы файлов, включая файлы, отображаемые в память (memory-mapped files). Т.к. отображаемые в память файлы не подпадают под ограничения PAM-а, их нужно отфильтровать с помощью "grep /". Т.к. нас интересует не число дескрипторов, а число уникальных файлов, выполняем "sort -k9 -u". Эта команда отберет строки с уникальными значениями в 9ой колонке (именно в ней в выдаче lsof идет имя файла). Ну и "wc -l" выполнит подсчет полученных в итоге строк. Полученное значение будет на 1 больше, чем число файлов, т.к. в выборку, получаемую на выходе sort, попадет строка с заголовками колонок, выдаваемая lsof-ом. За методику подсчета спасибо https://serverfault.com/questions/396872/why-or-how-does-the-number-of-open-file-descriptors-in-use-by-root-exceed-ulim.

Для пользователя аналогичная операция делается так:
lsof -u <username> | grep / | sort -k9 -u |wc -l
Как изменить ограничение на число открытых файлов в Linux, я уже писал тут: http://barbitoff.blogspot.com/2012/05/pam-limits-root.html. В Windows, вероятно, есть аналогичное решение этой проблемы.

Pam limits и пользователь root

При установке ограничений с помощью PAM Limits на пользователя root есть достаточно существенная особенность: шаблон "*" не применяет правило к суперпользователю. Для применения правила к root-у необходимо указать имя "root" явно. Например, для установки абсолютно всем пользователям, включая root-а, soft / hard ограничения в 4096 открытых файлов, необходимо добавить в /etc/security/limits.conf следующие строки:
* - nofile 4096
root - nofile 4096
Не уверен, касается ли это PAM во всех дистрибутивах, но по крайней мере для Debian / Ubuntu это так.
Кстати, чтобы данные ограничения применялись абсолютно всегда, нужно добавить в /etc/pam.d/common-session следующую строку:
session required /lib/security/pam_limits.so

Wamp Server Manager: Ошибка при запуске "Exception Exception in module wampmanager.exe at ..."

Проблема:

При запуске Wamp Server Manager (на Windows Server 2008 R2 x64, версия Wamp Server 2.2D x64) вываливается ошибка:


, а затем:


Решение:

Установить Microsoft Visual C++ Redistributable 2008 в соответствии с разрядностью ОС.

понедельник, 21 мая 2012 г.

Подключение по ssh к инстансу Amazon EC2 с помощью putty

Задача:

Подключиться по ssh к Instance`у Amazon EC2 (Ubuntu Server 12.04) с помощью putty, используя полученный при генерации keypair приватный ключ (файл с расширением .pem).

Решение:

Напрямую pem-файл putty не ест - вываливается ошибка:
Unable to use key file "***.pem" (OpenSSH SSH-2 private key)
, поэтому нужно воспользоваться утилитой puttygen для преобразования ключа в putty-формат (.ppk): открываем, загружаем pem-файл через File -> Load private key, сохраняем полученный ppk кнопкой "Save private key". Всё, ppk-файл можно использовать при подключении с помощью putty, указав его в Connection -> SSH -> Auth (в поле "Private key file for authentication"). Также в SSH -> Data нужно указать имя пользователя "ubuntu" в поле "Auto-login username".


воскресенье, 20 мая 2012 г.

bash: выполнение скрипта внутри текущей сессии bash

Чтобы выполнить скрипт не в новом процессе, а в текущей сессии bash, нужно использовать команду source (или просто "."). Например, чтобы выполнить скрипт "vars", находящийся в текущей директории, в текущей bash-сессии, нужно сделать:
source ./vars
или
. ./vars

Debian Squeeze / Mint 12: монтируем файловую систему Amazon EC2 Instance`а (да и вообще ФС любого сервера, на который есть ssh-доступ)

Задача:

Смонтировать файловую систему Amazon EC2 Instance`а в папку на локальном Debian`е / Mint`е, используя ssh и приватный ключ, сгенерированный при создании инстанса. На EC2-инстансе запущена Ubuntu Server 12.04.

Решение:

Итак, у нас есть pem-файл приватного ключа для подключения по ssh к EC2-инстансу. Для начала нужно проверить, что просто по ssh у нас доступ есть:
ssh -i /path/to/your/pemfile.pem ubuntu@ec2-xxxxxxxxx.compute-1.amazonaws.com
Здесь /path/to/your/pemfile.pem - путь к pem-файлу с приватным ключом, полученному при генерации пары ключей на aws.amazon.com.
Теперь устанавливаем пакет sshfs, позволяющий монтировать файловую систему, используя протокол передачи файлов по ssh и FUSE. Sshfs не позволяет задать из командной строки путь к приватному ключу ssh, но позволяет указать путь к конфигурационному файлу ssh, поэтому создаем файл конфигурации ssh (например, /home/user1/.ssh/aws-config) со следующим содержимым:
IdentityFile /path/to/your/pemfile.pem
Вот, теперь всё готово для монтирования файловой системы. Выполняем из-под root`а:
sshfs ubuntu@ec2-xxxxxxxxx.compute-1.amazonaws.com:/ /home/user1/aws -F /home/user1/.ssh/aws-config -o allow_other
Эта команда смонтирует корень удаленной файловой системы инстанса Amazon EC2 в папку /home/user1/aws. Опция allow_other позволит иметь доступ к смонтированной файловой системе не только root`у.
Для размонтирования файловой системы используется команда:
fusermount -u /home/user1/aws

пятница, 18 мая 2012 г.

Недоступные для редактирования поля на вкладке "Замещающий текст" в свойствах таблицы MS Word 2010

Проблема:

В свойствах таблицы документа doc, открытого в MS Word 2010, недоступны поля ввода на вкладке "Замещающий текст":


Причина:

Замещающий текст для таблиц поддерживается в MS Word 2010 только для документов docx.

среда, 16 мая 2012 г.

Проект веб-приложения в Netbeans: автоматизация обновления из svn и развертывания на удаленном Tomcat 6

Задача:

Есть проект веб-приложения Java в Netbeans. Папка проекта находится по контролем версий. Необходимо в один-два клика из IDE выполнить обновление исходников проекта из svn, его очистку, сброку и развертывание на удаленном сервере Tomcat, отличном от того, который прописан в свойствах проекта как сервер для выполнения (там прописан локальный тестовый сервер, а разворачивать в автоматическом режиме нужно на боевом).

Решение:

Во-первых, чтобы не городить огород в виде лишних скриптов, было решено добавить новую цель в build.xml проекта. С моими скудными пока познанями в ant я немного побуксовал, но всё же добился поставленной цели.
Для обновления из svn можно использовать ant task (например, svntask), вот только последний отказался работать за проксёй с авторизацией даже после использование setproxy, поэтому я решил особенно не заморачиваться и вызывать с помощью задания exec клиент TortoiseSVN. Можно было бы конечно воспользоваться и консольным клиентом, но мне появляющееся GUI-окошко TortoiseSVN  при обновлении не помешало; даже наоборот, было удобнее видеть, что именно произошло при Update`е. В итоге ant-цель для обновления проекта из svn выглядела так:
<target description="Updates ${basedir} from svn repository using TortoiseSVN" name="tortoise-svn-update">
    <echo message="Updating from SVN using TortoiseSVN"/>
    <property name="tortoisesvn.home" value="C:\Program Files\TortoiseSVN\bin\"/>
    <exec executable="${tortoisesvn.home}\TortoiseProc.exe">
      <arg value="/command:update"/>
      <arg value="/path:${basedir}"/>    
    </exec>
</target>
Теперь осталось создать цель для развертывания проекта на удаленном сервере:
<target depends="tortoise-svn-update,clean,dist" description="Remote deployment" name="deploy-remote">
<property name="deployment.remote.server.url" value="http://mytomcathost:8080"/>
<property name="deployment.remote.server.manager.login" value="administrator"/>
<property name="deployment.remote.server.manager.password" value="xxx"/>
<property name="deployment.remote.context" value="mycontext"/>
<property name="war.file.name" value="${deployment.remote.context}"/>
<property name="tomcat.lib.path" value="C:/tomcat/lib/"/>
<echo message="Remote undeploying ${deployment.remote.context}"/>
<taskdef name="undeploy"  classname="org.apache.catalina.ant.UndeployTask"
classpath="${tomcat.lib.path}catalina-ant.jar"/>
<undeploy url="${deployment.remote.server.url}/manager" username="${deployment.remote.server.manager.login}"
 password="${deployment.remote.server.manager.password}" path="/${deployment.remote.context}"/>

<echo message="Remote deploying ${basedir}/dist/${war.file.name}.war to /${deployment.remote.context}"/>
<taskdef name="deploy" classname="org.apache.catalina.ant.DeployTask"
 classpath="${tomcat.lib.path}catalina-ant.jar"/>
<deploy url="${deployment.remote.server.url}/manager" username="${deployment.remote.server.manager.login}"
password="${deployment.remote.server.manager.password}" path="/${deployment.remote.context}"
war="${basedir}/dist/${war.file.name}.war"/>            
</target>
Здесь предполагается, что имя создаваемого при компиляции war-ника совпадает с контекстом, в котором разворачивается приложение на удаленном сервере (war.file.name =  deployment.remote.context), при необходимости это можно поменять. Путь к библиотекам томката нужно прописывать, чтобы ant взял оттуда библиотеку с заданиями для работы с томкатом.
Теперь, когда есть задание в build.xml, можно разместить его либо в меню NetBeans, либо на панели инструментов, либо назначить ему сочетание клавиш. Не очень универсальный подход конечно, т.к. получается что глобальное меню / панель / сочетание клавиш NB будет использоваться для задания одного проекта, но я пока не нашел как сделать так, чтобы команда меню / кнопка панели / сочетания клавиш вызывали цель из build.xml текущего проекта, а не по абсолютному пути к этому build.xml. Делается добавление пунктов меню / кнопок панели / сочетаний клавиш следующим образом:
  1. В обозревателе файлов NB открывается необходимый проект и выбирается его build.xml. 
  2. При этом на панели "Навигатор" отобразятся все ant-цели. Достаточно щелкнуть правой кнопкой мыши по нужной цели и выбрать "Создать сочетание клавиш":

В появившемся диалоге можно выбрать, что именно мы добавляем, пункт меню, кнопку панели или сочетание клавиш и задать необходимые параметры - и вуаля, мы получаем возможность в 1-2 клика сделать update и развертывание на удаленном сервере, как и хотели:


вторник, 15 мая 2012 г.

PostgreSQL: удаление строк из таблицы в случае отсутствия первичного ключа и наличия полностью идентичных строк

Задача:

Есть некая таблица, в которой 8 строк, и последние 4 полностью повторяют первые 4. Необходимо удалить строки-дубликаты.

Решение:

Использовать LIMIT в DELETE Postgres не позволяет (хотя вроде бы в какой-то другой СУБД я такую возможность встречал), но можно использовать внутренний идентификатор строк CTID, который позволяет различать 2 идентичные строки. В моем случае следующий запрос удаляет последние 4 строки:
DELETE FROM r_administrative_level WHERE CTID IN('(0,5)','(0,6)','(0,7)','(0,8)')

response.sendRedirect на включаемой jsp-странице

Проблема:

Вызов response.sendRedirect() на jsp-странице, включаемой из другой страницы с помощью тега <jsp:include/>, игнорируется.

Причина:

В соответствии со спецификацией редиректы невозможны на включаемой странице / сервлете:
The included servlet cannot change the response status code or set headers; any attempt to make a change is ignored.
Печально, но факт.

Возможное решение:

Использовать включение директивой <%@include>. Такое включение производится на этапе компиляции и не накладывает ограничений на включаемую страницу.

понедельник, 14 мая 2012 г.

Windows: запуск запланированной задачи из командной строки

Запуск запланированной задачи из командной строки выполняется следующим образом:
Schtasks /Run /TN имя_задачи
Например, для запуска задачи архивации:
Schtasks /Run /TN Microsoft\Windows\WindowsBackup\AutomaticBackup

пятница, 4 мая 2012 г.

Блокировка фрагмента веб-страницы с помощью dojo

Если нужно временно заблокировать какой-то элемент веб-страницы (затемнив его и показав анимашку ожидания), можно воспользоваться виджетом dojox.widget.Standby. Он легко создается программно (правда, для версии dojo 1.3 необходимо ещё импортировать CSS dojox/widget/Standby/Standby.css):
dojo.require("dojox.widget.Standby");

var standbyWindget = null;
dojo.addOnLoad(function(){
standbyWindget = new dojox.widget.Standby({target:document.getElementById("elemToOverlay")});
document.body.appendChild(standbyWindget.domNode);
standbyWindget.startup(); });
Данный код создаст виджет ожидания, отображаемый поверх элемента с id="elemToOverlay". Показывается этот виджет вызовом:
standbyWindget.show()
, а скрывается:
standbyWindget.hide()
В качестве target можно указать document.body, чтобы заблокировать всю страницу, однако, такой подход имеет минус, заключающийся в том, что анимашка ожидания будет размещена посередине документа, а не viewport`а.

четверг, 3 мая 2012 г.

"450 4.7.1 ...: Recipient address rejected: Service temporarily unavailable, please try later" при программной отправке почты по SMTP

Проблема:

При отправке почты по SMTP из Java с использованием JavaMail периодически возникает ошибка:
450 4.7.1 ... : Recipient address rejected: Service temporarily unavailable, please try later 
Причем такое поведение наблюдается только при первой попытке отправить письмо адресату. Через какой-то промежуток времени отправка тому же адресату заканчивается успехом, и после этого почта ему отправляется уже без проблем. 

Причина:

Срабатывает т.н. грейлистинг

Решение:

Либо пробовать отправить почту повторно, либо добавить адрес отправителя в "белый список" почтового сервера получателя.