Журнал LinuxFormat - перейти на главную

LXF170:Рубрика сисадмина

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(Новая страница: «Категория: Постояные рубрики == По рецептам доктора Брауна == ''Эзо­те­ри­че­ское сис­т…»)
 
 
(не показана 1 промежуточная версия 1 участника)
Строка 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]

Текущая версия на 15:24, 15 ноября 2018

Содержание

[править] По рецептам доктора Брауна

Эзо­те­ри­че­ское сис­тем­ное ад­ми­ни­ст­ри­ро­ва­ние из при­чуд­ли­вых за­во­ро­тов ки­шок сер­вер­ной

(thumbnail)
Д-р Крис Браун Доктор обучает, пишет и консультирует по Linux. Ученая степень по физике элементарных частиц ему в этом совсем не помогает.

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 “”; while ($stmt->fetch()) { echo “”;

}

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

  1. 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);

  1. 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 “”; while ($stmt->fetch()) { echo “”; } 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]

Персональные инструменты
купить
подписаться
Яндекс.Метрика