Dễ dàng REST với kbmMW #20 – OpenAPI và Swagger UI

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://swagger.io/

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 đó

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/

Thẻ: kbmMW OpenAPI Swagger UI RESTful API API Documentation

Đăng vào ngày 8 tháng 6 lúc 01:28