# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com># # Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:# # The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN# THE SOFTWARE.require'async/io/endpoint'require_relative'proxy'require_relative'redirection'require'async/container/forked'moduleFalconclassHostdefinitialize@app=nil@app_root=nil@config_path="config.ru"@endpoint=nil@ssl_certificate=nil@ssl_key=nil@ssl_context=nilendattr_accessor:appattr_accessor:app_rootattr_accessor:config_pathattr_accessor:endpointattr_accessor:ssl_certificateattr_accessor:ssl_keyattr_accessor:ssl_contextdeffreezereturniffrozen?ssl_contextsuperenddefapp?@app||@config_pathenddefload_app(verbose=false)return@appif@appif@config_pathrack_app,options=Rack::Builder.parse_file(@config_path)returnServer.middleware(rack_app,verbose: verbose)endenddefself_signed!(hostname)authority=Localhost::Authority.fetch(hostname)@ssl_context=authority.server_context.tapdo|context|context.alpn_select_cb=lambdado|protocols|ifprotocols.include?"h2"return"h2"elsifprotocols.include?"http/1.1"return"http/1.1"elsifprotocols.include?"http/1.0"return"http/1.0"elsereturnnilendendcontext.session_id_context="falcon"endenddefssl_certificate_path=path@ssl_certificate=OpenSSL::X509::Certificate.new(File.read(path))enddefssl_key_path=path@ssl_key=OpenSSL::PKey::RSA.new(File.read(path))enddefssl_context@ssl_context||=OpenSSL::SSL::SSLContext.new.tapdo|context|context.cert=@ssl_certificatecontext.key=@ssl_keycontext.session_id_context="falcon"context.set_paramscontext.setupendenddefstart(*args)ifself.app?Async::Container::Forked.newdoDir.chdir(@app_root)if@app_rootapp=self.load_app(*args)server=Falcon::Server.new(app,self.server_endpoint)server.runendendendendclassHostsDEFAULT_ALPN_PROTOCOLS=['h2','http/1.1'].freezedefinitialize@named={}@server_context=nil@server_endpoint=nilenddefeach(&block)@named.each(&block)enddefendpoint@server_endpoint||=Async::HTTP::URLEndpoint.parse('https://[::]',ssl_context: self.ssl_context,reuse_address: true)enddefssl_context@server_context||=OpenSSL::SSL::SSLContext.new.tapdo|context|context.servername_cb=Proc.newdo|socket,hostname|self.host_context(socket,hostname)endcontext.session_id_context="falcon"context.alpn_protocols=DEFAULT_ALPN_PROTOCOLScontext.set_paramscontext.setupendenddefhost_context(socket,hostname)ifhost=@named[hostname]socket.hostname=hostnamereturnhost.ssl_contextendenddefadd(name,host=Host.new,&block)host=Host.newyieldhostifblock_given?@named[name]=host.freezeenddefclient_endpointsHash[@named.collect{|name,host|[name,host.endpoint]}]enddefproxyProxy.new(Falcon::BadRequest,self.client_endpoints)enddefredirectionRedirection.new(Falcon::BadRequest,self.client_endpoints)enddefcall(controller)self.eachdo|name,host|ifcontainer=host.startcontroller<<containerendendproxy=hosts.proxydebug_trap=Async::IO::Trap.new(:USR1)profile=RubyProf::Profile.new(merge_fibers: true)controller<<Async::Container::Forked.newdo|task|Process.setproctitle("Falcon Proxy")server=Falcon::Server.new(proxy,Async::HTTP::URLEndpoint.parse('https://0.0.0.0',reuse_address: true,ssl_context: hosts.ssl_context))Async::Reactor.rundo|task|task.asyncdodebug_trap.install!$stderr.puts"Send `kill -USR1 #{Process.pid}` for detailed status :)"debug_trap.trapdotask.reactor.print_hierarchy($stderr)# Async.logger.level = Logger::DEBUGendendtask.asyncdo|task|start_time=Async::Clock.nowwhiletruetask.sleep(600)duration=Async::Clock.now-start_timeputs"Handled #{proxy.count} requests; #{(proxy.count.to_f/duration.to_f).round(1)} requests per second."endend$stderr.puts"Starting server"server.runendendendendend