htttee 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -2
- data/.travis.yml +6 -0
- data/README.md +13 -1
- data/bin/htttee +2 -2
- data/bin/htttee-exec +8 -7
- data/config.ru +1 -1
- data/htttee.gemspec +4 -3
- data/lib/htttee/client.rb +4 -6
- data/lib/htttee/client/consumer.rb +38 -34
- data/lib/htttee/server.rb +45 -39
- data/lib/htttee/server/api.rb +155 -139
- data/lib/htttee/server/chunked_body.rb +13 -15
- data/lib/htttee/server/middleware/async_fixer.rb +12 -15
- data/lib/htttee/server/middleware/dechunker.rb +44 -45
- data/lib/htttee/server/middleware/rechunker.rb +15 -0
- data/lib/htttee/server/mock.rb +44 -46
- data/lib/htttee/server/pubsub_redis.rb +4 -0
- data/lib/htttee/server/sse/body.rb +71 -0
- data/lib/htttee/server/sse/middleware.rb +96 -0
- data/lib/htttee/version.rb +2 -4
- data/spec/client_spec.rb +102 -2
- data/spec/helpers/rackup.rb +1 -1
- data/spec/spec_helper.rb +3 -3
- metadata +72 -46
- data/deploy/after_restart.rb +0 -1
- data/deploy/before_restart.rb +0 -4
- data/deploy/before_symlink.rb +0 -2
- data/deploy/cookbooks/cloudkick-plugins/recipes/default.rb +0 -8
- data/deploy/cookbooks/cloudkick-plugins/recipes/resque.rb +0 -18
- data/deploy/cookbooks/cloudkick-plugins/templates/default/resque.sh.erb +0 -4
- data/deploy/cookbooks/god/recipes/default.rb +0 -50
- data/deploy/cookbooks/god/templates/default/config.erb +0 -3
- data/deploy/cookbooks/god/templates/default/god-inittab.erb +0 -3
- data/deploy/cookbooks/main/attributes/owner_name.rb +0 -3
- data/deploy/cookbooks/main/attributes/recipes.rb +0 -1
- data/deploy/cookbooks/main/libraries/dnapi.rb +0 -7
- data/deploy/cookbooks/main/recipes/default.rb +0 -2
- data/deploy/cookbooks/nginx/files/default/chunkin-nginx-module-v0.22rc1.zip +0 -0
- data/deploy/cookbooks/nginx/files/default/nginx-1.0.0.tar.gz +0 -0
- data/deploy/cookbooks/nginx/recipes/default.rb +0 -2
- data/deploy/cookbooks/nginx/recipes/install.rb +0 -0
- data/deploy/cookbooks/nginx/templates/default/nginx.conf.erb +0 -41
- data/deploy/cookbooks/resque/recipes/default.rb +0 -47
- data/deploy/cookbooks/resque/templates/default/resque.rb.erb +0 -73
- data/deploy/cookbooks/resque/templates/default/resque.yml.erb +0 -3
- data/deploy/cookbooks/resque/templates/default/resque_scheduler.rb.erb +0 -73
- data/deploy/solo.rb +0 -7
| @@ -1,21 +1,19 @@ | |
| 1 | 
            -
            module  | 
| 2 | 
            -
              module  | 
| 3 | 
            -
                 | 
| 4 | 
            -
                   | 
| 5 | 
            -
                     | 
| 6 | 
            -
                       | 
| 7 | 
            -
                        @body_callback.call(chunk(fragment))
         | 
| 8 | 
            -
                      end
         | 
| 1 | 
            +
            module HTTTee
         | 
| 2 | 
            +
              module Server
         | 
| 3 | 
            +
                class ChunkedBody < Thin::DeferrableBody
         | 
| 4 | 
            +
                  def call(body)
         | 
| 5 | 
            +
                    body.each do |fragment|
         | 
| 6 | 
            +
                      @body_callback.call(chunk(fragment))
         | 
| 9 7 | 
             
                    end
         | 
| 8 | 
            +
                  end
         | 
| 10 9 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 10 | 
            +
                  def succeed(*a)
         | 
| 11 | 
            +
                    @body_callback.call("0\r\n\r\n")
         | 
| 12 | 
            +
                    super
         | 
| 13 | 
            +
                  end
         | 
| 15 14 |  | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
                    end
         | 
| 15 | 
            +
                  def chunk(fragment)
         | 
| 16 | 
            +
                    "#{fragment.size.to_s(16)}\r\n#{fragment}\r\n"
         | 
| 19 17 | 
             
                  end
         | 
| 20 18 | 
             
                end
         | 
| 21 19 | 
             
              end
         | 
| @@ -1,20 +1,17 @@ | |
| 1 | 
            +
            module HTTTee
         | 
| 2 | 
            +
              module Server
         | 
| 3 | 
            +
                class AsyncFixer
         | 
| 4 | 
            +
                  def initialize(app)
         | 
| 5 | 
            +
                    @app = app
         | 
| 6 | 
            +
                  end
         | 
| 1 7 |  | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
                module Server
         | 
| 5 | 
            -
                  class AsyncFixer
         | 
| 6 | 
            -
                    def initialize(app)
         | 
| 7 | 
            -
                      @app = app
         | 
| 8 | 
            -
                    end
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                    def call(env)
         | 
| 11 | 
            -
                      tuple = @app.call(env)
         | 
| 8 | 
            +
                  def call(env)
         | 
| 9 | 
            +
                    tuple = @app.call(env)
         | 
| 12 10 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                      end
         | 
| 11 | 
            +
                    if tuple.first == -1
         | 
| 12 | 
            +
                      Thin::Connection::AsyncResponse
         | 
| 13 | 
            +
                    else
         | 
| 14 | 
            +
                      tuple
         | 
| 18 15 | 
             
                    end
         | 
| 19 16 | 
             
                  end
         | 
| 20 17 | 
             
                end
         | 
| @@ -1,59 +1,58 @@ | |
| 1 | 
            -
            module  | 
| 2 | 
            -
              module  | 
| 3 | 
            -
                 | 
| 4 | 
            -
                   | 
| 5 | 
            -
                     | 
| 6 | 
            -
             | 
| 7 | 
            -
                    end
         | 
| 1 | 
            +
            module HTTTee
         | 
| 2 | 
            +
              module Server
         | 
| 3 | 
            +
                class Dechunker
         | 
| 4 | 
            +
                  def initialize(app)
         | 
| 5 | 
            +
                    @app = app
         | 
| 6 | 
            +
                  end
         | 
| 8 7 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 8 | 
            +
                  def call(env)
         | 
| 9 | 
            +
                    env['rack.input'] = ChunkedBody.new(env['rack.input']) if chunked?(env)
         | 
| 11 10 |  | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 11 | 
            +
                    @app.call(env)
         | 
| 12 | 
            +
                  end
         | 
| 14 13 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 14 | 
            +
                  def chunked?(env)
         | 
| 15 | 
            +
                    env['HTTP_TRANSFER_ENCODING'] == 'chunked'
         | 
| 16 | 
            +
                  end
         | 
| 18 17 |  | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 18 | 
            +
                  class ChunkedBody
         | 
| 19 | 
            +
                    extend Forwardable
         | 
| 21 20 |  | 
| 22 | 
            -
             | 
| 21 | 
            +
                    CRLF = "\r\n"
         | 
| 23 22 |  | 
| 24 | 
            -
             | 
| 23 | 
            +
                    attr_reader :input
         | 
| 25 24 |  | 
| 26 | 
            -
             | 
| 25 | 
            +
                    def_delegators :input, :callback, :errback
         | 
| 27 26 |  | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 27 | 
            +
                    def initialize(input)
         | 
| 28 | 
            +
                      @input, @buffer = input, ''
         | 
| 29 | 
            +
                    end
         | 
| 31 30 |  | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                        end
         | 
| 31 | 
            +
                    def each(&blk)
         | 
| 32 | 
            +
                      @input.each do |chunk|
         | 
| 33 | 
            +
                        dechunk(chunk, &blk)
         | 
| 36 34 | 
             
                      end
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def dechunk(chunk, &blk)
         | 
| 38 | 
            +
                      @buffer << chunk
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      loop do
         | 
| 41 | 
            +
                        return unless @buffer[CRLF]
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                        string_length, remainder = @buffer.split(CRLF, 2)
         | 
| 44 | 
            +
                        length = string_length.to_i(16)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                        if length == 0
         | 
| 47 | 
            +
                          @buffer = ''
         | 
| 48 | 
            +
                          @input.succeed
         | 
| 49 | 
            +
                          return
         | 
| 50 | 
            +
                        elsif remainder.size >= length + 2 # length + CRLF
         | 
| 51 | 
            +
                          data, @buffer = remainder[0...length], remainder[(length+2)..-1]
         | 
| 37 52 |  | 
| 38 | 
            -
             | 
| 39 | 
            -
                         | 
| 40 | 
            -
             | 
| 41 | 
            -
                        loop do
         | 
| 42 | 
            -
                          return unless @buffer[CRLF]
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                          string_length, remainder = @buffer.split(CRLF, 2)
         | 
| 45 | 
            -
                          length = string_length.to_i(16)
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                          if length == 0
         | 
| 48 | 
            -
                            @buffer = ''
         | 
| 49 | 
            -
                            @input.succeed
         | 
| 50 | 
            -
                            return
         | 
| 51 | 
            -
                          elsif remainder.size >= length + 2 # length + CRLF
         | 
| 52 | 
            -
                            data, @buffer = remainder.split(CRLF, 2)
         | 
| 53 | 
            -
                            blk.call(data)
         | 
| 54 | 
            -
                          else
         | 
| 55 | 
            -
                            return
         | 
| 56 | 
            -
                          end
         | 
| 53 | 
            +
                          blk.call(data)
         | 
| 54 | 
            +
                        else
         | 
| 55 | 
            +
                          return
         | 
| 57 56 | 
             
                        end
         | 
| 58 57 | 
             
                      end
         | 
| 59 58 | 
             
                    end
         | 
    
        data/lib/htttee/server/mock.rb
    CHANGED
    
    | @@ -1,66 +1,64 @@ | |
| 1 1 | 
             
            require 'rack/mux'
         | 
| 2 2 |  | 
| 3 | 
            -
            module  | 
| 4 | 
            -
              module  | 
| 5 | 
            -
                module  | 
| 6 | 
            -
                  module Mock
         | 
| 3 | 
            +
            module HTTTee
         | 
| 4 | 
            +
              module Server
         | 
| 5 | 
            +
                module Mock
         | 
| 7 6 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 7 | 
            +
                  def self.boot_forking_server
         | 
| 8 | 
            +
                    o,i = IO.pipe
         | 
| 10 9 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 10 | 
            +
                    if pid = fork
         | 
| 11 | 
            +
                      at_exit { Process.kill(:SIGTERM, pid) }
         | 
| 13 12 |  | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
                      end
         | 
| 13 | 
            +
                      i.close
         | 
| 14 | 
            +
                      URI.parse(o.read)
         | 
| 15 | 
            +
                    else
         | 
| 16 | 
            +
                      o.close
         | 
| 17 | 
            +
                      process_child(i)
         | 
| 20 18 | 
             
                    end
         | 
| 19 | 
            +
                  end
         | 
| 21 20 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                        uri = client.get("/mux-uri").body
         | 
| 27 | 
            -
                        i << uri
         | 
| 28 | 
            -
                        i.close
         | 
| 29 | 
            -
                      end
         | 
| 21 | 
            +
                  def self.process_child(i)
         | 
| 22 | 
            +
                    EM.run do
         | 
| 23 | 
            +
                      client = Rack::Client.new { run HTTTee::Server.mock_app }
         | 
| 30 24 |  | 
| 31 | 
            -
                       | 
| 25 | 
            +
                      uri = client.get("/mux-uri").body
         | 
| 26 | 
            +
                      i << uri
         | 
| 27 | 
            +
                      i.close
         | 
| 32 28 | 
             
                    end
         | 
| 33 29 |  | 
| 34 | 
            -
                     | 
| 35 | 
            -
             | 
| 36 | 
            -
                        @app = Rack::Mux.new(async_safe(app), thin_options)
         | 
| 37 | 
            -
                      end
         | 
| 30 | 
            +
                    exit
         | 
| 31 | 
            +
                  end
         | 
| 38 32 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
                       | 
| 33 | 
            +
                  class ThinMuxer
         | 
| 34 | 
            +
                    def initialize(app)
         | 
| 35 | 
            +
                      @app = Rack::Mux.new(async_safe(app), thin_options)
         | 
| 36 | 
            +
                    end
         | 
| 42 37 |  | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 38 | 
            +
                    def call(env)
         | 
| 39 | 
            +
                      @app.call(env)
         | 
| 40 | 
            +
                    end
         | 
| 46 41 |  | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 42 | 
            +
                    def async_safe(app)
         | 
| 43 | 
            +
                      AsyncFixer.new(app)
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    def thin_options
         | 
| 47 | 
            +
                      { :server => Thin, :environment => 'none' }
         | 
| 50 48 | 
             
                    end
         | 
| 49 | 
            +
                  end
         | 
| 51 50 |  | 
| 52 | 
            -
             | 
| 51 | 
            +
                  class EchoUri
         | 
| 53 52 |  | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 53 | 
            +
                    def initialize(app)
         | 
| 54 | 
            +
                      @app = app
         | 
| 55 | 
            +
                    end
         | 
| 57 56 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
                        end
         | 
| 57 | 
            +
                    def call(env)
         | 
| 58 | 
            +
                      if env['PATH_INFO'] == '/mux-uri'
         | 
| 59 | 
            +
                        [200, {'Content-Type' => 'text/plain'}, [env['X-Mux-Uri']]]
         | 
| 60 | 
            +
                      else
         | 
| 61 | 
            +
                        @app.call(env)
         | 
| 64 62 | 
             
                      end
         | 
| 65 63 | 
             
                    end
         | 
| 66 64 | 
             
                  end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            module HTTTee
         | 
| 2 | 
            +
              module Server
         | 
| 3 | 
            +
                module SSE
         | 
| 4 | 
            +
                  class Body
         | 
| 5 | 
            +
                    include EventMachine::Deferrable
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    MESSAGE = 'message'
         | 
| 8 | 
            +
                    CTRL    = 'ctrl'
         | 
| 9 | 
            +
                    EOF     = 'eof'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    CTRL_TABLE = {
         | 
| 12 | 
            +
                      "\n"    => 'newline',
         | 
| 13 | 
            +
                      "\r"    => 'carriage-return',
         | 
| 14 | 
            +
                      "\r \n" => 'crlf',
         | 
| 15 | 
            +
                    }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def initialize(body)
         | 
| 18 | 
            +
                      @body = body
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def call(chunks)
         | 
| 22 | 
            +
                      chunks.each do |chunk|
         | 
| 23 | 
            +
                        send_chunk(chunk)
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def send_chunk(chunk)
         | 
| 28 | 
            +
                      data, ctrl, remaining = chunk.partition(%r{\r \n|\n|\r})
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                      send_data(data)             unless data.empty?
         | 
| 31 | 
            +
                      send_ctrl(CTRL_TABLE[ctrl]) unless ctrl.empty?
         | 
| 32 | 
            +
                      send_chunk(remaining)       unless remaining.empty?
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def send_data(data)
         | 
| 36 | 
            +
                      send_event(MESSAGE)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      send_message(data)
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def send_ctrl(type)
         | 
| 42 | 
            +
                      send_event(CTRL)
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      send_message(type)
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def send_message(data)
         | 
| 48 | 
            +
                      send "data", data, "\n\n"
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def send_event(event)
         | 
| 52 | 
            +
                      send "event", event, "\n"
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def send(type, data, term)
         | 
| 56 | 
            +
                      @body.call(["#{type}: #{data}#{term}"])
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def succeed
         | 
| 60 | 
            +
                      send_ctrl(EOF)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                      @body.succeed
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    def each(&blk)
         | 
| 66 | 
            +
                      @body.each(&blk)
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
            end
         | 
| @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            module HTTTee
         | 
| 2 | 
            +
              module Server
         | 
| 3 | 
            +
                module SSE
         | 
| 4 | 
            +
                  class Middleware
         | 
| 5 | 
            +
                    def initialize(app)
         | 
| 6 | 
            +
                      @app = app
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def call(env)
         | 
| 10 | 
            +
                      if sse_request?(env)
         | 
| 11 | 
            +
                        wrap(env)
         | 
| 12 | 
            +
                      elsif sse_supported?(env)
         | 
| 13 | 
            +
                        return sse_shell
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      @app.call(env)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def wrap(env)
         | 
| 20 | 
            +
                      env['rack.response_body'] = SSE::Body.new(env['rack.response_body'])
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      cb = env['async.callback']
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      env['async.callback'] = lambda do |(status, headers, body)|
         | 
| 25 | 
            +
                        cb.call([status, headers.merge('Content-Type' => 'text/event-stream', 'Connection' => 'Keep-Alive'), body])
         | 
| 26 | 
            +
                      end
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def sse_request?(env)
         | 
| 30 | 
            +
                      env['HTTP_ACCEPT'] == 'text/event-stream'
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def sse_supported?(env)
         | 
| 34 | 
            +
                      env['HTTP_USER_AGENT'].to_s =~ /WebKit/ &&
         | 
| 35 | 
            +
                        env['HTTP_ACCEPT'] =~ %r{text/html}
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def sse_shell
         | 
| 39 | 
            +
                      [200, {'Content-Type' => 'text/html'}, [<<-HTML]]
         | 
| 40 | 
            +
            <!DOCTYPE html>
         | 
| 41 | 
            +
            <html>
         | 
| 42 | 
            +
            <head>
         | 
| 43 | 
            +
              <meta charset="utf-8" />
         | 
| 44 | 
            +
              <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
         | 
| 45 | 
            +
            </head>
         | 
| 46 | 
            +
            <body><pre><code></code></pre>
         | 
| 47 | 
            +
              <script>
         | 
| 48 | 
            +
                var source = new EventSource(window.location.pathname);
         | 
| 49 | 
            +
                var body = $("html,body");
         | 
| 50 | 
            +
                var cursor = $("pre code")[0];
         | 
| 51 | 
            +
                var resetLine = false;
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                function append(data) {
         | 
| 54 | 
            +
                  if(resetLine == true) {
         | 
| 55 | 
            +
                    cursor.innerHTML = data;
         | 
| 56 | 
            +
                  } else {
         | 
| 57 | 
            +
                    cursor.innerHTML += data;
         | 
| 58 | 
            +
                  }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  resetLine = false;
         | 
| 61 | 
            +
                }
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                source.onmessage = function(e) {
         | 
| 64 | 
            +
                  append(e.data);
         | 
| 65 | 
            +
                };
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                source.onerror = function(e) {
         | 
| 68 | 
            +
                  console.log(e);
         | 
| 69 | 
            +
                };
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                source.addEventListener('ctrl', function(e) {
         | 
| 72 | 
            +
                  if(e.data == 'newline') {
         | 
| 73 | 
            +
                    append("\\n");
         | 
| 74 | 
            +
                    cursor = $("<code></code>").insertAfter(cursor)[0];
         | 
| 75 | 
            +
                  } else if(e.data == 'crlf' || e.data == 'carriage-return') {
         | 
| 76 | 
            +
                    resetLine = true;
         | 
| 77 | 
            +
                  } else if(e.data == 'eof') {
         | 
| 78 | 
            +
                    source.close();
         | 
| 79 | 
            +
                  } else {
         | 
| 80 | 
            +
                    console.log(e);
         | 
| 81 | 
            +
                  }
         | 
| 82 | 
            +
                }, false);
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                window.setInterval(function(){
         | 
| 85 | 
            +
                  window.scrollTo(0, document.body.scrollHeight);
         | 
| 86 | 
            +
                }, 100);
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              </script>
         | 
| 89 | 
            +
            </body>
         | 
| 90 | 
            +
            </html>
         | 
| 91 | 
            +
                      HTML
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         |