reel-0.6.1/0000755000004100000410000000000012676060250012501 5ustar www-datawww-datareel-0.6.1/Rakefile0000644000004100000410000000023012676060250014141 0ustar www-datawww-data#!/usr/bin/env rake require "bundler/gem_tasks" Dir[File.expand_path("../tasks/**/*.rake", __FILE__)].each { |task| load task } task :default => :specreel-0.6.1/Gemfile0000644000004100000410000000055112676060250013775 0ustar www-datawww-datasource 'https://rubygems.org' gem 'celluloid' gem 'celluloid-io' gem 'http' gem 'jruby-openssl' if defined? JRUBY_VERSION gem 'coveralls', require: false # Specify your gem's dependencies in reel.gemspec gemspec group :development do gem 'guard-rspec' gem 'pry' end platforms :rbx do gem 'racc' gem 'rubinius-coverage' gem 'rubysl', '~> 2.0' end reel-0.6.1/examples/0000755000004100000410000000000012676060250014317 5ustar www-datawww-datareel-0.6.1/examples/server_sent_events.rb0000755000004100000410000000711112676060250020572 0ustar www-datawww-data#!/usr/bin/env ruby # See: http://www.w3.org/TR/eventsource/ # Run with: bundle exec examples/server_sent_events.rb # Test with: curl -vNH 'Accept: text/event-stream' -H 'Last-Event-ID: 1' -H 'Cache-Control: no-cache' http://localhost:63310 require 'bundler/setup' require 'time' require 'reel' class ServerSentEvents < Reel::Server::HTTP include Celluloid::Logger def initialize(ip = '127.0.0.1', port = 63310) @connections = [] @history = [] @lastEventId = 0 async.ping async.ring #not needed for Production, only to have some events here. super(ip, port, &method(:on_connection)) end #broadcasts events to all clients def broadcast(event, data) #only keep the last 5000 Events if @history.size >= 6000 @history.slice!(0, @history.size - 1000) end @lastEventId += 1 @history << {id: @lastEventId, event: event, data: data} info "Sending Event: #{event} Data: #{data} to #{@connections.count} Clients" @connections.each do |socket| async.send_sse(socket, data, event, @lastEventId) end true end private #event and id are optional, Eventsource only needs data def send_sse(socket, data, event = nil, id = nil) begin socket.id id if id socket.event event if event socket.data data rescue Reel::SocketError, NoMethodError @connections.delete(socket) if @connections.include?(socket) end end #Lines that start with a Colon are Comments and will be ignored def send_ping @connections.each do |socket| begin socket << ":\n" rescue Reel::SocketError @connections.delete(socket) end end end #apache 2.2 closes connections after five seconds when nothing is send, see this as a poor mans Keep-Alive def ping every(5) do send_ping end end #only used to have some events here, not needed for Production. def ring every(2) do broadcast(:time, Time.now.httpdate) end end def handle_request(request) query = {} (request.query_string || '').split('&').each do |kv| key, value = kv.split('=') if key && value key, value = CGI.unescape(key), CGI.unescape(value) query[key] = value end end #see https://github.com/celluloid/reel/blob/master/lib/reel/stream.rb#L35 eventStream = Reel::EventStream.new do |socket| @connections << socket socket.retry 5000 #after a Connection reset resend newer Messages to the Client, query['lastEventId'] is needed for https://github.com/Yaffle/EventSource if @history.count > 0 && id = (request.headers['Last-Event-ID'] || query['lastEventId']) begin if history = @history.select {|h| h[:id] >= Integer(id)}.map {|a| "id: %d\nevent: %s\ndata: %s" % [a[:id], a[:event], a[:data]]}.join("\n\n") socket << "%s\n\n" % [history] else socket << "id\n\n" end rescue ArgumentError, Reel::SocketError @connections.delete(socket) request.close end else socket << "id\n\n" end end #X-Accel-Buffering is nginx(?) specific. Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications request.respond Reel::StreamResponse.new(:ok, { 'Content-Type' => 'text/event-stream; charset=utf-8', 'Cache-Control' => 'no-cache', 'X-Accel-Buffering' => 'no', 'Access-Control-Allow-Origin' => '*'}, eventStream) end def on_connection(connection) connection.each_request do |request| handle_request(request) end end end ServerSentEvents.run reel-0.6.1/examples/spy_hello_world.rb0000755000004100000410000000124612676060250020057 0ustar www-datawww-data#!/usr/bin/env ruby # Run with: bundle exec examples/hello_world.rb require 'rubygems' require 'bundler/setup' require 'reel' addr, port = '127.0.0.1', 1234 puts "*** Starting server on http://#{addr}:#{port}" Reel::Server::HTTP.run(addr, port, spy: true) do |connection| # For keep-alive support connection.each_request do |request| # Ordinarily we'd route the request here, e.g. # route request.url request.respond :ok, "hello, world!\n" end # Reel takes care of closing the connection for you # If you would like to hand the connection off to another thread or actor, # use, connection.detach and then manually call connection.close when done end reel-0.6.1/examples/hello_world.rb0000755000004100000410000000123112676060250017156 0ustar www-datawww-data#!/usr/bin/env ruby # Run with: bundle exec examples/hello_world.rb require 'rubygems' require 'bundler/setup' require 'reel' addr, port = '127.0.0.1', 1234 puts "*** Starting server on http://#{addr}:#{port}" Reel::Server::HTTP.run(addr, port) do |connection| # For keep-alive support connection.each_request do |request| # Ordinarily we'd route the request here, e.g. # route request.url request.respond :ok, "hello, world!" end # Reel takes care of closing the connection for you # If you would like to hand the connection off to another thread or actor, # use, connection.detach and then manually call connection.close when done end reel-0.6.1/examples/https_hello_world.rb0000755000004100000410000000153612676060250020410 0ustar www-datawww-data#!/usr/bin/env ruby # Run with: bundle exec examples/hello_world.rb require 'rubygems' require 'bundler/setup' require 'reel' addr, port = '127.0.0.1', 4430 options = { :cert => File.read(File.expand_path("../../spec/fixtures/server.crt", __FILE__)), :key => File.read(File.expand_path("../../spec/fixtures/server.key", __FILE__)) } puts "*** Starting server on #{addr}:#{port}" Reel::Server::HTTPS.supervise(addr, port, options) do |connection| # For keep-alive support connection.each_request do |request| # Ordinarily we'd route the request here, e.g. # route request.url request.respond :ok, "hello, world!" end # Reel takes care of closing the connection for you # If you would like to hand the connection off to another thread or actor, # use, connection.detach and then manually call connection.close when done end sleep reel-0.6.1/examples/websockets.rb0000755000004100000410000000651412676060250017026 0ustar www-datawww-datarequire 'rubygems' require 'bundler/setup' require 'reel' require 'celluloid/autostart' class TimeServer include Celluloid include Celluloid::Notifications def initialize async.run end def run now = Time.now.to_f sleep now.ceil - now + 0.001 every(1) { publish 'time_change', Time.now } end end class TimeClient include Celluloid include Celluloid::Notifications include Celluloid::Logger def initialize(websocket) info "Streaming time changes to client" @socket = websocket subscribe('time_change', :notify_time_change) end def notify_time_change(topic, new_time) @socket << new_time.inspect rescue Reel::SocketError info "Time client disconnected" terminate end end class WebServer < Reel::Server::HTTP include Celluloid::Logger def initialize(host = "127.0.0.1", port = 1234) info "Time server example starting on #{host}:#{port}" super(host, port, &method(:on_connection)) end def on_connection(connection) while request = connection.request if request.websocket? info "Received a WebSocket connection" # We're going to hand off this connection to another actor (TimeClient) # However, initially Reel::Connections are "attached" to the # Reel::Server::HTTP actor, meaning that the server manages the connection # lifecycle (e.g. error handling) for us. # # If we want to hand this connection off to another actor, we first # need to detach it from the Reel::Server (in this case, Reel::Server::HTTP) connection.detach route_websocket request.websocket return else route_request connection, request end end end def route_request(connection, request) if request.url == "/" return render_index(connection) end info "404 Not Found: #{request.path}" connection.respond :not_found, "Not found" end def route_websocket(socket) if socket.url == "/timeinfo" TimeClient.new(socket) else info "Received invalid WebSocket request for: #{socket.url}" socket.close end end def render_index(connection) info "200 OK: /" connection.respond :ok, <<-HTML Reel WebSockets time server example

Time Server Example

The time is now: ...
HTML end end TimeServer.supervise_as :time_server WebServer.supervise_as :reel sleep reel-0.6.1/examples/roundtrip.rb0000755000004100000410000001023512676060250016676 0ustar www-datawww-datarequire 'rubygems' require 'bundler/setup' require 'reel' require 'celluloid/autostart' class RoundtripServer include Celluloid include Celluloid::Notifications def initialize async.run end def run now = Time.now.to_f sleep now.ceil - now + 0.001 every(1) do publish 'read_message' end end end class Writer include Celluloid include Celluloid::Notifications include Celluloid::Logger def initialize(websocket) info "Writing to socket" @socket = websocket subscribe('write_message', :new_message) end def new_message(topic, new_time) @socket << new_time.inspect rescue Reel::SocketError info "WS client disconnected" terminate end end class Reader include Celluloid include Celluloid::Notifications include Celluloid::Logger def initialize(websocket) info "Reading socket" @socket = websocket subscribe('read_message', :new_message) end def new_message(topic) msg = @socket.read publish 'write_message', msg rescue Reel::SocketError, EOFError info "WS client disconnected" terminate end end class WebServer < Reel::Server::HTTP include Celluloid::Logger def initialize(host = "0.0.0.0", port = 9000) info "Roundtrip example starting on #{host}:#{port}" super(host, port, &method(:on_connection)) end def on_connection(connection) while request = connection.request if request.websocket? info "Received a WebSocket connection" # We're going to hand off this connection to another actor (Writer/Reader) # However, initially Reel::Connections are "attached" to the # Reel::Server::HTTP actor, meaning that the server manages the connection # lifecycle (e.g. error handling) for us. # # If we want to hand this connection off to another actor, we first # need to detach it from the Reel::Server (in this case, Reel::Server::HTTP) connection.detach route_websocket request.websocket return else route_request connection, request end end end def route_request(connection, request) if request.url == "/" return render_index(connection) end info "404 Not Found: #{request.path}" connection.respond :not_found, "Not found" end def route_websocket(socket) if socket.url == "/ws" Writer.new(socket) Reader.new(socket) else info "Received invalid WebSocket request for: #{socket.url}" socket.close end end def render_index(connection) info "200 OK: /" connection.respond :ok, <<-HTML Reel WebSockets roundtrip example

Roundtrip communication with websockets

Latest message is: ...
HTML end end RoundtripServer.supervise_as :roundtrip_server WebServer.supervise_as :reel sleep reel-0.6.1/.coveralls.yml0000644000004100000410000000003112676060250015266 0ustar www-datawww-dataservice-name: travis-pro reel-0.6.1/log/0000755000004100000410000000000012676060250013262 5ustar www-datawww-datareel-0.6.1/log/.gitignore0000644000004100000410000000000212676060250015242 0ustar www-datawww-data* reel-0.6.1/.rspec0000644000004100000410000000007412676060250013617 0ustar www-datawww-data--color --format documentation --backtrace --order random reel-0.6.1/logo.png0000644000004100000410000011160112676060250014147 0ustar www-datawww-dataPNG  IHDRX pHYs  iCCPPhotoshop ICC profilexc``$PPTR~!11 !/?/020|pYɕ4\PTp(%8 CzyIA c HRvA cHvH3c OIjE s~AeQfzFcJ~RBpeqIjng^r~QA~QbIj ^<#U*( >1H.-*%CC"C= o]KW0cc btY9y!K[z,٦}cg͡3#nM4}2?~ 4] cHRMz%u0`:o_FIDATxw|U훞z 7 Ei"] )*"A:RBeN6!( ~g{ιG n G@+ @X@ @  @X@JqҖX~^q3Ԧo(94Dr &1^F@ f62&`\E@APFQT4c^kXmu)FŽo3d>4ӯl/3d}e=w)J.j` < =wV]$65DiKiShG.6~"+@ºNH42XȨgzp7~=)L5/{^<V>Lk='[\ejG(M0ywqxy=AXuiC\.|rݧ_Qo <V--z|{ MMthסL'q̼hbv  ?ƪ4Mz4-@ P ,b(_Yz |k|wz3Àwr-NF @{ϻ+b}dL."*F> es{>xps\.@%Su@=^/cL~`eҎBK<|D:R7fFHe ܮDn]ULf a-*sWwTLEA3M>1YPI$4W9Z j!pdF,z9yI8V@%TKq5f+Bv2[.ވrMK`0ъ6 hƭ|4>܆PZ71434՛>KF RRm9arXL)#.mw%Ta;LrD`|Fd4P6Rӎ3<VA|?L^P.N7s<J)Jݧinao4KXnP)tܯ1!.+ , ӛfGwqEGW~fuI]N/ac_aE0h| 1sWhFN\%͏"Ȣ7QugmQ)@6ao^|yȧm7> Alc0rOI~LWھMPJcC0@~" siM)(~1o[.7__)\C#>y1\i,;_lb[CB+R  |nk7G @0x.9GD: R-?AM,1='(Xylb"/8G~/՘H#e:FHz J )3zؒ'?,*xOCXnWѲsԞr$^ #8/jvNW)L47 Dyi&ƺGwx[89(8;'?23~Ozޝ!3Pv|oz|Z[; pR+ƍ7+8^s Ow?F(@?Ѵ`eCpf;rEIX<Fc$5A -y>^l ]2W)Her]vW$[pxMw4F:&!Ta0 +њekI "M o yRn] '9 *p7A*rvwwֹ9G H2oJ'%4b%%r8hͫ4% YL$n| mW3Ck_.Fj7?Hs/!|U)򣸓8[,Z:@Kx_9E&(xmK"`29xF 8l>zB̈_mxܙ*ҒuɋNx'MY+܂'YC&*Ygf多hqPR8wRehF}?g8B"~aoe GXVX{dG A[z1Ӣ ^(-|&NyuV(xqw.a@UkPpq;KĶnM{2=\1K4%|/Ms`.2o ,Aa8vv7H&xbF@K6q\g>`-O7ƴ< EE糎ODʂ rsaKےQi>?ӄ~ݘQ.Ͼ,[::|@Ƃ ~g&%O%~'?XpdkU1fXMt1~xGn`* x csqajT ӦVmϣ a`;ӗp ץtՂ,܎X`\>W(@0~ pYJ(wR8gG!G:s MGƆ LJa^!wLzqиZ$p4? I'0q$v١ɳYg7Ws[eM,Gܟ%^-3F,X`&є<^IX1NI;q2M3یf e&1 ig#r5-cЄAIa&gU RqTwSZ+c~:y1>ǔ'эT@ >!8ݧT^\a֝S.3BY-Z}\R+<( 5_Gt U %ɲγ7vbTVhh~iUYeSQaQmGE2%"<_٭E'F.f[{i_Nu&r7=ӆI3|L=gQl|ZZ- BXYuԡmoZ!i|ٖKu~8|>/$Rp4~*@+t՟>n'脘=:S`⵮Z=:h;@<ϖT7o_׌3IK55^:a +3uƘj}ZqgT%ZY8s< xo#=+cr_&U@q$y  I29Ąk̸0I :| +XCiU#xma,6.UK9#y`UҴ λrI%8VNʋ5@6["}mΌťo[_ W<9tZ-B30 n4Wջ' MqgNZA!]M5M&`B2Uۦc?jHY.0nb'Ə ׼@ { mW `Ⱦw߶`ҌQK`X*VD̝yhC3--}f3[* $)]XkȈȈ`cn7淦mSOhA7xFC]aHBaXC6X'4`Ra2׊1^3J g&IDrʉ|{ Vf]2D'iw1a93o ^"tO7ˋ0.P1#OPSJ-n"^#=9)NQQ :M5:=9IjӛK5:85 )f2@' lSƟe*'spq0iwdGƍy0 @$WWjRv(=݇Pd4Lh{nE?0msb"ܘL5Bn6QIvsL;JřOUL$m)F4nau-ڣ1{sc<}c0j/p L 8w `z(c!UN8LOn1t~][i:]`(xr YFV]<'dҏALɆAҘi:dFoғ9 iIGwܟk#ZҔU8b:sڧ݃ sk5oF~11L`>@ ~䗊G!4=iERgEI[}Ztw *ђޒ\e( ~ a?ЛgWn9K@bhLt.MҍU@~Sx$n ]Wgg<0Rz!xh&2&`ipQ֎==,3CP>k%'ikw2F2g|֌g |>?(^͚Y7p1Mz SLt/EXS>g> &^z’HUyK;'FzOsL2|Jb}B4E{bL&|芍֜T ףIk`%+M`43Tv૴Ѕ c< t -a):7?o/>27G&)~ӝgM#%?M/n٣?ۛuXv>MrSi)?KMxiw\Pںu(@1,}jQTD)_oSI8 ً`-_ .z6|"t(3Ԣ,?S/|^b*tg9hDgى'tkbݾ妏9< ij(TtSn{s]&xgXLsZ wgJU`S pS QLMwˑѠ >&ɩP2h{`Z.2O.;y·>" T`>k3S33mO;ցU4 PV$${(tu,>WCQF0/)T-xl8pmHNRC,ZZL۟s*sΩI3kY5CwcE4>5FO s{H%xIZA3yx/j q'xY|J50_}I9ҩJ_zG@Zi*,=9P6e D33nRH1RiZSre)+) bPr2}8ϚAkuĻ+9tۉs.7tVdVʻO-p=9M1C؈~ꢡҝ-OQA$O6j8YEGY41|1u¥7a PV7>a)+jF?fl,9?F2#cb<~Z@UL + ak<*^9Oʥ N7c_Дx 'Da7kkk y'#!i<( &:SW3/oVS_?hC\7-@YºWU1hF ҃9(9(U~ ̼76-?n`8wt{sB$YQ_ }OB^#z[ߕr5C/Wq0/NXԤns |acepV{EzR9Ry!",le@~5sWƬ&{Tӹ6iMl I~*M qjb*]b\V~tRSO0OXʼX&D*37O3K'=W4%W"䋰_Kl0P[m#-b 6nmU;n`cib t*P+]Q4Ջ]9QcL neEw2Wﵮޡ(s]+zy/75'i|DP1(wH}ۑ[ OVl@ xQ ~dCaG \?% xMuU.`VXc ~SyB3ml]'FW_KoR#"v9vlA{.-ut_hv19;YOg~.bD¬^RjtPMٖJZÄ|Y Ou]6 vq~KS`Xߟi=> ~f6锖|%Q Ej+e^^6fl>_rwHc){0 a?pF2g:^"A׎wtv(oR,%u 8䁰nn:flnЪ#3pdM .8{H8xC.Tlp!l"Zl [9AINN+:2 W1||ExG>@KȐ<4@X䁰^?’su1V|$$A3 FRIDž]_'(0{-C<}ʨ%2yfm xl2h40"qZDz5mCW kgQVҜԌbAnAJ:piЀZ$#1ݹ7-3梔ؙD &Ӛd&!bmQoDY9Ք ^sBԼ cpلu=/+HΨ/ֳGWTP*̘uڞj 8T^er}hs@J1V|STecaF6ƻ8}g_a q4ʽӘӧz2ߖM(|N#O0uh{EqmUld^ʚƘwHg~3">bJ}_tu+ iǯˏI]r<Ī=rp#Lt(ǹyVząV[Z6}z2GP|֜ g <:1@aJCÛ*MdA$$A0PhQ \sK>ɵ.rLx菝:e8݆y)$R3&>a3/PY4%LۆX2%L1M;Pe͋qe*ٿ8(0S蝧aA /55!XЂ8ĝ{}ޮ\h|pztfl?I BVn?}Qu5:ъae ̌YY2IpS]mg5K3Ch2ʃZ*՛O(@P@ ijWqQBTʂ8'V iUNNb[J>#9og |]$Ʋoݵk:DgЈ w0|CSa N{lI/>~c_\ pل/QpA[nJph((nj4Ǧwܟ1AxA0 *^ނA>-xq7EWܕ79S\b !3)Ioa\Kʉ¦T e57H<͹p])_x#s>[Qeҷ ZAS.|L؆/X`EHaQMY6  uTT] k`Ć񤲝I0mp}1aCsq\pŚ:άܒѰRi~jQI}3"S4nw5ꉓ@qntgvi8}_ nuqX/Э+ˎ0.fB(H H8n+Ҕ9mv)ߵE.%.2`#w9S}]b g wx DXy>". ˿cDc/_H8H U/Ӟ2!iH] `)˨knjҰ`%ld9~9׷(`ժT2L]*ڝį+XW &,OUFC!8 vc&K/!_Rq&\|L\ 4~$a&s(1h(F]8C&*;iA0ZSիhλ9Γt \#01 (p̜.1 IGRP Š 0aD%?Jb@,V'4Fc$ ȧ'2XGaDPwj3dDE@ѲПApą 㴣E&RUc y"ROb V&ܘB:6qb<b2 J R0F !Bm>MhWH\  @0ֿ_Wv~,X::#۽Szw b] Ē*nZlU%,}hhpo RQ(|(GH^C^@a-ic#1Z&E:]c;0QM9ot E߷bhJS^i@"XŪjV bWەv)ݚzQQPP0)3X^c4*.ɓx܉J#L2MLˠ/qNmF+gk*PR7Gсa V݈%*#FyK( Ɓ!Qz0Z%uisL@p $R2]65 FՑo17}54Ǭ|wjcC84Ul IXAJ j$ )ULxB N Qu tbiI>0$"v.H 2G)K&3EmzLڼu%aD Lڅj5"zG3wl HX"DIt(Šܕnt; ²Uz72 !fhm ˜| P;\|8BklTc WgZʒݸz3MV:/t٪`%X%ncSPmd$V 7zInPtJSu; ezӛLnsX gn82\/iŠݓ=]pʶstˢ2V7.2ɰ0=moߕTtTnd'.nL5Qu_4|gd ]o쌎wݘt*_]jU亵x4}sBօU7a)h6p؛8{^蠙pyHZ>>G2BhA0 hzʨd;icJEɽ÷g6'U{Mxm\=-aA>jULs֫!45s gnRL*[ NN4$;pʤnt$֭XY<z-.O^#N20mw.tQVKtTyVܐd#'n?P:6֔I7ZbCG/wWu5H,b\ }Q[N +ABAذQA,|&dͿSV^W̳^9̲ڲ-f "|7ׇ)B:Adn^0Nɲ0n<vm*/\|᫒Pc*V(Ӥ(:Ǝ }btPtl=1bpwq FCT12.NKLXlNg㼦a`SUS\geh؞{>Uh3E\׵wB*zNuS~垮q6ONovurKjʩ򵠲?hin$&db&wlkgϘ9}bk$Ӣ3,kE0&u[ Wz4͇եs} XXuŁ٪OX.%4[|p_y4lL'! /G\rX@e%RD=029/+~N_iq¥JVceHve 0bDÉ2/S>G 0cǩ_Vwṉc%ůsI}bz_Rc)Hb vlc pd}9v-)dppң0uT)tDKI?z~d>',͆,+KEw꺣Qxѝ ij@͛|A&];UA.pYʺĔW2}|ڄy R(ǂ|(ܭP6YZ7Y+I'PZaNn(['7knPm8Ehhx_-&^ZJa4\d@YXMO`-/:qBs/4^Qz% yN]Lo1gϪm)DXxW}A|[U2Svm"LjҚ=lB7TGiʸи@ ןcV{p6ӹdˮR}ltz=+ť)#x]FEzrax9؉F䷌e&c0GW4|(tV'ϗglUA&,+Q?238hL2/~e%QSBiB$hCCKj{TzxSdADNZ1>.bcX\zpFEzn\\`. TF1|hتRJk\ΣI&/P\uH[q?p: OT=-UAt,VЎy81qE=w,3kb/1N=rK«1M2(mԺ// 6p+z ͣT8[U0 KpE͞yvj#!D$Q$s=ґ9m1HKJcgtMw~xDq.ĔU }^F( 91 =N?;ڤ QXC|Ow33.\k^;q6c]L2=I;qzhP&\)d6yuR+ς^(uy1W%?`AzWA{j `IVT²RCSNnqj !ְ}WAUSvYT'8[6>ԭ7U'^3SӇl->׳,cg%= \<ؤj$ІPO {EʟbwpUșs,`%:@ґWp0恄 g "aD~9&iU'39؍hEmAwa 9sӯOASBA:n,ΝЕX,Xr(oh>XW?KE^1uy;H̘m F4ϲLfIb|AY 12rpVswh'4PJF_vi٪HXRz֧^Yx 4ܜǞ6Km4M4I yK!%j0а+إVjQXD?6P٬, hDxrP0b&Q7F2tI}N0t71m5)%[E0P_N&-N-{82>D? P&0!6ɘˤ٪FX v9#cJ/-'vSOa5=N_GcX[g+0a$6Jdt230&.zA.+XYB3\CEOvPu[-kш # O CDc0 [ݫvj9|GO5Nڹ3t 阯@qt̷WG/#S8[UKQ|oyy0ђgfv/WTZ ߮[J9`;Omyt Qp"Ʉfut2`M_[cA<ڟvoG4<޿5{ N1,**;}* ^R"ΟIcgfJ[,oN!CU_mWLGz}.z**phB-`v&v/yO2+N 0h~ ^rmXL= Dܗe]miέ6U0`<?+D!+.FOW@'i xMWrcdiKhxu']Ўzd -*]yHϕGbi/N(*H0;Ǽx >E MFaYJnH_[$E%ngVa)u7϶VZO+d#7we:V > ٖ,*.,:}H4QU鸊WmZ<B'G.VTf,~sNjU|k 91Me+\Bb6fʠ_>lQsg&[1!Q(XrVoesv6~-C/ FweA&9?7?Hn&]wOFYz'N2p; TΣ!*/osCO (S*X9j9^=B]_umW-D/>VPq}ѿR{Ԥt8Qj\R^վ%QYЌ6f?Jāp;m8QTA;k[e{?Tq6ʇዌBۄ~<>^!o!k$[dą`B4@:Ё8[G ]|4 tH'Wiu)MDьWuc@,qV][Ct7LA#V)U&hh)"QRLHzxKdR6;rt 7G@ v[V~^,6A.FAtDP(uxe)Du2[l`Ie4W .a.&"a,!&!$6M$61*"(Ab*FA,&1* 8i/prS;(G'eȽRU6H8 7ɶgRȥ1۵]b|Kӫ~|s7yZ.6cTZ17LdG9 2JwJ$t.3~oխ;M HrTA\V*˚ʪx*?=wꎆܠפQBP _\?բXW KK[h:3.. n`3] O1y>UF )OWd-E$Z]HJq")ͅ~é[X}3'^j4I-'CA;Vś s(15݇1{b0H$Du*Tme((bYK[Dn$f1QTQ:q>5"yooHJwwr*_[X %qG ~jZu-KV:d G9˯y^h)"E$JD RyId9%dVc.i^ŠWkH )aJI09Ȟ5wO_o 7N (?m*~P0gq3т4zb! 0 Y!%.Q19׽+UpN',ĤS"FsF2DvO3,ss' fGjmRZ"Aew.SzK$FSJX ,1WuB>l5yT rQ8Л(SZlQD{m+O ؅K .Z"N{77lbp1 |Cuޔ+J>a)YR~f2Vُd`]"wI¼P ]:YEIkYydP ]O0h2$!%pH^iUN GTTѧ#ݑeFJX3SgJ M*fN.lzX$\JDU8; {~|/9yw= NgQCJ; 3xwT#F P ` 0 zP>A?m.Ej%1EDnq }XVNvb5?ZXxH5#>T<ٻ%MD (v9i%%WxM 4N lj5VG|lP,81'̪htWNgL IßtQ}Oys O>WS |n3V 8i;8`LqveyNO^6#i/(r,gIܞSbغС;.uމ3lY)u]|kvP_5,ޡ?cgjQOrLY9y-O X uCRź+*B&d}DiFf1nHeqZھM|Źu2H'=QޛWrϞ۽t߳h61!޴&ӁPix9'l#8ІoxpamS}&R:S8ґ]LUB;M8 2vgk&;0e/1x Q5";H>:Ck>,ngXbV]sByߚ+YgFl/(N6$2Q0*KSWx[Oq⌚jjʛc*jIV3=qX-_tdÎB-V&p >[03T%FBMa3^e/J_sc3YQ}',k (Ϗ|Js'&2k cq<7:b3,, FWi>։M{dmU0a&I+X{aӇ D9ZozM 0i~qF*#GD:}3L8@#h@{19smle[QR|ڗ0QjА+CSNV:x.NͥRFQ\1 %,d%׾NF@bv멄]^.\lտCX ^"*7kcyұ2qbOO 7D55pcˤ?jvښV;] +XJ쿗Si6y?ntۋnHÓ@#49+)񑥢%:R^w<4Ԁ gHl6G?w,֑y-۝D_á ܭ_;1;)YPHV]&\BMiտCX*FzNX2n\½5 CE7xNft_11{ m_rev{Vlm]El33K)68FbVrhVP&ښE+;EN3_At-\L>5>'kIqV'JZ#byܫդsᕃ4"TxV(X|Ja5 /];<@XW =n^NW'.pdb7X$ÊqKc)2hr k|j ZPI4V͔f8*܄^#ď~=|yF?ayR){!QR6kPw7_1?zVQp~RO7s$zvWY[Oge1$K^!V]b\asZ'+Y_)ք2q.3 fǀX>i,O>JqBJHVKpyL#73MeiLu˰N; F~G6$ cA ~>w1w<pB ad-F\I=Z C>VUBv0a oBs8MeBA7o "~4>&3\U[t#0ukZ(;{l.5F睂+e[NQEWEXs ! e>֑sL ZYMЎyO?u4LFZpFQ5NAYԥq)kߑ;y$;33o\$\߂BNXTUjnTܤĠe dk bKJ]֖{\eeoG*s9hMV{e@i8*` U b;g`wVP;[3M2 <*X R{o $7uO 5.^-QX{ޯS?6l!^X$|Uz/re]3V-Z2K=JY-K 0&8Agt %ۧS|Td! a]9́ZQ:J}9Ie/Hk߾ Un_&xd›ƞ*2*33v AE0c~[\.\tup = 7=3ӉuM@2?fUy )-e&|/E5R1 RBLA$+lAT9]BEPåNa]V򊰂U(+]<˪a'hm ezNJ7)#w'37B jOlI%ŧ/nc]x$4qOU я(Y1nsF[b|Iڱ f48&\$+a# yc] `5s)uD"9P:6UDDD*.@]֯5Jݟqd҆RӬq Lڄ:4I2INXɉ ^bͫuȢc@4H1xC;hdR984ܛl,qI7b*k\#X֕#>a oSѨ_ݵ 2@}U-L(.!@=EUeljX{(3;^lLJ=UBEdQ%T$oVDdIK_X v틗=m&}De@]yC""J49 }<>:8a~BMưzWH֪V]q+|=|3]<|N'8M]'MyF fEG#\m+$~" f i4QON.-ڕmem?b“ÌG>O^vYW9y&.0`eO *Ҍu[lp*0Wj/}7k\ !DOZpb-hXb$S.oӗrM#P|2v0h~?Jp3pNC$NRNpQU7q+ "+*W bR5 zwwShTg7::ǻhuU1yYpEJYT| %@am{\䧋+~MYd(C1L'Ɗ 7UTA-L%MW), ?2} l:$k.]f gNvsF҇~/=һ,1/eghG*7޵zۊz/;XK5HR=μGw>gwc.eW]f5*yQ.ˀ4]ש:1 U< *~,dŲWM{2\a*vIUS8>`91%(_9,<1-8Cr6,tcljf&w>3 (BwbVfyYΩ.+]:l"1֛x9e%d|Ew~2rzQ4@|_T<QxU׃7De:$iw~qnǀdČ3FAQeSyzre<ljJrs"IJc *|"05QU0LK6 ausikE.uf(}W粊qҜz42 i?h&jU׃x&7IcqL?-pVu]SEQ, 7ߦA*Y՗57y44 ĉ ^lhQ$k7r afT ǯ0ɥ}0&}N|굘x`W/+=>dk):uBڙbpF[0>6UuH̳]ױw٪OXw`Q ߀T0I1~'_Pڷ<' zwƣld̐]BLՓ ixm ')w:o] qB)':Y ه mv2v].p?kڃOq΢)'B1~+.Lwf 4\>#Yב'_DR8[u= +|tntsJ:F:*fRcH&&$Q-?n46Wv7uDП-xdQ8kn:Lx܂/_b͂kQevWq!iGYA#ߜB4fdQ4|ہ{ @]b˸d:yv gaeМ'#͇dyuSAAQR .6-t2xqI|G0YF F?p6;fx/uY{(a~d6nH d81ѐDf3Z WT.<̴!J|SGrsQWG\kO^ǓI#-_TƬb,yV *1^mV3&OtR8[uKhYz?( 7'0I8D4;I!L_5T>Ϯ=(3hd:!z{ !#64 !Nn5vyѸ <QܦOZ;ݩோ3Ma藲?Lx]3Qef9Cyg1cVaLi#YqM\/ ՛L`epC9 yd;Z,(YH!k14P]J&R"%\"O!LcDUt 0"*M v uājN|IҧΑtX0alS/wD5E^eQS.R2}`ο}fj.f1Mb(ALӮ%̉z$\W-|\5U[ukG]L-)[oTd#Hǹkܳ͗v!LQ'X-h$3X(=So&3@"·<UYGJLˢ/z*A%LI.N}[FqT7ԺOCUyd*\C%022օUMW}Yu7G:uVow /Ab^U5a))!!b|*; Huw(bb;9MΝNʊRZH?ed[C ey*jƻdB+3d3+ m,M,Q̳i~鏸vEk*ǭz$fv_ݫWHU,bŦo5µ[ u%zy|q] L˃-9oea gNv/a!qd3}*f!)^%%R`ߒ*ud 2UDJ{lłdY,FH %oJIATQ)Idq{b 1HyEvH|"t2dE/-499 kNXU,b%HjЮfK$+)&y:.u]p֞vt#6KKd{RI gY@$!w[F >p߰KI68ABy:CX:iY$Lu RYHKĠ-N"d颚8+XX?un $H;Asx͖IX困(I+]q>lkTe wr9؏+?*oQ8XЌ ^$>JYccDr3Sa &FgQզ/z5T$~ H1a'r461Tg5OF8'PQpG UӮZǞ϶.=V]CM~6Y& J_qaAI}) 帟$O5t}I>缇W 'e{byt2PuraJ46c(V"d pA/ ,aDЅ)q|Ad̢D߄No)SȐ|ہ>yt_:|rpɄ٪kIX*g~9wSīS0PjIQn&OsΑmfWb $R|GW**|]x7X1b',atel͈lq&TI1SX3M^RrͶ;ȹ5ԔYi忇4ͷVb/;pJSwŝkZ/I-U$bU~i;}𼌕2TeHV2K$L"$J/,z$+D$]D^Q{ PA:~ワJeqHWoT}/OUO`oSE=n7cdo="ELY-7 \wdM>1ۂIJ .̵Z d ˃zWzˈW+:٪kW5//tTaG?|\x~Kp`@!HFH%I,0=x S)KOF'g#ɬHVji餒R{ał 7Z.MTP8d,.bc.fW+u/ϴm;/}+:qLˡ݆̻/--V];de_m7ѝ|^ff,‡XeDJ(bRVQI 1HKAtH1Alb`Id)XQ^yk_rXWCX"1#r^(#ťD❱p,f! ~&) RAROG /eae%Fd_sna5]ve埋;&HBOcs㠗kCV]فl#&#zW,SQU{sRxsn͔P7]%\j#BTA^ GЧRƉQT ;%VIUr ` 򽄉Ql"&oػo9='0r)(DW'BDD XW4Sz[ 6 eŰ W~]MfYmյ~ !d{2Fz"ͷ2o X;+ҭ 烿y 8 L)>&hoz)6,ר-'|^Z  Xp$z`N/ ]>zEE_*;ZW]kR_ _+H8|٪kcQթ8u0 |k֐Q^n5{Qt(K)|L8(J5;cFPtpƣ-eVl-&Mg%ƝF`hzkIJC3L|&br/"f9?G]ֳ@|{ $\Vӫ@]k$1OUUW ؈[ YMg< c8qLR (FQNpCǺ"҉dP$ҙID0`0)NjVWŭt>I C;2|(Kr>gYRE|KrzjQT$$.;߭lo-S8[uա  P򈊖#9n1Ԥ6=c.BWZo/`%8NrO}%sI"rJ\Rmz/J1)v=bǯ鶭Jn-= #XWrjz,x25ZnerX0,ϪUWzlV3oKasQzJuO~؁1b+ d, 1qq}|f1a}֑0)As$Lw0RD\7uJ,+SEag0l2U6dm)9,6@+&2Ѵ|SpV{5 XNxE@ <qLWj(Tx|KQ CO>ӼvyKbYFt$٤2VmA hNǙWPpWx>=xg2j9 lbWP, h~a٪IX~:abKpg t>@k>Kyݎ -D3|k!4Чx,(YV7!ҕ$g ~NpxgQP4SJY3o[Sш !a"Stcn弛,*jW"ear;..;a(=v٪GX&80 wQJ1<ݏvx%J1BAeyL8OA cez 8ޑ947]36~f8k8sλqŠ8zz 8qwLdzTο䰾$V%ff0Y%V Ǝ ͫEX#^u (+6k`|:^İֱ g4gOxUf xތ{PQPo4A^gvEK{S>o9z+) .2H&bŻQG/P۪} 62k#Z>YѪW?C0"Շ+s g-}rZ:{r+"%6RGB%H{T""E$C>z d$"Ed.%M$HUA̲RrC?Q?$JW$H=a3yI-C! 3ePP6#BP\.FԸ [DFAsQQD.`P@þ: 8 G]]=cȭgk:}}(h˶""x5lYGKU~DDYKX<뵄ѷViF J?QЪ~^X+ja,36(69gԣ-1} d/H7ӎy4g̡ iPFѼf S6/Vwa4 DՑ;F]HT_Ck?BEEdMcW5ÊT;qr| ory{$~wьy?aZʴQծ$t`8^4jS)2b&8qc;y uxq>>W~4e -cN* vf!F9V4F<4MvƱv:ֿ2~.=*u}QZZ 2H EDKdKeB_GH [-9!U(oS*hE%(""dxD$ "er-vDNȃIθTI|ti"-_ V_V137a^-Lb$&ѐqaƋ**wUƬ 1Dt"6'9h |fA\&&4RlO>ϒP^'JЇpR[\1f LW_ۘԻD5bуCշC x (:cS䝃ߟX٤"TWz'&lZ .DNJaۘI_RIT7XrJa(dh\( w[ r&'O;9J;Χh7wn#@I~3 1NJl}kqYBsR;GQ>q ^7XG!u#h3 /U@og>.>CfG`/qLmbڶa87td$AܑI{rL0bʸxiUHɾw>E>Eڌ*͡gxDj:nOl6S mʙ?PgH-M=?TdT*UFE}+"_T\Ul+uk#u$]lr|)".HlRjꓤ$.4C4q rDPi#Sd nEbJX6!0^6rm9hN|oQE#ːm"rHf8D(_2Nĩy3ҫu3^X96c$jo櫾?u F0Yъd T 81b|9O09\JWzhtC=b{Z9nN0GardHLkՂU|B `Б[myDV% d#@7m҅ b3ҟFtg/X͓h xXЁ^dYcھn/S㺸jTr= ILӥu*򪀎FZ]\&`VxUeB :OZqs{Ხ?~J 5pt5<@9*vT!0r&-La!ƴy`,Sɤ1g5`*KϮ9fNnvQz:uOƴ7%:pXTXFtv=ouZUر;iP! Y52TД;Tq&wăT YlGAet:p N18&.aOṚ́^ZUk CZt  h:23XP ]hǺ,XH] [2"X*y4R^u1g/#Nƒxw2N! _Ãd95';ީ~&qo {mHah@{b^ϫn+-sxgk<9d]T,$iӍƇ!8JtGEnĆ"Ƴ>qcQjhfT%|g+ No+Õ ],džX#>`z .s*ځ<02: ܇gwpBfa{= ʺV-X6ԏ+# YCc2n>g=O0>l 0^!Σ 1C1qAFPLG -n9/&9E]|X1yKvӭs_C&&6p~ۋf_Gt阻a+Z`{q(@e@[FprJiNg\Bs=XIc_7s3YLRvjiv_Fv/@Ytf&^b$Skg{F}|~v}߃tjQ2|4åSPGi]%!˂ vS*`p["6 "mC@6"wE~)Rz ZL#k q+)jz(c7g9^XϜ)^B) S& :]\-hDN(͹~׺,XHaㄎ 6[]ia1lT: )g9:Ō~C* ćSc$wiŮONF2 fGOwU6نJCN{AWP|kG>5wЋSAd],38JN4*?!\F=UQ`0sϰ2zͤCq,S x彸ZDX#H P.f@4mKVs~5޴Q#A_Jk )$Nu R&c :9i ,8I:Uϣ $hyrWb x*8zDZ-N_%xgs,6zqb[m@O8QA42ː1kj`!+>"JL|2$(N1yد f>39fZ}]6PsMiRYt|ntp+~n>'/9cX^8 (XC:.FE GtPmӧ]3SK!ԺV-X' 5o#Tf_ٽipRlBsY@1w-f.-~1jݬ:flf06u$IAd?ɕcZBrȦc_mo Ctdc 9F-XH5&R墄y]Aa6Q\/h[E4ܠǘdEA/z7ㆄJs9 l 8xk2Ie HN6Yeyyl8NstD Fyi㗐uAYڀU)6_GT@ ]N) example_host, "Upgrade" => "websocket", "Connection" => "Upgrade", "Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==", "Origin" => "http://example.com", "Sec-WebSocket-Protocol" => "chat, superchat", "Sec-WebSocket-Version" => "13" } end let :case_handshake_headers do { "HoSt" => example_host, "UpgRAde" => "websocket", "ConnECTion" => "Upgrade", "Sec-WebsOCket-Key" => "dGhlIHNhbXBsZSBub25jZQ==", "Origin" => "http://example.com", "Sec-WEBsOCKET-pROTOCol" => "chat, superchat", "Sec-WEBsOCKET-vERsion" => "13" } end let(:handshake) { WebSocket::ClientHandshake.new(:get, example_url, handshake_headers) } let(:case_handshake) do WebSocket::ClientHandshake.new(:get, example_url, case_handshake_headers) end end end end reel-0.6.1/spec/fixtures/0000755000004100000410000000000012676060250015304 5ustar www-datawww-datareel-0.6.1/spec/fixtures/example.txt0000644000004100000410000000007012676060250017475 0ustar www-datawww-dataThis is an example of a static file being served by Reelreel-0.6.1/spec/reel/0000755000004100000410000000000012676060250014362 5ustar www-datawww-datareel-0.6.1/spec/reel/connection_spec.rb0000644000004100000410000003552012676060250020065 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Reel::Connection do let(:fixture_path) { File.expand_path("../../fixtures/example.txt", __FILE__) } it "reads requests without bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.to_s request = connection.request expect(request.url).to eq "/" expect(request.version).to eq "1.1" expect(request['Host']).to eq "www.example.com" expect(request['Connection']).to eq "keep-alive" expect(request['User-Agent']).to eq "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S" expect(request['Accept']).to eq "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" expect(request['Accept-Encoding']).to eq "gzip,deflate,sdch" expect(request['Accept-Language']).to eq "en-US,en;q=0.8" expect(request['Accept-Charset']).to eq "ISO-8859-1,utf-8;q=0.7,*;q=0.3" end end it "reads requests with bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) body = "Hello, world!" example_request = ExampleRequest.new example_request.body = body client << example_request.to_s request = connection.request expect(request.url).to eq "/" expect(request.version).to eq "1.1" expect(request['Content-Length']).to eq body.length.to_s expect(request.body.to_s).to eq example_request.body end end it "reads requests with large bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.to_s request = connection.request fixture_text = File.read(fixture_path) File.open(fixture_path) do |file| connection.respond :ok, file connection.close end response = client.read(4096) expect(response[(response.length - fixture_text.length)..-1]).to eq fixture_text end end it "enumerates requests with #each_request" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.to_s request_count = 0 connection.each_request do |request| request_count += 1 expect(request.url).to eq "/" request.respond :ok client.close end expect(request_count).to eq 1 end end context "streams responses when transfer-encoding is chunked" do def test_chunked_response(request, client) # Sending transfer_encoding chunked without a body enables streaming mode request.respond :ok, :transfer_encoding => :chunked # This will send individual chunks request << "Hello" request << "World" request.finish_response # Write trailer and reset connection to header mode response = "" begin while chunk = client.readpartial(4096) response << chunk break if response =~ /0\r\n\r\n$/ end rescue EOFError end crlf = "\r\n" fixture = "5#{crlf}Hello#{crlf}5#{crlf}World#{crlf}0#{crlf*2}" expect(response[(response.length - fixture.length)..-1]).to eq fixture end it "with keep-alive" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.to_s request = connection.request test_chunked_response(request, client) connection.close end end it "without keep-alive" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.tap{ |r| r['Connection'] = 'close' }.to_s request = connection.request test_chunked_response(request, client) connection.close end end it "with pipelined requests" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) 2.times do client << ExampleRequest.new.to_s end client << ExampleRequest.new.tap { |r| r['Connection'] = 'close' }.to_s 3.times do request = connection.request test_chunked_response(request, client) end connection.close end end end it "reset the request after a response is sent" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new(:get, "/", "1.1", {'Connection' => 'close'}) client << example_request expect(connection.request).not_to be_nil connection.respond :ok, "Response sent" expect(connection.request).to be_nil end end it "resets if client dropped connection" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new client << example_request expect(connection.request).not_to be_nil client.close # client drops connection # now send more than the send buffer can hold, triggering a # error (ECONNRESET or EPIPE) connection.respond :ok, ("Some Big Response sent"*100000) # connection should be at end expect(connection.request).to be_nil end end it "raises an error trying to read two pipelines without responding first" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) 2.times do client << ExampleRequest.new.to_s end expect do 2.times { request = connection.request } end.to raise_error(Reel::StateError) end end it "reads pipelined requests without bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) 3.times { client << ExampleRequest.new.to_s } 3.times do request = connection.request expect(request.url).to eq "/" expect(request.version).to eq "1.1" expect(request['Host']).to eq "www.example.com" expect(request['Connection']).to eq "keep-alive" expect(request['User-Agent']).to eq "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S" expect(request['Accept']).to eq "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" expect(request['Accept-Encoding']).to eq "gzip,deflate,sdch" expect(request['Accept-Language']).to eq "en-US,en;q=0.8" expect(request['Accept-Charset']).to eq "ISO-8859-1,utf-8;q=0.7,*;q=0.3" connection.respond :ok, {}, "" end end end it "reads pipelined requests with bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) 3.times do |i| body = "Hello, world number #{i}!" example_request = ExampleRequest.new example_request.body = body client << example_request.to_s end 3.times do |i| request = connection.request expected_body = "Hello, world number #{i}!" expect(request.url).to eq "/" expect(request.version).to eq "1.1" expect(request['Content-Length']).to eq expected_body.length.to_s expect(request.body.to_s).to eq expected_body connection.respond :ok, {}, "" end end end it "reads pipelined requests with streamed bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer, 4) 3.times do |i| body = "Hello, world number #{i}!" example_request = ExampleRequest.new example_request.body = body client << example_request.to_s end 3.times do |i| request = connection.request expected_body = "Hello, world number #{i}!" expect(request.url).to eq "/" expect(request.version).to eq "1.1" expect(request['Content-Length']).to eq expected_body.length.to_s expect(request).not_to be_finished_reading new_content = "" while chunk = request.body.readpartial(1) new_content << chunk end expect(new_content).to eq(expected_body) expect(request).to be_finished_reading connection.respond :ok, {}, "" end end end # This test will deadlock rspec waiting unless # connection.request works properly it "does not block waiting for body to read before handling request" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new content = "Hi guys! Sorry I'm late to the party." example_request['Content-Length'] = content.length client << example_request.to_s request = connection.request expect(request).to be_a(Reel::Request) client << content expect(request.body.to_s).to eq(content) end end it "blocks on read until written" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new content = "Hi guys! Sorry I'm late to the party." example_request['Content-Length'] = content.length client << example_request.to_s request = connection.request timers = Timers::Group.new timers.after(0.2){ client << content } read_body = "" timers.after(0.1){ timers.wait # continue timers, the next bit will block waiting for content read_body = request.read(8) } timers.wait expect(request).to be_a(Reel::Request) expect(read_body).to eq(content[0..7]) end end it "streams body properly with #read and buffered body" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new content = "I'm data you can stream!" example_request['Content-Length'] = content.length client << example_request.to_s request = connection.request expect(request).to be_a(Reel::Request) expect(request).not_to be_finished_reading client << content rebuilt = [] connection.readpartial(64) # Buffer some body while chunk = request.read(8) rebuilt << chunk end expect(request).to be_finished_reading expect(rebuilt).to eq(["I'm data", " you can", " stream!"]) end end context "#readpartial" do it "streams request bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer, 8) example_request = ExampleRequest.new content = "I'm data you can stream!" example_request['Content-Length'] = content.length client << example_request.to_s request = connection.request expect(request).to be_a(Reel::Request) expect(request).not_to be_finished_reading client << content rebuilt = [] while chunk = request.body.readpartial(8) rebuilt << chunk end expect(request).to be_finished_reading expect(rebuilt).to eq(["I'm data", " you can", " stream!"]) end end end context "#respond" do it "accepts Fixnum as status" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) connection.respond 200, {}, [] end end end context "#each" do it "streams request bodies" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new content = "I'm data you can stream!" example_request['Content-Length'] = content.length client << example_request.to_s request = connection.request expect(request).to be_a(Reel::Request) expect(request).not_to be_finished_reading client << content data = "" request.body.each { |chunk| data << chunk } expect(request).to be_finished_reading expect(data).to eq("I'm data you can stream!") end end end describe "IO#read duck typing" do it "raises an exception if length is a negative value" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new client << example_request.to_s request = connection.request expect { request.read(-1) }.to raise_error(ArgumentError) end end it "returns an empty string if the length is zero" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new client << example_request.to_s request = connection.request expect(request.read(0)).to be_empty end end it "reads to EOF if length is nil, even small buffer" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer, 4) example_request = ExampleRequest.new example_request.body = "Hello, world!" expect(connection.buffer_size).to eq(4) client << example_request.to_s request = connection.request expect(request.read).to eq "Hello, world!" end end it "reads to EOF if length is nil" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new example_request.body = "Hello, world!" client << example_request.to_s request = connection.request expect(request.read).to eq "Hello, world!" end end it "uses the optional buffer to recieve data" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new example_request.body = "Hello, world!" client << example_request.to_s request = connection.request buffer = '' expect(request.read(nil, buffer)).to eq "Hello, world!" expect(buffer).to eq "Hello, world!" end end it "returns with the content it could read when the length longer than EOF" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new example_request.body = "Hello, world!" client << example_request.to_s request = connection.request expect(request.read(1024)).to eq "Hello, world!" end end it "returns nil at EOF if a length is passed" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new client << example_request.to_s request = connection.request expect(request.read(1024)).to be_nil end end it "returns an empty string at EOF if length is nil" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) example_request = ExampleRequest.new client << example_request.to_s request = connection.request expect(request.read).to be_empty end end end end reel-0.6.1/spec/reel/http_server_spec.rb0000644000004100000410000000246612676060250020276 0ustar www-datawww-datarequire 'spec_helper' require 'net/http' RSpec.describe Reel::Server::HTTP do let(:endpoint) { URI(example_url) } let(:response_body) { "ohai thar" } it "receives HTTP requests and sends responses" do ex = nil handler = proc do |connection| begin request = connection.request expect(request.method).to eq 'GET' expect(request.version).to eq "1.1" expect(request.url).to eq example_path connection.respond :ok, response_body rescue => ex end end with_reel(handler) do response = Net::HTTP.get endpoint expect(response).to eq response_body end raise ex if ex end it "echoes request bodies as response bodies" do ex = nil handler = proc do |connection| begin request = connection.request expect(request.method).to eq 'POST' connection.respond :ok, request.body.to_s rescue => ex end end with_reel(handler) do http = Net::HTTP.new(endpoint.host, endpoint.port) request = Net::HTTP::Post.new(endpoint.request_uri) request['connection'] = 'close' request.body = response_body response = http.request(request) expect(response).to be_a Net::HTTPOK expect(response.body).to eq(response_body) end raise ex if ex end end reel-0.6.1/spec/reel/response/0000755000004100000410000000000012676060250016220 5ustar www-datawww-datareel-0.6.1/spec/reel/response/writer_spec.rb0000644000004100000410000000126112676060250021073 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Reel::Response::Writer do let(:fixture_path) { File.expand_path("../../../fixtures/example.txt", __FILE__) } let(:expected_response) { "HTTP/1.1 200 OK\r\nContent-Length: 56\r\n\r\n#{File.read(fixture_path)}" } it "streams static files" do with_socket_pair do |socket, peer| writer = described_class.new(socket) File.open(fixture_path, 'r') do |file| response = Reel::Response.new(:ok, {}, file) writer.handle_response(response) end buf = "" begin buf << peer.read(95) rescue IOError # End of body! end expect(buf).to eq expected_response end end end reel-0.6.1/spec/reel/websocket_spec.rb0000644000004100000410000001276112676060250017716 0ustar www-datawww-datarequire 'spec_helper' require 'websocket_parser' RSpec.describe Reel::WebSocket do include WebSocketHelpers let(:example_message) { "Hello, World!" } let(:another_message) { "What's going on?" } it "performs websocket handshakes" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << handshake.to_data request = connection.request expect(request).to be_websocket websocket = request.websocket expect(websocket).to be_a Reel::WebSocket expect(handshake.errors).to be_empty end end it "raises an error if trying to close a connection upgraded to socket" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << handshake.to_data websocket = connection.request.websocket expect(websocket).to be_a Reel::WebSocket expect { connection.close }.to raise_error(Reel::StateError) end end it "knows its URL" do with_websocket_pair do |_, websocket| expect(websocket.url).to eq(example_path) end end it "knows its headers" do with_websocket_pair do |_, websocket| expect(websocket['Host']).to eq(example_host) end end it "reads frames" do with_websocket_pair do |client, websocket| message = WebSocket::Message.new(example_message) message.mask! next_message = WebSocket::Message.new(another_message) next_message.mask! client << message.to_data client << next_message.to_data expect(websocket.read).to eq(example_message) expect(websocket.read).to eq(another_message) end end describe "WebSocket#next_message" do it "triggers on the next sent message" do skip "update to new Celluloid internal APIs" with_websocket_pair do |client, websocket| f = Celluloid::Future.new websocket.on_message do |message| f << Celluloid::SuccessResponse.new(:on_message, message) end message = WebSocket::Message.new(example_message) message.mask! client << message.to_data websocket.read message = f.value expect(message).to eq(example_message) end end end describe "WebSocket#read_every" do it "automatically executes read" do skip "update to new Celluloid internal APIs" with_websocket_pair do |client, websocket| class MyActor include Celluloid def initialize(websocket) websocket.read_every 0.1 end end f = Celluloid::Future.new websocket.on_message do |message| f << Celluloid::SuccessResponse.new(:on_message, message) end message = WebSocket::Message.new(example_message) message.mask! client << message.to_data MyActor.new(websocket) message = f.value expect(message).to eq(example_message) end end end it "writes messages" do with_websocket_pair do |client, websocket| websocket.write example_message websocket.write another_message parser = WebSocket::Parser.new parser.append client.readpartial(4096) until first_message = parser.next_message expect(first_message).to eq(example_message) parser.append client.readpartial(4096) until next_message = parser.next_message expect(next_message).to eq(another_message) end end it "closes" do with_websocket_pair do |_, websocket| expect(websocket).not_to be_closed websocket.close expect(websocket).to be_closed end end it "exposes addr and peeraddr" do with_websocket_pair do |client, websocket| expect(websocket).to respond_to(:peeraddr) expect(websocket.peeraddr.first).to eq "AF_INET" expect(websocket).to respond_to(:addr) expect(websocket.addr.first).to eq "AF_INET" end end it "raises a RequestError when connection used after it was upgraded" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << handshake.to_data remote_host = connection.remote_host request = connection.request expect(request).to be_websocket websocket = request.websocket expect(websocket).to be_a Reel::WebSocket expect { connection.remote_host }.to raise_error(Reel::StateError) expect(websocket.remote_host).to eq(remote_host) end end it "performs websocket handshakes with header key case-insensitivity" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << case_handshake.to_data request = connection.request expect(request).to be_websocket websocket = request.websocket expect(websocket).to be_a Reel::WebSocket expect(case_handshake.errors).to be_empty end end it "pings clients" do with_websocket_pair do |client, websocket| websocket.ping "<3" pinger = double "pinger" expect(pinger).to receive(:ping) { |msg| expect(msg).to eq("<3") } parser = WebSocket::Parser.new parser.on_ping { |msg| pinger.ping msg } parser << client.readpartial(4096) end end def with_websocket_pair with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << handshake.to_data request = connection.request expect(request).to be_websocket websocket = request.websocket expect(websocket).to be_a Reel::WebSocket # Discard handshake client.readpartial(4096) yield client, websocket end end end reel-0.6.1/spec/reel/https_server_spec.rb0000644000004100000410000000672312676060250020461 0ustar www-datawww-datarequire 'spec_helper' require 'net/http' RSpec.describe Reel::Server::HTTPS do let(:example_https_port) { example_port + 1 } let(:example_url) { "https://#{example_addr}:#{example_https_port}#{example_path}" } let(:endpoint) { URI(example_url) } let(:response_body) { "ohai thar" } let(:ca_file) { certs_dir.join('ca.crt').to_s } let(:server_cert) { certs_dir.join("server.crt") .read } let(:server_key) { certs_dir.join("server.key") .read } let(:client_cert) { certs_dir.join("client.crt") .read } let(:client_cert_unsigned) { certs_dir.join("client.unsigned.crt").read } let(:client_key) { certs_dir.join("client.key") .read } it "receives HTTP requests and sends responses" do ex = nil handler = proc do |connection| begin request = connection.request expect(request.method).to eq 'GET' expect(request.version).to eq "1.1" expect(request.url).to eq example_path connection.respond :ok, response_body rescue => ex end end with_reel_https_server(handler) do http = Net::HTTP.new(endpoint.host, endpoint.port) http.use_ssl = true http.ca_file = self.ca_file request = Net::HTTP::Get.new(endpoint.path) response = http.request(request) expect(response.body).to eq response_body end raise ex if ex end it 'verifies client SSL certs when provided with a CA' do ex = nil handler = proc do |connection| begin request = connection.request expect(request.method).to eq 'GET' expect(request.version).to eq '1.1' expect(request.url).to eq example_path connection.respond :ok, response_body rescue => ex end end with_reel_https_server(handler, :ca_file => self.ca_file) do http = Net::HTTP.new(endpoint.host, endpoint.port) http.use_ssl = true http.ca_file = self.ca_file http.cert = OpenSSL::X509::Certificate.new self.client_cert http.key = OpenSSL::PKey::RSA.new self.client_key request = Net::HTTP::Get.new(endpoint.path) response = http.request(request) expect(response.body).to eq response_body end raise ex if ex end it %{fails to verify client certificates that aren't signed} do ex = nil handler = proc do |connection| begin request = connection.request expect(request.method).to eq 'GET' expect(request.version).to eq '1.1' expect(request.url).to eq example_path connection.respond :ok, response_body rescue => ex end end with_reel_https_server(handler, :ca_file => self.ca_file) do http = Net::HTTP.new(endpoint.host, endpoint.port) http.use_ssl = true http.ca_file = self.ca_file http.cert = OpenSSL::X509::Certificate.new self.client_cert_unsigned http.key = OpenSSL::PKey::RSA.new self.client_key request = Net::HTTP::Get.new(endpoint.path) expect { http.request(request) }.to raise_error(OpenSSL::SSL::SSLError) end raise ex if ex end def with_reel_https_server(handler, options = {}) options = { :cert => server_cert, :key => server_key }.merge(options) server = described_class.new(example_addr, example_https_port, options, &handler) yield server ensure server.terminate if server && server.alive? end end reel-0.6.1/spec/reel/response_spec.rb0000644000004100000410000000200712676060250017556 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe Reel::Response do it "streams enumerables" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.to_s request = connection.request connection.respond Reel::Response.new(:ok, ["Hello", "World"]) connection.close response = client.read(4096) crlf = Reel::Response::Writer::CRLF fixture = "5#{crlf}Hello#{crlf}5#{crlf}World#{crlf}0#{crlf*2}" expect(response[(response.length - fixture.length)..-1]).to eq fixture end end it "canonicalizes response headers" do with_socket_pair do |client, peer| connection = Reel::Connection.new(peer) client << ExampleRequest.new.to_s request = connection.request connection.respond Reel::Response.new(:ok, {"content-type" => "application/json"}, "['mmmkay']") connection.close response = client.read(4096) expect(response["Content-Type: application/json"]).to_not be_nil end end end reel-0.6.1/spec/support/0000755000004100000410000000000012676060250015147 5ustar www-datawww-datareel-0.6.1/spec/support/create_certs.rb0000644000004100000410000000403512676060250020141 0ustar www-datawww-datarequire 'fileutils' require 'certificate_authority' FileUtils.mkdir_p(certs_dir) # # Certificate Authority # ca = CertificateAuthority::Certificate.new ca.subject.common_name = 'honestachmed.com' ca.serial_number.number = 1 ca.key_material.generate_key ca.signing_entity = true ca.sign! 'extensions' => { 'keyUsage' => { 'usage' => %w(critical keyCertSign) } } ca_cert_path = File.join(certs_dir, 'ca.crt') ca_key_path = File.join(certs_dir, 'ca.key') File.write ca_cert_path, ca.to_pem File.write ca_key_path, ca.key_material.private_key.to_pem # # Server Certificate # server_cert = CertificateAuthority::Certificate.new server_cert.subject.common_name = '127.0.0.1' server_cert.serial_number.number = 1 server_cert.key_material.generate_key server_cert.parent = ca server_cert.sign! server_cert_path = File.join(certs_dir, 'server.crt') server_key_path = File.join(certs_dir, 'server.key') File.write server_cert_path, server_cert.to_pem File.write server_key_path, server_cert.key_material.private_key.to_pem # # Client Certificate # client_cert = CertificateAuthority::Certificate.new client_cert.subject.common_name = '127.0.0.1' client_cert.serial_number.number = 1 client_cert.key_material.generate_key client_cert.parent = ca client_cert.sign! client_cert_path = File.join(certs_dir, 'client.crt') client_key_path = File.join(certs_dir, 'client.key') File.write client_cert_path, client_cert.to_pem File.write client_key_path, client_cert.key_material.private_key.to_pem # # Self-Signed Client Cert # client_unsigned_cert = CertificateAuthority::Certificate.new client_unsigned_cert.subject.common_name = '127.0.0.1' client_unsigned_cert.serial_number.number = 1 client_unsigned_cert.key_material.generate_key client_unsigned_cert.sign! client_unsigned_cert_path = File.join(certs_dir, 'client.unsigned.crt') client_unsigned_key_path = File.join(certs_dir, 'client.unsigned.key') File.write client_unsigned_cert_path, client_unsigned_cert.to_pem File.write client_unsigned_key_path, client_unsigned_cert.key_material.private_key.to_pem reel-0.6.1/spec/support/example_request.rb0000644000004100000410000000210512676060250020675 0ustar www-datawww-datarequire 'forwardable' class ExampleRequest extend Forwardable def_delegators :@headers, :[], :[]= attr_accessor :method, :path, :version, :body def initialize(method = :get, path = "/", version = "1.1", headers = {}, body = nil) @method = method.to_s.upcase @path = path @version = "1.1" @headers = { 'Host' => 'www.example.com', 'Connection' => 'keep-alive', 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S', 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Encoding' => 'gzip,deflate,sdch', 'Accept-Language' => 'en-US,en;q=0.8', 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3' }.merge(headers) @body = body end def to_s if @body && !@headers['Content-Length'] @headers['Content-Length'] = @body.length end "#{@method} #{@path} HTTP/#{@version}\r\n" << @headers.map { |k, v| "#{k}: #{v}" }.join("\r\n") << "\r\n\r\n" << (@body ? @body : '') end end reel-0.6.1/.travis.yml0000644000004100000410000000025312676060250014612 0ustar www-datawww-datalanguage: ruby sudo: false rvm: - 2.0.0 - 2.1.8 - 2.2.4 - 2.3.0 - jruby-9.0.5.0 matrix: fast_finish: true notifications: irc: "irc.freenode.org#celluloid" reel-0.6.1/lib/0000755000004100000410000000000012676060250013247 5ustar www-datawww-datareel-0.6.1/lib/reel/0000755000004100000410000000000012676060250014176 5ustar www-datawww-datareel-0.6.1/lib/reel/mixins.rb0000644000004100000410000000202512676060250016031 0ustar www-datawww-datamodule Reel module HTTPVersionsMixin HTTP_VERSION_1_0 = '1.0'.freeze HTTP_VERSION_1_1 = '1.1'.freeze DEFAULT_HTTP_VERSION = HTTP_VERSION_1_1 end module ConnectionMixin # Obtain the IP address of the remote connection def remote_ip socket.peeraddr(false)[3] end alias remote_addr remote_ip # Obtain the hostname of the remote connection def remote_host # NOTE: Celluloid::IO does not yet support non-blocking reverse DNS socket.peeraddr(true)[2] end end module RequestMixin def method @request_info.http_method end def headers @request_info.headers end def [](header) headers[header] end def version @request_info.http_version || HTTPVersionsMixin::DEFAULT_HTTP_VERSION end def url @request_info.url end def uri @uri ||= URI(url) end def path uri.path end def query_string uri.query end def fragment uri.fragment end end end reel-0.6.1/lib/reel/spy.rb0000644000004100000410000000331512676060250015340 0ustar www-datawww-datarequire 'forwardable' module Reel # Prints out all traffic to a Reel server. Useful for debugging class Spy extend Forwardable def_delegators :@socket, :closed? def_delegators :@socket, :addr, :peeraddr, :setsockopt, :getsockname def initialize(socket, logger = STDOUT) @socket, @logger = socket, logger @proto, @port, _, @ip = @socket.peeraddr connected end # Log a connection to this server def connected log :connect, "+++ #{@ip}:#{@port} (#{@proto}) connected\n" end # Read from the client def readpartial(maxlen, outbuf = "") data = @socket.readpartial(maxlen, outbuf) log :read, data data end # Write data to the client def write(string) log :write, string @socket << string end alias << write # Close the socket def close @socket.close log :close, "--- #{@ip}:#{@port} (#{@proto}) disconnected\n" end # Log the given event def log(type, str) case type when :connect @logger << Colors.green(str) when :close @logger << Colors.red(str) when :read @logger << Colors.gold(str) when :write @logger << Colors.white(str) else raise "unknown event type: #{type.inspect}" end end module Colors module_function def escape(n); "\033[#{n}m"; end def reset; escape 0; end def color(n); escape "1;#{n}"; end def colorize(n, str); "#{color(n)}#{str}#{reset}"; end def green(str); colorize(32, str); end def red(str); colorize(31, str); end def white(str); colorize(39, str); end def gold(str); colorize(33, str); end end end end reel-0.6.1/lib/reel/connection.rb0000644000004100000410000001142712676060250016667 0ustar www-datawww-datarequire 'reel/request' module Reel # A connection to the HTTP server class Connection include HTTPVersionsMixin include ConnectionMixin CONNECTION = 'Connection'.freeze TRANSFER_ENCODING = 'Transfer-Encoding'.freeze KEEP_ALIVE = 'Keep-Alive'.freeze CLOSE = 'close'.freeze attr_reader :socket, :parser, :current_request attr_accessor :request_state, :response_state # Attempt to read this much data BUFFER_SIZE = 16384 attr_reader :buffer_size def initialize(socket, buffer_size = nil) @attached = true @socket = socket @keepalive = true @buffer_size = buffer_size || BUFFER_SIZE @parser = Request::Parser.new(self) @request_fsm = Request::StateMachine.new(@socket) reset_request @response_state = :headers end # Is the connection still active? def alive?; @keepalive; end # Is the connection still attached to a Reel::Server? def attached?; @attached; end # Detach this connection from the Reel::Server and manage it independently def detach @attached = false self end def readpartial(size = @buffer_size) unless @request_fsm.state == :headers || @request_fsm.state == :body raise StateError, "can't read in the '#{@request_fsm.state}' request state" end @parser.readpartial(size) end # Read a request object from the connection def request raise StateError, "already processing a request" if current_request req = @parser.current_request @request_fsm.transition :headers @keepalive = false if req[CONNECTION] == CLOSE || req.version == HTTP_VERSION_1_0 @current_request = req req rescue IOError, Errno::ECONNRESET, Errno::EPIPE @request_fsm.transition :closed @keepalive = false nil end # Enumerate the requests from this connection, since we might receive # many if the client is using keep-alive def each_request while req = request yield req # Websockets upgrade the connection to the Websocket protocol # Once we have finished processing a Websocket, we can't handle # additional requests break if req.websocket? end end # Send a response back to the client # Response can be a symbol indicating the status code or a Reel::Response def respond(response, headers_or_body = {}, body = nil) raise StateError, "not in header state" if @response_state != :headers if headers_or_body.is_a? Hash headers = headers_or_body else headers = {} body = headers_or_body end if @keepalive headers[CONNECTION] = KEEP_ALIVE else headers[CONNECTION] = CLOSE end case response when Symbol, Fixnum, Integer response = Response.new(response, headers, body) when Response else raise TypeError, "invalid response: #{response.inspect}" end if current_request current_request.handle_response(response) else raise RequestError end # Enable streaming mode if response.chunked? and response.body.nil? @response_state = :chunked_body elsif @keepalive reset_request else @current_request = nil @parser.reset @request_fsm.transition :closed end rescue IOError, SystemCallError, RequestError # The client disconnected early, or there is no request @keepalive = false @request_fsm.transition :closed @parser.reset @current_request = nil end # Close the connection def close raise StateError, "socket has been hijacked from this connection" unless @socket @keepalive = false @socket.close unless @socket.closed? end # Hijack the socket from the connection def hijack_socket # FIXME: this doesn't do a great job of ensuring we can hijack the socket # in its current state. Improve the state detection. if @request_fsm != :ready && @response_state != :headers raise StateError, "connection is not in a hijackable state" end @request_fsm.transition :hijacked @response_state = :hijacked socket = @socket @socket = nil socket end # Raw access to the underlying socket def socket raise StateError, "socket has already been hijacked" unless @socket @socket end # Reset the current request state def reset_request @request_fsm.transition :headers @current_request = nil @parser.reset end private :reset_request # Set response state for the connection. def response_state=(state) if state == :headers reset_request end @response_state = state end end end reel-0.6.1/lib/reel/response.rb0000644000004100000410000000361212676060250016363 0ustar www-datawww-datarequire 'http/headers' module Reel class Response CONTENT_LENGTH = 'Content-Length'.freeze TRANSFER_ENCODING = 'Transfer-Encoding'.freeze CHUNKED = 'chunked'.freeze # Use status code tables from the HTTP gem STATUS_CODES = HTTP::Response::Status::REASONS SYMBOL_TO_STATUS_CODE = Hash[STATUS_CODES.map { |k, v| [v.downcase.gsub(/\s|-/, '_').to_sym, k] }].freeze attr_reader :status # Status has a special setter to coerce symbol names attr_accessor :reason # Reason can be set explicitly if desired attr_reader :headers, :body, :version def initialize(status, body_or_headers = nil, body = nil) self.status = status if body_or_headers.is_a?(Hash) headers = body_or_headers.dup @body = body else headers = {} @body = body_or_headers end case @body when String headers[CONTENT_LENGTH] ||= @body.bytesize when IO headers[CONTENT_LENGTH] ||= @body.stat.size when Enumerable headers[TRANSFER_ENCODING] ||= CHUNKED when NilClass else raise TypeError, "can't render #{@body.class} as a response body" end @headers = HTTP::Headers.coerce(headers) @version = http_version end def chunked? headers[TRANSFER_ENCODING].to_s == CHUNKED end # Set the status def status=(status, reason=nil) case status when Integer @status = status @reason ||= STATUS_CODES[status] when Symbol if code = SYMBOL_TO_STATUS_CODE[status] self.status = code else raise ArgumentError, "unrecognized status symbol: #{status}" end else raise TypeError, "invalid status type: #{status.inspect}" end end def http_version # FIXME: real HTTP versioning "HTTP/1.1".freeze end private :http_version end end reel-0.6.1/lib/reel/response/0000755000004100000410000000000012676060250016034 5ustar www-datawww-datareel-0.6.1/lib/reel/response/writer.rb0000644000004100000410000000334312676060250017700 0ustar www-datawww-datamodule Reel class Response class Writer CRLF = "\r\n" def initialize(socket) @socket = socket end # Write body chunks directly to the connection def write(chunk) chunk_header = chunk.bytesize.to_s(16) @socket << chunk_header + CRLF @socket << chunk + CRLF rescue IOError, SystemCallError => ex raise Reel::SocketError, ex.to_s end # Finish the response and reset the response state to header def finish_response @socket << "0#{CRLF * 2}" end # Render a given response object to the network socket def handle_response(response) @socket << render_header(response) return response.render(@socket) if response.respond_to?(:render) case response.body when String @socket << response.body when IO Celluloid::IO.copy_stream(response.body, @socket) when Enumerable response.body.each { |chunk| write(chunk) } finish_response when NilClass # Used for streaming Transfer-Encoding chunked responses return else raise TypeError, "don't know how to render a #{response.body.class}" end response.body.close if response.body.respond_to?(:close) end # Convert headers into a string def render_header(response) response_header = "#{response.version} #{response.status} #{response.reason}#{CRLF}" unless response.headers.empty? response_header << response.headers.map do |header, value| "#{header}: #{value}" end.join(CRLF) << CRLF end response_header << CRLF end private :render_header end end end reel-0.6.1/lib/reel/server/0000755000004100000410000000000012676060250015504 5ustar www-datawww-datareel-0.6.1/lib/reel/server/http.rb0000644000004100000410000000104512676060250017010 0ustar www-datawww-datamodule Reel class Server class HTTP < Server # Create a new Reel HTTP server # # @param [String] host address to bind to # @param [Fixnum] port to bind to # @option options [Fixnum] backlog of requests to accept # # @return [Reel::Server::HTTP] Reel HTTP server actor def initialize(host, port, options={}, &callback) server = Celluloid::IO::TCPServer.new(host, port) options.merge!(host: host, port: port) super(server, options, &callback) end end end end reel-0.6.1/lib/reel/server/https.rb0000644000004100000410000000433312676060250017176 0ustar www-datawww-datamodule Reel class Server class HTTPS < Server # Create a new Reel HTTPS server # # @param [String] host address to bind to # @param [Fixnum] port to bind to # @option options [Fixnum] backlog of requests to accept # @option options [String] :cert the server's TLS certificate # @option options [String] :key the server's TLS key # @option options [Array] :extra_cert_chain TLS certificate chain # # @return [Reel::Server::HTTPS] Reel HTTPS server actor def initialize(host, port, options={}, &callback) # Ideally we can encapsulate this rather than making Ruby OpenSSL a # mandatory part of the Reel API. It would be nice to support # alternatives (e.g. Puma's MiniSSL) ssl_context = OpenSSL::SSL::SSLContext.new ssl_context.cert = OpenSSL::X509::Certificate.new options.fetch(:cert) ssl_context.key = OpenSSL::PKey::RSA.new options.fetch(:key) ssl_context.ca_file = options[:ca_file] ssl_context.ca_path = options[:ca_path] ssl_context.extra_chain_cert = options[:extra_chain_cert] # if verify_mode isn't explicitly set, verify peers if we've # been provided CA information that would enable us to do so ssl_context.verify_mode = case when options.include?(:verify_mode) options[:verify_mode] when options.include?(:ca_file) OpenSSL::SSL::VERIFY_PEER when options.include?(:ca_path) OpenSSL::SSL::VERIFY_PEER else OpenSSL::SSL::VERIFY_NONE end @tcpserver = Celluloid::IO::TCPServer.new(host, port) server = Celluloid::IO::SSLServer.new(@tcpserver, ssl_context) options.merge!(host: host, port: port) super(server, options, &callback) end def run loop do begin socket = @server.accept rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT, Errno::EHOSTUNREACH => ex Logger.warn "Error accepting SSLSocket: #{ex.class}: #{ex.to_s}" retry end async.handle_connection socket end end end end end reel-0.6.1/lib/reel/logger.rb0000644000004100000410000000061012676060250015777 0ustar www-datawww-datarequire 'logger' module Reel module Logger @logger = ::Logger.new STDERR module_function def logger=(logger) @logger = logger end def logger @logger end def debug(msg); @logger.debug(msg); end def info(msg); @logger.info(msg); end def warn(msg); @logger.warn(msg); end def error(msg); @logger.error(msg); end end endreel-0.6.1/lib/reel/request.rb0000644000004100000410000000737012676060250016222 0ustar www-datawww-datarequire 'forwardable' require 'reel/request/body' require 'reel/request/info' require 'reel/request/parser' require 'reel/request/state_machine' require 'reel/response/writer' module Reel class Request extend Forwardable include RequestMixin def_delegators :@connection, :remote_addr, :respond def_delegator :@response_writer, :handle_response attr_reader :body # request_info is a RequestInfo object including the headers and # the url, method and http version. # # Access it through the RequestMixin methods. def initialize(request_info, connection = nil) @request_info = request_info @connection = connection @finished = false @buffer = "" @finished_read = false @websocket = nil @body = Request::Body.new(self) @response_writer = Response::Writer.new(connection.socket) end # Returns true if request fully finished reading def finished_reading?; @finished_read; end # When HTTP Parser marks the message parsing as complete, this will be set. def finish_reading! raise StateError, "already finished" if @finished_read @finished_read = true end # Fill the request buffer with data as it becomes available def fill_buffer(chunk) @buffer << chunk end # Read a number of bytes, looping until they are available or until # readpartial returns nil, indicating there are no more bytes to read def read(length = nil, buffer = nil) raise ArgumentError, "negative length #{length} given" if length && length < 0 return '' if length == 0 res = buffer.nil? ? '' : buffer.clear chunk_size = length.nil? ? @connection.buffer_size : length begin while chunk_size > 0 chunk = readpartial(chunk_size) break unless chunk res << chunk chunk_size = length - res.length unless length.nil? end rescue EOFError end return length && res.length == 0 ? nil : res end # Read a string up to the given number of bytes, blocking until some # data is available but returning immediately if some data is available def readpartial(length = nil) if length.nil? && @buffer.length > 0 slice = @buffer @buffer = "" else unless finished_reading? || (length && length <= @buffer.length) @connection.readpartial(length ? length - @buffer.length : @connection.buffer_size) end if length slice = @buffer.slice!(0, length) else slice = @buffer @buffer = "" end end slice && slice.length == 0 ? nil : slice end # Write body chunks directly to the connection def write(chunk) unless @connection.response_state == :chunked_body raise StateError, "not in chunked body mode" end @response_writer.write(chunk) end alias_method :<<, :write # Finish the response and reset the response state to header def finish_response raise StateError, "not in body state" if @connection.response_state != :chunked_body @response_writer.finish_response @connection.response_state = :headers end # Can the current request be upgraded to a WebSocket? def websocket?; @request_info.websocket_request?; end # Return a Reel::WebSocket for this request, hijacking the socket from # the underlying connection def websocket @websocket ||= begin raise StateError, "can't upgrade this request to a websocket" unless websocket? WebSocket.new(self, @connection) end end # Friendlier inspect def inspect "#<#{self.class} #{method} #{url} HTTP/#{version} @headers=#{headers.inspect}>" end end end reel-0.6.1/lib/reel/version.rb0000644000004100000410000000007412676060250016211 0ustar www-datawww-datamodule Reel VERSION = "0.6.1" CODENAME = "Garland" end reel-0.6.1/lib/reel/request/0000755000004100000410000000000012676060250015666 5ustar www-datawww-datareel-0.6.1/lib/reel/request/parser.rb0000644000004100000410000000436112676060250017513 0ustar www-datawww-datamodule Reel class Request class Parser include HTTPVersionsMixin attr_reader :socket, :connection def initialize(connection) @parser = HTTP::Parser.new(self) @connection = connection @socket = connection.socket @buffer_size = connection.buffer_size @currently_reading = @currently_responding = nil @pending_reads = [] @pending_responses = [] reset end def add(data) @parser << data end alias_method :<<, :add def http_method @parser.http_method end def http_version # TODO: add extra HTTP_VERSION handler when HTTP/1.2 released @parser.http_version[1] == 1 ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0 end def url @parser.request_url end def current_request until @currently_responding || @currently_reading readpartial end @currently_responding || @currently_reading end def readpartial(size = @buffer_size) bytes = @socket.readpartial(size) @parser << bytes end # # HTTP::Parser callbacks # def on_headers_complete(headers) info = Info.new(http_method, url, http_version, headers) req = Request.new(info, connection) if @currently_reading @pending_reads << req else @currently_reading = req end end # Send body directly to Reel::Response to be buffered. def on_body(chunk) @currently_reading.fill_buffer(chunk) end # Mark current request as complete, set this as ready to respond. def on_message_complete @currently_reading.finish_reading! if @currently_reading.is_a?(Request) if @currently_responding @pending_responses << @currently_reading else @currently_responding = @currently_reading end @currently_reading = @pending_reads.shift end def reset popped = @currently_responding if req = @pending_responses.shift @currently_responding = req elsif @currently_responding @currently_responding = nil end popped end end end end reel-0.6.1/lib/reel/request/info.rb0000644000004100000410000000122212676060250017143 0ustar www-datawww-datamodule Reel class Request class Info CASE_INSENSITVE_HASH = Hash.new do |hash, key| hash[hash.keys.find {|k| k =~ /#{key}/i}] if key end attr_reader :http_method, :url, :http_version, :headers def initialize(http_method, url, http_version, headers) @http_method = http_method @url = url @http_version = http_version @headers = CASE_INSENSITVE_HASH.merge headers end UPGRADE = 'Upgrade'.freeze WEBSOCKET = 'websocket'.freeze def websocket_request? headers[UPGRADE] && headers[UPGRADE].downcase == WEBSOCKET end end end end reel-0.6.1/lib/reel/request/body.rb0000644000004100000410000000305712676060250017155 0ustar www-datawww-datamodule Reel class Request # Represents the bodies of Requests class Body include Enumerable def initialize(request) @request = request @streaming = nil @contents = nil end # Read exactly the given amount of data def read(length) stream! @request.read(length) end # Read up to length bytes, but return any data that's available def readpartial(length = nil) stream! @request.readpartial(length) end # Iterate over the body, allowing it to be enumerable def each while chunk = readpartial yield chunk end end def empty? to_str.empty? end # Eagerly consume the entire body as a string def to_str return @contents if @contents raise StateError, "body is being streamed" unless @streaming.nil? begin @streaming = false @contents = "" while chunk = @request.readpartial @contents << chunk end rescue @contents = nil raise end @contents end alias_method :to_s, :to_str # Easier to interpret string inspect def inspect "#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>" end # Assert that the body is actively being streamed def stream! raise StateError, "body has already been consumed" if @streaming == false @streaming = true end private :stream! end end end reel-0.6.1/lib/reel/request/state_machine.rb0000644000004100000410000000120412676060250021014 0ustar www-datawww-datamodule Reel class Request # Tracks the state of Reel requests class StateMachine include Celluloid::FSM def initialize(socket) @socket = socket @hijacked = false end default_state :headers state :headers, :to => [:body, :hijacked, :closed] state :body, :to => [:headers, :closed] state :hijacked do @hijacked = true end # FSM fails open for valid transitions # Set an empty array to disallow transitioning out of closed state :closed, :to => [] do @socket.close unless @hijacked || @socket.closed? end end end end reel-0.6.1/lib/reel/stream.rb0000644000004100000410000000454312676060250016024 0ustar www-datawww-datamodule Reel class Stream def initialize(&proc) @proc = proc end def call(socket) @socket = socket @proc.call self end def write(data) @socket << data data rescue IOError, Errno::ECONNRESET, Errno::EPIPE raise SocketError, "error writing to socket" end alias :<< :write # behaves like a true Rack::Response/BodyProxy object def each yield self end def close @socket.close unless closed? end alias finish close def closed? @socket.closed? end end class EventStream < Stream # EventSource-related helpers # # @example # Reel::EventStream.new do |socket| # socket.event 'some event' # socket.retry 10 # end # # @note # though retry is a reserved word, it is ok to use it as `object#retry` # %w[event id retry].each do |meth| define_method meth do |data| # unlike on #data, these messages expects a single \n at the end. write "%s: %s\n" % [meth, data] end end def data(data) # - any single message should not contain \n except at the end. # - EventSource expects \n\n at the end of each single message. write "data: %s\n\n" % data.gsub(/\n|\r/, '') self end end class ChunkStream < Stream def write(chunk) chunk_header = chunk.bytesize.to_s(16) super chunk_header + Response::Writer::CRLF super chunk + Response::Writer::CRLF self end alias :<< :write # finish does not actually close the socket, # it only inform the browser there are no more messages def finish write "" end def close finish super end end class StreamResponse < Response IDENTITY = 'identity'.freeze def initialize(status, headers, body) self.status = status @body = body case @body when EventStream # EventSource behaves extremely bad on chunked Transfer-Encoding headers[TRANSFER_ENCODING] = IDENTITY when ChunkStream headers[TRANSFER_ENCODING] = CHUNKED when Stream else raise TypeError, "can't render #{@body.class} as a response body" end @headers = HTTP::Headers.coerce(headers) @version = http_version end def render(socket) @body.call socket end end end reel-0.6.1/lib/reel/server.rb0000644000004100000410000000262012676060250016031 0ustar www-datawww-datamodule Reel # Base class for Reel servers. # # This class is a Celluloid::IO actor which provides a barebones server # which does not open a socket itself, it just begin handling connections once # initialized with a specific kind of protocol-based server. # For specific protocol support, use: # Reel::Server::HTTP # Reel::Server::HTTPS # Coming soon: Reel::Server::UNIX class Server include Celluloid::IO # How many connections to backlog in the TCP accept queue DEFAULT_BACKLOG = 100 execute_block_on_receiver :initialize finalizer :shutdown def initialize(server, options={}, &callback) @spy = STDOUT if options[:spy] @options = options @callback = callback @server = server @server.listen(options.fetch(:backlog, DEFAULT_BACKLOG)) async.run end def shutdown @server.close if @server end def run loop { async.handle_connection @server.accept } end def handle_connection(socket) if @spy require 'reel/spy' socket = Reel::Spy.new(socket, @spy) end connection = Connection.new(socket) begin @callback.call(connection) ensure if connection.attached? connection.close rescue nil end end rescue RequestError, EOFError # Client disconnected prematurely # TODO: log this? end end end reel-0.6.1/lib/reel/websocket.rb0000644000004100000410000000620712676060250016516 0ustar www-datawww-datarequire 'forwardable' require 'websocket/driver' module Reel class WebSocket extend Forwardable include ConnectionMixin include RequestMixin attr_reader :socket def_delegators :@socket, :addr, :peeraddr def_delegators :@driver, :ping def initialize(info, connection) driver_env = DriverEnvironment.new(info, connection.socket) @socket = connection.hijack_socket @request_info = info @driver = ::WebSocket::Driver.rack(driver_env) @driver.on(:close) do @socket.close end @message_stream = MessageStream.new(@socket, @driver) @driver.start rescue EOFError close end def on_message(&block) @driver.on :message do |message| block.(message.data) end end [:error, :close, :ping, :pong].each do |meth| define_method "on_#{meth}" do |&proc| @driver.send(:on, meth, &proc) end end def read_every(n, unit = :s) cancel_timer! # only one timer allowed per stream seconds = case unit.to_s when /\Am/ n * 60 when /\Ah/ n * 3600 else n end @timer = Celluloid.every(seconds) { read } end alias read_interval read_every alias read_frequency read_every def read @message_stream.read end def closed? @socket.closed? end def write(msg) if msg.is_a? String @driver.text(msg) elsif msg.is_a? Array @driver.binary(msg) else raise "Can only send byte array or string over driver." end rescue IOError, Errno::ECONNRESET, Errno::EPIPE cancel_timer! raise SocketError, "error writing to socket" rescue cancel_timer! raise end alias_method :<<, :write def close @driver.close @socket.close end def cancel_timer! @timer && @timer.cancel end private class DriverEnvironment extend Forwardable attr_reader :env, :socket def_delegator :@info, :url def_delegator :@socket, :write RACK_HEADERS = { 'HTTP_ORIGIN' => 'Origin', 'HTTP_SEC_WEBSOCKET_KEY' => 'Sec-WebSocket-Key', 'HTTP_SEC_WEBSOCKET_KEY1' => 'Sec-WebSocket-Key1', 'HTTP_SEC_WEBSOCKET_KEY2' => 'Sec-WebSocket-Key2', 'HTTP_SEC_WEBSOCKET_EXTENSIONS' => 'Sec-WebSocket-Extensions', 'HTTP_SEC_WEBSOCKET_PROTOCOL' => 'Sec-WebSocket-Protocol', 'HTTP_SEC_WEBSOCKET_VERSION' => 'Sec-WebSocket-Version' }.freeze def initialize(info, socket) @info, @socket = info, socket @env = Hash.new {|h,k| @info.headers[RACK_HEADERS[k]]} end end class MessageStream def initialize(socket, driver) @socket = socket @driver = driver @message_buffer = [] @driver.on :message do |message| @message_buffer.push(message.data) end end def read while @message_buffer.empty? buffer = @socket.readpartial(Connection::BUFFER_SIZE) @driver.parse(buffer) end @message_buffer.shift end end end end reel-0.6.1/lib/reel.rb0000644000004100000410000000212012676060250014516 0ustar www-datawww-datarequire 'uri' require 'http/parser' require 'http' require 'celluloid/io' require 'reel/version' require 'reel/mixins' require 'reel/connection' require 'reel/logger' require 'reel/request' require 'reel/response' require 'reel/server' require 'reel/server/http' require 'reel/server/https' require 'reel/websocket' require 'reel/stream' # A Reel good HTTP server module Reel # Error reading a request class RequestError < StandardError; end # Error occurred performing IO on a socket class SocketError < RequestError; end # Error occurred when trying to use the socket after it was upgraded class SocketUpgradedError < NilClass def self.method_missing(m, *) raise(Reel::RequestError, 'Reel::Connection#socket can not be used anymore as it was upgraded. Use Reel::WebSocket instance instead.') end end # Error occured during a WebSockets handshake class HandshakeError < RequestError; end # The method given was not understood class UnsupportedMethodError < ArgumentError; end # wrong state for a given operation class StateError < RuntimeError; end end reel-0.6.1/reel.gemspec0000644000004100000410000000226412676060250015001 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('../lib/reel/version', __FILE__) Gem::Specification.new do |gem| gem.authors = ["Tony Arcieri"] gem.email = ["tony.arcieri@gmail.com"] gem.description = "A Celluloid::IO-powered HTTP server" gem.summary = "A Reel good HTTP server" gem.homepage = "https://github.com/celluloid/reel" gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } gem.files = `git ls-files`.split("\n") gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") gem.name = "reel" gem.require_paths = ["lib"] gem.version = Reel::VERSION gem.add_runtime_dependency 'celluloid', '>= 0.15.1' gem.add_runtime_dependency 'celluloid-io', '>= 0.15.0' gem.add_runtime_dependency 'http', '>= 0.6.0.pre' gem.add_runtime_dependency 'http_parser.rb', '>= 0.6.0' gem.add_runtime_dependency 'websocket-driver', '>= 0.5.1' gem.add_development_dependency 'rake' gem.add_development_dependency 'rspec', '>= 2.11.0' gem.add_development_dependency 'certificate_authority' gem.add_development_dependency 'websocket_parser', '>= 0.1.6' end reel-0.6.1/.gitignore0000644000004100000410000000023212676060250014466 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp reel-0.6.1/tasks/0000755000004100000410000000000012676060250013626 5ustar www-datawww-datareel-0.6.1/tasks/rspec.rake0000644000004100000410000000017712676060250015613 0ustar www-datawww-datarequire 'rspec/core/rake_task' RSpec::Core::RakeTask.new RSpec::Core::RakeTask.new(:rcov) do |task| task.rcov = true end reel-0.6.1/benchmarks/0000755000004100000410000000000012676060250014616 5ustar www-datawww-datareel-0.6.1/benchmarks/hello_node.js0000644000004100000410000000042712676060250017267 0ustar www-datawww-data/* Run with: node hello_node.js */ var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, "127.0.0.1"); console.log('Server running at http://127.0.0.1:1337/'); reel-0.6.1/benchmarks/reel_pool.rb0000644000004100000410000000123012676060250017117 0ustar www-datawww-datarequire 'bundler/setup' require 'reel' class MyConnectionHandler include Celluloid def handle_connection(connection) connection.each_request { |req| handle_request(req) } rescue Reel::SocketError connection.close end def handle_request(request) request.respond :ok, '' end end connectionPool = MyConnectionHandler.pool Reel::Server::HTTP.run('127.0.0.1', 3000) do |connection| # We're handing this connection off to another actor, so # we detach it first before handing it off connection.detach # Let a Connection Pool handle the connections for Roflscale Applications connectionPool.async.handle_connection(connection) end reel-0.6.1/benchmarks/hello_goliath.rb0000644000004100000410000000040012676060250017747 0ustar www-datawww-data# Run with: ruby hello_goliath.rb -sv -e production require 'goliath' class Hello < Goliath::API # default to JSON output, allow Yaml as secondary use Goliath::Rack::Render, ['json', 'yaml'] def response(env) [200, {}, "Hello World"] end end reel-0.6.1/benchmarks/hello_reel.rb0000644000004100000410000000035712676060250017262 0ustar www-datawww-datarequire 'rubygems' require 'bundler/setup' require 'reel' addr, port = '127.0.0.1', 1234 puts "*** Starting server on #{addr}:#{port}" Reel::Server::HTTP.new(addr, port) do |connection| connection.respond :ok, "Hello World" end sleep reel-0.6.1/CHANGES.md0000644000004100000410000000551012676060250014074 0ustar www-datawww-data## 0.6.1 (2016-03-14) * [#221](https://github.com/celluloid/reel/pull/221) Remove rack dependency. Add WebSocket ping forward. (**@kenichi**) ## 0.6.0 "Garland" (2016-02-13) * [#214](https://github.com/celluloid/reel/pull/214): Fix ChunkStream termination. (**@ogoid**) * [#182](https://github.com/celluloid/reel/pull/182): Do not allow transitioning out of closed. (**@zanker**) * [#168](https://github.com/celluloid/reel/pull/168): Revert removal of addr and peeraddr delegates. (**@d-snp**) * [#167](https://github.com/celluloid/reel/pull/167): Delegate #addr, #peeraddr, etc. methods in Spy. (**@d-snp**) * [#166](https://github.com/celluloid/reel/pull/166): Switch to websocket-driver gem. (**@d-snp**) * [#162](https://github.com/celluloid/reel/pull/162): Fix for #150: Reel::StateError: already processing a request when client is killed. (**@artcom**) * [#155](https://github.com/celluloid/reel/pull/155): Handle Errno::ECONNRESET in SSL server. (**@czaks**) * [#152](https://github.com/celluloid/reel/pull/152): Case insensitivity for header field names. (**@kenichi**) * [#151](https://github.com/celluloid/reel/pull/151): Support for new http.rb gem API. (**@ixti**) * [#148](https://github.com/celluloid/reel/pull/148): Fix stack level too deep when writing to ChunkStream. (**@bastjan**) ## 0.5.0 "Bette" (2014-04-15) * Reel::Server(::SSL) renamed to Reel::Server::HTTP and Reel::Server::HTTPS * New Reel::Spy API for observing requests and responses from the server * Fixes to chunked encoding handling * Update websocket_parser gem to 0.1.6 * Update to "The HTTP Gem" 0.6.0 * Ensure response bodies are always closed * Support for passing a fixnum status to Connection#respond ## 0.4.0 "Garbo" * Rack adapter moved to the reel-rack project * Pipelining support * Reel::Connection#each_request for iterating through keep-alive requests * Reel::Request#body now returns a Reel::RequestBody object instead of a String * New WebSocket API: obtain WebSockets through Reel::Request#websocket instead of through Reel::Connection#request. Allows processing of WebSockets through other means than the built-in WebSocket support * Allow Reel to stop cleanly * Remove `on_error` callback system * Increase buffer size * Remove Reel::App (unmaintained, sorry) * Reel::CODENAME added (0.4.0 is "Garbo") ## 0.3.0 * Reel::App: Sinatra-like DSL for defining Reel apps using Octarine * Chunked upload support * Lots of additional work on the Rack adapter * Expose websockets through Rack as rack.websocket * Performance optimization work * Bugfix: Send CRLF after chunks * Bugfix: Increase TCP connection backlog to 1024 ## 0.2.0 * Initial WebSockets support via Reel::WebSocket * Experimental Rack adapter by Alberto Fernández-Capel * Octarine (Sinatra-like DSL) support by Grant Rodgers ## 0.1.0 * Initial release reel-0.6.1/README.md0000644000004100000410000001303612676060250013763 0ustar www-datawww-data![Reel](https://github.com/celluloid/reel/raw/master/logo.png) ======= [![Gem Version](https://badge.fury.io/rb/reel.svg)](http://rubygems.org/gems/reel) [![Build Status](https://secure.travis-ci.org/celluloid/reel.svg?branch=master)](http://travis-ci.org/celluloid/reel) [![Code Climate](https://codeclimate.com/github/celluloid/reel.svg)](https://codeclimate.com/github/celluloid/reel) [![Coverage Status](https://coveralls.io/repos/celluloid/reel/badge.svg?branch=master)](https://coveralls.io/r/celluloid/reel) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/celluloid/reel/master/LICENSE.txt) > "A dizzying lifetime... reeling by on celluloid" _-- Rush / Between The Wheels_ Reel is a fast, non-blocking "evented" web server built on [http_parser.rb][parser], [websocket_parser][websockets], [Celluloid::IO][celluloidio], and [nio4r][nio4r]. Thanks to Celluloid, Reel also works great for multithreaded applications and provides traditional multithreaded blocking I/O support too. [parser]: https://github.com/tmm1/http_parser.rb [websockets]: https://github.com/afcapel/websocket_parser [celluloidio]: https://github.com/celluloid/celluloid-io [nio4r]: https://github.com/celluloid/nio4r Connections to Reel can be either non-blocking and handled entirely within the Reel::Server thread (handling HTTP, HTTPS, or UNIX sockets), or the same connections can be dispatched to worker threads where they will perform ordinary blocking IO. Reel provides no built-in thread pool, however you can build one yourself using Celluloid.pool, or because Celluloid already pools threads to begin with, you can simply use an actor per connection. This gives you the best of both worlds: non-blocking I/O for when you're primarily I/O bound, and threads for where you're compute bound. ### Is it any good? [Yes](http://news.ycombinator.com/item?id=3067434) Documentation ------------- [Please see the Reel Wiki](https://github.com/celluloid/reel/wiki) for detailed documentation and usage notes. [YARD documentation](http://rubydoc.info/github/celluloid/reel/master/frames) is also available. Framework Adapters ------------------ ### Rack A Rack adapter for Reel is available at: https://github.com/celluloid/reel-rack ### Webmachine The most notable library with native Reel support is [webmachine-ruby](https://github.com/seancribbs/webmachine-ruby), an advanced HTTP framework for Ruby with a complete state machine for proper processing of HTTP/1.1 requests. Together with Reel, Webmachine provides full streaming support for both requests and responses. To use Reel with Webmachine, add the following to your Gemfile: ```ruby gem 'webmachine', git: 'git://github.com/seancribbs/webmachine-ruby.git' ``` Then use `config.adapter = :Reel` when configuring a Webmachine app, e.g: ```ruby MyApp = Webmachine::Application.new do |app| app.routes do add ['*'], MyHome end app.configure do |config| config.ip = MYAPP_IP config.port = MYAPP_PORT config.adapter = :Reel # Optional: handler for incoming websockets config.adapter_options[:websocket_handler] = proc do |websocket| # websocket is a Reel::WebSocket websocket << "hello, world" end end end MyApp.run ``` See the [Webmachine documentation](http://rubydoc.info/gems/webmachine/frames/file/README.md) for further information Ruby API -------- Reel aims to provide a "bare metal" API that other frameworks (such as Rack and Webmachine) can leverage. This API can also be nice in performance critical applications. ### Block Form Reel lets you pass a block to initialize which receives connections: ```ruby require 'celluloid/autostart' require 'reel' Reel::Server::HTTP.supervise("0.0.0.0", 3000) do |connection| # Support multiple keep-alive requests per connection connection.each_request do |request| # WebSocket support if request.websocket? puts "Client made a WebSocket request to: #{request.url}" websocket = request.websocket websocket << "Hello everyone out there in WebSocket land" websocket.close else puts "Client requested: #{request.method} #{request.url}" request.respond :ok, "Hello, world!" end end end sleep ``` When we read a request from the incoming connection, we'll either get back a Reel::Request object, indicating a normal HTTP connection, or a Reel::WebSocket object for WebSockets connections. ### Subclass Form You can also subclass Reel, which allows additional customizations: ```ruby require 'celluloid/autostart' require 'reel' class MyServer < Reel::Server::HTTP def initialize(host = "127.0.0.1", port = 3000) super(host, port, &method(:on_connection)) end def on_connection(connection) connection.each_request do |request| if request.websocket? handle_websocket(request.websocket) else handle_request(request) end end end def handle_request(request) request.respond :ok, "Hello, world!" end def handle_websocket(sock) sock << "Hello everyone out there in WebSocket land!" sock.close end end MyServer.run ``` Supported Ruby Versions ----------------------- This library supports and is tested against the following Ruby versions: * Ruby (MRI) 2.0, 2.1, 2.2, 2.3 * JRuby 9000 Contributing ------------ * Fork this repository on GitHub * Make your changes and send us a pull request * If we like them we'll merge them * If we've accepted a patch, feel free to ask for commit access License ------- Copyright (c) 2012-2016 Tony Arcieri. Distributed under the MIT License. See LICENSE.txt for further details. reel-0.6.1/Guardfile0000644000004100000410000000052712676060250014332 0ustar www-datawww-dataguard :rspec, cmd: "bundle exec rspec" do require "guard/rspec/dsl" dsl = Guard::RSpec::Dsl.new(self) # RSpec files rspec = dsl.rspec watch(rspec.spec_helper) { rspec.spec_dir } watch(rspec.spec_support) { rspec.spec_dir } watch(rspec.spec_files) # Ruby files ruby = dsl.ruby dsl.watch_spec_files_for(ruby.lib_files) end