REBOL

Учебник REBOL BBS - Доска объявлений CGI Web

Как создать простую электронную доску объявлений (доску сообщений) как программу REBOL CGI.

Авторы Carl Sassenrath, Gregg Irwin
Дата: 1-фев-2010
перевод: pochinok@bk.ru

Contents:

1. Вступление
1.1 Обзор функций RBBS
1.2 Что не включено
1.3 Связанные учебные статьи
1.4 Как это попробовать
1.5 Заявление о лицензии
2. Общий дизайн
3. Конфигурация
4. HTML шаблоны
4.1 Основной HTML-шаблон
5. Функции поддержки и инициализация
5.1 Тестовый режим
5.2 Установка базы двоичных чисел
5.3 Буфер вывода HTML
5.4 Функция гиперссылки HREF
5.5 Кодирование HTML
5.6 Форматирование даты
5.7 Чтение ввода CGI
5.8 Извлечение содержимого тела HTML
5.9 Вывод веб-страницы
5.10 Вывод ошибок
5.11 Удаление нежелательных HTML-тегов
5.12 Вывод веб-формы
6. Функции темы
6.1 Создание номера идентификатора темы
6.2 Загрузка и сохранение тем
6.3 Добавление новой темы
6.4 Поиск темы в базе данных
6.5 Обновление базы данных тем
6.6 Ссылка на тему
6.7 Создание страницы сводки по теме
7. Функции сообщений
7.1 Загрузка и сохранение сообщений
7.2 Добавление нового сообщения
7.3 Очистка старых сообщений
7.4 Скрытие адресов электронной почты
7.5 Создание списка сообщений
7.6 Создание страницы вывода сообщений
8. Основная программа
8.1 Чтение запроса CGI
8.2 Фильтр HTML-тегов
8.3 Преобразование полей CGI
8.4 Обработать команду CGI
9. Вывод

1. Вступление

Основная цель этого руководства - дать вам лучшее представление о том, как писать более содержательные типы программ с помощью REBOL. Многие методы и шаблоны программирования, описанные в этом руководстве, полезны для написания других типов программ на REBOL. Например, метод, который мы используем для HTML-шаблонов и веб-форм, обычно используется в большинстве программ REBOL CGI. Это также верно в отношении общего способа обработки CGI, а также способа хранения данных в файлах данных REBOL. Мы также опишем удобные методы управления небольшими наборами данных, удобные способы указать конфигурацию программы и то, как протестировать программу, запустив ее локально с помощью REBOL, а не загружая её каждый раз.

1.1 Обзор функций RBBS

Вот список основных функций, предоставляемых этим учебником BBS:

  • Реализует открытую систему электронных досок объявлений.
  • Работает как обычный сценарий CGI с использованием REBOL/Core 2.5.6.
  • На странице сводки отображаются текущие темы (цепочки) сообщений и связанная информация
  • Страницы сообщений показывают последние сообщения по заданной теме
  • Новые темы сообщений создаются из веб-формы
  • Новые сообщения добавляются в тему из веб-формы
  • Сохраняет сообщения по темам на диске
  • Автоматически удаляет (очищает) старые сообщения
  • Использует легко редактируемые файлы и формы HTML-шаблонов.
  • Легко настраивать и настраивать

1.2 Что не включено

Мы не хотим, чтобы это руководство получилось слишком большим, поэтому мы решили пока не включать некоторые функции. Некоторые из них могут быть добавлены как часть будущего руководства по CGI.

  • Учетные записи пользователей (включая регистрацию пользователей)
  • Файлы cookie браузера
  • Необычное форматирование сообщений
  • Поиск сообщений
  • Методы блокировки файлов

1.3 Связанные учебные статьи

Если вы не читали «Быстрый и легкий CGI - учебник и руководство для начинающих» и «Создание и обработка веб-форм с помощью CGI», мы предлагаем вам сначала прочитать их; хотя бы просмотреть их. Некоторые из используемых здесь функций подробно описаны в этих статьях.

1.4 Как это попробовать

Конечно, весь смысл этого руководства - дать вам хорошую отправную точку и хорошее понимание того, как создать свою собственную систему досок объявлений в REBOL. Так что вы, несомненно, захотите попробовать это.

Если вы хотите увидеть, как это выглядит в целом, вы можете опробовать тестовую версию BBS на веб-сайте REBOL.net:

Тестовая страница RBBS

Чтобы получить исходный код и установить его на свой веб-сервер, выполните следующие действия.

  1. Загрузите исходный код RBBS вы можете получить его на нашем тестовом сайте RBBS или на REBOL.org.
  2. Загрузите последнюю версию REBOL/Core и перенесите её в каталог CGI вашего веб-сервера.
  3. Следуйте инструкциям в Quick and Easy CGI* Учебник и руководство для начинающих по настройке REBOL для написания сценариев CGI. Если у вас нет необычной конфигурации веб-сервера, это займет всего несколько минут.
  4. Протестируйте BBS с помощью веб-браузера, чтобы перейти в нужное место на вашем веб-сервере.

1.5 Заявление о лицензии

Вы можете свободно использовать и распространять это программное обеспечение для любых законных целей, если вы указали авторские права, информацию об авторах и примечание ниже. Это лицензия BSD.

Это программное обеспечение предоставляется правообладателями и участниками «как есть», и любые явные или подразумеваемые гарантии, включая, но не ограничиваясь, подразумеваемые гарантии товарного состояния и пригодности для определенной цели, не признаются. Ни при каких обстоятельствах владелец авторских прав или участники не несут ответственности за любые прямые, косвенные, случайные, особые, примерные или косвенные убытки (включая, помимо прочего, приобретение заменяющих товаров или услуг; потерю возможности использования, данных или прибыли; или прерывание бизнеса), независимо от того, вызваны ли они и по любой теории ответственности, будь то по контракту, строгой ответственности или деликта (включая халатность или иным образом), возникающим каким-либо образом в результате использования этого программного обеспечения, даже если было сообщено о возможности такого ущерба.

2. Общий дизайн

Прежде чем мы начнем описывать детали программы RBBS, было бы неплохо дать вам обзор ее конструкции.

На схеме ниже показаны основные компоненты конструкции.

Описание схемы:

  • Файл HTML-шаблона (HTML Template) слева используется для придания программе RBBS общего внешнего веб-вида и предоставляет таблицы стилей.
  • Файл шаблона формы (Form Template) слева предоставляет веб-форму для ввода новых сообщений. Преимущество хранения их в виде отдельных файлов заключается в том, что их можно редактировать с помощью любого инструмента редактирования HTML-страниц.
  • Сценарий RBBS (RBBS Script) в центре - это сценарий CGI, который запускается вашим веб-сервером при указании определенного URL-адреса.
  • Файл конфигурации (Config) - это небольшой файл конфигурации, который вы можете использовать для настройки своей копии RBBS. Это также позволяет запускать более одной копии RBBS на одном сервере.
  • В "базе данных" тем (Topic "Database") справа хранятся актуальные темы сообщений (цепочки).
  • «Архив сообщений» (Message Archive)- это набор файлов (по одному для каждой темы), в которых хранятся фактические сообщения BBS.

Эти последние два элемента представляют собой файлы данных REBOL, созданные самой программой RBBS. Если вы изучите любой из этих файлов, вы заметите, что они имеют формат REBOL. Мы опишем структуру этих файлов в следующих разделах.

3. Конфигурация

Мы хотим, чтобы конфигурация оставалась простой и легкой в настройке. Для этого существует файл конфигурации (так что вам не нужно возиться с каким-либо кодом, чтобы подогнать его под вашу серверную среду) с настройками по умолчанию, которые должны работать "из коробки" для большинства людей.

Файл конфигурации также позволяет легко запускать более одной копии RBBS с одного и того же сервера. Вы можете запустить любое количество досок объявлений.

Параметры конфигурации живут в собственном «контексте». В этом случае контекст - это объект, который дает нам простой способ ссылаться на вещи в пространстве имен конфигурации.

Файл конфигурации на самом деле представляет собой сценарий REBOL, который будет выполняться, поэтому вы можете включить и другой код (при необходимости). Например, под объектом конфигурации вы увидите код, который создает каталог сообщений, если он еще не существует.

config: context [

    title: "Simple REBOL Message Board"
    cgi-path: %/cgi-bin/rbbs.r

    base-dir:  %rbbs/
    topic-id: join base-dir %id.r
    topic-db: join base-dir %topics.db
    msg-dir:  join base-dir %messages/

    html-template: join base-dir %template.html
    html-form: join base-dir %form.html

    max-days: 60  ; delete msgs older than this if...
    max-msgs: 100 ; max messages is reached.

    msg-order: none ; or 'new-first for reverse order

    tags-allowed: [<b> <i> <p> <br> <pre> <blockquote> <a> <font>]

]

if not exists? config/msg-dir [make-dir config/msg-dir]

Вот сводка полей конфигурации, перечисленных выше:

Поле

Тип данных

Описание

title

string!

Текст, который будет отображаться как заголовок для доски объявлений.

cgi-path

file!

Путь URL-адреса на вашем веб-сервере к скрипту доски объявлений.

base-dir

file!

Путь к файлам topic-id, topic-db и msg-dir относительно указанного выше пути cgi.

topic-id

file!

Имя файла счетчика идентификаторов темы (цепочки) сообщения.

topic-db

file!

Имя файла базы данных тем сообщения (на самом деле просто список).

msg-dir

file!

Имя каталога, в котором будут храниться сообщения, относительно базового каталога.

html-template

file!

Имя файла основного шаблона HTML, используемого для создания внешнего вида всех страниц. Также включает определения стилей HTML. Вы можете отредактировать этот файл в редакторе HTML (или текстовом редакторе), чтобы получить желаемый вид.

html-form

file!

Имя файла HTML-формы, используемой для ввода данных сообщений. Этот файл можно изменить, чтобы форма выглядела так, как вы хотите.

max-days

integer!

Количество дней, в течение которых сообщение может быть удалено из системы.

max-msgs

integer!

Когда количество сообщений в теме достигает этого предела, старые сообщения (старше max-days) будут удалены из темы.

msg-order

word! or none!

В каком порядке должны отображаться сообщения. Используйте none для хронологического порядка или new-first для обратного хронологического порядка.

tags-allowed

block!

Это блок тегов, разрешенных в сообщениях. Любые другие теги будут удалены в качестве меры безопасности.

В msg-dir файл будет создан для каждой темы с именем файла, являющимся идентификатором темы topic-id (целым числом):

messages/       ; msg-dir из конфигурации
    1           ; сообщения по теме 1
    2           ; сообщения по теме 2
    (etc.)

4. HTML шаблоны

В этом примере шаблоны HTML очень просты, но вы можете создать свои собственные, чтобы они соответствовали внешнему виду вашего сайта. Важными элементами HTML являются несколько специальных слов, которые заменяются содержимым, заполняющим шаблон при запуске сценария CGI.

Вот ключевые слова, которые заменяются в шаблоне:

$date
$content
$title
$version

Если эти слова конфликтуют с другими элементами на вашей странице, вы можете изменить код, чтобы использовать другие теги. Эти теги используются только в функции show-page, поэтому их легко изменить.

4.1 Основной HTML-шаблон

Вот шаблон HTML, используемый для создания всех страниц BBS. Измените его как хотите. Вы даже можете использовать программу редактирования веб-страниц, если вы сохраняете ключевые слова в шаблоне.

<!-- Page generated by REBOL -->
<html>
<head>
    <title>$title</title>
    <meta http-equiv="content-type" content="text/html;CHARSET=iso-8859-1">
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="expires" content="-1"> 
    <style type="text/css">
body, p, td {font-family: arial, sans-serif, helvetica; font-size: 10pt;}
h1 {font-size: 14pt;}
h2 {font-size: 12pt; color: #2030a0; width: 100%;
    border-bottom: 1px solid #c09060;}
h3 {font-size: 10pt; color: #2030a0;}
tt {font-family: "courier new", monospace, courier; font-size: 9pt;}
pre {font: bold 10pt "courier new", monospace, console;
    background-color: #e0e0e0; padding: 16px; border: solid #a0a0a0 1px;}
.title {Font-Size: 16 pt; Font-Weight: bold;}
</style>
</head>
<body bgcolor="white">
<center>
<table width="660" cellpadding="4" cellspacing="0" border="0">
<tr>
<td><a href="http://www.rebol.net"><img
src="http://www.rebol.net/graphics/reb-bare.jpg"
border=0 alt="REBOL"></a></td>
</tr>
<tr height="10"><td></td></tr>
<tr><td>
<h1>$title</h1>
<p>$content<p>
</td></tr>
<tr><td><img src="http://www.rebol.net/graphics/reb-tail.jpg"
border=0></td></tr>
<tr>
<td><p align=center><font color="#808080">
Message Board Tutorial $version
<a href="http://www.rebol.net/cgi-bin/rbbs.r?cmd=source"
>[Source]</a> - </font>
<a href="http://www.rebol.com"><font color="#808080">REBOL.COM</font></a>
</p></td>
</tr>
</table>
</center>
</body>
</html>

HTML-форма для ввода данных

Форма для ввода новых сообщений очень проста и не содержит заменяемых тегов. Это хорошо сформированный HTML-файл, поэтому вы можете редактировать его с помощью любого редактора веб-страниц. Но мы будем использовать тело как компонент на других страницах.

<html>
<body>
<table border="0" cellpadding="2" cellspacing="1" width="100%">
    <tr>
        <td width="87">
        <p align="right"><b>Subject:</b>
        </td>
<td><input type="text" name="subject" size="46"></td>
</tr>
<tr>
<td width="87">
<p align="right"><b>Name:</b>
</td>
<td><input type="text" name="name" size="46"></td>
</tr>
<tr>
<td width="87">
<p align="right"><b>Email:</b>
</td>
<td><input type="text" name="email" size="46"></td>
</tr>
<tr>
<td width="87" valign="top">
<p align="right"><b><br>Message:</b>
</td>
<td><textarea name="message" rows="8" cols="58"></textarea></td>
</tr>
<tr>
<td width="87">&nbsp;</td>
<td><input type="submit" name="submit" value="submit"></td>
</tr>
</table>
</body>
</html>

5. Функции поддержки и инициализация

Есть несколько глобальных функций поддержки и этапов инициализации, некоторые из которых не нуждаются в пояснениях. Но мы сделаем некоторые комментарии по поводу других; в конце концов, это учебник.

5.1 Тестовый режим

В целях тестирования мы часто запускаем сценарий локально с помощью REBOL/View, чтобы убедиться, что он работает правильно. Поскольку он запускается локально, нормальной среды CGI не существует. Чтобы определить это, мы используем этот тест:

; If not in CGI environment, set Test-Mode.
test-mode: not system/options/cgi/request-method

В тестовом режиме вывод записывается в файл и просматривается локально, потому что нет браузера, ожидающего его получения.

5.2 Установка базы двоичных чисел

Параметр system/options/binary-base устанавливает систему счисления для двоичных значений, используемых скриптом.

system/options/binary-base: 64

Это даёт простой способ позволить REBOL поработать за нас на протяжении остальной части скрипта. Вы можете увидеть эффект от этого, установив для него разные значения в консоли и посмотрев на результаты. Например:

>> system/options/binary-base
== 16

>> to binary! "Testing, 1...2...3"
== #{54657374696E672C20312E2E2E322E2E2E33}

>> system/options/binary-base: 64
== 64

>> to binary! "Testing, 1...2...3"
== 64#{VGVzdGluZywgMS4uLjIuLi4z}

>> system/options/binary-base: 2
== 2

>> to binary! "Testing, 1...2...3"
== 2#{
0101010001100101011100110111010001101001011011100110011100101100
00100000001100010010111000101110001011100011001000101110001...

См. Страницу часто задаваемых вопросов REBOL для получения более подробной информации, но мы используем базу 64 в этом скрипте.

5.3 Буфер вывода HTML

В этом скрипте будет много использоваться функция emit. Это функция, которая добавляет данные в буфер, который мы в конечном итоге вернем браузеру.

Сам буфер - это просто глобальная строка. Прежде чем вы начнете кричать об использовании глобального для чего-то вроде этого, поймите, что сценарии REBOL часто очень маленькие и разработаны с учетом простоты. Каждая мелочь, которую вы можете упростить, помогает. Чем сложнее становится сценарий, тем больше вам нужно таких инструментов, как инкапсуляция (например, объекты); сложность порождает сложность.

Код вывода HTML очень прост:

html: make string! 5000
emit: func [data] [append repend html data newline]

См. Другие руководства по CGI для полного описания этого метода.

5.4 Функция гиперссылки HREF

Функция href - это очень простая функция-оболочка, которая упрощает создание тегов href. Если вы много работаете с HTML или XML, вам нужно знать build-tag; и если вам не нравится, как это работает, вы можете настроить его. Это мезонинная функция, поэтому вы можете изменять ее исходный код.

href: func [data] [build-tag [a href (reduce data)]]

5.5 Кодирование HTML

Функция encode-html не требует пояснений. В этом руководстве он используется только для кодирования исходного кода для отображения в веб-браузере. Он не используется для самих сообщений.

Единственное, на что следует обратить внимание, если вы новичок в REBOL, - это то, как здесь используется foreach. Несколько слов устанавливаются на каждом проходе, помещая их в блок; значения в серии используются в «группах», которые соответствуют количеству используемых слов (в данном случае два).

encode-html: func [
    "Make HTML tags into HTML viewable escapes (for posting code)"
    text
][
    foreach [from to] ["&" "&amp;"  "<" "&lt;"  ">" "&gt;"] [
        replace/all text from to
    ]
]

Если вам это непонятно, попробуйте следующее в сеансе консоли REBOL.

foreach [from to] ["&" "&amp;"  "<" "&lt;"  ">" "&gt;"] [
    print [from to]
]

5.6 Форматирование даты

Функция nice-date также должна быть понятной благодаря встроенной строке справки в спецификации функции.

nice-date: func [
    "Convert date/time to a friendly format."
    date [date!]
    /local n day time diff
][
    n: now
    time: date/time
    diff: n/date - date/date
    if not day: any [
        if diff < 2 [
            time: difference n date
            time/3: 0
            return reform [time "hrs ago"]
        ]
        if diff < 7 [pick system/locale/days date/weekday]
    ][
        day: form date/date
        if n/date/year = date/date/year [clear find/last day #"-"]
    ]
    join day [<br> time " ET"]
]

Эта функция широко использует усовершенствования, что является важной концепцией REBOL. Если вы использовали другой объектно-ориентированный язык, возможно, вы знакомы с «точечной нотацией», которая обычно используется для доступа к свойствам объекта; уточнения обеспечивают ту же функцию для объектов в REBOL, но они также используются для других собственных типов данных REBOL (например, значений даты "data!"), которые имеют "свойства", к которым вы можете получить доступ.

В nice-date есть параметр с именем date, который является date!. Data! значения в REBOL могут содержать как дату, так и время; чтобы получить только компонент даты, используйте для них уточнение /date. Поскольку параметр в этом примере называется date, вы получаете date/date, чтобы получить только часть даты аргумента.

Уточнения также работают с блоками, и во многих случаях вы можете указать индекс или слово; например time/3 выше работает так же, как time/second, поскольку секунды являются третьим элементом во значения time!. Уточнения могут применяться последовательно (date/date/year) для доступа к подэлементам; не имеет значения, являются ли эти элементы собственными значениями, значениями серий или объектами. Стандартное обозначение пути REBOL прозрачно работает для всех типов данных.

Уточнения также используются в спецификациях функций для определения дополнительных переключателей или параметров, и, как будто этого было недостаточно, уточнения сами по себе являются типом данных; это значения, с которыми вы можете работать, точно так же, как word! (слово!), string! (строка!), tag! (тег!), и другие значения.

5.7 Чтение ввода CGI

Функция read-cgi удобна, поскольку позволяет остальной части кода не знать, использовался ли метод CGI GET или CGI POST.

read-cgi: func [
    "Read CGI data. Return data as string or NONE."
    /limit size "Limit to this number of bytes"
    /local data buffer
][
    if none? limit [size: 300000]
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
                if (length? data) > size [
                    print ["aborted - posting is too long:"
                        length? data "limit:" size]
                    quit
                ]
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

Обратите внимание, что эта функция также позволяет ограничивать размер входящих данных. Это можно использовать для предотвращения отправки сообщения размером 10 МБ и т.д..

5.8 Извлечение содержимого тела HTML

Функция read-body используется для извлечения тела любого файла HTML, чтобы его можно было встроить в другие страницы. В RBBS он используется для формы ввода сообщения.

read-body: func [
    "Extract the body contents of an HTML file."
    html [file!]
][
    html: read html
    remove/part html find/tail find html "<BODY" ">"
    clear find html </BODY>
    html
]

5.9 Вывод веб-страницы

Функция show-page объединяет данные, сгенерированные различными командами, с файлом шаблона HTML и возвращает результат браузеру. Он даже поддерживает тестовый режим для облегчения тестирования во время разработки. В тестовом режиме он записывает данные во временный файл и открывает браузер для просмотра этого файла. (REBOL/View требуется на вашем локальном компьютере, чтобы это работало.)

show-page: func [
    "Merge template with title and contents, and output it."
    title    ; page title
    content  ; page contents
    /local template
][
    template: read config/html-template
    replace/all template "$title" title
    replace/all template "$date" now/date
    replace/all template "$version" system/script/header/version
    replace template "$content" content
    either test-mode [
        write %temp-page.html template
        browse %temp-page.html
        halt
    ][
        print template
        quit
    ]
]

5.10 Вывод ошибок

Show-error - это просто небольшая обертка, которая возвращает слегка измененный результат, добавляя примечание о том, что что-то пошло не так. Он использует show-page для выполнения большей части работы.

show-error: func [
    "Tell user about an error."
    block "Block to be formed."
][
    show-page "An Error Occurred..." reform block
]

Такие небольшие функции-оболочки, как эта, помогают упростить код во многих случаях (посмотрите на такие функции, как remold ,form и другие, чтобы увидеть, сколько дизайнов REBOL включено, и как часто они используются другими).

5.11 Удаление нежелательных HTML-тегов

Filter-text - это функция, которая позволяет проходить основным тегам HTML, но удаляет другие. Это уменьшает возможность проникновения вредоносного кода, но все же позволяет людям до некоторой степени размечать свои сообщения. Он использует настройку конфигурации с разрешенными тегами, чтобы определить, какие теги могут проходить.

filter-tags: func [
    "Filter HTML to only allow specific tags."
    page [string!]
    /local block extended
][
    block: load/markup page
    extended: make block! length? block
    foreach tag config/tags-allowed [
        append extended append to-string tag " "
    ]
    remove-each item block [
        if tag? item [
            not any [
                find config/tags-allowed item
                all [ ; allow </tag>
                    item/1 = slash
                    find config/tags-allowed next item
                ]
                foreach tag extended [
                    if find/match item tag [break/return true]
                ]
            ]
        ]
    ]
    to-string block
]

5.12 Вывод веб-формы

Emit-form встраивает форму в вывод HTML для нас, включая небольшую логику, основанную на том, используется ли форма на главной странице доски объявлений, чтобы добавить новую тему, или на странице темы, чтобы добавить новое сообщение . Посмотрите на созданный HTML-код или откройте консоль, чтобы увидеть, что делает эта функция.

emit-form: func [
    "Emit the submission form (for both topics and messages)."
    topic-id [integer! none!] ; Use NONE to allow topic input
    /local text type
][
    text: read-body config/html-form
    type: 'topic
    if topic-id [
        ; Remove subject field from the form:
        remove/part find text <tr> find/tail text </tr>
        ; Add a hidden field for the topic id:
        append text build-tag [input type hidden name id value (topic-id)]
        type: 'msg
    ]
    emit [
        build-tag [form action (config/cgi-path) method post]
        build-tag [input type hidden name cmd value (type)]
        text
        </form>
    ]
]

6. Функции темы

Каждой теме дается уникальный идентификационный номер для ее идентификации. Фактические сообщения для темы хранятся в файле, названном с этим идентификационным номером. Главный файл themes.db содержит список тем в виде блока блоков. Каждый блок в этом файле имеет формат:

[topic id create-date modified-date msg-count]

Каждый раз, когда создаётся новая тема, она добавляется в файл тем. Каждый раз, когда добавляется сообщение, файл тем обновляется, чтобы показать новую дату изменения и счётчик сообщений.

6.1 Создание номера идентификатора темы

Когда добавляется новая тема, мы генерируем для неё уникальный идентификатор. Мы делаем это, просто увеличивая счётчик, хранящийся в файле.

next-topic-id: func [
    "Create next topic id #"
    /local n
][
    save %id.r n: 1 + any [attempt [load config/topic-id] 0]
    n
]

6.2 Загрузка и сохранение тем

Список тем - мини-база данных, если хотите - хранится в загружаемом формате REBOL, поэтому к нему легко получить доступ.

load-topics: does [any [attempt [load/all config/topic-db] []]]

Сохранить базу данных тем также легко, поскольку мы используем собственный формат данных REBOL.

save-topics: func [data] [write config/topic-db mold/only data]

6.3 Добавление новой темы

Чтобы добавить новую тему, нам нужна только новая строка темы, которая не обязательно должна быть уникальной (идентификатор, который мы сгенерировали выше, является нашим уникальным идентификатором). Блок информации для новой темы добавляется к файлу тем в виде новой строки данных.

Вновь сгенерированный идентификатор возвращается из функции добавления темы add-topic.

add-topic: func [
    {Add a new topic. Store it in topic file. Return id.}
    topic
][
    id: next-topic-id
    write/append config/topic-db
        append remold [topic id now now 0 ""] newline
    id
]

Важной функцией, используемой в надстройке, является remold, которая сочетает в себе встроенные функции сокращения и формования для упрощения использования. Что нужно знать о mold, так это то, что написано в справке: «Преобразует значение в строку, читаемую с помощью REBOL». Ключевые слова там REBOL-читабельны. form также преобразует значения в строки, но они дают разные результаты. Если вы не знакомы с ними, потратьте некоторое время на то, чтобы поиграть в консоли и посмотреть, как они работают и чем отличаются, учитывая разные типы данных.

6.4 Поиск темы в базе данных

Must-find-topic найдет тему (по идентификационному номеру) в базе данных тем или покажет ошибку в выводе, что тема не может быть найдена.

must-find-topic: func [
    "Return topic record or show an error"
    topic-id
][
    foreach topic load-topics [
        if topic/2 = topic-id [return topic]
    ]
    show-error "Invalid message topic. Contact the administrator."
]

6.5 Обновление базы данных тем

Update-topic обновляет информацию, хранящуюся в основном списке тем, когда по этой теме публикуется новое сообщение. Помните, запись темы выглядит так:

[topic id create-date modified-date msg-count]

В функции вы увидите обозначение пути, используемое для доступа к этим полям по номеру, чтобы обновить их. После обновления темы весь список тем сортируется (в обратном порядке по дате изменения) и сохраняется на диск.

update-topic: func [
    "Update message status for topic"
    topic-id
    count "number of messages"
    name "last message from"
    /local topics
][
    topics: load-topics
    foreach topic topics [
        if topic/2 = topic-id [
            topic/4: now
            topic/5: count
            if not topic/6 [append topic none]
            topic/6: name
            sort/reverse/compare topics 4
            save-topics topics
            exit
        ]
    ]
]

6.6 Ссылка на тему

Link-topic - небольшая, но мощная функция. Он использует информацию о конфигурации о том, где находится сценарий CGI, поэтому он может генерировать ссылку для его вызова; он также позволяет указать необязательную «закладку» с уточнением, а затем вызывает href для создания тега href для ссылки. Он используется в emit-themes и list-messages.

link-topic: func [
    "Create an HREF link to a message topic"
    topic-id
    /bookmark name
    /local path
][
    path: join config/cgi-path ["?cmd=msgs&id=" topic-id "&"]
    if bookmark [repend path [#"#" name]]
    href path
]

6.7 Создание страницы сводки по теме

Emit-themes генерирует таблицу HTML, помещая вывод в глобальный буфер HTML, который будет возвращен процессом CGI.

emit-topics: func [
    "Generate listing of all topics"
][
    emit {
        <table border=0 width="100%" cellpadding=4 cellspacing=1
            bgcolor=silver>
        <tr bgcolor=maroon>
        <td align=center><font color=white><b>Msgs</b></font></td>
        <td width=80%><font color=white><b>Topic</b></font></td>
        <td align=right nowrap><font color=white>
        <b>Last Posting</b>
        </font></td>
        <td><font color=white><b>From</b></font></td>
        </tr>
    }
    foreach topic load-topics [
        emit [
            <tr bgcolor=white>
            <td><p align=center> topic/5 </p></td>
            <td width=80%> link-topic topic/2 <b> topic/1 </b></a></td>
            <td align=right nowrap> nice-date topic/4 </td>
            <td> topic/6 </td>
            </tr>
        ]
    ]
    emit </table>
]

Стоит отметить, как тип данных tag! упрощает создание разметки; вам не нужно постоянно переходить к языковым строкам и обратно, просто введите теги, как обычно, и REBOL распознает их.

7. Функции сообщений

Есть много параллелей между функциями темы и сообщения.

Каждый файл сообщения хранится под идентификатором темы для него. Записи сообщений имеют формат:

[name email date message]

Сообщение хранится в двоичном формате, чтобы избежать любых возможных проблем, связанных с его разграничением как значением REBOL.

7.1 Загрузка и сохранение сообщений

Ещё раз, поскольку мы используем собственный формат REBOL для хранения данных, загрузить его в блок для использования скриптами очень просто. Об этих функциях особо нечего сказать, потому что они очень похожи на функции темы.

load-messages: func [
    "Load messages for a specific topic."
    topic-id
][
    any [attempt [load/all config/msg-dir/:topic-id] []]
]

save-messages: func [
    "Save messages for a specific topic."
    topic-id
    messages
][
    write config/msg-dir/:topic-id mold/only messages
]

7.2 Добавление нового сообщения

Новые сообщения добавляются простым добавлением их в конец файла сообщений для темы. Этот метод используется (write/append), поэтому нам не нужно читать все предыдущие сообщения, чтобы записать новое сообщение.

add-message: func [
    {Add a new message.}
    topic-id
    name
    email
    message
][
    write/append config/msg-dir/:topic-id append
        remold [name email now to-binary message] newline
]

Поскольку в этом руководстве не используются блокировки файлов при изменении файлов, возможно, что два отдельных пользователя, которые отправляют сообщения в одно и то же время, могут столкнуться при обновлении файла сообщения. Чтобы сделать это менее вероятным (но всё же есть небольшая вероятность), этот метод write/append полезен.

7.3 Очистка старых сообщений

Purge-messages - новая функция, не имеющая аналогов в наборе тематических функций. Он использует два значения конфигурации, чтобы определить, сколько сообщений требуется для запуска очистки и какой возраст сообщений должен быть для очистки.

purge-messages: func [
    "If message limit is exceeded, purge older messages."
    msgs
    /local today
][
    if (length? msgs) > config/max-msgs [
        today: now
        remove-each msg msgs [
            msg/3/date + confg/max-days < today
        ]
        save-messages topic-id msgs
    ]
]

Обратите внимание, как используются уточнения для получения даты сообщения (msg/3/date). Само сообщение является блоком, поэтому msg/3 получает третий элемент в блоке, который является значением data!. Поскольку мы хотим проверить только дату, а не время, добавлено уточнение /date.

Обратите внимание на скобки в следующей строке:

if (length? msgs) > config/max-msgs [

REBOL выполняет оценку слева направо, но инфиксные операторы имеют приоритет над вызовами функций, поэтому без скобок он будет оцениваться следующим образом:

if length? (msgs > config/max-msgs) [

что привело бы к ошибке при попытке сравнить блок с числом. Если мы всё перевернём, мы можем удалить скобки, потому что REBOL должен оценить функцию, чтобы получить второй операнд для оператора <.

if config/max-msgs < length? msgs [

Выберите подход, который, по вашему мнению, наиболее очевиден в таких ситуациях; не всегда есть единственный «правильный» способ делать что-то.

7.4 Скрытие адресов электронной почты

Obscure-email - это базовая функция, используемая для маскировки адресов электронной почты, чтобы предотвратить их легкое извлечение, но при этом позволить их отображать в браузере. Обратите внимание, что мы заменяем char! значение с значением tag!; функция replace поддерживает это и работает не только со строковыми значениями, но и с любыми значениями серий. replace - это также мезонинная функция, поэтому вы можете использовать для неё source, чтобы увидеть, как она работает.

obscure-email: func [
    "Make email more difficult for harvesters"
    email
][
    either any-string? email [replace email #"@" <br>][""]
]

7.5 Создание списка сообщений

Emit-messages параллельны emit-topics.

emit-messages: func [
    "Generate listing of messages"
    msgs "block of messages"
][
    emit {
        <table border=0 width="100%" cellpadding=3 cellspacing=1
            bgcolor=silver>
        <tr bgcolor=navy>
        <td><font color=white><b>Sender</b></font></td>
        <td width=80%><font color=white><b>Message</b></font></td>
        <td align=right nowrap><font color=white>
        <b>When Sent</b>
        </font></td>
        </tr>
    }
    foreach msg msgs [
        emit [
            <tr bgcolor=white>
                <td nowrap><b> msg/1 </b><br>
                    <i> obscure-email msg/2 <i>
                </td>
                <td width=80%> to-string msg/4 </td>
                <td align=right nowrap> nice-date msg/3 </td>
            </tr>
        ]
    ]
    emit </table>
]

7.6 Создание страницы вывода сообщений

list-messages формирует главную страницу сообщения для темы. Он включает несколько ссылок вверху, за которыми следует таблица всех сообщений.

Поскольку этот код должен обрабатывать все сообщения, он также использует возможность очистки любых старых сообщений. Это позволяет избежать повторной загрузки всех сообщений для операции очистки, что позволяет сэкономить время вычислений.

list-messages: func [
    "Emit message list with form. Return title."
    topic-id
    /update "Update message count"
    /local rec
][
    rec: must-find-topic topic-id
    emit [
        <b>
        href config/cgi-path "Return to Topics" </a> " | "
        href #end "Go to End" </a> " | "
        link-topic topic-id "Refresh" </a>
        </b><p>
    ]
    msgs: load-messages topic-id
    if all [update not empty? msgs] [
        purge-messages msgs
        update-topic topic-id length? msgs first last msgs
    ]
    if config/msg-order = 'new-first [msgs: head reverse msgs]
    emit-messages msgs
    emit [
        <p><b>
        href config/cgi-path "Return to Topics" </a> " | "
        link-topic/bookmark topic-id "end" "Refresh" </a>
        </b><p>
    ]
    emit {<h2 id=end>Add a Message:</h2>}
    emit-form topic-id
    reform ["Messages for:" rec/1]
]

8. Основная программа

Основная программа для доски сообщений отвечает за чтение запроса CGI (будь то GET или POST), его декодирование, фильтрация нежелательных данных, проверка обязательных полей (и возврат ошибок для отображения в случае сбоя), отправку команды и возврат команды.

8.1 Чтение запроса CGI

Прочтите CGI-запрос и преобразуйте его в стандартный объект. Запрос CGI мог быть передан с помощью GET или POST, вам действительно все равно. Этот метод обрабатывает оба случая.

Как только у нас есть запрос CGI во время, мы преобразуем его в блок REBOL, который затем передается в функцию построения, которая безопасно преобразует его в объект.

if not cgi: read-cgi [quit]
cgi: construct/with decode-cgi cgi context [
    cmd: id: name: email: subject: message: none
]

Никогда не используйте make object! или функцию context для преобразования блока CGI в объект. Эти функции позволяют оценивать код, что было бы небезопасно. Вместо этого используйте функцию construct. Она не оценивает свои поля.

8.2 Фильтр HTML-тегов

Отфильтруйте запрещенные теги HTML, отправленные в любое поле.

foreach word next first cgi [
    val: get in cgi word
    if string? val [set in cgi word filter-tags val]
]

8.3 Преобразование полей CGI

Преобразуйте поля CGI по мере необходимости. Например, команда CGI преобразуется из строки в слово. (Для небольших программ, подобных этому руководству, это действие на самом деле не требуется. Команда может оставаться строкой. Однако в целом слова более эффективны, чем строки.)

cgi/cmd: attempt [to-word cgi/cmd]
cgi/id: attempt [to-integer cgi/id]
if not email? cgi/email: attempt [load cgi/email] [cgi/email: none]

Функция check-fields, определенная ниже, будет использоваться для проверки того, что все обязательные поля были предоставлены в веб-форме.

check-fields: func [/subject][
    if all [subject empty? trim cgi/message] [
        show-error "Subject required"
    ]
    if empty? trim cgi/name [show-error "Name field required"]
    if empty? trim cgi/message [show-error "Message is required"]
]

8.4 Обработать команду CGI

Этот код обрабатывает команду CGI и показывает последнюю страницу вывода.

Команда CGI поступает из поля ввода в веб-форме или из самого URL-адреса ссылки. Вот это просто слово.

switch/default cgi/cmd [
    msgs [
        title: list-messages cgi/id
    ]
    msg [
        check-fields
        rec: must-find-topic cgi/id
        add-message cgi/id cgi/name cgi/email cgi/message
        title: list-messages/update cgi/id
    ]
    topic [
        check-fields/subject
        id: add-topic cgi/subject
        add-message id cgi/name cgi/email cgi/message
        title: list-messages/update id
    ]
    source [
        title: "REBOL Message Board Source"
        emit [
            <h2> "REBOL Code" </h2>
            <pre> detab encode-html read %test.r </pre>
            <h2> "HTML Form Code (form.html)" </h2>
            <pre> detab encode-html read %form.html </pre>
            <h2> "HTML Template Code (template.html)" </h2>
            <pre> detab encode-html read %template.html </pre>
        ]
    ]
][
    title: config/title
    emit-topics
    emit {<h2>Add a New Topic:</h2>}
    emit-form none
]

show-page title html

9. Вывод

Мы надеемся, что вы нашли это руководство полезным как пример более подробного веб-приложения CGI.

REBOL предоставляет элегантный способ создания широкого спектра приложений CGI. Скрипты, созданные с помощью REBOL, обычно более читабельны, что упрощает их поддержку и изменение на протяжении многих лет.

Но REBOL полезен не только для сценариев CGI. REBOL включает в себя множество других мощных функций, включая собственный графический пользовательский интерфейс. Вы обнаружите, что многие методы программирования, используемые в этом руководстве, помогут вам в написании других типов программ REBOL.

MakeDoc2 by REBOL - 27-Jul-2021