zero 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|