Kept the prototype out of search engines in the Website service.
This commit is contained in:
1 parent
359fe0d6ac
commit
326299fc35
12 files changed
+232
-44
No files matched your search
@@ -64,6 +64,7 @@ func application(
|
||||
cacheControl: reader.cacheControl,
|
||||
compressionMinResponseSize: reader.compressionMinResponseSize,
|
||||
httpsRedirect: reader.httpsRedirect,
|
||||
isIndexable: reader.isIndexable,
|
||||
rateLimit: reader.rateLimit,
|
||||
securityHeaders: reader.securityHeaders,
|
||||
// An unset origin leaves the pages without canonical URLs and language alternates, rather than
|
||||
@@ -181,6 +182,7 @@ private func logger(
|
||||
/// - analytics: the analytics tracker both pages embed, or `nil` to omit it.
|
||||
/// - cacheControl: the cache-control directives applied to the served static files.
|
||||
/// - compressionMinResponseSize: the minimum response body size, in bytes, before compression is applied.
|
||||
/// - isIndexable: whether search engines may index the site; while `false`, every response is marked `noindex`.
|
||||
/// - httpsRedirect: the origin plain-HTTP requests are redirected to, and whether the forwarded-protocol header is trusted.
|
||||
/// - rateLimit: the rate limit configuration, currently applied to no route.
|
||||
/// - securityHeaders: the security headers applied to every response.
|
||||
@@ -201,6 +203,7 @@ private func router(
|
||||
cacheControl: CacheControl,
|
||||
compressionMinResponseSize: Int,
|
||||
httpsRedirect: HTTPSRedirectMiddleware<AppRequestContext>.Configuration,
|
||||
isIndexable: Bool,
|
||||
rateLimit: RateLimitMiddleware<AppRequestContext>.Configuration,
|
||||
securityHeaders: SecurityHeadersMiddleware<AppRequestContext>.Configuration,
|
||||
siteOrigin: String?,
|
||||
@@ -241,6 +244,11 @@ private func router(
|
||||
await home.refresh(with: .init(from: listings))
|
||||
}
|
||||
|
||||
// Outermost, so the mark reaches redirects, errors, and served files alike.
|
||||
if !isIndexable {
|
||||
router.add(middleware: NoIndexMiddleware())
|
||||
}
|
||||
|
||||
router.addMiddleware {
|
||||
LogRequestsMiddleware(logLevel)
|
||||
SecurityHeadersMiddleware(
|
||||
|
||||
@@ -264,6 +264,16 @@ package extension ConfigReader {
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether search engines may index the site, read from the `site.indexable` key.
|
||||
///
|
||||
/// Off by default: the prototype holds demo data, and a sign-in anyone can use. While off, every response is marked `noindex`.
|
||||
var isIndexable: Bool {
|
||||
bool(
|
||||
forKey: .Site.indexable,
|
||||
default: false
|
||||
)
|
||||
}
|
||||
|
||||
/// The origin passkeys are bound to, read from the `passkeys.origin` key.
|
||||
var passkeysOrigin: String {
|
||||
string(
|
||||
|
||||
@@ -82,6 +82,11 @@ extension AbsoluteConfigKey {
|
||||
/// The absolute configuration key for the directory the static files are served from.
|
||||
public static let staticFiles: AbsoluteConfigKey = .init(.Path.staticFiles)
|
||||
}
|
||||
/// A namespace for the site configuration keys, as absolute keys.
|
||||
public enum Site {
|
||||
/// The absolute configuration key for letting search engines index the site.
|
||||
public static let indexable: AbsoluteConfigKey = .init(.Site.indexable)
|
||||
}
|
||||
/// A namespace for the security headers configuration keys, as absolute keys.
|
||||
public enum Security {
|
||||
/// The absolute configuration key for the `Content-Security-Policy` header value.
|
||||
|
||||
@@ -107,5 +107,7 @@ extension ConfigKey {
|
||||
public enum Site {
|
||||
/// The configuration key for the public origin the site is served at (scheme and host, no trailing slash).
|
||||
public static let origin: ConfigKey = "site.origin"
|
||||
/// The configuration key for letting search engines index the site.
|
||||
public static let indexable: ConfigKey = "site.indexable"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import Hummingbird
|
||||
import Infrastructure
|
||||
|
||||
/// Keeps the site out of search engines: every response — pages, files, redirects, errors — is marked `X-Robots-Tag: noindex, nofollow`.
|
||||
///
|
||||
/// A header rather than `robots.txt`, which stays open on purpose: a crawler barred from a page never reads its `noindex`, and may still
|
||||
/// list the bare URL from links elsewhere. A header also covers what carries no markup, such as the photos sellers upload.
|
||||
public struct NoIndexMiddleware<Context: RequestContext>: RouterMiddleware {
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
/// The directive stamped on every response.
|
||||
public static var directive: String { "noindex, nofollow" }
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates the middleware.
|
||||
public init() {}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
public func handle(
|
||||
_ request: Request,
|
||||
context: Context,
|
||||
next: (Request, Context) async throws -> Response
|
||||
) async throws -> Response {
|
||||
do {
|
||||
var response = try await next(request, context)
|
||||
|
||||
response.headers[.robotsTag] = Self.directive
|
||||
|
||||
return response
|
||||
} catch let error as HTTPError {
|
||||
// An error thrown past this point becomes a response further out: the mark travels with it.
|
||||
var error = error
|
||||
|
||||
error.headers[.robotsTag] = Self.directive
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user