Данный проект является экспериментом. Здесь предыстория и небольшой анализ ситуации.
Девиз проекта:
Only an gormless fool will begin to place business logic in stored procedures
Репозиторий: GitHub
Концепт
Работает это так:
- Запускается приложение Ormless на сервере. При запуске оно считывает все странички с JavaScript-скриптами и кладёт их в переменную.
- Пользователь открывает страничку. Веб-сервер получает соответствующую команду и отдаёт из переменной страничку.
- Страница запрашивает данные, для чего отправляет API-команду на сервер.
- Сервер получает API-команду, проверяет авторизацию. Если всё нормально, команда сравнивается с имеющимся на сервере описанием этой команды — проверяются типы параметров и их содержимое.
- Если всё нормально — команда преобразуется в SQL-команду запуска хранимки. Команда отрабатывает, результат заворачивается в JSON.
- JSON передаётся на браузер как ответ.
- Переходим к пункту 2. Запись данных по тому же сценарию.
Разработка ведётся в двух местах:
- Разработка интерфейса и прочего взаимодействия с пользователем (JavaScript).
- Разработка БД и реализация бизнес-логики (один из SQL).
Как пользоваться
- В папке с программой необходимо создать папку (например
pages
) с веб-страничками (*.htm
,*.css
,*.js
), картинками и другими медиафайлами. - Заполнить файл настроек веб-сервера, файл
config.yml
. - Заполнить файл-описание команд API
command_parameters.json
. Соответствующие хранимки должны быть в базе данных. - Запустить приложение.
Ниже всё расписано более подробно.
Файл настроек
Рассмотрим содержимое файла настроек:
WebPublic:
DomainName: lvh.me
HttpPort: 80
HttpIp: localhost
Https:
Enabled: false
Port: 443
Ip: localhost
Provider: letsencrypt
SubscriberEmail: info@lvh.me
CertFolder: ssl
CertPemFileName: ssl/cert.pem
KeyPemFileName: ssl/key.pem
WebPagesFolder: pages
JsSettingsPath: /js/_settings.js
CommandPathPrefix: /cmd/
ParametersCountLimit: 30
CommandParametersFileName: command_parameters.json
OAuthVerificationCodePath: /cmd/oauth_verification_code
OAuthYandex:
ClientId: aaa
ClientSecret: bbb
OAuthGoogle:
ClientId: ccc
ClientSecret: ddd
OAuthGitHub:
ClientId: eee
ClientSecret: fff
DbConnectionString: sqlserver://user:password@localhost?database=test&connection+timeout=30
Подробнее о каждом параметре.
DomainName
Адрес приложения снаружи или имя сайта. Нужен для генерации ссылок, создании сертификата SSL и проч. Если публичный (внешний) порт отличается от стандартного, указываем здесь. Например lvh.me:5000
. Протокол подставит программа. Если SSL используется — https://
, иначе http://
.
HttpPort
Указывает какой порт слушать. Из-за особенностей хостинга может не совпадать с публичным.
HttpIp
Указывает IP для прослушивания. Из-за хостинга может потребоваться указать что-то отличное от localhost
.
Https
Веб-сервер поддерживает работу с SSL. Включается параметром Enabled
. Указываем также IP
и Port
для прослушивания. Есть два режима работы: letsencrypt
— если будем использовать автопродляемый бесплатный сертификат от Let’s Encrypt и custom
— если сертификат покупной.
В первом случае указываем почту для обратной связи SubscriberEmail
и папку CertFolder
, в которую будут записаны сертификаты и закрытый ключ.
Во втором случае указываем пути файлов — сертификата CertPemFileName
и закрытого ключа KeyPemFileName
в формате PEM. Если у Вас другой формат — разделите их по файлам в текстовом редакторе.
WebPagesFolder
Имя папки с файлами веб-страниц и ресурсами. Файлы из этой папки будут доступны снаружи по тем же адресам, что и внутри папки. Кроме файла index.html
. Например: файл, который лежит в папке по адресу \img\aaa.png
снаружи будет доступен по адресу https://domain.name/img/aaa.png
.
JsSettingsPath
Это относительный от доменного имени путь (снаружи) до генерируемого во время запуска веб-сервера JS-файла с полезными константами для формирования веб-страниц на клиенте. Пример содеримого данного файла:
const THIS_APP_URL = "http://lvh.me:9000";
const OAUTH_YANDEX_URL = "https://oauth.yandex.ru/authorize?client_id=xxx&response_type=code&state=yandex%token";
const OAUTH_GOOGLE_URL = "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&client_id=yyy.apps.googleusercontent.com&redirect_uri=...";
const OAUTH_GITHUB_URL = "https://github.com/login/oauth/authorize?client_id=zzz...";
THIS_APP_URL
— это собственный адрес.OAUTH_YANDEX_URL
,OAUTH_GOOGLE_URL
,OAUTH_GITHUB_URL
— это ссылки на страницы авторизации соответствующего сервиса авторизации.
CommandPathPrefix
Все адреса API-команд базы данных форматированы так:
Доменное имя сайта + префикс команды + имя команды + "?" + GET-параметры (если вызов GET-методом). Пример: http://lvh.me:9000/cmd/entity.category_list?p=11
ParametersCountLimit
Максимальное количество параметров в API-команде, для предварительной проверки.
CommandParametersFileName
Имя файла с описаниями API-команд. См. ниже.
OAuthVerificationCodePath
URL команды верификации OAuth. Это адрес для Callback-вызовов серверов OAuth. В личных кабинетах следует указывать адрес сайта + это значение. Из примера выше: http://lvh.me/cmd/oauth_verification_code
.
OAuthYandex, OAuthGoogle, OAuthGitHub
Настройки OAuth для авторизации на соответствующем сервисе авторизации. Содержат по две настройки:
client_id
— идентификатор клиента, полученный при регистрации на соответстувющем сервисе.client_secret
— это пароль или секрет, также выданный при регистрации.
DbConnectionString
Строка подключения к базе данных.
Файл описания API-команд
Для валидации команды перед вызовом используется файл-описание. В базе данных должны быть реализованы хранимые процедуры в точном соответствии с файлом описания. Все поля обязательны, если не указано иное. Пример:
[
{
"cmd_name": "#USER_REGISTER#",
"db_proc_name": "Entity.UserRegister",
"call_method": "ORMLESS",
"description": "Запись пользователя, прошедшего регистрацию через сервис OAuth",
"parameters": [
{ "name" : "user_name", "type" : "string", "description": "Веб сервер передаёт здесь ФИО пользователя или ник" }
,{ "name" : "user_email", "type" : "string", "description": "Веб сервер передаёт здесь адрес электронной почты" }
,{ "name" : "ext_id", "type" : "string", "description": "Внешний идентификатор в сервисе авторизации OAuth" }
,{ "name" : "oauth_service_name", "type" : "string", "description": "Наименование сервиса авторизации" }
]
}
,{
"cmd_name": "entity.category_list",
"db_proc_name": "Entity.CategoryList",
"call_method": "GET",
"description": "Процедура возвращает список элементов дерева категорий",
"parameters": [
{ "name" : "p", "type" : "int", "default" : "0", "description": "Идентификатор родительского элемента. Если 0 - то нужно вернуть элементы верхнего уровня." }
]
}
]
cmd_name
Все команды имеют имя, как составная часть адреса команды. Есть также команды для внутреннего пользования, ведь веб-сервер Ormless тоже использует БД. Вот названия необходимых ему команд:
#USER_REGISTER#
— запись пользователя при успешной авторизации через сервис OAuth. Вызвать снаружи эту команду нельзя.
db_proc_name
Название хранимки в базе данных.
call_method
Метод вызова данной команды:
GET
— вызов снаружи HTTP-методом GETPOST
— вызов снаружи HTTP-методом POSTORMLESS
— при внутреннем вызове веб-сервера
description
Текстовое описание процедуры, как элемент документации.
parameters
Массив параметров процедуры. Каждый элемент содержит:
name
— имя параметра хранимой процедуры.type
— тип значения параметра.default
— значение по умолчанию. Указывать необязательно. Если параметр не передан, то будет использоваться указанное значение. Если параметр не передан и это значение не указано — веб-сервер вернёт ошибку до вызова хранимой процедуры.description
— описание параметра, как элемент документации.
Параметры могут быть следующих типов:
bool
int
uint
float
string
Диапазоны значений всех числовых параметров соответствуют размеру переменной в 64 бит.
Ограничения
- В программе использован драйвер для БД Microsoft SQL Server. При использовании другой БД программу необходимо перекомпилиовать с соответствующим драйвером.
Возможные улучшения
- Скомпилировать приложение с драйверами всех баз, или с одним из универсальных драйверов и вынести это в настройку.
- Возможно имеет смысл давать назначать для API-команд не только префикс, а URL целиком.
- Возможно нужно добавить тип переменных date.
- Добавить логирование по ошибкам, чтобы потом статистику в админку выводить. Может быть стоит хранить в БД это.