lib/rtsp/response.rb in rtsp-0.0.1.alpha vs lib/rtsp/response.rb in rtsp-0.1.0

- old
+ new

@@ -1,75 +1,135 @@ require 'rubygems' -require 'socket' require 'sdp' +require_relative 'error' module RTSP + + # Parses raw response data from the server/client and turns it into + # attr_readers. class Response + attr_reader :rtsp_version attr_reader :code attr_reader :message attr_reader :body - - def initialize(response) - response_array = response.split "\r\n\r\n" - if response_array.empty? - response_array = response.split "\n\n" + # @param [String] raw_response The raw response string returned from the + # server/client. + def initialize(raw_response) + if raw_response.nil? || raw_response.empty? + raise RTSP::Error, "#{self.class} received nil string--this shouldn't happen." end - head = response_array.first - body = response_array.last == head ? "" : response_array.last + @raw_response = raw_response + + head, body = split_head_and_body_from @raw_response parse_head(head) @body = parse_body(body) end - # Reads through each line of the RTSP response and creates a - # snake-case accessor with that value set. + # @return [String] The unparsed response as a String. + def to_s + @raw_response + end + + # Custom redefine to make sure all the dynamically created instance + # variables are displayed when this method is called. # - # @param [String] head + # @return [String] + def inspect + me = "#<#{self.class.name}:#{self.__id__} " + + self.instance_variables.each do |variable| + me << "#{variable}=#{instance_variable_get(variable).inspect}, " + end + + me.sub!(/, $/, "") + me << ">" + + me + end + + # Takes the raw response text and splits it into a 2-element Array, where 0 + # is the text containing the headers and 1 is the text containing the body. + # + # @param [String] raw_response + # @return [Array<String>] 2-element Array containing the head and body of + # the response. Body will be nil if there wasn't one in the response. + def split_head_and_body_from raw_response + head_and_body = raw_response.split("\r\n\r\n", 2) + head = head_and_body.first + body = head_and_body.last == head ? nil : head_and_body.last + + [head, body] + end + + # Pulls out the RTSP version, response code, and response message (AKA the + # status line info) into instance variables. + # + # @param [String] line The String containing the status line info. + def extract_status_line(line) + line =~ /RTSP\/(\d\.\d) (\d\d\d) ([^\r\n]+)/ + @rtsp_version = $1 + @code = $2.to_i + @message = $3 + + if @rtsp_version.nil? + raise RTSP::Error, "Status line corrupted: #{line}" + end + end + + # Reads through each header line of the RTSP response, extracts the response + # code, response message, response version, and creates a snake-case + # accessor with that value set. + # + # @param [String] head The section of headers from the response text. def parse_head head lines = head.split "\r\n" lines.each_with_index do |line, i| if i == 0 - line =~ /RTSP\/1.0 (\d\d\d) ([^\r\n]+)/ - @code = $1.to_i - @message = $2 + extract_status_line(line) next end if line.include? ": " - header_field = line.strip.split(": ") - header_name = header_field.first.downcase.gsub(/-/, "_") - create_reader(header_name, header_field.last) + header_and_value = line.strip.split(":", 2) + header_name = header_and_value.first.downcase.gsub(/-/, "_") + create_reader(header_name, header_and_value[1].strip) end end end + # Reads through each line of the RTSP response body and parses it if + # needed. Returns a SDP::Description if the Content-Type is + # 'application/sdp', otherwise returns the String that was passed in. + # + # @param [String] body + # @return [SDP::Description,String] def parse_body body - #response[:body] = read_nonblock(size).split("\r\n") unless @content_length == 0 if body =~ /^(\r\n|\n)/ body.gsub!(/^(\r\n|\n)/, '') end if @content_type == "application/sdp" SDP.parse body + else + body end end - # @param [Number] size - # @param [Hash] options - # @option options [Number] time Duration to read on the non-blocking socket. - def read_nonblock(size, options={}) - options[:time] ||= 1 - buffer = nil - timeout(options[:time]) { buffer = @socket.read_nonblock(size) } - - buffer - end - private + # Creates an attr_reader with the name given and sets it to the value that's + # given. + # + # @param [String] name + # @param [String] value def create_reader(name, value) + unless value.empty? + value = value =~ /^[0-9]*$/ ? value.to_i : value + end + instance_variable_set("@#{name}", value) self.instance_eval "def #{name}; @#{name}; end" end end end