(1 из 2)      << | < | 1 | 2 | > | >>

5 октября 2013 года

Уже долгое время на всех моих компьютерах нет винды, а на интранет/интернет серверах также отсутствует и php. Все что необходимо разрабатывается на java, а в последнее время я полностью перешел на scala. Однако иногда приходится организовывать взаимодействие с удаленными серверами, работающими на других технологиях.

Пару дней назад мне потребовалось написать на scala клиента для сервера популярной биржи ссылок sape.ru. Разработчики софта этой биржи сидят на php (вероятно поэтому интерфейс системы часто подтормаживает и отображает не актуальную информацию, да и ошибка 504 появляется регулярно) и программ-клиентов для сайтов на java или scala не предлагают. Впрочем, для java все-таки нашлось решение от стороннего разработчика:

https://code.google.com/p/javasape/

Его можно было легко использовать из кода, как на java так и на scala, однако оно задействует сторонние библиотеки, а мне хотелось получить решение чисто на scala. В результате получилось то, о чем я хочу рассказать в этой статье.

Краткая схема работы клиента такова. Серверу биржи периодически отправляется запрос с указанием кода пользователя и хоста ресурса, на котором будут размещаться оплаченные оптимизаторами ссылки. Сервер в ответ возвращает сразу все ссылки для хоста в виде сериализованного в php массива, который внутри себя содержит другие массивы (здесь и далее ... - означает некоторый пропущенный текст):

a:11:{s:18:"__sape_delimiter__";s:2:".";s:12:"__for_user__";s:32:"...";
s:12:"__for_host__";s:17:"http://my.site.ru";s:6:"/page1";a:1:{i:0;s:7:" link1 ";}s:6:"/page2";
a:2:{i:0;s:7:" link2 ";i:1;s:7:" link3 ";}...";}s:16:"__sape_new_url__";s:20:"";
s:12:"__sape_ips__";a:2:{i:0;s:13:"188.72.80.205";i:1;s:13:"188.72.80.201";}}

Полноценно разбирать этот текст и восстанавливать массив не требуется. Нам нужно преобразовать его в структуру Map[String,Any] таким образом, чтобы по страницам /page1, /page2, ... можно было получать тексты ссылок, располагаемых на них.

При разборе массива удобно использовать разделители его элементов ";". Однако после последнего элемента перед закрывающей фигурной скобкой разделитель явно не на месте, поэтому, прежде всего, переставим местами стоящие рядом символы ";}" --> "};":

replaceAll("\";}","\"};")

При обнаружении массива вырезается его содержимое в фигурных скобках и разбирается основной функцией parse:

def part(s: String, left: Char = '"', right: Char = '"') = {
   val k = s.indexOf(left)
   val n = s.lastIndexOf(right)
   s.slice(k+1,if (n>k) n else s.length)
 }

 def parseArray(s: String): Map[String, Any] = {
   parse(part(s,'{','}'))
 }

Это может привести к рекурсии, так как элементами массива могут оказаться другие массивы. Содержание массива разбивается на части благодаря разделителю ";".

К сожалению, обычный метод split у строки использовать не получится из-за возможной вложенности массива в массив, поэтому пришлось написать специальную функцию toParts, которая разделяет массив на части, перебирая разделители ";" с глубиной вложенности 0 (по отношению к фигурным скобкам).

def levels(s: String) = {
   var i = 0
   s.map(c => {
     i += (if (c=='{') 1 else if (c=='}') -1 else 0)
     i
   })
 }

 def toParts(s: String) = {
   val a = (s.indices zip levels(s)).filter(p => (s(p._1)==';') && (p._2==0)).map((p => p._1))
   ((Array(-1) ++ a) zip (a ++ Array(s.length))).map(x => s.slice(x._1+1,x._2))
 }

Функция levels как раз возвращает список уровней вложенности для каждого символа строки. После получения списка из частей - элементов массива функция parse группирует их по два и преобразует пары в кортежи вида String -> Any.

def value(s: String) = {
   val a = s.split(':')
   a.head match {
     case "a" => parseArray(s)
     case _ => part(s)
   }
 }

 def toTuple(a: Array[String]) = {
   if (a.length == 2) {
     part(a.head) -> value(a.last)
   } else "" -> ""
 }

 def parse (s: String) = {
   if (s.head == 'a') parseArray(s) 
   else toParts(s).grouped(2).map(a => toTuple(a)).filter(x => x._1 != "").toMap
 }

В результате разбора ответа сервера при помощи написанного парсера, мы получаем структуру вида Map[String,Any]:

val raw: Map[String,Any] = Map(__sape_ips__ -> Map(i:0 -> 188.72.80.205, i:1 -> 188.72.80.201),
 __sape_delimiter__ -> ., __for_host__ -> http://my.site.ru, __for_user__ -> ..., /page2 -> 
Map(i:0 -> " link2 ", i:1 -> " link3 "), __sape_new_url__ -> , ... -> 
Map(), /page1 -> Map(i:0 -> " link1 "))

Теперь ссылки размещаемые, например, на странице http://my.site.ru/page1 находятся так:

raw("/page1").values.mkString(raw.getOrElse("__sape_delimiter__"," ").toString)

На следующей странице приведен исходный код программы полностью.

(1 из 2)      << | < | 1 | 2 | > | >>

Комментарии (0)

Автор (*):Город:
Эл.почта:Сайт:
Текст (*):