mirror of
https://github.com/openfaas/faas.git
synced 2025-06-08 16:26:47 +00:00
Enable routing to Alexa functions
Add first Alexa-compatible function HostnameIntent
This commit is contained in:
parent
2b9a1c10e4
commit
9656b530c4
1
faas
Submodule
1
faas
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit d94cfeb660705028b6c101412a03519a8164712d
|
1
gateway/.gitignore
vendored
1
gateway/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
gateway
|
gateway
|
||||||
|
sample.txt
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from golang:1.7.3
|
from golang:1.7.3
|
||||||
|
|
||||||
RUN go get -d github.com/docker/docker/api/types \
|
RUN go get -d github.com/docker/docker/api/types \
|
||||||
&& go get -d github.com/docker/docker/api/types/filters \
|
&& go get -d github.com/docker/docker/api/types/filters \
|
||||||
&& go get -d github.com/docker/docker/api/types/swarm \
|
&& go get -d github.com/docker/docker/api/types/swarm \
|
||||||
&& go get -d github.com/docker/docker/client
|
&& go get -d github.com/docker/docker/client \
|
||||||
RUN go get github.com/gorilla/mux \
|
&& go get github.com/gorilla/mux \
|
||||||
&& go get github.com/prometheus/client_golang/prometheus
|
&& go get github.com/prometheus/client_golang/prometheus
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/alexellis/faas/gateway
|
WORKDIR /go/src/github.com/alexellis/faas/gateway
|
||||||
|
|
||||||
|
@ -3,13 +3,16 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"strconv"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/alexellis/faas/gateway/metrics"
|
"github.com/alexellis/faas/gateway/metrics"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@ -19,6 +22,28 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AlexaSessionApplication struct {
|
||||||
|
ApplicationId string `json:"applicationId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlexaSession struct {
|
||||||
|
SessionId string `json:"sessionId"`
|
||||||
|
Application AlexaSessionApplication `json:"application"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlexaIntent struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlexaRequest struct {
|
||||||
|
Intent AlexaIntent `json:"intent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlexaRequestBody struct {
|
||||||
|
Session AlexaSession `json:"session"`
|
||||||
|
Request AlexaRequest `json:"request"`
|
||||||
|
}
|
||||||
|
|
||||||
func lookupSwarmService(serviceName string) (bool, error) {
|
func lookupSwarmService(serviceName string) (bool, error) {
|
||||||
var c *client.Client
|
var c *client.Client
|
||||||
var err error
|
var err error
|
||||||
@ -33,38 +58,69 @@ func lookupSwarmService(serviceName string) (bool, error) {
|
|||||||
return len(services) > 0, err
|
return len(services) > 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAlexa(requestBody []byte) AlexaRequestBody {
|
||||||
|
body := AlexaRequestBody{}
|
||||||
|
buf := bytes.NewBuffer(requestBody)
|
||||||
|
fmt.Println(buf)
|
||||||
|
str := buf.String()
|
||||||
|
parts := strings.Split(str, "sessionId")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
json.Unmarshal(requestBody, &body)
|
||||||
|
fmt.Println("Alexa SDK request found")
|
||||||
|
fmt.Printf("Session=%s, Intent=%s, App=%s\n", body.Session.SessionId, body.Request.Intent, body.Session.Application.ApplicationId)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeService(w http.ResponseWriter, r *http.Request, metrics metrics.MetricOptions, service string, requestBody []byte) {
|
||||||
|
start := time.Now()
|
||||||
|
buf := bytes.NewBuffer(requestBody)
|
||||||
|
url := "http://" + service + ":" + strconv.Itoa(8080) + "/"
|
||||||
|
fmt.Println("Forwarding request to ", url)
|
||||||
|
response, err := http.Post(url, "text/plain", buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody, _ := ioutil.ReadAll(response.Body)
|
||||||
|
w.Write(responseBody)
|
||||||
|
seconds := time.Since(start).Seconds()
|
||||||
|
fmt.Printf("Took %f\n", seconds)
|
||||||
|
metrics.GatewayServerlessServedTotal.Inc()
|
||||||
|
metrics.GatewayFunctions.Observe(seconds)
|
||||||
|
}
|
||||||
|
|
||||||
func makeProxy(metrics metrics.MetricOptions) http.HandlerFunc {
|
func makeProxy(metrics metrics.MetricOptions) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
metrics.GatewayRequestsTotal.Inc()
|
metrics.GatewayRequestsTotal.Inc()
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
log.Println(r.Header)
|
log.Println(r.Header)
|
||||||
header := r.Header["X-Function"]
|
header := r.Header["X-Function"]
|
||||||
log.Println(header)
|
log.Println(header)
|
||||||
|
|
||||||
exists, err := lookupSwarmService(header[0])
|
if len(header) > 0 {
|
||||||
if err != nil {
|
exists, err := lookupSwarmService(header[0])
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists == true {
|
|
||||||
requestBody, _ := ioutil.ReadAll(r.Body)
|
|
||||||
buf := bytes.NewBuffer(requestBody)
|
|
||||||
|
|
||||||
response, err := http.Post("http://"+header[0]+":"+strconv.Itoa(8080)+"/", "text/plain", buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
responseBody, _ := ioutil.ReadAll(response.Body)
|
if exists == true {
|
||||||
w.Write(responseBody)
|
requestBody, _ := ioutil.ReadAll(r.Body)
|
||||||
metrics.GatewayServerlessServedTotal.Inc()
|
invokeService(w, r, metrics, header[0], requestBody)
|
||||||
|
}
|
||||||
metrics.GatewayFunctions.Observe(time.Since(start).Seconds())
|
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
requestBody, _ := ioutil.ReadAll(r.Body)
|
||||||
w.Write([]byte("Provide an x-function header."))
|
alexaService := isAlexa(requestBody)
|
||||||
|
if len(alexaService.Session.SessionId) > 0 &&
|
||||||
|
len(alexaService.Session.Application.ApplicationId) > 0 &&
|
||||||
|
len(alexaService.Request.Intent.Name) > 0 {
|
||||||
|
fmt.Println("Alexa skill detected")
|
||||||
|
invokeService(w, r, metrics, alexaService.Request.Intent.Name, requestBody)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("Provide an x-function header."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
oneshot.sh
Executable file
12
oneshot.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker network create --driver overlay --attachable functions
|
||||||
|
git clone https://github.com/alexellis/faas && cd faas
|
||||||
|
cd watchdog
|
||||||
|
./build.sh
|
||||||
|
docker build -t catservice .
|
||||||
|
docker service rm catservice ; docker service create --network=functions --name catservice catservice
|
||||||
|
cd ..
|
||||||
|
cd gateway
|
||||||
|
docker build -t server . ;docker rm -f server; docker run -v /var/run/docker.sock:/var/run/docker.sock --name server -p 8080:8080 --network=functions server
|
||||||
|
|
11
sample-functions/HostnameIntent/Dockerfile
Normal file
11
sample-functions/HostnameIntent/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM alpine:latest
|
||||||
|
RUN apk --update add nodejs
|
||||||
|
COPY ./fwatchdog /usr/bin/
|
||||||
|
|
||||||
|
COPY package.json .
|
||||||
|
COPY handler.js .
|
||||||
|
COPY sample.json .
|
||||||
|
|
||||||
|
RUN npm i
|
||||||
|
ENV fprocess="node handler.js"
|
||||||
|
CMD ["fwatchdog"]
|
22
sample-functions/HostnameIntent/handler.js
Normal file
22
sample-functions/HostnameIntent/handler.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"use strict"
|
||||||
|
let fs = require('fs');
|
||||||
|
let sample = require("./sample.json");
|
||||||
|
|
||||||
|
var content = '';
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function(buf) { content += buf.toString(); });
|
||||||
|
process.stdin.on('end', function() {
|
||||||
|
fs.readFile("/etc/hostname", "utf8", (err, data) => {
|
||||||
|
if(err) {
|
||||||
|
return console.log(err);
|
||||||
|
}
|
||||||
|
// console.log(content);
|
||||||
|
|
||||||
|
sample.response.outputSpeech.text = "Your hostname is: " + data;
|
||||||
|
sample.response.card.content = "Your hostname is: "+ data
|
||||||
|
sample.response.card.title = "Your hostname";
|
||||||
|
console.log(JSON.stringify(sample));
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
12
sample-functions/HostnameIntent/package.json
Normal file
12
sample-functions/HostnameIntent/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "HostnameIntent",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "handler.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
16
sample-functions/HostnameIntent/sample.json
Normal file
16
sample-functions/HostnameIntent/sample.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"response": {
|
||||||
|
"outputSpeech": {
|
||||||
|
"type": "PlainText",
|
||||||
|
"text": "There's currently 6 people in space"
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"content": "There's currently 6 people in space",
|
||||||
|
"title": "People in space",
|
||||||
|
"type": "Simple"
|
||||||
|
},
|
||||||
|
"shouldEndSession": true
|
||||||
|
},
|
||||||
|
"sessionAttributes": {}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user