zero 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +59 -0
- data/Guardfile +15 -0
- data/README.md +60 -0
- data/Thorfile +6 -0
- data/lib/zero.rb +7 -0
- data/lib/zero/controller.rb +46 -0
- data/lib/zero/rack_request.rb +44 -0
- data/lib/zero/renderer.rb +139 -0
- data/lib/zero/request.rb +100 -0
- data/lib/zero/request/accept.rb +28 -0
- data/lib/zero/request/accept_type.rb +58 -0
- data/lib/zero/request/client.rb +31 -0
- data/lib/zero/request/parameter.rb +101 -0
- data/lib/zero/request/server.rb +41 -0
- data/lib/zero/response.rb +80 -0
- data/lib/zero/router.rb +63 -0
- data/lib/zero/version.rb +3 -0
- data/spec/fixtures/templates/index.html.erb +1 -0
- data/spec/fixtures/templates/index.json.erb +1 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/unit/controller/call_spec.rb +22 -0
- data/spec/unit/controller/renderer_spec.rb +11 -0
- data/spec/unit/renderer/read_template_path_spec.rb +53 -0
- data/spec/unit/renderer/render_spec.rb +50 -0
- data/spec/unit/renderer/template_path.rb +8 -0
- data/spec/unit/renderer/type_map_spec.rb +9 -0
- data/spec/unit/request/accept/encoding_spec.rb +9 -0
- data/spec/unit/request/accept/language_spec.rb +9 -0
- data/spec/unit/request/accept/types_spec.rb +9 -0
- data/spec/unit/request/accept_spec.rb +7 -0
- data/spec/unit/request/accepttype/each_spec.rb +10 -0
- data/spec/unit/request/accepttype/preferred_spec.rb +35 -0
- data/spec/unit/request/client/address_spec.rb +9 -0
- data/spec/unit/request/client/hostname_spec.rb +9 -0
- data/spec/unit/request/client/user_agent_spec.rb +9 -0
- data/spec/unit/request/client_spec.rb +8 -0
- data/spec/unit/request/content_type_spec.rb +16 -0
- data/spec/unit/request/create_spec.rb +21 -0
- data/spec/unit/request/delete_spec.rb +15 -0
- data/spec/unit/request/get_spec.rb +15 -0
- data/spec/unit/request/head_spec.rb +15 -0
- data/spec/unit/request/method_spec.rb +8 -0
- data/spec/unit/request/parameter/[]_spec.rb +56 -0
- data/spec/unit/request/parameter/custom_spec.rb +18 -0
- data/spec/unit/request/parameter/initialize_spec.rb +12 -0
- data/spec/unit/request/parameter/payload_spec.rb +33 -0
- data/spec/unit/request/parameter/query_spec.rb +25 -0
- data/spec/unit/request/params_spec.rb +8 -0
- data/spec/unit/request/patch_spec.rb +15 -0
- data/spec/unit/request/path_spec.rb +9 -0
- data/spec/unit/request/post_spec.rb +15 -0
- data/spec/unit/request/put_spec.rb +15 -0
- data/spec/unit/request/server/hostname_spec.rb +9 -0
- data/spec/unit/request/server/port_spec.rb +7 -0
- data/spec/unit/request/server/protocol_spec.rb +9 -0
- data/spec/unit/request/server/software_spec.rb +9 -0
- data/spec/unit/request/server_spec.rb +8 -0
- data/spec/unit/response/response_spec.rb +146 -0
- data/spec/unit/router/call_spec.rb +55 -0
- data/zero.gemspec +27 -0
- metadata +345 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'accept_type'
|
2
|
+
|
3
|
+
module Zero
|
4
|
+
class Request
|
5
|
+
# encapsulates the accept header to easier work with
|
6
|
+
# this is partly copied from sinatra
|
7
|
+
class Accept
|
8
|
+
MEDIA_TYPE_SEPERATOR = ','
|
9
|
+
MEDIA_PARAM_SEPERATOR = ';'
|
10
|
+
MEDIA_QUALITY_REGEX = /q=[01]\./
|
11
|
+
|
12
|
+
KEY_HTTP_ACCEPT = 'HTTP_ACCEPT'
|
13
|
+
KEY_HTTP_ACCEPT_LANGUAGE = 'HTTP_ACCEPT_LANGUAGE'
|
14
|
+
KEY_HTTP_ACCEPT_ENCODING = 'HTTP_ACCEPT_ENCODING'
|
15
|
+
|
16
|
+
# create a new accept object
|
17
|
+
def initialize(environment)
|
18
|
+
@types = AcceptType.new(environment[KEY_HTTP_ACCEPT])
|
19
|
+
@language = AcceptType.new(environment[KEY_HTTP_ACCEPT_LANGUAGE])
|
20
|
+
@encoding = AcceptType.new(environment[KEY_HTTP_ACCEPT_ENCODING])
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :types
|
24
|
+
attr_reader :language
|
25
|
+
attr_reader :encoding
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Zero
|
2
|
+
class Request
|
3
|
+
# This class provides an interface to access information of accept schemas.
|
4
|
+
class AcceptType
|
5
|
+
MEDIA_TYPE_SEPERATOR = ','
|
6
|
+
MEDIA_PARAM_SEPERATOR = ';'
|
7
|
+
MEDIA_QUALITY_REGEX = /q=[01]\./
|
8
|
+
|
9
|
+
# create a new instance of AcceptType
|
10
|
+
def initialize(string)
|
11
|
+
if string.nil?
|
12
|
+
@elements = []
|
13
|
+
else
|
14
|
+
@elements = parse_elements(string)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# return the preferred type
|
19
|
+
# @return String the preferred media type
|
20
|
+
def preferred
|
21
|
+
@elements.first
|
22
|
+
end
|
23
|
+
|
24
|
+
# iterate over all media types
|
25
|
+
def each
|
26
|
+
@elements.each {|element| yield element}
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# converts the accept string to a useable array
|
32
|
+
# @param string the string containing media ranges and options
|
33
|
+
def parse_elements(string = '*/*')
|
34
|
+
string.
|
35
|
+
gsub(/\s/, '').
|
36
|
+
split(MEDIA_TYPE_SEPERATOR).
|
37
|
+
map do |accept_range|
|
38
|
+
extract_order(*accept_range.split(MEDIA_PARAM_SEPERATOR))
|
39
|
+
end.
|
40
|
+
sort_by(&:last).
|
41
|
+
map(&:first)
|
42
|
+
end
|
43
|
+
|
44
|
+
# extract the order of the type
|
45
|
+
# @param media_type the type itself
|
46
|
+
# @param params further options to the type
|
47
|
+
# @return Array the media type and quality in that order
|
48
|
+
def extract_order(media_type, *params)
|
49
|
+
params.each do |param|
|
50
|
+
if param.match(MEDIA_QUALITY_REGEX)
|
51
|
+
return [media_type, 10 - param[4..-1].to_i]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
[media_type, 0]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Zero
|
2
|
+
class Request
|
3
|
+
# This class represents all information about the client of a request.
|
4
|
+
class Client
|
5
|
+
# the key for the ip of the client
|
6
|
+
KEY_REMOTE_ADDR = 'REMOTE_ADDR'
|
7
|
+
# the key for the hostname
|
8
|
+
KEY_REMOTE_HOST = 'REMOTE_HOST'
|
9
|
+
# the key for the user agent
|
10
|
+
KEY_USER_AGENT = 'HTTP_USER_AGENT'
|
11
|
+
|
12
|
+
# creates a new client with the data of the request environment
|
13
|
+
# @param environment a hash representation of the request
|
14
|
+
def initialize(environment)
|
15
|
+
@address = environment[KEY_REMOTE_ADDR]
|
16
|
+
@hostname = environment[KEY_REMOTE_HOST]
|
17
|
+
@user_agent = environment[KEY_USER_AGENT]
|
18
|
+
end
|
19
|
+
|
20
|
+
# the ip address of the client
|
21
|
+
# @return [String] the address of the client
|
22
|
+
attr_reader :address
|
23
|
+
# the hostname of the client
|
24
|
+
# @return [String] the hostname of the client
|
25
|
+
attr_reader :hostname
|
26
|
+
# the user agent of the client
|
27
|
+
# @return [String] the user agent of the client
|
28
|
+
attr_reader :user_agent
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# TODO should that go into the main zero file?
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Zero
|
5
|
+
class Request
|
6
|
+
# represents all parameter set in a session
|
7
|
+
#
|
8
|
+
# This class holds all parameters available in the rack environment, split
|
9
|
+
# on query and payload parameters.
|
10
|
+
class Parameter
|
11
|
+
# they key for the query string
|
12
|
+
ENV_KEY_QUERY = 'QUERY_STRING'
|
13
|
+
# the key for the payload
|
14
|
+
ENV_KEY_PAYLOAD = 'rack.input'
|
15
|
+
# the key for custom parameters
|
16
|
+
ENV_KEY_CUSTOM = 'zero.params.custom'
|
17
|
+
# the key for the content type
|
18
|
+
ENV_KEY_CONTENT_TYPE = 'CONTENT_TYPE'
|
19
|
+
# all content types which used for using the body as a parameter input
|
20
|
+
PAYLOAD_CONTENT_TYPES = [
|
21
|
+
'application/x-www-form-urlencoded',
|
22
|
+
'multipart/form-data'
|
23
|
+
].to_set
|
24
|
+
|
25
|
+
# get the query parameters
|
26
|
+
attr_reader :query
|
27
|
+
alias_method(:get, :query)
|
28
|
+
|
29
|
+
# get the payload or form data parameters
|
30
|
+
attr_reader :payload
|
31
|
+
alias_method(:post, :payload)
|
32
|
+
|
33
|
+
# get all custom parameters
|
34
|
+
attr_reader :custom
|
35
|
+
|
36
|
+
# creates a new parameter instance
|
37
|
+
#
|
38
|
+
# This should never be called directly, as it will be generated for you.
|
39
|
+
# This instance gives you the options to get query parameters (mostly
|
40
|
+
# called GET parameters) and payload parameters (or POST parameters).
|
41
|
+
# @param environment [Hash] the rack environment
|
42
|
+
def initialize(environment)
|
43
|
+
@query = extract_query_params(environment)
|
44
|
+
@payload = extract_payload_params(environment)
|
45
|
+
if environment.has_key?(ENV_KEY_CUSTOM)
|
46
|
+
@custom = environment[ENV_KEY_CUSTOM]
|
47
|
+
else
|
48
|
+
@custom = {}
|
49
|
+
environment[ENV_KEY_CUSTOM] = @custom
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# get a parameter
|
54
|
+
#
|
55
|
+
# With this method you can get the value of a parameter. First the
|
56
|
+
# custom parameters are checked, then payload and after that the query
|
57
|
+
# ones.
|
58
|
+
#
|
59
|
+
# *Beware, that this may lead to security holes!*
|
60
|
+
#
|
61
|
+
# @param key [String] a key to look for
|
62
|
+
# @returns [String] the value of the key
|
63
|
+
def [](key)
|
64
|
+
@custom[key] || @payload[key] || @query[key]
|
65
|
+
end
|
66
|
+
|
67
|
+
# set a custom key/value pair
|
68
|
+
#
|
69
|
+
# Use this method if you want to set a custom parameter for later use. If
|
70
|
+
# the key was already set it will be overwritten.
|
71
|
+
# @param key [String] the key to use for saving the parameter
|
72
|
+
# @param value [Object] the value for the key
|
73
|
+
def []=(key, value)
|
74
|
+
@custom[key] = value
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# extracts the key value pairs from the environment
|
80
|
+
# @return Hash all key value pairs from query string
|
81
|
+
def extract_query_params(environment)
|
82
|
+
return {} if environment[ENV_KEY_QUERY].length == 0
|
83
|
+
parse_string(environment[ENV_KEY_QUERY])
|
84
|
+
end
|
85
|
+
|
86
|
+
# extracts the key value pairs from the body
|
87
|
+
# @return Hash all key value pairs from the payload
|
88
|
+
def extract_payload_params(environment)
|
89
|
+
return {} unless PAYLOAD_CONTENT_TYPES.include?(environment[ENV_KEY_CONTENT_TYPE])
|
90
|
+
parse_string(environment[ENV_KEY_PAYLOAD].read)
|
91
|
+
end
|
92
|
+
|
93
|
+
# parse the query string like input to a hash
|
94
|
+
# @param query [String] the query string
|
95
|
+
# @return [Hash] the key/valuie pairs
|
96
|
+
def parse_string(query)
|
97
|
+
Hash[URI.decode_www_form(query)]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Zero
|
2
|
+
class Request
|
3
|
+
# This class represents all server related information of a request.
|
4
|
+
class Server
|
5
|
+
# the key for the server name
|
6
|
+
# @api private
|
7
|
+
KEY_SERVER_NAME = 'SERVER_NAME'
|
8
|
+
# the key for the server port
|
9
|
+
# @api private
|
10
|
+
KEY_SERVER_PORT = 'SERVER_PORT'
|
11
|
+
# the key for the server protocol
|
12
|
+
# @api private
|
13
|
+
KEY_SERVER_PROTOCOL = 'SERVER_PROTOCOL'
|
14
|
+
# the key for the server software
|
15
|
+
# @api private
|
16
|
+
KEY_SERVER_SOFTWARE = 'SERVER_SOFTWARE'
|
17
|
+
|
18
|
+
# This creates a new server instance extracting all server related
|
19
|
+
# information from the environment.
|
20
|
+
def initialize(environment)
|
21
|
+
@hostname = environment[KEY_SERVER_NAME]
|
22
|
+
@port = environment[KEY_SERVER_PORT].to_i
|
23
|
+
@protocol = environment[KEY_SERVER_PROTOCOL]
|
24
|
+
@software = environment[KEY_SERVER_SOFTWARE]
|
25
|
+
end
|
26
|
+
|
27
|
+
# get the port
|
28
|
+
# @return [Numeric] the port
|
29
|
+
attr_reader :port
|
30
|
+
# get the hostname of the server
|
31
|
+
# @return [String] the hostname
|
32
|
+
attr_reader :hostname
|
33
|
+
# get the protocol of the server (normally it should be HTTP/1.1)
|
34
|
+
# @return [String] the protocol
|
35
|
+
attr_reader :protocol
|
36
|
+
# get the server software
|
37
|
+
# @return [String] the server software name
|
38
|
+
attr_reader :software
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Zero
|
2
|
+
|
3
|
+
# This is the representation of a response
|
4
|
+
#
|
5
|
+
class Response
|
6
|
+
attr_reader :status
|
7
|
+
attr_accessor :header, :body
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
# Sets default status code to 200.
|
11
|
+
#
|
12
|
+
def initialize
|
13
|
+
@status = 200
|
14
|
+
@header = {}
|
15
|
+
@body = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets the status.
|
19
|
+
# Also converts every input directly to an integer.
|
20
|
+
#
|
21
|
+
# @param [Integer] status The status code
|
22
|
+
#
|
23
|
+
def status=(status)
|
24
|
+
@status = status.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the data of the response as an array:
|
28
|
+
# [status, header, body]
|
29
|
+
# to be usable by any webserver.
|
30
|
+
#
|
31
|
+
# Sets the Content-Type to 'text/html', if it's not already set.
|
32
|
+
# Sets the Content-Length, if it's not already set. (Won't fix wrong
|
33
|
+
# lengths!)
|
34
|
+
# Removes Content-Type, Content-Length and body on status code 204 and 304.
|
35
|
+
#
|
36
|
+
# @return [Array] Usable by webservers
|
37
|
+
#
|
38
|
+
def to_a
|
39
|
+
# Remove content length and body, on status 204 and 304
|
40
|
+
if status == 204 or status == 304
|
41
|
+
header.delete('Content-Length')
|
42
|
+
header.delete('Content-Type')
|
43
|
+
self.body = []
|
44
|
+
else
|
45
|
+
# Set content length, if not already set
|
46
|
+
content_length unless header.has_key? 'Content-Length'
|
47
|
+
# Set content type, if not already set
|
48
|
+
self.content_type = 'text/html' unless header.has_key? 'Content-Type'
|
49
|
+
end
|
50
|
+
|
51
|
+
[status, header, body]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Sets the content length header to the current length of the body
|
55
|
+
# Also creates one, if it does not exists
|
56
|
+
#
|
57
|
+
def content_length
|
58
|
+
self.header['Content-Length'] = body.join.bytesize.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets the content type header to the given value
|
62
|
+
# Also creates it, if it does not exists
|
63
|
+
#
|
64
|
+
# @param [String] value Content-Type tp set
|
65
|
+
#
|
66
|
+
def content_type=(value)
|
67
|
+
self.header['Content-Type'] = value
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sets the Location header to the given URL and the status code to 302.
|
71
|
+
#
|
72
|
+
# @param [String] location Redirect URL
|
73
|
+
#
|
74
|
+
def redirect(location, status = 302)
|
75
|
+
self.status = status
|
76
|
+
self.header['Location'] = location
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/lib/zero/router.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Zero
|
2
|
+
# makes it possible to route urls to rack applications
|
3
|
+
#
|
4
|
+
# This class can be used to build a small rack application which routes
|
5
|
+
# requests to the given application.
|
6
|
+
# In the URLs it is also possible to use placeholders which then get assigned
|
7
|
+
# as variables to the parameters.
|
8
|
+
#
|
9
|
+
# @example of a simple router
|
10
|
+
# router = Zero::Router.new(
|
11
|
+
# '/' => WelcomeController,
|
12
|
+
# '/posts' => PostController
|
13
|
+
# )
|
14
|
+
#
|
15
|
+
# @example of a router with variables
|
16
|
+
# router = Zero::Router.new(
|
17
|
+
# '/foo/:id' => FooController
|
18
|
+
# )
|
19
|
+
class Router
|
20
|
+
# match for variables in routes
|
21
|
+
VARIABLE_MATCH = %r{:(\w+)[^/]?}
|
22
|
+
# the replacement string to make it an regex
|
23
|
+
VARIABLE_REGEX = '(?<\1>.+?)'
|
24
|
+
|
25
|
+
# create a new router instance
|
26
|
+
#
|
27
|
+
# @example of a simple router
|
28
|
+
# router = Zero::Router.new(
|
29
|
+
# '/' => WelcomeController,
|
30
|
+
# '/posts' => PostController
|
31
|
+
# )
|
32
|
+
#
|
33
|
+
# @param routes [Hash] a map of URLs to rack compatible applications
|
34
|
+
def initialize(routes)
|
35
|
+
@routes = {}
|
36
|
+
routes.each do |route, target|
|
37
|
+
@routes[
|
38
|
+
Regexp.new(
|
39
|
+
route.gsub(VARIABLE_MATCH, VARIABLE_REGEX) + '$')] = target
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# call the router and call the matching application
|
44
|
+
#
|
45
|
+
# This method has to be called with a rack compatible environment, then the
|
46
|
+
# method will find a matching route and call the application.
|
47
|
+
# @param env [Hash] a rack environment
|
48
|
+
# @return [Array] a rack compatible response
|
49
|
+
def call(env)
|
50
|
+
request = Zero::Request.create(env)
|
51
|
+
@routes.each do |route, target|
|
52
|
+
match = route.match(request.path)
|
53
|
+
if match
|
54
|
+
match.names.each_index do |i|
|
55
|
+
request.params[match.names[i]] = match.captures[i]
|
56
|
+
end
|
57
|
+
return target.call(request.env)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
[404, {'Content-Type' => 'text/html'}, ['Not found!']]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/zero/version.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
success
|
@@ -0,0 +1 @@
|
|
1
|
+
{text: 'success'}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter '_spec.rb'
|
4
|
+
add_filter 'spec_helper.rb'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rack'
|
8
|
+
require 'erb'
|
9
|
+
require 'tilt'
|
10
|
+
require 'zero'
|
11
|
+
|
12
|
+
class SpecTemplateContext
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
def initialize(name)
|
16
|
+
@name = name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class SpecController < Zero::Controller
|
21
|
+
def process; end
|
22
|
+
def render; @response = [200, {'Content-Type' => 'text/html'}, ['foo']]; end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SpecApp
|
26
|
+
attr_reader :env
|
27
|
+
|
28
|
+
def self.call(env)
|
29
|
+
@env = env
|
30
|
+
return [200, {'Content-Type' => 'text/html'}, ['success']]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class EnvGenerator
|
35
|
+
KEY_REQUEST_METHOD = 'REQUEST_METHOD'
|
36
|
+
KEY_REQUEST_GET = 'GET'
|
37
|
+
KEY_REQUEST_HEAD = 'HEAD'
|
38
|
+
KEY_REQUEST_POST = 'POST'
|
39
|
+
KEY_REQUEST_PUT = 'PUT'
|
40
|
+
KEY_REQUEST_DELETE = 'DELETE'
|
41
|
+
|
42
|
+
def self.generate_env(uri, options)
|
43
|
+
Rack::MockRequest.env_for(uri, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.get(uri, options = {})
|
47
|
+
generate_env(uri, options.merge(KEY_REQUEST_METHOD => KEY_REQUEST_GET))
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.head(uri, options = {})
|
51
|
+
generate_env(uri, options.merge(KEY_REQUEST_METHOD => KEY_REQUEST_HEAD))
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.post(uri, options = {})
|
55
|
+
generate_env(uri, options.merge(KEY_REQUEST_METHOD => KEY_REQUEST_POST))
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.put(uri, options = {})
|
59
|
+
generate_env(uri, options.merge(KEY_REQUEST_METHOD => KEY_REQUEST_PUT))
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.delete(uri, options = {})
|
63
|
+
generate_env(uri, options.merge(KEY_REQUEST_METHOD => KEY_REQUEST_DELETE))
|
64
|
+
end
|
65
|
+
end
|