# frozen_string_literal: true # encoding: UTF-8 # Refinements # ======================================================================= require 'nrser/refinements/types' using NRSER::Types # Namespace # ============================================================================ module Locd # Definitions # ======================================================================= # Stuff for running the proxy server, which does "vhost"-style routing of # HTTP requests it receives to user-defined sites. # # It does this by matching the HTTP `Host` header against site labels. # # Built off [proxymachine][], which is itself built on [eventmachine][]. # # [proxymachine]: https://rubygems.org/gems/proxymachine # [eventmachine]: https://rubygems.org/gems/eventmachine # module Proxy # Constants # ====================================================================== # {Regexp} to match HTTP "Host" header line. # # @return [Regexp] # HOST_RE = /^Host\:\ /i # Mixins # ============================================================================ # Add {.logger} and {#logger} methods include NRSER::Log::Mixin # Module Methods # ====================================================================== # See if the lines include complete HTTP headers. # # Looks for the `'\r\n\r\n'` string that separates the headers from the # body. # # @param [String] data # Data received so far from {ProxyMachine}. # # @return [Boolean] # `true` if `data` contains complete headers. # def self.headers_received? data data.include? "\r\n\r\n" end # Generate an HTTP text response string. # # @param [String] status # The HTTP status header. # # @param [String] text # Text response body. # # @return [String] # Full HTTP response. # def self.http_response_for status, text [ "HTTP/1.1 #{ status }", "Content-Type: text/plain; charset=utf-8", "Status: #{ status }", "", text ].join( "\r\n" ) end # Get the request host from HTTP header lines. # # @param [Array] lines # @return [String] # def self.extract_host lines lines. find { |line| line =~ HOST_RE }. chomp. split( ' ', 2 )[1] end # Get the request path from HTTP header lines. # # @param [Array] lines # @return [String] # def self.extract_path lines lines[0].split( ' ' )[1] end # Route request based on data, see {ProxyMachine} docs for details. # # @todo # This finds the agent using the host as a pattern, so it will match # with unique partial label. I think in the case that the host is not # the full label it should probably return a HTTP redirect to the full # label so that the user URL is bookmark-abel, etc...? # # @param [String] data # Data received so far. # # @return [Hash error logger.error error error.to_proxy_machine_cmd rescue Exception => error logger.error error {close: http_response_for( '500 Server Error', error.message )} end # Range of ports to allocate to {Locd::Agent::Site} when one is not # provided by the user. # # Start (inclusive) and end (exclusive) values come from `site.ports.start` # and `site.ports.end` config values, which default to # # 55000...56000 # # @return [Range] # def self.port_range Locd.config[:site, :ports, :start]...Locd.config[:site, :ports, :end] end # Find a port in {.port_range} that is not already used by a # {Locd::Agent::Site} to give to a new site. # # @return [Fixnum] # Port number. # # @raise # If a port can not be found. # def self.allocate_port allocated_ports = Locd::Agent::Site.ports port = port_range.find { |port| ! allocated_ports.include? port } if port.nil? raise "Could not allocate port for #{ remote_key }" end port end # Um, find and start a {Locd::Agent::Site} from a pattern. # # @param pattern (see Locd::Agent.find_only!) # @return [Locd::Agent::Site] # @raise (see Locd::Agent.find_only!) # def self.find_and_start_site pattern logger.debug "Finding and starting site...", pattern: pattern site = Locd::Agent::Site.find_only! pattern logger.debug "Found site!", site: site if site.running? logger.debug "Site is RUNNING" else logger.debug "Site STOPPED, starting..." site.start logger.debug "Site started." end site end # Get the proxy's port from it's `.plist` if it exists, otherwise from # the config setting. # # @return [Fixnum] # Port number. # # @raise [TypeError] # If we can't find a suitable config setting when looking for one. # def self.port if proxy = Locd::Agent::Proxy.get proxy.port else Locd.config[:proxy, :port, type: t.pos_int] end end # Run the proxy server. # # @param [String] bind # Address to bind to. # # @param [Fixnum] port # Port to listen on. # # @return [void] # Not sure if/when this method ever returns. # def self.serve bind: config[:proxy, :bind], port: config[:proxy, :port] logger.info "Loc'd is starting ProxyMachine, hang on a sec...", bind: bind, port: port require 'locd/proxymachine' ProxyMachine.set_router method( :route ) ProxyMachine.run 'locd', bind, port end # .serve end # module Proxy # /Namespace # ============================================================================ end # module Locd