mirror of
https://gitea.invidious.io/iv-org/shard-kemal.git
synced 2024-08-15 00:53:36 +00:00
Refactor helpers into module namespaces
This commit is contained in:
parent
aaa2109837
commit
1cd329b92f
11 changed files with 357 additions and 177 deletions
|
@ -4,6 +4,7 @@ private def handle(request, fallthrough = true)
|
|||
io = IO::Memory.new
|
||||
response = HTTP::Server::Response.new(io)
|
||||
context = HTTP::Server::Context.new(request, response)
|
||||
context.app = Kemal.application
|
||||
handler = Kemal::StaticFileHandler.new "#{__DIR__}/static", fallthrough
|
||||
handler.call context
|
||||
response.close
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require "./helpers/*"
|
||||
|
||||
# Kemal Base
|
||||
# The DSL currently consists of
|
||||
# - get post put patch delete options
|
||||
|
@ -5,6 +7,10 @@
|
|||
# - before_*
|
||||
# - error
|
||||
class Kemal::Base
|
||||
include FileHelpers
|
||||
include Templates
|
||||
include Macros
|
||||
|
||||
HTTP_METHODS = %w(get post put patch delete options)
|
||||
FILTER_METHODS = %w(get post put patch delete options all)
|
||||
|
||||
|
|
|
@ -51,7 +51,11 @@ module Kemal
|
|||
|
||||
def serve_static?(key)
|
||||
config = @serve_static
|
||||
config.try(&.[key]?) || config == true
|
||||
(config.is_a?(Hash) && config[key]?) || false
|
||||
end
|
||||
|
||||
|
||||
def extra_options(&@extra_options : OptionParser ->)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# - WebSocket(ws)
|
||||
# - before_*
|
||||
# - error
|
||||
require "./dsl/*"
|
||||
|
||||
{% for method in Kemal::Base::HTTP_METHODS %}
|
||||
def {{method.id}}(path, &block : HTTP::Server::Context -> _)
|
||||
|
|
|
@ -26,7 +26,7 @@ end
|
|||
# Logs the output via `logger`.
|
||||
# This is the built-in `Kemal::LogHandler` by default which uses STDOUT.
|
||||
def log(message : String)
|
||||
Kemal.application.logger.write "#{message}\n"
|
||||
Kemal.application.log(message)
|
||||
end
|
||||
|
||||
# Enables / Disables logging.
|
||||
|
@ -65,7 +65,6 @@ end
|
|||
# ```
|
||||
def logger(logger : Kemal::BaseLogHandler)
|
||||
Kemal.application.logger = logger
|
||||
Kemal.application.add_handler logger
|
||||
end
|
||||
|
||||
# Enables / Disables static file serving.
|
||||
|
@ -94,7 +93,7 @@ end
|
|||
# end
|
||||
# ```
|
||||
def headers(env : HTTP::Server::Context, additional_headers : Hash(String, String))
|
||||
env.response.headers.merge!(additional_headers)
|
||||
Kemal.application.headers(env, additional_headers)
|
||||
end
|
||||
|
||||
# Send a file with given path and base the mime-type on the file extension
|
||||
|
@ -110,82 +109,7 @@ end
|
|||
# send_file env, "./path/to/file", "image/jpeg"
|
||||
# ```
|
||||
def send_file(env : HTTP::Server::Context, path : String, mime_type : String? = nil)
|
||||
config = Kemal.config.serve_static
|
||||
file_path = File.expand_path(path, Dir.current)
|
||||
mime_type ||= Kemal::Utils.mime_type(file_path)
|
||||
env.response.content_type = mime_type
|
||||
env.response.headers["Accept-Ranges"] = "bytes"
|
||||
env.response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
minsize = 860 # http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits ??
|
||||
request_headers = env.request.headers
|
||||
filesize = File.size(file_path)
|
||||
filestat = File.info(file_path)
|
||||
|
||||
Kemal.config.static_headers.try(&.call(env.response, file_path, filestat))
|
||||
|
||||
File.open(file_path) do |file|
|
||||
if env.request.method == "GET" && env.request.headers.has_key?("Range")
|
||||
next multipart(file, env)
|
||||
end
|
||||
|
||||
condition = config.is_a?(Hash) && config["gzip"]? == true && filesize > minsize && Kemal::Utils.zip_types(file_path)
|
||||
if condition && request_headers.includes_word?("Accept-Encoding", "gzip")
|
||||
env.response.headers["Content-Encoding"] = "gzip"
|
||||
Gzip::Writer.open(env.response) do |deflate|
|
||||
IO.copy(file, deflate)
|
||||
end
|
||||
elsif condition && request_headers.includes_word?("Accept-Encoding", "deflate")
|
||||
env.response.headers["Content-Encoding"] = "deflate"
|
||||
Flate::Writer.open(env.response) do |deflate|
|
||||
IO.copy(file, deflate)
|
||||
end
|
||||
else
|
||||
env.response.content_length = filesize
|
||||
IO.copy(file, env.response)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
private def multipart(file, env : HTTP::Server::Context)
|
||||
# See http://httpwg.org/specs/rfc7233.html
|
||||
fileb = file.size
|
||||
startb = endb = 0
|
||||
|
||||
if match = env.request.headers["Range"].match /bytes=(\d{1,})-(\d{0,})/
|
||||
startb = match[1].to_i { 0 } if match.size >= 2
|
||||
endb = match[2].to_i { 0 } if match.size >= 3
|
||||
end
|
||||
|
||||
endb = fileb - 1 if endb == 0
|
||||
|
||||
if startb < endb < fileb
|
||||
content_length = 1 + endb - startb
|
||||
env.response.status_code = 206
|
||||
env.response.content_length = content_length
|
||||
env.response.headers["Accept-Ranges"] = "bytes"
|
||||
env.response.headers["Content-Range"] = "bytes #{startb}-#{endb}/#{fileb}" # MUST
|
||||
|
||||
if startb > 1024
|
||||
skipped = 0
|
||||
# file.skip only accepts values less or equal to 1024 (buffer size, undocumented)
|
||||
until (increase_skipped = skipped + 1024) > startb
|
||||
file.skip(1024)
|
||||
skipped = increase_skipped
|
||||
end
|
||||
if (skipped_minus_startb = skipped - startb) > 0
|
||||
file.skip skipped_minus_startb
|
||||
end
|
||||
else
|
||||
file.skip(startb)
|
||||
end
|
||||
|
||||
IO.copy(file, env.response, content_length)
|
||||
else
|
||||
env.response.content_length = fileb
|
||||
env.response.status_code = 200 # Range not satisfable, see 4.4 Note
|
||||
IO.copy(file, env.response)
|
||||
end
|
||||
Kemal.application.send_file(env, path, mime_type)
|
||||
end
|
||||
|
||||
# Send a file with given data and default `application/octet-stream` mime_type.
|
||||
|
@ -200,10 +124,7 @@ end
|
|||
# send_file env, data_slice, "image/jpeg"
|
||||
# ```
|
||||
def send_file(env : HTTP::Server::Context, data : Slice(UInt8), mime_type : String? = nil)
|
||||
mime_type ||= "application/octet-stream"
|
||||
env.response.content_type = mime_type
|
||||
env.response.content_length = data.bytesize
|
||||
env.response.write data
|
||||
Kemal.application.send_file(env, data, mime_type)
|
||||
end
|
||||
|
||||
# Configures an `HTTP::Server::Response` to compress the response
|
||||
|
@ -211,7 +132,7 @@ end
|
|||
#
|
||||
# Disabled by default.
|
||||
def gzip(status : Bool = false)
|
||||
add_handler HTTP::CompressHandler.new if status
|
||||
Kemal.application.gzip(status)
|
||||
end
|
||||
|
||||
# Adds headers to `Kemal::StaticFileHandler`. This is especially useful for `CORS`.
|
47
src/kemal/dsl/macros.cr
Normal file
47
src/kemal/dsl/macros.cr
Normal file
|
@ -0,0 +1,47 @@
|
|||
def content_for_blocks
|
||||
Kemal.application.content_for_blocks
|
||||
end
|
||||
|
||||
macro content_for(key, file = __FILE__)
|
||||
Kemal::Macros.content_for({{key}}, {{file}}) do
|
||||
{{yield}}
|
||||
end
|
||||
end
|
||||
|
||||
# Yields content for the given key if a `content_for` block exists for that key.
|
||||
macro yield_content(key)
|
||||
Kemal::Macros.yield_content({{key}})
|
||||
end
|
||||
|
||||
# Render view with a layout as the superview.
|
||||
#
|
||||
# render "src/views/index.ecr", "src/views/layout.ecr"
|
||||
#
|
||||
macro render(filename, layout)
|
||||
Kemal::Macros.render({{filename}}, {{layout}})
|
||||
end
|
||||
|
||||
# Render view with the given filename.
|
||||
macro render(filename)
|
||||
Kemal::Macros.render({{filename}})
|
||||
end
|
||||
|
||||
# Halt execution with the current context.
|
||||
# Returns 200 and an empty response by default.
|
||||
#
|
||||
# halt env, status_code: 403, response: "Forbidden"
|
||||
macro halt(env, status_code = 200, response = "")
|
||||
Kemal::Macros.halt({{env}}, {{status_code}}, {{response}})
|
||||
end
|
||||
|
||||
# Extends context storage with user defined types.
|
||||
#
|
||||
# class User
|
||||
# property name
|
||||
# end
|
||||
#
|
||||
# add_context_storage_type(User)
|
||||
#
|
||||
macro add_context_storage_type(type)
|
||||
Kemal::Macros.add_context_storage_type({{type}})
|
||||
end
|
7
src/kemal/dsl/templates.cr
Normal file
7
src/kemal/dsl/templates.cr
Normal file
|
@ -0,0 +1,7 @@
|
|||
def render_404
|
||||
Kemal.application.render_404
|
||||
end
|
||||
|
||||
def render_500(context, backtrace, verbosity)
|
||||
Kemal.application.render_500(context, backtrace, verbosity)
|
||||
end
|
136
src/kemal/helpers/file_helpers.cr
Normal file
136
src/kemal/helpers/file_helpers.cr
Normal file
|
@ -0,0 +1,136 @@
|
|||
module Kemal::FileHelpers
|
||||
def log(message)
|
||||
logger.write "#{message}\n"
|
||||
end
|
||||
|
||||
# Send a file with given path and base the mime-type on the file extension
|
||||
# or default `application/octet-stream` mime_type.
|
||||
#
|
||||
# ```
|
||||
# send_file env, "./path/to/file"
|
||||
# ```
|
||||
#
|
||||
# Optionally you can override the mime_type
|
||||
#
|
||||
# ```
|
||||
# send_file env, "./path/to/file", "image/jpeg"
|
||||
# ```
|
||||
def send_file(env : HTTP::Server::Context, path : String, mime_type : String? = nil)
|
||||
config = env.app.config
|
||||
file_path = File.expand_path(path, Dir.current)
|
||||
mime_type ||= Kemal::Utils.mime_type(file_path)
|
||||
env.response.content_type = mime_type
|
||||
env.response.headers["Accept-Ranges"] = "bytes"
|
||||
env.response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
minsize = 860 # http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits ??
|
||||
request_headers = env.request.headers
|
||||
filesize = File.size(file_path)
|
||||
filestat = File.stat(file_path)
|
||||
|
||||
config.static_headers.try(&.call(env.response, file_path, filestat))
|
||||
|
||||
File.open(file_path) do |file|
|
||||
if env.request.method == "GET" && env.request.headers.has_key?("Range")
|
||||
next multipart(file, env)
|
||||
end
|
||||
if request_headers.includes_word?("Accept-Encoding", "gzip") && config.serve_static?("gzip") && filesize > minsize && Kemal::Utils.zip_types(file_path)
|
||||
env.response.headers["Content-Encoding"] = "gzip"
|
||||
Gzip::Writer.open(env.response) do |deflate|
|
||||
IO.copy(file, deflate)
|
||||
end
|
||||
elsif request_headers.includes_word?("Accept-Encoding", "deflate") && config.serve_static?("gzip") && filesize > minsize && Kemal::Utils.zip_types(file_path)
|
||||
env.response.headers["Content-Encoding"] = "deflate"
|
||||
Flate::Writer.open(env.response) do |deflate|
|
||||
IO.copy(file, deflate)
|
||||
end
|
||||
else
|
||||
env.response.content_length = filesize
|
||||
IO.copy(file, env.response)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
private def multipart(file, env : HTTP::Server::Context)
|
||||
# See http://httpwg.org/specs/rfc7233.html
|
||||
fileb = file.size
|
||||
|
||||
range = env.request.headers["Range"]
|
||||
match = range.match(/bytes=(\d{1,})-(\d{0,})/)
|
||||
|
||||
startb = 0
|
||||
endb = 0
|
||||
|
||||
if match
|
||||
if match.size >= 2
|
||||
startb = match[1].to_i { 0 }
|
||||
end
|
||||
|
||||
if match.size >= 3
|
||||
endb = match[2].to_i { 0 }
|
||||
end
|
||||
end
|
||||
|
||||
if endb == 0
|
||||
endb = fileb - 1
|
||||
end
|
||||
|
||||
if startb < endb && endb < fileb
|
||||
content_length = 1 + endb - startb
|
||||
env.response.status_code = 206
|
||||
env.response.content_length = content_length
|
||||
env.response.headers["Accept-Ranges"] = "bytes"
|
||||
env.response.headers["Content-Range"] = "bytes #{startb}-#{endb}/#{fileb}" # MUST
|
||||
|
||||
if startb > 1024
|
||||
skipped = 0
|
||||
# file.skip only accepts values less or equal to 1024 (buffer size, undocumented)
|
||||
until skipped + 1024 > startb
|
||||
file.skip(1024)
|
||||
skipped += 1024
|
||||
end
|
||||
if skipped - startb > 0
|
||||
file.skip(skipped - startb)
|
||||
end
|
||||
else
|
||||
file.skip(startb)
|
||||
end
|
||||
|
||||
IO.copy(file, env.response, content_length)
|
||||
else
|
||||
env.response.content_length = fileb
|
||||
env.response.status_code = 200 # Range not satisfable, see 4.4 Note
|
||||
IO.copy(file, env.response)
|
||||
end
|
||||
end
|
||||
|
||||
def headers(env, additional_headers)
|
||||
env.response.headers.merge!(additional_headers)
|
||||
end
|
||||
|
||||
# Send a file with given data and default `application/octet-stream` mime_type.
|
||||
#
|
||||
# ```
|
||||
# send_file env, data_slice
|
||||
# ```
|
||||
#
|
||||
# Optionally you can override the mime_type
|
||||
#
|
||||
# ```
|
||||
# send_file env, data_slice, "image/jpeg"
|
||||
# ```
|
||||
def send_file(env : HTTP::Server::Context, data : Slice(UInt8), mime_type : String? = nil)
|
||||
mime_type ||= "application/octet-stream"
|
||||
env.response.content_type = mime_type
|
||||
env.response.content_length = data.bytesize
|
||||
env.response.write data
|
||||
end
|
||||
|
||||
# Configures an `HTTP::Server::Response` to compress the response
|
||||
# output, either using gzip or deflate, depending on the `Accept-Encoding` request header.
|
||||
#
|
||||
# Disabled by default.
|
||||
def gzip(status : Bool = false)
|
||||
add_handler HTTP::CompressHandler.new if status
|
||||
end
|
||||
end
|
|
@ -1,98 +1,101 @@
|
|||
require "kilt"
|
||||
|
||||
CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(String))).new
|
||||
module Kemal::Macros
|
||||
def content_for_blocks
|
||||
@content_for_blocks ||= Hash(String, Tuple(String, Proc(String))).new
|
||||
end
|
||||
|
||||
# `content_for` is a set of helpers that allows you to capture
|
||||
# blocks inside views to be rendered later during the request. The most
|
||||
# common use is to populate different parts of your layout from your view.
|
||||
#
|
||||
# The currently supported engines are: ecr and slang.
|
||||
#
|
||||
# ## Usage
|
||||
#
|
||||
# You call `content_for`, generally from a view, to capture a block of markup
|
||||
# giving it an identifier:
|
||||
#
|
||||
# ```
|
||||
# # index.ecr
|
||||
# <% content_for "some_key" do %>
|
||||
# <chunk of="html">...</chunk>
|
||||
# <% end %>
|
||||
# ```
|
||||
#
|
||||
# Then, you call `yield_content` with that identifier, generally from a
|
||||
# layout, to render the captured block:
|
||||
#
|
||||
# ```
|
||||
# # layout.ecr
|
||||
# <%= yield_content "some_key" %>
|
||||
# ```
|
||||
#
|
||||
# ## And How Is This Useful?
|
||||
#
|
||||
# For example, some of your views might need a few javascript tags and
|
||||
# stylesheets, but you don't want to force this files in all your pages.
|
||||
# Then you can put `<%= yield_content :scripts_and_styles %>` on your
|
||||
# layout, inside the <head> tag, and each view can call `content_for`
|
||||
# setting the appropriate set of tags that should be added to the layout.
|
||||
macro content_for(key, file = __FILE__)
|
||||
%proc = ->() {
|
||||
__kilt_io__ = IO::Memory.new
|
||||
{{ yield }}
|
||||
__kilt_io__.to_s
|
||||
}
|
||||
# `content_for` is a set of helpers that allows you to capture
|
||||
# blocks inside views to be rendered later during the request. The most
|
||||
# common use is to populate different parts of your layout from your view.
|
||||
#
|
||||
# The currently supported engines are: ecr and slang.
|
||||
#
|
||||
# ## Usage
|
||||
#
|
||||
# You call `content_for`, generally from a view, to capture a block of markup
|
||||
# giving it an identifier:
|
||||
#
|
||||
# ```
|
||||
# # index.ecr
|
||||
# <% content_for "some_key" do %>
|
||||
# <chunk of="html">...</chunk>
|
||||
# <% end %>
|
||||
# ```
|
||||
#
|
||||
# Then, you call `yield_content` with that identifier, generally from a
|
||||
# layout, to render the captured block:
|
||||
#
|
||||
# ```
|
||||
# # layout.ecr
|
||||
# <%= yield_content "some_key" %>
|
||||
# ```
|
||||
#
|
||||
# ## And How Is This Useful?
|
||||
#
|
||||
# For example, some of your views might need a few javascript tags and
|
||||
# stylesheets, but you don't want to force this files in all your pages.
|
||||
# Then you can put `<%= yield_content :scripts_and_styles %>` on your
|
||||
# layout, inside the <head> tag, and each view can call `content_for`
|
||||
# setting the appropriate set of tags that should be added to the layout.
|
||||
macro content_for(key, file = __FILE__)
|
||||
%proc = ->() {
|
||||
__kilt_io__ = IO::Memory.new
|
||||
{{ yield }}
|
||||
__kilt_io__.to_s
|
||||
}
|
||||
|
||||
CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, %proc
|
||||
nil
|
||||
end
|
||||
content_for_blocks[{{key}}] = Tuple.new {{file}}, %proc
|
||||
nil
|
||||
end
|
||||
|
||||
# Yields content for the given key if a `content_for` block exists for that key.
|
||||
macro yield_content(key)
|
||||
if CONTENT_FOR_BLOCKS.has_key?({{key}})
|
||||
__caller_filename__ = CONTENT_FOR_BLOCKS[{{key}}][0]
|
||||
%proc = CONTENT_FOR_BLOCKS[{{key}}][1]
|
||||
%proc.call if __content_filename__ == __caller_filename__
|
||||
# Yields content for the given key if a `content_for` block exists for that key.
|
||||
macro yield_content(key)
|
||||
if content_for_blocks.has_key?({{key}})
|
||||
__caller_filename__ = content_for_blocks[{{key}}][0]
|
||||
%proc = content_for_blocks[{{key}}][1]
|
||||
%proc.call if __content_filename__ == __caller_filename__
|
||||
end
|
||||
end
|
||||
|
||||
# Render view with a layout as the superview.
|
||||
# ```
|
||||
# render "src/views/index.ecr", "src/views/layout.ecr"
|
||||
# ```
|
||||
macro render(filename, layout)
|
||||
__content_filename__ = {{filename}}
|
||||
content = render {{filename}}
|
||||
render {{layout}}
|
||||
end
|
||||
|
||||
# Render view with the given filename.
|
||||
macro render(filename)
|
||||
Kilt.render({{filename}})
|
||||
end
|
||||
|
||||
# Halt execution with the current context.
|
||||
# Returns 200 and an empty response by default.
|
||||
#
|
||||
# ```
|
||||
# halt env, status_code: 403, response: "Forbidden"
|
||||
# ```
|
||||
macro halt(env, status_code = 200, response = "")
|
||||
{{env}}.response.status_code = {{status_code}}
|
||||
{{env}}.response.print {{response}}
|
||||
{{env}}.response.close
|
||||
next
|
||||
end
|
||||
|
||||
# Extends context storage with user defined types.
|
||||
#
|
||||
# ```
|
||||
# class User
|
||||
# property name
|
||||
# end
|
||||
#
|
||||
# add_context_storage_type(User)
|
||||
# ```
|
||||
macro add_context_storage_type(type)
|
||||
{{ HTTP::Server::Context::STORE_MAPPINGS.push(type) }}
|
||||
end
|
||||
end
|
||||
|
||||
# Render view with a layout as the superview.
|
||||
#
|
||||
# ```
|
||||
# render "src/views/index.ecr", "src/views/layout.ecr"
|
||||
# ```
|
||||
macro render(filename, layout)
|
||||
__content_filename__ = {{filename}}
|
||||
content = render {{filename}}
|
||||
render {{layout}}
|
||||
end
|
||||
|
||||
# Render view with the given filename.
|
||||
macro render(filename)
|
||||
Kilt.render({{filename}})
|
||||
end
|
||||
|
||||
# Halt execution with the current context.
|
||||
# Returns 200 and an empty response by default.
|
||||
#
|
||||
# ```
|
||||
# halt env, status_code: 403, response: "Forbidden"
|
||||
# ```
|
||||
macro halt(env, status_code = 200, response = "")
|
||||
{{env}}.response.status_code = {{status_code}}
|
||||
{{env}}.response.print {{response}}
|
||||
{{env}}.response.close
|
||||
next
|
||||
end
|
||||
|
||||
# Extends context storage with user defined types.
|
||||
#
|
||||
# ```
|
||||
# class User
|
||||
# property name
|
||||
# end
|
||||
#
|
||||
# add_context_storage_type(User)
|
||||
# ```
|
||||
macro add_context_storage_type(type)
|
||||
{{ HTTP::Server::Context::STORE_MAPPINGS.push(type) }}
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# This file contains the built-in view templates that Kemal uses.
|
||||
# Currently it contains templates for 404 and 500 error codes.
|
||||
|
||||
<<<<<<< HEAD
|
||||
def render_404
|
||||
<<-HTML
|
||||
<!DOCTYPE html>
|
||||
|
@ -32,4 +33,57 @@ def render_500(context, exception, verbosity)
|
|||
|
||||
context.response.print template
|
||||
context
|
||||
=======
|
||||
module Kemal::Templates
|
||||
def render_404
|
||||
template = <<-HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
||||
color:#888;margin:20px}
|
||||
img { max-width: 579px; width: 100%; }
|
||||
#c {margin:0 auto;width:500px;text-align:left}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Kemal doesn't know this way.</h2>
|
||||
<img src="/__kemal__/404.png">
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
end
|
||||
|
||||
def render_500(context, backtrace, verbosity)
|
||||
message = if verbosity
|
||||
"<pre>#{HTML.escape(backtrace)}</pre>"
|
||||
else
|
||||
"<p>Something wrong with the server :(</p>"
|
||||
end
|
||||
|
||||
template = <<-HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
||||
color:#888;margin:20px}
|
||||
#c {margin:0 auto;width:500px;text-align:left}
|
||||
pre {text-align:left;font-size:14px;color:#fff;background-color:#222;
|
||||
font-family:Operator,"Source Code Pro",Menlo,Monaco,Inconsolata,monospace;
|
||||
line-height:1.5;padding:10px;border-radius:2px;overflow:scroll}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Kemal has encountered an error. (500)</h2>
|
||||
#{message}
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
context.response.status_code = 500
|
||||
context.response.print template
|
||||
context
|
||||
end
|
||||
>>>>>>> Refactor helpers into module namespaces
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue