Skip to content

Instantly share code, notes, and snippets.

@doriantaylor
Created April 26, 2022 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doriantaylor/932defd722d922051d0efd43e9a7c68b to your computer and use it in GitHub Desktop.
Save doriantaylor/932defd722d922051d0efd43e9a7c68b to your computer and use it in GitHub Desktop.
script to use in rewrite map for aliased subdomains
#!/usr/bin/env ruby
require 'time'
require 'pathname'
require 'uri'
NULL = 'NULL'.freeze
RECHECK = 5 # how many seconds to wait before stat()
class RewriteProxy
def initialize root, basename
@root = Pathname(root).expand_path.freeze
@dom = @root.to_s.split(?/).drop(1).reverse.join(?.).split(?.).freeze
@base = basename
@map = {}
end
# load the rewrite map into a hash
def load_map file
fh = file.is_a?(IO) ? file : (@root + file).open
out = {}
fh.readlines.each do |line|
# regex will empty the line
source, target = line.chomp.sub(/(?:^|\s+)#.*/, '').split(/\s+/)
# will be nil if the line is empty
out[source.freeze] ||= target.freeze if source and target
# (note we use ||= to get the first record, not the last one)
# warn "#{@base} -> #{target}" if source.empty?
end
out
end
# refresh the map file if necessary
def map_for host
# get the domain labels (yes that's what they're called)
labels = host.split(?.)
# warn (labels & @dom).inspect
# bail out unless the host matches the directory structure
return if (labels & @dom).empty?
# now we get the file
file = @root + (labels - @dom).join(?/) + @base
# nothing to do if this doesn't exist
return unless file.readable?
# warn file
# this is a handbrake from testing it every time
now = Time.now
map = @map[host]
# byeeee
return map[:data] if map and now - map[:checked] < RECHECK
# okay now we hit the file system
stat = file.stat
if !map or map[:mtime] < stat.mtime
# ensure that we initialize the object
map ||= @map[host] ||= {}
# populate the fields
map[:mtime] = stat.mtime
map[:data] = load_map file
# warn map[:data].size
end
# update the checked timestamp unconditionally
map[:checked] = now
# we return just the data
map[:data]
end
def handle_line line
begin
line = line.strip
line = 'https://' + line unless /^https?:\/\//i.match? line
uri = URI(line).normalize
rescue URI::Error
return NULL
end
# nothin' to do if there's nothin' to do
return NULL unless map = map_for(uri.host)
# try the raw request-uri, then just the path
out = map[uri.request_uri.delete_prefix ?/] ||
map[uri.path.delete_prefix ?/] || NULL
# warn "#{@base}: #{uri} -> #{out}"
out
end
def run input = $stdin, output = $stdout
input.sync = true if input.respond_to? :sync=
output.sync = true if input.respond_to? :sync=
begin
while line = input.readline
output.puts handle_line line
end
rescue EOFError
# this does not need to whine when the server is restarted
exit
end
end
end
if __FILE__ == $0
# this thing assumes you have a vhost alias that maps
# *.subdomain.rootdir.biz to .../rootdir.biz/subdomain/*, and each
# of these subdomains has a set of text rewrite maps. this program
# then loads and caches the rewrite maps (periodically stat()ing
# them to reload). you install it in apache like this:
#
# RewriteMap whatever "prg:/wherever/rewrite-proxy.rb rootdir mapfile"
#
# (usage: rewrite-proxy.rb rootdir mapfile)
RewriteProxy.new(*ARGV[0,2]).run
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment