serverless-rack 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +12 -0
- data/LICENSE +21 -0
- data/README.md +392 -0
- data/lib/serverless_rack.rb +212 -0
- data/serverless-rack.gemspec +26 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0e289b85392f4e213d35b038b1fdb7799465808977279b3c851d15e3f38a31cf
|
4
|
+
data.tar.gz: 5cfa5111685c7d22109325c0bbb6539aadcae750c7ea2407cd62285619a73544
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9c906ef876675f6df4bae686f50623bdb31769122e8ce36b82110c9eb92baab10c9a48308bfa7f9c2a8a7b3e27b2a4b531e95618f6898002a496666c7d9a98c6
|
7
|
+
data.tar.gz: 88c461157b7449c9fd4e974767d452e10059b87805916aed60392cd421b7745865d1dec7e23e0a1c645dd03b190a976ac09a2d913fdb29bc156c9c05ed1c5301
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Logan Raarup
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,392 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<img src="https://logandk.github.io/serverless-rack/assets/header.svg">
|
3
|
+
</p>
|
4
|
+
|
5
|
+
[](https://nodei.co/npm/serverless-rack/)
|
6
|
+
|
7
|
+
[](http://www.serverless.com)
|
8
|
+
[](https://travis-ci.org/logandk/serverless-rack)
|
9
|
+
[](https://codecov.io/gh/logandk/serverless-rack)
|
10
|
+
[](https://david-dm.org/logandk/serverless-rack)
|
11
|
+
[](https://david-dm.org/logandk/serverless-rack?type=dev)
|
12
|
+
|
13
|
+
A Serverless v1.x plugin to build your deploy Ruby Rack applications using Serverless. Compatible
|
14
|
+
Rack application frameworks include Sinatra, Cuba and Padrino.
|
15
|
+
|
16
|
+
### Features
|
17
|
+
|
18
|
+
- Transparently converts API Gateway and ALB requests to and from standard Rack requests
|
19
|
+
- Supports anything you'd expect from Rack such as redirects, cookies, file uploads etc.
|
20
|
+
- Bundler integration, including dockerized bundling of binary dependencies
|
21
|
+
- Convenient `rack serve` command for serving your application locally during development
|
22
|
+
- CLI commands for remote execution of Ruby code (`rack exec`), rake tasks ('rack rake') and shell commands (`rack command`)
|
23
|
+
|
24
|
+
## Install
|
25
|
+
|
26
|
+
```
|
27
|
+
sls plugin install -n serverless-rack
|
28
|
+
```
|
29
|
+
|
30
|
+
This will automatically add the plugin to `package.json` and the plugins section of `serverless.yml`.
|
31
|
+
|
32
|
+
## Sinatra configuration example
|
33
|
+
|
34
|
+
```
|
35
|
+
project
|
36
|
+
├── api.rb
|
37
|
+
├── config.ru
|
38
|
+
├── Gemfile
|
39
|
+
└── serverless.yml
|
40
|
+
```
|
41
|
+
|
42
|
+
### api.rb
|
43
|
+
|
44
|
+
A regular Sinatra application.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require 'sinatra'
|
48
|
+
|
49
|
+
get '/cats' do
|
50
|
+
'Cats'
|
51
|
+
end
|
52
|
+
|
53
|
+
get '/dogs/:id' do
|
54
|
+
'Dog'
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
### config.ru
|
59
|
+
|
60
|
+
```
|
61
|
+
require './api'
|
62
|
+
run Sinatra::Application
|
63
|
+
```
|
64
|
+
|
65
|
+
### serverless.yml
|
66
|
+
|
67
|
+
All functions that will use Rack need to have `rack_adapter.handler` set as the Lambda handler and
|
68
|
+
use the default `lambda-proxy` integration for API Gateway. This configuration example treats
|
69
|
+
API Gateway as a transparent proxy, passing all requests directly to your Sinatra application,
|
70
|
+
and letting the application handle errors, 404s etc.
|
71
|
+
|
72
|
+
```yaml
|
73
|
+
service: example
|
74
|
+
|
75
|
+
provider:
|
76
|
+
name: aws
|
77
|
+
runtime: ruby2.5
|
78
|
+
|
79
|
+
plugins:
|
80
|
+
- serverless-rack
|
81
|
+
|
82
|
+
functions:
|
83
|
+
api:
|
84
|
+
handler: rack_adapter.handler
|
85
|
+
events:
|
86
|
+
- http: ANY /
|
87
|
+
- http: ANY {proxy+}
|
88
|
+
```
|
89
|
+
|
90
|
+
### Gemfile
|
91
|
+
|
92
|
+
Add Sinatra to the application bundle.
|
93
|
+
|
94
|
+
```
|
95
|
+
source 'https://rubygems.org'
|
96
|
+
|
97
|
+
gem 'sinatra'
|
98
|
+
```
|
99
|
+
|
100
|
+
## Deployment
|
101
|
+
|
102
|
+
Simply run the serverless deploy command as usual:
|
103
|
+
|
104
|
+
```
|
105
|
+
$ bundle install --path vendor/bundle
|
106
|
+
$ sls deploy
|
107
|
+
Serverless: Packaging Ruby Rack handler...
|
108
|
+
Serverless: Packaging gem dependencies using docker...
|
109
|
+
Serverless: Packaging service...
|
110
|
+
Serverless: Excluding development dependencies...
|
111
|
+
Serverless: Uploading CloudFormation file to S3...
|
112
|
+
Serverless: Uploading artifacts...
|
113
|
+
Serverless: Uploading service .zip file to S3 (1.64 MB)...
|
114
|
+
Serverless: Validating template...
|
115
|
+
Serverless: Updating Stack...
|
116
|
+
Serverless: Checking Stack update progress...
|
117
|
+
..............
|
118
|
+
Serverless: Stack update finished...
|
119
|
+
```
|
120
|
+
|
121
|
+
## Usage
|
122
|
+
|
123
|
+
### Automatic bundling of gems
|
124
|
+
|
125
|
+
You'll need to include any gems that your application uses in the bundle
|
126
|
+
that's deployed to AWS Lambda. This plugin helps you out by doing this automatically,
|
127
|
+
as long as you specify your required gems in a [Gemfile](https://bundler.io/gemfile.html):
|
128
|
+
|
129
|
+
```
|
130
|
+
source 'https://rubygems.org'
|
131
|
+
|
132
|
+
gem 'rake'
|
133
|
+
gem 'sinatra'
|
134
|
+
```
|
135
|
+
|
136
|
+
For more information, see https://bundler.io/docs.html.
|
137
|
+
|
138
|
+
### Dockerized bundling
|
139
|
+
|
140
|
+
If your application depends on any gems that include compiled binaries, these
|
141
|
+
must be compiled for the lambda execution environment. Enabling the `dockerizeBundler` configuration
|
142
|
+
option will fetch and build the gems using a [docker image](https://hub.docker.com/r/logandk/serverless-rack-bundler)
|
143
|
+
that emulates the lambda environment:
|
144
|
+
|
145
|
+
```yaml
|
146
|
+
custom:
|
147
|
+
rack:
|
148
|
+
dockerizeBundler: false
|
149
|
+
```
|
150
|
+
|
151
|
+
### Bundler configuration
|
152
|
+
|
153
|
+
You can use the automatic bundling functionality of _serverless-rack_ without the Rack request
|
154
|
+
handler itself by including the plugin in your `serverless.yml` configuration, without specifying
|
155
|
+
`rack_adapter.handler` as the handler for any of your lambda functions.
|
156
|
+
This will omit the Rack handler from the package, but include any gems specified in the `Gemfile`.
|
157
|
+
|
158
|
+
If you don't want to use automatic gem bundling you can set `custom.rack.enableBundler` to `false`:
|
159
|
+
|
160
|
+
```yaml
|
161
|
+
custom:
|
162
|
+
rack:
|
163
|
+
enableBundler: false
|
164
|
+
```
|
165
|
+
|
166
|
+
In order to pass additional arguments to `bundler` when installing requirements, the `bundlerArgs`
|
167
|
+
configuration option is available:
|
168
|
+
|
169
|
+
```yaml
|
170
|
+
custom:
|
171
|
+
rack:
|
172
|
+
bundlerArgs: --no-cache
|
173
|
+
```
|
174
|
+
|
175
|
+
If your `bundler` executable is not in `$PATH`, set the path explicitly using the `bundlerBin`
|
176
|
+
configuration option:
|
177
|
+
|
178
|
+
```yaml
|
179
|
+
custom:
|
180
|
+
rack:
|
181
|
+
bundlerBin: /path/to/bundler
|
182
|
+
```
|
183
|
+
|
184
|
+
### Local server
|
185
|
+
|
186
|
+
For convenience, a `sls rack serve` command is provided to run your Rack application
|
187
|
+
locally. This command requires the `rack` gem to be installed, and acts as a simple
|
188
|
+
wrapper for `rackup`.
|
189
|
+
|
190
|
+
By default, the server will start on port 5000.
|
191
|
+
|
192
|
+
```
|
193
|
+
$ sls rack serve
|
194
|
+
[2019-01-03 18:13:21] INFO WEBrick 1.4.2
|
195
|
+
[2019-01-03 18:13:21] INFO ruby 2.5.1 (2018-03-29) [x86_64-linux-gnu]
|
196
|
+
[2019-01-03 18:13:21] INFO WEBrick::HTTPServer#start: pid=25678 port=5000
|
197
|
+
```
|
198
|
+
|
199
|
+
Configure the port using the `-p` parameter:
|
200
|
+
|
201
|
+
```
|
202
|
+
$ sls rack serve -p 8000
|
203
|
+
[2019-01-03 18:13:21] INFO WEBrick 1.4.2
|
204
|
+
[2019-01-03 18:13:21] INFO ruby 2.5.1 (2018-03-29) [x86_64-linux-gnu]
|
205
|
+
[2019-01-03 18:13:21] INFO WEBrick::HTTPServer#start: pid=25678 port=8000
|
206
|
+
```
|
207
|
+
|
208
|
+
When running locally, an environment variable named `IS_OFFLINE` will be set to `True`.
|
209
|
+
So, if you want to know when the application is running locally, check `ENV["IS_OFFLINE"]`.
|
210
|
+
|
211
|
+
### Remote command execution
|
212
|
+
|
213
|
+
The `rack exec` command lets you execute ruby code remotely:
|
214
|
+
|
215
|
+
```
|
216
|
+
$ sls rack exec -c "puts (1 + Math.sqrt(5)) / 2"
|
217
|
+
1.618033988749895
|
218
|
+
|
219
|
+
$ cat count.rb
|
220
|
+
3.times do |i|
|
221
|
+
puts i
|
222
|
+
end
|
223
|
+
|
224
|
+
$ sls rack exec -f count.rb
|
225
|
+
0
|
226
|
+
1
|
227
|
+
2
|
228
|
+
```
|
229
|
+
|
230
|
+
The `rack command` command lets you execute shell commands remotely:
|
231
|
+
|
232
|
+
```
|
233
|
+
$ sls rack command -c "pwd"
|
234
|
+
/var/task
|
235
|
+
|
236
|
+
$ cat script.sh
|
237
|
+
#!/bin/bash
|
238
|
+
echo "dlrow olleh" | rev
|
239
|
+
|
240
|
+
$ sls rack command -f script.sh
|
241
|
+
hello world
|
242
|
+
```
|
243
|
+
|
244
|
+
The `rack rake` command lets you execute Rake tasks remotely:
|
245
|
+
|
246
|
+
```
|
247
|
+
$ sls rack rake -t "db:rollback STEP=3"
|
248
|
+
```
|
249
|
+
|
250
|
+
### Explicit routes
|
251
|
+
|
252
|
+
If you'd like to be explicit about which routes and HTTP methods should pass through to your
|
253
|
+
application, see the following example:
|
254
|
+
|
255
|
+
```yaml
|
256
|
+
service: example
|
257
|
+
|
258
|
+
provider:
|
259
|
+
name: aws
|
260
|
+
runtime: ruby2.5
|
261
|
+
|
262
|
+
plugins:
|
263
|
+
- serverless-rack
|
264
|
+
|
265
|
+
functions:
|
266
|
+
api:
|
267
|
+
handler: rack_adapter.handler
|
268
|
+
events:
|
269
|
+
- http:
|
270
|
+
path: cats
|
271
|
+
method: get
|
272
|
+
integration: lambda-proxy
|
273
|
+
- http:
|
274
|
+
path: dogs/{id}
|
275
|
+
method: get
|
276
|
+
integration: lambda-proxy
|
277
|
+
```
|
278
|
+
|
279
|
+
### Custom domain names
|
280
|
+
|
281
|
+
If you use custom domain names with API Gateway, you might have a base path that is
|
282
|
+
at the beginning of your path, such as the stage (`/dev`, `/stage`, `/prod`). In this case, set
|
283
|
+
the `API_GATEWAY_BASE_PATH` environment variable to let `serverless-rack` know.
|
284
|
+
|
285
|
+
The example below uses the [serverless-domain-manager](https://github.com/amplify-education/serverless-domain-manager)
|
286
|
+
plugin to handle custom domains in API Gateway:
|
287
|
+
|
288
|
+
```yaml
|
289
|
+
service: example
|
290
|
+
|
291
|
+
provider:
|
292
|
+
name: aws
|
293
|
+
runtime: ruby2.5
|
294
|
+
environment:
|
295
|
+
API_GATEWAY_BASE_PATH: ${self:custom.customDomain.basePath}
|
296
|
+
|
297
|
+
plugins:
|
298
|
+
- serverless-rack
|
299
|
+
- serverless-domain-manager
|
300
|
+
|
301
|
+
functions:
|
302
|
+
api:
|
303
|
+
handler: rack_adapter.handler
|
304
|
+
events:
|
305
|
+
- http: ANY /
|
306
|
+
- http: ANY {proxy+}
|
307
|
+
|
308
|
+
custom:
|
309
|
+
customDomain:
|
310
|
+
basePath: ${opt:stage}
|
311
|
+
domainName: mydomain.name.com
|
312
|
+
stage: ${opt:stage}
|
313
|
+
createRoute53Record: true
|
314
|
+
```
|
315
|
+
|
316
|
+
### File uploads
|
317
|
+
|
318
|
+
In order to accept file uploads from HTML forms, make sure to add `multipart/form-data` to
|
319
|
+
the list of content types with _Binary Support_ in your API Gateway API. The
|
320
|
+
[serverless-apigw-binary](https://github.com/maciejtreder/serverless-apigw-binary)
|
321
|
+
Serverless plugin can be used to automate this process.
|
322
|
+
|
323
|
+
Keep in mind that, when building Serverless applications, uploading
|
324
|
+
[directly to S3](http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html)
|
325
|
+
from the browser is usually the preferred approach.
|
326
|
+
|
327
|
+
### Raw context and event
|
328
|
+
|
329
|
+
The raw context and event from AWS Lambda are both accessible through the Rack
|
330
|
+
request. The following example shows how to access them when using Flask:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
require 'sinatra'
|
334
|
+
|
335
|
+
get '/' do
|
336
|
+
puts request.env['serverless.event']
|
337
|
+
puts request.env['serverless.context']
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
341
|
+
### Text MIME types
|
342
|
+
|
343
|
+
By default, all MIME types starting with `text/` and the following whitelist are sent
|
344
|
+
through API Gateway in plain text. All other MIME types will have their response body
|
345
|
+
base64 encoded (and the `isBase64Encoded` API Gateway flag set) in order to be
|
346
|
+
delivered by API Gateway as binary data (remember to add any binary MIME types that
|
347
|
+
you're using to the _Binary Support_ list in API Gateway).
|
348
|
+
|
349
|
+
This is the default whitelist of plain text MIME types:
|
350
|
+
|
351
|
+
- `application/json`
|
352
|
+
- `application/javascript`
|
353
|
+
- `application/xml`
|
354
|
+
- `application/vnd.api+json`
|
355
|
+
- `image/svg+xml`
|
356
|
+
|
357
|
+
In order to add additional plain text MIME types to this whitelist, use the
|
358
|
+
`textMimeTypes` configuration option:
|
359
|
+
|
360
|
+
```yaml
|
361
|
+
custom:
|
362
|
+
rack:
|
363
|
+
textMimeTypes:
|
364
|
+
- application/custom+json
|
365
|
+
- application/vnd.company+json
|
366
|
+
```
|
367
|
+
|
368
|
+
## Usage without Serverless
|
369
|
+
|
370
|
+
The AWS API Gateway to Rack mapping module is available as a gem.
|
371
|
+
|
372
|
+
Use this gem if you need to deploy Ruby functions to handle
|
373
|
+
API Gateway events directly, without using the Serverless framework.
|
374
|
+
|
375
|
+
```
|
376
|
+
gem install --install-dir vendor/bundle serverless-rack
|
377
|
+
```
|
378
|
+
|
379
|
+
Initialize your Rack application and in your Lambda event handler, call
|
380
|
+
the request mapper:
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
require 'serverless_rack'
|
384
|
+
|
385
|
+
$app ||= Proc.new do |env|
|
386
|
+
['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]
|
387
|
+
end
|
388
|
+
|
389
|
+
def handler(event:, context:)
|
390
|
+
handle_request(app: $app, event: event, context: context)
|
391
|
+
end
|
392
|
+
```
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module handles conversion between API Gateway/ALB and Rack requests/responses.
|
4
|
+
#
|
5
|
+
# Author: Logan Raarup <logan@logan.dk>
|
6
|
+
|
7
|
+
require 'rack'
|
8
|
+
require 'base64'
|
9
|
+
|
10
|
+
TEXT_MIME_TYPES = [
|
11
|
+
'application/json',
|
12
|
+
'application/javascript',
|
13
|
+
'application/xml',
|
14
|
+
'application/vnd.api+json',
|
15
|
+
'image/svg+xml'
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def keepalive_event?(event)
|
19
|
+
['aws.events', 'serverless-plugin-warmup'].include?(event['source'])
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_script_name(event, headers)
|
23
|
+
if ENV['API_GATEWAY_BASE_PATH']
|
24
|
+
"/#{ENV['API_GATEWAY_BASE_PATH']}"
|
25
|
+
elsif (headers['Host'] || '').include?('amazonaws.com')
|
26
|
+
"/#{event['requestContext']['stage']}"
|
27
|
+
else
|
28
|
+
''
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_path_info(event)
|
33
|
+
# If a user is using a custom domain on API Gateway, they may have a base
|
34
|
+
# path in their URL. This allows us to strip it out via an optional
|
35
|
+
# environment variable.
|
36
|
+
if ENV['API_GATEWAY_BASE_PATH']
|
37
|
+
base_path = "/#{ENV['API_GATEWAY_BASE_PATH']}"
|
38
|
+
return event['path'][base_path.length..-1] if event['path'].start_with?(base_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
event['path']
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_body(event)
|
45
|
+
if event['isBase64Encoded']
|
46
|
+
Base64.decode64(event['body'])
|
47
|
+
else
|
48
|
+
event['body'] || ''
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_http_headers(headers)
|
53
|
+
headers = headers.map do |key, value|
|
54
|
+
["HTTP_#{key.upcase.tr('-', '_')}", value]
|
55
|
+
end
|
56
|
+
|
57
|
+
headers = headers.reject do |key, _value|
|
58
|
+
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].include?(key)
|
59
|
+
end
|
60
|
+
|
61
|
+
headers.to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_environ(event:, context:, headers:, body:)
|
65
|
+
{
|
66
|
+
'REQUEST_METHOD' => event['httpMethod'],
|
67
|
+
'SCRIPT_NAME' => parse_script_name(event, headers),
|
68
|
+
'PATH_INFO' => parse_path_info(event),
|
69
|
+
'QUERY_STRING' => Rack::Utils.build_query(event['queryStringParameters'] || {}),
|
70
|
+
'SERVER_NAME' => headers['Host'] || 'lambda',
|
71
|
+
'SERVER_PORT' => headers['X-Forwarded-Port'] || '80',
|
72
|
+
'CONTENT_LENGTH' => body.bytesize.to_s,
|
73
|
+
'CONTENT_TYPE' => headers['Content-Type'] || '',
|
74
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
75
|
+
'REMOTE_ADDR' => (event['requestContext']['identity'] || {})['sourceIp'] || '',
|
76
|
+
'REMOTE_USER' => (event['requestContext']['authorizer'] || {})['principalId'] || '',
|
77
|
+
'rack.version' => Rack::VERSION,
|
78
|
+
'rack.url_scheme' => headers['X-Forwarded-Proto'] || 'http',
|
79
|
+
'rack.input' => StringIO.new(body),
|
80
|
+
'rack.errors' => $stderr,
|
81
|
+
'rack.multithread' => false,
|
82
|
+
'rack.multiprocess' => false,
|
83
|
+
'rack.run_once' => false,
|
84
|
+
'serverless.event' => event,
|
85
|
+
'serverless.context' => context,
|
86
|
+
'serverless.authorizer' => event['requestContext']['authorizer']
|
87
|
+
}.merge(parse_http_headers(headers))
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_status_description(event:, status:)
|
91
|
+
return {} unless event['requestContext']['elb']
|
92
|
+
|
93
|
+
# If the request comes from ALB we need to add a status description
|
94
|
+
description = Rack::Utils::HTTP_STATUS_CODES[status]
|
95
|
+
|
96
|
+
{ 'statusDescription' => "#{status} #{description}" }
|
97
|
+
end
|
98
|
+
|
99
|
+
def text_mime_type?(headers:, text_mime_types:)
|
100
|
+
mime_type = headers['Content-Type'] || 'text/plain'
|
101
|
+
|
102
|
+
return false if headers['Content-Encoding']
|
103
|
+
return true if mime_type.start_with?('text/')
|
104
|
+
return true if text_mime_types.include?(mime_type)
|
105
|
+
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
def format_body(body:, headers:, text_mime_types:)
|
110
|
+
response_data = ''
|
111
|
+
body.each { |part| response_data += part }
|
112
|
+
|
113
|
+
return {} if response_data.empty?
|
114
|
+
|
115
|
+
if text_mime_type?(headers: headers, text_mime_types: text_mime_types)
|
116
|
+
{
|
117
|
+
'body' => response_data,
|
118
|
+
'isBase64Encoded' => false
|
119
|
+
}
|
120
|
+
else
|
121
|
+
{
|
122
|
+
'body' => Base64.strict_encode64(response_data),
|
123
|
+
'isBase64Encoded' => true
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def all_casings(input_string)
|
129
|
+
# Permute all casings of a given string.
|
130
|
+
# A pretty algoritm, via @Amber
|
131
|
+
# http://stackoverflow.com/questions/6792803/finding-all-possible-case-permutations-in-python
|
132
|
+
if input_string.empty?
|
133
|
+
yield ''
|
134
|
+
else
|
135
|
+
first = input_string[0]
|
136
|
+
if first.downcase == first.upcase
|
137
|
+
all_casings(input_string[1..-1]) do |sub_casing|
|
138
|
+
yield first + sub_casing
|
139
|
+
end
|
140
|
+
else
|
141
|
+
all_casings(input_string[1..-1]) do |sub_casing|
|
142
|
+
yield first.downcase + sub_casing
|
143
|
+
yield first.upcase + sub_casing
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def format_headers(headers:)
|
150
|
+
headers = headers.to_hash
|
151
|
+
|
152
|
+
# If there are headers multiple occurrences, e.g. Set-Cookie, create
|
153
|
+
# case-mutated variations in order to pass them through APIGW.
|
154
|
+
# This is a hack that's currently needed.
|
155
|
+
headers.keys.each do |key|
|
156
|
+
values = headers[key].split("\n")
|
157
|
+
|
158
|
+
next if values.size < 2
|
159
|
+
|
160
|
+
headers.delete(key)
|
161
|
+
|
162
|
+
all_casings(key) do |casing|
|
163
|
+
headers[casing] = values.shift
|
164
|
+
break if values.empty?
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
{ 'headers' => headers }
|
169
|
+
end
|
170
|
+
|
171
|
+
def format_response(event:, status:, headers:, body:, text_mime_types:)
|
172
|
+
response = { 'statusCode' => status }
|
173
|
+
|
174
|
+
response.merge!(
|
175
|
+
format_headers(headers: headers)
|
176
|
+
)
|
177
|
+
|
178
|
+
response.merge!(
|
179
|
+
format_status_description(event: event, status: status)
|
180
|
+
)
|
181
|
+
|
182
|
+
response.merge!(
|
183
|
+
format_body(
|
184
|
+
body: body,
|
185
|
+
headers: headers,
|
186
|
+
text_mime_types: text_mime_types
|
187
|
+
)
|
188
|
+
)
|
189
|
+
|
190
|
+
response
|
191
|
+
end
|
192
|
+
|
193
|
+
def handle_request(app:, event:, context:, config: {})
|
194
|
+
return {} if keepalive_event?(event)
|
195
|
+
|
196
|
+
status, headers, body = app.call(
|
197
|
+
build_environ(
|
198
|
+
event: event,
|
199
|
+
context: context,
|
200
|
+
headers: Rack::Utils::HeaderHash.new(event['headers'] || {}),
|
201
|
+
body: parse_body(event)
|
202
|
+
)
|
203
|
+
)
|
204
|
+
|
205
|
+
format_response(
|
206
|
+
event: event,
|
207
|
+
status: status,
|
208
|
+
headers: headers,
|
209
|
+
body: body,
|
210
|
+
text_mime_types: TEXT_MIME_TYPES + config['text_mime_types'].to_a
|
211
|
+
)
|
212
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'serverless-rack'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.summary =
|
5
|
+
'Serverless plugin to deploy Ruby Rack applications (Sinatra/Padrino/Cuba etc.) '\
|
6
|
+
'and bundle gems'
|
7
|
+
s.description =
|
8
|
+
'A Serverless v1.x plugin to build your deploy Ruby Rack applications using '\
|
9
|
+
'Serverless. Compatible Rack application frameworks include Sinatra, '\
|
10
|
+
'Cuba and Padrino.'
|
11
|
+
s.authors = ['Logan Raarup']
|
12
|
+
s.email = 'logan@logan.dk'
|
13
|
+
s.files = [
|
14
|
+
'lib/serverless_rack.rb',
|
15
|
+
'CHANGELOG.md',
|
16
|
+
'Gemfile',
|
17
|
+
'LICENSE',
|
18
|
+
'README.md',
|
19
|
+
'serverless-rack.gemspec'
|
20
|
+
]
|
21
|
+
s.homepage = 'https://github.com/logandk/serverless-rack'
|
22
|
+
s.license = 'MIT'
|
23
|
+
|
24
|
+
s.required_ruby_version = '>= 2.2.0'
|
25
|
+
s.add_dependency 'rack', '~> 2.0'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: serverless-rack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Logan Raarup
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
description: A Serverless v1.x plugin to build your deploy Ruby Rack applications
|
28
|
+
using Serverless. Compatible Rack application frameworks include Sinatra, Cuba and
|
29
|
+
Padrino.
|
30
|
+
email: logan@logan.dk
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- CHANGELOG.md
|
36
|
+
- Gemfile
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- lib/serverless_rack.rb
|
40
|
+
- serverless-rack.gemspec
|
41
|
+
homepage: https://github.com/logandk/serverless-rack
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.2.0
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.7.6
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Serverless plugin to deploy Ruby Rack applications (Sinatra/Padrino/Cuba
|
65
|
+
etc.) and bundle gems
|
66
|
+
test_files: []
|