Imported Upstream version 0.5.0

parents
service-name: travis-pro
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
.rvmrc
--color
--format documentation
--backtrace
--warnings
rvm:
- 1.8.7
- 1.9.3
- 2.0.0
- ree
- ruby-head
- jruby-18mode
- jruby-19mode
- jruby-head
- rbx-18mode
- rbx-19mode
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
0.5.0
-----
* Add query string support
* New response delegator allows HTTP.get(uri).response
* HTTP::Chainable#stream provides a shorter alias for
with_response(:object)
* Better string inspect for HTTP::Response
* Curb compatibility layer removed
0.4.0
-----
* Fix bug accessing https URLs
* Fix several instances of broken redirect handling
* Add default user agent
* Many additional minor bugfixes
0.3.0
-----
* New implementation based on tmm1's http_parser.rb instead of Net::HTTP
* Support for following redirects
* Support for request body through {:body => ...} option
* HTTP#with_response (through Chainable)
0.2.0
-----
* Request and response objects
* Callback system
* Internal refactoring ensuring true chainability
* Use the certified gem to ensure SSL certificate verification
0.1.0
-----
* Testing against WEBrick
* Curb compatibility (require 'http/compat/curb')
0.0.1
-----
* Initial half-baked release
0.0.0
-----
* Vapoware release to claim the "http" gem name >:D
source 'http://rubygems.org'
gem 'jruby-openssl' if defined? JRUBY_VERSION
gem 'coveralls', :require => false
# Specify your gem's dependencies in http.gemspec
gemspec
group :development do
gem 'guard-rspec'
gem 'celluloid-io' if RUBY_VERSION >= "1.9.3"
end
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard :rspec do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
end
Copyright (c) 2011 Tony Arcieri, Carl Lerche
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.
The HTTP Gem*
==============
[![Gem Version](https://badge.fury.io/rb/http.png)](http://rubygems.org/gems/http)
[![Build Status](https://secure.travis-ci.org/tarcieri/http.png?branch=master)](http://travis-ci.org/tarcieri/http)
[![Code Climate](https://codeclimate.com/github/tarcieri/http.png)](https://codeclimate.com/github/tarcieri/http)
[![Coverage Status](https://coveralls.io/repos/tarcieri/http/badge.png?branch=master)](https://coveralls.io/r/tarcieri/http)
*NOTE: this gem has the worst name in the history of SEO. But perhaps we can fix
that if we all refer to it as "The HTTP Gem". Entering that phrase into Google
actually pulls it up as #4 for me!
The HTTP Gem is an easy-to-use client library for making requests from Ruby. It uses
a simple method chaining system for building requests, similar to libraries
like JQuery or Python's [Requests](http://docs.python-requests.org/en/latest/).
Installation
------------
Add this line to your application's Gemfile:
gem 'http'
And then execute:
$ bundle
Or install it yourself as:
$ gem install http
Inside of your Ruby program do:
require 'http'
...to pull it in as a dependency.
Making Requests
---------------
Let's start with getting things:
```ruby
>> HTTP.get("http://www.google.com")
=> "<html><head><meta http-equiv=\"content-type\" content=..."
```
That's it! The result is the response body as a string. To obtain an HTTP::Response object
instead of the response body, chain `.response` on the end of the request:
```ruby
>> HTTP.get("http://www.google.com").response
=> #<HTTP/1.0 200 OK @headers={"Content-Type"=>"text/html; charset=UTF-8", "Date"=>"Fri, ...>
```
Making POST requests is simple too. Want to POST a form?
```ruby
HTTP.post "http://example.com/resource", :form => {:foo => "42"}
```
Making GET requests with query string parameters is as simple.
```ruby
HTTP.get "http://example.com/resource", :params => {:foo => "bar"}
```
Want to POST with a specific body, JSON for instance?
```ruby
HTTP.post "http://example.com/resource", :body => JSON.dump(:foo => '42')
```
Or have it serialize JSON for you:
```ruby
HTTP.post "http://example.com/resource", :json => {:foo => '42'}
```
It's easy!
Adding Headers
--------------
The HTTP library uses the concept of chaining to simplify requests. Let's say
you want to get the latest commit of this library from Github in JSON format.
One way we could do this is by tacking a filename on the end of the URL:
```ruby
HTTP.get "https://github.com/tarcieri/http/commit/HEAD.json"
```
The Github API happens to support this approach, but really this is a bit of a
hack that makes it easy for people typing URLs into the address bars of
browsers to perform the act of content negotiation. Since we have access to
the full, raw power of HTTP, we can perform content negotiation the way HTTP
intends us to, by using the Accept header:
```ruby
HTTP.with_headers(:accept => 'application/json').
get("https://github.com/tarcieri/http/commit/HEAD")
```
This requests JSON from Github. Github is smart enough to understand our
request and returns a response with Content-Type: application/json. If you
happen to have a library loaded which defines the JSON constant and implements
JSON.parse, the HTTP library will attempt to parse the JSON response.
Shorter aliases exists for HTTP.with_headers:
```ruby
HTTP.with(:accept => 'application/json').
get("https://github.com/tarcieri/http/commit/HEAD")
HTTP[:accept => 'application/json'].
get("https://github.com/tarcieri/http/commit/HEAD")
```
Content Negotiation
-------------------
As important a concept as content negotiation is HTTP, it sure should be easy,
right? But usually it's not, and so we end up adding ".json" onto the ends of
our URLs because the existing mechanisms make it too hard. It should be easy:
```ruby
HTTP.accept(:json).get("https://github.com/tarcieri/http/commit/HEAD")
```
This adds the appropriate Accept header for retrieving a JSON response for the
given resource.
Contributing to HTTP
--------------------
* Fork HTTP on github
* Make your changes and send me a pull request
* If I like them I'll merge them
* If I've accepted a patch, feel free to ask for a commit bit!
Copyright
---------
Copyright (c) 2013 Tony Arcieri. See LICENSE.txt for further details.
#!/usr/bin/env rake
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
task :default => :spec
#!/usr/bin/env ruby
#
# Example of using the HTTP Gem with Celluloid::IO
# Make sure to 'gem install celluloid-io' before running
#
# Run as: bundle exec examples/celluloid.rb
#
require 'celluloid/io'
require 'http'
puts HTTP.get("https://www.google.com/", :socket_class => Celluloid::IO::TCPSocket, :ssl_socket_class => Celluloid::IO::SSLSocket)
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/http/version', __FILE__)
Gem::Specification.new do |gem|
gem.authors = ["Tony Arcieri"]
gem.email = ["tony.arcieri@gmail.com"]
gem.description = "HTTP so awesome it will lure Catherine Zeta Jones into your unicorn petting zoo"
gem.summary = "HTTP should be easy"
gem.homepage = "https://github.com/tarcieri/http"
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 = "http"
gem.require_paths = ["lib"]
gem.version = HTTP::VERSION
gem.add_runtime_dependency 'http_parser.rb'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'rspec', '>= 2.11'
gem.add_development_dependency 'json'
end
require 'http/parser'
require 'http/chainable'
require 'http/client'
require 'http/mime_type'
require 'http/options'
require 'http/request'
require 'http/request_stream'
require 'http/response'
require 'http/response_parser'
require 'http/uri_backport' if RUBY_VERSION < "1.9.0"
# HTTP should be easy
module HTTP
extend Chainable
class << self
# HTTP[:accept => 'text/html'].get(...)
alias_method :[], :with_headers
end
end
Http = HTTP unless defined?(Http)
module HTTP
module Chainable
# Request a get sans response body
def head(uri, options = {})
request :head, uri, options
end
# Get a resource
def get(uri, options = {})
request :get, uri, options
end
# Post to a resource
def post(uri, options = {})
request :post, uri, options
end
# Put to a resource
def put(uri, options = {})
request :put, uri, options
end
# Delete a resource
def delete(uri, options = {})
request :delete, uri, options
end
# Echo the request back to the client
def trace(uri, options = {})
request :trace, uri, options
end
# Return the methods supported on the given URI
def options(uri, options = {})
request :options, uri, options
end
# Convert to a transparent TCP/IP tunnel
def connect(uri, options = {})
request :connect, uri, options
end
# Apply partial modifications to a resource
def patch(uri, options = {})
request :patch, uri, options
end
# Make an HTTP request with the given verb
def request(verb, uri, options = {})
branch(options).request verb, uri
end
# Make a request invoking the given event callbacks
def on(event, &block)
branch default_options.with_callback(event, block)
end
# Make a request through an HTTP proxy
def via(*proxy)
proxy_hash = {}
proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a? String
proxy_hash[:proxy_port] = proxy[1] if proxy[1].is_a? Integer
proxy_hash[:proxy_username]= proxy[2] if proxy[2].is_a? String
proxy_hash[:proxy_password]= proxy[3] if proxy[3].is_a? String
if proxy_hash.keys.size >=2
branch default_options.with_proxy(proxy_hash)
else
raise ArgumentError, "invalid HTTP proxy: #{proxy_hash}"
end
end
alias_method :through, :via
# Specify the kind of response to return (:auto, :object, :body, :parsed_body)
def with_response(response_type)
branch default_options.with_response(response_type)
end
# Alias for with_response(:object)
def stream; with_response(:object); end
def with_follow(follow)
branch default_options.with_follow(follow)
end
# Make a request with the given headers
def with_headers(headers)
branch default_options.with_headers(headers)
end
alias_method :with, :with_headers
# Accept the given MIME type(s)
def accept(type)
if type.is_a? String
with :accept => type
else
mime_type = HTTP::MimeType[type]
raise ArgumentError, "unknown MIME type: #{type}" unless mime_type
with :accept => mime_type.type
end
end
def default_options
@default_options ||= HTTP::Options.new
end
def default_options=(opts)
@default_options = HTTP::Options.new(opts)
end
def default_headers
default_options.headers
end
def default_headers=(headers)
@default_options = default_options.dup do |opts|
opts.headers = headers
end
end
def default_callbacks
default_options.callbacks
end
def default_callbacks=(callbacks)
@default_options = default_options.dup do |opts|
opts.callbacks = callbacks
end
end
private
def branch(options)
HTTP::Client.new(options)
end
end
end
require 'http/options'
require 'uri'
module HTTP
# Clients make requests and receive responses
class Client
include Chainable
BUFFER_SIZE = 4096 # Input buffer size
attr_reader :default_options
def initialize(default_options = {})
@default_options = HTTP::Options.new(default_options)
end
def body(opts, headers)
if opts.body
body = opts.body
elsif opts.form
headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
body = URI.encode_www_form(opts.form)
end
end
# Make an HTTP request
def request(method, uri, options = {})
opts = @default_options.merge(options)
host = URI.parse(uri).host
opts.headers["Host"] = host
headers = opts.headers
proxy = opts.proxy
method_body = body(opts, headers)
if opts.params
uri="#{uri}?#{URI.encode_www_form(opts.params)}"
end
request = HTTP::Request.new method, uri, headers, proxy, method_body
if opts.follow
code = 302
while code == 302 or code == 301
# if the uri isn't fully formed complete it
if not uri.match(/\./)
uri = "#{method}://#{host}#{uri}"
end
host = URI.parse(uri).host
opts.headers["Host"] = host
method_body = body(opts, headers)
request = HTTP::Request.new method, uri, headers, proxy, method_body
response = perform request, opts
code = response.code
uri = response.headers["Location"]
end
end
opts.callbacks[:request].each { |c| c.call(request) }
response = perform request, opts
opts.callbacks[:response].each { |c| c.call(response) }
format_response method, response, opts.response
end
def perform(request, options)
parser = HTTP::Response::Parser.new
uri = request.uri
socket = options[:socket_class].open(uri.host, uri.port) # TODO: proxy support
if uri.is_a?(URI::HTTPS)
if options[:ssl_context] == nil
context = OpenSSL::SSL::SSLContext.new
else
context = options[:ssl_context]
end
socket = options[:ssl_socket_class].new(socket, context)
socket.connect
end
request.stream socket
begin
parser << socket.readpartial(BUFFER_SIZE) until parser.headers
rescue IOError, Errno::ECONNRESET, Errno::EPIPE => ex
raise IOError, "problem making HTTP request: #{ex}"
end
response = HTTP::Response.new(parser.status_code, parser.http_version, parser.headers) do
if !parser.finished? || (@body_remaining && @body_remaining > 0)
chunk = parser.chunk || begin
parser << socket.readpartial(BUFFER_SIZE)
parser.chunk || ""
end
@body_remaining -= chunk.length if @body_remaining
@body_remaining = nil if @body_remaining && @body_remaining < 1
chunk
end
end
@body_remaining = Integer(response['Content-Length']) if response['Content-Length']
response
end
def format_response(method, response, option)
case option
when :auto, NilClass
if method == :head
response
else
HTTP::Response::BodyDelegator.new(response, response.parse_body)
end
when :object
response
when :parsed_body
HTTP::Response::BodyDelegator.new(response, response.parse_body)
when :body
HTTP::Response::BodyDelegator.new(response)
else raise ArgumentError, "invalid response type: #{option}"
end
end
end
end
module HTTP
module Header
# Matches HTTP header names when in "Canonical-Http-Format"
CANONICAL_HEADER = /^[A-Z][a-z]*(-[A-Z][a-z]*)*$/
# Transform to canonical HTTP header capitalization
def canonicalize_header(header)
header.to_s.split(/[\-_]/).map(&:capitalize).join('-')
end
end
end
module HTTP
# Yes, HTTP bundles its own MIME type library. Maybe it should be spun off
# as a separate gem or something.
class MimeType
@mime_types, @shortcuts = {}, {}
class << self
def register(obj)
@mime_types[obj.type] = obj
@shortcuts[obj.shortcut] = obj if obj.shortcut
end
def [](type)
if type.is_a? Symbol
@shortcuts[type]
else
@mime_types[type]
end
end
end
attr_reader :type, :shortcut
def initialize(type, shortcut = nil)
@type, @shortcut = type, shortcut
@parse_with = @emit_with = nil
self.class.register self
end
# Define
def parse_with(&block)
@parse_with = block
end
def emit_with(&block)
@emit_with = block
end
def parse(obj)
@parse_with ? @parse_with[obj] : obj
end
def emit(obj)
@emit_with ? @emit_with[obj] : obj
end
end
end
# MIME type registry
require 'http/mime_types/json'
json = HTTP::MimeType.new 'application/json', :json
json.parse_with do |obj|
if defined?(JSON) and JSON.respond_to? :parse
JSON.parse(obj)
else
obj
end
end
json.emit_with do |obj|
if obj.is_a? String
obj
elsif obj.respond_to? :to_json
obj.to_json
else
obj
end
end
require 'http/version'
require 'openssl'
require 'socket'
module HTTP
class Options
# How to format the response [:object, :body, :parse_body]
attr_accessor :response
# HTTP headers to include in the request
attr_accessor :headers
# Query string params to add to the url
attr_accessor :params
# Form data to embed in the request
attr_accessor :form
# Explicit request body of the request
attr_accessor :body
# HTTP proxy to route request
attr_accessor :proxy
# Before callbacks
attr_accessor :callbacks
# Socket classes
attr_accessor :socket_class, :ssl_socket_class
# SSL context
attr_accessor :ssl_context
# Follow redirects
attr_accessor :follow
protected :response=, :headers=, :proxy=, :params=, :form=, :callbacks=, :follow=
@default_socket_class = TCPSocket
@default_ssl_socket_class = OpenSSL::SSL::SSLSocket
class << self
attr_accessor :default_socket_class, :default_ssl_socket_class
def new(options = {})
return options if options.is_a?(self)
super
end
end
def initialize(options = {})
@response = options[:response] || :auto
@headers = options[:headers] || {}