Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support creating a lambda directly from a ring handler #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,19 @@ Add `ring/ring-core`, `ring/ring-json`,`compojure`,`lambdada` and `cheshire` dep
(with-open [writer (io/writer os)]
(let [request (parse-stream (io/reader is :encoding "UTF-8") true)]
(generate-stream (handler request) writer))))
```

Alteratively, you can use the ring-handler->lambda utility function

```clojure
;; no wrap-apigw
(def handler (wrap-json-response
(wrap-json-params
(wrap-params
(wrap-keyword-params
app)))))

(deflambda-ring-handler example.LambdaRingHandler handler)
```

It is a common to use AWS Scheduled Events to warmup the Lambda function.
Expand Down
42 changes: 2 additions & 40 deletions src/ring/middleware/apigw.clj
Original file line number Diff line number Diff line change
@@ -1,47 +1,9 @@
(ns ring.middleware.apigw
(:require [clojure.string :as string])
(:import [java.io ByteArrayInputStream]
[java.net URLEncoder]))

(defn- generate-query-string [params]
(string/join "&" (map (fn [[k v]]
(str (URLEncoder/encode (name k)) "=" (URLEncoder/encode v)))
params)))

(defn- request->http-method [request]
(-> (:httpMethod request)
(string/lower-case)
(keyword)))

(defn- apigw-request->ring-request [apigw-request]
{:pre [(every? #(contains? apigw-request %) [:httpMethod :path :queryStringParameters])
(contains? #{"GET" "POST" "OPTIONS" "DELETE" "PUT"} (:httpMethod apigw-request))]}
{:uri (:path apigw-request)
:query-string (generate-query-string (:queryStringParameters apigw-request))
:request-method (request->http-method apigw-request)
:headers (:headers apigw-request)
:body (when-let [body (:body apigw-request)] (ByteArrayInputStream. (.getBytes body "UTF-8")))})

(defn- no-scheduled-route-configured-error [request]
(throw (ex-info "Got Scheduled Event but no scheduled-event-route configured"
{:request request})))

(defn- apigw->ring-request [request scheduled-event-route]
(let [scheduled-event? (= "Scheduled Event" (:detail-type request))]
(cond
(and scheduled-event? scheduled-event-route) (apigw-request->ring-request {:path scheduled-event-route
:queryStringParameters ""
:headers nil
:httpMethod "GET"})
scheduled-event? (no-scheduled-route-configured-error request)
:else (apigw-request->ring-request request))))
(:require [ring-apigw-lambda-proxy.core :refer [apigw->ring-request ring-response->apigw]]))

(defn wrap-apigw-lambda-proxy
([handler] (wrap-apigw-lambda-proxy handler {}))
([handler {:keys [scheduled-event-route]}]
(fn [request]
(let [response (handler (apigw->ring-request request scheduled-event-route))]
{:statusCode (:status response)
:headers (:headers response)
:body (:body response)}))))

(ring-response->apigw response)))))
49 changes: 49 additions & 0 deletions src/ring_apigw_lambda_proxy/core.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
(ns ring-apigw-lambda-proxy.core
(:require [clojure.string :as string])
(:import [java.io ByteArrayInputStream]
[java.net URLEncoder]))

(defn- generate-query-string [params]
(string/join "&" (map (fn [[k v]]
(str (URLEncoder/encode (name k)) "=" (URLEncoder/encode v)))
params)))

(defn- request->http-method [request]
(-> (:httpMethod request)
(string/lower-case)
(keyword)))

(defn- apigw-request->ring-request [apigw-request]
{:pre [(every? #(contains? apigw-request %) [:httpMethod :path :queryStringParameters])
(contains? #{"GET" "POST" "OPTIONS" "DELETE" "PUT"} (:httpMethod apigw-request))]}
{:uri (:path apigw-request)
:query-string (generate-query-string (:queryStringParameters apigw-request))
:request-method (request->http-method apigw-request)
:headers (:headers apigw-request)
:body (when-let [body (:body apigw-request)] (ByteArrayInputStream. (.getBytes body "UTF-8")))})

(defn- no-scheduled-route-configured-error [request]
(throw (ex-info "Got Scheduled Event but no scheduled-event-route configured"
{:request request})))

(defn apigw->ring-request [request scheduled-event-route]
(let [scheduled-event? (= "Scheduled Event" (:detail-type request))]
(cond
(and scheduled-event? scheduled-event-route) (apigw-request->ring-request {:path scheduled-event-route
:queryStringParameters ""
:headers nil
:httpMethod "GET"})
scheduled-event? (no-scheduled-route-configured-error request)
:else (apigw-request->ring-request request))))

(defn ring-response->apigw [response]
{:statusCode (:status response)
:headers (:headers response)
:body (:body response)})

(defmacro deflambda-ring-handler [name handler & [scheduled-event-route]]
`(uswitch.lambada.core/deflambdafn ~name [is# os# ctx#]
(with-open [writer# (clojure.java.io/writer os#)]
(let [request# (cheshire.core/parse-stream (io/reader is# :encoding "UTF-8") keyword)
response# (-> request# (apigw->ring-request ~scheduled-event-route) ~handler ring-response->apigw)]
(cheshire.core/generate-stream response# writer#)))))