forked from gsamokovarov/web-console
-
Notifications
You must be signed in to change notification settings - Fork 177
/
Copy pathmiddleware.rb
138 lines (108 loc) · 3.99 KB
/
middleware.rb
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
# frozen_string_literal: true
require "active_support/core_ext/string/strip"
require "json"
module WebConsole
class Middleware
TEMPLATES_PATH = File.expand_path("../templates", __FILE__)
cattr_accessor :mount_point, default: "/__web_console"
cattr_accessor :whiny_requests, default: true
def initialize(app)
@app = app
end
def call(env)
app_exception = catch :app_exception do
request = create_regular_or_whiny_request(env)
return call_app(env) unless request.permitted?
if id = id_for_repl_session_update(request)
return update_repl_session(id, request)
elsif id = id_for_repl_session_stack_frame_change(request)
return change_stack_trace(id, request)
end
status, headers, body = call_app(env)
if (session = Session.from(Thread.current)) && acceptable_content_type?(headers)
headers["x-web-console-session-id"] = session.id
headers["x-web-console-mount-point"] = mount_point
template = Template.new(env, session)
body, headers = Injector.new(body, headers).inject(template.render("index"))
end
[ status, headers, body ]
end
rescue => e
WebConsole.logger.error("\n#{e.class}: #{e}\n\tfrom #{e.backtrace.join("\n\tfrom ")}")
raise e
ensure
# Clean up the fiber locals after the session creation. Object#console
# uses those to communicate the current binding or exception to the middleware.
Thread.current[:__web_console_exception] = nil
Thread.current[:__web_console_binding] = nil
raise app_exception if Exception === app_exception
end
private
def acceptable_content_type?(headers)
headers["content-type"].to_s.include?("html")
end
def json_response(opts = {})
status = opts.fetch(:status, 200)
headers = { "content-type" => "application/json; charset = utf-8" }
body = yield.to_json
[ status, headers, [ body ] ]
end
def json_response_with_session(id, request, opts = {})
return respond_with_unavailable_session(id) unless session = Session.find(id)
json_response(opts) { yield session }
end
def create_regular_or_whiny_request(env)
request = Request.new(env)
whiny_requests ? WhinyRequest.new(request) : request
end
def repl_sessions_re
@_repl_sessions_re ||= %r{#{mount_point}/repl_sessions/(?<id>[^/]+)}
end
def update_re
@_update_re ||= %r{#{repl_sessions_re}\z}
end
def binding_change_re
@_binding_change_re ||= %r{#{repl_sessions_re}/trace\z}
end
def id_for_repl_session_update(request)
if request.xhr? && request.put?
update_re.match(request.path) { |m| m[:id] }
end
end
def id_for_repl_session_stack_frame_change(request)
if request.xhr? && request.post?
binding_change_re.match(request.path) { |m| m[:id] }
end
end
def update_repl_session(id, request)
json_response_with_session(id, request) do |session|
if input = request.params[:input]
{ output: session.eval(input) }
elsif input = request.params[:context]
{ context: session.context(input) }
end
end
end
def change_stack_trace(id, request)
json_response_with_session(id, request) do |session|
session.switch_binding_to(request.params[:frame_id], request.params[:exception_object_id])
{ ok: true }
end
end
def respond_with_unavailable_session(id)
json_response(status: 404) do
{ output: format(I18n.t("errors.unavailable_session"), id: id) }
end
end
def respond_with_unacceptable_request
json_response(status: 406) do
{ output: I18n.t("errors.unacceptable_request") }
end
end
def call_app(env)
@app.call(env)
rescue => e
throw :app_exception, e
end
end
end