MCP

среда, 28 октября 2009 г.

Использование DefaultHttpHandler для отдачи файлов пользователю

Тут на конкурсе задали вопрос: чем отличаются Http Handlers и Http Modules. Ответов полно, дублировать их желания нет.  Всегда можно открыть MSDN и почитать про это.

Вкратце, Handler, это обработчик запроса, например, когда обращаемся на /default.aspx, в web.config ищется подходящий обработчик и ему запрос отдаётся на растерзание. Естественно, если вы поглядите в web.config для вашего приложения, вы там скорее всего такого не найдёте. Но если вспомнить иеархию конфигруационных файлов в .NET, то становится ясно, что нужно искать в базовом web.config файле. У меня он находится тут: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config.

Этот зверь в 30Кб, полностью описывает стандартные настройки приложения, которые можно заменить в своём. Там как раз и есть обработчик aspx-файлов:

<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="true">

Собственно он и говорит рабочему процессу, что все подобные запросы должна обрабатывать фабрика с названием PageHandler (фабрика, это небольшой класс, который позволяет уже самостоятельно выбрать нужный Handler, в зависимости от более тонких условий). Также там можно найти другие интересные строчки:

<add path="*.config" verb="*" type="System.Web.HttpForbiddenHandler" validate="true"/>
...
<add path="*.soap" verb="*" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>

Например, эти говорят что *.config файлы нельзя показывать (например тот же web.config), а *.soap нужно для Remoting. Естественно, все эти правила можно заменить в своём приложении, да и при этом не забыть, что сам IIS по умолчанию не все расширения отдаёт ASP.NET, и надо добавить соответствующий фильтр в  настройках вашего приложения (ну или глобальный, на все файлы).

Про Http Handler's почему-то не получилось коротко, но это я случайно.   Дальше я расскажу более интересную вещь. А пока немножко про Http Modules.

Собственно, с ними всё просто, поскольку файл мы уже открыли и видим что за httpHandlers идут httpModules. Они-то  и будут будут вызваны HttpApplication во время обработки запроса, при этом они могут сделать что-нибудь полезное не прерывая и не перенаправляя запрос. Например, разобраться с сессией, авторизацией или логгированием. Да мало ли чем, всегда их можно подключить и отключить. Например мы хотим фатальные ошибки приложения сразу отправлять админу по ICQ. Сделали модуль, подключили к нужным приложениям. Всё работает, красота.  


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

Я часто встречал реализацию в виде /getfile.ashx?id=123 — где собственный хендлер как-то пытается отдать файлы. Идея хорошая, но не очень красивая, к тому же приходится рисовать собственный хендлер, отдающий файлы, что как-то неправильно.

Небольшое отсупление, по возможности старайтесь передавать имя файла в запросе, а не через Content-Disposition, иначе будут  проблемы с русскими именами файлов, да и периодические глюки, когда у клиента имя какое-то не такое. Для этого всего-лишь нужно делать запросы вида /getfile.ashx?id=123/requiredFileName немного подправить разбор входящих параметров, а дальше браузер сам придумает как обозвать файл.

Собственно моя идея состоит в том, что нужно использовать существующий DefaultHttpHandler, который и предназначен для отдачи статики, только для этого придётся выполнить пару простых условий:

  • Необходим .NET Framework 3.5 SP1, потому что в предыдущих реализация данного хендлера более примитивная.
  • Файлы должны быть физически доступны из приложения, т.е. пользователь должен иметь теоретическую возможность их скачать (правда имя может быть любое и лежать они могут как хотят. Для запрета прямой скачки, делаем соответствующие правила в web.config)

Теперь, собственно, осталось придумать по какому пути пользователи будут обращаться к файлам и навесить на этот путь свой простенький хендлер, отнаследованный от DefaultHttpHandler. Можете использовать различные url-rewriting решения, или написать своё. Если вам приходится вешаться на aspx файлы, то можете в фабрике возвращать следующий Handler:

PageParser.GetCompiledPageInstance(url, null, context)

Т.е. практически вручную запустить обработку страницы (с некоторым оговорками, но не о них сейчас речь).

Далее, в своём хендлере делаем следующее:

  • В методе BeginRequest  определяем "настоящий" путь к файлу по запросу. Например, запрос, /123/myfile.jpg меняем на /files/storedFile_123.dat
  • вызываем у контекста (вам его передадут) метод: context.RewritePath с полученным путём.
  • Дальше, чтобы не отдавать запрос IIS (а для оптимизации DefaultHttphandler попробует это сделать), что-нибудь пишем в Response, например context.Response.Write(' '). 
  • Вызываем базовый метод BeginRequest

В общем-то всё. Мы подменили путь и сказали обработать его как следует. С докачкой, и прочими плюшками. Но небольшое уточнение, зачем же не стоит отдавать запрос IISу? На самом деле, в определённых случаях это нужно делать, ибо производительность будет выше, но IIS делает пару нехороший действий:

  • Записывает в заголовки Content-Location с настоящим именем файла, т.е. хоть и не страшно, но мы светим то, как у нас хранятся файлы, т.е. неаккуратненько как-то
  • Если он не знает расширения файла, то в целях безопасности может его и не отдать (Свойства сервера, типы MIME — тут указаны расширения, которые IIS знает)

Ещё можно обрабатывать ошибки, писать логи и статистику, но это уже тонкости и детали, главное, что достаточно быстро (гораздо быстрее чем читать этот пост ).


Вышесказанное относится к IIS 6.0, в седьмом, возможно всё будет ещё проще, но я с ним мало работал, и в основном в классическом режиме, так что не буду ничего утверждать заранее.

вторник, 27 октября 2009 г.

Калькулятор размеров мониторов

Я как-то давно написал простенькую страничку, которая по введённой диагонали монитора и разрешению выдаёт его физический размер, dpi и соотношение сторон. Вещь на самом деле очень полезная и интересная, можно узнать, например, что:

  1. Монитор в 22" с разрешением 1920х1080 имеет по вертикали практически такой же размер как и обычная 17"-ка (1280х1024)
  2. Sony Vaio P имеет безумное разрешение в 1600x768, что даёт 222 dpi, что в 2.3 раза меньше стандартной точки (!!) Другими словами, в комплект должна идти лупа
  3. Ноутбуки в 15.4" обычно имеют разрешение 1280х800 что близко к классическим 96dpi (98 если точно)
  4. и многое другое

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

Пользуйтесь.

Изменение буквы системного диска в Windows

Раз уж сегодня начал активно писать в блог, то продолжу.  На этот раз не про конкурс, а небольшая заметка про то, как изменить букву диска в Windows, в случаях когда стандартными средствами (Управление Дисками) это не получается (нельзя, запрещено и так далее, в том же духе).

Стандартная ситуация, это когда при установке Windows не на основной диск система оказывается на диске D:, вместо диска C: и изменить это не получается, ибо диск системный и вообще загрузочный. Иногда подобное желание бывает и позднее (например системный диск X: а хочется сделать Y:).

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

Итак, всё просто. Открываем реестр, смотрим следующую ветку:

HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices

Дальше находим ключ \DosDevices\X: — где X, это имя диска, которое нужно сменить. Ну и меняем на то, что нужно.  Потом перегружаемся и наслаждаемся жизнью или ловим проблемы с тем, что ничего не работает.

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

  1. Загрузиться из другой системы. Если её нет под рукой, замечательно подходит установочный диск от Висты или свежее. Вместо установки выбираем Recovery и открываем командную строку. Если у вас есть Far, то можете ж
    80;ть припеваючи на данном шаге.  Если нет, то работайте в консоли.
  2. Копируем все файлы (чтобы проще было) из Windows\System32\Config в любую другую подходящую папку. Там хранится реестр и журнал событий.
  3. Для надёжности копируем Documents And Settings\ntuser.dat — реестр для конкретного пользователя (в Висте и Семерке путь немного другой: Users)

Собственно, на этом всё. Для восстановления нужно проделать те же действия, только скопировать обратно. В частности, этот способ часто помогает, если нужно переустановить Windows на такой же, по причине фатального сбоя. Это поможет восстановить реестр и может быть даже всё заработает. Но очень опасный способ (с другой стороны уже всё сломано, можно и попробовать починить).

GAC

Не выдержал и решил написать ответ на вопрос про GAC для конкурса Гайдара про платформу.

Переписывать MSDN и Рихтера не буду, для разнообразия скажу своё мнение про GAC, чтобы ответ отличался от остальных.   Собственно расскажу, не про то, чем хорош GAC, а чем он плох.

Для начала в двух словах про него — это глобальный кеш сборок, и любое приложение, которое использует опредёленную сборку сначала посмотрит на её наличие в GACе, а только потом будет искать в других местах. Т.е. можно считать GAC банальным хранилищем DLLек, как Windows\System32, но с одним важным отличием. Сборки хранятся не по имении библиотеки, а по имени, Strong Name ключу, версии, культуре и платформе (x86, x64). Т.е. можно хранить разные версии сборок и даже умудряться их использовать в одном приложении (хотя лучше постараться не делать так). Т.е. GAC полезная функциональность, но есть несколько НО.

Самое главное, теряется возможность xcopy deployment, необходимо использовать gacutil или делать приличный Installer. Но если подумать, то что нужно класть в GAC? Нужно туда размещать библиотеку, которую будут использовать несколько приложений на компьютере, иначе особого смысла в этом нет. Другими словами, это должен быть конкретный shared-компонент. А кому нужен shared-компонент? Только разработчикам. Для обычных пользователей это в большинстве случаев не нужно. А разработчики очень не любят сложные процессы установки библиотек. Им бы в проект скопировать что надо, и не думать потом о библиотеках. В противном случае получается, что у разработчика может всё работать, ибо сторонняя сборка в GACe, а у клиента уже всё будет падать, ибо разработчик забыл её приложить к приложению.

Например, мой epic fail был с библиотекой Microsoft.mshtml.dll, которая оказалось что ставится со студией, и напрямую в приложении не используется, а используется через третьи библиотеки. В результате, у меня приложение работало на всех компьютерах, а половина клиентов ругалась. Конечно, я уже умный, и подобных вещей стараюсь не допускать, но вот пример как легко можно сломать приложение использованием GACа.

Собственно, если мы планируем использовать сборку в GACе, то у нас сразу усложняется разработка. Необходимо писать скрипты для билда, которые будут выковыривать сборку из GAC, компилировать новую версию и вставлять обратно. Вроде не сложно, но программисты ленивые, и обязательно что-нибудь схалтурят, и при деплое получим проблемы.  Ещё нужно обязательно использовать Strong Name, что на этапе разработки иногда плохо, ибо придётся раздавать ключ всем разработчикам (проблема в секурности), также сразу включается версионирование, которое менять нужно вручную. Иначе получатся две абсолютно разных сборки с версией 1.0.0.0, одна из которых в GAC, а вторая нужна приложению, и это вызовет замечательные проблемы, которые будет очень весело отлаживать.


Собственно, что я хотел сказать:

  • Использовать GAC нужно, когда есть проблемы с версионированием. Это позволит их решить. Например, приложение А использует библиотеки B и С. B использует библиотеку D версии 1.0.0.0, а C использует D версии 1.2.0.0. Как это это необходимо решать. Тут нужен GAC, ибо два файла с одним именем к приложению подцеплять плохо.
  • Использовать GAC нужно, когда есть много приложений, использующих одну сборку. Как вариант — хостинг многих приложений с общим компонентом. И то, может быть проще не использовать GAC, ибо места на диске не так уж и много сэкономится, а если случайно выкинут сборку из GAC, то можно получить проблемы.
  • Хороший пример того, что нужно класть в GAC — системные библиотеки .NET Framework
  • Во всех остальных случаях — не стоит. (Моё личное мнение).

.NET Remoting по протоколу SOAP. Возможно ли?

Тут @gaidar устроил конкурс и задаёт всякие разные вопросы.  В принципе, отвечать на них не планировал, ибо умные ответы писать долго и получится переписывание книжек/MSDN, а глупые можно и в Твиттере

Но, тут задали вопрос про Remoting, а это же моя любимая тема.

Собственно, Remoting может использовать любой канал, хоть голубиную почту, с сообщениями, закодированными морзянкой, этим он и отличается в лучшую сторону, но вопрос именно в том, что можно ли из подобного SOAP/Remoting сделать настоящий Web Service? Ответ сложный, с одной стороны — да, ибо можно использовать SOAP, с другой стороны, эти сообещия будут весьма страшными и заточенными под remoting-клиента, а не обычного Web-Service клиента. Собственно, проблема в том, что для веб-сервисов нужны  WSDL и XSD описания методов, а Remoting хоть и делает WSDL, но делает его для себя, и особо не смотрит на стандартные неймспейсы и типы.

Так что ответ: В принципе да. Но если вы хотите, чтобы клиентами были не только те же .NET приложения, то вам же придётся приложить множество усилий, чтобы использовать только базовые типы, не использовать CAO и эвенты, т.е. сильно урезать функционал Remoting, чтобы свести его к веб-сервисам. А оно вам надо? Гораздо проще тупо и использовать веб-сервисы для тех целей, когда они нужны, это проще и понятнее, а Remoting не использовать вовсе, не нравится он мне. 

пятница, 23 октября 2009 г.

RDC vs RAdmin/VNC

Иногда бывает нужно зайти удалённо на свой компьютер. С одной стороны стандартный RDP рулит, но он обычно создаёт новую сессию, так что получается две запущенные сессии — локальная и удалённая. А это весьма раздражает, ибо заводится весь авторан, да и задача часто бывает именно посмотреть что в конкретно творится, а не просто завести сессию.

Так что обычно для этого приходится использовать RAdmin или какой-нить VNC. У них есть хороший побочный эффект, что человек может работать за компьютером, а другие смотреть на это дело. Но когда нужно подключиться самому к себе, это часто мешает (неприятно, когда коллеги по работе могут смотреть чем ты сейчас занимаешься, а ты их не видишь и даже не знаешь про это ).

Но есть простое решение с RDP, которое многие не знают, но тем не менее, оно сделано именно для таких ситуаций — залогиниться именно в локальную сессию удалённо. Делается это просто, запускается клиент с соответствующим ключом:

mstsc /console

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

mstsc /admin

Работает он также как и предыдущий.

Так что пользуйтесь, без всяких дополнительных программ, стандартные средства рулят .

четверг, 3 сентября 2009 г.

Немножко про Reflection

Недавно проверял скорость работы Reflection в плане того, насколько оно медленное. Расписывать все случаи не буду, также не буду утверждать что все замеры корректные, просто немного данных для информации, чтобы вы знали, что ожидать от Reflection:

  • Вызов метода через рефлекшен отличается в зависимости от того, является ли метод public или private
  • Для public вызов метода где-то в 100-150 (!) раз медленнее, чем обычный вызов
  • Для private вызов медленнее где-то раз в 400-500 (!). Выводы делайте сами.
  • Время на вызовы GetMethodInfo относительно невелико, при этом, похоже это всё кешируется. Так что особо оптимизировать вызовы тут смысла нет
  • Если вы точно знаете сигнатуру метода, который берёте через рефлекшен (в качестве варианта, пнуть какой-нибудь приватный метод, или при использовании какого-либо варианта плагинов), то можно воспользоваться Delegate.CreateDelegate с указанием нужного типа, получить правильный делегат и его вызов будет таким же быстрым (возможно даже чуть быстрее, есть подозрение на использование call вместо callvirt), как и прямой вызов
  • Если точно не знаете сигнатуру, то можно использовать Delegate.DynamicInvoke но производительность будет примерно такая же, как и у чистого рефлекшена
  • Если хотите использовать Expression для генерации, в частности LambdaExpression то совет тот же. Используйте чётко типизированный .Compile() и получите почти идеальную производительность (где-то 1.5, но компиляция не очень шустрая). С учётом того, что экспрешены — достаточно могучий механизм, подобное стоит использовать. Есть интересные задачи, где они могут сильно облегчить жизнь, или даже сделать что-то особенное.

Ну вот, в общем и всё. Пока для меня остаётся открытым вопрос: "Как использовать преимущества делегатов без возможности указания нужного типа напрямую", чтобы получить хорошую производительность для рефлешкена. Смотрю в сторону DynamicMethod, но это уж больно серьёзная артиллерия (хотя производительность замечательная).