Class: Rack::TailFile
- Inherits:
-
Object
- Object
- Rack::TailFile
- Defined in:
- lib/rack/tail_file.rb,
lib/rack/tail_file/version.rb
Overview
Rack::File serves files below the root
directory given, according to the path info of the Rack request. e.g. when Rack::File.new(“/etc”) is used, you can access ‘passwd’ file as localhost:9292/passwd
Handlers can detect if bodies are a Rack::File, and use mechanisms like sendfile on the path
.
Constant Summary collapse
- SEPS =
Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
- ALLOWED_VERBS =
%w[GET HEAD]
- F =
::File
- VERSION =
"0.0.2"
Instance Attribute Summary collapse
-
#cache_control ⇒ Object
Returns the value of attribute cache_control.
-
#path ⇒ Object
(also: #to_path)
Returns the value of attribute path.
-
#root(env) ⇒ Object
Returns the value of attribute root.
Instance Method Summary collapse
- #_call(env) ⇒ Object
- #available? ⇒ Boolean
- #call(env) ⇒ Object
- #each ⇒ Object
- #file_path(env) ⇒ Object
-
#initialize(root, headers = {}, default_mime = 'text/plain') ⇒ TailFile
constructor
A new instance of TailFile.
- #method_allowed?(env) ⇒ Boolean
- #path_info_for(env) ⇒ Object
- #path_is_within_root?(env) ⇒ Boolean
- #requested_lines_size(env) ⇒ Object
- #requested_size(env, response) ⇒ Object
- #serving(env) ⇒ Object
- #tail_size_for(line_count) ⇒ Object
- #target_file(env) ⇒ Object
Constructor Details
Instance Attribute Details
#cache_control ⇒ Object
Returns the value of attribute cache_control.
24 25 26 |
# File 'lib/rack/tail_file.rb', line 24 def cache_control @cache_control end |
#path ⇒ Object Also known as: to_path
Returns the value of attribute path.
23 24 25 |
# File 'lib/rack/tail_file.rb', line 23 def path @path end |
#root(env) ⇒ Object
Returns the value of attribute root.
22 23 24 |
# File 'lib/rack/tail_file.rb', line 22 def root @root end |
Instance Method Details
#_call(env) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/rack/tail_file.rb', line 40 def _call(env) return fail(405, "Method Not Allowed") unless method_allowed?(env) return fail(403, "Forbidden") unless path_is_within_root?(env) @path = file_path(env) if available? serving(env) else fail(404, "File not found: #{path_info_for(env)}") end end |
#available? ⇒ Boolean
57 58 59 60 61 62 63 |
# File 'lib/rack/tail_file.rb', line 57 def available? begin F.file?(@path) && F.readable?(@path) rescue SystemCallError false end end |
#call(env) ⇒ Object
34 35 36 |
# File 'lib/rack/tail_file.rb', line 34 def call(env) dup._call(env) end |
#each ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/rack/tail_file.rb', line 147 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 |
#file_path(env) ⇒ Object
85 86 87 |
# File 'lib/rack/tail_file.rb', line 85 def file_path(env) target_file env end |
#method_allowed?(env) ⇒ Boolean
53 54 55 |
# File 'lib/rack/tail_file.rb', line 53 def method_allowed? env ALLOWED_VERBS.include? env["REQUEST_METHOD"] end |
#path_info_for(env) ⇒ Object
65 66 67 |
# File 'lib/rack/tail_file.rb', line 65 def path_info_for env Utils.unescape(env["PATH_INFO"]) end |
#path_is_within_root?(env) ⇒ Boolean
69 70 71 72 73 |
# File 'lib/rack/tail_file.rb', line 69 def path_is_within_root? env root = root env target = target_file env !target.relative_path_from(root).to_s.split(SEPS).any?{|p| p == ".."} end |
#requested_lines_size(env) ⇒ Object
105 106 107 |
# File 'lib/rack/tail_file.rb', line 105 def requested_lines_size(env) (env.fetch("QUERY_STRING")[/\d+/] || 50).to_i end |
#requested_size(env, response) ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/rack/tail_file.rb', line 123 def requested_size(env, 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 tail_size = tail_size_for requested_lines_size(env) 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 |
#serving(env) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/rack/tail_file.rb', line 89 def serving(env) 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(env, response).to_s response end |
#tail_size_for(line_count) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/rack/tail_file.rb', line 109 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 |