class Glim::LocalServer::Servlet

Public Class Methods

new(server, config) click to toggle source
# File lib/local_server.rb, line 119
def initialize(server, config)
  @config = config
end

Public Instance Methods

content_for_file(file) click to toggle source
# File lib/local_server.rb, line 170
def content_for_file(file)
  if file.frontmatter?
    begin
      file.output
    rescue Glim::Error => e
      content = "<pre>#{e.messages.join("\n")}</pre>"
      self.create_page("Error", "Exception raised for <code>#{file}</code>", content)
    rescue => e
      content = "<pre>#{e.to_s}</pre>"
      self.create_page("Error", "Exception raised for <code>#{file}</code>", content)
    end
  else
    File.read(file.path)
  end
end
create_page(title, heading, content) click to toggle source
# File lib/local_server.rb, line 278
      def create_page(title, heading, content)
        <<~HTML
        <style>body {
          font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
        }
        </style>
        <title>#{title}</title>
        <h1>#{heading}</h1>
        #{content}
        HTML
      end
directory_index_for_path(path) click to toggle source
# File lib/local_server.rb, line 209
def directory_index_for_path(path)
  candidates = self.files.map { |file| file.output_path('/') }
  candidates = candidates.select { |candidate| path_descends_from?(candidate, path) }
  candidates = candidates.map { |candidate| candidate.sub(/(^#{Regexp.escape(path.chomp('/'))}\/[^\/]+\/?).*/, '\1') }.sort.uniq
  candidates.unshift(path + '/..') if path != '/'

  heading = "Index of <code>#{path}</code>"
  content = candidates.map do |candidate|
    "<li><a href = '#{candidate}'>#{candidate.sub(/.*?([^\/]+\/?)$/, '\1')}</a></li>"
  end

  self.create_page("Directory Index", heading, "<ul>#{content.join("\n")}</ul>")
end
do_GET(request, response) click to toggle source
# File lib/local_server.rb, line 123
def do_GET(request, response)
  @@mutex.synchronize do
    do_GET_impl(request, response)
  end
end
do_GET_impl(request, response) click to toggle source
# File lib/local_server.rb, line 129
def do_GET_impl(request, response)
  status, mime_type, body, file = 200, nil, nil, nil

  if request.path == '/.ws/script.js'
    mime_type, body = self.mime_type_for(request.path), self.websocket_script
  elsif page = self.find_page(request.path)
    file = page
  elsif dir = self.find_directory(request.path)
    if request.path.end_with?('/')
      if request.path == '/' || @config['show_dir_listing']
        mime_type, body = 'text/html', self.directory_index_for_path(dir)
      else
        $log.warn("Directory index forbidden for: #{request.path}")
        status = 403
      end
    else
      response['Location'] = "#{dir}/"
      status = 302
    end
  else
    $log.warn("No file for request: #{request.path}")
    status = 404
  end

  if status != 200 && body.nil? && file.nil?
    unless file = self.find_error_page(status, request.path)
      mime_type, body = 'text/html', self.error_page_for_status(status, request.path)
    end
  end

  mime_type ||= file ? self.mime_type_for(file.output_path('/')) : 'text/plain'
  body      ||= content_for_file(file)

  response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
  response['Pragma']        = 'no-cache'
  response['Expires']       = '0'
  response.status           = status
  response.content_type     = mime_type
  response.body             = mime_type.start_with?('text/html') ? inject_reload_script(body) : body
end
error_page_for_status(status, path) click to toggle source
# File lib/local_server.rb, line 223
def error_page_for_status(status, path)
  case status
    when 302 then title, heading, content = "302 Redirecting…", "Redirecting…",    "Your browser should have redirected you."
    when 403 then title, heading, content = "403 Forbidden",    "Forbidden",       "You don't have permission to access <code>#{path}</code> on this server."
    when 404 then title, heading, content = "404 Not Found",    "Not Found",       "The requested URL <code>#{path}</code> was not found on this server."
    else          title, heading, content = "Error #{status}",  "Error #{status}", "No detailed description of this error."
  end
  self.create_page(title, heading, content)
end
files() click to toggle source
# File lib/local_server.rb, line 303
def files
  @config.site.files_and_documents.select { |file| file.write? }
end
find_directory(path) click to toggle source
# File lib/local_server.rb, line 204
def find_directory(path)
  path = path.chomp('/') unless path == '/'
  self.files.map { |file| File.dirname(file.output_path('/')) }.find { |dir| path == dir }
end
find_error_page(status, path) click to toggle source
# File lib/local_server.rb, line 197
def find_error_page(status, path)
  candidates = self.files.select do |file|
    file.basename == status.to_s && path_descends_from?(path, File.dirname(file.output_path('/')))
  end
  candidates.max { |lhs, rhs| lhs.output_path('/').size <=> rhs.output_path('/').size }
end
find_page(path) click to toggle source
# File lib/local_server.rb, line 186
def find_page(path)
  self.files.find do |file|
    candidate = file.output_path('/')
    if path == candidate || path + File.extname(candidate) == candidate
      true
    elsif path.end_with?('/')
      File.basename(candidate, '.*') == 'index' && path + File.basename(candidate) == candidate
    end
  end
end
inject_reload_script(content) click to toggle source
# File lib/local_server.rb, line 290
def inject_reload_script(content)
  return content unless @config['livereload']

  script_tag = "<script src='#{@config['url']}/.ws/script.js'></script>"
  if content =~ /<head.*?>/
    content = "#$`#$&#{script_tag}#$'"
  elsif content =~ /<html.*?>/
    content = "#$`#$&#{script_tag}#$'"
  else
    content = script_tag + content
  end
end
mime_type_for(filename, encoding = nil) click to toggle source
# File lib/local_server.rb, line 307
def mime_type_for(filename, encoding = nil)
  if type = MIME::Types.type_for(filename).shift
    if type.ascii? || type.media_type == 'text' || %w( ecmascript javascript ).include?(type.sub_type)
      "#{type.content_type}; charset=#{encoding || @config['encoding']}"
    else
      type.content_type
    end
  else
    'application/octet-stream'
  end
end
path_descends_from?(path, parent) click to toggle source
# File lib/local_server.rb, line 274
def path_descends_from?(path, parent)
  parent == '/' || path[parent.chomp('/').size] == '/' && path.start_with?(parent)
end
websocket_script() click to toggle source
# File lib/local_server.rb, line 233
      def websocket_script
        <<~JS
        const glim = {
          connect: function (host, port, should_retry, should_reload) {
            const server = host + ":" + port
            console.log("Connecting to Glim’s live reload server (" + server + ")…");

            const socket = new WebSocket("ws://" + server + "/socket");

            socket.onopen = () => {
              console.log("Established connection: Live reload enabled.")
              if(should_reload) {
                document.location.reload(true);
              }
            };

            socket.onmessage = (event) => {
              console.log("Message from live reload server: " + event.data);

              if(event.data == 'reload') {
                document.location.reload(true);
              }
              else if(event.data == 'close') {
                window.close();
              }
            };

            socket.onclose = () => {
              console.log("Lost connection: Live reload disabled.")

              if(should_retry) {
                window.setTimeout(() => this.connect(host, port, should_retry, true), 2500);
              }
            };
          },
        };

        glim.connect('#{@config['host']}', #{@config['livereload_port']}, true /* should_retry */, false /* should_reload */);
        JS
      end