#-- # # Author:: Francis Cianfrocca (gmail: blackhedd) # Homepage:: http://rubyeventmachine.com # Date:: 15 November 2006 # # See EventMachine and EventMachine::Connection for documentation and # usage examples. # #---------------------------------------------------------------------------- # # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. # Gmail: blackhedd # # This program is free software; you can redistribute it and/or modify # it under the terms of either: 1) the GNU General Public License # as published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version; or 2) Ruby's License. # # See the file COPYING for complete licensing information. # #--------------------------------------------------------------------------- # # module EventMachine module Protocols # In the grand, time-honored tradition of re-inventing the wheel, we offer # here YET ANOTHER protocol that handles line-oriented data with interspersed # binary text. This one trades away some of the performance optimizations of # EventMachine::Protocols::LineAndTextProtocol in order to get better correctness # with regard to binary text blocks that can switch back to line mode. It also # permits the line-delimiter to change in midstream. # This was originally written to support Stomp. module LineText2 # TODO! We're not enforcing the limits on header lengths and text-lengths. # When we get around to that, call #receive_error if the user defined it, otherwise # throw exceptions. MaxBinaryLength = 32*1024*1024 #-- # Will loop internally until there's no data left to read. # That way the user-defined handlers we call can modify the # handling characteristics on a per-token basis. # def receive_data data return unless (data and data.length > 0) # Do this stuff in lieu of a constructor. @lt2_mode ||= :lines @lt2_delimiter ||= "\n" @lt2_linebuffer ||= [] remaining_data = data while remaining_data.length > 0 if @lt2_mode == :lines delimiter_string = case @lt2_delimiter when Regexp remaining_data.slice(@lt2_delimiter) else @lt2_delimiter end ix = remaining_data.index(delimiter_string) if delimiter_string if ix @lt2_linebuffer << remaining_data[0...ix] ln = @lt2_linebuffer.join @lt2_linebuffer.clear if @lt2_delimiter == "\n" ln.chomp! end receive_line ln remaining_data = remaining_data[(ix+delimiter_string.length)..-1] else @lt2_linebuffer << remaining_data remaining_data = "" end elsif @lt2_mode == :text if @lt2_textsize needed = @lt2_textsize - @lt2_textpos will_take = if remaining_data.length > needed needed else remaining_data.length end @lt2_textbuffer << remaining_data[0...will_take] tail = remaining_data[will_take..-1] @lt2_textpos += will_take if @lt2_textpos >= @lt2_textsize # Reset line mode (the default behavior) BEFORE calling the # receive_binary_data. This makes it possible for user code # to call set_text_mode, enabling chains of text blocks # (which can possibly be of different sizes). set_line_mode receive_binary_data @lt2_textbuffer.join receive_end_of_binary_data end remaining_data = tail else receive_binary_data remaining_data remaining_data = "" end end end end # The line delimiter may be a regular expression or a string. Anything # passed to set_delimiter other than a regular expression will be # converted to a string. def set_delimiter delim @lt2_delimiter = case delim when Regexp delim else delim.to_s end end # Called internally but also exposed to user code, for the case in which # processing of binary data creates a need to transition back to line mode. # We support an optional parameter to "throw back" some data, which might # be an umprocessed chunk of the transmitted binary data, or something else # entirely. def set_line_mode data="" @lt2_mode = :lines (@lt2_linebuffer ||= []).clear receive_data data.to_s end def set_text_mode size=nil if size == 0 set_line_mode else @lt2_mode = :text (@lt2_textbuffer ||= []).clear @lt2_textsize = size # which can be nil, signifying no limit @lt2_textpos = 0 end end # Alias for #set_text_mode, added for back-compatibility with LineAndTextProtocol. def set_binary_mode size=nil set_text_mode size end # In case of a dropped connection, we'll send a partial buffer to user code # when in sized text mode. User overrides of #receive_binary_data need to # be aware that they may get a short buffer. def unbind @lt2_mode ||= nil if @lt2_mode == :text and @lt2_textpos > 0 receive_binary_data @lt2_textbuffer.join end end # Stub. Should be subclassed by user code. def receive_line ln # no-op end # Stub. Should be subclassed by user code. def receive_binary_data data # no-op end # Stub. Should be subclassed by user code. # This is called when transitioning internally from text mode # back to line mode. Useful when client code doesn't want # to keep track of how much data it's received. def receive_end_of_binary_data # no-op end end end end