Sign in Join
Wiki copytemporary

fpWeb Tutorial/ru: what changed

Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11```text
22 Первоначально основано на учебнике fcl-web (в формате PDF) пользователя форума Leledumbo.
33```
44
55## Введение
66
77fpWeb - это фреймворк веб-приложений, поставляемый FPC в своем дистрибутиве по умолчанию как часть пакета fcl-web. Сам фреймворк построен на основе функций fcl-web. Фреймворк построен с мыслью о RAD, чтобы эффективно использовать компонентность при создании динамического контента. Предоставляется пакет Lazarus, который может использовать фреймворк методом перетаскивания для управления сеансами и создания контента. В этом руководстве мы попытаемся охватить основные функции fpWeb, чтобы с его помощью можно было создать обычное веб-приложение. Обратите внимание, что это руководство не пытается научить вас HTTP-протоколу, HTML, CSS, JavaScript или манипуляциям с базой данных, поскольку владение протоколом и клиентскими языками должно быть предварительным условием работы для каждого программиста веб-приложений, а манипуляции с базой данных не отличаются от десктопной реализации.
88
99## Архитектура (ПОЖАЛУЙСТА, прочтите)
1010
1111Перед тем, как начать, необходимо знать архитектуру и поток приложения, чтобы избежать путаницы, когда определенные вещи не работают или работают неожиданно. Поэтому, пожалуйста, потратьте немного времени на чтение этого раздела.
1212
1313### Приложение
1414
1515Под приложением здесь понимается протокол, который будет реализовывать ваше приложение. fpWeb с радостью переключится с CGI, FCGI, с модуля Apache на встроенный сервер и т.д., если fcl-web реализует еще что- нибудь в будущем. Каждое приложение реализовано в своем собственном модуле, поэтому для переключения с одного приложения на другое, за исключением модуля Apache, нужно просто изменить соответствующий идентификатор в разделе uses. В настоящее время (по состоянию на 3.0.0 / 3.1.1) это:
1616
1717- fpCGI -\> CGI
1818- fpFCGI -\> FastCGI
1919- fpApache (также требуется httpd) -\> модуль Apache
2020- fpHttpApp -\> встроенный сервер
2121- microhttpapp -\> встроенный сервер с использованием библиотеки GNU libmicrohttp.
2222- fphttpsys -\> поддержка системой Windows протокола HTTP.
2323
2424В этом руководстве мы будем использовать встроенный сервер для простоты, потому что вам не придется иметь дело с настройкой виртуального сервера и путаницей со сложным файлом конфигурации и управлением службами. Ваше приложение будет единым переносным бинарным веб-приложением! Другая причина может заключаться в том, что существует больше, чем одно приложение веб-сервера, и каждое имеет свой способ настройки. Было бы излишним пытаться охватить их всех, пока их документация уже выполняет свою работу. Модуль Apache реализован как (динамическая) библиотека, тогда как другие протоколы являются обычным приложением. Каждое приложение может иметь определенные свойства (например, порт), доступные и значимые только для этого приложения. Вот почему, если вы посмотрите на примеры fcl-web, пары .lpi / .lpr для каждого протокола помещаются в свои собственные каталоги, только веб-модули являются общими.
2525
2626### Веб-модули
2727
2828![](/wiki-images/5/50/FpWeb-overview.png)
2929
3030Приложения fpWeb состоят из веб-модулей, которые фактически создают контент. Веб-модуль может содержать веб-действия, которые могут еще больше разделить функциональность. Например, веб-модуль аутентификации может иметь веб-действия входа и выхода. Хотя модуль about web может вообще не нуждаться в действии и обслуживает только один контент. Веб-модуль интегрирован с fpTemplate, который можно использовать для создания динамического контента из файла шаблона. Это примерно похоже на то, что делает PHP, только разрыв между логикой и представлением скорее вынужденный, чем предполагаемый. Некоторые говорят, что fpTemplate реализует пассивное представление, в то время как PHP по умолчанию реализует шаблон проектирования активного представления.
3131
3232## Установка
3333
3434Пакет fpWeb для Lazarus по умолчанию не установлен (но идет в стандартной поставке). Чтобы включить fpWeb:
3535
36361. Откройте Lazarus и выберите **Package-\>Install/Uninstall Package**
37372. В списке Available (Доступно) для установки найдите **weblaz** и нажмите **Install selection** (Установить выбранное). Нажмите **Save and rebuild IDE**(Сохранить и пересобрать IDE) и подтвердите, нажав **Continue**(Продолжить).
38383. Позвольте IDE пересобраться и перезапуститься. Если все пойдет хорошо, у вас должна появиться вкладка fpWeb на палитре компонентов, как показано ниже:
3939
4040
4141
4242![](/wiki-images/5/55/Fpwebtutorial_inst.png)
4343
4444## Специализированные модули
4545
4646Класс*TFPWebModule* (используемый ниже) - это простой пример модуля fpWEB, который можно использовать для всех видов HTTP-запросов.
4747
4848Однако fpWEB поставляется с некоторыми специализированными модулями, которые имеют дополнительную поддержку для специализированных задач:
4949
5050- Класс **TSimpleFileModule** в модуле *fpwebfile.pp* можно использовать для отправки файлов. Вы указываете ему каталог, и он делает все остальное.
5151- Класс **TFPHTMLModule** в модуле *fphtml.pp* можно использовать для создания HTML.
5252- Класс **TProxyWebModule** в модуле *fpwebproxy.pp* - это готовый прокси для пересылки.
5353- Класс **TFPWebProviderDataModule** в модуле *fpwebdata.pp* обслуживает данные в формате JSON, которые могут использоваться хранилищами ExtJS.
5454- Класс **TSQLDBRestModule** в модуле *sqldbrestmodule.pp* реализует полноценный сервер REST, поддерживаемый SQLDB. См. дополнительную информацию в [[SQLDBRestBridge]].
5555- Класс **TJSONRPCModule** в модуле *webjsonrpc.pp* реализует службу JSON-RPC.
5656- Класс **TExtDirectModule** в модуле *fpextdirect.pp* реализует вариант Ext.Direct службы JSON-RPC.
5757
5858## Hello, World!
5959
6060Создадим простое веб-приложение. Как обычно преподают при изучении программирования: "Hello, World!" будет нашим первым приложением.\
6161
62621\. Откройте Lazarus и выберите **Project-\>New Project**, затем выберите **HTTP server Application**
6363
6464
6565![Создать новое приложение HTTP-сервера](/wiki-images/5/57/Fpwebtutorial_create_app.png)\
6666
67672\. Появится другое диалоговое окно для обслуживания статических файлов, выбора порта и многопоточности. Просто используйте порт по умолчанию 8080.
6868
6969
7070``` pascal
7171You may skip the static files serving (go to tips and tricks section if you want to know it more).
7272```
7373
7474(*Вы можете пропустить обслуживание статических файлов (перейдите в раздел советов и рекомендаций, если хотите узнать больше)*)
7575
7676
7777![](/wiki-images/e/e6/Fpwebtutorial_port_selection.PNG)
7878
7979:**ВАЖНО!:**
8080
8181
8282Если вы решите использовать потоки на *nix, не забудьте добавить cthreads в качестве первого модуля в разделе uses .lpr, в противном случае будет создан RTE 232. При запуске с консоли должно отображаться сообщение:
8383
8484``` pascal
8585This binary has no thread support compiled in. Recompile the application with a thread-driver in the program uses clause before other units using thread.
8686```
8787
8888(*В этом двоичном файле не скомпилирована поддержка потоков. Перекомпилируйте приложение с драйвером потока в программе using предложение перед другими модулями, использующими поток*)\
8989
90903\. С 14 января 2017г. (или FPC 3.0.4) вам может потребоваться открыть .lpr и добавить следующую строку в основной текст, если ее еще там нет:
9191
9292
9393``` pascal
9494Application.LegacyRouting := true;
9595```
9696
9797причина будет объяснена в главе [[fpWeb Tutorial/ru|Маршрутизация]].
9898
99994\. Что бы вы ни выбрали, нажмите "OK", и вы будете представлены в приложении fpWeb с одним модулем по умолчанию.\
1001005\. Переместите фокус на модуль и перейдите в **Object Inspector**. Не стесняйтесь переименовывать модуль, если хотите.\
1011016\. Выберите вкладку **Events** и нажмите кнопку справа от второго столбца в строке**OnRequest**, чтобы создать обработчик событий.
102102
103103
104104![Создание обработчика OnRequest веб-модуля в object inspector](/wiki-images/8/8d/Fpwebtutorial_on_request.png)\
105105
106106
107107Вы будете перенаправлены в редактор исходного кода со следующим кодом:
108108
109109:
110110
111111``` pascal
112112procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
113113AResponse: TResponse; var Handled: Boolean);
114114begin
115115 |
116116end;
117117```
118118
119119
120120Заполните событие:
121121
122122:
123123
124124``` pascal
125125procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
126126AResponse: TResponse; var Handled: Boolean);
127127begin
128128 AResponse.Content := 'Hello, World!';
129129 Handled := true;
130130end;
131131```
132132
1331337\. Теперь запустите приложение (или нажмите F9).\
1341348\. Откройте браузер и введите:
135135
136136
137137<http://localhost:8080/>\
138138
1391399\. Вы должны увидеть отображающееся "Hello, World!".\
140140
141141Если это не так, проверьте ниже:
142142
143143- Фреймворк выполняет много операций по обработке исключений, и отладчик IDE может их поймать и прервать работу вашего приложения. Можно добавить большинство исключений в список игнорирования, чтобы вы могли больше сосредоточиться на потоке приложения. Продолжайте пропускать и продолжайте, пока не перестанет появляться диалоговое окно и браузер не покажет результат.
144144
145145- **Handled := true** - это способ, которым мы сообщаем фреймворку, что запрос был обработан. Если вы не установите его (или установите для него значение **false**), вместо этого будет отображаться страница с ошибкой. На данный момент это не влияет на поток запросов, но будет позже. Так что держите это так, пока не придет время, чтобы использовать его с пользой.
146146
147147- другой трек: тест без брандмауэра, загруженного в ОЗУ (как приложение, как сервис или демон, или как оба).
148148
149149## Чтение данных GET и POST
150150
151151Динамический контент, скорее всего, будет запускаться из пользовательского ввода, либо через формы, предоставляя значения в URL-адресе, и т. Д. Эти данные отправляются вместе с запросом, который представлен в методе как **ARequest** параметр типа **TRequest**.
152152
153153### Чтение GET
154154
155155Данные GET предоставляются как **ARequest.QueryFields**, который является потомком **TStrings**. Короче говоря, все, что вы обычно делаете с TStrings, применимо здесь, например, доступ к данным в стиле карты через свойство **Values**.
156156
157157Повторно используя приведенный выше код, замените тело метода на:
158158
159159``` pascal
160160procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
161161 AResponse: TResponse; var Handled: Boolean);
162162var
163163 LName: String;
164164begin
165165 LName := ARequest.QueryFields.Values['Name'];
166166 if LName = EmptyStr then
167167 with AResponse.Contents do
168168 begin
169169 Add('<form action="' + ARequest.URI + '" method="GET"');
170170 Add('<label for="name">Please tell me your name:</label>');
171171 Add('<input type="text" name="name" id="name" />');
172172 Add('<input type="submit" value="Send" />');
173173 Add('</form>');
174174 end
175175 else
176176 AResponse.Content := 'Hello, ' + LName + '!';
177177 Handled := true;
178178end;
179179```
180180
181181**ARequest.URI** - это просто ссылка на текущий URI, поэтому даже когда вы меняете зарегистрированный модуль или имя действия, этот код остается прежним.
182182
183183Обратите внимание, что, как и в Паскале, обращение к данным осуществляется без учета регистра.
184184
185185Теперь вы можете попробовать запросить /, который отобразит
186186
187187```text
188188 Please tell me your name
189189```
190190
191191и /?name=\<напишите здесь что угодно, например: Bob\>, который отобразит
192192
193193```text
194194 Hello, Bob!
195195```
196196
197197### Чтение POST
198198
199199POST на самом деле не сильно отличается от GET, отличается только тем, к какому свойству получить доступ. Если доступ к GET осуществляется через **ARequest.QueryFields**, доступ к POST осуществляется через **ARequest.ContentFields**. Стиль POST предыдущего кода:
200200
201201``` pascal
202202procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
203203 AResponse: TResponse; var Handled: Boolean);
204204var
205205 LName: String;
206206begin
207207 LName := ARequest.ContentFields.Values['Name'];
208208 if LName = EmptyStr then
209209 with AResponse.Contents do
210210 begin
211211 Add('<form action="' + ARequest.URI + '" method="POST"');
212212 Add('<label for="name">Please tell me your name:</label>');
213213 Add('<input type="text" name="name" id="name" />');
214214 Add('<input type="submit" value="Send" />');
215215 Add('</form>');
216216 end
217217 else
218218 AResponse.Content := 'Hello, ' + LName + '!';
219219 Handled := true;
220220end;
221221```
222222
223223### Чтение загружаемых файлов
224224
225225Единственное исключение - чтение полей **multipart/form-data**, то есть файлов. Оно доступно в **ARequest.Files** как экземпляр **TUploadedFiles**, который является потомком **TCollection**. Ниже приведен общедоступный интерфейс TUploadedFiles, который вы можете использовать для доступа к файлам:
226226
227227``` pascal
228228TUploadedFiles = Class(TCollection)
229229...
230230public
231231 Function First: TUploadedFile;
232232 Function Last: TUploadedFile;
233233 Function IndexOfFile(AName: String) : Integer;
234234 Function FileByName(AName: String) : TUploadedFile;
235235 Function FindFile(AName: String) : TUploadedFile;
236236 Property Files[Index: Integer] : TUploadedFile read GetFile Write SetFile; default;
237237end;
238238```
239239
240240Каждый **TUploadedFile** сам по себе имеет несколько свойств:
241241
242242``` pascal
243243TUploadedFile = Class(TCollectionItem)
244244...
245245Public
246246 Destructor Destroy; override;
247247 Property FieldName: String Read FFieldName Write FFieldName;
248248 Property FileName: String Read FFileName Write FFileName;
249249 Property Stream: TStream Read GetStream;
250250 Property Size: Int64 Read FSize Write FSize;
251251 Property ContentType: String Read FContentType Write FContentType;
252252 Property Disposition: String Read FDisposition Write FDisposition;
253253 Property LocalFileName: String Read FLocalFileName Write FLocalFileName;
254254 Property Description: String Read FDescription Write FDescription;
255255end;
256256```
257257
258258Они должны быть достаточно информативными, за исключением **FileName** и **LocalFileName**. **FileName** - это **name** исходного файла, загружаемого с клиента, **LocalFileName** - это путь к файлу на сервере, где файл временно хранится. Обратите внимание на разницу, выделенную жирным шрифтом выше.
259259
260260Опять же, повторное использование того же обработчика запросов:
261261
262262``` pascal
263263procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
264264 AResponse: TResponse; var Handled: Boolean);
265265var
266266 n: Integer;
267267 f: TUploadedFile;
268268 i: Integer;
269269begin
270270 n := ARequest.Files.Count;
271271 if n = 0 then
272272 with AResponse.Contents do
273273 begin
274274 Add('<form id="form" action="' + ARequest.URI + '" method="POST" enctype="multipart/form-data">');
275275 Add('<label for="name">Drag n drop or click to add file:</label>');
276276 Add('<input type="file" name="input" />');
277277 Add('<input type="submit" value="Send" />');
278278 Add('</form>');
279279 end
280280 else
281281 begin
282282 f := ARequest.Files[0];
283283 AResponse.Contents.LoadFromStream(f.Stream);
284284 end;
285285 Handled := true;
286286end;
287287```
288288
289289перетащите и бросьте файл (желательно текстовый, так как он будет отображаться как текст) в поле входного файла (или нажмите соответствующую кнопку), затем нажмите кнопку *Send*' (Отправить). Должно отобразиться содержимое файла.
290290
291291## Cookies
292292
293293### Отправка
294294
295295![Концепция «cookie», изобретенная Netscape в 1994 году, позволяет HTTP-серверу идентифицировать всех своих клиентов.](/wiki-images/5/5c/Cookie_session_Object_Pascal.png)\
296296Файлы cookie - небольшой фрагмент данных, отправленный веб-сервером и хранимый на компьютере пользователя. Веб-клиент (обычно веб-браузер) всякий раз при попытке открыть страницу соответствующего сайта пересылает этот фрагмент данных веб-серверу в составе HTTP-запроса ([Wiki](https://ru.wikipedia.org/wiki/Cookie)). **AResponse.Cookies** содержит список отправляемых файлов cookie. Это потомок **TCollection**, соответственно содержащийся **TCookie** является потомком **TCollectionItem**. Следовательно, вы можете использовать TCollection как способ управления элементами для управления ими.
297297
298298Вот пример:
299299
300300``` pascal
301301procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
302302 AResponse: TResponse; var Handled: Boolean);
303303var
304304 C: TCookie;
305305begin
306306 C := AResponse.Cookies.Add;
307307 C.Name := 'mycookie';
308308 C.Value := 'somevalue';
309309 Handled := true;
310310end;
311311```
312312
313313Вы не увидите никаких результатов в своем браузере. Но если вы используете какие-то инструменты разработчика (в Chrome есть встроенный) можно увидеть заголовок ответа:
314314
315315![](/wiki-images/6/69/Fpwebtutorial_cookie_set.png)
316316
317317Обратите внимание, что файл cookie имеет атрибуты, поэтому вы можете установить не только имя и значение. Просмотрите интерфейс TCookie, чтобы узнать, какие свойства поддерживаются.
318318
319319### Получение
320320
321321Как только вы укажете выше заголовок **Set-Cookie**, последующий запрос на ваш сайт будет содержать дополнительный заголовок, содержащий значение, которое вы просили установить ранее:
322322
323323![](/wiki-images/9/93/Fpwebtutorial_cookie_get.png)
324324
325325К счастью, способ чтения не отличается от данных GET и POST. Связанное свойство - **ARequest.CookieFields**. Чтобы прочитать ранее отправленный файл cookie:
326326
327327``` pascal
328328procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
329329 AResponse: TResponse; var Handled: Boolean);
330330begin
331331 AResponse.Contents.Add('<p>Cookie get: ' + ARequest.CookieFields.Values['mycookie'] + '</p>');
332332 Handled := true;
333333end;
334334```
335335
336336## Сессии
337337
338338TFPWebModule является потомком TSessionHTTPModule, поэтому имеет возможность управления сессией. Сессия основана на модулях, поэтому каждый модуль может выбирать, использовать или не использовать управление сессией.
339339
340340Сессия реализована абстрактно. По умолчанию реализация не предусмотрена. Один пример реализации с использованием файлов .ini приведен в модуле **iniwebsession**. Вы должны иметь этот модуль в своем проекте или реализовать его, чтобы управление сессией работало. Если вы решите реализовать один из них, в основном вам необходимо расширить и реализовать абстрактные методы в классах **TCustomSession** и *'TSessionFactory'*.
341341
342342### Активация
343343
344344Чтобы активировать управление сессией, установите для свойства **CreateSession** в значение true. Сессия будет запущена до обработки запроса. В случае новой сессией будет вызвано **OnNewSession**. Здесь инициализируйте переменные сессии.
345345
346346### Управление переменными сессии
347347
348348Переменные сессии представлены как **Session.Variables** (примечание: объект сессии является эквивалентом массива \$ _SESSION, используемого в Php). Это строка для сопоставления строк, подобная структуре, поэтому вы можете читать / писать так:
349349
350350``` pascal
351351Session.Variables['myvar'] := myvar; // записываем
352352...
353353myvar := Session.Variables['myvar']; // читаем
354354```
355355
356356Задание переменной пустой строки **НЕ** удаляет ее. Если вы действительно хотите удалить переменную, вместо этого вызовите **Session.RemoveVariable**.
357357
358358### Завершение
359359
360360Вызывайте **Session.Terminate** всякий раз, когда вы хотите завершить сессию (например, выход пользователя из системы). Сессия также автоматически истечет, если следующий запрос поступит после **Session.TimeOutMinutes** с момента последнего запроса. Когда сессия завершается, будет вызван **OnSessionExpired**. Сделайте там все, что вам нужно.
361361
362362## Маршрутизация
363363
364364Начиная с FPC 3.0.4, был реализован новый механизм маршрутизации. Вместо сохранения обратной совместимости решено, что по умолчанию будет использоваться новая маршрутизация. Таким образом, любой старый код (или новый код в зависимости от старой маршрутизации) необходимо перенести, добавив:
365365
366366``` pascal
367367Application.LegacyRouting := true;
368368```
369369
370370в *.lpr файле.
371371
372372### Старый способ
373373
374374#### Использование нескольких модулей
375375
376376В вашем приложении может быть несколько модулей. Щелкните меню "File", затем щелкните "New...". В появившемся диалоговом окне выберите "Web Module" в дереве выбора модулей.
377377
378378![](/wiki-images/e/e5/Fpwebtutorial_new_module.png)
379379
380380затем нажмите OK.
381381
382382Поскольку в вашем приложении существует несколько модулей, вы больше не можете делать запрос (request) только с использованием `/`. Платформа не сможет волшебным образом выбрать, какой модуль должен обслуживать ответ (response), поэтому есть два способа указать, какой модуль вы хотите вызвать:
383383
384384- /\<module name\>
385385- /?module=\<module name\>
386386
387387Во втором формате вы можете изменить "module" (который является значением по умолчанию) на любой допустимый ключ строки запроса, изменив **Application.ModuleVariable**.
388388
389389#### С использованием Actions
390390
391391До сих пор мы использовали только веб-модули с обработчиком единого запроса. Это не сильно масштабируется, поскольку ваше веб-приложение становится все более и более сложным. Более того, некоторые функции могут иметь общие свойства и могут быть лучше логически сгруппированы, например:
392392
393393- Account module
394394 - Login action
395395 - Logout action
396396 - Register action
397397- Product module
398398 - Create action
399399 - Update action
400400 - Delete action
401401 - Details action
402402
403403##### Поток обработки запросов (request)
404404
405405Прежде, чем использовать действие (action), важно знать, как обрабатывается запрос fpWeb. В противном случае ваше действие (action) может оказаться бесполезным, потому что ваш модуль данных всегда обрабатывает запрос. Как такое могло случится? Возвращаясь немного назад, вспомните **Handled := true**, которое мы всегда делали раньше? Теперь в игру вступает параметр **Handled**.
406406
407407**Каждый** запрос сначала будет проходить через модуль **OnRequest**, независимо от запрошенного действия. Только если для этого **не** установлено значение Handled, выполняется OnRequest веб-действия.
408408
409409В общем, поток запросов:
410410
411411![](/wiki-images/e/ea/Fpwebtutorial_request_flow.png)
412412
413413Обратите внимание на окошко "Our Concern" (наша озабоченность), это то, на что мы собираемся обратить наше внимание.
414414
415415##### Добавление действий в веб-модули
416416
417417Чтобы добавить действие, выберите веб-модуль и перейдите в инспектор объектов. На вкладке свойств выберите "Actions" и нажмите кнопку во втором столбце.
418418
419419![](/wiki-images/9/95/Fpwebtutorial_manage_actions.png)
420420
421421Появится всплывающее окно, в котором вы можете добавлять, удалять и изменять порядок действий.
422422
423423![](/wiki-images/d/db/Fpwebtutorial_edit_actions.png)
424424
425425Нажмите "Add", в списке появится новое действие. Выберите его, затем перейдите в инспектор объектов. В настоящее время он будет показывать свойства и события этого вновь созданного действия. Переименуйте свойство Name (это будет имя, которое вы напишете в URL-адрес, поэтому дайте ему короткое, простое, но описательное имя) как вы желаете, я выберу "Hello". Двигаемся дальше на вкладку событий, сделайте то же самое, что и OnRequest для модуля, нажмите кнопку справа от строки OnRequest, чтобы создать обработчик запросов.
426426
427427![](/wiki-images/2/2e/Fpwebtutorial_action_onrequest.png)
428428
429429Вы будете представлены в том же интерфейсе OnRequest, но этот обрабатывает веб-действие вместо веб-модуля. Все, что вы можете сделать в OnRequest веб-модуля, можно сделать и здесь. Скопируйте тело метода из 'Hello, World!' раздела.
430430
431431Не забудьте удалить **Handled := true** из тела **OnRequest** предыдущего веб-модуля (или полностью удалить событие), чтобы действие позаботилось об обработке запроса.
432432
433433Запустите свой проект и запустите браузер. Теперь, поскольку обработка запросов делегирована веб-действию, вы больше не можете просто запрашивать /, но вам нужно / \<action name\> или \<свойство ActionVar модуля\>=\<action name\>. Обратите внимание, что \<свойство ActionVar модуля\> по умолчанию имеет значение пустой строки, в отличие от **Application.ModuleVariable**, для которого "модуль" является значением по умолчанию. Итак, по умолчанию вы можете использовать только /\<action name\> формы.
434434
435435Если у вас несколько модулей, то у вас есть множество вариантов:
436436
437437- /\<module name\>/\<action name\>
438438- /\<module name\>?action=\<action name\>
439439- /\<action name\>?module=\<module name\>
440440- /?module=\<module name\>&action=\<action name\>
441441
442442Обратите внимание, что как только у модуля есть хотя бы одно действие, /\<module или action name\> по умолчанию сопоставляется с /\<action name\>. Чтобы изменить поведение таким образом, чтобы по умолчанию оно отображалось как /\<module name\>, установите для параметра **Application.PreferModuleName** значение **true**. В случае нескольких модулей, если имя модуля не указано, модуль по умолчанию будет обрабатывать данное действие. Чтобы изменить поведение таким образом, что имя модуля задавалось явно, установите для **Application.AllowDefaultModule** значение **false**.
443443
444444Следующие таблицы обобщают то, что произойдет на основе двух свойств:
445445
446-<thead>
447-
448-</thead>
449-<tbody>
450-
451-</tbody>
446+| /<module or action name> | | Application.PreferModuleName | |
447+|---|---|---|---|
448+| true | false | | |
449+| Application.AllowDefaultModule | true | /<module name> | /<default module>/<action name> |
450+| | false | /<module name> | ERROR |
452451
453452##### Действие по умолчанию
454453
455454Помните предыдущую диаграмму, "Delegate request handling to actions" (Делегировать обработку запросов действиям) на самом деле не так просто, но если мы расширим эту диаграмму, изображение будет слишком большим, чтобы поместиться. Итак, вот схема этой части:
456455
457456![](/wiki-images/4/44/Fpwebtutorial_request_delegation_flow.png)
458457
459458Две важные вещи из потока: DefActionWhenUnknown и действие по умолчанию. Первое является свойством веб-модуля, а второе соответствует свойству Default действия. В последнем случае, если имеется более двух действий, для которых свойство Default установлено в значение `true`, порядок действий (то, как он отображается во всплывающем окне управления действиями) будет учитываться для определения того, какое действие является действием по умолчанию. Эти два свойства определяют, что должно делать приложение, если для данного запроса не найдено подходящего действия.
460459
461460Следующие таблицы обобщают то, что произойдет на основе двух свойств:
462461
463-<thead>
464-
465-</thead>
466-<tbody>
467-
468-</tbody>
469-
470-<thead>
471-
472-</thead>
473-<tbody>
462+| Запрос с недопустимым названием действия | | DefActionWhenUnknown | |
463+|---|---|---|---|
464+| true | false | | |
465+| Action.Default | true | Запрос обрабатывается действием по умолчанию | Ошибка: действие не найдено для действия: <action name> |
466+| | false | Ошибка: неверное имя действия и нет действия по умолчанию | Ошибка: действие не найдено для действия: <action name> |
474467
475-</tbody>
468+| Запрос без имени действия, т.е.: / | | DefActionWhenUnknown | |
469+|---|---|---|---|
470+| true | false | | |
471+| Action.Default | true | Запрос обрабатывается действием по умолчанию | Запрос обрабатывается действием по умолчанию |
472+| | false | Ошибка: нет имени действия и действия по умолчанию. | Ошибка: нет имени действия и действия по умолчанию. |
476473
477474В случае приведенного выше ответа об ошибке должна следовать трассировка стека с информацией о номере строки, если вы создаете свое приложение с параметром `-gl`. Позже мы увидим, как создать собственный обработчик для этого (трассировка стека не годится для производства). Но пока убедитесь, что вы понимаете концепцию веб-модуля и веб-действия, особенно поток запросов. Играйте, пока не решите, что готовы к следующему разделу.
478475
479476### Новый механизм
480477
481478Новый механизм чрезвычайно гибок и работает даже без модулей данных (старый механизм работает только с модулями данных).
482479
483480Выделенный модуль для этой маршрутизации предоставляется как **httproute** (добавьте его в пункт использования программы/модуля, где вы хотите зарегистрировать маршруты). Модуль содержит функцию **HTTPouter**, которая возвращает одноэлементный объект, отвечающий за управление маршрутом приложения, и имеет метод **RegisterRoute** для регистрации вашего маршрута.
484481
485482#### Синтаксис маршрута
486483
487484Первый параметр **HTTPouter.RegisterRoute** — это маршрут, который будет сопоставляться с входящим запросом. Это может быть как простое ***** выше, что означает 0 или более путей или просто любые пути, так и сложное, как **/api/v1/:resource/***, что означает, что заголовок **REQUEST_URI** должен начинаться с **/api/v1/**, за которым следует что-то еще, что будет привязано к переменной с именем resource и заканчиваться 0 или более путями. Он будет соответствовать:
488485
489486- /api/v1/products
490487- /api/v1/products/1
491488- /api/v1/products/1/clone
492489- /api/v1/products/something/else/that/is/really/long/and/silly
493490
494491но не:
495492
496493- /api/v1
497494- /excuse/me/api/v1/products
498495
499496В основном есть только 3 специальных символа:
500497
501498- *  обозначает 0 или более путей
502499
503500-
504501 param  обозначающий часть
505502
506503- /  обозначающий разделитель частей
507504
508505ваш маршрут будет состоять из этих 3 символов, а также всего остального, из чего состоит маршрут. 
509506
510507#### Регистрация маршрута
511508
5125092-й, 3-й или 4-й параметр (в зависимости от того, хотите ли вы обрабатывать конкретный метод HTTP и/или передавать ему дополнительные данные) **HTTPRouter.RegisterRoute** перегружен несколькими возможностями:
513510
514511- Процедура обратного вызова
515512
516513``` pascal
517514TRouteCallback = Procedure(ARequest: TRequest; AResponse);
518515```
519516
520517- Событие обратного вызова
521518
522519``` pascal
523520TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;
524521```
525522
526523- Объект, удовлетворяющий интерфейсу (CORBA)
527524
528525``` pascal
529526IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
530527 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
531528end;
532529```
533530
534531- Объект, расширяющий абстрактный класс маршрутизатора
535532
536533``` pascal
537534TRouteObject = Class(TObject, IRouteInterface)
538535Public
539536 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); virtual; abstract;
540537end;
541538
542539TRouteObjectClass = Class of TRouteObject;
543540```
544541
545542По умолчанию, если второй параметр не является **TRouteMethod**, будут совпадать все методы HTTP. Используйте один из **rmUnknown, rmAll, rmGet, rmPost, rmPut, rmDelete, rmOptions, rmHead, rmTrace**, чтобы соответствовать только определенному методу HTTP.
546543
547544Порядок регистрации имеет значение. Если есть два или более маршрута, соответствующих текущему запросу, его обработает ранее зарегистрированный.
548545
549546В этом новом механизме автономнаяй "Hello, World!"-программа может быть такой простой, как:
550547
551548``` pascal
552549uses
553550 fphttpapp, httpdefs, httproute;
554551procedure DoHello(ARequest:TRequest; AResponse : TResponse);
555552begin
556553 AResponse.Content:='<html><body><h1>Hello,World!</h1></body></html>'
557554end;
558555
559556begin
560557 HTTPRouter.RegisterRoute('*', @DoHello);
561558 Application.Port := 9000;
562559 Application.Initialize;
563560 Application.Run;
564561end.
565562```
566563
567564#### Пример веб-сервера
568565
569566Это пример простого кроссплатформенного многопоточного веб-сервера.
570567
571568``` pascal
572569program webserver;
573570
574571{$mode objfpc}{$H+}
575572
576573uses
577574 {$ifdef UNIX}
578575 cthreads, cmem,
579576 {$endif}
580577 fphttpapp, httpdefs, httproute;
581578
582579procedure route1(aReq: TRequest; aResp: TResponse);
583580begin
584581 aResp.content:='<html><body><h1>Route 1 The Default</h1></body></html>'
585582end;
586583
587584procedure route2(aReq: TRequest; aResp: TResponse);
588585begin
589586 aResp.content:='<html><body><h1>Route 2</h1></body></html>'
590587end;
591588
592589begin
593590 HTTPRouter.registerRoute('/', @route1, true);
594591 HTTPRouter.registerRoute('/route2', @route2);
595592 Application.port := 8080;
596593 Application.threaded := true;
597594 Application.initialize;
598595 Application.run;
599596end.
600597```
601598
602599## Использование шаблонов
603600
604601fpWeb имеет встроенную поддержку FPTemplate, универсального движка шаблонов Free Pascal. Его не обязательно использовать из контекста веб-приложения, но с интегрированной поддержкой все будет немного проще. По крайней мере, управление памятью можно игнорировать, так как об этом позаботится модуль.
605602
606603Есть два уровня, на которых можно использовать шаблоны: действие и модуль. Полная поддержка RAD до конца не реализована, поэтому в некоторых моментах вам нужно перейти к ручному кодированию.
607604
608605Существует два режима работы: непараметризованный и параметризованный. Активный режим управляется свойством **AllowTagParams**, которое должно быть достаточно очевидным, какое значение относится к какому режиму.
609606
610607Строка шаблона может быть передана из файла через свойство **FileName** или прямая строка через свойство **Template**. `Template.Template` - я знаю, это звучит странно :) Если оба заполнены, то FileName будет иметь приоритет.
611608
612609Два свойства: **StartDelimiter** и **EndDelimiter** определяют, как механизм должен распознавать тег шаблона. Например, если у вас есть:
613610
614611- StartDelimiter = '{+'
615612- EndDelimiter = '+}'
616613
617614затем строка '{+title+}' определяет тег шаблона с именем 'title'. Обратите внимание, что пробелы важны, поэтому '{+ title +}' определяет тег шаблона с именем ' title ', а не просто 'title'.
618615
619616Специальные для параметризованного режима дополнительные три свойства: **ParamStartDelimiter**, **ParamEndDelimiter** и **ParamValueSeparator** определяют, как движок должен распознавать параметр тега шаблона. Например, если у вас есть:
620617
621618- ParamStartDelimiter = '\[-'
622619- ParamEndDelimiter = '-\]'
623620- ParamValueSeparator = '='
624621
625622тогда строка '{+data \[-p=v-\]\[-a=b-\] +}' определяет тег шаблона с именем 'data' с параметром 'p' значения 'v' и параметром 'a' значения ' б'. Это можно использовать для передачи параметра уровня шаблона, такого как ожидаемый формат даты, заголовок-строка-нижний колонтитул для настраиваемого выходного представления, имя файла и т. д., как вы решите.
626623
627624Как следствие другого способа работы, основное событие, в котором работает шаблон, также отличается. Непараметризованный будет использовать **OnGetParam**, а параметризованный будет использовать **OnReplaceTag**. У них, конечно, другой интерфейс:
628625
629626``` pascal
630627Type
631628 // OnGetParam: только для поддержки простого тега шаблона (напр: {Name})
632629 TGetParamEvent = Procedure(
633630 Sender: TObject;
634631 Const ParamName: String;
635632 Out AValue: String
636633 ) Of Object;
637634 // OnReplaceTag: для тегов с поддержкой параметров
638635 TReplaceTagEvent = Procedure(
639636 Sender: TObject;
640637 Const TagString: String;
641638 TagParams: TStringList;
642639 Out ReplaceText: String
643640 ) Of Object;
644641```
645642
646643В **OnGetParam** вы проверяете **ParamName**, а затем соответственно назначаете **AValue**, т. е. если вы хотите, чтобы тег 'title' был заменен на 'My App', тогда заполните метод следующим образом:
647644
648645``` pascal
649646// используйте Trim(), если вы хотите, чтобы пробелы вокруг тега были незначительными
650647case Trim(ParamName) of
651648 'title': AValue := 'My App';
652649else
653650 AValue := 'UNKNOWN';
654651end;
655652```
656653
657654В **OnReplaceTag** вы проверяете **TagString** и, возможно, **TagParams**, а затем соответственно назначаете **ReplaceText**, т.е., если вы хотите, чтобы тег 'datetime' был заменен текущим временем с параметром 'datetimeformat', чтобы указать, как следует форматировать дату и время, заполните метод следующим образом:
658655
659656``` pascal
660657// используйте Trim(), если вы хотите, чтобы пробелы вокруг тега были незначительными
661658case Trim(TagString) of
662659 'datetime': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
663660else
664661 AValue := 'UNKNOWN';
665662end;
666663```
667664
668665### На уровне действий
669666
670667Создайте/выберите действие, затем перейдите в инспектор объектов. Вы увидите свойство подкомпонента с именем **Template**(Шаблон). Этот шаблон является обычным экземпляром **TFPTemplate**. Разверните его и заполните свойства, как описано выше. Теперь перейдите на вкладку Events(События), снова разверните **Template**, вы увидите два события. Заполните тот, который основан на вашем значении свойства **AllowTagParams**.
671668
672669> **Note:** Если ваш Lazarus не может автоматически заполнять событие, попробуйте ввести имя вручную в поле редактирования, а затем нажмите кнопку "...". Это ошибка в текущем Lazarus, которая может быть исправлена ​​в будущем.
673670
674671На этом уровне шаблон с содержимым не устанавливается автоматически в качестве обработчика запроса. Это может измениться в будущем, но давайте разберемся с его текущим состоянием. Заполните событие **OnRequest** действия, затем заполните его:
675672
676673``` pascal
677674with Actions.CurrentAction as TFPWebAction do
678675begin
679676 AResponse.Content := Template.GetContent;
680677end;
681678Handled := true;
682679```
683680
684681Приведение необходимо, поскольку **CurrentAction** имеет тип **TCustomWebAction** вместо **TFPWebAction**. Без него мы не сможем получить доступ к свойству **Template**.
685682
686683### На уровне модуля
687684
688685На уровне модуля вам в настоящее время придется делать это вручную, поскольку поддержка RAD не реализована. Связанным свойством является ModuleTemplate. Однако это не обычный экземпляр **TFPTemplate**, а специальный класс **TFPWebTemplate**, являющийся его потомком.
689686
690687Идея здесь состоит в том, чтобы модуль предоставлял макет, а действия предоставляли контент с возможностью предоставления дополнительных переменных. Поэтому рекомендуется оставить **AllowTagParams** как есть и назначить событие **OnGetParam** только **модуля**. НЕ назначайте **OnGetParam** для **ModuleTemplate**, так как он **никогда** не будет вызываться.
691688
692689Тег шаблона с именем 'content' будет автоматически заменен содержимым, созданным действием, все остальное заменяется либо из внутренних переменных шаблона, либо из **OnGetParam**.
693690
694691### Использование отдельного шаблона
695692
696693Несмотря на несколько неполную интеграцию, ничто не мешает вам использовать fpTemplate (или любые другие решения для шаблонов) вручную, вне встроенной поддержки. В некоторых случаях это может быть даже лучше, поскольку оно будет модульным.
697694
698695## Использольвание html Producer object
699696
700697Предполагая, что существует TWebAction с именем act_get_my_html, вы должны попросить написать в AResponse возврат внутреннего потока байтов: при ответе с помощью решения html Producer objects мы должны использовать методы писателя. Он управляет потоком памяти с целью ускорения. Здесь нет строкового типа, как в приведенном выше решении fpTemplate, которое использует обработку текста строки, как обычно в Php. Producer записывает в AResponse рекурсивную итерацию " foreach ", которая проходит по дереву полиморфных объектов HTML Dom, состоящему из иерархии классов `THtmlCustomElement = Class(TDOMElement)`(см. модуль `htmlelements.pp`). Итак, AResponse пишется параметром aWriter, со следующим вызовом:
701698
702699``` pascal
703700procedure TFPWebModule1.act_get_my_htmlRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
704701begin
705702 (* спрашиваем у HTMLEntityProducer1 содержимое своих html-элементов DOM: *)
706703 HTMLEntityProducer1.HandleRequest(ARequest, AResponse, Handled);
707704 Handled := True;
708705end;
709706```
710707
711708Повторим еще раз, что работа Producer заключается в преобразовании потока внутренней памяти в текст в объекте ответа: именно так был разработан этот шаблон, хотя существует метод рендеринга text-html с именем ProduceContent только для целей отладки. Вы можете переопределить этот метод, если вы пишете или отлаживаете компонент Producer.
712709
713710Вот пример с html-объектом Producer, позволяющий создать веб-приложение «способом RAD», то есть с помощью перетаскивания компонента HTMLEntityProducer из палитры:
714711
715712``` pascal
716713procedure TFPWebModule1.HTMLEntityProducer1WriteEntity(Sender: THTMLContentProducer; aWriter: THTMLWriter);
717714begin
718715
719716 aWriter.startHeader;
720717 aWriter.Meta('','Content-Type','text/html; charset=UTF-8');
721718 aWriter.title('My web page');
722719 aWriter.link('stylesheet','stylesheet.css','text/css','screen');
723720 aWriter.EndHeader;
724721
725722 aWriter.Startbody;
726723 aWriter.Startparagraph;
727724 aWriter.heading2('Hello, world from inside §1:');
728725 aWriter.Text('Here is text written inside the current paragraph.');
729726 aWriter.Endparagraph;
730727 aWriter.paragraph('This is another text written inside a self "started and ended" paragraph.');
731728 aWriter.Startparagraph;
732729 aWriter.heading2('Hello, world from inside §2:');
733730 aWriter.Text('Here is the final text.');
734731 aWriter.Image.src := 'logo.png';
735732 AnotherProducer.WriteContent(aWriter);
736733 aWriter.Endparagraph;
737734 aWriter.Endbody;
738735
739736end;
740737```
741738
742739Свойство TWebAction.ContentProducer позволяет связать THTTPContentProducer с его веб-действием, предоставляемым через URI в сети.
743740
744741\[ToDo: нет официальной документации о компонентах «способ RAD» fpWeb (html producer, html provider, html adapter, html formatter и т.д.)\]
745742
746743## Советы и хитрости
747744
748745### Возврат другого кода ответа HTTP
749746
750747По умолчанию fpWeb возвращает HTTP 200 OK, чтобы указать на успешную обработку запроса. Это, конечно, не всегда так, поскольку пользовательский ввод может быть не таким, как мы ожидали. Для этого установите для параметра **AResponse.Code** в обработчике запроса код, который вы хотите вернуть.
751748
752749### Перенаправление запроса на другой URL
753750
754751Обычная практика после успешного входа в систему — перенаправление пользователя на страницу его учетной записи. Это можно сделать, вызвав **AResponse.SendRedirect** в обработчике запросов, указав URL-адрес для перенаправления запроса.
755752
756753### Обслуживание статических файлов (встроенный веб-сервер)
757754
758755Помните диалоговое окно в разделе [[fpWeb Tutorial/ru|Hello, World!]] после выбора приложения HTTP-сервера? Если вы отметите "Register location to serve files from"(Зарегистрировать местоположение для обслуживания файлов), вы можете заполнить "Location"(Расположение) (сегмент URI, не должен содержать слэшей) и "Directory"(Каталог) (физический каталог на вашем компьютере, должен существовать во время выполнения), то мастер просто добавит:
759756
760757``` pascal
761758RegisterFileLocation('<Location>','<Directory>');
762759```
763760
764761в начало вашего *.lpr-файла и добавит модуль **fpwebfile** в раздел uses. Вы можете сделать это вручную в любое время, а также зарегистрироваться несколько раз для разных местоположений/каталогов. После этого вы можете запросить **/\<Location\>/\<любое имя файла в Directory\>**, и оно будет предоставлено автоматически. Обратите внимание, что *mimetype* файла определяется **fpmimetypes**. Вызовите **MimeTypes.LoadFromFile** с вашим файлом *mime.types*, чтобы указать правильный тип *mimetype* на основе его расширения. В противном случае файл всегда будет обслуживаться как **application/octet-stream**, что означает, что браузер загрузит его, а не интерпретирует (особенно важно для файлов JavaScript и CSS).
765762
766763Вы можете получить полные mime.types [здесь](http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?&view=co).
767764
768765В Lazarus 2.0.6 или новее вы должны добавить в начало своей программы полный путь к файлу mime.types.
769766
770767``` pascal
771768begin
772769 MimeTypesFile := Application.Location + 'mime.txt';
773770```
774771
775772Примите во внимание, что путь по умолчанию, идущий с проектом, это - *lib\\\$(TargetCPU)-\$(TargetOS)*
776773
777774Например: *httpproject\lib\i386-win32\mime.txt*
778775
779776### Централизованное управление конфигурацией и модулями
780777
781778По умолчанию программный файл (.lpr) содержит модуль протокола. Это ограничивает возможность использования объекта **Application** из других контекстов, таких как веб-модули. К счастью, нетрудно провести рефакторинг, чтобы получить то, что мы хотим. Мы удаляем вызовы **RegisterHTTPModule** из веб-модулей и не будем использовать .lpr для очистки основного блока с единственным идентификатором модуля в разделе uses, назовем его: брокер. Содержимое модуля:
782779
783780``` pascal
784781unit Brokers;
785782
786783{$mode objfpc}{$H+}
787784
788785interface
789786
790787{ $define cgi}
791788{ $define fcgi}
792789{$define httpapp}
793790
794791uses
795792 CustWeb;
796793
797794function GetApp: TCustomWebApplication; inline;
798795
799796implementation
800797
801798uses
802799 {$ifdef cgi}fpcgi{$endif}
803800 {$ifdef fcgi}fpfcgi{$endif}
804801 {$ifdef httpapp}fphttpapp{$endif}
805802 ,webmodule1
806803 ,webmodule2
807804 ;
808805
809806function GetApp: TCustomWebApplication;
810807begin
811808 Result := Application;
812809end;
813810
814811initialization
815812 RegisterHTTPModule('wm1', TWebModule1);
816813 RegisterHTTPModule('wm2', TWebModule2);
817814 {$ifndef cgi}
818815 Application.Port := 2015;
819816 {$endif}
820817 Application.Initialize;
821818 Application.Run;
822819end.
823820```
824821
825822Таким образом, мы можем управлять регистрацией веб-модуля, а также предоставлять API для получения объекта **Application** (представленного как **TCustomWebApplication**), в то же время легко переключаясь между реализациями протокола в одном месте.
826823
827824### Элегантное завершение (FastCGI / встроенный веб-сервер)
828825
829826Вместо нажатия `Ctrl`+`C` в вашем приложении есть способ корректно завершить работу вашего приложения, выполнив необходимую очистку, вызвав **Application.Terminate**. Возможно, вам придется использовать предыдущий трюк, чтобы легко получить доступ к объекту **Application**. Распространенной реализацией является предоставление определенного защищенного паролем модуля/действия, которое вызывает метод Terminate. Впрочем, вы можете выбрать любой способ.
830827
831828### Пользовательский обработчик исключений
832829
833830\[отредактировано 1 мая 2020г =\> переход от указателя метода к простой процедуре..\]
834831
835832Чтобы переопределить обработчик исключений по умолчанию, который выдает трассировку стека всякий раз, когда возникает исключение (например, по HTTP 404 или 500), и, следовательно, не подходит для производства, вы должны задать **Application.OnShowRequestException**.
836833
837834Это метод, поэтому вам нужно будет предоставить свою процедуру, реализующую метод, и присвоить ее с помощью объекта, т.е.: если у вас есть **MyExceptionHandler** как объект **TMyExceptionHandler**, который имеет метод **MyShowRequestException**, вы можете назначить его посредством:
838835
839836``` pascal
840837Application.OnShowRequestException := @MyExceptionHandler.MyShowRequestException;
841838```
842839
843840не забудьте **.Create()** **MyExceptionHandler** ПЕРЕД назначением выше, иначе вы получите **EAccessViolation**!
844841
845842Вы должны предоставить свою глобальную процедуру, которая реализует ваш собственный обработчик исключений (в рабочей среде рекомендуется заменить стек вызовов кодом состояния HTTP и его объяснением). Затем вы можете переопределить обработчик исключений по умолчанию, назначив его следующим образом:
846843
847844``` pascal
848845Application.OnShowRequestException := @MyShowRequestException;
849846```
850847
851848### Чисто ручное кодирование (дизайнер форм не требуется)
852849
853850Нет необходимости использовать конструктор форм Lazarus для написания fpWeb-приложения. Вы можете использовать технику чисто ручного кодирования, чтобы написать его. Секрет кроется в третьем параметре **RegisterHTTPModule**: **SkipStreaming**. Если для этого параметра установлено значение true, fpWeb не будет искать ресурс .lfm. Поэтому все должно обрабатываться вручную: настройки свойств, обработчики событий, регистрация действий и т.д.
854851
855852Обратите внимание, что логично делать то, что обычно делается через инспектор объектов в переопределенном конструкторе. Внутри него вызовите унаследованный конструктор, передав в качестве параметров AOwner и CreateMode. После этого вы можете установить свойства, назначить обработчики событий и т.д. Пример:
856853
857854``` pascal
858855type
859856 THelloWorldModule = class(TFPWebModule)
860857 constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
861858 procedure Request(Sender: TObject; ARequest: TRequest;
862859 AResponse: TResponse; var Handled: Boolean);
863860 end;
864861
865862constructor THelloWorldModule.CreateNew(AOwner: TComponent; CreateMode: Integer);
866863begin
867864 inherited CreateNew(AOwner,CreateMode);
868865 OnRequest := @Request;
869866end;
870867
871868procedure THelloWorldModule.Request(Sender: TObject; ARequest: TRequest;
872869 AResponse: TResponse; var Handled: Boolean);
873870begin
874871 AResponse.Content := 'Hello, World!';
875872 Handled := true;
876873end;
877874```
878875
879876## См.также
880877
881878- [Lazarus для Интернета (учебник)](https://www.freepascal.org/~michael/articles/#web1)
882879- [Lazarus для Интернета: сеансы и шаблоны (учебник))](https://www.freepascal.org/~michael/articles/#web2) В этой статье описывается класс TSQLDBSession, который управляет подключением SQL, которое наследуется от веб-сеанса (для управления историями переменных веб-сеанса в рамках сеанса подключения к базе данных, имеющей возможности блокировки строк).
883880- примеры - чтение текстовых файлов - в вашем каталоге ...\fpc\x.x.x\source\packages\fcl-web\examples.\
884881
885882![](/wiki-images/c/c0/Cgi-listrecords.png)
886883
887884- XML или JSON на ExtJS: если вы хотите использовать более или менее сложный движок, который очень «продвинут» в отношении аспекта рендеринга объектов Javascript на стороне браузера, в дополнение к их функциональным возможностям (например, ExtJS для отображения сетка с поддержкой базы данных, динамическая графика и т. д.), для такого решения очень часто требуется файл XML или JSON. Существуют классы Adapter и Formatter (например, классы TExtJSXMLWebdataInputAdaptor, TExtJSJSONDataFormatter).
888885
889886В целом, классы Adapter адаптируют узлы входящего jSon или XML к отображению своих полей базы данных TField. А классы Formatter сопоставляют каждую запись TField с ее узлом в формате jSon или XML перед отправкой в ​​браузер.\
890887* [Форматы веб-данных в Lazarus/FPC](https://www.freepascal.org/~michael/articles/#webdata)
891888
892889- [Практическое построение GUI в ExtJS](https://www.freepascal.org/~michael/articles/#extjs1)
893890- [Смешивание веб-приложений ExtJS и Pascal: ExtPascal](https://www.freepascal.org/~michael/articles/#extpascal)
894891
895892---
896893*From the Free Pascal wiki, page "fpWeb Tutorial/ru", as it stood on 2022-06-07 after a change by Zoltanleo, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history