-
Notifications
You must be signed in to change notification settings - Fork 399
MSC2246: Asynchronous media uploads #2246
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
Changes from all commits
b439277
a83c79c
9a395ed
29e3463
7cf22be
658aac8
bbd7d08
0bffcb7
4d009a9
1cbc04e
c65f2bf
63cef50
8ccf85f
12e907b
d582bb3
173edf3
f438754
725675c
d55f1f9
955177b
823fcca
3b00026
9627af2
045c21e
011031b
6cb7e31
fedc697
7652f59
098dd90
9559ab0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# MSC2246: Asynchronous media uploads | ||
Sending media to Matrix currently requires that clients first upload the media | ||
to the content repository and then send the event. This is a problem for some | ||
use cases, such as bridges that want to preserve message order, as reuploading | ||
a large file would block all messages. | ||
|
||
## Proposal | ||
This proposal proposes a way to send the event containing media before actually | ||
uploading the media, which would make the aformentioned bridge message order | ||
preservation possible without blocking all other messages behind a long upload. | ||
|
||
In the future, this new functionality could be used for streaming file | ||
transfers, as requested in [matrix-spec#432]. | ||
|
||
### Content repository behavior | ||
The proposal adds two new endpoints to the content repository API and modifies | ||
the download and thumbnail endpoints. | ||
|
||
#### `POST /_matrix/media/v1/create` | ||
Create a new MXC URI without content. Like `/upload`, this endpoint requires | ||
auth, can be rate limited, and returns the `content_uri` that can be used in | ||
events. | ||
|
||
The request body should be an empty JSON object. In the future, the body could | ||
be used for metadata about the file, such as the mime type or access control | ||
settings (related: [MSC701]). | ||
|
||
The server may optionally enforce a maximum age for unused media IDs to delete | ||
media IDs when the client doesn't start the upload in time, or when the upload | ||
was interrupted and not resumed in time. The server should include the maximum | ||
POSIX millisecond timestamp to complete the upload in the `unused_expires_at` | ||
field in the response JSON. The recommended default expiration is 24 hours which | ||
should be enough time to accommodate users on poor connection who find a better | ||
connection to complete the upload. | ||
|
||
##### Rate Limiting | ||
|
||
The server should rate limit requests to create media. | ||
|
||
The server should limit the number of concurrent *pending media uploads* a given | ||
user can have. A pending media upload is a created MXC URI that (a) is not | ||
expired (the `unused_expires_at` timestamp has not passed) and (b) the media has | ||
not yet been uploaded for. | ||
|
||
In both cases, the server should respond with `M_LIMIT_EXCEEDED` optionally | ||
providing details in the `error` field, but servers may wish to obscure the | ||
exact limits that are used and not provide such details. | ||
|
||
##### Example response | ||
```json | ||
{ | ||
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", | ||
"unused_expires_at": 1647257217083 | ||
} | ||
``` | ||
|
||
#### `PUT /_matrix/media/v3/upload/{serverName}/{mediaId}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the intention that this endpoint support the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. In the future those details could be moved to the /create call request body, but that wasn't defined here |
||
Upload content to a MXC URI that was created earlier. This endpoint requires | ||
auth. If the upload is successful, an empty JSON object and status code 200 is | ||
returned. Rate limiting additionally can apply here. | ||
|
||
If the endpoint is called with a media ID that already has content, the request | ||
should be rejected with the error code `M_CANNOT_OVERWRITE_MEDIA` and HTTP | ||
status code 409. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the upload fails but the bridge has already sent the message containing the MXC URI? This might need some more detail on exactly how multiple calls to the endpoint work, although it's been touched on in other comments. To throw a few ideas out:
For concurrent uploads, could leave it up to the HS to decide how to handle sensibly, possibly advising that later uploads kick off any started earlier? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fwiw, as the person currently holding this MSC up, I'm not too concerned with this case. The two most likely scenarios are:
There's certainly an argument for improved error handling though - I'll leave that to @dbkr & others to decide if it warrants an |
||
|
||
If the upload request comes from a user other than the one who created the media | ||
ID, the request should be rejected with an `M_FORBIDDEN` error. | ||
|
||
If the serverName/mediaId combination is not known, not local, or expired, an | ||
`M_NOT_FOUND` error is returned. | ||
|
||
If the MXC's `unused_expires_at` is reached before the upload completes, the | ||
server may either respond immediately with `M_NOT_FOUND` or allow the upload to | ||
continue. | ||
|
||
For other errors, such as file size, file type or user quota errors, the normal | ||
`/upload` rules apply. | ||
|
||
#### Changes to the `/download` and `/thumbnail` endpoints | ||
A new query parameter, `timeout_ms` is added to the endpoints that can | ||
download media. It's an integer that specifies the maximum number of | ||
milliseconds that the client is willing to wait to start receiving data. | ||
turt2live marked this conversation as resolved.
Show resolved
Hide resolved
|
||
The default value is 20000 (20 seconds). The content repository can and should | ||
impose a maximum value for this parameter. The content repository can also | ||
choose to respond before the timeout if it desires. | ||
|
||
If the media is available immediately (for example in the case of a | ||
non-asynchronous upload), the content repository should ignore this parameter. | ||
|
||
If the MXC has expired, the content repository should respond with `M_NOT_FOUND` | ||
and a HTTP 404 status code. | ||
|
||
If the data is not available when the server chooses to respond, the content | ||
repository returns a `M_NOT_YET_UPLOADED` error with a HTTP 504 status code. | ||
|
||
For the `/download` endpoint, the server could also stream data directly as it | ||
is being uploaded. However, streaming creates several implementation and spec | ||
complications (e.g. how to stream if the media repo has multiple workers, what | ||
to do if the upload is interrupted), so specifying exactly how streaming works | ||
is left for another MSC. | ||
Comment on lines
+96
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fwiw, this is sort of an implied feature from most sane media repo implementations: it's quite rare that a server ever wants to buffer the media into memory so will stream it from a cache, disk, or external service as it receives the data. By extension, they already will have had to figure out what stream interruption, multiple workers, etc means. However, clients obviously shouldn't rely on this functionality existing, but I also don't think it necessarily needs an MSC either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this paragraph has some hidden history, this is a change from the original requirement (include streaming) after I commented about some security concerns here. TLDR: it's not as much about being able to stream, but that the file is mutable and uncommitted while it's streamed, that there's an edge case where; if a client closes the upload stream, starts again, but then uploads a wholly different file, how should everything then fail/proceed gracefully to accommodate this new situation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that sounds like a thing we should fix as part of this MSC regardless, to be honest. Does a partial failed upload count as uploading the media? (ie: does the server mark the media as "uploaded" at the start or the request handling or at the end?) what happens if the client tries to upload the media twice (concurrently)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also poked at the "partial uploads" problem here; imo, there shouldn't be partial uploads, and the client should send Concurrent uploading is something I hadn't considered. |
||
|
||
## Potential issues | ||
Other clients may time out the download if the sender takes too long to upload | ||
media. | ||
|
||
## Alternatives | ||
|
||
## Security considerations | ||
turt2live marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The primary attack vector that must be prevented is a malicious user creating a | ||
large number of MXC URIs and sending them to a room without uploading the | ||
corresponding media. Clients in that room would then attempt to download the | ||
media, holding open connections to the server and potentially exhausting the | ||
number of available connections. | ||
|
||
This attack vector is stopped in multiple ways: | ||
|
||
1. Limits on `/create` prevent users from creating MXC URIs too quickly and also | ||
require them to finish uploading files (or let some of their MXCs expire) | ||
before creating new MXC URIs. | ||
|
||
2. Servers are free to respond to `/download` and `/thumbnail` requests before | ||
the `timeout_ms` has been reached and respond with `M_NOT_YET_UPLOADED`. For | ||
example, if the server is under connection count pressure, it can choose to | ||
respond to waiting download connections with `M_NOT_YET_UPLOADED` to free | ||
connections in the pool. | ||
|
||
3. Once the media is expired, servers can respond immediately to `/download` and | ||
`/thumbnail` requests with `M_NOT_FOUND`. | ||
|
||
## Future work | ||
|
||
Future MSCs might wish to address large file uploads. One approach would be to | ||
add metadata to the `/create` call via a query parameter (for example | ||
`?large_file_upload=true`. Servers would have the ability to impose restrictions | ||
on how many such "large file" uploads a user can have concurrently. For such a | ||
situation, the server would likely send a more generous `unused_expires_at` | ||
timestamp to allow for a long-running upload. | ||
|
||
## Unstable prefix | ||
While this MSC is not in a released version of the spec, implementations should | ||
use `fi.mau.msc2246` as a prefix and as an `unstable_features` flag in the | ||
`/versions` endpoint. | ||
|
||
* `POST /_matrix/media/unstable/fi.mau.msc2246/create` | ||
* `PUT /_matrix/media/unstable/fi.mau.msc2246/upload/{serverName}/{mediaId}` | ||
* `?fi.mau.msc2246.timeout_ms` | ||
* `FI.MAU.MSC2246_NOT_YET_UPLOADED` | ||
* `FI.MAU.MSC2246_CANNOT_OVERWRITE_MEDIA` | ||
|
||
[matrix-spec#432]: https://github.com/matrix-org/matrix-spec/issues/432 | ||
[MSC701]: https://github.com/matrix-org/matrix-doc/issues/701 |
Uh oh!
There was an error while loading. Please reload this page.