å€ãã®Rubyéçºè
ã¯ãæ¢åã®ãµãŒããŒã§ã®éåæã³ãŒãå®è¡ã§ã®åäœãç¥ã£ãŠããŸãã EventMachineã§äœãã䜿çšããããRuby :: ConcurrentãCelluloidãæãæµ®ãã¹ãŠãã ããã
ãããã«ãããããã¯GILã®ããã«éåžžã«å¹ççã«æ©èœããŸããïŒRuby 3ãåŸ
ã¡æã¿ãä¿¡ããŠããŸãïŒã
ãããããã®åé¡ã®ãªãå®è£
ããããŸããJVMã®äžã«ããå®è£
ã®1ã€ã«JRubyããããã©ã€ãã©ãªèªäœãã¯ããã«å¿«é©ã«æããããŸãã
ç§ã¯ããŸããã€ã³ãããŸãããå°ãªããšã誰ãã圌ã«ã€ããŠèããããšããããšæããŸãã ãã®å®è£
ã®äž»ãªæ©èœã¯ãJVMäžã®ã©ã€ãã©ãªãšã®ç°¡åãªçµ±åã§ãã ããã«ãããã©ã€ãã©ãªãšããŒã«ã®éžæã®ç¯å²ãåºãããŸãã
ãã®ãããJavaã®äžçã«ã¯ãExecutorã§æšæºã®ç«¶åJavaã¢ãã«ã䜿çšããŠãã¢ã¯ã¿ãŒã«å®è£
ããããšããç§ãã¡ãæãã©ã€ãã©ãªããããŸãã Nettyã©ã€ãã©ãªãåŒã³åºããŸãã åŸã«ãRatpackãªã©ãä»ã®ãã®ããã®ããŒã¹ã§éçºãããŸããã
Ratpackã¯éåæWebãµãŒããŒã§ãããNettyã¯å
éšã«ãããããæ¥ç¶ã§éåžžã«å¹ççã«åäœããäžè¬ã«IOã§çç£çãªãµãŒããŒãæ§ç¯ããããã«å¿
èŠãªãã¹ãŠãå«ãŸããŠããŸãã
ãããã£ãŠãRatpackã®æ©èœãRubyïŒJRubyïŒã®æè»æ§ãšã·ã³ãã«ãã䜿çšããŠãçããªã³ã¯ãå±éããæãåçŽãªãµãŒãã¹ãäœæããŸãã ã€ã³ã¿ãŒãããã«ã¯æ°å€ãã®äŸããããŸããããããã¯ãã¹ãŠãå®è¡ããŠç°¡åãªçããåŸãæ¹æ³ã«ãªããŸãã
ã¡ããªãã¯ã®æ¥ç¶ã«é¢ããå¥ã®äŸïŒæåŸã«ãªã³ã¯ïŒããããŸããGroovyã«ã®ã¿æäŸãããŠãããããããã¥ã¡ã³ãã®ã¡ãœããã¯JRubyã«ã¯ãŸã£ããé©ããŠããŸããã
ãã®äŸã§ã¯ã次ãèæ
®ããŸãã
- ã©ã€ãã©ãªã®æ¥ç¶
- ãµãŒããŒäœæ
- æ¥ç¶ã¡ããªãã¯
- å€éšãªãœãŒã¹ãžã®ãªã¯ãšã¹ãã®éåæå®è¡
- ãµãŒãã¹ã®ãã¹ã
- Herokuã§ãã¹ãŠãåãã
ã©ã€ãã©ãªãæ¥ç¶ãã
ãã¹ãŠã®Rubyããã°ã©ããŒã¯ãã³ãã©ãŒã䜿çšããŸããã圌ã®ããªãç掻ã¯æ²ããããã³ã¹ãçæããã®ä»ã®åéºã«æºã¡ãŠããŸããã
Javaã®äžçã«ã¯ããããã®äŸåé¢ä¿ãåŒãåºããŠã¢ããªã±ãŒã·ã§ã³ããã«ãããããŸããŸãªãã«ããŒããããŸãããããã¯Rubyã®æ¹æ³ã§ã¯ãããŸããã
ããã§jbundlerãç»å ŽããŸããã bundlerãšåãæ©èœãå®è¡ããŸãããJavaã©ã€ãã©ãªã®å ŽåãããŒãããããšJRubyããå©çšå¯èœã«ãªããŸãã çŸäººïŒ
ãããã£ãŠãRatpackãã¢ããªã±ãŒã·ã§ã³ã«æ¥ç¶ããå¿
èŠããããŸãã ã³ã¢ã®ã¿ã§ååã§ãæ®ãã¯äœ¿çšããŠããŸããã
GemfileïŒ
source 'https://rubygems.org' ruby '2.3.0', :engine => 'jruby', :engine_version => '9.1.2.0' gem 'rake' gem 'activesupport', '4.2.5' gem 'jruby-openssl' gem 'jbundler', '0.9.2' gem 'jrjackson' group :test, :development do gem 'pry' end group :test do gem 'rspec' gem 'simplecov', require: false end
jarfileïŒ
jar 'io.ratpack:ratpack-core', '1.4.2' jar 'org.slf4j:slf4j-simple', '1.7.10'
ã³ã³ãœãŒã«ã§å®è¡ããŸã
bundle install bundle exec jbundle install
å°æ¥çã«ã¯ããã«2ã3ã®ã©ã€ãã©ãªãè¿œå ããŸãããä»ã®ãšããããã«ã€ããŠè©³ãã説æããŸãã
ãµãŒããŒäœæ
ãã¹ãŠã®äŸåé¢ä¿ãããŠã³ããŒãããåŸãããŒã¹ãµãŒããŒãäœæãããã¹ãŠãæ©èœããããšã確èªããŸãã Rackããªããããéåžžã®ããŒã«ã䜿çšããŠã«ãŒãã£ã³ã°ãè¡ããŸãã
ãŸããå¿
èŠãªJavaã¯ã©ã¹ãã€ã³ããŒãããŸãã
require 'java' java_import 'ratpack.server.RatpackServer' java_import 'ratpack.server.ServerConfig' java_import 'java.net.InetAddress'
ãããŠããµãŒããŒã¯ã©ã¹ã宣èšããŸãã
module UrlExpander class Server attr_reader :port, :host def self.run new('0.0.0.0', ENV['PORT'] || 3000).tap(&:run) end def initializer(host, port) @host = host @port = port end def run @server = RatpackServer.of do |s| s.serverConfig(config) s.handlers do |chain| chain.get 'status', Handler::Status chain.all Handler::Default end end @server.start end def shutdown @server.stop end private def config ServerConfig.embedded .port(port.to_i) .address(InetAddress.getByName(host)) .development(ENV['RACK_ENV'] == 'development') .base_dir(BaseDir.find) .props("application.properties") end end end
ãµãŒãã¹ã®ãšã³ããã€ã³ãã¹ããŒã¿ã¹ãäœæããŸãããããã«ãããååãšããŠãµãŒããŒã皌åããŠãããã©ããã確èªã§ããŸãã
handlersã¡ãœããã¯ãã«ãŒãã£ã³ã°ãå®çŸ©ããChainã€ã³ã¿ãŒãã§ãŒã¹ãæž¡ããããããã¯ãåãå
¥ããŸãã ã¹ããŒã¿ã¹ã宣èšããã«ã¯ãHTTPã¡ãœãããšåçã®getã¡ãœããã䜿çšããŸãã
2çªç®ã®åŒæ°ã¯ã Handlerã€ã³ã¿ãŒãã§ãŒã¹ãå®è£
ãããªããžã§ã¯ãã§ãã ç§ãã¡ã®å Žåãããã¯handleã¡ãœããã宣èšãããŠããã¢ãžã¥ãŒã«ã§ãçŸåšã®ã³ã³ããã¹ããåããŸãã ã芧ã®ãšããããã¹ãŠãéåžžã«ã·ã³ãã«ã§æ確ã§ãã 3é建ãŠã®å·¥å Žãªã©ã¯ãããŸããã
å®éã«ã¯ãã³ãã©ãŒèªäœããã¹ãŠãOKã§ãããšçããŠãã ããïŒ
module UrlExpander module Handler class Status def self.handle(ctx) ctx.render 'OK' end end end end
Ratpackã«ã¯ç¬èªã®ãã«ã¹ãã§ãã¯ã®å®è£
ããããŸããããã®äŸã§ã¯åé·ã§ãã
æ¥ç¶ã¡ããªãã¯
ããã§ãµãŒãã¹ã®ã¹ããŒã¿ã¹ãç£èŠã§ããããã«ãªããŸãããããµãŒãã¹ã®å
容ãå¿çæéããªã¯ãšã¹ãã®æ°ããã®ä»ã®ææšãææ¡ããŠãããšäŸ¿å©ã§ãã
ãã®ããã«ã¯ãã¡ããªãã¯ãå¿
èŠã§ãã Ratpackã¯Dropwizardãšçµ±åãããŠãããããJarfileã«ããã€ãã®ããã±ãŒãžãè¿œå ããŠã€ã³ã¹ããŒã«ããå¿
èŠããããŸã
jar 'io.ratpack:ratpack-guice', '1.4.2' jar 'io.ratpack:ratpack-dropwizard-metrics', '1.4.2'
次ã«ããµãŒããŒã«æ¥ç¶ããŸãã ããã¯éåžžã«ç°¡åã§ãããã€ãã®ã»ã¯ã·ã§ã³ãå€æŽããã ãã§ãã
java_import 'ratpack.guice.Guice' java_import 'ratpack.dropwizard.metrics.DropwizardMetricsConfig' java_import 'ratpack.dropwizard.metrics.DropwizardMetricsModule' java_import 'ratpack.dropwizard.metrics.MetricsWebsocketBroadcastHandler'
ã¬ãžã¹ããªã«ã¢ãžã¥ãŒã«ãç»é²ããŸãã
s.serverConfig(config) s.registry(Guice.registry { |g| g.module(DropwizardMetricsModule.new) })
ãããŠãã®èšå®ãããŒãããŸãïŒ
def config ServerConfig.embedded .port(port.to_i) .address(InetAddress.getByName(host)) .development(ENV['RACK_ENV'] == 'development') .base_dir(BaseDir.find) .props("application.properties") .require("/metrics", DropwizardMetricsConfig.java_class) end
ãŸããWebSocketã䜿çšããŠã¡ããªãã¯ãåãåãããã®ãã³ãã©ãŒãè¿œå ããŸãã
s.handlers do |chain| chain.get 'status', Handler::Status chain.get 'metrics-report', MetricsWebsocketBroadcastHandler.new chain.all Handler::Default end
å®äºããããã¡ããªãã¯ã®ã¢ããããŒããã³ã³ãœãŒã«ãŸãã¯StatsDã«æ¥ç¶ããããšãã§ããŸãã åºåçšã®WebSocketãã§ããã®ã§ã衚瀺çšã®ããŒãžãè¿œå ããŸãã
ã¹ããŒã ã¯æšæºã§ããããããªãã¯ãã©ã«ããŒã«ã¯ãã¹ãŠã®çµ±èšãå«ãŸããŸãã ãããè¿ãããã«ããã©ã«ããŒãšã€ã³ããã¯ã¹ãã¡ã€ã«ã®ååãæå®ããŠãè¿œå ã®ã«ãŒããäœæããŸãã
s.handlers do |chain| chain.files do |f| f.dir('public').indexFiles('index.html') end chain.get 'status', Handler::Status chain.get 'metrics-report', MetricsWebsocketBroadcastHandler.new chain.all Handler::Default end
å€éšãªãœãŒã¹ãžã®ãªã¯ãšã¹ãã®éåæå®è¡
ãµãŒããŒã¯åœç€Ÿã§èµ·åããæå®ãããããŒãããªãã¹ã³ãããªã¯ãšã¹ãã«å¿çããŸãã 次ã«ããšã³ããã€ã³ããè¿œå ããŸããããã«ãããã·ã§ãŒããªã³ã¯ãééãããã¹ãŠã®URLãè¿ãããŸãã ã¢ã«ãŽãªãºã ã¯åçŽã§ããªãã€ã¬ã¯ãããšã«æ°ããLocationãé
åã«ä¿åããŠããè¿ããŸãã
s.handlers do |chain| chain.get 'status', Handler::Status chain.path 'expand', Handler::Expander chain.all Handler::Default end
è¿œå ããããšã³ããã€ã³ãã¯ãPOSTèŠæ±ãšGETèŠæ±ã®äž¡æ¹ãåãå
¥ããŸãã
ããããã³ã°APIã®ã¿ãããå Žåãåãªã¯ãšã¹ãã¯ç¬èªã®ã¹ã¬ããã§åŠçãããŸããåŠçããããšããµãŒããŒããã®å¿çãåŸ
æ©ããæéã®90ïŒ
ã«ãªããŸãã æå°éã®äŸ¿å©ãªèšç®ããããŸãã ããã幞ããªããšã«ãRatpackã¯éåæãµãŒããŒã§ãããéåæhttpã¯ã©ã€ã¢ã³ããPromiseãªã©ã®å®å
šãªã³ã³ããŒãã³ãã»ãããæäŸããŸãã
ããã§ãåãœãŒã¹ãªã³ã¯ã«PromiseãäœæããŠã¿ãŸããããPromiseã¯æ£åžžã«å®äºãããšLocationé
åãè¿ããŸãã
å
éšã§ã¯ãURLã§GETãå®è¡ããã³ãŒã«ããã¯ãåã£ãŠãµãŒããŒããæ°ããå ŽæãååŸããŸãã
ãããã£ãŠãå®å
URLãšãã¹ãŠã®äžéURLãé
åã«å
¥ããŸãã
module UrlExpander module Handler class Expander < Base java_import 'ratpack.exec.util.ParallelBatch' java_import 'ratpack.http.client.HttpClient' def execute data = request.present? ? JrJackson::Json.load(request) : {}
ãªã³ã¯ãåéããHttpClientãäœæããŸã
httpClient = ctx.get HttpClient.java_class
æž¡ããããã¹ãŠã®URLãåéããäœããªãå Žåã¯ãããã«ç©ºã®ãããã§Promiseãè¿ããŸãã
urls = [*data['urls'], *data['url'], *params['url']].compact unless urls.present? return Promise.value({}) end
転éããããã¹ãŠã®ãªã³ã¯ã«å¯ŸããŠäžŠåãªã¯ãšã¹ããäœæããŸãã
tasks = urls.map do |url| Promise.async do |down| uri = Java.java.net.URI.new(url) locations = [url] httpClient.get(uri) do |spec| spec.onRedirect do |resposne, action| locations << resposne.getHeaders.get('Location') action end end .then do |_resp| down.success(locations); end end end
å®è¡ãåŸ
ã£ãŠãçµæãåéããŠè¿ããŸãã
ParallelBatch.of(tasks).yieldAll.flatMap do |results| response = results.each_with_object({}) do |result, locations| result.value.try do |list| locations[list.first] = list[1..-1] end end Promise.value response end end end end end
ãã®çµæãPromiseãããã§ãŒã³ãååŸããã³ãŒããéåæã«å®è¡ããŸãã
ãµãŒãã¹ããã¹ããã
圌ããæžãããã®ããã¹ãããæã§ãã å€ãè¯ãrspecããã¹ãããŸããããã¥ã¢ã³ã¹ããããŸãã ãªããªã Ratpack + Promiseã䜿çšããå Žåãã©ã€ãã©ãªããéé¢ããŠãã¹ãããããšã¯ã§ããŸãããäœããã®çç±ã§ãããã®Promiseãå®è¡ããå¿
èŠããããŸãã åäœããã€ãã³ãã«ãŒããå¿
èŠã§ãã ãããè¡ãã«ã¯ããããããè¿œå ã®JARã©ã€ãã©ãªãŒãæ¥ç¶ããŸãã
jar 'io.ratpack:ratpack-test', '1.4.2'
ãã®ã©ã€ãã©ãªã䜿çšãããšããã¹ããªã¯ãšã¹ãïŒãã¹ããµãŒããŒã®äœæïŒãšPromiseã®å®è¡ã®äž¡æ¹ãæŽçã§ããŸãã åŸè
ã®å Žåã ExecHarnessã¯ã©ã¹ã䜿çšãããŸããããã¥ã¡ã³ãã§ã¯è©³çŽ°ã«èª¬æãããŠããããµã³ãã«ã¯JRubyã«ç°¡åã«ç§»æ€ã§ããŸãã
GETèŠæ±ã®å®è¡æ¹æ³ããã¹ããããã¹ããµãŒããŒãå®è¡ã§ããEmbeddedAppã䜿çšããŸãã äœæãç°¡çŽ åããããã®ããŸããŸãªéçã¡ãœããããããŸãã
ç¹å®ã®å Žåã ãã¹ã«é¢ä¿ãªããã³ãã©ãŒããã¹ãããã ããªã®ã§ã次ã®ããã«äœæããŸãã
describe UrlExpander::Handler::Expander do let(:server) do EmbeddedApp.fromHandlers do |chain| chain.all(described_class) end end
ãããŠããã¹ãŠãæ£åžžã«æ©èœããããšã確èªããŸãã
let(:url) { 'http://bit.ly/1bh0k2I' } context 'get request' do it do server.test do |client| response = client.params do |builder| builder.put('url', url) end .getText response = JrJackson::Json.load(response) expect(response).to be_present expect(response).to be_key url expect(response[url].last).to match /\/ya\.ru/ end end end
ãã¹ãã¡ãœããã¯å®è¡ãéå§ãããããã¯ã«ãªã¯ãšã¹ããå®è¡ãããTestHTTPClientã®ã€ã³ã¹ã¿ã³ã¹ãæž¡ããŸãã 次ã«ãåä¿¡ããå¿çã確èªããŸãã ã芧ã®ãšããããã¹ãŠãéåžžã«ç°¡åã§ãã
ExecHarnessãšã¯ç°ãªããEmbeddedAppã¯ãã§ãã¯ããšã«ãµãŒããŒãåäœæããŸãããExecHarnessã¯EventLoopã1åã ãå®è¡ããŸãã
ãããã£ãŠãã³ãŒããã§ããéãRatpack Contextããåé¢ããŠãç¬ç«ããŠãã¹ãã§ããããã«ããããšããå§ãããŸãã
Herokuã§èµ·å
ãã¹ãŠã®æºåãæŽã£ãããherokuã§ãããžã§ã¯ããéå§ããŸãã ãã®æé ã¯ãéåžžã®ã«ããŒãµãŒãã¹ãéå§ããå Žåãšã»ãšãã©å€ãããŸããã
å¯äžã®éãã¯ãJARã©ã€ãã©ãªãã€ã³ã¹ããŒã«ããå¿
èŠããããherokuã¯ãã®æäœãèªåçã«å®è¡ããªãããšã§ãã
ãã®ããã«å°ããªããã¯ãè¡ãããŸãã ååãšããŠãããã¯ã©ãã§ã説æãããŠããŸãããæŽåæ§ã®ããã«ããã§ç¹°ãè¿ããŸãã ãã«ãããã»ã¹äžã«ãã¹ã¿ãã£ãã¯ããã«ããããŸããããã䜿çšããŠã次ã®ã¬ãŒãã¿ã¹ã¯ãè¿œå ããŸãã
task "assets:precompile" do require 'jbundler' config = JBundler::Config.new JBundler::LockDown.new( config ).lock_down JBundler::LockDown.new( config ).lock_down("--vendor") end
ã¢ã»ã³ããªäžã«ãJarfileã§æå®ãããã©ã€ãã©ãªããã¹ãŠã€ã³ã¹ããŒã«ãããŸãã
ãããã«
ã芧ã®ãšãããRatpackãJRubyãšçµã¿åãããŠäœ¿çšââããããšã¯ããã»ã©é£ããã¯ãããŸããããåæã«JVMãšNettyã®ãã¹ãŠã®æ©èœã«ã¢ã¯ã»ã¹ã§ããŸãã ããã«åºã¥ããŠãé«æ§èœã®éåæãµãŒããŒãæ§ç¯ã§ããŸãã ãããã¯ãã¹ãŠæ¬çªç°å¢ã«å¯Ÿå¿ããŠãããHello Worldã®ãã¹ãã§ã¯ããŠã©ãŒã ã¢ããåŸã«ããã«ãŒã³ã³ãããŒå
ã®EC2 c4.largeã§æ倧25k rpsã衚瀺ãããŸãã ãŠã©ãŒã ã¢ããã®ããã«çŽ3äžä»¶ã®ãªã¯ãšã¹ããè¡ãããæåã¯æéãå€åããŠããŸãããæåŸã«ã¯ãã§ã«å®å®ããŠããŸãã ããã«ãããªãè€éãªããžãã¯ã§ãã£ãŠããã¯ãšãªã®å®è¡æéã¯æ°ããªç§ã§ãã 確ãã«ã¿ã¹ã¯ã«äŸåããŸãããPumaãRatpackïŒæéãè©äŸ¡ããããã«ãã¹ãæžã¿ïŒã«çœ®ãæããã ãã§ãã倧å¹
ã«å¢å ããŸããã ã³ãŒããå®å
šã«ãªãã¡ã¯ã¿ãªã³ã°ããã³åèããJVMã®å³å¯ãªæé©åãè¡ã£ãåŸãæéã¯æ¡éãã«ççž®ãããŸããã ã ãããJavaã®ããã©ãŒãã³ã¹ãšæè»æ§ãRubyã®éçºé床ãæ¢ããŠãã人ãããäžæ¹ã§ããã®ãã¢ãèŠãããšããå§ãããå®èšŒæžã¿ã®ã³ãŒãããããŸãã
åç
§è³æ