Как стать автором
Обновить

IIS 7: компрессия JSON ответа

Время на прочтение 4 мин
Количество просмотров 14K
Несколько дней назад мой знакомый обратил внимание, что его большие JSON ответы (порядка 0,5-1 мегабайт) не паковались. Рабочая связка Windows 2008 R2 + IIS 7.5 + ASP.NET MVC 4. Проблема обозначена, начался поиск решения. Чтобы воспроизвести наши действия, для статьи я написал отдельное приложение.

Метод действия

public ActionResult Index()
{
	if (Request.IsAjaxRequest())
	{
		return Json(new {/*большой объем данных*/}, JsonRequestBehavior.AllowGet);
	}
	return View();
}

Представление

<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>

<script type="text/javascript">
	jQuery(function ($) {
		$.ajax({
			url: '@Url.Action("Index")'
		});
	})
</script>

Первое, что было проверено, HTTP заголовки



Со скриншота видно, что идет ajax запрос X-Requested-With: XMLHttpRequest и браузер сообщает, что он понимает gzip или deflate заголовком Accept-Encoding: gzip, deflate, на что сервер отвечает json-ом Content-Type: application/json, но без сжатия, так как отсутствует заголовок Content-Encoding.

Далее пошла проверка на установленные модули веб сервера Administrative Tools -> Server Manager -> Roles -> IIS



Static/Dynamic Content Comprassion установлены. (Хочу обратить внимание, для Windows 7 установка осуществляется через добавление/удаление компонентов).
Далее проверяем, включена ли компрессия для приложения на IIS.





Если нет, то ставим галочки и применяем изменения. Разрешить компрессию также возможно через web.config

<system.webServer>
  <urlCompression doStaticCompression="true" doDynamicCompression="true" />
</system.webServer>

либо Configuration Editor -> system.webServer -> urlCompression







Кажется, уже все в порядке, но IIS как не отдавал Content-Encoding в заголовке ответа, так и не отдает.

В ход пошел

<system.webServer>
    <httpCompression>
      <dynamicTypes>
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />        
      </dynamicTypes>
    </httpCompression>
</system.webServer>

и, ура, победа, мы получаем долгожданный Content-Encoding: gzip. Но есть одно очень большое НО. httpCompression секцию можно определить только в applicationhost.config. А это значит, что либо вы администратор IIS, либо такой вариант вам не подходит (если, конечно, вы не убедите вашего хостера в обратном).

Предположим, что найденный вариант вам не подходит, а сжимать ответ вам все-таки надо. Что мы имеем по умолчанию в настройках динамической компрессии.



Компрессия будет распространяться, к примеру, на text/javascript. Конечно, это больше похоже на хак, но это лучше, чем ничего.

Меняем наш метод действия на

public ActionResult Index()
{
	if (Request.IsAjaxRequest())
	{
		return new JsonResult()
		{
			ContentType = "text/javascript",
			ContentEncoding = Encoding.UTF8,
			JsonRequestBehavior = JsonRequestBehavior.AllowGet,
			Data = new {/*большой объем данных*/}
		};
	}
	return View();
}

и при ответе используя функцию $.ajax, тут же получаем ошибку парсинга данных. Иными словами, теперь мы должны использовать либо $.getJSON, либо явно указывать, что мы ожидаем json.

Представление

<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>

<script type="text/javascript">
	jQuery(function ($) {
		$.ajax({
			url: '@Url.Action("Index")',
			dataType: 'json',
		});
	})
</script>

И третье решение, которое появилось, но под те задачи не подходило (это не значит, что оно не достойно внимания) – паковать самим.

Привожу пример со стэка, с которым я полностью согласен

public class CompressAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {

        var encodingsAccepted = filterContext.HttpContext.Request.Headers["Accept-Encoding"];
        if (string.IsNullOrEmpty(encodingsAccepted)) return;

        encodingsAccepted = encodingsAccepted.ToLowerInvariant();
        var response = filterContext.HttpContext.Response;

        if (encodingsAccepted.Contains("deflate"))
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
        else if (encodingsAccepted.Contains("gzip"))
        {
            response.AppendHeader("Content-encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
    }
}

Степень сжатия данных


Пока игрались с выше сказанным, была замечена еще одна интересная вещь, сжатый контент в Fiddler распаковывался и самим же фидлером опять перепаковывался, сжатия у фидлера на глаз было на ~10-20% меньше байт. Как найти настройки степени сжатия для IIS, ума не приложу. Не считая третьего варианта, в котором можно использовать параметр конструкторов CompressionLevel. Может, кто из вас подскажет? Этот момент для себя считаю важным, так как в зависимости от проекта иногда важен объем передаваемого трафика, а иногда, наоборот, процессорное время.

Буду рад услышать еще варианты решения проблемы, какими бы безумными идеи на первый взгляд не казались.
Теги:
Хабы:
+16
Комментарии 11
Комментарии Комментарии 11

Публикации

Истории

Работа

.NET разработчик
66 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн