lib/wrest/uri.rb
# frozen_string_literal: true
# Copyright 2009 Sidu Ponnappa
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
module Wrest # :nodoc:
# Wrest::Uri provides a simple api for
# REST calls. String#to_uri is a convenience
# method to build a Wrest::Uri from a string url.
# Note that a Wrest::Uri is immutable.
#
# Basic HTTP Authentication is supported.
# Example:
# "http://kaiwren:fupuppies@coathangers.com/portal/1".to_uri
# "http://coathangers.com/portal/1".to_uri(:username => 'kaiwren', :password => 'fupuppies')
#
# The second form is preferred as it can handle passwords with special characters like ^ and @
#
# You can find examples that use real APIs (like delicious) under the wrest/examples directory.
class Uri
attr_reader :uri, :username, :password, :uri_string, :uri_path, :query, :default_headers
# Valid tuples for the options are:
# :asynchronous_backend => Can currently be set to either Wrest::AsyncRequest::EventMachineBackend.new
# or Wrest::AsyncRequest::ThreadBackend.new. Easier to do using Uri#using_em and
# Uri#using_threads.
# :callback => Accepts a hash where the keys are response codes or ranges of response codes and
# the values are the corresponding blocks that will be invoked should the response
# code match the key.
# :default_headers => Accepts a hash containing a set of default request headers with which the headers
# passed to Uri#get, Uri#post etc. are merged. Incoming headers will override the
# defaults if there are any clashes. Use this to set cookies or use OAuth2 Authorize
# headers. When extending or cloning a Uri, passing in a new set of default_headers
# will result in the old set being overridden.
# :username, :password => HTTP authentication. Passing nil for either username or password will skip it.
# See Wrest::Native::Request for other available options and their default values.
def initialize(uri_string, options = {})
@options = options.clone
setup_uri_state!(uri_string)
setup_request_config!
end
# Builds a Wrest::UriTemplate by extending the current URI
# with the pattern passed to it.
def to_template(pattern)
template_pattern = URI.join(uri_string, pattern).to_s
UriTemplate.new(template_pattern, @options)
end
# Build a new Wrest::Uri by appending _path_ to
# the current uri. If the original Wrest::Uri
# has a username and password, that will be
# copied to the new Wrest::Uri as well.
#
# Example:
# uri = "https://localhost:3000/v1".to_uri
# uri['/oogas/1'].get
#
# To change the username and password on the new
# instance, simply pass them as an options map.
#
# Example:
# uri = "https://localhost:3000/v1".to_uri(:username => 'foo', :password => 'bar')
# uri['/oogas/1', {:username => 'meh', :password => 'baz'}].get
def [](path, options = nil)
Uri.new(uri + File.join(uri_path, path), options || @options)
end
# Clones a Uri, building a new instance with exactly the same uri string.
# You can however change the Uri options or add new ones.
def clone(opts = {})
merged_options = @options.merge(opts)
merged_options[:default_headers] = opts[:default_headers] ? @default_headers.merge(opts[:default_headers]) : {}
Uri.new(@uri_string, merged_options)
end
def eql?(other)
self == other
end
def ==(other)
return false if other.class != self.class
other.uri == uri && username == other.username && password == other.password
end
def hash
[@uri, @username, @password].hash
end
# This produces exactly the same string as the Wrest::Uri was constructed with.
# If the orignial URI contained a HTTP username and password, that too will
# show up, so be careful if using this for logging.
def to_s
uri_string
end
# :nodoc:
def build_get(parameters = {}, headers = {}, &block)
Http::Get.new(self, parameters, default_headers.merge(headers),
block ? @options.merge(callback_block: block) : @options)
end
# :nodoc:
def build_put(body = '', headers = {}, parameters = {}, &block)
Http::Put.new(self, body.to_s, default_headers.merge(headers), parameters,
block ? @options.merge(callback_block: block) : @options)
end
# :nodoc:
def build_patch(body = '', headers = {}, parameters = {}, &block)
Http::Patch.new(self, body.to_s, default_headers.merge(headers), parameters,
block ? @options.merge(callback_block: block) : @options)
end
# :nodoc:
def build_post(body = '', headers = {}, parameters = {}, &block)
Http::Post.new(self, body.to_s, default_headers.merge(headers), parameters,
block ? @options.merge(callback_block: block) : @options)
end
# :nodoc:
def build_post_form(parameters = {}, headers = {}, &block)
headers = default_headers.merge(headers).merge(Wrest::H::ContentType => Wrest::T::FormEncoded)
body = Utils.hash_to_param(parameters)
Http::Post.new(self, body, headers, {}, block ? @options.merge(callback_block: block) : @options)
end
# :nodoc:
def build_delete(parameters = {}, headers = {}, &block)
Http::Delete.new(self, parameters, default_headers.merge(headers),
block ? @options.merge(callback_block: block) : @options)
end
# Make a GET request to this URI. This is a convenience API
# that creates a Wrest::Native::Get, executes it and returns a Wrest::Native::Response.
#
# Remember to escape all parameter strings if necessary, using URI.escape
def get(parameters = {}, headers = {}, &block)
build_get(parameters, headers, &block).invoke
end
# Make a GET request to this URI. This is a convenience API
# that creates a Wrest::Native::Get.
#
# Remember to escape all parameter strings if necessary, using URI.escape
#
# Note: get_async does not return a response and the response should be accessed through callbacks.
# This implementation of asynchronous get is currently in alpha. Hence, it should not be used in production.
def get_async(parameters = {}, headers = {}, &block)
@asynchronous_backend.execute(build_get(parameters, headers, &block))
end
# Make a PUT request to this URI. This is a convenience API
# that creates a Wrest::Native::Put, executes it and returns a Wrest::Native::Response.
#
# Remember to escape all parameter strings if necessary, using URI.escape
def put(body = '', headers = {}, parameters = {}, &block)
build_put(body, headers, parameters, &block).invoke
end
# Make a PUT request to this URI. This is a convenience API
# that creates a Wrest::Native::Put.
#
# Remember to escape all parameter strings if necessary, using URI.escape
#
# Note: put_async does not return a response and the response should be accessed through callbacks.
# This implementation of asynchronous put is currently in alpha. Hence, it should not be used in production.
def put_async(body = '', headers = {}, parameters = {}, &block)
@asynchronous_backend.execute(build_put(body, headers, parameters, &block))
end
# Make a PATCH request to this URI. This is a convenience API
# that creates a Wrest::Native::Patch, executes it and returns a Wrest::Native::Response.
#
# Remember to escape all parameter strings if necessary, using URI.escape
def patch(body = '', headers = {}, parameters = {}, &block)
build_patch(body, headers, parameters, &block).invoke
end
# Make a PATCH request to this URI. This is a convenience API
# that creates a Wrest::Native::Patch.
#
# Remember to escape all parameter strings if necessary, using URI.escape
#
# Note: patch_async does not return a response and the response should be accessed through callbacks.
def patch_async(body = '', headers = {}, parameters = {}, &block)
@asynchronous_backend.execute(build_patch(body, headers, parameters, &block))
end
# Makes a POST request to this URI. This is a convenience API
# that creates a Wrest::Native::Post, executes it and returns a Wrest::Native::Response.
#
# Remember to escape all parameter strings if necessary, using URI.escape
def post(body = '', headers = {}, parameters = {}, &block)
build_post(body, headers, parameters, &block).invoke
end
# Makes a POST request to this URI. This is a convenience API
# that creates a Wrest::Native::Post.
# Remember to escape all parameter strings if necessary, using URI.escape
#
# Note: post_async does not return a response and the response should be accessed through callbacks.
# This implementation of asynchronous post is currently in alpha. Hence, it should not be used in production.
def post_async(body = '', headers = {}, parameters = {}, &block)
@asynchronous_backend.execute(build_post(body, headers, parameters, &block))
end
# Makes a POST request to this URI. This is a convenience API
# that mimics a form being posted; some allegly RESTful APIs like FCBK require
# this.
#
# Form encoding involves munging the parameters into a string and placing them
# in the body, as well as setting the Content-Type header to
# application/x-www-form-urlencoded
def post_form(parameters = {}, headers = {}, &block)
build_post_form(parameters, headers, &block).invoke
end
# Makes a POST request to this URI. This is a convenience API
# that mimics a form being posted; some allegly RESTful APIs like FCBK require
# this.
#
# Form encoding involves munging the parameters into a string and placing them
# in the body, as well as setting the Content-Type header to
# application/x-www-form-urlencoded
#
# Note: post_form_async does not return a response and the response should be accessed through callbacks.
# This implementation of asynchronous post_form is currently in alpha. Hence, it should not be used in production.
def post_form_async(parameters = {}, headers = {}, &block)
@asynchronous_backend.execute(build_post_form(parameters, headers, &block))
end
# Makes a DELETE request to this URI. This is a convenience API
# that creates a Wrest::Native::Delete, executes it and returns a Wrest::Native::Response.
#
# Remember to escape all parameter strings if necessary, using URI.escape
def delete(parameters = {}, headers = {}, &block)
build_delete(parameters, headers, &block).invoke
end
# Makes a DELETE request to this URI. This is a convenience API
# that creates a Wrest::Native::Delete.
#
# Remember to escape all parameter strings if necessary, using URI.escape
#
# Note: delete_async does not return a response and the response should be accessed through callbacks.
# This implementation of asynchronous delete is currently in alpha. Hence, it should not be used in production.
def delete_async(parameters = {}, headers = {}, &block)
@asynchronous_backend.execute(build_delete(parameters, headers, &block))
end
# Makes an OPTIONS request to this URI. This is a convenience API
# that creates a Wrest::Native::Options, executes it and returns the Wrest::Native::Response.
def options
Http::Options.new(self, @options).invoke
end
def https?
@uri.is_a?(URI::HTTPS)
end
# Provides the full path of a request.
# For example, for
# http://localhost:3000/demons/1/chi?sort=true
# this would return
# /demons/1/chi?sort=true
def full_path
uri.request_uri
end
def protocol
uri.scheme
end
def host
uri.host
end
def port
uri.port
end
include Http::ConnectionFactory
include Uri::Builders
private
def setup_request_config!
@username = (@options[:username] ||= @uri.user)
@password = (@options[:password] ||= @uri.password)
@asynchronous_backend = @options[:asynchronous_backend] || Wrest::AsyncRequest.default_backend
@options[:callback] = Callback.new(@options[:callback]) if @options[:callback]
@default_headers = @options[:default_headers] || {}
end
def setup_uri_state!(uri_string)
@uri_string = uri_string.to_s
@uri = URI.parse(@uri_string)
uri_scheme = URI.split(@uri_string)
@uri_path = uri_scheme[-4].split('?').first || ''
@uri_path = (@uri_path.empty? ? '/' : @uri_path)
@query = uri_scheme[-2] || ''
end
end
end