Class: WEBrick::HTTPServlet::DefaultFileHandler

Inherits:
AbstractServlet show all
Defined in:
lib/webrick/httpservlet/filehandler.rb

Overview

Servlet for serving a single file. You probably want to use the FileHandler servlet instead as it handles directories and fancy indexes.

Example:

server.mount('/my_page.txt', WEBrick::HTTPServlet::DefaultFileHandler,
             '/path/to/my_page.txt')

This servlet handles If-Modified-Since and Range requests.

Instance Method Summary collapse

Methods inherited from AbstractServlet

#do_HEAD, #do_OPTIONS, get_instance, #service

Constructor Details

#initialize(server, local_path) ⇒ DefaultFileHandler

Creates a DefaultFileHandler instance for the file at local_path.



37
38
39
40
# File 'lib/webrick/httpservlet/filehandler.rb', line 37

def initialize(server, local_path)
  super(server, local_path)
  @local_path = local_path
end

Instance Method Details

#do_GET(req, res) ⇒ Object

:stopdoc:



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/webrick/httpservlet/filehandler.rb', line 44

def do_GET(req, res)
  st = File::stat(@local_path)
  mtime = st.mtime
  res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)

  if not_modified?(req, res, mtime, res['etag'])
    res.body = ''
    raise HTTPStatus::NotModified
  elsif req['range']
    make_partial_content(req, res, @local_path, st.size)
    raise HTTPStatus::PartialContent
  else
    mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
    res['content-type'] = mtype
    res['content-length'] = st.size.to_s
    res['last-modified'] = mtime.httpdate
    res.body = File.open(@local_path, "rb")
  end
end

#make_partial_content(req, res, filename, filesize) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/webrick/httpservlet/filehandler.rb', line 118

def make_partial_content(req, res, filename, filesize)
  mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
  unless ranges = HTTPUtils::parse_range_header(req['range'])
    raise HTTPStatus::BadRequest,
      "Unrecognized range-spec: \"#{req['range']}\""
  end
  File.open(filename, "rb"){|io|
    if ranges.size > 1
      time = Time.now
      boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
      parts = []
      ranges.each {|range|
        prange = prepare_range(range, filesize)
        next if prange[0] < 0
        parts.concat(prange)
      }
      raise HTTPStatus::RequestRangeNotSatisfiable if parts.empty?
      res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
      if req.http_version < '1.1'
        res['connection'] = 'close'
      else
        res.chunked = true
      end
      res.body = multipart_body(io.dup, parts, boundary, mtype, filesize)
    elsif range = ranges[0]
      first, last = prepare_range(range, filesize)
      raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
      res['content-type'] = mtype
      res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
      res['content-length'] = (last - first + 1).to_s
      res.body = io.dup
    else
      raise HTTPStatus::BadRequest
    end
  }
end

#multipart_body(body, parts, boundary, mtype, filesize) ⇒ Object

returns a lambda for webrick/httpresponse.rb send_body_proc



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/webrick/httpservlet/filehandler.rb', line 90

def multipart_body(body, parts, boundary, mtype, filesize)
  lambda do |socket|
    begin
      begin
        first = parts.shift
        last = parts.shift
        socket.write(
          "--#{boundary}#{CRLF}" \
          "Content-Type: #{mtype}#{CRLF}" \
          "Content-Range: bytes #{first}-#{last}/#{filesize}#{CRLF}" \
          "#{CRLF}"
        )

        begin
          IO.copy_stream(body, socket, last - first + 1, first)
        rescue NotImplementedError
          body.seek(first, IO::SEEK_SET)
          IO.copy_stream(body, socket, last - first + 1)
        end
        socket.write(CRLF)
      end while parts[0]
      socket.write("--#{boundary}--#{CRLF}")
    ensure
      body.close
    end
  end
end

#not_modified?(req, res, mtime, etag) ⇒ Boolean

Returns:

  • (Boolean)


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/webrick/httpservlet/filehandler.rb', line 64

def not_modified?(req, res, mtime, etag)
  if ir = req['if-range']
    begin
      if Time.httpdate(ir) >= mtime
        return true
      end
    rescue
      if HTTPUtils::split_header_value(ir).member?(res['etag'])
        return true
      end
    end
  end

  if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
    return true
  end

  if (inm = req['if-none-match']) &&
     HTTPUtils::split_header_value(inm).member?(res['etag'])
    return true
  end

  return false
end

#prepare_range(range, filesize) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/webrick/httpservlet/filehandler.rb', line 155

def prepare_range(range, filesize)
  first = range.first < 0 ? filesize + range.first : range.first
  return -1, -1 if first < 0 || first >= filesize
  last = range.last < 0 ? filesize + range.last : range.last
  last = filesize - 1 if last >= filesize
  return first, last
end