diff --git a/README.md b/README.md index c8156c948ec5f1840ea48ea6c718e012946ff79e..b638dad368f595ed7ec25287e94f498cd5ac1fd5 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ This container acts in a similar way to the API Gateway on AWS. Requests can be There are three options for routing: -* Routing is enabled through a `X-Function` header which matches a service name (function) directly. +* Functions created on the overlay network can be invoked by: http://localhost:8080/function/{servicename} * Routing automatically detects Alexa SDK requests and forwards to a service name (function) that matches the Intent name -* [todo] individual routes can be set up mapping to a specific service name (function). +* Routing is enabled through a `X-Function` header which matches a service name (function) directly. Features: diff --git a/gateway/build.sh b/gateway/build.sh index 7787c53088d0da4dfac57e87fe50f394cf097ac5..3b05335bf0ab629c37ae8f336c701303e3f69219 100755 --- a/gateway/build.sh +++ b/gateway/build.sh @@ -1,5 +1,4 @@ #!/bin/sh -echo Building catservice:latest - -docker build -t catservice . +echo Building server:latest +docker build -t server . diff --git a/gateway/server.go b/gateway/server.go index da9d4c06044a0a3acc102facb61cffa0c07b756b..6b00b139c82dd1b528c5e33b7d1079885e2c5588 100644 --- a/gateway/server.go +++ b/gateway/server.go @@ -51,6 +51,7 @@ func lookupSwarmService(serviceName string) (bool, error) { if err != nil { log.Fatal("Error with Docker client.") } + fmt.Printf("Resolving: '%s'\n", serviceName) serviceFilter := filters.NewArgs() serviceFilter.Add("name", serviceName) services, err := c.ServiceList(context.Background(), types.ServiceListOptions{Filters: serviceFilter}) @@ -95,6 +96,7 @@ func invokeService(w http.ResponseWriter, r *http.Request, metrics metrics.Metri return } + w.WriteHeader(http.StatusOK) w.Write(responseBody) seconds := time.Since(start).Seconds() fmt.Printf("[%s] took %f seconds\n", stamp, seconds) @@ -102,7 +104,22 @@ func invokeService(w http.ResponseWriter, r *http.Request, metrics metrics.Metri metrics.GatewayFunctions.Observe(seconds) } -func makeProxy(metrics metrics.MetricOptions) http.HandlerFunc { +func lookupInvoke(w http.ResponseWriter, r *http.Request, metrics metrics.MetricOptions, name string) { + exists, err := lookupSwarmService(name) + if err != nil || exists == false { + if err != nil { + log.Fatalln(err) + } + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Error resolving service.")) + } + if exists == true { + requestBody, _ := ioutil.ReadAll(r.Body) + invokeService(w, r, metrics, name, requestBody) + } +} + +func makeProxy(metrics metrics.MetricOptions, wildcard bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { metrics.GatewayRequestsTotal.Inc() @@ -110,16 +127,15 @@ func makeProxy(metrics metrics.MetricOptions) http.HandlerFunc { log.Println(r.Header) header := r.Header["X-Function"] log.Println(header) - - if len(header) > 0 { - exists, err := lookupSwarmService(header[0]) - if err != nil { - log.Fatalln(err) - } - if exists == true { - requestBody, _ := ioutil.ReadAll(r.Body) - invokeService(w, r, metrics, header[0], requestBody) - } + fmt.Println(wildcard) + + if wildcard == true { + vars := mux.Vars(r) + name := vars["name"] + fmt.Println("invoke by name") + lookupInvoke(w, r, metrics, name) + } else if len(header) > 0 { + lookupInvoke(w, r, metrics, header[0]) } else { requestBody, _ := ioutil.ReadAll(r.Body) alexaService := isAlexa(requestBody) @@ -160,12 +176,16 @@ func main() { prometheus.Register(GatewayServerlessServedTotal) prometheus.Register(GatewayFunctions) - r := mux.NewRouter() - r.HandleFunc("/", makeProxy(metrics.MetricOptions{ + metricsOptions := metrics.MetricOptions{ GatewayRequestsTotal: GatewayRequestsTotal, GatewayServerlessServedTotal: GatewayServerlessServedTotal, GatewayFunctions: GatewayFunctions, - })) + } + + r := mux.NewRouter() + r.HandleFunc("/", makeProxy(metricsOptions, false)) + + r.HandleFunc("/function/{name:[a-zA-Z]+}", makeProxy(metricsOptions, true)) metricsHandler := metrics.PrometheusHandler() r.Handle("/metrics", metricsHandler) diff --git a/sample-functions/WebhookStash/Dockerfile b/sample-functions/WebhookStash/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fda40f3e4b20c39e0675fc7d0576d8d4aa1b8957 --- /dev/null +++ b/sample-functions/WebhookStash/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.7.3 +RUN mkdir -p /go/src/app +COPY handler.go /go/src/app +WORKDIR /go/src/app +RUN go get -d -v +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . + +COPY fwatchdog /usr/bin/ + +ENV fprocess="/go/src/app/app" +CMD ["fwatchdog"] diff --git a/sample-functions/WebhookStash/README.md b/sample-functions/WebhookStash/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2d26b5b6a851386cc336edc33b491ab61e036ae8 --- /dev/null +++ b/sample-functions/WebhookStash/README.md @@ -0,0 +1,18 @@ +WebhookStash +============ + +Example serverless function shows how to stash way contents of webhooks called via API gateway. + +Each file is saved with the UNIX timestamp in nano seconds plus an extension of .txt + +Example: + +``` +# curl -X POST -v -d @$HOME/.ssh/id_rsa.pub localhost:8080/function/webhookstash +``` + +Then if you find the replica you can check the disk: + +``` +# docker exec webhookstash.1.z054csrh70tgk9s5k4bb8uefq find +``` diff --git a/sample-functions/WebhookStash/handler.go b/sample-functions/WebhookStash/handler.go new file mode 100644 index 0000000000000000000000000000000000000000..5e41b6b14c7ce1e35cc970c1b42eb02a3bc0d4c0 --- /dev/null +++ b/sample-functions/WebhookStash/handler.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + "time" +) + +func main() { + input, _ := ioutil.ReadAll(os.Stdin) + fmt.Println("Stashing request") + now := time.Now() + stamp := strconv.FormatInt(now.UnixNano(), 10) + + ioutil.WriteFile(stamp+".txt", input, 0644) +}