Aggiungere target="blank" sui link esterni con un Rack middleware
Quante volte avete sentito, magari a termine dei lavori, la richiesta "tutti i link verso l'esterno dovrebbero aprirsi in un tab separato"? Questo è un tipico esempio di lavoro tremendamente noioso da fare per vie canoniche -- perchè richiederebbe un editing di tutti i link presenti in tutte le viste -- ma banale da realizzare passando per un middleware Rack.
In basso il codice. Il middleware usa Nokogiri per parsare tutte le pagine HTML (quelle con Content-Type impostato a text/html), e per ogni link trovato controlla il dominio: se non coincide con quello del server, aggiunge il fatidico attributo target al link.
1 require 'nokogiri'
2
3 module Rack
4 class TargetBlank
5 include Rack::Utils
6
7 def initialize(app)
8 @app = app
9 end
10
11 def call(env)
12 @request = Rack::Request.new(env)
13 status, @headers, @body = @app.call(env)
14 @headers = HeaderHash.new(@headers)
15 if is_html_content?
16 body = edit_external_links(body_to_string)
17 update_response_body(body)
18 update_content_length
19 end
20 [status, @headers, @body]
21 end
22
23 private
24
25 def edit_external_links(body)
26 doc = Nokogiri::HTML(body)
27 found_links = false
28 doc.css('a[href]').each do |link|
29 uri = URI(link['href'])
30 if uri.absolute? and uri.host != @request.host
31 link['target'] = 'blank'
32 found_links = true
33 end
34 end
35 found_links ? doc.to_html : body
36 end
37
38 def body_to_string
39 s = ""
40 @body.each { |x| s << x }
41 s
42 end
43
44 def update_content_length
45 length = 0
46 @body.each { |s| length += Rack::Utils.bytesize(s) }
47 @headers['Content-Length'] = length.to_s
48 end
49
50 def update_response_body(body)
51 if @body.class.name == "ActionController::Response"
52 @body.body = body
53 else
54 @body = [body]
55 end
56 end
57
58 def is_html_content?
59 @headers.key?('Content-Type') && @headers['Content-Type'].include?('text/html')
60 end
61 end
62 end