JAVA / [Из песочницы] Java DNS API, Wikipedia и twitter-марафон в одном флаконе

в 18:29, , рубрики: DNS, java, twitter, wikipedia, метки: , , ,

Некоторое время назад я решал задачу автоматической покупки домена. Нужно было оформить в виде библиотеки (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. Уличенный в мошенничестве будет дисквалифицирован»
Я обрадовался - значит рано покупать лекарства.


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js