fix: Don't fail entire preview requests when auxiliary requests fail #2448

Open
nex wants to merge 9 commits from nex/fix/nonatomic-url-previews into main
Owner

#2437 was really annoying me so I fixed it, and also added additional opengraph tags to improve spec compliance (although this isn't a replacement for arbitrary OG tags, I'll do that in another PR since I unfortunately saw how awful our url preview storage is)

Fixes #2437

Pull request checklist:

  • This pull request targets the main branch, and the branch is named something other than
    main.
  • I have written an appropriate pull request title and my description is clear.
  • I understand I am responsible for the contents of this pull request.
  • I have followed the contributing guidelines:
#2437 was really annoying me so I fixed it, and also added additional opengraph tags to improve spec compliance (although this isn't a replacement for arbitrary OG tags, I'll do that in another PR since I unfortunately saw how awful our url preview storage is) Fixes #2437 **Pull request checklist:** <!-- You need to complete these before your PR can be considered. If you aren't sure about some, feel free to ask for clarification in #dev:continuwuity.org. --> - [x] This pull request targets the `main` branch, and the branch is named something other than `main`. - [x] I have written an appropriate pull request title and my description is clear. - [x] I understand I am responsible for the contents of this pull request. - I have followed the [contributing guidelines][c1]: - [x] My contribution follows the [code style][c2], if applicable. - [x] I ran [pre-commit checks][c1pc] before opening/drafting this pull request. - [x] I have [tested my contribution][c1t] (or proof-read it for documentation-only changes) myself, if applicable. This includes ensuring code compiles. - [x] My commit messages follow the [commit message format][c1cm] and are descriptive. <!-- Notes on these requirements: - While not required, we encourage you to sign your commits with GPG or SSH to attest the authenticity of your changes. - While we allow LLM-assisted contributions, we do not appreciate contributions that are low quality, which is typical of machine-generated contributions that have not had a lot of love and care from a human. Please do not open a PR if all you have done is asked ChatGPT to tidy up the codebase with a +-100,000 diff. - In the case of code style violations, reviewers may leave review comments/change requests indicating what the ideal change would look like. For example, a reviewer may suggest you lower a log level, or use `match` instead of `if/else` etc. - In the case of code style violations, pre-commit check failures, minor things like typos/spelling errors, and in some cases commit format violations, reviewers may modify your branch directly, typically by making changes and adding a commit. Particularly in the latter case, a reviewer may rebase your commits to squash "spammy" ones (like "fix", "fix", "actually fix"), and reword commit messages that don't satisfy the format. - Pull requests MUST pass the `Checks` CI workflows to be capable of being merged. This can only be bypassed in exceptional circumstances. If your CI flakes, let us know in matrix:r/dev:continuwuity.org. - Pull requests have to be based on the latest `main` commit before being merged. If the main branch changes while you're making your changes, you should make sure you rebase on main before opening a PR. Your branch will be rebased on main before it is merged if it has fallen behind. - We typically only do fast-forward merges, so your entire commit log will be included. Once in main, it's difficult to get out cleanly, so put on your best dress, smile for the cameras! --> [c1]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md [c2]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/docs/development/code_style.mdx [c1pc]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md#pre-commit-checks [c1t]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md#running-tests-locally [c1cm]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md#commit-messages
nex added this to the 26.11.0 milestone 2026-11-03 19:19:58 +00:00
feat: Support most other specified opengraph tags
Some checks failed
Auto Labeler / Apply labels based on changed files (pull_request_target) Successful in 3s
Checks / Prek / Check changed files (pull_request) Successful in 5s
Checks / Changelog / Check changelog is added (pull_request_target) Failing after 7s
Checks / Prek / Pre-commit & Formatting (pull_request) Successful in 1m10s
Checks / Prek / Clippy and Cargo Tests (pull_request) Successful in 7m43s
Documentation / Build and Deploy Documentation (pull_request) Successful in 10m42s
c545965456
Member

unfortunately saw how awful our url preview storage is

i am planning on completely revamping this in the media rewrite (since the two codebases are very linked together), feel free to change it early but i might replace it a second time when i finish the rewrite

> unfortunately saw how awful our url preview storage is i am planning on completely revamping this in the media rewrite (since the two codebases are very linked together), feel free to change it early but i might replace it a second time when i finish the rewrite
Author
Owner

That's fine, my plan was to write a migration that

  1. Read all existing URL previews stored
  2. Deserialised them
  3. Re-stored them as raw JSON
  4. and then simply reading+writing JSON to the db instead of whatever weird byte splitting we're doing now.

I wouldn't be too distraught to see it improved further by a rewrite later

That's fine, my plan was to write a migration that 1. Read all existing URL previews stored 2. Deserialised them 3. Re-stored them as raw JSON 4. and then simply reading+writing JSON to the db instead of whatever weird byte splitting we're doing now. I wouldn't be too distraught to see it improved further by a rewrite later
@ -349,0 +351,4 @@
..Default::default() /* TODO(nex): This weird manual field de/serialisation just
* needs replacing with JSON storage to avoid this mess.
* This is consequently intentionally dropping information
* added later (like image alt text). I'll do it later. */
Member

This will be rewritten as part of the media rewrite, feel free to change this todo (and the one on L251) over to me

This will be rewritten as part of the media rewrite, feel free to change this todo (and the one on L251) over to me
Author
Owner

I'll cc you into the TODO but I'll leave it assigned to me also so that I don't forget to do my aforementioned interim migration

I'll cc you into the TODO but I'll leave it assigned to me also so that I don't forget to do my aforementioned interim migration
Member

sounds great

sounds great
nex marked this conversation as resolved
silversys approved these changes 2026-11-03 19:39:50 +00:00
Dismissed
Owner

@nex wrote in #2448 (comment):

That's fine, my plan was to write a migration that

1. Read all existing URL previews stored

2. Deserialised them

3. Re-stored them as raw JSON

4. and then simply reading+writing JSON to the db instead of whatever weird byte splitting we're doing now.

I wouldn't be too distraught to see it improved further by a rewrite later

whatever lets me delete all that data has my APPROVED

@nex wrote in https://forgejo.ellis.link/continuwuation/continuwuity/pulls/2448#issuecomment-38583: > That's fine, my plan was to write a migration that > > 1. Read all existing URL previews stored > > 2. Deserialised them > > 3. Re-stored them as raw JSON > > 4. and then simply reading+writing JSON to the db instead of whatever weird byte splitting we're doing now. > > > I wouldn't be too distraught to see it improved further by a rewrite later whatever lets me delete all that data has my APPROVED
Member

@nex wrote in #2448 (comment):

That's fine, my plan was to write a migration that

1. Read all existing URL previews stored

2. Deserialised them

3. Re-stored them as raw JSON

4. and then simply reading+writing JSON to the db instead of whatever weird byte splitting we're doing now.

I wouldn't be too distraught to see it improved further by a rewrite later

My current "plan" (if you can call if that) is to just use serde's standard serializer on the structs, and probably actually using the value instead of storing everything in the key (i believe previews used that same (stupid) method of serialization and storage).

(i might be misremembering, i have it written down somewhere but my memory is ass)

@nex wrote in https://forgejo.ellis.link/continuwuation/continuwuity/pulls/2448#issuecomment-38583: > That's fine, my plan was to write a migration that > > 1. Read all existing URL previews stored > > 2. Deserialised them > > 3. Re-stored them as raw JSON > > 4. and then simply reading+writing JSON to the db instead of whatever weird byte splitting we're doing now. > > > I wouldn't be too distraught to see it improved further by a rewrite later My current "plan" (if you can call if that) is to just use serde's standard serializer on the structs, and probably actually using the value instead of storing everything in the key (i believe previews used that same (stupid) method of serialization and storage). (i might be misremembering, i have it written down somewhere but my memory is ass)
silversys requested changes 2026-11-03 19:46:07 +00:00
Dismissed
silversys left a comment

missing changelog :^)

missing changelog :^)
@ -301,0 +336,4 @@
preview_data.image_height = image_data.image_height;
preview_data.image_type = obj
.properties
.get("image:type")
Contributor
no need for image: you can just use type https://docs.rs/webpage/2.1.0/src/webpage/opengraph.rs.html#55-88
nex marked this conversation as resolved
@ -301,0 +344,4 @@
.flatten();
preview_data.image_alt = obj
.properties
.get("image:alt")
Contributor

same here

same here
nex marked this conversation as resolved
@ -307,0 +360,4 @@
preview_data.video_size = video_data.video_size;
preview_data.video_type = obj
.properties
.get("video:type")
Contributor

same here

same here
nex marked this conversation as resolved
@ -307,0 +368,4 @@
.flatten();
preview_data.video_alt = obj
.properties
.get("video:alt")
Contributor

same here

same here
nex marked this conversation as resolved
@ -307,0 +355,4 @@
|e| info!(error=?e, asset_url=%obj.url, "Could not download URL preview video"),
) {
preview_data.video = video_data.video;
preview_data.video_width = video_data.video_width;
Contributor

not set by download_video so .get("width")

not set by download_video so .get("width")
nex marked this conversation as resolved
@ -219,6 +219,7 @@ pub(crate) async fn get_media_preview_route(
)));
}
info!("Fetching a URL preview for {url} on behalf of {sender_user}");
Contributor

debug! ?

debug! ?
Author
Owner

I don't think so, I'd like to know when my server is doing things on behalf of users, like making external requests (especially useful in untrusted environments like continuwuity.rocks). Continuwuity has a bad habit of under-logging so I'd like to avoid debug! et al unless the log is actually useful to debugging, not just normal operation

I don't think so, I'd like to know when my server is doing things on behalf of users, like making external requests (especially useful in untrusted environments like continuwuity.rocks). Continuwuity has a bad habit of under-logging so I'd like to avoid debug! et al unless the log is actually useful to *debugging*, not just normal operation
Owner

Requests should be logged at the router level, and this log should be a structured log anyway.

Requests should be logged at the router level, and this log should be a structured log anyway.
Author
Owner

My server receives an average of 10 requests per second with spikes into the thousands in some situations, and that's normal for a federated server. Expecting people to use the router logs for this stuff is infeasible

My server receives an average of 10 requests per second with spikes into the thousands in some situations, and that's normal for a federated server. Expecting people to use the router logs for this stuff is infeasible
@ -307,0 +356,4 @@
) {
preview_data.video = video_data.video;
preview_data.video_width = video_data.video_width;
preview_data.video_height = video_data.video_height;
Contributor

not set by download_video so .get("height")

not set by download_video so .get("height")
nex marked this conversation as resolved
@ -319,0 +390,4 @@
preview_data.determiner = props.get("determiner").cloned();
preview_data.locale = props.get("locale").cloned();
preview_data.site_name = props.get("site_name").cloned();
preview_data.site_type = props.get("type").cloned();
Contributor

i think it should be og_type here as it's an extended type

i think it should be og_type here as it's an extended type
nex marked this conversation as resolved
@ -319,0 +391,4 @@
preview_data.locale = props.get("locale").cloned();
preview_data.site_name = props.get("site_name").cloned();
preview_data.site_type = props.get("type").cloned();
preview_data.url = props.get("url").cloned().or_else(|| Some(url.to_owned()));
Contributor

this will always be None, webpage puts og:type into og_type and not into properties

this will always be None, webpage puts og:type into og_type and not into properties
nex marked this conversation as resolved
feat: Automatically populate UrlPreviewData, correctly populate media fields
Some checks failed
Checks / Changelog / Check changelog is added (pull_request_target) Successful in 6s
Checks / Prek / Check changed files (pull_request) Successful in 12s
Documentation / Build and Deploy Documentation (pull_request) Successful in 1m9s
Checks / Prek / Clippy and Cargo Tests (pull_request) Failing after 7m44s
Checks / Prek / Pre-commit & Formatting (pull_request) Successful in 21m12s
326d4a93d3
fix: Remove attachment metadata when the attachment has no URL
All checks were successful
Checks / Changelog / Check changelog is added (pull_request_target) Successful in 8s
Checks / Prek / Check changed files (pull_request) Successful in 23s
Documentation / Build and Deploy Documentation (pull_request) Successful in 1m5s
Checks / Prek / Pre-commit & Formatting (pull_request) Successful in 1m13s
Checks / Prek / Clippy and Cargo Tests (pull_request) Successful in 7m44s
e785d78b88
ginger approved these changes 2026-11-04 02:22:42 +00:00
@ -84,3 +84,3 @@
.timeout(Duration::from_secs(config.url_preview_timeout))
.redirect(redirect::Policy::limited(3))
.user_agent(url_preview_user_agent)
.user_agent(url_preview_user_agent.replace("<version>", conduwuit::version_ua()))
Owner

ew.

ew.
Author
Owner

I did this because the docstring for that option starts with continuwuity/<version> and I naively assumed that <version> would be substituted with the real version if I specified a custom UA, and then was taken by surprised when continuwuity/<version> literally appeared in my logs. Anything more than a replace will also then require a documentation update, which is wayy out of scope for this fix with some cheap wins

I did this because the docstring for that option starts with `continuwuity/<version>` and I naively assumed that `<version>` would be substituted with the real version if I specified a custom UA, and then was taken by surprised when `continuwuity/<version>` literally appeared in my logs. Anything more than a replace will also then require a documentation update, which is wayy out of scope for this fix with some cheap wins
ginger marked this conversation as resolved
@ -24,1 +25,4 @@
pub struct UrlPreviewData {
// From https://ogp.me/
/// The title of your object as it should appear within the graph
/// "The Rock".
Owner

why is dwayne johnson in my doc comment

why is dwayne johnson in my doc comment
Author
Owner

I copied the descriptions from the ogp website until I got bored

I copied the descriptions from the ogp website until I got bored
ginger marked this conversation as resolved
Aranjedeath approved these changes 2026-11-04 05:40:20 +00:00
Dismissed
Aranjedeath left a comment

works great!

works great!
silversys left a comment

I'd replace the info! with a structured log like jade suggested, other than that it lgtm

I'd replace the `info!` with a structured log like jade suggested, other than that it lgtm
silversys approved these changes 2026-11-04 14:31:57 +00:00
ginger force-pushed nex/fix/nonatomic-url-previews from e785d78b88
All checks were successful
Checks / Changelog / Check changelog is added (pull_request_target) Successful in 8s
Checks / Prek / Check changed files (pull_request) Successful in 23s
Documentation / Build and Deploy Documentation (pull_request) Successful in 1m5s
Checks / Prek / Pre-commit & Formatting (pull_request) Successful in 1m13s
Checks / Prek / Clippy and Cargo Tests (pull_request) Successful in 7m44s
to 79c5af78cd
Some checks failed
Checks / Changelog / Check changelog is added (pull_request_target) Successful in 7s
Documentation / Build and Deploy Documentation (pull_request) Successful in 1m2s
Checks / Prek / Check changed files (pull_request) Successful in 6s
Checks / Prek / Pre-commit & Formatting (pull_request) Failing after 1m16s
Update flake hashes / update-flake-hashes (pull_request) Successful in 1m23s
Checks / Prek / Clippy and Cargo Tests (pull_request) Successful in 7m45s
2026-11-05 15:37:01 +00:00
Compare
Jade requested review from Jade 2026-11-05 16:50:03 +00:00
Owner

most recent push fails rust format, but otherwise is ready to go and works extremely well.

most recent push fails rust format, but otherwise is ready to go and works extremely well.
Jade left a comment

TODO

TODO
@ -10,2 +10,3 @@
use conduwuit::{Err, Result, debug, err, utils::response::LimitReadExt};
use conduwuit::{Err, Result, debug, err, utils::response::LimitReadExt, info};
use ipaddress::IPAddress;
use lettre::message::header::ContentType;
Owner

we already depend on mime, use it directly

we already depend on mime, use it directly
Jade left a comment

I am too tired to be giving proper code review, but:

  • has this been tested with the url_preview feature disabled (or do we just want to remove that compile option?)
  • does the database caching actually store all the newly added fields?
  • can we priorities using trusted data to fill out the width/height/mime/etc fields automatically?
I am too tired to be giving proper code review, but: - has this been tested with the `url_preview` feature disabled (or do we just want to remove that compile option?) - does the database caching actually store all the newly added fields? - can we priorities using trusted data to fill out the width/height/mime/etc fields automatically?
@ -219,10 +219,15 @@ pub(crate) async fn get_media_preview_route(
)));
}
info!("Fetching a URL preview for {url} on behalf of {sender_user}");
Owner

(1) please use structured logging, (2) I think this is unlikely to be relevant to typical server admins, and is just going to be log spam

(1) please use structured logging, (2) I think this is unlikely to be relevant to typical server admins, and is just going to be log spam
@ -115,3 +154,1 @@
| audio if audio.starts_with("audio/") =>
self.download_audio(url.as_str(), None).await?,
| _ => return Err!(Request(Unknown("Unsupported Content-Type"))),
| img if img.starts_with("image/") => self.download_image(url.as_str()).await?,
Owner

can we also url.join(&obj.url) to deal with relative paths?

can we also `url.join(&obj.url)` to deal with relative paths?
@ -300,3 +345,1 @@
preview_data = self.download_video(&obj.url, Some(preview_data)).await?;
preview_data.video_width = obj.properties.get("width").and_then(|v| v.parse().ok());
preview_data.video_height = obj.properties.get("height").and_then(|v| v.parse().ok());
info!(asset_url=%obj.url, "Downloading preview video");
Owner

this is gonna log even when downloading audio/videos is disabled (same for in the audio function)

this is gonna log even when downloading audio/videos is disabled (same for in the audio function)
@ -315,0 +381,4 @@
preview_data.video_type = None;
preview_data.video_alt = None;
preview_data.video_height = None;
preview_data.video_width = None;
Owner

video_size (and image_size above?)

video_size (and image_size above?)
Some checks failed
Checks / Changelog / Check changelog is added (pull_request_target) Successful in 7s
Required
Details
Documentation / Build and Deploy Documentation (pull_request) Successful in 1m2s
Checks / Prek / Check changed files (pull_request) Successful in 6s
Required
Details
Checks / Prek / Pre-commit & Formatting (pull_request) Failing after 1m16s
Required
Details
Update flake hashes / update-flake-hashes (pull_request) Successful in 1m23s
Checks / Prek / Clippy and Cargo Tests (pull_request) Successful in 7m45s
Required
Details
This pull request has changes requested by an official reviewer.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 'nex/fix/nonatomic-url-previews:nex/fix/nonatomic-url-previews'
git switch 'nex/fix/nonatomic-url-previews'
Sign in to join this conversation.
No milestone
No assignees
6 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
continuwuation/continuwuity!2448
No description provided.