Реализация чата на ASP.NET с использованием Long Polling

Автор: Вячеслав Гринин | веб-программирование | 13 Мар 2010 2:06 пп

Бродя по просторам интернета, на одном из сайтов я увидел простой чат. Просмотрев страницу в FireBug я понял, что никакими апплетами там и не пахнет, а значит чат реализован на простом JavaScript.

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

И встал передо мной вопрос – как они это сделали? Как они обеспечили такую высокую скорость отдачи свежих данных? Ведь веб-сервер на то и сервер, что может лишь отвечать на запросы пользователя, но уж никак не самостоятельно инициировать запросы.

Непродолжительный поиск по интернетам дал мне ответ. И ответ этот “Long Polling“, а встречается еще и другое название технологии – Comet.

Итак, как же это работает?

По сути, это все тот же старый добрый AJAX, и использование объекта XmlHttpRequest. С одной большой разницей, заключенной на серверной стороне. Разница состоит вот в чем. Клиент посылает XHR-запрос и длительное время ожидает ответа сервера. А “длительное время” отклика обеспечивает сам сервер, который не сразу отдает данные клиенту, а лишь только тогда, когда у него в очереди появляются свежие данные, то есть когда ему действительно есть что сказать.

Клиент, дождавшись ответа, обрабатывает его (например, отображает сообщение на странице), а затем, без промедления, снова запрашивает сервер. И снова ждет.

Если же подав запрос клиент так и не дождался ответа из-за таймаута, то он снова запрашивает сервер. А откуда этот таймаут? А просто серверу нам нечего ответить, вот он и не отдает клиенту результат запроса. Ждет. Но не дождавшись, разрывает соединение по таймауту. Такая вот несложная схема.

Это и называется Long Polling, то есть – длительный опрос.

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

Итак… Без особых размышлений приведу здесь листинг кода и объясню принцип его действия.

using System;
using System.Collections.Generic;

// Класс описывающий одно сообщение от клиента и
// метод его сериализации
public class CometMessage
{
 public string UserName;
 public string Message;
 public string Serialize()
 {
 return "{'user': '" + UserName + "', 'message': '" + Message + "'}";
 }
}

// Собственно, серверная часть
public static class CometServer
{
 // вспомогательный объект для блокировки ресурсов
 // многопоточного приложения
 private static Object _lock = new Object();

 // Список, хранящий состояние всех подключенных клиентов
 private static List<CometAsyncState> _clientStateList =
   new List<CometAsyncState>();

 // Возвращаем сообщение каждому подключенному клиенту
 public static void PushMessage(CometMessage message)
 {
 lock (_lock)
 {
 // Пробегаем по списку всех подключенных клиентов
 foreach (CometAsyncState clientState in _clientStateList)
 {
 if (clientState.CurrentContext.Session != null)
 {
 // И пишем в выходной поток текущее сообщение
 clientState.CurrentContext.Response.Write(message.Serialize());
 // После чего завершаем запрос - вот именно после этого результаты
 // запроса пойдут ко всем подключенным клиентам
 clientState.CompleteRequest();
 }
 }
 }
 }

 // Срабатывает кажды раз при запуске клиентом запроса Long poll
 // так как при этом HttpContext клиента изменяется, то надо обновить
 // все изменившиеся данные клиента в списке, идентифицируемом по
 // гуиду, который у клиента в течение работы остается постоянным
 public static void UpdateClient(CometAsyncState state, String guid)
 {
 lock (_lock)
 {
 // ищем клиента в списке по его гуиду
 CometAsyncState clientState = _clientStateList.Find(s => s.ClientGuid
   == guid);
 if (clientState != null)
 {
 // и если он нашелся, то обновляем все его параметры
 clientState.CurrentContext = state.CurrentContext;
 clientState.ExtraData = state.ExtraData;
 clientState.AsyncCallback = state.AsyncCallback;
 }
 }
 }

 // Регистрация клиента
 public static void RegicterClient(CometAsyncState state)
 {
 lock (_lock)
 {
 // Присваиваем гуид и добавляем в список
 state.ClientGuid = Guid.NewGuid().ToString("N");
 _clientStateList.Add(state);
 }
 }

 // Разрегистрация клиента
 public static void UnregisterClient(CometAsyncState state)
 {
 lock (_lock)
 {
 // Просто удаляем его из списка
 _clientStateList.Remove(state);
 }
 }
}

Итак:

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

Далее идет описание класса CometServer в котором и живет вся серверная логика LongPolling-технологии.

Заметим, что в переменной _clientStateList (строка 24) хранятся все подключенные на данный момент к серверу клиенты.

Метод PushMessage (строка 24) пробегает по всему списку клиентов и пишет в выходной поток каждого переданное ему в качестве аргумента сообщение, естественно, предварительно сериализовав его. После чего для каждого клиента в обязательном порядке вызывается метод CompleteRequest(), который завершает асинхронный запрос, передавая его результаты клиенту.

Метод UpdateClient (строка 51) по полученному им гуиду клиента, находит клиента в списке clientStateList и обновляет все его параметры, такие как HttpContext-например. Этим мы обеспечиваем всегда актуальное состояние списка клиентов, после каждого их реконнекта.

Метод RegicterClient (строка 69) /опечатка вышла в его имени, сами исправите при необходимости:)/ добавляет нового клиента в очередь.

Метод UnregisterClient (строка 80) соответственно – удаляет клиента из очереди. Что вполне законно. Зачем уведомлять клиента о новых сообщениях в чате, если он давно покинул чат?

Дальше идет описание класса CometAsyncState. Зачем мы его создали? Да чтобы хранить такие параметры клиента, как CurrentContext, AsyncCallback и ClientGuid. Ну то есть, чтобы отдельный поток, порожденный в пуле потоков и отвечающий за одного клиента, мог собственно функционировать и взаимодействовать с клиентом. Этот класс, по большому счету, всего лишь набор заглушек для методов интерфейса IAsyncResult плюс метод CompleteRequest(), который вызывает callback-функцию при завершении потока.

using System;
using System.Threading;
using System.Web;

public class CometAsyncState : IAsyncResult
{
    // Чтобы было где хранить все это и был создан
    // класс-наследник от IAsyncResult
    public HttpContext CurrentContext;
    public AsyncCallback AsyncCallback;
    public object ExtraData;
    public string ClientGuid;
    private Boolean _isCompleted;

    // Конструктор
    public CometAsyncState(HttpContext context,
      AsyncCallback callback, object data)
    {
        CurrentContext = context;
        AsyncCallback = callback;
        ExtraData = data;
        _isCompleted = false;
    }

    // Завершим запрос
    public void CompleteRequest()
    {
        // При завершении запроса просто выставим флаг
        // что он завершен
        // и вызовем callback
        _isCompleted = true;
        if (AsyncCallback != null)
        {
            AsyncCallback(this);
        }
    }

    #region IAsyncResult Members
    // И снова видим набор заглушек для интерфейса IAsyncResult

    public Boolean CompletedSynchronously
    {
        get
        {
            return false;
        }
    }

    public bool IsCompleted
    {
        get
        {
            return _isCompleted;
        }
    }

    public object AsyncState
    {
        get
        {
            return ExtraData;
        }
    }

    public WaitHandle AsyncWaitHandle
    {
        get
        {
            return new ManualResetEvent(false);
        }
    }
    #endregion
}

Осталась малость – клиентский JavaScript. Ниже я приведу его с моими подробными комментариями в самом коде и не буду его так детально разжевывать. Потому что весь он разжеван в начале статьи, где я описывал принцип работы LongPolling.

var clientGuid

$(document).ready(function() {
 // Подключаемся после загрузки страницы,
 // запускаем первый long polling
 Connect();
});

$(window).unload(function() {
 // При выгрузке страницы - запрашиваем сервер об отключении
 // клиента для экономии ресурсов
 Disconnect();
});

// Посылает lonp poll - запрос серверу
function SendRequest() {
 var url = './comet.ashx?guid=' + clientGuid;
 $.ajax({
 type: "POST",
 url: url,
 // Если запрос завершился успехом, значит сервер сообщил
 // о новых событиях - обрабатываем их
 success: ProcessResponse,
 // При ошибке (например таймауте), снова рекурсивно
 // посылаем запрос обеспечивая тем самым непрерывный
 // процесс прослушки серверных событий
 error: SendRequest
 });
}

// Регистрируемся на сервере
function Connect() {
 var url = './comet.ashx?cmd=register';
 $.ajax({
 type: "POST",
 url: url,
 success: OnConnected,
 error: ConnectionRefused
 });
}

// Разрегистрируемся на сервере
function Disconnect() {
 var url = './comet.ashx?cmd=unregister';
 $.ajax({
 type: "POST",
 url: url
 });
}

// Обработка сообщений, принятых с сервера
function ProcessResponse(transport) {
 eval('var d=' + transport + ';');
 document.getElementById("content").innerHTML +=
    ' <strong>' + d.user + '</strong> : "' + d.message + '"<br/>';
 // После отображения результатов запроса -
 // снова циклично делаем запрос.
 SendRequest();
}

// После регистрации на сервере сохраняем наш guid и
// посылаем первый long poll запрос на сервер
function OnConnected(transport) {
 clientGuid = transport;
 SendRequest();
}

// Если подключиться не удалось, то ждем три мекунды
// и опять пробуем подключиться
function ConnectionRefused() {
 $("#content").html("не удалось подключиться к серверу.
    Попробуем через 3 секунды...");
 setTimeout(Connect(), 3000);
}

// Отправка сообщения на сервер
function clickSendMessage() {
 var userName = document.getElementById("userName").value;
 var message = document.getElementById("message").value;
 var url = './comet.ashx?cmd=send&message=' + message + '&user=' + userName;
 $.ajax({
 type: "POST",
 url: url
 });
}

Ну и разумеется, код серверного хэндлера comet.ashx, который обрабатывает команды клиентского скрипта. Скажу сразу, хэндлер этот асинхронный, а потому унаследован не от IHttpHandler, а от IHttpAsyncHandler

< %@ WebHandler Language="C#" Class="CometAsyncHandler" %>

using System;
using System.Web;
using System.Threading;

public class CometAsyncHandler : IHttpAsyncHandler,
  System.Web.SessionState.IRequiresSessionState
{
    #region IHttpAsyncHandler Members

    public IAsyncResult BeginProcessRequest(HttpContext ctx,
      AsyncCallback cb, Object obj)
    {
        // Готовим объект для передачи его в QueueUserWorkItem
        CometAsyncState currentAsyncState =
          new CometAsyncState(ctx, cb, obj);

        // Добавляем в тредпул новый ждущий поток
        ThreadPool.QueueUserWorkItem(new WaitCallback(RequestWorker),
           currentAsyncState);

        return currentAsyncState;
    }

    public void EndProcessRequest(IAsyncResult ar)
    {
    }

    #endregion

    #region IHttpHandler Members
    // IHttpHandler Members - просто пустые заглушки,
    // так как нам не требуется реализация синхронных методов

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    public void ProcessRequest(HttpContext context)
    {
    }

    #endregion

    // Основная функция рабочего потока
    private void RequestWorker(Object obj)
    {
        // obj - второй параметр
        // при вызове ThreadPool.QueueUserWorkItem()
        CometAsyncState state = obj as CometAsyncState;

        string command =
          state.CurrentContext.Request.QueryString["cmd"];
        string guid =
          state.CurrentContext.Request.QueryString["guid"];

        switch (command)
        {
            case "register":
                // Регистрируем клиента в очереди сообщений
                CometServer.RegicterClient(state);
                state.CurrentContext.Response.Write(
                  state.ClientGuid.ToString());
                state.CompleteRequest();
                break;
            case "unregister":
                // Удаляем клиента из очереди сообщений
                CometServer.UnregisterClient(state);
                state.CompleteRequest();
                break;
            case "send":
                // Отсылка сообщения
                string message =
                  state.CurrentContext.Request.QueryString["message"];
                string userName =
                  state.CurrentContext.Request.QueryString["user"];
                CometServer.PushMessage(new CometMessage() {
                  Message = message, UserName = userName });
                state.CompleteRequest();
                break;
            default:
                // При реконнекте клиента
                if (guid != null)
                {
                    CometServer.UpdateClient(state, guid);
                }
                break;

        }
    }
}

Основная логика программы кроется в методе RequestWorker (строки 51-95), который в зависимости от полученной с клиента команды, выполняет либо регистрацию/разрегистрацию клиента, либо отправку сообщения. Либо обновление данных о клиенте при его реконнекте.

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

Вот и все. Надеюсь, что статья оказалось полезной для вас.

Прочтите еще:

AJAX. Загрузка файлов.

Автор: Вячеслав Гринин | веб-программирование | 20 Фев 2010 7:39 пп

Немного странное название статьи… Мы помним, что AJAX это асинхронный JavaScript, построенный на основе объекта XmlHttpRequest. Отличная технология! С некоторыми ограничениями. Одно из них – отсутствие кросдоменности – мы уже научились обходить в статье Кросдоменный JavaScript (JSONP). Но есть у этой замечательной технологии еще один недостаток – она не позволяет асинхронно загружать файлы. Ну вот не умеет объект XmlHttpRequest загружать файлы!

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

Для загрузки файлов мы используем технологию, которая называется Remote Scripting посредством IFRAME. А в общем, технология проста. Для загрузки файла мы все также будем использовать обычную форму FORM, в которой разместим самый обычный INPUT TYPE=’FILE’. Все как и прежде в случае синхронной загрузки файла. И вот здесь и начинаются отличия. А отличие состоит в том, что у формы target указывает на скрытый на странице IFRAME, который по сути и перезагрузится во время операции. Мало того, в IFRAME мы вернем результат операции в виде вызова простого JavaScript-коллбэка, который завершит операцию загрузки.

Итак, от слов к делу. Далее я приведу пример кода, который и реализует вышеописанную технологию. Код этот очень простой.

<html>
<head>
<script type="text/javascript">
 function onResponse(d) {
 eval('var obj = ' + d + ';');
 alert('Файл ' + obj.filename + (obj.success ? " " : " НЕ ") +
    "загружен.");
 }
 </script>
</head>

<iframe id="rFrame" name="rFrame" style="display: none">
</iframe>

<form action="handler.php" target="rFrame" method="POST"
   enctype="multipart/form-data">
<input type="file" name="loadfile">
<input type='submit' value='Загрузить'>
</form>

</html>

Здесь мы видим функцию onResponse, которая получает результат обработки файла(результат этот мы формируем на сервере). Еще мы видим IFRAME с идентификатором rFrame, про который мы говорили выше. И видим мы также саму форму загрузки файла.

У формы action=”handler.php” указывает на серверный обработчик, и не забудьте указать enctype=”multipart/form-data” иначе форма не сможет загружать файлы.

А дальше я приведу серверный код, содержимое файла handler.php.

<?php
 function jsOnResponse($obj)
 {
 echo '
 <script type="text/javascript">
 window.parent.onResponse("'.$obj.'");
 </script>
 ';
 }

 $dir = '/home/path/path/path';
 $name = basename($_FILES['loadfile']['name']);
 $file = $dir . $name;

 $success = move_uploaded_file($_FILES['loadfile']['tmp_name'], $file);
 jsOnResponse("{'filename':'" . $name . "', 'success':'" . $success . "'}");

?>

Этот код просто сохраняет файл в папку на сервере, объяснять саму загрузку нет смысла – она стандартна. Обращу лишь внимание, что после загрузки файла вызывается функция jsOnResponse которая просто пишет в выходной поток вызов JavaScript-функции window.parent.onResponse($obj). Клиентский IFRAME, приняв этот код, выполнит его, вызвав функцию onResponse у родительской страницы фрейма.

Вот и все. Пользуйтесь. Здесь можно скачать работающий код приведенного в статье примера.

Используя уникальную систему поиска Price-AZ можно заказать любые запчасти Фольксваген и Ауди на сайте ООО “Польга”. Теперь стало гораздо удобнее найти нужную запчасть не только по номеру, но и по ее названию. При магазине есть также удобный автосервис. Кстати, система Price-AZ позволяет найти запчасть на автомобиль любой марки.

Приятный и удобный интернет магазин в котором есть все: от бытовой техники, до фототехники и телефонов. Захотел даже купить себе оригинальные весы подсказывающие цветом индикации динамику изменения моего веса.

Прочтите еще:

AJAX с помощью jQuery и ASP.NET web-сервисов

Автор: Вячеслав Гринин | веб-программирование | 21 Сен 2009 5:14 пп

Если вы создаете сайты под ASP.NET, то возможно вам уже довелось использовать AJAX-framework, предоставляемый Microsoft. Тот самый, что использует ScriptManager, rfк в примере, приведенном ниже:

1) Содержимое страницы Default.aspx:

<asp:ScriptManager ID="MainSM" ScriptMode="Debug" runat="server">
 <Services>
 <asp:ServiceReference Path="~/usersrv.asmx" InlineScript="false" />
 </Services>
 </asp:ScriptManager>

2) Содержимое файла script.js:

// вызов ajax-метода GetPoint
usersrv.GetPoint(id,OnUSGetPointSucc,OnUSGetPointErr);
// callback успешного вызова
function OnUSGetPointSucc(res)
{
 alert('success');
}
// callback ошибки
function OnUSGetPointErr(err)
{
 alert('success');
}

3) Содержимое файла usersrv.asmx:

<%@ WebService Language="C#" CodeBehind="~/App_Code/usersrv.cs" %>

4) Содержимое файла usersrv.cs:

[WebService(Namespace = "http://tempuri.org/")]
[System.Web.Script.Services.ScriptService]
public class usersrv : System.Web.Services.WebService
{
[WebMethod]
public Dictionary<string, object> GetPoint(long p_id)
 {
 Dictionary<string, object> res = new Dictionary<string, object>();
 try
 {
   // готовим возвращаемые параметры и сохраняем их в res
 }
 catch (Exception)
 {
 res["Error"] = U.GTFR("userpoints_err");
 }
 return res;
 }
}

Этот метод достаточно прост и удобен, если бы не одно “но” – он весит 132 Кб. И обладает еще одним интересным свойством: если в нашем проекте есть несколько страниц, использующих AJAX, то каждая из них будет заново загружать этот скрипт, потому что при загрузке скрипта на странице используется примерно следующий URL http://domainname/ScriptResource.axd?d=TvjJs2RlM8MX3pIUhEsdnZKUzGh-9Wr9nvNBZJbxf-xzq-Wvzbpj6FfMJqZqOPR8Pku52J2zBqleUlYtVE8XCyfyo3kcbSnhbwzYc2LkdfQ1&t=ffffffffee41303f и для каждой страницы в пределах вашего проекта будет свой ScriptManager, а значит и свой уникальный ID в query-string. А значит скрипт этот для каждой страницы будет браться не из кэша, а запрашиваться заново с сервера.

В общем, мне все это не понравилось и я заинтересовался, как можно сэкономить трафик и перейти на AJAX.jQuery. Тем более, что он уже использовался в этом же проекте для других целей. Сказано – сделано! И вот какое решение я нашел.

Собственно, модификации пришлось подвергнуть как сами клиентские JavaScript-ы, так и методы, вызываемые на стороне сервера. Связано это с тем, что мне хотелось передавать в качестве параметров для web-сервиса сложные объекты, а не просто строки и целые значения. И получать в ответ хотелось тоже сложные объекты, а не просто строки.

Все это можно легко и просто сделать, передавая как в ту так и в другую сторону все же строки, но строки в формате JSON, который является родным для JavaScript. Да к тому же ASP.NET обладает встроенными возможностями сериализации/десериализации JSON.

Правда, для сериализации объекта в JSON на клиентской стороне пришлось использовать еще одну дополнительную библиотеку json2.js. Таким образом, обе библиотеки весят около 67 Кб. Да плюс ко всему в случае нескольких ajax-страниц в проекте, скрипт загрузится всего один раз, и для остальных страниц будет браться из кэша браузера.

Но от слов – к делу!  Вот содержимое скрипта, выполняемого на клиентской стороне:

// готовим передаваемые параметры в переменную par, здесь - всего лишь эмуляция
 var par = new Object();
 par.a = 5;
 par.b = 'data';
 par.c = new Array();
 par.c[0] = 5;
 par.c[1] = 10;
 // сериализуем объект
 var ser = JSON.stringify(par);
 // формируем ajax-запрос
 $.ajax({
 type: "POST",
 url: "usersrv.asmx/GetPoint",
 data: "{'par':'"+ser+"'}",
 contentType: "application/json; charset=utf-8",
 dataType: "json",
 success: function(r) {
 var res=r.d;
 // сериализуем для наглядности полученные данные и выводим их
на страницу
 // в реальном приложении здесь мы просто будем использовать готовые
данные
 // в переменной res без сериализации
 document.getElementById("result").innerHTML=JSON.stringify(r);
 },
 error: function(err) {
 // обработка ошибки
 alert('error: '+err.responseText);
 }
 });
 }

А вот содержимое usersrv.cs:

using System;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.Web.Script.Serialization;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class usersrv : System.Web.Services.WebService
{
 public usersrv() { }

 [WebMethod]
 public ResponseType GetPoint(string par)
 {
 ResponseType response = new ResponseType();
 RequestType request = new RequestType();
 try
 {
 // десериализуем полученные параметры
 JavaScriptSerializer ser = new JavaScriptSerializer();
 request = ser.Deserialize<RequestType>(par);
 }
 catch (Exception)
 {
 response.isError = true;
 response.message = "Parameters is invalid";
 return response;
 }

 // преобразуем переданные параметры и готовим ответ
 // здесь - всего лишь эмуляция обработки данных
 if (request.c != null && request.a == request.c.Length)
 {
 response.isError = false;
 response.message = "That's good";
 }
 else
 {
 response.isError = true;
 response.message = "Length is not the same";
 }
 return response;
 }
}

А вот описание двух воспомогательных классов:

public class RequestType
{
    public int a;
    public string b;
    public int[] c;
}
public class ResponseType
{
    public string message;
    public bool isError;
}

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

Ссылка на архив с готовым проектом здесь.

Прочтите еще:

AJAX. 2 – Подробнее об XMLHttpRequest

Автор: Вячеслав Гринин | веб-программирование | 30 Янв 2009 5:34 пп

В статье AJAX. 1 – Что это такое? я рассказал про то, в каких случаях можно использовать AJAX и привел тестовый пример использования AJAX в веб-разработке. Теперь я хочу немного углубиться в подробности работы с объектом XMLHttpRequest и расскажу про его основные свойства и методы. Итак…

Методы объекта XMLHttpRequest:

  • abort() – Прекращает исполнение текущего запроса. При этом объект XMLHttpRequest возвращет readyState=4 и status=0.
  • getAllResponseHeaders()- Возвращает HTTP-заголовки ответа в
    виде строки. Эта строка может выглядеть, например, так:
    Date: Fri, 30 Jan 2009 13:57:48 GMT Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4 OpenSSL/0.9.8d PHP/5.2.4 X-Powered-By: PHP/5.2.4 Content-Length: 91 Keep-Alive: timeout=5, max=98 Connection: Keep-Alive Content-Type: text/xml
  • getResponseHeader(”header-name”) – Возвращает заголовок “header-name” ответа. Для случая getResponseHeader(’Content-Type’) она вернет
    text/xml
  • open(”method”, “URL”, async-flag, “user-name”, “password”) – Инициализирует параметры запроса. Если в качестве “method” используется строка “POST”, то метод send(’content’) в качестве параметра должен принимать данные, передаваемые методом POST. Если же “method”=”GET”, то параметры обработчику передаются в параметре “URL” метода open()
  • send(”content”) – Выполняет HTTP-запрос.
  • setRequestHeader(”header-name”, “header-value”) – Добавляет в запрос HTTP-заголовок.

Свойства объекта XMLHttpRequest::

  • onreadystatechange – Установка функции обратного вызова, которая будет обслуживать изменение состояния запроса.
  • readyState – Возвращает состояние запроса
    0 – не инициализирован
    1 – идет отправка запроса
    2 – запрос отправлен
    3 – идет обмен
    4 – обмен завершен
  • responseText – Содержит ответ сервера в виде строки
  • responseXML – Содержит ответ сервера в виде XML-документа
  • status – Возвращает код состояния запроса
  • statusText – Возвращает сообщение о состоянии запроса

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

Прочтите еще:

AJAX. 1 – Что это такое?

Автор: Вячеслав Гринин | веб-программирование | 23 Янв 2009 9:01 пп

Эта статья открывает целый цикл статей, посвященных загадочной и очень увлекательной теме AJAX. Я постараюсь донести до вас, дорогие читатели, что же это за зверь такой, и как это можно использовать в веб-разработке для создания веб-приложений, написанных на ASP.NET, PHP, а может и каких других языках и платформах.
Когда некий узкий круг лиц узнал про AJAX и начал его использовать в своих приложениях, вокруг него поднялась такая шумиха, что казалось, будто об этом говорят все вокруг, не исключая бабушек на лавочке у подъезда. Я сам впервые услышал этот термин, когда ехал на трамвае в универ. Рядом со мной стояла парочка, и молодой, но уже бородатый парнишка с всклокоченными волосами рассказывал своей подружке об аяксе. Использовал он при этом такие высокопарные выражения, что я бы не удивился, если б узнал, что он втихомолку по вечерам строчит нервною рукою в тетрадь с пожелтевшими страницами оды или, как минимум, сонеты. В общем, «погиб поэт, невольник чести… стал программистом он, увы».
Но лирическое вступление слишком затянулось.
Так что же такое AJAX? Расшифровывается это слово, как Asynchronous JavaScript and XML, то есть «Асинхронный JavaScript и XML». Отсюда можно понять, что для успешного использования этой технологии, никуда не деться, нужно хоть немного знать JavaScript и уметь на нем писать. А вот XML позволительно и не знать, ибо можно обойтись и без него.
Так вот AJAX – это технология, позволяющая веб-страницам на клиентской стороне, то есть прямиком из браузера пользователя, в фоновом режиме обращаться к серверу и получать от него некоторые необходимые данные. А это значит, что для обновления части страницы браузеру не нужно перезагружать всю страницу целиком. И обращение это может происходить незаметно для пользователя, не заставляя его дожидаться перезагрузки страницы. При грамотном применении эта технология позволит вам, как программисту, сэкономить время пользователя и его трафик. А мы знаем, что пользователи – жутко нервные и обидчивые создания. Не дай бог заставить юзера ждать обновления страницы слишком долго, и он тут же переметнется к вашим конкурентам. Причем это совсем не шутки.
Чтобы увидеть один из аспектов применения технологии AJAX, зайдите на http://www.yandex.ru/ или на http://www.google.ru/ , введите в строке поиска что-нибудь, например букву «п», и тут же под полем ввода откроется список наиболее популярных поисковых запросов, начинающихся с буквы «п». Откуда взялся этот список? В тот момент, когда вы отпустили клавишу «п», ваш веб-браузер в фоновом режиме отправил запрос серверу вот такого вида: http://suggest.yandex.ru/suggest-ya.cgi?ct=text/html&part=%D0%BF , а в ответ очень быстро получил вот такую строку:
suggest.apply(["п", ["погода", "переводчик", "поздравления с днем рождения", "почта", "переводчик онлайн", "приколы", "прогноз погоды", "погода в москве", "поздравления с новым годом", "поздравления"], []], []) , где в квадратных скобочках можно увидеть все эти наиболее популярные поисковые запросы. JavaScript в браузере быстренько добавил к этому списочку HTML-разметку и отобразил ее на вашей странице в абсолютно позиционированном DIV-е.
Сайт http://maps.google.com/ вообще весь целиком и полностью построен на AJAX. Именно поэтому, когда вы двигаете мышью карту, ваша страница не перезагружается.
Посмотрим теперь из чего состоит AJAX. В его основе лежат 4 базовые вещи:

  1. JavaScript. Если вы профессионал в веб-разработке, то без него здесь никуда. Именно на нем реализована функциональность на стороне клиента. И даже если вы используете готовые AJAX-решения, вам не избежать того, что придется еще кое-что писать ручками.
  2. Объектная модель документа(DOM). Знание этой модели нужно, чтобы отобразить на странице в браузере ту информацию, что пришла в ответ на асинхронный запрос серверу от браузера.
  3. Объект XMLHttpRequest. Именно он позволяет из JavaScript организовать асинхронный доступ к серверу. Без этого объекта AJAX – мертв!
  4. Ну и, наконец, любая из серверных технологий, будь то PHP, ASP, ASP.NET, JSP, Ruby on Rails и т.д. и т.п. Вы можете использовать здесь то, что вам больше по душе.

Как это работает?

  1. Страница (конечно же, посредством JavaScript) в браузере пользователя, по какому-либо событию (нажатие клавиши, таймер) с помощью объекта XMLHttpRequest подает запрос серверу, обращаясь к некоему серверному обработчику, передавая ему некоторые параметры, например, значения, введенные пользователем в поле «Имя пользователя» на форме регистрации.
  2. Обработчик принимает этот запрос и обрабатывает его. Чаще всего, это некий поиск базе данных, и формирование результатов в виде структуры данных, а может и в виде готовой HTML-разметки. Собственно, в результате выполнения запроса на выходе получается некая строка, в которой упакованы данные. По завершению обработки данных, обработчик выдает эти данные обратно браузеру пользователя. Упаковка чаще всего производится либо с использованием JSON, либо XML.
  3. Браузер принимает эту строку, распаковывает ее, и использует для отображения на загруженной странице. В нашем случае, рядом с полем «Имя пользователя» появится надпись «имя свободно» или «имя занято». Кстати, в качестве первого примера использования AJAX мы напишем собственное веб-приложение, которое будет отвечать на вопрос, занято или свободно выбранное имя пользователя.

Ну что ж, а теперь попробуем создать свое первое веб-приложение, использующее технологию AJAX. Для этого вам понадобится локальный веб-сервер с поддержкой PHP, и если вы не знаете, откуда его взять, то зайдите сюда , и уже спустя несколько минут у вас будет свой собственный локальный веб-сервер.
А теперь создадим наше первое AJAX-приложение.

  1. Создайте страницу регистрации пользователя index.html. На ней расположены поле ввода имени пользователя, кнопка «Проверить имя» и DIV, в котором будут выводиться результаты проверки.
    <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
    <html xmlns=”http://www.w3.org/1999/xhtml“>
    <head>
    <title> Простой пример на AJAX</title>
    <script type=”text/javascript” src=”ajax.js”></script>
    </head>
    <body>
    Имя пользователя: <input type=”text” id=”username”><input type=”button” onclick=”checkName()” value=”проверить имя”>
    <div id=”umess”>…</div>
    </body>
    </html>
  2. Создайте файл ajax.js, содержащий в себе клиентский скрипт на JavaScript. Его функциональность подробно прокомментирована в самом тексте.
    // Это и есть объект XmlHttpRequest
    var xmlHttp=createXmlHttpRequest();
    // синоним для getElementById
    function gebi(id) {
    return document.getElementById(id);
    }
    // функция кроссбраузерно создает объект XmlHttpRequest
    function createXmlHttpRequest() {
    var xmlHttp;
    try {
    if(window.ActiveXObject) // для IE
    xmlHttp=new ActiveXObject(”Microsoft.XMLHTTP”);
    else // для остальных браузеров
    xmlHttp=new XMLHttpRequest();
    }
    catch(e) {
    xmlHttp=false;
    alert(”Объект XMLHttpRequest не создан!”);
    }
    return xmlHttp;
    }
    // вызывается по клику на кнопке “Проверить имя”
    function checkName() {
    // проверяем, завершил ли объект XMLHttpRequest прошлый вызов
    if(xmlHttp.readyState==4 || xmlHttp.readyState==0) {
    // получаем строку, введенную пользователем
    usname=encodeURIComponent(gebi(”username”).value);
    // говорим объекту XMLHttpRequest куда и как он должен обратиться
    xmlHttp.open(”GET”, “handler.php?username=”+usname, true);
    // говорим объекту XMLHttpRequest какую функцию он должен вызывать при изменении своего статуса
    xmlHttp.onreadystatechange=handleResponse;
    // посылаем асинхронный запрос серверу
    xmlHttp.send(null);
    }
    }
    // вызывается при изменении статуса объекта XMLHttpRequest
    function handleResponse() {
    // проверяем по readyState завершена ли транзакция
    if(xmlHttp.readyState==4) {
    // проверяем по status успешность транзакции
    if(xmlHttp.status==200) {
    // если все порядке, читаем возвращенный XML документ
    xmlResponse=xmlHttp.responseXML;
    xmlElement=xmlResponse.documentElement;
    res=xmlElement.firstChild.data;
    var umess=gebi(”umess”);
    // в соответствии с возвращенным результатом, выводим на страницу нужное сообщение
    if(res==’true’)
    umess.innerHTML=”Имя свободно”;
    else
    umess.innerHTML=”Имя занято”;
    }
    else
    alert(”Ошибка при обращении к серверу. “)+xmlHttp.statusText;
    }
    }
  3. Создайте файл handler.php. Именно с этим серверным скриптом будет взаимодействовать страница в браузере пользователя. По хорошему, здесь должная быть настоящая проверка на существование в базе MySQL пользователя с введенным именем. В демонстрационных целях здесь реализована фиктивная проверка по первой букве имени.
    <?php
    // возвращать будем XML
    header("Content-Type: text/xml");
    // создаем заголовок XML и элемент response
    echo '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
    echo ('<response>');
    // из GET-строки получаем имя пользователя
    $username=$_GET['username'];
    // Фиктивная проверка на существование такого пользователя БД
    if(substr($username,0,1)=='a')
    echo('true');
    else
    echo('false');
    // закрываем элемент response
    echo('</response>');
    ?>
  4. А теперь введите в браузере http://localhost/index.html и протестируйте ваше приложение.

Здесь расположен архив со всеми необходимыми файлами. Просто распакуйте его в корневую директорию вашего веб-сервера и выполните в браузере http://localhost/index.html.

Прочтите еще: