class Archival::HelperServer

Constants

MAGIC_GUID

Attributes

page[R]

Public Class Methods

new(port, build_dir) click to toggle source
# File lib/archival/helper_server.rb, line 10
def initialize(port, build_dir)
  @port = port
  @build_dir = build_dir
  @helper_dir = File.expand_path(File.join(File.dirname(__FILE__),
                                           '../../helper'))
end

Public Instance Methods

refresh_client() click to toggle source
# File lib/archival/helper_server.rb, line 38
def refresh_client
  ws_sendmessage('refresh')
end
start() click to toggle source
# File lib/archival/helper_server.rb, line 17
def start
  server = TCPServer.new @port
  loop do
    Thread.start(server.accept) do |client|
      req = ''
      method = nil
      path = nil
      while (line = client.gets) && (line != "\r\n")
        unless method
          req_info = line.split
          method = req_info[0]
          path = req_info[1]
        end
        req += line
      end
      client.close unless req
      handle_request(client, req, method, path)
    end
  end
end

Private Instance Methods

connect_socket(client, websocket_key) click to toggle source
# File lib/archival/helper_server.rb, line 69
def connect_socket(client, websocket_key)
  @socket = client
  response_key = Digest::SHA1.base64digest([websocket_key,
                                            MAGIC_GUID].join)
  #   puts "Responding to handshake with key: #{response_key}"

  @socket.write "HTTP/1.1 101 Switching Protocols\r\n"
  @socket.write "Upgrade: websocket\r\n"
  @socket.write "Connection: Upgrade\r\n"
  @socket.write "Sec-WebSocket-Accept: #{response_key}\r\n"
  @socket.write "\r\n"

  #   puts 'Handshake completed.'
  ws_loop
end
handle_request(client, req, method, path) click to toggle source
# File lib/archival/helper_server.rb, line 46
def handle_request(client, req, method, path)
  if method == 'GET' && path.start_with?('/js/archival-helper.js')
    # For this special file, serve it from the helper dir
    http_response(client, type: 'application/javascript') do
      serve_static(client, path, @helper_dir)
    end
    client.close
  elsif (matches = req.match(/^Sec-WebSocket-Key: (\S+)/))
    websocket_key = matches[1]
    # puts "Websocket handshake detected with key: #{websocket_key}"
    connect_socket(client, websocket_key)
  elsif method == 'GET'
    # For static paths, just serve the files they refer to.
    # TODO: mime type should be inferred from file type
    http_response(client, type: 'application/javascript') do
      serve_static(client, path)
    end
    client.close
  else
    client.close
  end
end
http_response(client, config) { || ... } click to toggle source
# File lib/archival/helper_server.rb, line 158
def http_response(client, config)
  status = config[:status] ||= 200
  type = config[:type] ||= 'text/html'
  client.print "HTTP/1.1 #{status}\r\n"
  client.print "Content-Type: #{type}\r\n"
  client.print "Access-Control-Allow-Origin: *\r\n"
  client.print "\r\n"
  yield
end
serve_static(client, path, base = @build_dir) click to toggle source
# File lib/archival/helper_server.rb, line 152
def serve_static(client, path, base = @build_dir)
  buffer = File.open(File.join(base, path)).read
  buffer.sub! '$PORT', @port.to_s
  client.print buffer
end
validate_ws_message() click to toggle source
# File lib/archival/helper_server.rb, line 100
def validate_ws_message
  first_byte = @socket.getbyte
  return unless first_byte

  fin = first_byte & 0b10000000
  opcode = first_byte & 0b00001111

  # Our server only supports single-frame, text messages.
  # Raise an exception if the client tries to send anything else.
  raise 'Archival dev server does not support continuations' unless fin
  # Some browsers send this regardless, so ignore it to keep the noise down.
  return unless opcode == 1

  second_byte = @socket.getbyte
  is_masked = second_byte & 0b10000000
  payload_size = second_byte & 0b01111111

  raise 'frame masked incorrectly' unless is_masked
  raise 'payload must be < 126 bytes in length' unless payload_size < 126

  payload_size
end
ws_getmessage() click to toggle source
# File lib/archival/helper_server.rb, line 123
def ws_getmessage
  payload_size = validate_ws_message
  return unless payload_size

  #   warn "Payload size: #{payload_size} bytes"

  mask = 4.times.map { @socket.getbyte }
  # warn "Got mask: #{mask.inspect}"

  data = payload_size.times.map { @socket.getbyte }
  # warn "Got masked data: #{data.inspect}"

  return unless data[0]

  unmasked_data = data.each_with_index.map do |byte, i|
    byte ^ mask[i % 4]
  end
  # warn "Unmasked the data: #{unmasked_data.inspect}"

  unmasked_data.pack('C*').force_encoding('utf-8')
end
ws_loop() click to toggle source
# File lib/archival/helper_server.rb, line 85
def ws_loop
  loop do
    msg = ws_getmessage
    next unless msg

    if msg == 'connected'
      ws_sendmessage('ready')
    elsif msg.start_with?('page:')
      page_path = Pathname.new(msg.sub(/^page:/, ''))
      @page = page_path.relative_path_from(@build_dir)
      ws_sendmessage('ok')
    end
  end
end
ws_sendmessage(message) click to toggle source
# File lib/archival/helper_server.rb, line 145
def ws_sendmessage(message)
  return unless @socket

  output = [0b10000001, message.size, message]
  @socket.write output.pack("CCA#{message.size}")
end