среда, 15 сентября 2010 г.

Максимальная длина URL

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

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

В спецификации RFC 2616 (Hypertext Transfer Protocol - HTTP/1.1), в разделе 3.2.1, говорится, что нет никаких ограничений на длину URI (URI — это официальный термин того, что большинство людей называет URL). Также она предупреждает, что URI длиннее чем примерно 255 символов могут не поддерживаться некоторыми старыми клиентами и прокси-серверами (это в 1999-м то году; незнаю насколько это сейчас актуально).

Еще есть спецификация RFC 3986 (Uniform Resource Locator (URI): Generic Syntax), в которой также указывается, что нет никаких ограничений на общую длину URL, за исключением ограничения в 255 символов на название хоста (из-за ограничений DNS).

Итак, максимальная длина URL теоретически может быть неограниченна.

Т.е. если мы хотим хранить ссылки в БД, то нужно быть готовым к ситуации, когда длина URL будет очень большой.

Отлично, с теорией все ясно. А как обстоят дела с практикой?

Практику уже исследовали до нас, потому просто воспользуюсь результатами. Самое последние (и, потому, наиболее актуальное) известное мне исследование проводил ресурс Boutell.Com в 2006-м году. Ихняя рекомендация — не использовать URL длинее чем 2000 символов, поскольку более длинные URL могут вызвать проблемы у наиболее популярных веб-браузерах (так уж сложилось, что наиболее популярными браузерами является семейство Internet Explorer).

На практике, URL длинее чем 2000 символов — проблемный URL.

Ниже приведу некоторые факты, для обрисовки полной картины:
  • Internet Explorer всех версий (на данный момент последней является 8-я) поддерживает максимальну длину URL в 2083 символов, но практический лимит — 2048 символов.
  • Другие браузеры имеют значительно больший "запас" (FF > 65k, Opera > 200k, Chrome/Safari >> 8)
  • Apache имеет ограничение по умолчанию в 8K символов.
  • По умолчанию, максимальная длина URL для IIS (Internet Information Server) — 16K.
  • Google _уже_ индексирует страницы с URL длиной до 2K символов, но из-за ограничений на сервере самого гугла, пользователь не сможет перейти на нее из результатов поиска (итого, максимальная "юзабельная" длина URL для гугла - 1855 символов)
  • Другие поисковики, и вебдевелоперские утилиты ведут себя плохо при размерах URL в 2K
Хотя стандарт и не накладывает ограничений на длину, но, де-факто, очень длинный URL — это скорее аномалия, чем рабочая ссылка. Потому с практической точки зрения, размера поля в 2K символов будет вполне достаточно (но опять же, нужно иметь в виду что, URL может "не влезть").

четверг, 3 декабря 2009 г.

Pathological Interfaces II: Маршалинг

WTF?Для маршаллинга COM-интерфейсов между потоками есть такой функционал системы COM, как CoMarshalInterThreadInterfaceInStream / CoGetInterfaceAndReleaseStream.

Функции выполняют именно то, что указанно в названии. А именно работу по облегчению "обмена" информацией об интерфейсе между потоками: в одном потоке сохранили данные о интерфейсе в IStream, а в другом восстановили их из этого же IStream’а.
Вроде ничего проще быть не может, все ясно, все понятно и очень удобно. Но разработчики подсунули свои грабли…

Дело в том, что, несмотря на то, что на выходе CoMarshalInterThreadInterfaceInStream получаем IStream, использовать этот стрим кроме как для функции, которой он предназначен нельзя. Совсем нельзя. Даже менять счетчик ссылок.

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

И не сразу понятно, что этот стрим предназначен исключительно для использования в качестве параметра CoGetInterfaceAndReleaseStream — новички могут наступить на расставленные грабли. Тем более что в мануалах о таком поведении не написано (ну может где и есть, но точно не там где должно быть).

Да что там новички! Ошибки делают и профессионалы из солидных контор. Например, во всех известных мне версиях Delphi функции объявлены как
function CoMarshalInterThreadInterfaceInStream(const iid: TIID; 
unk: IUnknown; out stm: IStream): HResult; stdcall;

function CoGetInterfaceAndReleaseStream(stm: IStream; const 
iid: TIID; out pv): HResult; stdcall;

Дело в том, что delphi по части интерфейсов ведет себя как managed-язык: автоматически наращивает/убавляет счетчик ссылок. Но поскольку CoGetInterfaceAndReleaseStream этот счетчик игнорирует и сразу уничтожает объект, то получается ситуация, что делфи пытается вызвать Release для уже уничтоженного объекта.

Обычно в таких случаях делают всяческие извращения, типа этого:
var
pStream : pointer; // Чтоб дельфа не вела подсчет ссылок вместо IStream будет обычный указатель

// В потоке 1: маршаллинг
CoMarshalInterThreadInterfaceInStream (IDispatch,  pObject1 as IUnknown, IStream (pStream));

// В потоке 2: демаршаллинг
CoGetInterfaceAndReleaseStream(IStream(pStream), IDispatch, pObject1);

А теперь вопрос: зачем сюда было тулить IStream? Чем обычный Cookie в виде DWORD не прокатил?

Отдельное "спасибо" и Borland-у: ведь достаточно было правильно оформить интерес к этим функциям, и не пришлось бы выдумывать вышеописанные ужасы маршаллинга
// Интерфейс стирма заменили на куку
function CoMarshalInterThreadInterfaceInStream(const iid: TIID; 
unk: IUnknown; out cookie: DWORD): HResult; stdcall;

function CoGetInterfaceAndReleaseStream(cookie: DWORD; const 
iid: TIID; out pv): HResult; stdcall;


Документация по функциям-виновникам сего поста:
1. CoMarshalInterThreadInterfaceInStream Function
2. CoGetInterfaceAndReleaseStream Function

Pathological Interfaces I: QueryInterface

WTF?К сожалению, далеко не у всех и не всегда удается сходу придумать хорошую архитектуру и "правильный" API. Часто бывает, что архитектура и API должна пройти несколько итераций и метаморфоз перед тем как лишится недостатков и "устаканится". Но на это нужно время. Хотя бы на то, чтоб кто-то мог опробовать это дизайнерское решение в действии.

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

Речь идет о ляпе в жизненно важном для COM методе QueryInterface, о способе оповещении, что интерфейс не поддерживается. Казалась бы, что тут может быть проще, либо интерфейс поддерживается, либо — нет. Но грабли нашлись даже в такой простой вещи. Вот что написано в MSDN:
"If the object does not support the interface specified in iid, *ppvObject is set to NULL. ... S_OK if the interface is supported, E_NOINTERFACE if not”
Т.е. согласно спекам, любая "хорошая" имплементация QueryInterface, в случае отсутствия запрашиваемого интерфейса, должна вернуть как код ошибки, так и занулить out-параметр *ppvObject.

Слово "хорошая" не зря выделено. API допускает, и даже провоцирует, на "плохую" реализацию: программист может и не занулить указатель на интерфейс, а вернуть только код ошибки E_NOINTERFACE (действительно, а зачем занулять out-параметр, если я возвращаю ошибку; не доглядел; а разве еще и параметр занулять нужно?). И все из-за того что спецификация обязывает сделать два действия, одно из которых совершенно не нужно. Последствия такой вот работы "не по спецификации" могут быть очень печальными — от AV до утечек памяти (вообще то все зависит только от грамотности кода COM-клиента — занулил ли он указатель перед вызовом, и вызывает ли Release в любом или только успешно случае). На самом деле, количество вариантов развития сюжета может быть на много больше, да и последствия могут проявится не сразу.

......

Вот такой вот получился интерфейс с "защитой от дурака" наоборот...

Мораль: Don’t design stupid APIs that return the same information twice!1


Почитать:
1. Pathological QueryInterfaces
2. The ways people mess up IUnknown::QueryInterface (перевод от GunSmoker - Что люди любят делать не так в IUnknown.QueryInterface)

суббота, 8 августа 2009 г.

The Zen of Python

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one and preferably only one obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let's do more of those!