/
base.cr
232 lines (199 loc) · 8.53 KB
/
base.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
require "./concerns/callbacks"
require "./concerns/cookies"
require "./concerns/flash"
require "./concerns/request_forgery_protection"
require "./concerns/session"
require "./concerns/x_frame_options"
module Marten
module Handlers
# Base handler implementation.
#
# This class defines the behaviour of a handler. A handler is initialized from an HTTP request and it is responsible
# for processing a request in order to produce an HTTP response (which can be an HTML content, a redirection, etc).
class Base
include Callbacks
include Cookies
include Flash
include RequestForgeryProtection
include Session
include XFrameOptions
HTTP_METHOD_NAMES = %w(get post put patch delete head options trace)
@@http_method_names : Array(String) = HTTP_METHOD_NAMES
@response : HTTP::Response? = nil
# Returns the HTTP method names that are allowed for the handler.
class_getter http_method_names
# Returns the associated HTTP request.
getter request
# Returns the HTTP response.
#
# This method will return the `Marten::HTTP::Response` object that is returned by the `#dispatch` method, so that
# it can be used in the context of `#after_dispatch` callbacks.
getter response
# Returns the associated route parameters.
getter params
# Allows to specify the allowed HTTP methods.
def self.http_method_names(*method_names : String | Symbol)
@@http_method_names = method_names.to_a.map(&.to_s)
end
def initialize(@request : HTTP::Request, @params : Hash(String, Routing::Parameter::Types))
end
def initialize(@request : HTTP::Request)
@params = {} of String => Routing::Parameter::Types
end
# Triggers the execution of the handler in order to produce an HTTP response.
#
# This method will be called by the Marten server when it comes to produce an HTTP response once a handler has
# been identified for the considered route. This method will execute the handler method associated with the
# considered HTTP method (eg. `#get` for the `GET` method) in order to return the final HTTP response. A 405
# response will be returned if the considered HTTP method is not allowed. The `#dispatch` method can also be
# overridden on a per-handler basis in order to implement any other arbitrary logics if necessary.
def dispatch : Marten::HTTP::Response
if self.class.http_method_names.includes?(request.method.downcase)
call_http_method
else
handle_http_method_not_allowed
end
end
# Handles a `GET` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return a 405 (not allowed) response.
def get
handle_http_method_not_allowed
end
# Handles a `POST` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return a 405 (not allowed) response.
def post
handle_http_method_not_allowed
end
# Handles a `PUT` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return a 405 (not allowed) response.
def put
handle_http_method_not_allowed
end
# Handles a `PATCH` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return a 405 (not allowed) response.
def patch
handle_http_method_not_allowed
end
# Handles a `DELETE` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return a 405 (not allowed) response.
def delete
handle_http_method_not_allowed
end
# Handles a `TRACE` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return a 405 (not allowed) response.
def trace
handle_http_method_not_allowed
end
# Handles a `HEAD` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation is to return whatever is returned by the `#get` method.
def head
# By default HEAD requests are delegated to the get handler - which will result in a not allowed response if the
# latest is not defined.
get
end
# Returns an empty response associated with a given status code.
def head(status : Int32) : HTTP::Response
HTTP::Response.new(content: "", content_type: "", status: status)
end
# Returns an HTTP response containing the passed raw JSON string.
#
# The response will use the `application/json` content type and the `200` status code (the latest can be set to
# something else through the use of the `status` argument).
def json(raw_json : String, status = 200)
HTTP::Response.new(content: raw_json, content_type: "application/json", status: status)
end
# Returns an HTTP response containing the passed object serialized as JSON.
#
# The response will use the `application/json` content type and the `200` status code (the latest can be set to
# something else through the use of the `status` argument).
def json(serializable, status = 200)
HTTP::Response.new(content: serializable.to_json, content_type: "application/json", status: status)
end
# Handles an `OPTIONS` HTTP request and returns a `Marten::HTTP::Response` object.
#
# The default implementation will return an HTTP response that includes an `Allow` header populated from the
# configured allowed HTTP methods.
def options
# Responds to requests for the OPTIONS HTTP verb.
response = HTTP::Response.new
response["Allow"] = self.class.http_method_names.join(", ") { |m| m.upcase }
response["Content-Length"] = "0"
response
end
# :nodoc:
def process_dispatch : Marten::HTTP::Response
before_callbacks_response = run_before_dispatch_callbacks
@response = before_callbacks_response || dispatch
after_callbacks_response = run_after_dispatch_callbacks
after_callbacks_response || response!
end
# Returns a redirect HTTP response for a specific `url`.
#
# By default, the HTTP response returned will be a "302 Found", unless the `permanent` argument is set to `true`
# (in which case the response will be a "301 Moved Permanently").
def redirect(url : String, permanent = false)
permanent ? HTTP::Response::MovedPermanently.new(url) : HTTP::Response::Found.new(url)
end
# Returns an HTTP response whose content is generated by rendering a specific template.
#
# The context of the rendered template can be specified using the `context` argument, while the content type and
# status code of the response can be specified using the `content_type` and `status` arguments.
def render(
template_name : String,
context : Hash | NamedTuple | Nil | Marten::Template::Context = nil,
content_type = HTTP::Response::DEFAULT_CONTENT_TYPE,
status = 200
)
template_context = Marten::Template::Context.from(context, request)
template_context["handler"] = self
HTTP::Response.new(
content: Marten.templates.get_template(template_name).render(template_context),
content_type: content_type,
status: status
)
end
# Returns an HTTP response generated from a content string, content type and status code.
def respond(content = "", content_type = HTTP::Response::DEFAULT_CONTENT_TYPE, status = 200)
HTTP::Response.new(content: content, content_type: content_type, status: status)
end
# Same as `#response` but with a nil-safety check.
def response!
response.not_nil!
end
# Convenient helper method to resolve a route name.
delegate reverse, to: Marten.routes
private def handle_http_method_not_allowed
HTTP::Response::MethodNotAllowed.new(self.class.http_method_names)
end
private def call_http_method
case request.method.downcase
when "get"
get
when "post"
post
when "put"
put
when "patch"
patch
when "delete"
delete
when "head"
head
when "options"
options
when "trace"
trace
else
handle_http_method_not_allowed
end
end
end
end
end