Содержание

Оригинал статьи: X-Road: Message Protocol for REST

Автор: Владимир Недорослев

REST сервисы в X-Road

Все REST сервисы должны быть описаны в соответствии со стандартом OPENAPI3. Пример правильно описанного сервиса смотрите в приложении.

Обращение к REST сервису

Используется протокол HTTP версии 1.1.

Участник/Подсистема отправителя указываются в заголовке HTTP. Используемый сервис указывается в самом URL в виде параметров.

Формат запроса

{http-request-method} /{protocol-version}/{serviceId}[/path][?query-parameters]

Заголовки HTTP запроса

В каждом запросе должен присутствовать следующий заголовок HTTP:

X-Road-Client: {client}

{client} — информация о том, кто обращается к сервису. Состоит из следующих частей:

[X-Road instance]/[member class]/[member code]/[subsystem code] 

[Subsystem code] является не обязательным. Например:

X-Road-Client: central-server/GOV/70000004/e-services

При ответе Сервер Безопасности устанавливает следующие заголовки HTTP:

Например:

X-Road-Client: central-server/GOV/70000004/e-services
X-Road-Service: central-server/GOV/70000002/gns-service/GnsTransportByPin/v1
X-Road-Id: fa2e18a5-c2cb-4d09-b994-f57727f7c3fb
X-Road-Request-Hash: 4c519cf0-0e5e-4ccf-b72b-8ed6fe289e6e
X-Road-Request-Id: f92591a3-6bf0-49b1-987b-0dd78c034cc3

Заголовок Accept

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

Accept: application/xml

Если заголовок Accept не указан явно, то Сервер Безопасности самостоятельно ставит значение:

application/json

Пример запроса:

GET /r1/INSTANCE/CLASS2/MEMBER2/SUBSYSTEM2/BARSERVICE/v1/bar/zyggy?some_parameter=1&another_parameter=test

Чтобы отправить запрос к определенному Серверу Безопасности, необходимо включить в запрос следующий заголовок HTTP:

X-Road-Security-Server: [X-Road instance]/[member class]/[member code]/[server code]

Обработка ошибкок

В случае возникновении ошибки во время обработки запроса или же на стороне X-Road, в ответ включается следующий заголовок:

X-Road-Error: [error type]

Заголовок будет содержать только тип ошибки. Детальная информация, как код ответа HTTP, тело сообщения об ошибки и т.д. будет находиться в теле ответа.

Все возникающие ошибки между клиентом и Сервером Безопасности можно поделить на 4 типа:

  1. Сервис вернул ошибку. Код состояния, тело ответа и заголовки HTTP создаются на стороне сервиса.
  2. На стороне сервиса возникли технические неполадки и он не может дать ответ. Код статуса 500 (Internal Server Error).
  3. Клиент послал запрос, не соответствующий протоколу сообщений X-Road для REST. Код статуса 400 (Bad Request)
  4. На стороне сервера безопасности возникли технические неполадки. Код статуса 500.

Для того чтобы различить, на чьей стороне возникла ошибка при коде статуса 500, необходимо обращать внимание на заголовок X-Road-Error: Если заголовок X-Road-Error отсутствует, то ошибка возникла на стороне сервиса, иначе – со стороны X-Road. В таком случае, дополнительная информация отображается в поле type в теле ответа. Например:

Примеры ошибок

1. Всё сработало на стороне Сервера Безопасности, но сервис вернул ошибку.

HTTP status code: 405
Response body:

"timestamp":"2019-03-21T09:45:19.904Z",  
"status":405,  
"error":"Method Not Allowed",  
"message":"Request method 'PUT' not supported",  
"path":"/v3/pet/findByStatus"

HTTP headers:

Content-Type: application/json;charset=utf-8
Date: Thu, 21 Mar 2019 09:45:19 GMT
x-road-id: 5ea48ae9-15c1-465a-be15-9b6ef2c7ef4a
x-road-client: DEV/COM/222/TESTCLIENT
x-road-service: DEV/COM/222/TESTSERVICE/petstore
x-road-request-id: f92591a3-6bf0-49b1-987b-0dd78c034cc3
x-road-request-hash: yFOLGuJ0zmLhZSgwp3ooSBQbR9ejSvTc6p6FvBmcSEB2tDD6bxpjiv8sHORxqz4MMgEADH7IcARNprLfEwudNw==
Content-Length: 159

2. Всё сработало на стороне Сервера Безопасности, но на стороне сервиса возникли технические неполадки

HTTP status code: 500
Response body:

{
"type":"Server.ServerProxy.NetworkError",
"message":"Connect to 10.139.178.1:8080 [/10.139.178.1] failed: Connection timed out (Connection timed out)",
"detail":"9bc95b6e-2f1d-4a41-a7e6-11eda7d734d5"
}

HTTP headers:

Date: Thu, 21 Mar 2019 11:42:03 GMT
Content-Type: application/json;charset=utf-8
X-Road-Error: Server.ServerProxy.NetworkError
Content-Length: 199

3. Клиент послал запрос, не соответствующий протоколу сообщений X-Road для REST

HTTP status code: 400 Response body:

{
"type": "Client.BadRequest",
"message": "Error parsing the client's REST request. Please that the request format corresponds to the X-Road Message Protocol for REST (r1).",
"detail": "018cbcae-537e-421b-b6f6-2608dc97bd90"
}

HTTP headers:

Date: Thu, 21 Mar 2019 11:45:12 GMT
Content-Type: application/json;charset=utf-8
X-Road-Error: Client.BadRequest
Content-Length: 167

4. На стороне сервера безопасности возникли технические неполадки

HTTP status code: 500
Response body:

{
"type":"Server.ServerProxy.DatabaseError",
"message":"Error accessing database (serverconf)",с
"detail":"3c4d0f08-440f-417f-b935-bc801e103d51"
}

HTTP headers:

Date: Thu, 21 Mar 2019 11:57:11 GMT
Content-Type: application/json;charset=utf-8
X-Road-Error: Server.ServerProxy.DatabaseError
Content-Length: 141''

Пример REST сервиса.

Сервис магазина питомцев описан по стандарту OPENAPI3. Полное описание сервиса смотрите в Приложении 1.
Пример предполагает, что serviceID привязан к https://petstore.niis.org/.

Обратите внимание: каждый запрос должен содержать аттрибут {protocol-version} (см. Формат запроса).
Например: r1

GET запрос

Сервис - /pets/{petId}
Метод - GET.
Описание - Находит питомца по ID.
Параметры - PetId – ID питомца.

Вызов сервиса через X-Road:

curl -X GET "https://{securityserver}/r1/{serviceId}/v2/pets/1124" -H "accept: application/json" -H "X-Road-Client: {client}"

Ответ:

{
"id": 1124,
"name": "Siddu",
"photoUrls": [],
"tags": [],
"status": "Offline"
}

Код статуса: 200
Заголовки:

Content-Type: application/json;charset=utf-8
Date: Thu, 21 Mar 2019 12:36:39 GMT
x-road-id: 29f4d011-ef17-4f2f-9bb1-0452ce17d3f5
x-road-client: DEV/COM/222/TESTCLIENT
x-road-service: DEV/COM/222/TESTSERVICE/petstore
x-road-request-id: f92591a3-6bf0-49b1-987b-0dd78c034cc3
x-road-request-hash: Xvx9V2U5c5RhDUiXpVLtW7L8vTd5cM2IOBU2n9efEk7/m3ECKyGAp7yTpJpTWpo6HcmwSaGO+cinxMVKjxJTOQ==
Content-Length: 1148

PUT запрос

Сервис - /pets/{petId}
Метод - PUT
Описание - Обновляет питомца по ID.
Параметры - body – объект Pet, который будет обновлён.

Вызов сервиса через X-Road:

curl -X PUT "https://{securityserver}/r1/{serviceId}/v2/pets/5657082955040009" -H "accept: application/json" -H "Content-Type: application/json"  -H "X-Road-Client: {client}" -d '{ "id": 0, "category": { "id": 0, "name": "string" }, "name": "doggie", "photoUrls": [ "string" ], "tags": [ { "id": 0, "name": "string" } ], "status": "available"}'

Ответ:

{
"id": 5657082955040009,
"category": {
  "id": 0,
  "name": "string"
},
"name": "doggie",
"photoUrls": [
  "string"
],
"tags": [
  {
    "id": 0,
    "name": "string"
  }
],
"status": "available"
}

Код статуса: 200
Заголовки:

Date: Thu, 21 Mar 2019 12:43:33 GMT
x-road-id: acdb2c7a-c705-41c2-b595-4cd62e78316e
x-road-client: DEV/COM/222/TESTCLIENT
x-road-service: DEV/COM/222/TESTSERVICE/petstore
x-road-request-id: f92591a3-6bf0-49b1-987b-0dd78c034cc3
x-road-request-hash: MOEfTqBjdqYiX3db9hxJ6JvHvCpYqfA6t0Uhdv6g2I29fMY8ld4CbN8tslj6mUQPXoRaUdPm7NdZeAYTg6zi+A==
Content-Length: 0

POST запрос

Сервис - /pets/{petId}
Метод - POST
Описание - Добавляет нового питомца.
Параметры - body – объект Pet, который будет добавлен.

Вызов сервиса через X-Road:

curl -X POST "https://{securityserver}/r1/{serviceId}/v2/pets" -H "accept: application/json" -H "Content-Type: application/json" -H "X-Road-Client: {client}" -d '{ "id": 0, "category": { "id": 0, "name": "string" }, "name": "doggie", "photoUrls": [ "string" ], "tags": [ { "id": 0, "name": "string" } ], "status": "available"}'

Ответ:

{
"id": 5657082955040122,
"category": {
  "id": 0,
  "name": "string"
},
"name": "doggie",
"photoUrls": [
  "string"
],
"tags": [
  {
    "id": 0,
    "name": "string"
  }
],
"status": "available"
}

Код статуса: 200
Заголовки:

Date: Thu, 21 Mar 2019 12:49:38 GMT
x-road-id: dcaaa3a2-a158-41e1-8775-309848052358
x-road-client: DEV/COM/222/TESTCLIENT
x-road-service: DEV/COM/222/TESTSERVICE/petstore
x-road-request-id: f92591a3-6bf0-49b1-987b-0dd78c034cc3
x-road-request-hash: VCNZdwTxl7m3XC6Mpfw1H6qJUtBcm3Y6tfCvg5b3W/fb2RRXsLF9wftR3u6ElclE+RFaiAN/OkSz02fAYbNKaw==
Content-Length: 0

POST запрос с приложением

Сервис - /pets/{petId}/images
Метод - POST
Описание - Загружает изображение питомца.
Параметры:
    • PetID – Id питомца.
    • AdditionalMetaData – дополнительная информация.
    • File – загружаемый файл.    

Вызов сервиса через X-Road:

curl -X POST "https://{securityserver}/r1/{serviceId}/v2/pets/1124/images" -H "accept: application/json" -H "Content-Type: multipart/form-data" -H "X-Road-client: {client}" -F "file=@A-fluffy-cat-looking-funny-surprised-or-concerned.jpg;type=image/jpeg"

Ответ:

{
"code":200,
"type":null,
"message":"additionalMetadata: null\nFile uploaded to ./file, 170025 bytes"
}

Код статуса: 200
Заголовки:

Content-Type: application/json;charset=utf-8
Date: Thu, 21 Mar 2019 13:02:29 GMT
x-road-id: 86e081a6-ec16-4b8d-b729-963f9659a80c
x-road-client: DEV/COM/222/TESTCLIENT
x-road-service: DEV/COM/222/TESTSERVICE/petstore
x-road-request-id: f92591a3-6bf0-49b1-987b-0dd78c034cc3
x-road-request-hash: EycIkZAz4WMvbKgnBvd0wUcN4A4w0RZMvugD36ZJ2PpwwGZuMGfxCoO4C0ZC3c4LBGF0rh61vunL3ssZV6TB3Q==
Content-Length: 100

DELETE запрос

Сервис - /pets/{petId}
Метод - DELETE.
Описание - Удаляет питомца.
Параметры - PetId – Id питомца.

Вызов сервиса через X-Road:

curl -X DELETE "https://{securityserver}/r1/{serviceId}/v2/pets/1124" -H "accept: application/json"  -H "X-Road-Client: {client}"

Ответ: <пусто>

Код статуса: 200
Заголовки:

Date: Thu, 21 Mar 2019 12:49:38 GMT
x-road-id: 6209d61b-6ab5-4443-a09a-b8d2a7c491b2
x-road-client: DEV/COM/222/TESTCLIENT
x-road-service: DEV/COM/222/TESTSERVICE/petstore
x-road-request-id: f92591a3-6bf0-49b1-987b-0dd78c034cc3
x-road-request-hash: lQBoldcyuI3BerjHfkleRQ45AyYoFlF7zXSN6yH/RwvTNWEcsTQM18EfqMxYfdkyGGB26oxAjAWv/AcfmZF7og==
Content-Length: 0

Приложение 1. Определение сервиса-примера

openapi: 3.0.0
info:
description: >-
  This is a sample server Petstore server.
version: 1.0.0
title: Petstore
contact:
  email: info@niis.org
license:
  name: Apache 2.0
  url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: pet
  description: Everything about your Pets
  externalDocs:
    description: Find out more
    url: 'https://niis.org'
- name: store
  description: Access to Petstore orders
- name: user
  description: Operations about user
  externalDocs:
    description: Find out more about our store
    url: 'https://niis.org'
paths:
/pets:
  get:
    tags:
      - pet
    summary: Get pets from store
    description: Search pets
    operationId: getPets
    parameters:
      - name: term
        in: query
        description: search term
        required: false
        schema:
          type: string
    responses:
      '200':
        description: successful operation
        content:
          application/xml:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Pet'
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Pet'
      '400':
        description: Invalid ID supplied
      '404':
        description: Pet not found
    security:
      - api_key: []
  post:
    tags:
      - pet
    summary: Add a new pet to the store
    description: ''
    operationId: addPet
    responses:
      '201':
        description: pet created
      '405':
        description: Invalid input
    security:
      - petstore_auth:
          - 'write:pets'
          - 'read:pets'
    requestBody:
      $ref: '#/components/requestBodies/Pet'
'/pets/{petId}':
  get:
    tags:
      - pet
    summary: Find pet by ID
    description: Returns a single pet
    operationId: getPetById
    parameters:
      - name: petId
        in: path
        description: ID of pet to return
        required: true
        schema:
          type: integer
          format: int64
    responses:
      '200':
        description: successful operation
        content:
          application/xml:
            schema:
              $ref: '#/components/schemas/Pet'
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
      '400':
        description: Invalid ID supplied
      '404':
        description: Pet not found
    security:
      - api_key: []
  put:
    tags:
      - pet
    summary: Update an existing pet
    description: ''
    operationId: updatePet
    parameters:
      - name: petId
        in: path
        description: ID of pet to return
        required: true
        schema:
          type: integer
          format: int64
    responses:
      '200':
        description: Pet updated
      '400':
        description: Invalid ID supplied
      '404':
        description: Pet not found
      '405':
        description: Validation exception
    security:
      - petstore_auth:
          - 'write:pets'
          - 'read:pets'
    requestBody:
      $ref: '#/components/requestBodies/Pet'
  delete:
    tags:
      - pet
    summary: Deletes a pet
    description: ''
    operationId: deletePet
    parameters:
      - name: api_key
        in: header
        required: false
        schema:
          type: string
      - name: petId
        in: path
        description: Pet id to delete
        required: true
        schema:
          type: integer
          format: int64
    responses:
      '200':
        description: Pet deleted
      '400':
        description: Invalid ID supplied
      '404':
        description: Pet not found
    security:
      - petstore_auth:
          - 'write:pets'
          - 'read:pets'
'/pets/{petId}/images':
  post:
    tags:
      - pet
    summary: Uploads an image
    description: ''
    operationId: uploadFile
    parameters:
      - name: petId
        in: path
        description: ID of pet to update
        required: true
        schema:
          type: integer
          format: int64
    responses:
      '200':
        description: successful operation
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiResponse'
    security:
      - petstore_auth:
          - 'write:pets'
          - 'read:pets'
    requestBody:
      content:
        multipart/form-data:
          schema:
            type: object
            properties:
              additionalMetadata:
                description: Additional data to pass to server
                type: string
              file:
                description: file to upload
                type: string
                format: binary
/store/inventories:
  get:
    tags:
      - store
    summary: Returns pet inventories by status
    description: Returns a map of status codes to quantities
    operationId: getInventory
    responses:
      '200':
        description: successful operation
        content:
          application/json:
            schema:
              type: object
              additionalProperties:
                type: integer
                format: int32
    security:
      - api_key: []
/store/orders:
  post:
    tags:
      - store
    summary: Place an order for a pet
    description: ''
    operationId: placeOrder
    responses:
      '200':
        description: successful operation
        content:
          application/xml:
            schema:
              $ref: '#/components/schemas/Order'
          application/json:
            schema:
              $ref: '#/components/schemas/Order'
      '400':
        description: Invalid Order
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Order'
      description: order placed for purchasing the pet
      required: true
'/store/orders/{orderId}':
  get:
    tags:
      - store
    summary: Find purchase order by ID
    description: >-
      For valid response try integer IDs with value >= 1 and <= 10.      
      Other values will generated exceptions
    operationId: getOrderById
    parameters:
      - name: orderId
        in: path
        description: ID of pet that needs to be fetched
        required: true
        schema:
          type: integer
          format: int64
          minimum: 1
          maximum: 10
    responses:
      '200':
        description: successful operation
        content:
          application/xml:
            schema:
              $ref: '#/components/schemas/Order'
          application/json:
            schema:
              $ref: '#/components/schemas/Order'
      '400':
        description: Invalid ID supplied
      '404':
        description: Order not found
  delete:
    tags:
      - store
    summary: Delete purchase order by ID
    description: >-
      For valid response try integer IDs with positive integer value.      
      Negative or non-integer values will generate API errors
    operationId: deleteOrder
    parameters:
      - name: orderId
        in: path
        description: ID of the order that needs to be deleted
        required: true
        schema:
          type: integer
          format: int64
          minimum: 1
    responses:
      '200':
        description: Purchase order deleted
      '400':
        description: Invalid ID supplied
      '404':
        description: Order not found
/users:
  post:
    tags:
      - user
    summary: Create user
    description: This can only be done by the logged in user.
    operationId: createUser
    responses:
      default:
        description: successful operation
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/User'
      description: Created user object
      required: true
/users/login:
  get:
    tags:
      - user
    summary: Logs user into the system
    description: ''
    operationId: loginUser
    parameters:
      - name: username
        in: query
        description: The user name for login
        required: true
        schema:
          type: string
      - name: password
        in: query
        description: The password for login in clear text
        required: true
        schema:
          type: string
    responses:
      '200':
        description: successful operation
        headers:
          X-Rate-Limit:
            description: calls per hour allowed by the user
            schema:
              type: integer
              format: int32
          X-Expires-After:
            description: date in UTC when token expires
            schema:
              type: string
              format: date-time
        content:
          application/xml:
            schema:
              type: string
          application/json:
            schema:
              type: string
      '400':
        description: Invalid username/password supplied
/users/logout:
  get:
    tags:
      - user
    summary: Logs out current logged in user session
    description: ''
    operationId: logoutUser
    responses:
      default:
        description: successful operation
'/users/{username}':
  get:
    tags:
      - user
    summary: Get user by user name
    description: ''
    operationId: getUserByName
    parameters:
      - name: username
        in: path
        description: 'The name that needs to be fetched. Use user1 for testing. '
        required: true
        schema:
          type: string
    responses:
      '200':
        description: successful operation
        content:
          application/xml:
            schema:
              $ref: '#/components/schemas/User'
          application/json:
            schema:
              $ref: '#/components/schemas/User'
      '400':
        description: Invalid username supplied
      '404':
        description: User not found
  put:
    tags:
      - user
    summary: Updated user
    description: This can only be done by the logged in user.
    operationId: updateUser
    parameters:
      - name: username
        in: path
        description: name that need to be updated
        required: true
        schema:
          type: string
    responses:
      '200':
        description: User updated
      '400':
        description: Invalid user supplied
      '404':
        description: User not found
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/User'
      description: Updated user object
      required: true
  delete:
    tags:
      - user
    summary: Delete user
    description: This can only be done by the logged in user.
    operationId: deleteUser
    parameters:
      - name: username
        in: path
        description: The name that needs to be deleted
        required: true
        schema:
          type: string
    responses:
      '200':
        description: User deleted
      '400':
        description: Invalid username supplied
      '404':
        description: User not found
externalDocs:
description: Find out more
url: 'https://niis.org'
servers:
- url: 'https://petstore.niis.org/v2'
- url: 'http://petstore.niis.org/v2'
components:
requestBodies:
  UserArray:
    content:
      application/json:
        schema:
          type: array
          items:
            $ref: '#/components/schemas/User'
    description: List of user object
    required: true
  Pet:
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Pet'
      application/xml:
        schema:
          $ref: '#/components/schemas/Pet'
    description: Pet object that needs to be added to the store
    required: true
securitySchemes:
  petstore_auth:
    type: oauth2
    flows:
      implicit:
        authorizationUrl: 'http://petstore.niis.org/oauth/dialog'
        scopes:
          'write:pets': modify pets in your account
          'read:pets': read your pets
  api_key:
    type: apiKey
    name: api_key
    in: header
schemas:
  Order:
    type: object
    properties:
      id:
        type: integer
        format: int64
      petId:
        type: integer
        format: int64
      quantity:
        type: integer
        format: int32
      shipDate:
        type: string
        format: date-time
      status:
        type: string
        description: Order Status
        enum:
          - placed
          - approved
          - delivered
      complete:
        type: boolean
        default: false
    xml:
      name: Order
  Category:
    type: object
    properties:
      id:
        type: integer
        format: int64
      name:
        type: string
    xml:
      name: Category
  User:
    type: object
    properties:
      id:
        type: integer
        format: int64
      username:
        type: string
      firstName:
        type: string
      lastName:
        type: string
      email:
        type: string
      password:
        type: string
      phone:
        type: string
      userStatus:
        type: integer
        format: int32
        description: User Status
    xml:
      name: User
  Tag:
    type: object
    properties:
      id:
        type: integer
        format: int64
      name:
        type: string
    xml:
      name: Tag
  Pet:
    type: object
    required:
      - name
      - photoUrls
    properties:
      id:
        type: integer
        format: int64
      category:
        $ref: '#/components/schemas/Category'
      name:
        type: string
        example: doggie
      photoUrls:
        type: array
        xml:
          name: photoUrl
          wrapped: true
        items:
          type: string
      tags:
        type: array
        xml:
          name: tag
          wrapped: true
        items:
          $ref: '#/components/schemas/Tag'
      status:
        type: string
        description: pet status in the store
        enum:
          - available
          - pending
          - sold
    xml:
      name: Pet
  ApiResponse:
    type: object
    properties:
      code:
        type: integer
        format: int32
      type:
        type: string
      message:
        type: string