LXF170:Рубрика сисадмина
Olkol (обсуждение | вклад) (Новая страница: «Категория: Постояные рубрики == По рецептам доктора Брауна == ''Эзотерическое сист…») |
Olkol (обсуждение | вклад) (→Задаем стандарт) |
||
Строка 14: | Строка 14: | ||
Лично я думаю, что у нас должна быть возможность выбирать, какой у нас должен быть выбор. | Лично я думаю, что у нас должна быть возможность выбирать, какой у нас должен быть выбор. | ||
+ | ===Сказка о двух прокси=== | ||
+ | |||
+ | Со словарем в руках Доктор изучает разницу между прямым и обратным прокси. | ||
+ | |||
+ | Мой Оксфордский словарь английского языка определяет прокси как «лицо, имеющее право выступать от имени другого лица, особенно при голосовании». В сфере компьютеров это слово имеет похожее значение: прокси-сервер выступает посредником для клиентов, запрашивающих ресурсы (обычно web-страницы) у других серверов. Вы, небось, знали это давно, а я совсем недавно узнал разницу между прямым и обратным прокси. И вот что я нарыл... | ||
+ | |||
+ | Прямой прокси расположен рядом с клиентом (браузером), обычно в локальной корпоративной сети. Он обслуживает ограниченное число клиентов, но может перенаправлять их на большое число целевых серверов – хоть на весь Интернет. Клиент должен знать, что он обязан отправлять запросы через прокси, и вы наверняка уже бодались с настройками в окне браузера, чтобы сообщить ему об этом. Прямые прокси обычно используются в корпоративных сетях, где они повышают производительность за счет кэширования результатов недавних запросов и повышают безопасность, фильтруя трафик на заданные сайты. | ||
+ | |||
+ | Обратный прокси расположен рядом с целевыми серверами, обычно в той же сети. Для клиента этот прокси выглядит как сам сервер. Клиент не знает, что его запрос перенаправляется. Обратный сервер обслуживает запросы от большого числа клиентов, но может перенаправлять их на ограниченное число серверов. Обратные прокси иногда используются для балансировки нагрузки, когда кластер серверов расположен за одним прокси. Иногда они возвращают статический контент сайта, а запросы на динамический контент перенаправляют на другой сервер. Например, web-сервер Apache можно использовать в качестве клиента Tomcat, при этом Apache будет отвечать за статический контент, а Tomcat – за сервлеты Java и страницы JSP. | ||
+ | |||
+ | ===Стек LAMP=== | ||
+ | |||
+ | В этой последней части данной серии мы зажигаем нашу лампу (LAMP) | ||
+ | и пишем настоящее web-приложение, управляемое данными. | ||
+ | |||
+ | Это последний из четырех уроков серии, в которой мы говорим о стеке LAMP (Linux, Apache, MySQL и PHP) и о том, как с его помощью создавать динамические, управляемые данными web-сайты. На первом уроке мы собрали части L, A и P. На втором – посмотрели, как HTML и PHP спелись для создания web-приложений. На третьем мы узнали о базах данных, немного познакомились с SQL и установили часть “M” стека, MySQL. С установкой новых компонентов покончено, и в этом месяце мы заставим все четыре компонента LAMP работать вместе, написав полноценное web-приложение, управляемое данными. Мы разработаем приложение, которое позволит читателям выполнять запросы к базе данных библиотеки через HTML-форму, и напишем серверный код на PHP, который выполнит эти запросы к базе данных и сформирует результат, отформатированный в HTML. Начнем... | ||
+ | |||
+ | ===Определение миссии=== | ||
+ | |||
+ | Начну с объяснения того, чего мы пытаемся добиться. Воспользовавшись базой данных library, созданной в прошлом месяце, мы создадим HTML-форму, которая позволит читателю ввести название и/или автора книги; затем мы создадим страницу на PHP, которая будет опрашивать каталог библиотеки и возвращать таблицу с соответствующими книгами. Визуально и форма запроса, и страница с результатами будут очень простыми, как показано на рисунках. Начнем с HTML-формы booksearch.html. Она и вправду проста: | ||
+ | |||
+ | <html> | ||
+ | |||
+ | <head> | ||
+ | |||
+ | <title>Simple book search</title> | ||
+ | |||
+ | </head> | ||
+ | |||
+ | <body> | ||
+ | |||
+ | <form action=”booksearch.php” method=”POST”> | ||
+ | |||
+ | Title: <INPUT type=”text” name=”searchtitle”> | ||
+ | |||
+ | Author: <INPUT type=”text” name=”searchauthor”> | ||
+ | |||
+ | <INPUT type=”submit” name=”booksearch” value=”Search”> | ||
+ | |||
+ | </form> | ||
+ | |||
+ | </body> | ||
+ | |||
+ | </html> | ||
+ | |||
+ | На этой форме есть два поля ввода и кнопка, которая возвратит данные формы обратно странице booksearch.php. С помощью этой формы можно осуществлять поиск по названию книги, автору или и по тому, и по другому сразу. | ||
+ | |||
+ | ===Заставляем «P» говорить с «M»=== | ||
+ | |||
+ | Теперь обратим свое внимание на страницу поиска книги. Новичков в PHP и web-приложениях она может малость запугать, поэтому разберем ее постепенно. Для начала обратимся к схеме на стр. 64, где детально показано, как работают web-приложения. | ||
+ | |||
+ | Большинство приложений следуют этим шести этапам. Нам нужно усвоить, как управление прыгает между браузером и сервером – читатель запрашивает стартовую страницу приложения, сервер отправляет форму, читатель ее заполняет, сервер обрабатывает запрос, читатель восхищается результатами и т. д. У молодых web-разработчиков проблем с этим нет, но пожилым программистам вроде меня, которые всю жизнь писали монолитный код, где все происходило в одной программе на одном компьютере, нужно время на перестройку. | ||
+ | |||
+ | '''Шаг 1: Сбор данных от пользователя''' | ||
+ | |||
+ | Как мы видели во второй части, PHP делает данные формы доступными в словаре $_REQUEST. Ключ в этом словаре – просто имя компонента формы. Так, чтобы получить название книги, можно сделать следующее: | ||
+ | |||
+ | $searchtitle = trim($_REQUEST[‘searchtitle’]); | ||
+ | |||
+ | $searchtitle = addslashes($searchtitle); | ||
+ | |||
+ | Функция trim() удаляет все пробелы в начале или в конце поля ввода, которые мог оставить там читатель. Функция addslashes() добавляет в строку обратные слэши перед символами, которые нужно заключить в кавычки в запросах к базе данных. Она также предоставляет базовую защиту от атак с применением SQL-инъекции, о которых я расскажу позже. Другая функция, которая может вам здесь пригодиться – mysqli_real_escape_string(), она делает это лучше, но специфична для MySQL. | ||
+ | |||
+ | '''Шаг 2: Подключимся к базе данных''' | ||
+ | |||
+ | Для доступа к базе данных MySQL из кода на PHP мы воспользуемся библиотекой mysqli, которая заменила библиотеку mysql (“i” означает «улучшенная [improved]»). Вам, наверное, понадобится ее установить, но в репозиториях CentOS она есть. Кроме того, нужно перезапустить Apache: | ||
+ | |||
+ | # yum install php-mysql | ||
+ | |||
+ | # service httpd restart | ||
+ | |||
+ | Эта библиотека предоставляет API в двух стилях: процедурном и объектно-ориентированном. Разница в основном стилистическая; здесь мы воспользуемся объектно-ориентированной версией. Для подключения к базе данных укажите имя или IP-адрес сервера MySQL, корректные имя пользователя и пароль и имя базы данных, к которой нужно подключиться. Ввызов функции выглядит так: | ||
+ | |||
+ | @ $db = new mysqli(“example.com”, “root”, “secret”, “library”); | ||
+ | |||
+ | if ($db->connect_error) { | ||
+ | |||
+ | echo “could not connect: “ . $db->connect_error; | ||
+ | |||
+ | exit(1); | ||
+ | |||
+ | } | ||
+ | |||
+ | Обратите внимание на @ в первой строке. Это оператор подавления ошибки PHP. Мы пользуемся им, чтобы сохранить управление в случае ошибок и обработать все ошибки самим. | ||
+ | |||
+ | '''Шаг 3: Построим SQL-запрос''' | ||
+ | |||
+ | В настоящем приложении этот шаг может усложниться, но мы оставим его простым: | ||
+ | |||
+ | $query = “ select * from books”; | ||
+ | |||
+ | $query = $query . “ where title like ‘%” . $searchtitle . “%’”; | ||
+ | |||
+ | Обратите внимание на оператор объединения строк в PHP (.), с помощью которого мы собрали запрос по кусочкам. Также обратите внимание на использование SQL-оператора like и шаблон %. Мы ищем книги не с точным совпадением названия, а с любым названием, которое содержит введенную пользователем строку. | ||
+ | |||
+ | '''Шаг 4: Выполним запрос''' | ||
+ | |||
+ | PHP предлагает несколько вариантов выполнения запроса в зависимости от того, как вы хотите работать с результатами. В этом примере мы воспользуемся технологией, в которой столбцы результата запроса связываются с заданными переменными PHP. В этом фрагменте кода $query содержит сам запрос, а $db – ссылку на подключение к базе данных, установленное на втором шаге. | ||
+ | |||
+ | $stmt = $db->prepare($query); | ||
+ | |||
+ | $stmt->bind_result($bookid, $title, $author, $onloan, $duedate, $borrowerid); | ||
+ | |||
+ | $stmt->execute(); | ||
+ | |||
+ | '''Шаг 5: Получим результаты''' | ||
+ | |||
+ | Запрос возвращает набор результатов, состоящий из нуля или более строк. С помощью связанных переменных, которые мы создали ранее, легко пройтись по этим строкам в цикле и получить результаты: | ||
+ | |||
+ | while ($stmt->fetch()) { | ||
+ | |||
+ | echo “$title was written by $author <br />”; | ||
+ | |||
+ | } | ||
+ | |||
+ | '''Шаг 6: Построим HTML-ответ''' | ||
+ | |||
+ | Обычно набор результатов из нескольких строк представляется пользователю в виде таблицы, каждая строка которой соответствует строке из набора результатов. Вот переработанная версия предыдущего фрагмента кода, в которую добавлено несколько HTML-тэгов таблицы: | ||
+ | |||
+ | echo “<table>”; | ||
+ | |||
+ | while ($stmt->fetch()) { | ||
+ | |||
+ | echo “<tr> <td> $title </td> <td> $author </td> </tr>”; | ||
+ | |||
+ | } | ||
+ | |||
+ | echo “</table>”; | ||
+ | |||
+ | Мы полагаемся на то, что переменные $title и $author заполнены даже внутри двойных кавычек. Теперь объединим все это вместе и создадим полноценную PHP-страницу booksearch.php. Не пугайтесь, если она покажется сложной. По большей части она состоит из фрагментов, которые мы уже видели. Добавлено немного дополнительной логики, чтобы пользователь мог искать по автору, названию книги или и по тому, и по другому вместе. Например, эта страница может построить запрос вроде select * from books where title like ‘ %Potter %’ and author like ‘ %Rowling %’ | ||
+ | |||
+ | <html> | ||
+ | |||
+ | <head> | ||
+ | |||
+ | <title>Library Book Search</title> | ||
+ | |||
+ | </head> | ||
+ | |||
+ | <body> | ||
+ | |||
+ | <h3>Book Search Results</h3><br> | ||
+ | |||
+ | <hr> | ||
+ | |||
+ | <?php | ||
+ | |||
+ | # Get data from form | ||
+ | |||
+ | $searchtitle = trim($_REQUEST[‘searchtitle’]); | ||
+ | |||
+ | $searchauthor = trim($_REQUEST[‘searchauthor’]); | ||
+ | |||
+ | if (!$searchtitle && !$searchauthor) { | ||
+ | |||
+ | echo “You must specify either a title or an author”; | ||
+ | |||
+ | exit(); | ||
+ | |||
+ | } | ||
+ | |||
+ | $searchtitle = addslashes($searchtitle); | ||
+ | |||
+ | $searchauthor = addslashes($searchauthor); | ||
+ | |||
+ | # Open the database | ||
+ | |||
+ | @ $db = new mysqli(‘localhost’, ‘root’, ‘rootpw’, ‘library’); | ||
+ | |||
+ | if ($db->connect_error) { | ||
+ | |||
+ | echo “could not connect: “ . $db->connect_error; | ||
+ | |||
+ | exit(); | ||
+ | |||
+ | } | ||
+ | |||
+ | # Build the query. Users are allowed to search on title, | ||
+ | |||
+ | # author, or both | ||
+ | |||
+ | $query = “ select * from books”; | ||
+ | |||
+ | if ($searchtitle && !$searchauthor) { // Title search only | ||
+ | |||
+ | $query = $query . “ where title like ‘%” . $searchtitle . “%’”; | ||
+ | |||
+ | } | ||
+ | |||
+ | if (!$searchtitle && $searchauthor) { // Author search only | ||
+ | |||
+ | $query = $query . “ where author like ‘%” . $searchauthor . “%’”; | ||
+ | |||
+ | } | ||
+ | |||
+ | if ($searchtitle && $searchauthor) { // Title and Author search | ||
+ | |||
+ | $query = $query . “ where title like ‘%” . $searchtitle . “%’ and author like ‘%” . $searchauthor .“%’”; // unfinished | ||
+ | |||
+ | } | ||
+ | |||
+ | # Run the query using bound result parameters | ||
+ | |||
+ | $stmt = $db->prepare($query); | ||
+ | |||
+ | $stmt->bind_result($bookid, $title, $author, $onloan, | ||
+ | |||
+ | $duedate, $borrowerid); | ||
+ | |||
+ | $stmt->execute(); | ||
+ | |||
+ | echo “<table border=1>”; | ||
+ | |||
+ | while ($stmt->fetch()) { | ||
+ | |||
+ | echo “<tr><td> $bookid </td> <td> $title </td><td> $author | ||
+ | |||
+ | </td></tr>”; | ||
+ | |||
+ | } | ||
+ | |||
+ | echo “</table>”; | ||
+ | |||
+ | ?> | ||
+ | |||
+ | </body> | ||
+ | |||
+ | </html> | ||
+ | |||
+ | В коде, который строит запрос, есть хитрые кавычки – просто помните, что двойные кавычки защищают одинарные. По данным сайта owasp (https://owasp.org/index.php/Topten), главная угроза web-сайтам – SQL-инъекция. Любое приложение, которое берет «недоверенные» данные, введенные пользователем, и строит на их основе запрос, который потом выполняет, уязвимо к такого рода атакам, если не предпринимает должных мер предосторожности. Под «недоверенными» данными я понимаю данные, которые вводит пользователь (возможно, злоумышленник) в виде текста в компонент для ввода данных на форме. | ||
+ | |||
+ | Лучше всего добавить на страницу код на JavaScript, который будет проверять все поля ввода. Хотя это помогает предотвратить отправку неправильно заполненной формы, это не защищает от атак, потому что ничто не мешает злоумышленнику отправить готовую форму с неправильным вводом. Другими словами, данные, которые вы получаете, не всегда приходят из формы, которую до этого заполнил пользователь. | ||
+ | |||
+ | Каков же принцип действия атаки с SQL-инъекцией? Вот пример – фрагмент кода PHP, который получает идентификатор клиента из формы и использует его для построения запроса. Идея состоит в том, чтобы клиент увидел информацию о банковском счете только для одного идентификатора. | ||
+ | |||
+ | # Part of the page accountview.php | ||
+ | |||
+ | $custid = $_REQUEST[“id”]; | ||
+ | |||
+ | $query = “select * from accounts where custID = ‘$custid’ “; | ||
+ | |||
+ | Теперь злоумышленник открывает в браузере адрес http://example.com/app/accountview.php?id=’ or ‘1’=’1 | ||
+ | |||
+ | Здесь «недоверенные» данные – это значение id. Наша страница построит следующий запрос: | ||
+ | |||
+ | select * from accounts where custID = ‘’ or ‘1’ = ‘1’ | ||
+ | |||
+ | Так как второе условие всегда верно, запрос вернет всю таблицу accounts! | ||
+ | |||
+ | ===Защита=== | ||
+ | |||
+ | Нам поможет использование готовых шаблонов запросов. Сначала мы задаем шаблон запроса, пометив знаком ? те места, куда попадут данные; затем мы можем выполнить запрос, добавив в шаблон данные на эти места. | ||
+ | |||
+ | $query = “insert into borrowers values (?, ?, ?)”; | ||
+ | |||
+ | $stmt = $db->prepare($query); | ||
+ | |||
+ | // ... some time later ... | ||
+ | |||
+ | $db->execute(array(108, “Fred Flintstone”, “4 Quarry Hill, Bedrock”)); | ||
+ | |||
+ | В реальности данные будут не жестко «вшиты» в код, а поступят из недоверенного источника. Готовые шаблоны помогают решить проблему, так как любые специальные символы во входных данных автоматически экранируются. Более надежная защита – проверка недоверенного ввода на соответствие ожидаемому синтаксису. Например, если клиент должен ввести дату в поле ввода “date”, можно сделать следующее: | ||
+ | |||
+ | $mydate = trim($_REQUEST[“date”]); | ||
+ | |||
+ | if (!ereg(“^[0-9]{4}-[0-9]{2}-[0-9]{2}$”, $mydate) { | ||
+ | |||
+ | echo “date must be YYYY-MM-DD”; | ||
+ | |||
+ | return; | ||
+ | |||
+ | Конечно, эта проверка с регулярным выражением не слишком придирчива к дате – и пользователь сможет ввести что-нибудь вроде 2014-88-99, но ее достаточно для того, чтобы обнаружить некорректные данные, введенные злоумышленником. | ||
+ | |||
+ | Во второй части этой серии я писал о сохранении состояния сессии в web-приложении с помощью объекта $_SESSION PHP и упомянул, что для этого невидимо для пользователя используется куки [cookie]. Информация, которая хранится таким образом, не переживет сессию. Когда пользователь закрывает браузер, сессия исчезает. Однако мы также можем создать «постоянные» куки (с датой истечения), которые позволят постоянно хранить информацию между различными сессиями. Куки отправляются сервером браузеру как часть заголовка HTTP-ответа. Вот пример куки из Интернета, полученный при отслеживании пакетов при открытии сайта amazon.co.uk: | ||
+ | |||
+ | Set-cookie: session-id=202-3921230-4252634; path=/; | ||
+ | |||
+ | domain=.amazon.co.uk; expires=Tue 1 Jan 2036 00:00:01 2036 GMT | ||
+ | |||
+ | Здесь session-id – имя куки, а это страшное длинное число – значение. Атрибуты domain и path задают сайт, которому принадлежит куки, а path – путь внутри сайта, к которому применяется куки (в данном случае это /, так что куки применяется ко всему сайту). | ||
+ | |||
+ | Чтобы сгенерировать куки в PHP, вызовите setcookie(). Функции нужно передать как минимум имя и значение куки; также можно передать дату истечения куки, путь и домен. Чтобы получить куки, обратитесь к массиву $_COOKIE, в качестве ключа используя имя куки. В ответ вы получите значение куки или null, если куки не существует. | ||
+ | |||
+ | Вот две маленькие страницы, которые иллюстрируют куки в действии. На первой мы пытаемся получить из куки имя посетителя. В случае успеха мы приветствуем пользователя по имени. В противном случае мы предлагаем пользователю форму, в которую он вводит свое имя. Она отправляется на вторую страницу, которая генерирует куки. Вот основы первой страницы (я пропустил обрамляющие тэги HTML): | ||
+ | |||
+ | <?php | ||
+ | |||
+ | // Назвал ли этот пользователь свое имя? | ||
+ | |||
+ | @ $visitor = $_COOKIE[‘visitor_name’]; | ||
+ | |||
+ | if (is_null($visitor)) { | ||
+ | |||
+ | ?> | ||
+ | |||
+ | <form action=”record_name.php”> | ||
+ | |||
+ | Пожалуйста, назовитесь: | ||
+ | |||
+ | <input type=text name=”yourname”> | ||
+ | |||
+ | <input type=submit value=”Submit”> | ||
+ | |||
+ | </form> | ||
+ | |||
+ | <?php } else { ?> | ||
+ | |||
+ | Привет <?= $visitor ?>, рады видеть вас снова! | ||
+ | |||
+ | <? } ?> | ||
+ | |||
+ | а вот вторая страница: | ||
+ | |||
+ | <?php | ||
+ | |||
+ | $visitor = $_REQUEST[‘yourname’]; | ||
+ | |||
+ | setcookie(‘visitor_name’, $visitor); | ||
+ | |||
+ | ?> | ||
+ | |||
+ | Спасибо, что представились, | ||
+ | |||
+ | <?= $visitor ?> | ||
+ | |||
+ | Чтобы стать web-разработчиком, нужно изучить огромное количество технологий, и это непростая задача. Но стек LAMP по крайней мере дает вам инструменты, с которыми это можно сделать. Удачи! | | ||
+ | |||
+ | ===Размещение сайта=== | ||
+ | |||
+ | Чтобы развернуть приложение, его нужно разместить в каталоге Document Root, заданном в файле настройки Apache (/etc/httpd/conf/httpd.conf). | ||
+ | |||
+ | В конфигурации по умолчанию из репозиториев CentOS он установлен в /var/www/html. Все что вам нужно – скопировать эти два файла (booksearch.html и booksearch.php) в этот каталог. Для проверки откройте браузер и наберите в адресной строке http://localhost/booksearch.html. | ||
+ | |||
+ | ===Советы по отладке=== | ||
+ | |||
+ | Если в вашем коде на PHP есть синтаксические ошибки, то при попытке открыть страницу вы скорее всего получите совершенно пустую (и абсолютно бесполезную) страницу. Чтобы заставить PHP сообщать об ошибках, измените одну строку в /etc/php.ini: | ||
+ | |||
+ | display_errors = On | ||
+ | |||
+ | и перезапустите Apache. Теперь синтаксические ошибки будут возвращаться браузеру. Отчет будет включать номер строки с ошибкой, но помните, что это номер строки, на которой споткнулся интерпретатор, а сама ошибка могла быть раньше. Отсутствие точки с запятой в предыдущей строке – наиболее частый прокол. Возможно, вам будет удобнее проверять ошибки с командной строки таким образом: | ||
+ | |||
+ | php -l booksearch.php | ||
+ | |||
+ | Флаг -l переводит PHP в мягкий режим – в нем интерпретатор не выполняет код, а только проверяет синтаксис. | ||
+ | |||
+ | > Общая схема. Большинство web-приложений, управляемых данными, выполняют эти шесть шагов. | ||
+ | |||
+ | ===Чтобы узнать больше=== | ||
+ | |||
+ | И у PHP, и у MySQL есть прекрасные сайты (php.net и dev.mysql.com соответственно) с новостями, статьями и ссылками на документацию. И, как я уже упомянул, сайт www.w3schools.com поможет вам познакомиться с широким набором технологий для web-разработки, не только с LAMP. Я бы также порекомендовал следующие четыре книги: | ||
+ | |||
+ | » Web-разработка на PHP и MySQL Люка Уэллинга [Luke Welling] и Лоры Томсон [Laura Thomson] | ||
+ | |||
+ | » Apache. Полное руководство Бена Лори [Ben Laurie] и Питера Лори [Peter Laurie] | ||
+ | |||
+ | » Руководство по основам web-дизайна с CSS и HTML Крейга Греннела [Craig Grannell] | ||
+ | |||
+ | » Программирование на PHP Расмуса Лердорфа [Rasmus Lerdorf], Кевина Татроу [Kevin Tatroe] и Питера Макинтайра [Peter MacIntyre] | ||
+ | |||
+ | ===Размещение сайта=== | ||
+ | |||
+ | Чтобы развернуть приложение, его нужно разместить в каталоге Document Root, заданном в файле настройки Apache (/etc/httpd/conf/httpd.conf). | ||
+ | |||
+ | В конфигурации по умолчанию из репозиториев CentOS он установлен в /var/www/html. Все что вам нужно – скопировать эти два файла (booksearch.html и booksearch.php) в этот каталог. Для проверки откройте браузер и наберите в адресной строке http://localhost/booksearch.html. | ||
+ | |||
+ | ===Советы по отладке=== | ||
+ | |||
+ | Если в вашем коде на PHP есть синтаксические ошибки, то при попытке открыть страницу вы скорее всего получите совершенно пустую (и абсолютно бесполезную) страницу. Чтобы заставить PHP сообщать об ошибках, измените одну строку в /etc/php.ini: | ||
+ | |||
+ | display_errors = On | ||
+ | |||
+ | и перезапустите Apache. Теперь синтаксические ошибки будут возвращаться браузеру. Отчет будет включать номер строки с ошибкой, но помните, что это номер строки, на которой споткнулся интерпретатор, а сама ошибка могла быть раньше. Отсутствие точки с запятой в предыдущей строке – наиболее частый прокол. Возможно, вам будет удобнее проверять ошибки с командной строки таким образом: | ||
+ | |||
+ | php -l booksearch.php | ||
+ | |||
+ | Флаг -l переводит PHP в мягкий режим – в нем интерпретатор не выполняет код, а только проверяет синтаксис. | ||
+ | |||
+ | > Общая схема. Большинство web-приложений, управляемых данными, выполняют эти шесть шагов. | ||
+ | |||
+ | ===Чтобы узнать больше=== | ||
+ | |||
+ | И у PHP, и у MySQL есть прекрасные сайты (php.net и dev.mysql.com соответственно) с новостями, статьями и ссылками на документацию. И, как я уже упомянул, сайт www.w3schools.com поможет вам познакомиться с широким набором технологий для web-разработки, не только с LAMP. Я бы также порекомендовал следующие четыре книги: | ||
+ | |||
+ | » Web-разработка на PHP и MySQL Люка Уэллинга [Luke Welling] и Лоры Томсон [Laura Thomson] | ||
+ | |||
+ | » Apache. Полное руководство Бена Лори [Ben Laurie] и Питера Лори [Peter Laurie] | ||
+ | |||
+ | » Руководство по основам web-дизайна с CSS и HTML Крейга Греннела [Craig Grannell] | ||
+ | |||
+ | » Программирование на PHP Расмуса Лердорфа [Rasmus Lerdorf], Кевина Татроу [Kevin Tatroe] и Питера Макинтайра [Peter MacIntyre] |
Версия 15:21, 15 ноября 2018
|
|
|
По рецептам доктора Брауна
Эзотерическое системное администрирование из причудливых заворотов кишок серверной
Linux предлагает выбор, а проприетарные системы его ограничивают. Но не может ли выбор быть слишком большим? В супермаркете я каждый раз вижу покупателей, застывших в нерешительности у прилавка с оливками или овощами только потому, что выбор у них слишком велик. Я видел и потенциальных пользователей Linux, сомневающихся в выборе дистрибутива, потому что на www.distrowatch.com они нашли как минимум 100 вариантов – и даже остановившись на одном из них, обнаружили, что у него есть настольная и серверная версии, 32- и 64-битные версии и т. д.
Эти затруднения от обилия привели к появлению «дистронаркоманов», неустанно прыгающих с дистрибутива на дистрибутив в надежде найти идеальный Linux, а как мне кажется – чтобы получить очередную дозу прыжков или просто поиграть с новинкой.
Задаем стандарт
Со стандартами дела обстоят не лучше. Компьютеры ныне хранят и обрабатывают числа, текст, изображения, звук и видео. Но по сути они всего лишь оперируют нулями и единицами, поэтому нам нужны стандарты, которые определяют представление этих высокоуровневых форм данных.
Стоит, однако, выйти за рамки скромного 32-битного целого числа, и наши попытки сделать эти стандарты... э-э... стандартными с треском провалятся. В Википедии перечислено более 80 форматов изображений и около 30 форматов звуковых файлов. С форматами файлов мультимедиа и того хуже: тут есть не только разные форматы кодирования аудио- и видеопотоков, но и разные форматы контейнеров, объединяющих потоки. Простите, но здесь мне выбор не нужен. Я бы предпочел иметь один формат, которым бы все пользовались и который понимали бы все устройства.
Лично я думаю, что у нас должна быть возможность выбирать, какой у нас должен быть выбор.
Сказка о двух прокси
Со словарем в руках Доктор изучает разницу между прямым и обратным прокси.
Мой Оксфордский словарь английского языка определяет прокси как «лицо, имеющее право выступать от имени другого лица, особенно при голосовании». В сфере компьютеров это слово имеет похожее значение: прокси-сервер выступает посредником для клиентов, запрашивающих ресурсы (обычно web-страницы) у других серверов. Вы, небось, знали это давно, а я совсем недавно узнал разницу между прямым и обратным прокси. И вот что я нарыл...
Прямой прокси расположен рядом с клиентом (браузером), обычно в локальной корпоративной сети. Он обслуживает ограниченное число клиентов, но может перенаправлять их на большое число целевых серверов – хоть на весь Интернет. Клиент должен знать, что он обязан отправлять запросы через прокси, и вы наверняка уже бодались с настройками в окне браузера, чтобы сообщить ему об этом. Прямые прокси обычно используются в корпоративных сетях, где они повышают производительность за счет кэширования результатов недавних запросов и повышают безопасность, фильтруя трафик на заданные сайты.
Обратный прокси расположен рядом с целевыми серверами, обычно в той же сети. Для клиента этот прокси выглядит как сам сервер. Клиент не знает, что его запрос перенаправляется. Обратный сервер обслуживает запросы от большого числа клиентов, но может перенаправлять их на ограниченное число серверов. Обратные прокси иногда используются для балансировки нагрузки, когда кластер серверов расположен за одним прокси. Иногда они возвращают статический контент сайта, а запросы на динамический контент перенаправляют на другой сервер. Например, web-сервер Apache можно использовать в качестве клиента Tomcat, при этом Apache будет отвечать за статический контент, а Tomcat – за сервлеты Java и страницы JSP.
Стек LAMP
В этой последней части данной серии мы зажигаем нашу лампу (LAMP) и пишем настоящее web-приложение, управляемое данными.
Это последний из четырех уроков серии, в которой мы говорим о стеке LAMP (Linux, Apache, MySQL и PHP) и о том, как с его помощью создавать динамические, управляемые данными web-сайты. На первом уроке мы собрали части L, A и P. На втором – посмотрели, как HTML и PHP спелись для создания web-приложений. На третьем мы узнали о базах данных, немного познакомились с SQL и установили часть “M” стека, MySQL. С установкой новых компонентов покончено, и в этом месяце мы заставим все четыре компонента LAMP работать вместе, написав полноценное web-приложение, управляемое данными. Мы разработаем приложение, которое позволит читателям выполнять запросы к базе данных библиотеки через HTML-форму, и напишем серверный код на PHP, который выполнит эти запросы к базе данных и сформирует результат, отформатированный в HTML. Начнем...
Определение миссии
Начну с объяснения того, чего мы пытаемся добиться. Воспользовавшись базой данных library, созданной в прошлом месяце, мы создадим HTML-форму, которая позволит читателю ввести название и/или автора книги; затем мы создадим страницу на PHP, которая будет опрашивать каталог библиотеки и возвращать таблицу с соответствующими книгами. Визуально и форма запроса, и страница с результатами будут очень простыми, как показано на рисунках. Начнем с HTML-формы booksearch.html. Она и вправду проста:
<html>
<head>
<title>Simple book search</title>
</head>
<body>
<form action=”booksearch.php” method=”POST”>
Title: <INPUT type=”text” name=”searchtitle”>
Author: <INPUT type=”text” name=”searchauthor”>
<INPUT type=”submit” name=”booksearch” value=”Search”>
</form>
</body>
</html>
На этой форме есть два поля ввода и кнопка, которая возвратит данные формы обратно странице booksearch.php. С помощью этой формы можно осуществлять поиск по названию книги, автору или и по тому, и по другому сразу.
Заставляем «P» говорить с «M»
Теперь обратим свое внимание на страницу поиска книги. Новичков в PHP и web-приложениях она может малость запугать, поэтому разберем ее постепенно. Для начала обратимся к схеме на стр. 64, где детально показано, как работают web-приложения.
Большинство приложений следуют этим шести этапам. Нам нужно усвоить, как управление прыгает между браузером и сервером – читатель запрашивает стартовую страницу приложения, сервер отправляет форму, читатель ее заполняет, сервер обрабатывает запрос, читатель восхищается результатами и т. д. У молодых web-разработчиков проблем с этим нет, но пожилым программистам вроде меня, которые всю жизнь писали монолитный код, где все происходило в одной программе на одном компьютере, нужно время на перестройку.
Шаг 1: Сбор данных от пользователя
Как мы видели во второй части, PHP делает данные формы доступными в словаре $_REQUEST. Ключ в этом словаре – просто имя компонента формы. Так, чтобы получить название книги, можно сделать следующее:
$searchtitle = trim($_REQUEST[‘searchtitle’]);
$searchtitle = addslashes($searchtitle);
Функция trim() удаляет все пробелы в начале или в конце поля ввода, которые мог оставить там читатель. Функция addslashes() добавляет в строку обратные слэши перед символами, которые нужно заключить в кавычки в запросах к базе данных. Она также предоставляет базовую защиту от атак с применением SQL-инъекции, о которых я расскажу позже. Другая функция, которая может вам здесь пригодиться – mysqli_real_escape_string(), она делает это лучше, но специфична для MySQL.
Шаг 2: Подключимся к базе данных
Для доступа к базе данных MySQL из кода на PHP мы воспользуемся библиотекой mysqli, которая заменила библиотеку mysql (“i” означает «улучшенная [improved]»). Вам, наверное, понадобится ее установить, но в репозиториях CentOS она есть. Кроме того, нужно перезапустить Apache:
# yum install php-mysql
# service httpd restart
Эта библиотека предоставляет API в двух стилях: процедурном и объектно-ориентированном. Разница в основном стилистическая; здесь мы воспользуемся объектно-ориентированной версией. Для подключения к базе данных укажите имя или IP-адрес сервера MySQL, корректные имя пользователя и пароль и имя базы данных, к которой нужно подключиться. Ввызов функции выглядит так:
@ $db = new mysqli(“example.com”, “root”, “secret”, “library”);
if ($db->connect_error) {
echo “could not connect: “ . $db->connect_error;
exit(1);
}
Обратите внимание на @ в первой строке. Это оператор подавления ошибки PHP. Мы пользуемся им, чтобы сохранить управление в случае ошибок и обработать все ошибки самим.
Шаг 3: Построим SQL-запрос
В настоящем приложении этот шаг может усложниться, но мы оставим его простым:
$query = “ select * from books”;
$query = $query . “ where title like ‘%” . $searchtitle . “%’”;
Обратите внимание на оператор объединения строк в PHP (.), с помощью которого мы собрали запрос по кусочкам. Также обратите внимание на использование SQL-оператора like и шаблон %. Мы ищем книги не с точным совпадением названия, а с любым названием, которое содержит введенную пользователем строку.
Шаг 4: Выполним запрос
PHP предлагает несколько вариантов выполнения запроса в зависимости от того, как вы хотите работать с результатами. В этом примере мы воспользуемся технологией, в которой столбцы результата запроса связываются с заданными переменными PHP. В этом фрагменте кода $query содержит сам запрос, а $db – ссылку на подключение к базе данных, установленное на втором шаге.
$stmt = $db->prepare($query);
$stmt->bind_result($bookid, $title, $author, $onloan, $duedate, $borrowerid);
$stmt->execute();
Шаг 5: Получим результаты
Запрос возвращает набор результатов, состоящий из нуля или более строк. С помощью связанных переменных, которые мы создали ранее, легко пройтись по этим строкам в цикле и получить результаты:
while ($stmt->fetch()) {
echo “$title was written by $author
”;
}
Шаг 6: Построим HTML-ответ
Обычно набор результатов из нескольких строк представляется пользователю в виде таблицы, каждая строка которой соответствует строке из набора результатов. Вот переработанная версия предыдущего фрагмента кода, в которую добавлено несколько HTML-тэгов таблицы:
echo “$title | $author |
Мы полагаемся на то, что переменные $title и $author заполнены даже внутри двойных кавычек. Теперь объединим все это вместе и создадим полноценную PHP-страницу booksearch.php. Не пугайтесь, если она покажется сложной. По большей части она состоит из фрагментов, которые мы уже видели. Добавлено немного дополнительной логики, чтобы пользователь мог искать по автору, названию книги или и по тому, и по другому вместе. Например, эта страница может построить запрос вроде select * from books where title like ‘ %Potter %’ and author like ‘ %Rowling %’
<html>
<head>
<title>Library Book Search</title>
</head>
<body>
Book Search Results
<?php
- Get data from form
$searchtitle = trim($_REQUEST[‘searchtitle’]);
$searchauthor = trim($_REQUEST[‘searchauthor’]);
if (!$searchtitle && !$searchauthor) {
echo “You must specify either a title or an author”;
exit();
}
$searchtitle = addslashes($searchtitle);
$searchauthor = addslashes($searchauthor);
- Open the database
@ $db = new mysqli(‘localhost’, ‘root’, ‘rootpw’, ‘library’);
if ($db->connect_error) {
echo “could not connect: “ . $db->connect_error;
exit();
}
# Build the query. Users are allowed to search on title,
# author, or both
$query = “ select * from books”;
if ($searchtitle && !$searchauthor) { // Title search only
$query = $query . “ where title like ‘%” . $searchtitle . “%’”;
}
if (!$searchtitle && $searchauthor) { // Author search only
$query = $query . “ where author like ‘%” . $searchauthor . “%’”;
}
if ($searchtitle && $searchauthor) { // Title and Author search
$query = $query . “ where title like ‘%” . $searchtitle . “%’ and author like ‘%” . $searchauthor .“%’”; // unfinished
}
# Run the query using bound result parameters
$stmt = $db->prepare($query);
$stmt->bind_result($bookid, $title, $author, $onloan,
$duedate, $borrowerid);
$stmt->execute();
echo “$bookid | $title | $author |
?>
</body>
</html>
В коде, который строит запрос, есть хитрые кавычки – просто помните, что двойные кавычки защищают одинарные. По данным сайта owasp (https://owasp.org/index.php/Topten), главная угроза web-сайтам – SQL-инъекция. Любое приложение, которое берет «недоверенные» данные, введенные пользователем, и строит на их основе запрос, который потом выполняет, уязвимо к такого рода атакам, если не предпринимает должных мер предосторожности. Под «недоверенными» данными я понимаю данные, которые вводит пользователь (возможно, злоумышленник) в виде текста в компонент для ввода данных на форме.
Лучше всего добавить на страницу код на JavaScript, который будет проверять все поля ввода. Хотя это помогает предотвратить отправку неправильно заполненной формы, это не защищает от атак, потому что ничто не мешает злоумышленнику отправить готовую форму с неправильным вводом. Другими словами, данные, которые вы получаете, не всегда приходят из формы, которую до этого заполнил пользователь.
Каков же принцип действия атаки с SQL-инъекцией? Вот пример – фрагмент кода PHP, который получает идентификатор клиента из формы и использует его для построения запроса. Идея состоит в том, чтобы клиент увидел информацию о банковском счете только для одного идентификатора.
# Part of the page accountview.php
$custid = $_REQUEST[“id”];
$query = “select * from accounts where custID = ‘$custid’ “;
Теперь злоумышленник открывает в браузере адрес http://example.com/app/accountview.php?id=’ or ‘1’=’1
Здесь «недоверенные» данные – это значение id. Наша страница построит следующий запрос:
select * from accounts where custID = ‘’ or ‘1’ = ‘1’
Так как второе условие всегда верно, запрос вернет всю таблицу accounts!
Защита
Нам поможет использование готовых шаблонов запросов. Сначала мы задаем шаблон запроса, пометив знаком ? те места, куда попадут данные; затем мы можем выполнить запрос, добавив в шаблон данные на эти места.
$query = “insert into borrowers values (?, ?, ?)”;
$stmt = $db->prepare($query);
// ... some time later ...
$db->execute(array(108, “Fred Flintstone”, “4 Quarry Hill, Bedrock”));
В реальности данные будут не жестко «вшиты» в код, а поступят из недоверенного источника. Готовые шаблоны помогают решить проблему, так как любые специальные символы во входных данных автоматически экранируются. Более надежная защита – проверка недоверенного ввода на соответствие ожидаемому синтаксису. Например, если клиент должен ввести дату в поле ввода “date”, можно сделать следующее:
$mydate = trim($_REQUEST[“date”]);
if (!ereg(“^[0-9]{4}-[0-9]{2}-[0-9]{2}$”, $mydate) {
echo “date must be YYYY-MM-DD”;
return;
Конечно, эта проверка с регулярным выражением не слишком придирчива к дате – и пользователь сможет ввести что-нибудь вроде 2014-88-99, но ее достаточно для того, чтобы обнаружить некорректные данные, введенные злоумышленником.
Во второй части этой серии я писал о сохранении состояния сессии в web-приложении с помощью объекта $_SESSION PHP и упомянул, что для этого невидимо для пользователя используется куки [cookie]. Информация, которая хранится таким образом, не переживет сессию. Когда пользователь закрывает браузер, сессия исчезает. Однако мы также можем создать «постоянные» куки (с датой истечения), которые позволят постоянно хранить информацию между различными сессиями. Куки отправляются сервером браузеру как часть заголовка HTTP-ответа. Вот пример куки из Интернета, полученный при отслеживании пакетов при открытии сайта amazon.co.uk:
Set-cookie: session-id=202-3921230-4252634; path=/;
domain=.amazon.co.uk; expires=Tue 1 Jan 2036 00:00:01 2036 GMT
Здесь session-id – имя куки, а это страшное длинное число – значение. Атрибуты domain и path задают сайт, которому принадлежит куки, а path – путь внутри сайта, к которому применяется куки (в данном случае это /, так что куки применяется ко всему сайту).
Чтобы сгенерировать куки в PHP, вызовите setcookie(). Функции нужно передать как минимум имя и значение куки; также можно передать дату истечения куки, путь и домен. Чтобы получить куки, обратитесь к массиву $_COOKIE, в качестве ключа используя имя куки. В ответ вы получите значение куки или null, если куки не существует.
Вот две маленькие страницы, которые иллюстрируют куки в действии. На первой мы пытаемся получить из куки имя посетителя. В случае успеха мы приветствуем пользователя по имени. В противном случае мы предлагаем пользователю форму, в которую он вводит свое имя. Она отправляется на вторую страницу, которая генерирует куки. Вот основы первой страницы (я пропустил обрамляющие тэги HTML):
<?php
// Назвал ли этот пользователь свое имя?
@ $visitor = $_COOKIE[‘visitor_name’];
if (is_null($visitor)) {
?>
<form action=”record_name.php”>
Пожалуйста, назовитесь:
<input type=text name=”yourname”>
<input type=submit value=”Submit”>
</form>
<?php } else { ?>
Привет <?= $visitor ?>, рады видеть вас снова!
<? } ?>
а вот вторая страница:
<?php
$visitor = $_REQUEST[‘yourname’];
setcookie(‘visitor_name’, $visitor);
?>
Спасибо, что представились,
<?= $visitor ?>
Чтобы стать web-разработчиком, нужно изучить огромное количество технологий, и это непростая задача. Но стек LAMP по крайней мере дает вам инструменты, с которыми это можно сделать. Удачи! |
Размещение сайта
Чтобы развернуть приложение, его нужно разместить в каталоге Document Root, заданном в файле настройки Apache (/etc/httpd/conf/httpd.conf).
В конфигурации по умолчанию из репозиториев CentOS он установлен в /var/www/html. Все что вам нужно – скопировать эти два файла (booksearch.html и booksearch.php) в этот каталог. Для проверки откройте браузер и наберите в адресной строке http://localhost/booksearch.html.
Советы по отладке
Если в вашем коде на PHP есть синтаксические ошибки, то при попытке открыть страницу вы скорее всего получите совершенно пустую (и абсолютно бесполезную) страницу. Чтобы заставить PHP сообщать об ошибках, измените одну строку в /etc/php.ini:
display_errors = On
и перезапустите Apache. Теперь синтаксические ошибки будут возвращаться браузеру. Отчет будет включать номер строки с ошибкой, но помните, что это номер строки, на которой споткнулся интерпретатор, а сама ошибка могла быть раньше. Отсутствие точки с запятой в предыдущей строке – наиболее частый прокол. Возможно, вам будет удобнее проверять ошибки с командной строки таким образом:
php -l booksearch.php
Флаг -l переводит PHP в мягкий режим – в нем интерпретатор не выполняет код, а только проверяет синтаксис.
> Общая схема. Большинство web-приложений, управляемых данными, выполняют эти шесть шагов.
Чтобы узнать больше
И у PHP, и у MySQL есть прекрасные сайты (php.net и dev.mysql.com соответственно) с новостями, статьями и ссылками на документацию. И, как я уже упомянул, сайт www.w3schools.com поможет вам познакомиться с широким набором технологий для web-разработки, не только с LAMP. Я бы также порекомендовал следующие четыре книги:
» Web-разработка на PHP и MySQL Люка Уэллинга [Luke Welling] и Лоры Томсон [Laura Thomson]
» Apache. Полное руководство Бена Лори [Ben Laurie] и Питера Лори [Peter Laurie]
» Руководство по основам web-дизайна с CSS и HTML Крейга Греннела [Craig Grannell]
» Программирование на PHP Расмуса Лердорфа [Rasmus Lerdorf], Кевина Татроу [Kevin Tatroe] и Питера Макинтайра [Peter MacIntyre]
Размещение сайта
Чтобы развернуть приложение, его нужно разместить в каталоге Document Root, заданном в файле настройки Apache (/etc/httpd/conf/httpd.conf).
В конфигурации по умолчанию из репозиториев CentOS он установлен в /var/www/html. Все что вам нужно – скопировать эти два файла (booksearch.html и booksearch.php) в этот каталог. Для проверки откройте браузер и наберите в адресной строке http://localhost/booksearch.html.
Советы по отладке
Если в вашем коде на PHP есть синтаксические ошибки, то при попытке открыть страницу вы скорее всего получите совершенно пустую (и абсолютно бесполезную) страницу. Чтобы заставить PHP сообщать об ошибках, измените одну строку в /etc/php.ini:
display_errors = On
и перезапустите Apache. Теперь синтаксические ошибки будут возвращаться браузеру. Отчет будет включать номер строки с ошибкой, но помните, что это номер строки, на которой споткнулся интерпретатор, а сама ошибка могла быть раньше. Отсутствие точки с запятой в предыдущей строке – наиболее частый прокол. Возможно, вам будет удобнее проверять ошибки с командной строки таким образом:
php -l booksearch.php
Флаг -l переводит PHP в мягкий режим – в нем интерпретатор не выполняет код, а только проверяет синтаксис.
> Общая схема. Большинство web-приложений, управляемых данными, выполняют эти шесть шагов.
Чтобы узнать больше
И у PHP, и у MySQL есть прекрасные сайты (php.net и dev.mysql.com соответственно) с новостями, статьями и ссылками на документацию. И, как я уже упомянул, сайт www.w3schools.com поможет вам познакомиться с широким набором технологий для web-разработки, не только с LAMP. Я бы также порекомендовал следующие четыре книги:
» Web-разработка на PHP и MySQL Люка Уэллинга [Luke Welling] и Лоры Томсон [Laura Thomson]
» Apache. Полное руководство Бена Лори [Ben Laurie] и Питера Лори [Peter Laurie]
» Руководство по основам web-дизайна с CSS и HTML Крейга Греннела [Craig Grannell]
» Программирование на PHP Расмуса Лердорфа [Rasmus Lerdorf], Кевина Татроу [Kevin Tatroe] и Питера Макинтайра [Peter MacIntyre]