Skip to content

Commit 72d85fb

Browse files
committed
Document shard usage
1 parent 6907364 commit 72d85fb

1 file changed

Lines changed: 213 additions & 9 deletions

File tree

README.md

Lines changed: 213 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,234 @@
11
![Logo](logo.svg)
22

3-
TODO: Write a description here
3+
Crowbar is an AWS Lambda runtime focused on efficiency, type safety and developer friendliness. In addition to the runtime itself, this shard also includes adapters to deploy Crystal web apps as lambdas and a CLI to easily build projects to be deployed.
44

55
## Installation
66

77
1. Add the dependency to your `shard.yml`:
88

9-
```yaml
10-
dependencies:
11-
crowbar:
12-
github: gabriel-ss/crowbar
13-
```
9+
```yaml
10+
dependencies:
11+
crowbar:
12+
github: gabriel-ss/crowbar
13+
```
1414
1515
2. Run `shards install`
1616

1717
## Usage
1818

19+
### Simple example
20+
1921
```crystal
22+
require "json"
2023
require "crowbar"
24+
25+
class Event
26+
include JSON::Serializable
27+
28+
getter operand : Int32
29+
end
30+
31+
Crowbar.handle_events of_type: Event do |event, context|
32+
{result: event.operand + 1}
33+
end
34+
35+
# Could also be a Proc:
36+
# handler = ->(event : Event, context : Crowbar::Context) { {result: event.operand + 1} }
37+
# Crowbar.handle_events with: handler
38+
```
39+
40+
The included CLI can be used to build a project targeting the AWS `provided.al2023` runtime by using Docker. The `docker` command must be available to the current user.
41+
42+
```bash
43+
# Build the project in a lambda compatible environment
44+
bin/crowbar build -- --production --release
45+
46+
# Deploy bundle to aws
47+
aws lambda update-function-code --function-name func --zip-file fileb://bundle.zip
48+
```
49+
50+
The lambda can then be invoked:
51+
52+
```bash
53+
aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '{"operand": 14}' response.json
54+
cat response.json # {"result":15}
55+
```
56+
57+
### Input and output types
58+
59+
#### Input
60+
61+
The incoming type is specified by the `of_type` parameter. If `Bytes`, `String` or `IO` is given, the unparsed event is given to the block/proc:
62+
63+
```crystal
64+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '"Hello"' response
65+
Crowbar.handle_events of_type: Bytes do |event, context|
66+
puts event # => Bytes[34, 72, 101, 108, 108, 111, 34]
67+
end
68+
```
69+
70+
```crystal
71+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '"Hello"' response
72+
Crowbar.handle_events of_type: String do |event, context|
73+
puts event # => "Hello"
74+
end
75+
```
76+
77+
```crystal
78+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '"Hello"' response
79+
Crowbar.handle_events of_type: IO do |event, context|
80+
puts event.gets_to_end # => "Hello"
81+
end
2182
```
2283

23-
TODO: Write usage instructions here
84+
If any other type is specified, the runtime will try to instantiate the type by calling its `from_json` method with the input:
85+
86+
```crystal
87+
record Event, ans : Int32 { include JSON::Serializable }
88+
89+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '{"ans": 42}' response
90+
Crowbar.handle_events of_type: Event do |event, context|
91+
puts event.ans # => 42
92+
end
93+
```
94+
95+
```crystal
96+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '{"ans": 42}' response
97+
Crowbar.handle_events of_type: Hash(String, Int32) do |event, context|
98+
puts event["ans"] # => 42
99+
end
100+
```
101+
102+
```crystal
103+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '{"ans": 42}' response
104+
Crowbar.handle_events of_type: NamedTuple(ans: Int32) do |event, context|
105+
puts event[:ans] # => 42
106+
end
107+
```
108+
109+
```crystal
110+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '{"ans": 42}' response
111+
Crowbar.handle_events of_type: JSON::Any do |event, context|
112+
puts event["ans"].as_i # => 42
113+
end
114+
```
115+
116+
#### Output
117+
118+
The output type is automatically inferred from the return type of the block/proc and follows the same logic as the input one: if `Bytes`, `String` or `IO` is returned, the raw result will be written to the lambda output. Note that even when an `IO` is returned, the output is only written when the block/proc finishes its execution.
119+
120+
```crystal
121+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '"anything"' response
122+
Crowbar.handle_events of_type: JSON::Any do |event, context|
123+
"Lambda Output"
124+
end
125+
126+
# cat response # => Lambda Output
127+
```
128+
129+
For other types, the output is first serialized by calling the `to_json` instance method on the returned object.
130+
131+
```crystal
132+
class Response
133+
include JSON::Serializable
134+
135+
property ans : Int32
136+
137+
def initialize(@ans); end
138+
end
139+
140+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '"anything"' response
141+
Crowbar.handle_events of_type: JSON::Any do |event, context|
142+
Response.new(42)
143+
end
144+
145+
# cat response # => {"ans":42}
146+
```
147+
148+
```crystal
149+
# aws lambda invoke --function-name func --cli-binary-format raw-in-base64-out --payload '"anything"' response
150+
Crowbar.handle_events of_type: JSON::Any do |event, context|
151+
{ans: 42}
152+
end
153+
154+
# cat response # => {"ans":42}
155+
```
156+
157+
### Streaming Response
158+
159+
All previous examples were for lambdas in buffered mode, but Crowbar also supports [response streaming](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). To put Crowbar into response streaming mode, simply pass the `writing_to` argument of the `handle_events` method to specify which type of output should be used. Valid values are `Crowbar::ResponseIO`, that can be used for lambdas in general, and `Crowbar::HttpResponseIO`, that allows defining extra parameters to lambda invoked through lambda URLs.
160+
161+
When `writing_to` is set, a third parameter becomes available to the block/proc, which is an IO of the specified type that can be written to:
162+
163+
```crystal
164+
Crowbar.handle_events of_type: String, writing_to: Crowbar::ResponseIO do |event, context, io|
165+
io.content_type = "text/html"
166+
167+
io.puts "<h1>Title</h1>"
168+
io.flush
169+
sleep 2.seconds
170+
io.puts "<p>Content</p>"
171+
io.flush
172+
end
173+
```
174+
175+
```crystal
176+
Crowbar.handle_events of_type: String, writing_to: Crowbar::HttpResponseIO do |event, context, io|
177+
io.status_code = HTTP::Status::OK
178+
io.headers = HTTP::Headers{"Content-Type" => "text/html"}
179+
io.cookies = HTTP::Cookies{"flavor" => "chocolate"}
180+
io.cookies.not_nil!.["flavor"].expires = Time.utc(2025, 1, 1, 10, 10, 10)
181+
182+
io.puts "<h1>Title</h1>"
183+
io.flush
184+
sleep 2.seconds
185+
io.puts "<p>Content</p>"
186+
io.flush
187+
end
188+
```
189+
190+
Writes to the IO will be reflected in the output of the lambda. Note that setting any property of the response IOs after some data has already been flushed has no effect; the metadata is written together with the first data write.
191+
192+
### Web Server Adapter
193+
194+
Crowbar also has an adapter layer that allows instances of `HTTP::Handler`/`HTTP::Server` from Crystal's standard library to be deployed behind AWS integrations such as API Gateway and ALB. To handle events using an HTTP server, simply call `handle_events` passing the server as the `with` parameter and the chosen adapter as the `using` parameter:
195+
196+
```crystal
197+
require "crowbar"
198+
require "crowbar/http"
199+
200+
server = HTTP::Server.new([
201+
HTTP::ErrorHandler.new,
202+
HTTP::LogHandler.new,
203+
HTTP::CompressHandler.new,
204+
]) do |context|
205+
context.response.content_type = "application/json"
206+
context.response.headers["Target"] = "Moon"
207+
context.response.cookies["flavor"] = "chocolate"
208+
context.response.print %({"ans": 42})
209+
end
210+
211+
Crowbar.handle_events with: server, using: Crowbar::LambdaURLAdapter
212+
```
213+
214+
Note that the HTTP adapter layer must be explicitly included with `require "crowbar/http"`. The available `APIGatewayV1Adapter`, `APIGatewayV2Adapter`, `ApplicationLoadBalancerAdapter`, `LambdaURLAdapter` and
215+
`LambdaURLStreamingAdapter`. For `APIGatewayV1Adapter` and `ApplicationLoadBalancerAdapter`, multi-value header can be enabled by setting the `multi_value_headers` property of the adapter to `true`. Be sure to also make the [corresponding configuration](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers) in AWS.
216+
217+
### Building and Deploying
218+
219+
Building a Crystal project in a way to make it compatible with the AWS custom lambda runtimes poses some challenges. To streamline the process, Crowbar includes a CLI tool that leverages docker to generate a bundle, ready to be deployed to AWS. For a simple project, a production build can be generated with:
220+
221+
```bash
222+
bin/crowbar build -- --production --release
223+
```
224+
225+
which outputs a file named `bundle.zip` compatible with the Amazon Linux 2023 Custom Runtime on an x86_64 architecture. The CLI also allows cross-compilation targeting arm64, for a full list of available options, check the `--help` of the build command.
226+
227+
The first execution of Crowbar's build will create the build environment from scratch using one of the official AWS runtime images, so it can take some minutes to complete the process. Subsequent executions will use the already built environment, so they should not add more than a few seconds compared to building outside a container.
24228

25-
## Development
229+
The build command tries to be flexible enough to cover the vast majority of use cases by offering a variety of options to customize the process. Nonetheless, if for whatever reason you need full control over your build process, the CLI also has a `plan` command. It takes the same arguments of `build`, but instead of building the project, it generates a Dockerfile and a build script that perform the same process executed by Crowbar, allowing for further customization.
26230

27-
TODO: Write development instructions here
231+
The generated zip file includes, in addition to other specified assets, both the built project and necessary dependencies, so it can be directly deployed to AWS. Unlike AWS provided runtimes such as python or node, Crowbar is directly linked against the user's event handler, so the `handler` parameter configured on the lambda isn't used by the runtime. Its value can be retrieved from the `_HANDLER` environment variable, though.
28232

29233
## Contributing
30234

0 commit comments

Comments
 (0)