Skip to content

Commit c7accee

Browse files
committed
PoC: Lambda dev server
1 parent 4e1f3ad commit c7accee

10 files changed

Lines changed: 409 additions & 18 deletions

File tree

Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM alpine
2+
3+
RUN apk add opam git musl-dev make m4 gcc bubblewrap bash coreutils pkgconfig openssl-libs-static openssl-dev
4+
RUN opam init --disable-sandboxing
5+
RUN opam install -y ocaml-base-compiler dune
6+
7+
WORKDIR /app
8+
9+
COPY . .
10+
11+
RUN opam install . -y --deps-only
12+
13+
RUN opam install uuidm
14+
15+
RUN eval $(opam env) && dune build --profile=static

a.patch

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
diff --git a/bin/dev.ml b/bin/dev.ml
2+
new file mode 100644
3+
index 0000000..372b3e3
4+
--- /dev/null
5+
+++ b/bin/dev.ml
6+
@@ -0,0 +1,19 @@
7+
+open Lwt.Infix
8+
+
9+
+let handler (_ctx: Unix.sockaddr Piaf.Server.ctx) =
10+
+ let response = Piaf.Response.of_string ~body:"hello world" `OK in
11+
+ Lwt.return response
12+
+
13+
+let main port =
14+
+ let listen_address = Unix.(ADDR_INET (inet_addr_loopback, port)) in
15+
+ Lwt.async (fun () ->
16+
+ Lwt_io.establish_server_with_client_socket
17+
+ listen_address
18+
+ (Piaf.Server.create handler)
19+
+ >|= fun _server ->
20+
+ Printf.printf "Listening on http://127.0.0.1:%d\n" port);
21+
+ let forever, _ = Lwt.wait () in
22+
+ Lwt_main.run forever
23+
+
24+
+let () =
25+
+ main 9000
26+
diff --git a/bin/dune b/bin/dune
27+
new file mode 100644
28+
index 0000000..5d84dec
29+
--- /dev/null
30+
+++ b/bin/dune
31+
@@ -0,0 +1,3 @@
32+
+(executable
33+
+ (name dev)
34+
+ (libraries piaf lwt ssl lwt.unix))
35+
\ No newline at end of file

dune-project

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,4 @@
22

33
(using fmt 1.1)
44

5-
(implicit_transitive_deps false)
6-
75
(name lambda-runtime)

event.json

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{
2+
"body": "eyJ0ZXN0IjoiYm9keSJ9",
3+
"resource": "/{proxy+}",
4+
"path": "/path/to/resource",
5+
"httpMethod": "post",
6+
"isBase64Encoded": true,
7+
"queryStringParameters": {
8+
"foo": "bar"
9+
},
10+
"multiValueQueryStringParameters": {
11+
"foo": [
12+
"bar"
13+
]
14+
},
15+
"pathParameters": {
16+
"proxy": "/path/to/resource"
17+
},
18+
"stageVariables": {
19+
"baz": "qux"
20+
},
21+
"headers": {
22+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
23+
"Accept-Encoding": "gzip, deflate, sdch",
24+
"Accept-Language": "en-US,en;q=0.8",
25+
"Cache-Control": "max-age=0",
26+
"CloudFront-Forwarded-Proto": "https",
27+
"CloudFront-Is-Desktop-Viewer": "true",
28+
"CloudFront-Is-Mobile-Viewer": "false",
29+
"CloudFront-Is-SmartTV-Viewer": "false",
30+
"CloudFront-Is-Tablet-Viewer": "false",
31+
"CloudFront-Viewer-Country": "US",
32+
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
33+
"Upgrade-Insecure-Requests": "1",
34+
"User-Agent": "Custom User Agent String",
35+
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
36+
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
37+
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
38+
"X-Forwarded-Port": "443",
39+
"X-Forwarded-Proto": "https"
40+
},
41+
"multiValueHeaders": {
42+
"Accept": [
43+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
44+
],
45+
"Accept-Encoding": [
46+
"gzip, deflate, sdch"
47+
],
48+
"Accept-Language": [
49+
"en-US,en;q=0.8"
50+
],
51+
"Cache-Control": [
52+
"max-age=0"
53+
],
54+
"CloudFront-Forwarded-Proto": [
55+
"https"
56+
],
57+
"CloudFront-Is-Desktop-Viewer": [
58+
"true"
59+
],
60+
"CloudFront-Is-Mobile-Viewer": [
61+
"false"
62+
],
63+
"CloudFront-Is-SmartTV-Viewer": [
64+
"false"
65+
],
66+
"CloudFront-Is-Tablet-Viewer": [
67+
"false"
68+
],
69+
"CloudFront-Viewer-Country": [
70+
"US"
71+
],
72+
"Host": [
73+
"0123456789.execute-api.us-east-1.amazonaws.com"
74+
],
75+
"Upgrade-Insecure-Requests": [
76+
"1"
77+
],
78+
"User-Agent": [
79+
"Custom User Agent String"
80+
],
81+
"Via": [
82+
"1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)"
83+
],
84+
"X-Amz-Cf-Id": [
85+
"cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA=="
86+
],
87+
"X-Forwarded-For": [
88+
"127.0.0.1, 127.0.0.2"
89+
],
90+
"X-Forwarded-Port": [
91+
"443"
92+
],
93+
"X-Forwarded-Proto": [
94+
"https"
95+
]
96+
},
97+
"requestContext": {
98+
"accountId": "123456789012",
99+
"resourceId": "123456",
100+
"stage": "prod",
101+
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
102+
"requestTime": "09/Apr/2015:12:34:56 +0000",
103+
"requestTimeEpoch": 1428582896000,
104+
"identity": {
105+
"cognitoIdentityPoolId": null,
106+
"accountId": null,
107+
"cognitoIdentityId": null,
108+
"caller": null,
109+
"accessKey": null,
110+
"sourceIp": "127.0.0.1",
111+
"cognitoAuthenticationType": null,
112+
"cognitoAuthenticationProvider": null,
113+
"userArn": null,
114+
"userAgent": "Custom User Agent String",
115+
"user": null
116+
},
117+
"path": "/prod/path/to/resource",
118+
"resourcePath": "/{proxy+}",
119+
"httpMethod": "POST",
120+
"apiId": "1234567890",
121+
"protocol": "HTTP/1.1"
122+
}
123+
}

lambda.zip

2.38 MB
Binary file not shown.

lib/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
(preprocess
55
(pps ppx_deriving_yojson))
66
(libraries yojson ppx_deriving_yojson.runtime bigstringaf bigarray-compat
7-
uri logs.lwt lwt lwt.unix piaf result))
7+
uri logs.lwt lwt lwt.unix piaf result uuidm))

lib/http.ml

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type api_gateway_request_identity =
5050
; user_agent : string option [@key "userAgent"]
5151
; user : string option
5252
}
53-
[@@deriving of_yojson]
53+
[@@deriving yojson]
5454

5555
(* APIGatewayProxyRequestContext contains the information to identify the AWS
5656
* account and resources invoking the Lambda function. It also includes Cognito
@@ -69,7 +69,7 @@ type api_gateway_proxy_request_context =
6969
; path : string option [@default None]
7070
; api_id : string [@key "apiId"] (* The API Gateway REST API ID *)
7171
}
72-
[@@deriving of_yojson { strict = false }]
72+
[@@deriving yojson { strict = false }]
7373

7474
type api_gateway_proxy_request =
7575
{ resource : string
@@ -86,7 +86,7 @@ type api_gateway_proxy_request =
8686
; body : string option
8787
; is_base64_encoded : bool [@key "isBase64Encoded"]
8888
}
89-
[@@deriving of_yojson { strict = false }]
89+
[@@deriving yojson { strict = false }]
9090

9191
type api_gateway_proxy_response =
9292
{ status_code : int [@key "statusCode"]
@@ -97,13 +97,103 @@ type api_gateway_proxy_response =
9797
[@@deriving to_yojson]
9898

9999
module API_gateway_request = struct
100-
type t = api_gateway_proxy_request [@@deriving of_yojson]
100+
type t = api_gateway_proxy_request [@@deriving yojson]
101+
102+
let mock (ctx: 'a Piaf.Server.ctx) =
103+
let piaf_headers_to_string_map headers =
104+
headers
105+
|> Piaf.Headers.to_list
106+
|> List.to_seq
107+
|> StringMap.of_seq
108+
in
109+
let identity = { cognito_identity_pool_id = None
110+
; account_id = None
111+
; cognito_identity_id = None
112+
; caller = None
113+
; access_key = None
114+
; api_key = None
115+
(* TODO: Use the actual IP address from Unix.sockaddr *)
116+
; source_ip = "127.0.0.1"
117+
; cognito_authentication_type = None
118+
; cognito_authentication_provider = None
119+
; user_arn = None
120+
; user_agent = Piaf.Headers.get ctx.request.headers "User-Agent"
121+
; user = None }
122+
in
123+
let uri = Piaf.Request.uri ctx.request in
124+
let request_id =
125+
(* TODO: Not sure if this is equivalent to aws_request_id, but probably. Need to confirm. *)
126+
Random.self_init ();
127+
Uuidm.to_string @@ Uuidm.v4_gen (Random.get_state ()) ()
128+
in
129+
let request_context = { account_id = "123456789012"
130+
; resource_id = "123456"
131+
; stage = "dev"
132+
; request_id
133+
; identity
134+
; resource_path = "/{proxy+}"
135+
; authorizer = None
136+
; http_method = Piaf.Method.to_string ctx.request.meth
137+
; protocol = Some (Piaf.Versions.HTTP.to_string ctx.request.version)
138+
; path = Some (Uri.path uri)
139+
; api_id = "1234567890" }
140+
in
141+
let body =
142+
Lwt_result.map_err Piaf.Error.to_string (Piaf.Body.to_string ctx.request.body)
143+
in
144+
let query_string_parameters =
145+
Uri.query uri
146+
|> List.map (fun (key, values) ->
147+
match List.length values with
148+
| 0 -> (key, "")
149+
| 1 -> (key, List.hd values)
150+
(* TODO: Property handle this one *)
151+
| _ -> failwith "Multiple values not supported for query strings for now")
152+
|> List.to_seq
153+
|> StringMap.of_seq
154+
in
155+
Lwt_result.map (fun body ->
156+
(to_yojson { resource = (Uri.path uri)
157+
; path = "/{proxy+}"
158+
; http_method = request_context.http_method
159+
; headers = piaf_headers_to_string_map ctx.request.headers
160+
; query_string_parameters
161+
; path_parameters = StringMap.empty
162+
; stage_variables = StringMap.empty
163+
; request_context
164+
; body = if body = String.empty then None else Some body
165+
; is_base64_encoded = false }))
166+
body
167+
168+
let response _ =
169+
Error "Not implemented"
101170
end
102171

103172
module API_gateway_response = struct
104173
type t = api_gateway_proxy_response [@@deriving to_yojson]
105174

106175
let to_yojson t = Lwt.return (to_yojson t)
176+
177+
let mock _ =
178+
Lwt_result.fail "Not implemented"
179+
180+
let response response =
181+
let string_map_to_piaf_headers headers =
182+
headers
183+
|> StringMap.to_seq
184+
|> List.of_seq
185+
|> Piaf.Headers.of_list
186+
in
187+
(* TODO: Support base64? *)
188+
assert (not response.is_base64_encoded);
189+
190+
let response =
191+
Piaf.Response.of_string
192+
~headers:(string_map_to_piaf_headers response.headers)
193+
~body:(response.body)
194+
(Piaf.Status.of_code response.status_code)
195+
in
196+
Ok response
107197
end
108198

109199
include Runtime.Make (API_gateway_request) (API_gateway_response)

lib/json.ml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ module Id = struct
3434
type t = Yojson.Safe.t [@@deriving yojson]
3535

3636
let to_yojson t = Lwt.return (to_yojson t)
37+
38+
let mock (ctx: 'a Piaf.Server.ctx) =
39+
let event =
40+
let open Lwt_result.Infix in
41+
Piaf.Body.to_string ctx.request.body
42+
>|= (fun body -> if body = String.empty then None else Some body)
43+
>|= Option.map Yojson.Safe.from_string
44+
>|= Option.value ~default:`Null
45+
in
46+
Lwt_result.map_err Piaf.Error.to_string event
47+
48+
let response response =
49+
let body = Yojson.Safe.to_string response in
50+
let response = Piaf.Response.of_string
51+
~headers:(Piaf.Headers.of_list [ ("Content-Type", "application/json") ])
52+
~body:body
53+
`OK
54+
in
55+
Ok response
56+
3757
end
3858

3959
include Runtime.Make (Id) (Id)

0 commit comments

Comments
 (0)