Skip to content

Commit ca371d1

Browse files
jshasyphar
authored andcommitted
Use header for canonical URLs on source pages
Since source pages can contain arbitrary files, and those files are auto-assigned content-types, they sometimes serve PDF files. Google considers those to be pages so they are subject to canonicalization; but we can't use the `<link rel="canonical">` method because they are PDFs. The header should work for all file types.
1 parent 4a1783b commit ca371d1

File tree

3 files changed

+33
-11
lines changed

3 files changed

+33
-11
lines changed

src/web/page/web_page.rs

+32-7
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@ use axum::{
1111
response::{IntoResponse, Response as AxumResponse},
1212
};
1313
use http::header::CONTENT_LENGTH;
14-
use iron::{headers::ContentType, response::Response, status::Status, IronResult, Request};
14+
use iron::{
15+
headers::{ContentType, Link, LinkValue, RelationType},
16+
response::Response,
17+
status::Status,
18+
IronResult, Request,
19+
};
1520
use serde::Serialize;
1621
use std::{borrow::Cow, sync::Arc};
1722
use tera::{Context, Tera};
1823

1924
/// When making using a custom status, use a closure that coerces to a `fn(&Self) -> Status`
2025
#[macro_export]
2126
macro_rules! impl_webpage {
22-
($page:ty = $template:literal $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => {
23-
$crate::impl_webpage!($page = |_| ::std::borrow::Cow::Borrowed($template) $(, status = $status)? $(, content_type = $content_type)?);
27+
($page:ty = $template:literal $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(, canonical_url = $canonical_url:expr)? $(,)?) => {
28+
$crate::impl_webpage!($page = |_| ::std::borrow::Cow::Borrowed($template) $(, status = $status)? $(, content_type = $content_type)? $(, canonical_url = $canonical_url)?);
2429
};
2530

26-
($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => {
31+
($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(, canonical_url = $canonical_url:expr)? $(,)?) => {
2732
impl $crate::web::page::WebPage for $page {
2833
fn template(&self) -> ::std::borrow::Cow<'static, str> {
2934
let template: fn(&Self) -> ::std::borrow::Cow<'static, str> = $template;
@@ -37,6 +42,14 @@ macro_rules! impl_webpage {
3742
}
3843
)?
3944

45+
46+
$(
47+
fn canonical_url(&self) -> Option<String> {
48+
let canonical_url: fn(&Self) -> Option<String> = $canonical_url;
49+
(canonical_url)(self)
50+
}
51+
)?
52+
4053
$(
4154
fn content_type() -> ::iron::headers::ContentType {
4255
$content_type
@@ -48,11 +61,11 @@ macro_rules! impl_webpage {
4861

4962
#[macro_export]
5063
macro_rules! impl_axum_webpage {
51-
($page:ty = $template:literal $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => {
52-
$crate::impl_axum_webpage!($page = |_| ::std::borrow::Cow::Borrowed($template) $(, status = $status)? $(, content_type = $content_type)?);
64+
($page:ty = $template:literal $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(, canonical_url = $canonical_url:expr)? $(,)?) => {
65+
$crate::impl_axum_webpage!($page = |_| ::std::borrow::Cow::Borrowed($template) $(, status = $status)? $(, content_type = $content_type)? $(, canonical_url = $canonical_url:expr)?);
5366
};
5467

55-
($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => {
68+
($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(, canonical_url = $canonical_url:expr)? $(,)?) => {
5669
impl axum::response::IntoResponse for $page
5770
{
5871
fn into_response(self) -> ::axum::response::Response {
@@ -134,12 +147,24 @@ pub trait WebPage: Serialize + Sized {
134147
response.extensions.insert::<CachePolicy>(cache);
135148
}
136149

150+
if let Some(canonical_url) = self.canonical_url() {
151+
let link_value = LinkValue::new(canonical_url)
152+
.push_rel(RelationType::ExtRelType("canonical".to_string()));
153+
154+
response.headers.set(Link::new(vec![link_value]));
155+
}
156+
137157
Ok(response)
138158
}
139159

140160
/// The name of the template to be rendered
141161
fn template(&self) -> Cow<'static, str>;
142162

163+
/// The canonical URL to set in response headers
164+
fn canonical_url(&self) -> Option<String> {
165+
None
166+
}
167+
143168
/// Gets the status of the request, defaults to `Ok`
144169
fn get_status(&self) -> Status {
145170
Status::Ok

src/web/source.rs

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ struct SourcePage {
171171

172172
impl_webpage! {
173173
SourcePage = "crate/source.html",
174+
canonical_url = |page| Some(page.canonical_url.clone()),
174175
}
175176

176177
pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {

templates/crate/source.html

-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
{{ macros::doc_title(name=file_list.metadata.name, version=file_list.metadata.version) }}
66
{%- endblock title -%}
77

8-
{%- block meta -%}
9-
<link rel="canonical" href="{{ canonical_url | safe }}" />
10-
{%- endblock -%}
11-
128
{%- block topbar -%}
139
{%- set metadata = file_list.metadata -%}
1410
{%- set latest_version = "" -%}

0 commit comments

Comments
 (0)