Skip to content
Browse files

First commit

  • Loading branch information...
0 parents commit f0b21c38b5c9134f166109cab12f33d34344e4b1 @jsl committed May 23, 2009
Showing with 262 additions and 0 deletions.
  1. +20 −0 LICENSE
  2. +50 −0 README.rdoc
  3. +25 −0 Rakefile
  4. +35 −0 http_headers.gemspec
  5. +41 −0 lib/http_headers.rb
  6. +1 −0 spec/fixtures/headers.txt
  7. +11 −0 spec/fixtures/headers2.txt
  8. +70 −0 spec/http_headers_spec.rb
  9. +9 −0 spec/spec_helper.rb
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Justin S. Leitgeb
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
50 README.rdoc
@@ -0,0 +1,50 @@
+= HttpHeaders
+
+HttpHeaders is a library for parsing HTTP-compliant header strings. It was designed to process the
+results from the header_str method on Curl::Easy objects in Ruby, but should work for other HTTP-compliant
+string formats.
+
+It correctly parses the content type, response code and most other key-value fields from the HTTP response header,
+including keys that may appear multiple times in the header such as Set-Cookie.
+
+== Installation
+
+Set up your machine to pull gems from gems.github.com if you haven't already. Then:
+
+ sudo gem install jsl-http_headers
+
+== Quick Start
+
+ h = HttpHeaders.new(header_str)
+ h.content_type
+ h.etag
+ h.set_cookie # Will return an Array of String values if more than one match is found
+
+== Implementation
+
+HttpHeaders uses ruby's +method_missing+ in order to build queries for the given parameter dynamically, so
+it doesn't need to parse values for every field at the time that the HttpHeader object is instantiated with
+a HTTP header string.
+
+== Possible Limitations
+
+Some attributes, such as Set-Cookie, may appear listed multiple times in the header. Where this occurs,
+we try to find all instances of the key and return an Array if multiple occurrences are found. Otherwise,
+nothing is done in the case where there are multiple values for a cookie given as values of a particular key -
+at this point the values won't be magically turned into an Array and it will be up to the user to program the
+desired behavior in a client library.
+
+== Feedback
+
+Please write the author if you have any questions or feedback about this library. Patches are welcome if you have ideas
+about how the behavior of HttpHeaders can be improved for particular cases. Please include specs using the associated
+test suite if you make improvements, and I would also be happy to receive more 'fixtures' for new failure cases where the
+library can be improved.
+
+== References
+
+[1] The HTTP Header Field Definitions - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+
+== Author
+
+Justin S. Leitgeb, <justin@phq.org>
25 Rakefile
@@ -0,0 +1,25 @@
+require 'rubygems'
+require 'spec'
+
+require 'rake'
+require 'spec/rake/spectask'
+require 'rake/rdoctask'
+
+desc 'Test the plugin.'
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ["--format", "specdoc", "--colour"]
+ t.libs << 'lib'
+ t.verbose = true
+end
+
+desc "Run all the tests"
+task :default => :spec
+
+desc 'Generate documentation for the hashback plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'HashBack'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
35 http_headers.gemspec
@@ -0,0 +1,35 @@
+Gem::Specification.new do |s|
+ s.name = "http_headers"
+ s.version = "0.0.1"
+ s.date = "2009-05-22"
+ s.summary = "Library for parsing the Curl header_str into attributes"
+ s.email = "justin@phq.org"
+ s.homepage = "http://github.com/jsl/hashback"
+ s.description = "Creates an object from the attributes in a header_str from Curl"
+ s.has_rdoc = true
+ s.authors = ["Justin Leitgeb"]
+ s.files = [
+ "Rakefile",
+ "http_headers.gemspec",
+ "LICENSE",
+ "README.rdoc",
+ "Rakefile",
+ "spec/http_headers_spec.rb",
+ "spec/fixtures/headers.txt",
+ "spec/fixtures/headers2.txt",
+ "lib/http_headers.rb"
+ ]
+ s.test_files = [
+ "spec/http_headers_spec.rb",
+ "spec/spec_helper.rb"
+ ]
+
+ s.extra_rdoc_files = [ "README.rdoc" ]
+
+ s.rdoc_options += [
+ '--title', 'HttpHeaders',
+ '--main', 'README.rdoc',
+ '--line-numbers',
+ '--inline-source'
+ ]
+end
41 lib/http_headers.rb
@@ -0,0 +1,41 @@
+# Parses a http header_str from Curl::Easy into component parts.
+class HttpHeaders
+
+ attr_reader :content
+
+ def initialize(str)
+ @content = str.split(/\\r\\n|\n|\r/)
+ end
+
+ def version
+ @content[0].match(/HTTP\/\d\.\d/)[0]
+ end
+
+ def response_code
+ @content[0].match(/HTTP\/\d\.\d (\d{3}.*)/)[1]
+ end
+
+ def method_missing(sym, *args)
+ detect_multi_value_keys(sym)
+ end
+
+ private
+
+ # Turns the given sym into a String after replacing the '_' character with the '-'
+ def method_sym_to_http_key(sym)
+ sym.to_s.gsub('_', '-')
+ end
+
+ def detect_multi_value_keys(tag)
+ tag = method_sym_to_http_key(tag)
+ regexp = /^#{tag}:/i
+ results = @content.select{|e| e =~ regexp }.map{|e| value_from(e)}
+ results.size <= 1 ? results.first : results
+ end
+
+ # Returns the key from this tag, which should be a string with key separated
+ # from val by ':'
+ def value_from(tag)
+ tag.split(/:\W+/)[1]
+ end
+end
1 spec/fixtures/headers.txt
@@ -0,0 +1 @@
+HTTP/1.1 200 OK\r\nServer: Sun-ONE-Web-Server/6.1\r\nDate: Fri, 22 May 2009 12:53:02 GMT\r\nContent-type: text/html\r\nSet-cookie: RMID=21167df34c454a16a02ee2e1; expires=Saturday, 22-May-2010 12:53:02 GMT; path=/; domain=.nytimes.com\r\nSet-cookie: adxcs=-; path=/; domain=.nytimes.com\r\nSet-cookie: adxcs=-; path=/; domain=.nytimes.com\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nCache-control: no-cache\r\nPragma: no-cache\r\nTransfer-encoding: chunked\r\n\r\n
11 spec/fixtures/headers2.txt
@@ -0,0 +1,11 @@
+HTTP/1.1 200 OK
+Last-Modified: Fri, 22 May 2009 15:35:08 GMT
+ETag: pnDSjJtGvlc2WrX6VND/w0qxEc8
+Content-Type: text/xml; charset=utf-8
+Date: Fri, 22 May 2009 18:18:08 GMT
+Expires: Fri, 22 May 2009 18:18:08 GMT
+Cache-Control: private, max-age=0
+X-Content-Type-Options: nosniff
+Server: GFE/2.0
+Transfer-Encoding: chunked
+
70 spec/http_headers_spec.rb
@@ -0,0 +1,70 @@
+require File.join(File.dirname(__FILE__), %w[spec_helper])
+
+describe HttpHeaders do
+ before do
+ @h = HttpHeaders.new(File.read(File.join(File.dirname(__FILE__), %w[fixtures headers.txt])))
+ @h2 = HttpHeaders.new(File.read(File.join(File.dirname(__FILE__), %w[fixtures headers2.txt])))
+ end
+
+ it "should have text/html as the content_type" do
+ @h.version.should == 'HTTP/1.1'
+ end
+
+ it "should have 200 OK as the response code" do
+ @h.response_code.should == '200 OK'
+ end
+
+ it "should have a nil etag for the first header file" do
+ @h.etag.should be_nil
+ end
+
+ it "should have an etag of pnDSjJtGvlc2WrX6VND/w0qxEc8" do
+ @h2.etag.should == 'pnDSjJtGvlc2WrX6VND/w0qxEc8'
+ end
+
+ it "should have Fri, 22 May 2009 18:18:08 GMT for date" do
+ @h2.date.should == 'Fri, 22 May 2009 18:18:08 GMT'
+ end
+
+ it "should have Fri, 22 May 2009 18:18:08 GMT for expires" do
+ @h2.expires.should == 'Fri, 22 May 2009 18:18:08 GMT'
+ end
+
+ it "should have private, max-age=0 for cache_control" do
+ @h2.cache_control.should == 'private, max-age=0'
+ end
+
+ it "should have Fri, 22 May 2009 15:35:08 GMT for last_modified" do
+ @h2.last_modified.should == 'Fri, 22 May 2009 15:35:08 GMT'
+ end
+
+ it "should have GFE/2.0 for server" do
+ @h2.server.should == 'GFE/2.0'
+ end
+
+ it "should have no-cache for pragma" do
+ @h.pragma.should == 'no-cache'
+ end
+
+ it "should have chunked for transfer_encoding" do
+ @h2.transfer_encoding.should == 'chunked'
+ end
+
+ describe "multi-valued keys like Set-cookie:" do
+ before do
+ @c = @h.set_cookie
+ end
+
+ it "should return an Array if multiple values are present" do
+ @c.should be_a(Array)
+ end
+
+ it "should have three cookies" do
+ @c.size.should == 3
+ end
+
+ it "should have adxcs=-; path=/; domain=.nytimes.com as value for one cookie" do
+ @c.include?("adxcs=-; path=/; domain=.nytimes.com").should be_true
+ end
+ end
+end
9 spec/spec_helper.rb
@@ -0,0 +1,9 @@
+require 'rubygems'
+require 'mocha'
+require 'spec'
+
+Spec::Runner.configure do |config|
+ config.mock_with(:mocha)
+end
+
+require File.join(File.dirname(__FILE__), %w[.. lib http_headers])

0 comments on commit f0b21c3

Please sign in to comment.
Something went wrong with that request. Please try again.