Classic ASP и первые корпоративные веб-системы Бишкека (2000)
2000 год. Бишкек. Количество интернет-пользователей в Кыргызстане перешагнуло отметку в 50 тысяч. Крупные компании - банки, импортёры техники, телекоммуникационные операторы - начали воспринимать сайт не просто как визитку, а как инструмент работы с партнёрами и клиентами.
Первые задачи: закрытые разделы для оптовых покупателей, личные кабинеты, скачивание актуального прайс-листа только после авторизации. Всё это требовало сессионной авторизации. Разработчики, работавшие на Windows-инфраструктуре (а большинство бишкекских корпоративных машин работало именно на Windows), выбирали Microsoft ASP.
Полная система авторизации для бишкекского сайта
<%
' login.asp - система входа, Бишкек 2000 год
' IIS 5, VBScript, SQL Server 7/2000
' Кодировка: windows-1251
Option Explicit
Dim strError
strError = ""
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim strLogin, strPass
strLogin = Trim(Request.Form("login"))
strPass = Trim(Request.Form("password"))
If strLogin = "" Or strPass = "" Then
strError = "Заполните оба поля."
Else
Dim bOk
bOk = VerifyUser(strLogin, strPass)
If bOk Then
Session("LoggedIn") = True
Session("Login") = strLogin
Session("LoginTime") = Now()
Dim strNext
strNext = Request.QueryString("next")
If strNext = "" Then strNext = "dashboard.asp"
Response.Redirect strNext
Response.End
Else
strError = "Неверный логин или пароль."
End If
End If
End If
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Вход - Личный кабинет</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body bgcolor="#EFEFEF">
<br><br>
<table width="320" border="0" cellpadding="0" cellspacing="0" align="center">
<tr>
<td bgcolor="#003399" height="26">
<font face="Arial" size="2" color="#FFFFFF">
<b>Вход в закрытый раздел</b>
</font>
</td>
</tr>
<tr>
<td bgcolor="#FFFFFF"
style="border:1px solid #003399; border-top:0; padding:14px;">
<% If strError <> "" Then %>
<p style="margin:0 0 10px 0;">
<font face="Arial" size="2" color="#CC0000">
<b><%= Server.HTMLEncode(strError) %></b>
</font>
</p>
<% End If %>
<form method="POST" action="login.asp">
<table border="0" cellpadding="3">
<tr>
<td><font face="Arial" size="2">Логин:</font></td>
<td>
<input type="text" name="login" size="22"
style="border:1px solid #999; font-size:12px;">
</td>
</tr>
<tr>
<td><font face="Arial" size="2">Пароль:</font></td>
<td>
<input type="password" name="password" size="22"
style="border:1px solid #999; font-size:12px;">
</td>
</tr>
<tr>
<td colspan="2" align="right">
<input type="submit" value="Войти"
style="background:#003399; color:#FFF;
border:1px solid #001166;
padding:3px 14px; cursor:pointer;
font-size:12px; font-weight:bold;">
</td>
</tr>
</table>
</form>
</td>
</tr>
<tr>
<td>
<font face="Arial" size="1" color="#666666">
Нет доступа?
<a href="mailto:admin@mojacompany.kg">Обратитесь к администратору</a>
</font>
</td>
</tr>
</table>
</body>
</html>
Функция проверки пароля через ADODB
<%
' Функция VerifyUser - проверка логина/пароля по базе данных
' Включается в login.asp через <!-- #include file="functions.asp" -->
Function VerifyUser(strLogin, strPass)
VerifyUser = False
Dim oConn, oCmd, oRS
Set oConn = Server.CreateObject("ADODB.Connection")
' Строка подключения к SQL Server
' В Бишкеке 2000 года сервер обычно стоял в том же офисе
oConn.Open "Provider=SQLOLEDB;" & _
"Data Source=BISHKEK-SRV01;" & _
"Initial Catalog=CompanyDB;" & _
"User ID=web_user;" & _
"Password=web_pass123;"
' Параметризованный запрос - защита от SQL-инъекций
' (даже в 2000 году это был правильный подход, хотя многие им пренебрегали)
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
oCmd.CommandText = "SELECT UserID, PasswordMD5, FullName, " & _
" CompanyName, IsActive " & _
"FROM Users " & _
"WHERE Login = ?"
oCmd.CommandType = 1 ' adCmdText
oCmd.Parameters.Append _
oCmd.CreateParameter("@login", 200, 1, 50, strLogin)
' adVarChar=200, adParamInput=1
Set oRS = oCmd.Execute()
If Not oRS.EOF Then
Dim bActive
bActive = CBool(oRS("IsActive"))
If bActive Then
' MD5-хеш - стандарт хранения паролей в 2000 году
' md5.asp подключался отдельно как include-файл
' (скачивался с aspfaq.com или codeguru.com)
Dim sHash
sHash = LCase(MD5(strPass))
If sHash = LCase(CStr(oRS("PasswordMD5"))) Then
VerifyUser = True
' Сохраняем данные пользователя в сессии
Session("UserID") = CStr(oRS("UserID"))
Session("FullName") = CStr(oRS("FullName"))
Session("CompanyName") = CStr(oRS("CompanyName"))
' Обновить время последнего входа и IP
Dim strIP
strIP = Request.ServerVariables("REMOTE_ADDR")
Call LogAccess(CStr(oRS("UserID")), strIP, True, oConn)
Else
' Неверный пароль - логируем попытку
Call LogAccess(CStr(oRS("UserID")), _
Request.ServerVariables("REMOTE_ADDR"), False, oConn)
End If
End If
End If
oRS.Close
oConn.Close
Set oRS = Nothing
Set oCmd = Nothing
Set oConn = Nothing
End Function
Sub LogAccess(strUID, strIP, bSuccess, oConn)
Dim oCmd
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
oCmd.CommandText = "INSERT INTO AccessLog " & _
"(UserID, IPAddress, LoginTime, Success) " & _
"VALUES (?, ?, GETDATE(), ?)"
oCmd.Parameters.Append oCmd.CreateParameter("@uid", 3, 1, 4, CLng(strUID))
oCmd.Parameters.Append oCmd.CreateParameter("@ip", 200, 1, 15, strIP)
oCmd.Parameters.Append oCmd.CreateParameter("@ok", 11, 1, 1, bSuccess)
oCmd.Execute
Set oCmd = Nothing
End Sub
%>
auth_check.asp и защита страниц
<%
' auth_check.asp - вставлять в начало каждой защищённой страницы
' <!-- #include file="auth_check.asp" -->
If Not CBool(Session("LoggedIn")) Then
Response.Redirect "login.asp?next=" & _
Server.URLEncode(Request.ServerVariables("URL"))
Response.End
End If
' Таймаут сессии: 60 минут (корпоративный стандарт)
If IsDate(Session("LoginTime")) Then
If DateDiff("n", Session("LoginTime"), Now()) > 60 Then
Session.Abandon
Response.Redirect "login.asp?msg=timeout"
Response.End
End If
End If
Session("LoginTime") = Now()
%>
Пример защищённой страницы - прайс-лист для партнёров:
<%
Option Explicit
%>
<!-- #include file="auth_check.asp" -->
<!-- #include file="header.asp" -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Прайс-лист партнёра</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body>
<h2>Добро пожаловать, <%= Server.HTMLEncode(Session("FullName")) %>!</h2>
<p>Компания: <b><%= Server.HTMLEncode(Session("CompanyName")) %></b>
| <a href="logout.asp">Выйти</a></p>
<hr>
<%
Dim oConn, oRS, oCmd
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open Application("DB_CONN")
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
' Прайс-лист актуален на дату запроса
oCmd.CommandText = "SELECT p.SKU, p.NameRU, p.PriceKGS, p.Stock " & _
"FROM Products p " & _
"WHERE p.IsActive = 1 AND p.StockQty > 0 " & _
"ORDER BY p.CategoryID, p.NameRU"
oCmd.CommandType = 1
Set oRS = oCmd.Execute()
%>
<h3>Актуальный прайс-лист
(<%= FormatDateTime(Now(), 2) %>)</h3>
<table border="1" cellpadding="5" cellspacing="0"
style="border-collapse:collapse; font-size:12px;">
<tr bgcolor="#003399">
<th><font color="#FFF">Артикул</font></th>
<th><font color="#FFF">Наименование</font></th>
<th><font color="#FFF">Цена (сом)</font></th>
<th><font color="#FFF">Наличие</font></th>
</tr>
<% Dim rowBg : rowBg = "#FFFFFF" %>
<% Do While Not oRS.EOF %>
<tr bgcolor="<%= rowBg %>">
<td><%= Server.HTMLEncode(oRS("SKU")) %></td>
<td><%= Server.HTMLEncode(oRS("NameRU")) %></td>
<td align="right">
<%= FormatNumber(oRS("PriceKGS"), 2) %>
</td>
<td align="center">
<% If CInt(oRS("Stock")) > 10 Then %>
<font color="green">✓ В наличии</font>
<% ElseIf CInt(oRS("Stock")) > 0 Then %>
<font color="orange">Мало (%= oRS("Stock") %)</font>
<% Else %>
<font color="red">Нет</font>
<% End If %>
</td>
</tr>
<% rowBg = IIf(rowBg = "#FFFFFF", "#F5F5F5", "#FFFFFF") %>
<% oRS.MoveNext %>
<% Loop %>
</table>
<%
oRS.Close : oConn.Close
Set oRS = Nothing : Set oConn = Nothing
%>
</body>
</html>
Схема БД для кыргызстанского торгового сайта
-- SQL Server 7/2000, Query Analyzer, Бишкек 2000 год
CREATE TABLE Users (
UserID INT IDENTITY(1,1) PRIMARY KEY,
Login VARCHAR(50) NOT NULL UNIQUE,
PasswordMD5 VARCHAR(32) NOT NULL,
FullName NVARCHAR(100) NOT NULL,
CompanyName NVARCHAR(150) NULL,
Phone VARCHAR(30) NULL,
IsActive BIT NOT NULL DEFAULT 1,
CreatedAt DATETIME NOT NULL DEFAULT GETDATE(),
LastLogin DATETIME NULL
)
GO
CREATE TABLE Products (
ProductID INT IDENTITY(1,1) PRIMARY KEY,
SKU VARCHAR(20) NOT NULL UNIQUE,
NameRU NVARCHAR(200) NOT NULL,
CategoryID INT NOT NULL,
PriceKGS DECIMAL(10,2) NOT NULL, -- в кыргызских сомах
Stock INT NOT NULL DEFAULT 0,
IsActive BIT NOT NULL DEFAULT 1
)
GO
CREATE TABLE AccessLog (
LogID INT IDENTITY(1,1) PRIMARY KEY,
UserID INT NOT NULL,
IPAddress VARCHAR(15) NOT NULL,
LoginTime DATETIME NOT NULL DEFAULT GETDATE(),
Success BIT NOT NULL
)
GO
Особенности ASP-разработки в Бишкеке 2000 года
Сервер в офисе. В отличие от казахстанских компаний, которые чаще арендовали хостинг в Москве, бишкекские корпоративные клиенты нередко держали сервер физически в офисе - поставили старый Pentium II, накатили Windows 2000 Server, IIS 5, SQL Server 7. Это означало: полный контроль, но никакой резервации - свет выключили, сервер лежит.
NVARCHAR для кыргызского. Кыргызские буквы Ң, Ү, Ө не входили в windows-1251. SQL Server 2000 поддерживал Unicode через NVARCHAR - это был правильный путь:
' Параметр с Unicode-строкой:
oCmd.Parameters.Append _
oCmd.CreateParameter("@name", 202, 1, 100, strFullName)
' 202 = adVarWChar (Unicode) вместо 200 (adVarChar cp1251)
' Так кыргызские имена хранились корректно уже в 2000 году
Переход к PHP к 2003 году. Бишкекские компании, начавшие с ASP в 2000-2001 годах, к 2003-2004 году переходили на PHP. Причина та же: PHP-хостинг был дешевле Windows VPS, документации на русском больше, сообщество активнее. Навыки ASP при этом не пропадали - Request/Response/Session-модель один в один отображалась на PHP $_POST/$_GET/$_SESSION.