Class: Above::MiddleWare::Static

Inherits:
Object
  • Object
show all
Defined in:
lib/above/middleware/static.rb

Overview

Middleware to serve static files

Instance Method Summary collapse

Constructor Details

#initialize(app:) ⇒ Static

Returns a new instance of Static.



7
8
9
# File 'lib/above/middleware/static.rb', line 7

def initialize(app:)
  @app = app # who you gonna call
end

Instance Method Details

#call(env:) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/above/middleware/static.rb', line 11

def call(env:)
  # env is {socket:, request:, config:, server:, valid:}

  status, header, body_arr = @app.call(env:) unless @app.nil? # usually nothing to call from here

  # If redirect or cache fired we wouldn't get to here in the default arrangemnet. Usually wouldn't have any
  # other middleware to call after this one. If indeed we don't, or if that didn't create a response, try to get the file requested
  if status.nil?
    response(env:)
  else
    [status, header, body_arr]
  end
end

#format_response(maybe_incomplete_triplet:, env:) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/above/middleware/static.rb', line 25

def format_response(maybe_incomplete_triplet:, env:)
  lang = env[:config]["Above::MiddleWare::Static"]["lang"]
  status, header, content_arr = maybe_incomplete_triplet
  if status < 20 || status > 59
    # TODO: input requests (from some other middleware?) not currently handled
    [42, Status::CODE[42], []]
  elsif status > 39
    # Ensure errors have some header text
    header = Status::CODE[status] if header.nil?
    [status, header, content_arr]
  elsif header.start_with?("text/")
    # TODO: multipart, streaming etc
    # header here is the mime_type, from #read_file
    content = content_arr.first.encode(Encoding.find("UTF-8"), invalid: :replace, undef: :replace, replace: "")
    [status, "#{header}; charset=utf-8; lang=#{lang}; size=#{content.length}", [content]]
  else
    [status, "#{header}; size=#{content_arr.first.length}", content_arr]
  end
end

#read_file(request:, env:, again: false) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/above/middleware/static.rb', line 45

def read_file(request:, env:, again: false)
  config = env[:config]["Above::MiddleWare::Static"]
  path = File.join(File.expand_path(config["docroot"]), request.path)
  mime_type = Mime.type(path:) # TODO - maybe replace marcel, this reads files twice...
  content = File.read(path)
  # Maybe TODO: multi part, streaming etc
  # nb header is incomplete at this point
  [20, mime_type, [content]]
rescue Errno::EISDIR
  if !again
    try_index(request:, env:, index: config["index"])
  else
    [51, Status::CODE[51], []]
  end
rescue Errno::ENOENT
  [51, Status::CODE[51], []]
end

#response(env:) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/above/middleware/static.rb', line 63

def response(env:)
  host = env[:server]["domain"]
  request = env[:request]
  response = if !env[:valid]
    # Bad request caught earlier
    [59, Status::CODE[59], []]
  elsif request.to_s.length > 1024
    # Request longer than maximum length
    [59, Status::CODE[59], []]
  elsif request.scheme != "gemini" || request.host != host
    # Currently - refuse proxy requests for different protocols or servers, maybe TODO
    [53, Status::CODE[53], []]
  else
    maybe_incomplete_triplet = read_file(request:, env:)
    format_response(maybe_incomplete_triplet:, env:)
  end
rescue NoMethodError
  # eg trying to encode nil content
  response = [40, Status::CODE[40], []]
rescue
  # Bad uri, no scheme etc that got this far
  response = [59, Status::CODE[59], []]
ensure
  response
end

#try_index(request:, env:, index:) ⇒ Object



89
90
91
92
93
# File 'lib/above/middleware/static.rb', line 89

def try_index(request:, env:, index:)
  new_request = request.dup
  new_request.path = File.join(request.path, index)
  read_file(request: new_request, env:, again: true)
end