//Оригинал статьи: [[https://github.com/nordic-institute/X-Road/blob/develop/doc/Protocols/pr-rest_x-road_message_protocol_for_rest.md|X-Road: Message Protocol for REST]] // //Автор: Владимир Недорослев// ====== REST сервисы в X-Road ====== Все REST сервисы должны быть описаны в соответствии со стандартом [[https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md|OPENAPI3]]. Пример правильно описанного сервиса смотрите в приложении. ====== Обращение к REST сервису ====== Используется протокол HTTP версии 1.1. Участник/Подсистема отправителя указываются в заголовке HTTP. Используемый сервис указывается в самом URL в виде параметров. ===== Формат запроса ===== {http-request-method} /{protocol-version}/{serviceId}[/path][?query-parameters] * **{http-request-method}** — один из методов, определённых стандартом [[https://tools.ietf.org/html/rfc7231|RFC7231]]. Например: GET, POST, PUT, DELETE. * **{protocol-version}** — версия Протокола Сообщений X-Road для REST. По умолчанию **r1**. * **{serviceId}** — вызываемый сервис. Состоит из следующих частей: * ''[X-Road instance]/[member class]/[member code]/[subsystem code]/[service code]''.\\ * **[Subsystem code]** является не обязательным. * **[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**: Участник/Подсистема, которые обратились к сервису. * **X-Road-Service**: ID вызванного сервиса. * ** X-Road-Id**: Уникальный идентификатор данного сообщения. * **X-Road-Request-Hash**: Закодированное с помощью SHA-512 сообщения запроса. * **X-Road-Error**: Этот заголовок предоставляется при возникновении ошибок со стороны X-Road. * **X-Road-Request-Id**: Уникальный идентификатор данного запроса. Например: 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-request-method}**: GET * **{protocol-version}**: /r1 * **{client}**: INSTANCE/CLASS1/MEMBER1/SUBSYSTEM1 * **{serviceId}**: /INSTANCE/CLASS2/MEMBER2/SUBSYSTEM2/BARSERVICE * **[path]**: /v1/bar/zyggy * **[query-parameters]**: ?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 типа: - Сервис вернул ошибку. Код состояния, тело ответа и заголовки HTTP создаются на стороне сервиса. - На стороне сервиса возникли технические неполадки и он не может дать ответ. Код статуса 500 (Internal Server Error). - Клиент послал запрос, не соответствующий протоколу сообщений X-Road для REST. Код статуса 400 (Bad Request) - На стороне сервера безопасности возникли технические неполадки. Код статуса 500. Для того чтобы различить, на чьей стороне возникла ошибка при коде статуса 500, необходимо обращать внимание на заголовок **X-Road-Error**: Если заголовок X-Road-Error отсутствует, то ошибка возникла на стороне сервиса, иначе – со стороны X-Road. В таком случае, дополнительная информация отображается в поле type в теле ответа. Например: * ''Server.ServerProxy.ServiceFailed'' означает, что Сервер Безопасности не получил ответ от сервиса. * ''Server.ServerProxy.DatabaseError'' означает, что на Сервере Безопасности возникла внутренняя ошибка. * ''Server.ClientProxy.OutdatedGlobalConf'' указывает на проблему со стороны обратившегося Сервера Безопасности. ===== Примеры ошибок ===== **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 сервиса. ====== Сервис магазина питомцев описан по стандарту [[https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md|OPENAPI3]]. Полное описание сервиса смотрите в Приложении 1.\\ Пример предполагает, что serviceID привязан к https://petstore.niis.org/. **__Обратите внимание:__ каждый запрос должен содержать аттрибут {protocol-version} (см. [[about-rest-detailed#Формат запроса|Формат запроса]]).**\\ **Например:** ''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