123456789_123456789_123456789_123456789_123456789_

Class: WEBrick::HTTPServlet::DefaultFileHandler

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: WEBrick::HTTPServlet::AbstractServlet
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.

Class Method Summary

AbstractServlet - Inherited

.get_instance

Factory for servlet instances that will handle a request from server using options from the mount point.

.new

Initializes a new servlet for server using options which are stored as-is in @options.

Instance Method Summary

AbstractServlet - Inherited

#do_GET

Raises a NotFound exception.

#do_HEAD

Dispatches to do_GET.

#do_OPTIONS

Returns the allowed HTTP request methods.

#service

Dispatches to a do_ method based on req if such a method is available.

#redirect_to_directory_uri

Redirects to a path ending in /.

Constructor Details

.new(server, local_path) ⇒ DefaultFileHandler

Creates a DefaultFileHandler instance for the file at local_path.

[ GitHub ]

  
# 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)

This method is for internal use only.
[ GitHub ]

  
# 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
    res['last-modified'] = mtime.httpdate
    res.body = File.open(@local_path, "rb")
  end
end

#make_partial_content(req, res, filename, filesize)

This method is for internal use only.
[ GitHub ]

  
# 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
      res.body = io.dup
    else
      raise HTTPStatus::BadRequest
    end
  }
end

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

This method is for internal use only.

returns a lambda for webrick/httpresponse.rb send_body_proc

[ GitHub ]

  
# 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

This method is for internal use only.
[ GitHub ]

  
# 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)

This method is for internal use only.
[ GitHub ]

  
# 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