IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  SOA и Web-сервисы | XML  >

Совет: Cохранение состояния в сессии сокращает трафик в приложениях Ajax

Серверное кэширование данных и коды ошибок HTTP

developerWorks
Опции документа

Опции документа, требующие включения JavaScript, не отображаются

Обсудить


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

Дэвид Мерц, писатель, Gnosis Software, Inc.

25.03.2009

Следование принципам REST (REpresentational State Transfer – передача репрезентативного состояния) при создании Web-приложений, в частности, приложений Ajax – это хороший способ избежать многих проблем. Однако у архитектуры REST есть один недостаток, заключающийся в передаче лишних данных через схожие запросы XMLHttpRequest. В данной статье рассказывается, как использовать серверные объекты cookies для хранения минимально необходимого количества информации, существенно сокращая объем трафика между клиентом и сервером. В то же время при отключении cookies приложение остается работоспособным.

Введение

Тот простой факт, что HTTP – это протокол без передачи информации о состоянии (stateless-протокол), можно рассматривать как одно из его ключевых преимуществ, так и как один из наиболее существенных недостатков. Все запросы к серверным ресурсам через HTTP должны обладать свойством идемпотентности - т.е. один и тот же запрос должен возвращать одни и те же данные при каждом выполнении. Идемпотентность также является одним из центральных принципов REST: один и тот же запрос, возможно, включающий в себя информацию о клиенте, обязан всегда возвращать один и тот же результат.

В отличие от философии REST, в приложениях Ajax часто бывает важна передача состояния. Например, определенные атрибуты или области интерфейса Web-приложения могут отображать текущее состояние серверных данных, которое может периодически опрашиваться при помощи JavaScript (существуют способы уведомления клиентов об изменении состояния, но в контексте нашей статьи они не имеют решающего значения). При этом Web-приложение в какой-то степени ожидает, что сервер всегда знает, что он должен делать в ответ на каждый запрос. Другими словами, он должен хранить информацию о том, какие данные были (или не были) отправлены клиенту, каким образом происходило взаимодействие и так далее.

Один из стандартных способов реализации принципов REST в приложениях Ajax заключается в использовании глобально уникальных идентификаторов (URI) для каждого запроса, запрашивающего наиболее свежие данные. Идентификатор UUID в запрос можно добавить, либо закодировав дополнительный параметр в URL, либо при помощи скрытой переменной формы. Например, можно направить следующий GET-запрос XMLHttpRequest к ресурсу:

http://myserver.example.com?uuid=4b879324-8ec0-4120-bba6-890eb0aa3fc0

Даже если следующий запрос будет выполнен всего секундой позже, в нем будет использован другой URI.

Хитрости идемпотентности

Термин "тот же самый результат" на самом деле не так очевиден, как может показаться. Запросы по одному и тому же URI будут возвращать полностью идентичные данные разве что в игрушечном мире. Даже статические Web-страницы могут изменяться со временем (например, если были исправлены опечатки в ранее опубликованной статье). Главным смыслом идемпотентности является то, что различие в результатах не должно быть прямым следствием обработки самих GET-запросов. Поэтому вполне допустимо обращаться к постоянно меняющемуся ресурсу, например, такому:

http://myserver.example.com/latest_data/

Проблема заключается в том, что определение того, какие данные являются «последними» (latest_data), зависит от разных факторов, а не только от того, когда и откуда эти данные были получены. Сервер может вполне соответствовать принципам REST и тем не менее поддерживать картину состояния окружающего мира.

Получение последних данных

У меня и моего коллеги, Мики Тебека (Miki Tebeca) был опыт разработки Web-приложения, которое постоянно опрашивало сервер на предмет обновления данных. Запросы выполнялись через объект XMLHttpRequest() в JavaScript. Пример сервера, написанного на Python, который будет продемонстрирован ниже, по сути является упрощенной и доработанной версией внутреннего модуля, созданного Мики.

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

Средства для решения первой проблемы предоставляются непосредственно протоколом HTTP, хотя этим полезным решением неоправданно пренебрегают. При отсутствии изменений сервер может (и должен) возвращать в HTTP-ответе код состояния 304. Далее Ajax-приложение должно проверять данный код и в случае его наличия не обновлять клиентское состояние, так как сервер не переслал новых данных.

Проблема, связанная с ресурсами сервера, может быть решена путем кэширования ранее полученных данных и их обновления по мере работы приложения. Этот вариант решения, как правило, подходит только в том случае, если «последние данные» представляют собой относительно небольшой дискретный набор записей, а не единый массив взаимосвязанных данных. Далее мы будем отслеживать кэшированное состояние сессии приложения-клиента при помощи клиентского объекта cookie, как показано в листинге 1.


Листинг 1. Код сервера с использованием сессий (server.cgi)
from datetime import datetime
session = ClientSession()
old_stuff = session.get("data", [])   # Обращение к кэшу
last_query = session.get("last", None)
prune_data(old_stuff, last_query)     # Отбрасывание устаревших данных
new_stuff = get_new_stuff()           # Получение свежих данных

if not new_stuff:
    print "Status: 304"               # Код состояния "ничего не изменилось"
else
    print session.cookie              # Печать нового или ранее созданного объекта cookie
    print "Content-Type: text/plain"
    print
    all_stuff = old_stuff + new_stuff
    session["data"] = all_stuff
    session["last"] = datetime.now().isoformat()
    print encode_data(all_stuff)      # XML, JSON или что-то еще...
session.save()

Определенную часть работы по управлению сессией берет на себя класс ClientSession, но он довольно прост. Фактически все, что требуется – это проверять каждого клиента на наличие объекта cookie, соответствующего данным, хранящимся в переменной old_stuff (листинг 2).


Листинг 2. Управление сессией
from os import environ
from Cookie import SimpleCookie
from random import shuffle
from string import letters
from cPickle import load, dump

COOKIE_NAME = "my.server.process"

class ClientSession(dict):
    def __init__(self):
        self.cookie = SimpleCookie()
        self.cookie.load(environ.get("HTTP_COOKIE",""))

        if COOKIE_NAME not in cookie:
            # Real UUID would be better
            lets = list(letters)
            shuffle(lets)
            self.cookie[COOKIE_NAME] = "".join(lets[:15])

        self.id = self.cookie[COOKIE_NAME].value
        try:
            session = load(open("session."+self.id, "rb"))
            self.update(session)
        except:       # Если кэш пуст, то не обновляем сессию
            pass

    def save(self):
        fh = open("session."+self.id, "wb")
        dump(self.copy(), fh, protocol=-1)  # Сохранение сессии
        fh.close()

Выполнение запроса через Ajax

Теперь, когда мы реализовали кэширование данных на стороне сервера, построение клиентского запроса на JavaScript не представляет особой сложности. Запрос будет выглядеть примерно так, как показано в листинге 3.


Листинг 3. Запрос к серверу на получение последних данных
var r = new XMLHttpRequest();
r.onreadystatechange=function() {
    if (r.readyState==4) {
        if (r.status==200) {  // Статус "OK"
            displayData(r.responseText);
        }
        else if (r.status==304) {
            // Код "Ничего не изменилось", поэтому ничего не выводим
        }
        else {
            alertProblem(r);
        }
    }
}
r.open("GET",'http://myserver.example.com/latest_data/',true)
r.send(null);

В данном примере мы опустим реализацию методов displayData() и alertProblem(). Вероятно, первый метод будет отвечать за обработку ответа сервера в соответствии с выбранным форматом передачи данных, например, JSON, XML и т.д., а также другими требованиями к приложению.

Кроме того, данный пример иллюстрирует выполнение только единичного запроса. В приложении, выполняющемся длительное время, запросы могут выполняться многократно, причем с использованием таких функций обратного вызова, как setTimeout() или setInterval(). В некоторых приложениях запросы могут также посылаться на сервер вследствие возникновения определенного события или выполнения действия.



В начало


Резюме

Примеры серверного кода в данной статье были написаны на Python, однако те же принципы применимы практически к любому языку, на котором можно создавать CGI или другие серверные приложения. Основная идея достаточно проста: используйте клиентский cookie (если он доступен) для получения доступа к кэшированным данным, а также код состояния 304 в случае, если данные не менялись с момента последнего запроса. Какой бы язык программирования вы ни использовали, код приложения будет выглядеть практически так же.

Хотя мы не уделяли много внимания обработке ошибок, дизайн приложения обеспечивает корректное поведение в случае отсутствия объектов cookie. Если данный клиент не содержит cookie (например, он не позволяет сохранять cookie или просто выполняет первый запрос в данной сессии), то содержимое переменной old_stuff будет представлять собой пустой список. В результате все данные для этого клиента будут новыми (будут присвоены переменной new_stuff). Приложение можно слегка усовершенствовать, добавив поддержку специального типа клиентских сообщений, в которых бы указывалось текущее состояние сессии. Это полезно для отладки приложения, так и для сброса состояния на сервере в случае, если клиент определил, что произошло рассогласование клиентского состояния и содержимого кэша. При этом все негативные последствия, связанные со сбросом кэша, сводятся к небольшому увеличению нагрузки на сервер и сеть передачи данных и не приводят к нарушению свойства идемпотентности.



Ресурсы



Об авторе

Дэвид Мерц (David Mertz) - большой знаток в области открытых стандартов и только умеренно пугает многословием. С Дэвидом можно связаться по mertz@gnosis.cx его жизнь описывается более подробно на http://gnosis.cx/dW/. Предложения и комментарии по этой, предыдущей или будущей статьям приветствуются. Можете также посмотреть книгу Дэвида Text Processing in Python.




Выскажите мнение об этой странице


Пожалуйста, найдите минутку и заполните форму, чтобы повысить уровень сервиса.



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты