Module: Capsium::Reactor::Serving

Includes:
Responses
Included in:
Capsium::Reactor
Defined in:
lib/capsium/reactor/serving.rb,
sig/capsium/reactor/serving.rbs

Overview

Serving of resolved routes (static files through the merged view, datasets, handler-route 501s), mixed into Reactor. Includes route inheritance processing per 05x-routing.

Instance Method Summary collapse

Methods included from Responses

#respond_error, #respond_forbidden, #respond_json, #respond_method_not_allowed, #respond_not_found, #respond_not_implemented, #respond_text

Instance Method Details

#authorized?(identity, route, response) ⇒ Boolean

Route-level access control; false when the response was written.

Parameters:

Returns:



81
82
83
84
85
86
87
88
89
90
91
# File 'lib/capsium/reactor/serving.rb', line 81

def authorized?(identity, route, response)
  case @authenticator.authorize(identity, route.access_control)
  when :unauthenticated
    @authenticator.challenge(response)
    false
  when :forbidden
    respond_forbidden(response)
    false
  else true
  end
end

#content_type_for(mount, route, content_path) ⇒ String

The route's declared MIME type, detected from the resolved file for resources the manifest does not list (dependency content reached through inheritance, layer-only files).

Parameters:

Returns:



132
133
134
135
136
# File 'lib/capsium/reactor/serving.rb', line 132

def content_type_for(mount, route, content_path)
  route.mime(mount.package.manifest) ||
    Marcel::MimeType.for(Pathname.new(content_path),
                         name: File.basename(content_path))
end

#headers_for(route) ⇒ Hash[String, String]

Parameters:

Returns:



138
139
140
# File 'lib/capsium/reactor/serving.rb', line 138

def headers_for(route)
  route.headers || (@cache_control ? { "Cache-Control" => @cache_control } : {})
end

#inherited_processing(route, body, headers) ⇒ [String, Hash[String, String]]

Route inheritance processing (05x-routing section "Route Inheritance"): responseRewrite replaces the body and/or overrides headers; responseHeaders are merged over the served headers. requestHeaders are parsed and exposed on the route for forwarding reactors; this reactor serves statically without an upstream, so they do not alter its responses.

Parameters:

Returns:



119
120
121
122
123
124
125
126
127
# File 'lib/capsium/reactor/serving.rb', line 119

def inherited_processing(route, body, headers)
  rewrite = route.response_rewrite
  if rewrite
    body = rewrite.body unless rewrite.body.nil?
    headers = headers.merge(rewrite.headers) if rewrite.headers
  end
  headers = headers.merge(route.response_headers) if route.response_headers
  [body, headers]
end

#mounts_by_lengthArray[Mount]

Returns:



30
31
32
# File 'lib/capsium/reactor/serving.rb', line 30

def mounts_by_length
  @mounts_by_length ||= @mounts.sort_by { |mount| -mount.path.length }
end

#record_request(request, response) ⇒ Object

One request metric and one log line per served request.

Parameters:

Returns:



17
18
19
20
21
22
23
# File 'lib/capsium/reactor/serving.rb', line 17

def record_request(request, response)
  status = response.status
  return if status.nil?

  @metrics.record(status)
  @log_buffer.add("#{request.request_method} #{request.path} -> #{status}")
end

#resolve_mount(path) ⇒ Mount?

The mount answering a request path: longest matching prefix wins.

Parameters:

Returns:



26
27
28
# File 'lib/capsium/reactor/serving.rb', line 26

def resolve_mount(path)
  mounts_by_length.find { |mount| mount.matches?(path) }
end

#root_mountMount

The root ("/") mount, or the first mount when nothing is mounted at the root.

Returns:



38
39
40
# File 'lib/capsium/reactor/serving.rb', line 38

def root_mount
  @mounts.find { |mount| mount.path == Mount::ROOT_PATH } || @mounts.first
end

#serve_content_api(mount, identity, inner, request, response) ⇒ Object

Parameters:

Returns:



71
72
73
74
75
76
# File 'lib/capsium/reactor/serving.rb', line 71

def serve_content_api(mount, identity, inner, request, response)
  route = mount.routes.resolve(inner)
  return unless !route || authorized?(identity, route, response)

  mount.content_api.handle(inner, request, response)
end

#serve_data_api(mount, identity, inner, request, response) ⇒ Object

Parameters:

Returns:



63
64
65
66
67
68
69
# File 'lib/capsium/reactor/serving.rb', line 63

def serve_data_api(mount, identity, inner, request, response)
  route = mount.data_api.route_for(inner)
  return respond_not_found(response) unless route
  return unless authorized?(identity, route, response)

  mount.data_api.handle(inner, request, response)
end

#serve_dataset(mount, dataset_name, response) ⇒ Object

Parameters:

Returns:



142
143
144
145
146
147
148
149
150
151
# File 'lib/capsium/reactor/serving.rb', line 142

def serve_dataset(mount, dataset_name, response)
  dataset = mount.package.storage.dataset(dataset_name)
  if dataset
    response.status = 200
    response["Content-Type"] = "application/json"
    response.body = JSON.generate(mount.dataset_data(dataset))
  else
    respond_not_found(response)
  end
end

#serve_file(mount, route, response) ⇒ Object

Parameters:

Returns:



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/capsium/reactor/serving.rb', line 101

def serve_file(mount, route, response)
  content_path = mount.merged_view.resolve(route.resource)
  return respond_not_found(response) unless content_path

  body, headers = inherited_processing(route, File.read(content_path),
                                       headers_for(route))
  response.status = 200
  response["Content-Type"] = content_type_for(mount, route, content_path)
  headers.each { |name, value| response[name] = value }
  response.body = body
end

#serve_mounted_request(mount, identity, request, response) ⇒ Object

Serves a request that matched a mount.

Parameters:

Returns:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/capsium/reactor/serving.rb', line 46

def serve_mounted_request(mount, identity, request, response)
  inner = mount.inner_path(request.path)
  return serve_data_api(mount, identity, inner, request, response) if DataApi.path?(inner)
  if GraphqlApi.path?(inner) && mount.graphql?
    return mount.graphql_api.handle(request, response)
  end
  if ContentApi.write_method?(request.request_method)
    return serve_content_api(mount, identity, inner, request, response)
  end

  route = mount.routes.resolve(inner)
  return respond_not_found(response) unless route
  return unless authorized?(identity, route, response)

  serve_route(mount, route, response)
end

#serve_route(mount, route, response) ⇒ Object

Parameters:

Returns:



93
94
95
96
97
98
99
# File 'lib/capsium/reactor/serving.rb', line 93

def serve_route(mount, route, response)
  case route.kind
  when :dataset then serve_dataset(mount, route.dataset, response)
  when :resource then serve_file(mount, route, response)
  else respond_not_implemented(response)
  end
end