Class: Rack::Tail::RequestHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/tail/request_handler.rb

Constant Summary collapse

SEPS =
Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
ALLOWED_VERBS =
%w[GET HEAD]
F =
::File

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env, root, headers, default_mime, default_lines) ⇒ RequestHandler

Returns a new instance of RequestHandler.



20
21
22
23
24
25
26
# File 'lib/rack/tail/request_handler.rb', line 20

def initialize(env, root, headers, default_mime, default_lines)
  @root = Pathname.new(root)
  @headers = headers
  @default_mime = default_mime
  @default_lines = default_lines
  @env = env
end

Instance Attribute Details

#default_linesObject

Returns the value of attribute default_lines.



18
19
20
# File 'lib/rack/tail/request_handler.rb', line 18

def default_lines
  @default_lines
end

#envObject

Returns the value of attribute env.



14
15
16
# File 'lib/rack/tail/request_handler.rb', line 14

def env
  @env
end

#pathObject

Returns the value of attribute path.



17
18
19
# File 'lib/rack/tail/request_handler.rb', line 17

def path
  @path
end

#rangeObject

Returns the value of attribute range.



16
17
18
# File 'lib/rack/tail/request_handler.rb', line 16

def range
  @range
end

#rootObject

Returns the value of attribute root.



15
16
17
# File 'lib/rack/tail/request_handler.rb', line 15

def root
  @root
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


45
46
47
48
49
50
51
# File 'lib/rack/tail/request_handler.rb', line 45

def available?
  begin
    F.file?(path) && F.readable?(path)
  rescue SystemCallError
    false
  end
end

#callObject



30
31
32
33
34
35
36
37
38
39
# File 'lib/rack/tail/request_handler.rb', line 30

def call
  return fail(405, "Method Not Allowed") unless method_allowed?
  return fail(403, "Forbidden") unless path_is_within_root?

  if available?
    serving
  else
    fail(404, "File not found: #{path_info}")
  end
end

#eachObject



123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rack/tail/request_handler.rb', line 123

def each
  F.open(path, "rb") do |file|
    file.seek(range.begin)
    remaining_len = range.end-range.begin+1
    while remaining_len > 0
      part = file.read([8192, remaining_len].min)
      break unless part
      remaining_len -= part.length

      yield part
    end
  end
end

#method_allowed?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/rack/tail/request_handler.rb', line 41

def method_allowed?
  ALLOWED_VERBS.include? env["REQUEST_METHOD"]
end

#path_infoObject



53
54
55
# File 'lib/rack/tail/request_handler.rb', line 53

def path_info
  Utils.unescape(env["PATH_INFO"])
end

#path_is_within_root?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/rack/tail/request_handler.rb', line 57

def path_is_within_root?
  !path.relative_path_from(root).to_s.split(SEPS).any?{|p| p == ".."}
end

#requested_lines_sizeObject



81
82
83
# File 'lib/rack/tail/request_handler.rb', line 81

def requested_lines_size
  (env.fetch("QUERY_STRING")[/\d+/] || default_lines).to_i
end

#requested_size(response) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rack/tail/request_handler.rb', line 99

def requested_size(response)
  # NOTE:
  #   We check via File::size? whether this file provides size info
  #   via stat (e.g. /proc files often don't), otherwise we have to
  #   figure it out by reading the whole file into memory.
  size = F.size?(path) || Utils.bytesize(F.read(path))

  # TODO handle invalid lines eg. lines=-3
  tail_size = tail_size_for requested_lines_size

  if tail_size == size
    response[0] = 200
    @range = 0..size-1
  else
    start_byte = size - tail_size - 1
    @range = start_byte..size-1
    response[0] = 206
    response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
    size = @range.end - @range.begin + 1
  end

  size
end

#servingObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/rack/tail/request_handler.rb', line 65

def serving
  last_modified = F.mtime(path).httpdate
  return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified

  headers = { "Last-Modified" => last_modified }
  mime = Mime.mime_type(F.extname(path), @default_mime)
  headers["Content-Type"] = mime if mime

  # Set custom headers
  @headers.each { |field, content| headers[field] = content } if @headers

  response = [ 200, headers, env["REQUEST_METHOD"] == "HEAD" ? [] : self ]
  response[1]["Content-Length"] = requested_size(response).to_s
  response
end

#tail_size_for(line_count) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rack/tail/request_handler.rb', line 85

def tail_size_for line_count
  elif = Elif.new(path)
  tail_size = 0
  line_count.times do
    begin
      tail_size += Rack::Utils.bytesize(elif.readline)
    rescue EOFError
      return tail_size
    end
  end
  tail_size - 1 # Don't include the first \n
end