Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Websocket spec proposal #2881

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions proposals/2022-02-08-Websockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# WebSockets

## Metadata

| Tag | Value |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Proposal | [2022-02-08-Websockets](https://github.com/OAI/OpenAPI-Specification/tree/main/proposals/2022-02-08-Websockets.md) |
| Authors | [Balazs Edes](https://github.com/bali182) |
| Review Manager | TBD |
| Status | Proposal |
| Implementations | - |
| Issues | [#55](https://github.com/OAI/OpenAPI-Specification/issues/55) |
| Previous Revisions | - |

## Change Log

| Date | Responsible Party | Description |
| ---------- | ----------------- | ------------------- |
| 2022-02-08 | Balazs Edes | First draft created |

## Introduction

This proposal aims to make [WebSocket](https://hu.wikipedia.org/wiki/WebSocket) api descriptions part of OpenAPI.

## Motivation

Currently the OpenAPI standard is limited to describing what the http standard allows. More and more applications make use of WebSockets, which is a simple and standard way to build event driven systems, and runs in the most important platform end users see: the browser. Since there is demand for it, I propose to add the ability to describe WebSockets in OpenAPI schemas, as HTTP/WebSocket apis often complement each other.

There are other initiatives to describe event driven messaging (eg.: [AsyncAPI](https://www.asyncapi.com)), but in terms of maturity I haven't seen anything close to OpenAPI. AsyncAPI is also trying to acomplish a wide array of other goals which delay it's maturity further. Looking at the traction that #55 gained over the years, I think it would be a reasonable goal to add the ability to describe WebSockets in OpenAPI.

## Proposed solution

This proposal aims to add a new `WebSocket Payload Object` type to the spec, and 2 additional fields to the [Operation Object](https://swagger.io/specification/#operation-object), `publish` and `subscribe` (better name suggestions are wellcome), that would allow us describing how to connect to a ws server, and what messages can we expect to receive or send.

**Example**

```yaml
openapi: "3.3.0"
paths:
/sample-socket:
get:
operationId: "sampleSocket"
publish:
description: "Short description about the messages you can push towards the server"
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/CreateUserSchema"
- $ref: "#/components/schemas/DeleteUserSchema"
- $ref: "#/components/schemas/LikePostSchema"
subscribe:
description: "Short description about the messages you can receive from the server"
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/UserCreatedSchema"
- $ref: "#/components/schemas/UserDeletedSchema"
- $ref: "#/components/schemas/PostLikedSchema"
```

## Detailed design

The [WebSocket spec](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) is currently very simple. From our point of view it allows:

1. Connecting to an endpoint using a URL
2. Sending messages in either binary or text format
3. Receiving messages in either binary or text format

This proposal aims to allow OpenAPI users to describe exactly the above mentioned points:

1. What URL can I connect to, to talk to this WebSocket?
2. What's the format and shape of data I'm allowed to send
3. What's the format and shape of data I can expect to receive

**Important**: This OpenAPI spec proposal doesn't concern itself with any frameworks, or any higher level abstractions people usually build on top of event driven messaging protocols, like topics/subjects/channels, implied message serialization, etc. It simply describes the above mentioned 3 points, and any further abstractions can be built on top of this, but not part of the core schema.

### Extension of the current OpenAPI spec

- Add a `WebSocket Payload Object` type (very similar to [Request Body Object](https://swagger.io/specification/#request-body-object), except the `required` field - which makes no sense here). It has the following fields:
- `description`: `string` - A brief description of the payload.
- `content`: `Map[string, `[Media Type Object](https://swagger.io/specification/#media-type-object)`]` The content of the payload.
- Add an optional field in the [Components Object](https://swagger.io/specification/#components-object) type, so payloads can be reused:
- `webSocketPayloads`: `Map[string, WebSocket Payload Object]` - Reuseable payloads
- Add 2 optional fields on the [Operation Object](https://swagger.io/specification/#operation-object) type:
- `publish`: [Reference Object](https://swagger.io/specification/#reference-object) | `WebSocket Payload Object` - describes the message(s) the client can publish/push to the server.
- `subscribe`: [Reference Object](https://swagger.io/specification/#reference-object) | `WebSocket Payload Object` - describes the message(s) the client can receive from the server.
Comment on lines +86 to +88

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both fields can be optional, how can a tool that reads an OpenAPI document identify a web socket endpoint with certainty? If the OpenAPI document author did not supply either field, the endpoint is unrecognisable from a standard endpoint.

What should happen if those fields are added to an operation other than get? Is that possible?

Copy link
Author

@bali182 bali182 Feb 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I'd say if either publish or subscribe is present, then it's a socket enabled path. If neither is supplied, it should fall back to be recognized as a regular http path. Are you suggesting something more explicit (extra boolean field maybe)?

  2. I'd say error. Similar situation, as to what should happen if a GET request has a requestBody.

What do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, that would work. If anyone needs more, they can always create a proposal for it!


### Simple chat example

```yaml
openapi: "3.3.0"
components:
schemas:
ChatMessage:
type: "object"
required:
- "from"
- "to"
- "message"
properties:
from:
type: "string"
to:
type: "string"
message:
type: "string"
paths:
/chat-using-socket:
get:
operationId: "chatSocket"
publish:
description: "You can publish chat messages to this server"
content:
application/json:
schema:
$ref: "#/components/schemas/ChatMessage"
subscribe:
description: "You can expect chat messages from the server"
content:
application/json:
schema:
$ref: "#/components/schemas/UserCreatedSchema"
```

### Simple chat, but reusing payloads

```yaml
openapi: "3.3.0"
components:
schemas:
ChatMessage:
type: "object"
required:
- "from"
- "to"
- "message"
properties:
from:
type: "string"
to:
type: "string"
message:
type: "string"
webSocketPayloads:
ChatMessagePayload:
description: "A chat message payload"
content:
application/json:
schema:
$ref: "#/components/schemas/ChatMessage"
paths:
/chat-using-socket:
get:
operationId: "chatSocket"
publish:
$ref: "#/components/webSocketPayloads/ChatMessagePayload"
subscribe:
$ref: "#/components/webSocketPayloads/ChatMessagePayload"
```

### Multiple message types

Note since channels/topics/subjects cannot be expressed we can use a union type with a discriminator to mimic this.

```yaml
openapi: '3.3.0'
components:
schemas:
CreateUserSchema: '...'
DeleteUserSchema: '...'
LikePostSchema: '...'
PublishMessageSchema:
discriminator:
propertyName: 'action'
mapping:
CreateUser: '#/components/schemas/CreateUserSchema'
CreateUser: '#/components/schemas/CreateUserSchema'
LikePost: '#/components/schemas/LikePostSchema'
oneOf:
- $ref: '#/components/schemas/CreateUserSchema'
- $ref: '#/components/schemas/DeleteUserSchema'
- $ref: '#/components/schemas/LikePostSchema'
UserCreatedSchema: '...'
UserDeletedSchema: '...'
PostLikedSchema: '...'
SubscribeMessageSchema:
discriminator:
propertyName: 'type'
mapping:
UserCreated: '#/components/schemas/UserCreatedSchema'
UserDeleted: '#/components/schemas/UserDeletedSchema'
PostLiked: '#/components/schemas/PostLikedSchema'
oneOf:
- $ref: '#/components/schemas/UserCreatedSchema'
- $ref: '#/components/schemas/UserDeletedSchema'
- $ref: '#/components/schemas/PostLikedSchema'
paths:
/sample-socket:
get:
operationId: 'sampleSocket'
publish:
description: 'Short description about the messages you can push towards the server'
content:
application/json:
schema:
- $ref: '#/components/schemas/PublishMessageSchema'
subscribe:
description: 'Short description about the messages you can receive from the server'
content:
application/json:
schema:
- $ref: '#/components/schemas/SubscribeMessageSchema'

```

## Backwards compatibility

As this change is additive (nothing is removed or modified in a non-backward compatible way) I don't see any backward compatibilty issues.

## Alternatives considered

There can be many alternatives, my motivation was to add support for WebSocket api descriptions, while

- Adding as few new concepts as possible
- Allowing reuse the same way as with all other parts of the schema, through `$ref`s
- Just describing what the standard allows (no frameworks)
- Not causing any breaking changes or backward compatibility issues
- I'm happy to take onboard any suggestions, alternatives, modifications to this! I'm sure there are many things I haven't thought of.