Пишем интерпретатор LISP на PHP

Эта статья носит больше обучающий характер, так как практическое применение такого интерпретатора придумать сложно. Почему именно лисп? Ну для начала этот язык мне очень нравится. Не смотря на синтаксис который некоторые считают неудачным это очень гибкий язык поддерживающий различные стили программирования. Кроме того, интерпретатор этого языка сравнительно несложен в написании и при правильной реализации один из самых быстрых интерпретаторов.

Читать дальше →

Тестирование производительности языка Go в сетевых приложениях

В последнее время на хабре проскальзывало несколько постов посвященных языку программирования Go, но все они по большей части носили достаточно поверхностный и ознакомительных характер. Это натолкнуло меня на мысль провести более глубокое тестировани этого языка. Для начала пару слов почему меня заинтересовал именно этот язык, а не Python, Ruby, Java и тд. Основным языком разработки последние 4 годя для меня является PHP, и поэтому я давно чуствовал потребность в изучении языка который удовлетворял бы следующим критериям:

  1. Быстрота. Уточню — не на 15-25% быстрее чем в PHP/Perl/Ruby (в хорошую погоду / с попутным ветром / в цикле вычисляющем число с тремя единичками/<добавьте свое условие>), а реально быстрее — в разы, а еще лучше на порядок быстрее чем популярные интерпретируемые языки.
  2. Простота написания. Да писать на C/C++ это круто, это для настоящих суровых программеров(а еще лучше взять и отдельные куски кода на asm переписать, чтобы «еще быстрее» стало), но этот путь не для меня.
  3. Удобство написания демонов. Конечно на их Perl/PHP тоже пишут… но я я думаю все согласяться что по надежности и стабильности работы они никогда не сравняться с аналогами написанными на C/C++.

На мой взгляд Go идеально подходит под эти критерии — хотя я уверен у каждого из читателей будет свое мнение на эту тему. Python/Perl/<....> тоже идеально подходят для этого, но этот пост не о них. Так как язык Go привлек меня в первую очередь с точки зрения написания сетевых демонов, то и тестировать его я буду на сетевых задачах. В качестве тестовой задачи я выбрал написание echo демона работающего по websockets протоколу.
Другой важной особенностью языка Go о которой обычно забывают упоминуть — это реализация в нем легковесных процессов(как например в Erlang или OCaml). В теории это должно резко повысить масштабируемость сетевых приложений написанных на Go, но хотелось бы узнать как дело обстоит на самом деле.

И так приступим непосредственно к тестам. На удивление написание сервера не заняло много времни:

//echod.go
package echod
import (
"http"
"io"
"websocket"
)

//Обработчик соединения
func EchoServer(ws *websocket.Conn) {
io.Copy(ws, ws)
}

func main() {
http.Handle("/echo", websocket.Handler(EchoServer))
err := http.ListenAndServe(":12345", nil)
if (err != nil) {
panic("ListenAndServe: " + err.String())
}
}


Все что от нас потребовалось это написать функцию обработчик соединения и использовать встроенную в Go реализацию websocket сервера. Для тестирования производительности новоиспеченного демона нам понадобиться клиент, который будет создавать для него нагрузку:

//echoclient.go
package echoclient;

import (
"websocket"
"log"
)

func echo() {
ws, err := websocket.Dial("ws://localhost:12345/echo", "", "http://localhost/")
defer ws.Close()

if err != nil {
panic("Dial: " + err.String())
}
pingMsg := "Hello, echod!\n"
if _, err := ws.Write([]byte(pingMsg)); err != nil {
panic("Write: " + err.String())
}
var receivedMsg = make([]byte, len(pingMsg) + 1);
if n, err := ws.Read(receivedMsg); err != nil {
panic("Read: " + err.String())
} else {
receivedMsg = receivedMsg[0:n]
}
if receivedStr := string(receivedMsg); pingMsg != receivedStr {
log.Stdoutf("Strings not equal !%s!,!%s!, %i, %i ", pingMsg, receivedStr, len(receivedStr), len(pingMsg))
}
}

func main() {
echo()
}


Как вы можете убедиться написание websocket клиента заняло еще меньше времени. Но запускать каждый тесты вручную и замерять время нехочтся, поэтому я использовал для этих целей встроенный в язык Go механизм юнит тестировния и профилирования. Для этого достаточно создать файл echoclient_test.go и написать в нем функции вида:
func TestEcho(t *testing.T) {} — для выполнения юнит тестирования и
func BenchmarkEcho(b *testing.B) {} — для выполнения профилирования соответсвенно.
Ниже исходный код функции которую я использовал для профилирования:

package echoclient;

import (
"testing"
)
//Функция используемая для запуска echo клиента в отдельном процессе
func echoRoutine(c chan int, index int) {
//Обработка ошибок, об этом подробнее ниже по тексту
defer func() {
//проверяем была ли какая-нибуть ошибка 
if err := recover(); err != nil {
print("Routine failed:", err, "\n")
c <- -index
} else {
c <- index
print("Exit go routine\n") 
}
}()

echo()
}
// Непосредственно сам бенчмарк
func BenchmarkCSP(b *testing.B) {
//b.N = 2000; - если нужно подсказываем профилировщику сколько раз мы хотим прогнать тест

// канал используемый для синхронизации потоков
c := make(chan int)
for i:= 0; i < b.N; i++ {
// запускаем каждый клиент в отдельной goroutine
go echoRoutine(c, i)
}
print("Fork all routines \n") 
var (
success, failed int
)
for i:= 0; i < b.N; i++ {
//ждем когда поток вернет результат выполнения
// если он отрицательный тест провален, если положительный соответсвенно выполнен
index := <- c
if index < 0 {
failed++
print(i, ". Goroutine failed:", -index, "\n")
} else {
success++
print(i, ". Goroutine:", index, "\n")
}
}
print("Totals, success:", success," failed: ", failed, " \n")
}


Теперь для того чтобы прогнать тесты нам достаточно запустить утилиту gotest и усказать ей регулярные выражения для выбора запускаемых тестов и бенчмарков. Несколько слов о том что же делает данный код — он одновременно запускает заданное количество легковесных потоков (goroutines), каждый из которых пингует наш echo сервер.
Остановлюсь немного подробнее на обработке ошибок. В Go она построена на основе 3-х встроенных функций — defer, recover и panic. Это аналог конструкции try… catch в других языках программирования, который позволяет поймать и обработать абсолютно любую ошибку. Ключевое defer говорит компилятору о том что данную функцию необходимо выполнить непосредственно при выходе из текущей функции, причем независимо от того каким образом завершилось ее выполнение — в результате какой-либо ошибки(panic) или обычным образом. Если исполнение функции завершилось в результате ошибки то мы можем восстановить нормальное выполнение программы путем вызова функции recover.

Результаты тестов




В первой колонке находиться количество одновременных соединений, вторая колонка — среднее время выполнения одного пинга в наносекундах, последующие — соответсвенно количество успешных и проваленных попыток. Ниже небольшой график зависимости среднего времени затрачиваемого на одно соединение от количества одновременных соединений:


А также зависимость количества успешных/провальных попыток от количества одновременных соединений:


Немного об условиях проведения тестов:
И клиент и сервер запускались на одной и тойже машине, с весьма скромными характеристиками —
Intel® Pentium® Processor T2390 (1M Cache, 1.86 GHz, 533 MHz FSB), 2GB RAM.

Выводы


Язык показал довольно высокую производительность в сетевых приложениях, но он еще недостаточно стабилен. В ходе проведения тестов обнаружилось что при нагрузке выше 1500 одновременных соединений сам сервер начал регулярно падать. Возможно дело в том что я исползовал 32 битный компилятор, в то время как основной разработчики считают именно 64 битную версию. Хочется верить что к моменту выхода протокола websockets в массы разработчики смогут довести язык Go до стабильности, достаточной для использования его в качестве основного языка для написания высоконагруженных websocket демонов.

Компактный и гибкий Javascript шаблонизатор

В последние дни на Хабре одна за одной вышли несколько статей посвященных шаблонизаторам на Javascript.
Идея вынести «View» из паттерна MVC на сторону клиента очень интересная, но поизучав существующие билиотеки я понял что мне категорически не хватает их возможностей. Простейший обход свойств объекта зачастую был невозможен.
Естественным образом родилась идея использовать в качестве языка разметки тот же Javascript.

Код получился весьма компактным и сходу удалось применить его в реальных задачах. Можно использовать всю гибкость Javascript, так как на выходе шаблонизатор его и генерит.
Читать дальше →

Что такое Media RSS?

Написание этой статьи навеяло, скудность информации в сети по столь полезной вещи как Media RSS. Media RSS — это расширенная спецификация RSS которая позволяет доставлять медиа-контент (аудио, видео и изображения). С самой описанием спецификации можно ознакомиться тут.

Самое распространенное применение mRSS получила для индексации видео в сети, поэтому поговорим именно об этом. В настоящий момент google и яндекс, yahoo успешно поддерживают формат mRSS. Причем если Вы на своем сайте регулярно размещаете видео, неважно физически расположено оно у Вас на сервере или для этого используются специализированные видео-серверы (youtube, vimeo, rutube), Вы имеете возможность проиндексировать его. После чего видео размещенное на сайте будет доступно поисковикам, это может оказаться очень не плохим подспорьем в продвижение вашего сайте.

В случае с Google Вы можете ссылку на Ваш mRSS указать в качестве sitemap через инструменты веб-разработчика. Для остальных поисковых систем можно просто разместить ссылку, например на главной, аналогично обычной RSS ленте, и видео попадет в индекс. Хочу обратить Ваше внимание, что часто встречается ситуация что не все видео будет индексируется, с этим придется смириться.

Теперь хочу рассказать не много о формате. Пример варианта mrss.xml:

<?xml version="1.0" encoding="UTF-8"?> <rss xmlns:media="http://search.yahoo.com/mrss/" xmlns:dcterms="http://purl.org/dc/terms/" version="2.0"> <channel> <title>Заголовок</title> <link>http://www.site.ru</link> <description>Описание</description> <image> <title>Заголовок изображения</title> <url>http://www.site.ru/images/logo.png</url> <link>http://www.site.ru</link> <width>145</width> <height>122</height> </image> <item> <title>Заголовок для отображения конктерного видео в ленте RSS</title> <link>http://site.ru/myvideo1</link> <description>Описание видео в ленте RSS</description> <pubDate>Tue, 21 Sep 2010 15:51:59 +0400</pubDate> <media:content duration="209"> <media:title type="plain">Заголовок видео для поисковика</media:title> <media:description type="plain">Описание видео для поисковика</media:description> <media:player url="http://vimeo.com/moogaloop.swf?clip_id=1"/> <media:thumbnail url="http://b.vimeocdn.com/ts/907/862/90786246_200.jpg"/> </media:content> <media:community> <media:tags>теги, через, запятую</media:tags> </media:community> </item> </channel> </rss>


Поскольку статей по RSS итак огромное количество, я расскажу лишь о элементах которые специфичны для mRSS. Обращаю Ваше внимание, что это минимальный набор необходимый для корректной индексации, Вы можете его расширить исходя из своих задач, ссылку на подробную спецификацию я дал выше.

В заголовке добавляется объявления пространства имен, в нашем случае достаточно использовать только

xmlns:media=«search.yahoo.com/mrss/»

<rss xmlns:media=«search.yahoo.com/mrss/» version=«2.0»>

Собственно основной элемент <media:content>

<media:content duration=«209»>

Для него необходимо задать время видео ролика в нашем случае duration=«209», этим атрибутом можно пренебречь но гугл настоятельно рекомендует его использовать.

В случае если Вы для размещения видео используете свой сервер, то в этом элементе нужно указать ссылку на местоположение этого видео:

<media:content url=«www.site.com/movie.mov»>

Также для этого элемента существует большой набор атрибутов (fileSize, type, medium, etc) подробнее об их назначение Вы можете узнать прочитав спецификацию. Для индексации видео этими атрибутами можно пренебречь.

Элемент media:content по сути является контейнером который содержит всю необходимая информация о самом видео:
<media:title> — заголовок видео
<media:description> — описание видео
<media:player> — указывается атрибут url который содержит ссылку на плеер с указанием видео.

В случае например использования видео-сервера vimeo.com, ссылка будет иметь вот такой вид

<media:player url=«vimeo.com/moogaloop.swf?clip_id=1»/>

где clip_id в данном случае уникальный идентификатор видео в системе vimeo.ru

<media:thumbnail> — превью для видео

С этим элементом у начинающих разработчиков часто происходит ступор, так как не понимают откуда его собственно брать. Все видео-серверы предоставляют доступ к своему API, подробнее можно узнать на официальных их сайтах. Там доступна информация как получить например время, превью и прочие свойства для размещенного у них видео.

Например в случае vimeo информация о любом файле доступна в виде xml файла
vimeo.com/api/v2/video/id.xml
где id — это уникальный идентификатор видео на сайте

Ну а далее я думаю не представляет труда получить необходимую Вам информацию.

Вторичный элемент это <media:community> — будем говорить социальная составляющая ролика (рейтинг, количество просмотров, теги и пр). В нашем случае достаточно использовать <media:tags> для размещения тегов, так как это наиболее значимый элемент для поисковой индексации.

Но для более полного отображения после поисковых запросов я рекомендую все таки использовать <media:statistics> и <media:starRating>

<media:statistics> — статистика по видео: просмотры и избранное

<media:statistics views=«132» favorites=«24»/>

<media:starRating> — информация о рейтинге видео: оценка, количество голосов, минимальная и максимальная оценка

<media:starRating average=«3.5» count=«20» min=«1» max=«10»/>

Надеюсь после прочтения Вы узнаете что то новое и это тайное знание пригодиться Вам для развития Ваших проектов. Если эта статья Вас заинтересует я хотел бы рассказать о практическом создание mRSS. Удачи!

Стоило ли так ждать Internet Explorer 9?

Корпорация Microsoft наконец-то представила публичную бета-версию IE9, которая отличается от предыдущих тестовых версий наличием полноценного интерфейса. И если о движке браузера многое было известно уже заранее, то новая внешность IE должна была стать сюрпризом для всех. Интерес пользователей постоянно подогревался различными утечками информации, скриншотами и даже видео работы нового интерфейса. В результате к презентации первой бета-версии IE9 было приковано внимание почти всех ведущих мировых IT-СМИ и немалого колличества простых пользователей. Получился ли сюрприз? Ответ читайте в этом топике.


Читать дальше →

Как взобраться на рельсы или первые шаги в освоении Ruby on Rails

Предыстория

Ruby on Rails — фрэймворк на языке Ruby, предназначенный для разработки веб-приложений. Сам Ruby был написан этим милым японцем в 1995 году. В 2003 году Дэвид и Джэйсон начали работу над системой управления проектами под названием Basecamp. В поиске инструмента для разработки выбор пал на Ruby. К июлю 2004 году был выпущен всеми ныне любимый Ruby on Rails, как результат «причесанного» программного каркаса, который был написан для Basecamp.

Читать дальше →

Аналог cookeis в spring remoting

В браузере есть способ хранить и передавать некоторую дополнительную информацию, которая не присутствует в параметрах при вызове GET или не хранится в теле запроса POST. Данная функциональность реализована с помощью кук.
Для браузера это позволяет создать некоторое постоянное хранилище данных на стороне клиента, с помощью которых сервер может идентифицировать клиента.
Spring Remoting можно реализовать поверх протокола http, однако ни одного упоминания про куки я не нашел. Попробуем реализовать их самостоятельно.

Пусть есть некий сервис, опубликованный с помощью spring remoting:

package foo;
public interface FooService {
   public String hello(String name);
}



package foo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class FooServiceImpl {
   private Log logger = LogFactory.getLog(getClass());
   public String hello(String name) {
      logger.info("Greeting "+name);
      return "Hello "+name;
   }
}



<bean id="fooService" class="foo.FooServiceImpl">

<bean name="/FooService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
   <property name="service" ref="fooService"/>
   <property name="serviceInterface" value="foo.FooService"/>
</bean>


На клиенте мы получаем ссылку на сервис:

<bean id="fooService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
   <property name="serviceUrl" value="http://${server.address}:${server.port}/server/remoting/FooService"/>
   <property name="serviceInterface" value="foo.FooService"/>
</bean>


Для начала реализуем способ хранения кук на клиенте. Так как клиент — swing-приложение, то в качестве хранителя можно использовать обычный синглтон. Все куки будем хранить в карте, ключ — имя куки.

package foo;

import java.util.*;
import java.io.Serializable;

public class CookiesHolder {

    private Map<String, Serializable> cookies;

    public void addCookie(String name, Serializable o) {
        if (cookies == null) {
            cookies =new HashMap<String, Serializable>();
        }
        cookies.put(name, o);
    }

    public Serializable getCookie(String name) {
        return cookies!=null ? cookies.get(name) : null;
    }

    public Set<String> getCookiesNames() {
        if (cookies==null) {
            return Collections.emptySet();
        }
        else {
            return cookies.keySet();
        }
    }
}



<bean id="cookieHolder" class="core.service.remote.CookiesHolder" />



Далее необходим способ передать куки на сервер при вызове любого метода. Изучая класс org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean, я наткнулся на метод 

/**
 * Set the RemoteInvocationFactory to use for this accessor.
 * Default is a {@link DefaultRemoteInvocationFactory}.
 * <p>A custom invocation factory can add further context information
 * to the invocation, for example user credentials.
 */
public void setRemoteInvocationFactory(RemoteInvocationFactory remoteInvocationFactory) {
   this.remoteInvocationFactory =
    (remoteInvocationFactory != null ? remoteInvocationFactory : new DefaultRemoteInvocationFactory());
}


По описанию выглядит как то, что нам нужно. Если изучить код
RemoteInvocationFactory, мы увидим единственный метод:

RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation);

Очевидно, что RemoteInvocation содержит данные о том, какой метод вызывается, какие параметры в него передаются. Посмотрим на код RemoteInvocation. Сразу же бросается в глаза поле

private Map attributes;

смотрим описание:
/**
* Add an additional invocation attribute. Useful to add additional
* invocation context without having to subclass RemoteInvocation
* Attribute keys have to be unique, and no overriding of existing
* attributes is allowed.
* The implementation avoids to unnecessarily create the attributes
* Map, to minimize serialization size.
* @param key the attribute key
* @param value the attribute value
* @throws IllegalStateException if the key is already bound
*/
public void addAttribute(String key, Serializable value) throws IllegalStateException {


Итак, при вызове метода сервиса, есть возможность, переопределив RemoteInvocationFactory, поместить в RemoteInvocation дополнительные атрибуты, в частности, наши куки:

package foo;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
import org.springframework.remoting.support.RemoteInvocation;

import java.io.Serializable;

public class CookiesBasedRemoteInvocationFactory extends DefaultRemoteInvocationFactory {
   
    private CookiesHolder cookiesHolder;
    @Override
    public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
        final RemoteInvocation invocation = super.createRemoteInvocation(methodInvocation);
        final Application instance = Application.getInstance();
        CookiesHolder holder;
        if (instance!=null) {
            holder = instance.getCookiesHolder();
        }
        else {
            holder = new CookiesHolder();            
        }
        for (String name : holder.getCookiesNames()) {
            final Object value = holder.getCookie(name);
            if (value instanceof Serializable) {
                invocation.addAttribute(name, (Serializable) value);
            }
        }
        return invocation;
    }

    public void setCookiesHolder(CookiesHolder cookiesHolder) {
        this.cookiesHolder = cookiesHolder;
    }

}



теперь изменим конфигурацию ссылки на сервис:
<bean id='cookiesBasedRemoteInvocationFactory' class="foo.CookiesBasedRemoteInvocationFactory"/>

<bean id="fooService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
   <property name="serviceUrl" value="http://${server.address}:${server.port}/server/remoting/FooService"/>
   <property name="serviceInterface" value="foo.FooService"/>
   <property name="remoteInvocationFactory" ref="cookiesBasedRemoteInvocationFactory"/>
</bean>


Таким образом, любой вызов метода сервиса (в частности hello()) будет передавать на сервер наши куки. Осталось их получить на сервере. Будем действовать аналогичным способом.

Создаем структуру для хранения кук:

package foo;

import org.springframework.util.Assert;

public class ServerCookiesHolder {

    private static ThreadLocal contextHolder = new ThreadLocal();

    public static void clearContext() {
        contextHolder.set(null);
    }

    public static CookiesHolder getContext() {
        if (contextHolder.get() == null) {
            contextHolder.set(new CookiesHolder());
        }

        return (CookiesHolder) contextHolder.get();
    }

    public static void setContext(CookiesHolder context) {
        Assert.notNull(context, "Only non-null CookiesHolder instances are permitted");
        contextHolder.set(context);
    }

}



Изучаем HttpInvokerServiceExporter, находим метод setRemoteInvocationExecutor, который устанавливает обработчика вызова метода. Переопределяем обработчик:

package foo;

import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
import org.springframework.remoting.support.RemoteInvocation;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;

public class CookiesBasedRemoteInvocationExecutor extends DefaultRemoteInvocationExecutor {

    @Override
    public Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Map map = invocation.getAttributes();
        if (map!=null) {

            final Set<String> set = map.keySet();
            final CookiesHolder cookiesContext = ServerCookiesHolder.getContext();
            for (String name : set) {
                cookiesContext.addCookie(name, invocation.getAttribute(name));
            }
        }
        return super.invoke(invocation, targetObject);
    }
}


Изменяем конфигурацию сервиса:


<bean id="cookiesBasedRemoteInvocationExecutor" class="foo.CookiesBasedRemoteInvocationExecutor"/>

<bean name="/FooService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
   <property name="service" ref="fooSearcher"/>
   <property name="serviceInterface" value="foo.FooService"/>
   <property name="remoteInvocationExecutor" ref="cookiesBasedRemoteInvocationExecutor"/>
</bean>



Вуаля. Проверяем. Измененный код сервиса:


   public String hello(String name) {
      logger.info("Greeting "+ServerCookiesHolder.getContext().getCookie("realName"));
      return "Hello "+name;
   }






Код вызова сервиса на клиенте:

public void test() {
   CookiesHolder.setCookie("realName", "Foo");
   fooService.hello("Fred");
}

Алармы — хорошо или плохо? Текущее положение дел в wap'e

Многие из вас далеки от этого «маленького интернета», который в народе принято называть «wap».
Но, все таки, хочу рассмотреть нынешнюю, не легкую ситуацию, которая сложилась в нашем рувапе.


Читать дальше →