В 2002 году вопрос, на чём запускать веб-приложение, имел два ответа. Корпоративный: Windows 2000 Server + IIS + SQL Server + ASP - дорого, лицензии, Windows-администратор. Свободный: Linux + Apache + MySQL + PHP.
LAMP не был продуктом. Его никто не изобретал. Это был органически сложившийся набор open-source инструментов, которые работали вместе надёжнее, чем каждый из них по отдельности. К 2002 году на LAMP работало большинство новых коммерческих сайтов. Shared-хостинг по всему миру - Apache на Linux, MySQL включён бесплатно, PHP в комплекте. За $8 в месяц.
Это изменило всё: кто мог строить в вебе, как быстро и за сколько.
Компоненты LAMP в 2002 году
Linux (Red Hat 7.3 / Debian 3.0): ОС. Бесплатна. Ядро 2.4 - производственный стандарт.
Apache 1.3 / 2.0: Веб-сервер. mod_php загружал PHP как модуль Apache - никаких CGI-процессов на каждый запрос. PHP работал in-process, в разы быстрее Perl CGI.
MySQL 3.23 / 4.0: СУБД. В MySQL 3.23 транзакций не было (только MyISAM). MySQL 4.0 (2002) добавил стабильный InnoDB с транзакциями.
PHP 4.3: Язык. register_globals включён по умолчанию - катастрофа безопасности, которую убивали годами. Процедурный код прямо в HTML. Быстро пишется, быстро деплоится.
Настоящее PHP-приложение 2002 года
<?php
// config.php - подключение к БД, стиль 2002 года
// Нет PDO (только в PHP 5.1). Нет MySQLi в PHP 4.
// Только функции mysql_* - расширение, удалённое в PHP 7.
define('DB_HOST', 'localhost');
define('DB_USER', 'webapp');
define('DB_PASS', 'secret123');
define('DB_NAME', 'myapp');
$conn = mysql_connect(DB_HOST, DB_USER, DB_PASS);
if (!$conn) {
die('Ошибка подключения к БД: ' . mysql_error());
}
mysql_select_db(DB_NAME, $conn);
mysql_query("SET NAMES 'utf8'", $conn);
?>
<?php
// products.php - страница каталога товаров
require_once 'config.php';
$cat_id = isset($_GET['cat']) ? (int)$_GET['cat'] : 0;
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$per_page = 20;
$offset = ($page - 1) * $per_page;
$where = $cat_id > 0
? "WHERE category_id = $cat_id AND active = 1"
: "WHERE active = 1";
$total_row = mysql_fetch_assoc(
mysql_query("SELECT COUNT(*) AS total FROM products $where", $conn)
);
$total_pages = ceil($total_row['total'] / $per_page);
$result = mysql_query(
"SELECT id, name, price, image, description
FROM products $where
ORDER BY created_at DESC
LIMIT $offset, $per_page",
$conn
);
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Каталог товаров</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="/css/style.css">
</head>
<body>
<?php include 'header.php'; ?>
<div id="content">
<h1>Каталог</h1>
<?php if (mysql_num_rows($result) === 0): ?>
<p>Товаров не найдено.</p>
<?php else: ?>
<table cellpadding="8" cellspacing="0" border="0" class="catalog">
<tr><th>Фото</th><th>Товар</th><th>Цена</th><th></th></tr>
<?php $i = 0; while ($row = mysql_fetch_assoc($result)): ?>
<tr class="<?php echo ($i++ % 2 === 0) ? 'even' : 'odd'; ?>">
<td><img src="/images/<?php echo htmlspecialchars($row['image']); ?>"
width="60" height="60"></td>
<td>
<a href="product.php?id=<?php echo $row['id']; ?>">
<?php echo htmlspecialchars($row['name']); ?>
</a>
<br><small><?php echo htmlspecialchars(substr($row['description'], 0, 80)); ?>...</small>
</td>
<td><?php echo number_format($row['price'], 2); ?> руб.</td>
<td><a href="cart.php?action=add&id=<?php echo $row['id']; ?>">В корзину</a></td>
</tr>
<?php endwhile; ?>
</table>
<div class="pagination">
<?php for ($p = 1; $p <= $total_pages; $p++): ?>
<?php if ($p == $page): ?><strong><?php echo $p; ?></strong>
<?php else: ?><a href="?cat=<?php echo $cat_id; ?>&page=<?php echo $p; ?>"><?php echo $p; ?></a>
<?php endif; ?>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<?php include 'footer.php'; ?>
</body>
</html>
.htaccess: швейцарский нож LAMP-разработчика
# .htaccess 2002 года - обязательное знание для каждого PHP-разработчика
RewriteEngine On
RewriteBase /
# Красивые URL: /product/42 → product.php?id=42
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^product/([0-9]+)/?$ product.php?id=$1 [L,QSA]
RewriteRule ^catalog/([a-z0-9-]+)/?$ products.php?slug=$1 [L,QSA]
# Отключаем register_globals - критично для безопасности
php_flag register_globals Off
php_flag display_errors Off
php_value upload_max_filesize 5M
# Запрет листинга директорий
Options -Indexes
# Защита конфигурационных файлов
<FilesMatch "^(config|db)\.php$">
Order allow,deny
Deny from all
</FilesMatch>
Что LAMP сделал правильно и неправильно
Правильно: нулевая стоимость лицензий. Один и тот же $8-хостинг работал у всех. mod_php давал достаточную производительность. LAMP открыл веб-разработку для тысяч разработчиков по всему миру, включая Россию.
Неправильно: register_globals = On по умолчанию делал SQL-инъекции и XSS тривиальными. Большинство российских сайтов 2002-2005 годов имели уязвимости именно из-за этого. Отсутствие транзакций в MyISAM по умолчанию приводило к несогласованным данным при сбоях.
Неправильно: mysql_real_escape_string() вместо параметризованных запросов - защита от SQL-инъекций была ручной и её часто забывали применять.
Эти ошибки исправлялись постепенно: PDO в PHP 5.1, InnoDB по умолчанию в MySQL 5.5, депрекация register_globals в PHP 5.3. Платформа росла вокруг своих уязвимостей.
К 2010 году LAMP лежал в основе WordPress, Drupal, Joomla, MediaWiki - программного обеспечения, на котором работало большинство контента в интернете. Это началось с $8 в месяц и свободного кода.