Phiên bản kbmMW sắp tới không chỉ sửa lỗi mà còn giới thiệu một tính năng chính mới: framework tạo bộ mã giả (stub) cho client.
Vậy framework tạo bộ mã giả cho client là gì?
Đó là một framework dựa trên smart services của kbmMW, có khả năng tạo ra mã có thể được sử dụng trực tiếp bởi nhiều loại client khác nhau để truy cập các HTTP smart services trên máy chủ ứng dụng kbmMW. (HTTP Smart Service là gì? Bạn có thể tham khảo bài viết của tác giả: Giới thiệu HTTP Smart Service trong kbmMW)
Hiện tại, kbmMW đã triển khai tính năng smart client, cho phép client dễ dàng truy cập các chức năng trong smart services. Tuy nhiên, do smart client phụ thuộc vào late binding, nhà phát triển không thể nhận được sự hỗ trợ từ IDE và trình biên dịch về các tham số và kiểu dữ liệu. Do đó, trình biên dịch cần tạo thêm mã để IDE và trình biên dịch có thể giải thích các hàm và phương thức được xuất bởi máy chủ.
Vấn đề này chính là thứ mà framework tạo bộ mã giả cần giải quyết, nhưng bài viết này sẽ không thảo luận về cách tạo mã giả cho client Delphi cho smart services, vì dù đã có các công việc chuẩn bị, tính năng cụ thể này có thể không được triển khai đầy đủ trong phiên bản sắp tới. Thay vào đó, tôi sẽ giới thiệu cách sử dụng framework tạo bộ mã giả để triển khai một bộ mã phức tạp hơn, đáp ứng các yêu cầu điển hình trong thế giới REST. Khoảng một năm trước, khi tôi làm việc với một công ty lớn về mã Java, tôi đã nhận ra điều này.
Trong thế giới Java, việc sử dụng Swagger (sau này được đổi tên thành OpenAPI) để tài liệu hóa các giao diện REST là một tiêu chuẩn thực tế. Ngày nay, OpenAPI đã được hầu hết các nhà phát triển REST công nhận và hỗ trợ.
OpenAPI cung cấp mô tả giao diện REST, có thể được sử dụng cho tài liệu và cũng có thể tự động tạo mã (bộ mã giả) cho nhiều môi trường phát triển khác nhau, giúp các môi trường này dễ dàng tận dụng các chức năng được xuất qua giao diện REST.
Bạn có thể đọc thêm về OpenAPI tại:
https://blog.csdn.net/sanyaoxu_2/article/details/80555328
OpenAPI không chỉ trở thành tiêu chuẩn thực tế mà còn tạo ra nhiều công cụ khác nhau để tạo, chỉnh sửa, xem và kiểm tra mô tả giao diện REST (thường được gọi là file Swagger).
Một trong những công cụ đó là Swagger-UI, một giao diện người dùng được xây dựng bằng Javascript và HTML, có thể được cung cấp bởi máy chủ web, cung cấp giao diện người dùng đơn giản và dễ sử dụng cho các giao diện REST được công khai trên máy chủ.
kbmMW hiện đã hỗ trợ đầy đủ tất cả các tính năng này.
Hãy bắt đầu đơn giản với Swagger-UI để xem giao diện REST của máy chủ SimpleInvocation demo kbmMW trông như thế nào:
Ở bên trái, bạn có thể thấy khai báo OpenAPI của các giao diện REST được công khai, bên phải là giao diện người dùng thân thiện, cho phép gọi các giao diện này bằng cách nhấp vào các nút đơn giản. Việc điền tham số thậm chí còn dễ dàng hơn.
Nếu tôi cuộn sang phải đến phương thức AddNumbers và nhấp vào cột đó, nó sẽ mở các thông tin khác, cùng với một nút cho phép chúng ta thử gọi REST.
Điều này rất tuyệt!
Vậy làm thế nào để bật OpenAPI cho máy chủ ứng dụng hỗ trợ REST?
Việc này thực sự rất dễ dàng.
Để trả về OpenAPI specification cho dịch vụ REST, chúng ta chỉ cần thêm một phương thức REST công khai khác vào dịch vụ trong Unit2.
[kbmMW_Rest('method:get, path: "api", <strong>responseMimeType</strong>:"application/x-yaml"')]
function OpenAPI:string;
Phương thức này có thể được đặt tên và đường dẫn REST tùy ý, nhưng chúng ta nên cung cấp đúng responseMimeType. OpenAPI specification chuẩn được biểu diễn bằng YAML, may mắn là kbmMW hoàn toàn hỗ trợ. Nó cũng cho phép tạo OpenAPI description ở định dạng JSON, nhưng đây chủ yếu là để tương thích với các hệ thống không hỗ trợ YAML. Vì vậy, trong ví dụ này, chúng ta chỉ định loại phản hồi là YAML trong responseMimeType.
// Trả về OpenAPI specification.
function TkbmMWCustomService2.OpenAPI:string;
begin
// Trả về OpenAPI specification cho tất cả các phương thức REST trong dịch vụ này
// dưới dạng YAML. Thêm giá trị ASettings: 'json:true' để trả về specification
// dưới dạng JSON.
// Thêm 'servers: [ "url1", "url2",.. "urln" ]' vào ASettings nếu bạn muốn
// nhúng thông tin vị trí máy chủ trong specification.
// Thêm 'inline:true' để nội tuyến hóa các định nghĩa đối tượng thay vì sử dụng $ref.
// Dòng ví dụ tiếp theo sử dụng framework cấu hình để làm cho việc cài đặt dễ dàng cấu hình.
Result:=TkbmMWSmartOpenAPIStubGenerator.<strong>GenerateOpenAPI</strong>('',self,'inline:$(OpenAPI.inline=false)');
end;
Cài đặt mã cho hàm OpenAPI rất đơn giản, chỉ gọi phương thức GenerateOpenAPI của bộ tạo mã giả OpenAPI, dịch vụ được "OpenAPI'ified", và có thể đặt chuỗi cài đặt. Chuỗi cài đặt có thể trống, trong ví dụ này, chuỗi cài đặt chứa giá trị:
inlinei:$(OpenAPI.inline=false)
Lý do là có hai cách hợp lệ để tạo OpenAPI specification cho giao diện REST, có thể nội tuyến hoặc tham chiếu đến các đối tượng.
Nội tuyến có nghĩa là mỗi đối tượng sẽ được giải thích chi tiết ở mọi vị trí nó có thể được sử dụng trong tất cả các mô tả cuộc gọi REST, trong khi tham chiếu có nghĩa là mô tả và tham chiếu đến các thành phần OpenAPI style (đối tượng) khi cần. Tham chiếu là mặc định. Tuy nhiên, trong mã ví dụ, tôi chọn cấu hình nó bằng cách sử dụng framework cấu hình kbmMW. Chúng ta có thể viết: inline: false hoặc inline: true, nhưng mẫu sẽ hỏi giá trị hiện tại của cấu hình OpenAPI.inline, có thể là true hoặc false. Nếu không tìm thấy giá trị như vậy, kbmMW sẽ sử dụng giá trị mặc định false (như đã hiển thị sau =).
Chúng ta cũng có thể chỉ định JSON là ưu tiên. Điều này chỉ cần thêm json:true vào chuỗi cài đặt, như sau:
inline:$(OpenAPI.inline=false), json:true
Rõ ràng, điều này cũng có thể được cấu hình như nội tuyến.
Nhưng chúng ta sẽ giữ nguyên, vì vậy đầu ra của hàm OpenAPI sẽ là mô tả OpenAPI được định dạng YAML cho các phương thức REST trong dịch vụ.
Vì vậy, nếu chúng ta mở URL trong trình duyệt: http://localhost:888/myserver/api, chúng ta sẽ nhận được toàn bộ mô tả OpenAPI:
openapi: "3.0.0"
info:
title: SMARTDEMO
description: "HTTP smart service supp. FastCGI"
version: "1"
paths:
/myserver/api:
get:
operationId: get_myserver_api
responses:
"200":
content:
application/x-yaml:
schema:
type: string
description: "Success response"
/myserver/helloworld:
get:
operationId: get_myserver_helloworld
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/now1:
get:
operationId: get_myserver_now1
responses:
"200":
content:
text/plain:
schema:
type: string
format: date-time
description: "Success response"
/myserver/now2:
get:
operationId: get_myserver_now2
responses:
"200":
content:
text/plain:
schema:
type: number
format: double
description: "Success response"
/myserver/echostring/{AString}:
get:
operationId: get_myserver_echostring__AString_
parameters:
-
in: path
name: AString
required: true
schema:
type: string
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/myechostring/{AString}:
get:
operationId: get_myserver_myechostring__AString_
parameters:
-
in: path
name: AString
required: true
schema:
type: string
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/echourl:
get:
operationId: get_myserver_echourl
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/echoheader:
get:
operationId: get_myserver_echoheader
parameters:
-
in: header
name: Accept
required: true
schema:
type: string
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/echoanyheader/{AHeaderName}:
get:
operationId: get_myserver_echoanyheader__AHeaderName_
parameters:
-
in: path
name: AHeaderName
required: true
schema:
type: string
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/echocookie:
get:
operationId: get_myserver_echocookie
parameters:
-
in: cookie
name: MyCookie
required: true
schema:
type: string
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/echoreversedstring:
post:
operationId: post_myserver_echoreversedstring
requestBody:
required: true
content:
text/plain:
schema:
type: string
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/myserver/echobytes:
post:
operationId: post_myserver_echobytes
requestBody:
required: true
content:
text/plain:
schema:
type: string
format: byte
responses:
"200":
content:
text/plain:
schema:
type: string
format: byte
description: "Success response"
/myserver/echoreversedconfigstring:
get:
operationId: get_myserver_echoreversedconfigstring
responses:
"200":
content:
text/plain:
schema:
type: string
description: "Success response"
/someabspath/addnumbers:
get:
summary: "Adds two numbers and returns result"
operationId: add_numbers
parameters:
-
in: query
name: arg1
required: true
description: "First numeric argument"
schema:
type: integer
format: int32
-
in: query
name: arg2
required: true
description: "Second numeric argument"
schema:
type: integer
format: int32
responses:
"200":
content:
text/plain:
schema:
type: integer
format: int32
description: "The result of the added numbers"
/myserver/storeperson:
post:
operationId: post_myserver_storeperson
requestBody:
required: true
content:
text/plain:
schema:
"$ref": "#/components/schemas/person"
responses:
"200":
content:
text/plain:
schema:
type: integer
format: int32
description: "Success response"
/myserver/getperson/{id}:
get:
operationId: get_myserver_getperson__id_
parameters:
-
in: path
name: id
required: true
schema:
type: integer
format: int32
responses:
"200":
content:
application/json:
schema:
"$ref": "#/components/schemas/person"
description: "Success response"
/myserver/getpersons:
get:
operationId: get_myserver_getpersons
responses:
"200":
content:
application/json:
schema:
type: array
items:
"$ref": "#/components/schemas/person"
description: "Success response"
components:
schemas:
person:
properties:
Name:
type: string
Address:
type: string
Age:
type: integer
format: int32
type: object
title: person
Bạn có thể tự do nghiên cứu nếu muốn. Bạn có thể nhận thấy có một vài nơi có thể thêm tóm tắt và mô tả. Ví dụ, hãy kiểm tra cuộc gọi addnumbers:
/someabspath/addnumbers:
get:
summary: "Adds two numbers and returns result"
operationId: add_numbers
parameters:
-
in: query
name: arg1
required: true
description: "First numeric argument"
schema:
type: integer
format: int32
-
in: query
name: arg2
required: true
description: "Second numeric argument"
schema:
type: integer
format: int32
responses:
"200":
content:
text/plain:
schema:
type: integer
format: int32
description: "The result of the added numbers"
Tóm tắt và mô tả đến từ đâu?
Hãy xem định nghĩa của hàm AddNumbers trong Unit2.pas, điều này rõ ràng:
// Add two numbers.
// It can be called from regular clients, smart clients
// and REST clients.
// It can be called from a browser like this:
// http://.../someabspath/addnumbers?arg1=10&arg2=20
[kbmMW_Method]
[kbmMW_Rest('method:get, path: "/someabspath/addnumbers", '+
'id:"add_numbers", '+
'summary:"Adds two numbers and returns result", '+
'resultDescription:"The result of the added numbers"')]
function AddNumbers([kbmMW_Rest('value: "$arg1", required: true, description:"First numeric argument"')] const AValue1:integer;
[kbmMW_Rest('value: "$arg2", required: true, description:"Second numeric argument"')] const AValue2:integer;
[kbmMW_Arg(mwatRemoteLocation)] const ARemoteLocation:string):integer;
Thuộc tính kbmMW_Rest thậm chí có giá trị id. OpenAPI yêu cầu mỗi đường dẫn REST phải có ID duy nhất. kbmMW sẽ tự động cố gắng tạo một cái, nhưng bạn có thể chọn tên ID của riêng mình bằng cú pháp id, như đã hiển thị ở trên. id, summary, description và resultDescription đều là (theo kbmMW) tùy chọn. Nếu OpenAPI cần các giá trị mô tả và không có giá trị nào được đưa ra, kbmMW sẽ cung cấp các giá trị mặc định.
Vậy bây giờ chúng ta có thể tạo các mô tả OpenAPI hợp lệ. Làm thế nào để máy chủ ứng dụng dựa trên kbmMW của chúng ta sử dụng Swagger-UI để trình bày chúng?
Vì kbmMW có thể hoạt động như một máy chủ web, điều này thực sự cũng rất dễ dàng. Chúng ta chỉ cần thêm một instance của TkbmMWFilePool vào form chính (Unit1) và đặt thuộc tính FilePool của module dữ liệu dịch vụ (Unit2) để trỏ đến nó.
Bây giờ, kbmMW sẽ hoạt động như một máy chủ web thông thường và sẽ cố gắng cung cấp tệp khi không tìm thấy hàm REST nào để gọi.
Trong cùng thư mục với tệp thực thi máy chủ SimpleInvocation, bạn nên tạo một thư mục có tên MyServer (để khớp với dịch vụ), bên dưới, chúng ta thêm một thư mục api, cả hai đều chỉ để khớp với logic đường dẫn trong hàm OpenAPI của Unit2. Thực tế, bạn không nhất thiết phải sử dụng cấu trúc đường dẫn cụ thể này, nhưng tôi đã chọn cấu trúc này cho mục đích trình diễn.
Trong thư mục api, chúng ta sẽ đặt các tệp được tải xuống từ https://swagger.io/tools/swagger-ui/
Sau đó
và
Bạn có thể tải xuống tất cả các tệp từ thư mục dist vào thư mục api\dist, hoặc có thể tải xuống tất cả các tệp bằng cách nhấp vào nút clone hoặc download. Nếu bạn thực hiện thao tác sau, hãy mở tệp zip đã tải xuống và giải nén thư mục dist chứa nội dung vào thư mục api.
Cuối cùng, thêm một tệp có tên index.html vào thư mục api. Bạn có thể sao chép/dán nội dung từ:
<!-- HTML for static distribution bundle build -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger Editor</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Roboto,sans-serif;
font-size: 9px;
line-height: 1.42857143;
color: #444;
margin: 0px;
}
#swagger-editor {
font-size: 1.3em;
}
.container {
height: 100%;
max-width: 880px;
margin-left: auto;
margin-right: auto;
}
#editor-wrapper {
height: 100%;
border:1em solid #000;
border:none;
}
.Pane2 {
overflow-y: scroll;
}
</style>
<link href="./dist/swagger-editor.css" rel="stylesheet">
<link rel="icon" type="image/png" href="./dist/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./dist/favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-editor"></div>
<script src="./dist/swagger-editor-bundle.js"> </script>
<script src="./dist/swagger-editor-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const editor = SwaggerEditorBundle({
dom_id: '#swagger-editor',
layout: 'StandaloneLayout',
presets: [
SwaggerEditorStandalonePreset
]
})
window.editor = editor
}
</script>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
</body>
</html>
Bây giờ bạn đã sẵn sàng để bắt đầu.
Khởi động máy chủ. Sau đó, khởi động trình duyệt và nhập:
http://localhost:888/myserver/api/index.html?url=/myserver/api
Điều này chỉ thị kbmMW cung cấp tệp index.html, tệp này sau đó yêu cầu các tệp còn lại cần thiết để tạo giao diện Swagger-UI. Cuối cùng, chúng ta chỉ thị Swagger-UI tải mô tả OpenAPI từ URL /myserver/api (nếu bạn nhớ, sẽ được gọi trong hàm OpenAPI của Unit2).
Bây giờ bạn nên nhận được giao diện tương tự như được hiển thị ở đầu bài viết này.
Trong giao diện Swagger-UI, bạn có thể tạo bộ khung máy chủ và bộ mã giả client cho tất cả các chức năng REST được công khai bởi kbmMW.
Chúc bạn vui với Swagger!!!
Nếu bạn thích kbmMW, hãy chia sẻ nó. Chia sẻ bài viết blog để mọi người biết về sản phẩm này!
https://components4developers.blog/2018/12/31/rest-easy-with-kbmmw-20-openapi/