Некоторое время назад я решал задачу автоматической покупки домена. Нужно было оформить в виде библиотеки (jar и файл настроек), которая использовалась бы в корпоративном приложении на Java. Я начал поиски DNS провайдеров с public API. Желательно, чтобы API были попроще, и домены подешевле — такой баланс оказалось не просто найти.
Было рассмотрено много вариантов, некоторые из которых можно найти тут: stackoverflow. В последствии, я сузил круг до следующих претендентов:
http://Dnsimple.com
http://www.namecheap.com/
http://www.linode.com/
Последний многие советовали, но это
Первый имеет, на мой взгляд, очень удобный API – аутентификация может проходить вообще через HTTP Header X-DNSimple-Token, но есть и традиционный подход с http basic authorization. Формат запроса очень прост – короткие урлы. Ответы можно получать в JSON (XML тоже доступен) – вобщем все радует глаз, кроме цены — .com домен там стоит $14/y. Знающие люди поймут, что это неприемлимо.
Когда начал разбираться с http://www.namecheap.com/ — все оказалось довольно не плохо. И со стороны покупателя — цены в норме, на рынке он больше 10 лет (dnsimple.com через who is дал Creation date: 07 Apr 2010 17:32:00 – мелковат еще, плюс ID доменов, когда я создавал их через API был в районе 3000). И со стороны программиста: запросы в XML, но структура не сильно запутанная. Аутентификация через параметры в самом урле – ничего сложного.
Что мне больше всего понравилось – у них единственных из всех кандидатов оказался правильный sandbox. При регистрации в тестовом окружении у тебя на счету $9000 и ты можешь реально потестировать функции покупки, renew, reactivate и т.д. для домена. В DNsimple, к примеру, я не нашел возможности пользоваться тестовым окружением без того, чтоб вводить номер кредитки – а что это за sandbox, который не может работать без реальных данных?
Кроме того у них при редактировании host records домена есть возможность задать нестандартные значения (не только A, CNAME, AAAA, etc.) – есть еще “URL” – это позволяет делать редирект с вашего домена на некий урл (произвольный), а это как раз требовалось для задачи, и в случае отсутствия такой опции было бы необходимо что-то придумывать со стороны сервера заказчика. Такая фича есть далеко не у всех DNS провайдеров.
И еще этот провайдер довольно часто раздает купоны со скидкой (в API есть возможность использовать эти купоны) и организовывает акции (об одной из них – в конце статьи). Например недавно там были скидки для всех кто переводит свои домены от GoDaddy, в связи с SOPA-позициями последнего.
Перейдем к коду
Полностью он выложен в свободном доступе: github.com кому надо пользуйтесь (заказчику по барабану).
Интерфейс DNSProvider имеет конкретную реализацию: NamecheapProvider, где присутствуют основные функции для работы с доменами – покупка, обновление записей, реактивация. Есть пакет моделей, где находятся объекты основных сущностей: Domain, DomainRecord, RecordType. Все остальное это классы запросов, и xml парсеры ответов.
Базовый класс запроса:
public abstract class DNSBaseRequest {
private List params = new ArrayList();
protected DNSBaseRequest(Properties properties) {
params.add(new BasicNameValuePair("ApiUser", properties.getProperty("api.login")));
params.add(new BasicNameValuePair("ApiKey", properties.getProperty("api.key")));
params.add(new BasicNameValuePair("UserName", properties.getProperty("api.login")));
params.add(new BasicNameValuePair("ClientIp", properties.getProperty("client.ip")));
//each class has its own command - his purpose
params.add(new BasicNameValuePair("Command", getCommand()));
}
protected abstract String getCommand();
....
}* This source code was highlighted with Source Code Highlighter.
Каждый класс запроса реализует свою getCommand, которая соответствует его назначению:
public class DomainReactivateRequest extends DNSBaseRequest{
public DomainReactivateRequest(String domainName, Properties properties) {
super(properties);
addParam(new BasicNameValuePair("DomainName", domainName));
}
@Override
protected String getCommand() {
return "namecheap.domains.reactivate";
}
}* This source code was highlighted with Source Code Highlighter.
Интерфейс парсера:
public interface XmlResponseParser {
T parse(String xml);
}* This source code was highlighted with Source Code Highlighter.
И пример реализации для получения списка доменов из вашего аккаунта:
public class DomainsListParser extends DefaultHandler implements XmlResponseParser {
private static final Logger log = LoggerFactory.getLogger(DomainsListParser.class);
private SAXParser parser;
private ArrayList result;
public DomainsListParser() throws Exception{
SAXParserFactory factory = SAXParserFactory.newInstance();
parser = factory.newSAXParser();
result = new ArrayList();
}
public ArrayList parse(String xml) {
try {
parser.parse(new InputSource(new StringReader(xml)), this);
} catch(Exception e){
log.error("Error in parsing string.", e);
}
return result;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ("Domain".equalsIgnoreCase(qName)){
Domain domain = new Domain(Long.parseLong(attributes.getValue("ID")), attributes.getValue("Name"));
domain.setCreateDate(attributes.getValue("Created"));
domain.setExpireDate(attributes.getValue("Expires"));
result.add(domain);
}
}
}* This source code was highlighted with Source Code Highlighter.
Друг с другом запрос и разбор ответа сводит класс на дженериках:
public class ProviderOperator {
private static final Logger log = LoggerFactory.getLogger(ProviderOperator.class);
private DNSRequestProcessor dnsRequestProcessor;
public ProviderOperator(DNSRequestProcessor dnsRequestProcessor) {
this.dnsRequestProcessor = dnsRequestProcessor;
}
public T process(DNSBaseRequest dnsRequest, XmlResponseParser parser, S defaultResult) {
T result = defaultResult;
try {
String xml = dnsRequestProcessor.get(dnsRequest);
log.debug("Response: {}", xml);
result = parser.parse(xml);
} catch (Exception e) {
log.error("Error in parse", e);
}
return result;
}
}* This source code was highlighted with Source Code Highlighter.
Который принимает их через параметры и возвращает результат разбора xml ответа.
Для расширения функциональности нужно добавить функцию в интерфейс, создать класс запроса, класс парсера ответа, и объединить их через вызов ProviderOperator в соотв. функции NamecheapProvider.
Напоследок интересная история
www.namecheap.com имеет традицию – каждый год они организовывают твиттер-марафон на определенную тему. В течение 48 часов задаются 48 вопросов. Каждый час дается правильный ответ на предыдущий вопрос и задается следующий. Победителям деньги на счет (для покупки доменов) и пара iPad2 – самый ходовой приз большинства викторин. В этом году марафон решили приурочить к суперкубку. Я в американских видах спорта не силен, но решил для интереса ответить на какой-нибудь вопрос. Номер третий звучал примерно так:«In what year was Bart Starr elected to the Wisconsin Athletic Hall of Fame»
- не долго думая, пошел в википедию смотреть его биографию. Отдельным обзацом было написано, что это произошло в 1980. Я твитнул это число в ответ. Через час твит с правильным ответом меня немного расстроил – 1981. Ну кто бы сомневался, подумал я, что в википедии даты не точные. Но все-таки решил зайти туда снова и посмотреть – расстроился еще больше. Там было действительно написано 1981. Я подумал, что пора затариваться таблетками для улучшения зрения, повышения внимания и ускорения работы
Однако через пару часов от организатора марафона поступил интересный твит, приблизительно такого содержания:«Уважаемые участники, мы считаем НЕДОПУСТИМЫМ редактировать википедию, чтобы дезинформировать соперников – это противоречит духу fair play. Уличенный в мошенничестве будет дисквалифицирован»
Я обрадовался - значит рано покупать лекарства.