Bot Deployment auf Azure

28. Februar 2017
Nachdem ich im letzten Artikel die theoretischen Grundlagen des Bot-Frameworks kurz beschrieben habe, wird es nun Zeit für erste praktische Erfahrungen. Dafür zeige ich nachfolgend wie man mittels Node.js und Web-Chat[1]  innerhalb kurzer Zeit einen Bot mit GUI aufsetzt, wie man ein Azure-Deployment durchführt und welche Konfigurationen dabei zu beachten sind. 

Bot-Logik

Inhaltlich beschreibe ich in diesem Abschnitt nur das Programmieren einer einfachen Bot-Logik und die Visualisierung über Web-Chat. Eine komplexere Logik würde den Rahmen für den Artikel sprengen. Zudem hätte ich nichts beleuchten können, was Microsoft nicht schon in ihrer gut gepflegten Dokumentation[2] abbildet. Wer sich also zu den Themen Session-Handling, Speicherung der User-Daten und Rich-Media-Objekte tiefergehend interessiert, sollte unbedingt einen Blick in die Doku werfen.
 
Bevor das Programmieren aber beginnt, müssen noch zwei Voraussetzungen erfüllt sein. Zum einem muss eine Bot-Registration über das MS-Portal[3] gemacht werden und Node.js sollte lokal installiert sein. Sobald diese Punkte erfüllt sind, kann der Bot das Licht der Welt entdecken. 
 
Dafür erstelle ich initial eine package.json Datei und referenziere die npm-Module restify und botbuilder.
 
{
  "name": "botframework-poc",
  "version": "0.0.2",
  "description": "",
  "main": "app.js",
  "keywords": ["bot"],
  "engines": {
    "node": ">=5.6.0"
  },
  "author": "Patrick Rudolph",
  "license": "MIT",
  "dependencies": {
    "restify": "^4.0.4",
    "botbuilder": "latest"
  }
}
 
Danach installiere ich mir die Module über npm install und erstelle anschließend eine Datei mit dem Namen app.js im Root-Verzeichnis meiner Anwendung. In der app.js importiere ich als erstes meine Module über require().
 
var restify = require('restify');
var builder = require('botbuilder');
var connector = require('./config/connectorConfig').chatConnector();
 
Die Variable connector referenziert auf meine connectorConfig.js und enthält meine App-ID sowie mein App-Passwort, welche ich nach der Bot-Registrierung auf dem MS-Portal bekommen habe. Die Microsoft Credentials könnte man auch in der Datei app.js hinterlegen, aber zur besseren Kapselung habe ich sie wie folgt ausgelagert.
 
var builder = require('botbuilder');

exports.chatConnector = function(){

    return new builder.ChatConnector({
        appId: '<APP_ID>',
        appPassword: '<APP_PASSWORD>'
    });
};
 
Nachdem meine Komponenten in der app.js und connectorConfig.js definiert sind, instanziiere ich einen Server über restify und lasse ihn auf einen lokalen Port lauschen. Das Ausliefern der statischen Frontend-Datei handle ich über server.get() – dieser Punkt wird später noch wichtig bei der Einbindung von Web-Chat. Im Anschluss binde ich den Bot-Konnektor an den Server-Kontext.
 
var restify = require('restify');
var builder = require('botbuilder');
var connector = require('./config/connectorConfig').chatConnector();

// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 8090, function () {
    console.log('%s listening to %s', server.name, server.url);
});

//provide static files for frontend
server.get(/\/public\/?.*/, restify.serveStatic({
    directory: __dirname
}));

// bind bot connector to server context
server.post('/api/messages', connector.listen());
 
Nachdem der restify-Server initialisiert wurde, kann auch schon die Bot-Logik programmiert werden. Im ersten Schritt erzeuge ich hierfür eine neue Bot-Instanz und beginne über das Schlüsselwort bot.dialog() einen neuen Dialog. Innerhalb dieser Funktion wird jede Bot-Frage und Antwort wasserfall-artig abgearbeitet. Für jede Bot-Iteration (neue Frage) dient als Funktions-Gerüst function(session, results), wobei der Parameter results die User-Antwort  immer an die nächste Funktion weitergibt. Über den Parameter session kann man zudem Antworten des Users speichern.
 
In meinem simplen Code-Beispiel erzeuge ich über builder.Prompts() Fragen für den User, speichere mir die Antworten im Session-Objekt und lasse sie mir am Ende wieder ausgeben. Meine fertige app.js sieht somit wie folgt aus:
 
var restify = require('restify');
var builder = require('botbuilder');
var connector = require('./config/connectorConfig').chatConnector();

// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 8090, function () {
    console.log('%s listening to %s', server.name, server.url);
});

//provide static files for frontend
server.get(/\/public\/?.*/, restify.serveStatic({
    directory: __dirname
}));

// bind bot connector to server context
server.post('/api/messages', connector.listen());

// new bot instance
var bot = new builder.UniversalBot(connector);


bot.dialog('/', [
    function (session) {
        builder.Prompts.text(session, "Hello... What's your name?");
    },
    function (session, results) {
        session.userData.name = results.response;
        builder.Prompts.number(session, "Hi " + results.response + ", How many years have you been coding?");
    },
    function (session, results) {
        session.userData.coding = results.response;
        builder.Prompts.choice(session, "What language do you code Node using?", ["JavaScript", "CoffeeScript", "TypeScript"]);
    },
    function (session, results) {
        session.userData.language = results.response.entity;
        builder.Prompts.text(session, "Got it... " + session.userData.name +
            " you've been programming for " + session.userData.coding +
            " years and use " + session.userData.language + ".");
    }
]);
 

Testen 

Um die Anwendung zu starten gebe ich den Befehl node app.js ein. Nachdem der restify-Server läuft, öffne ich meinen lokalen Port nach außen mittels ngrok. Eine Einleitung für die Einrichtung und den Umgang findet sich u.a. hier. Die von ngrok generierte URL trage ich danach als Messaging-Endpoint in meine Bot-Registration im MS-Portal ein. Wenn diese Schritte abgeschlossen sind, ist es möglich die Logik über den Bot-Emulator[4] zu testen.   
 

Web-Chat UI

Die Logik des Bots ist damit implementiert, was noch fehlt ist das Frontend. Die Vorbereitungen hierfür habe ich in der app.js schon hinterlegt – mittels server.get() können statische Dateien wie html ausgeliefert werden. Im Root-Verzeichnis meiner Anwendung habe ich deshalb einen public Ordner erstellt, welcher die webchat.html enthält (siehe Abbildung 6).
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebChat</title>
</head>
<body>
<h2>WebChat-UI</h2>
<iframe src='https://webchat.botframework.com/embed/BSS_TestBot?s=<WEBCHAT_SECRET>'></iframe>
</body>
</html>
 
Um Web-Chat in die eigene Seite zu integrieren gibt es zwei Wege. Beim einfachen Weg, welchen ich hier zeige, genügt es ein iframe einzubinden. Der umfangreiche Weg führt dagegen über die Einbindung der Web-Chat-Library, was auch eine Anpassung der app.js mit sich zieht. Wie die Vorgehensweisen für die alternativen Einbindungen ablaufen, ist auf GitHub gut dokumentiert.
 
Für die Integration über iframe setzte ich folgende URL: src=‘https://webchat.botframework.com/embed/BSS_TestBot?s=<WEBCHAT_SECRET>
Nach dem Host-Präfix, gebe ich den Namen meines registrierten Bots an (BSS_TestBot) und füge zum Schluss mein Web-Chat-Secret hinzu, welches ich über das MS-Portal generiert habe. Das Frontend mit Web-Chat-UI ist nun unter localhost:<PORT>/public/webchat.html erreichbar. 
 

Azure Depolyment

Es gibt verschiedene Wege um einen Bot auf Azure zu deployen, in meinem Beispiel gehe ich über die Azure-CLI und referenziere auf mein lokales Projekt. Ich werde an dieser Stelle nicht alle Schritte beschreiben die ich gemacht habe, da die Dokumentation[5] von MS eins zu eins beschreibt wie ich vorgegangen bin. 
 
Kurz gesagt, habe ich eine neue Azure-Seite angelegt und mein lokales Bot-Repository mit der Seite verbunden. Dadurch brauche ich in der Command-Line nur den Befehl git push azure master eingeben und auf Azure wird automatisch deployed. 
 
Doch bevor man auf der Azure-Seite einen funktionierenden Bot sieht, ist noch eine web.config im Root-Verzeichnis notwendig. In der Config-Datei hinterlege ich Verweise zu meiner app.js und webchat.html, damit Azure meine Komponenten richtig routet. 
 
<configuration>
     <system.webServer>
          <webSocket enabled="false" />
          <handlers>
               <add name="iisnode" path="app.js" verb="*" modules="iisnode"/>
          </handlers>
          <rewrite>
               <rules>
                    <rule name="Redirect to HTTPS" stopProcessing="true">
                        <match url="(.*)" />
                        <conditions>
                            <add input="{HTTPS}" pattern="off" />
                        </conditions>
                        <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Permanent" />
                    </rule>
                    <rule name="Root Hit Redirect" stopProcessing="true">
                        <match url="^$" />
                        <action type="Redirect" url="/webchat.html" />
                    </rule>
                    <rule name="" patternSyntax="Wildcard">
                         <match url="*" negate="false" />
                         <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
                         <action type="Rewrite" url="app.js" />
                    </rule>
               </rules>
          </rewrite>
          <security>
                <requestFiltering>
                    <hiddenSegments>
                         <remove segment="bin" />
                    </hiddenSegments>
                    <requestLimits maxQueryString="4000" maxUrl="4000" />
                </requestFiltering>
          </security>
          <iisnode
               enableXFF="true"
               maxLogFileSizeInKB="10485760"
               loggingEnabled="true"
               logDirectory="../../../../../LogFiles/Application"
          />
          <httpErrors existingResponse="PassThrough"></httpErrors>
          <httpProtocol>
            <customHeaders>
                <add name="Access-Control-Allow-Origin" value="*" />
                <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
                <add name="Access-Control-Allow-Headers" value="Origin, Content-Type, Authorization, Accept" />
            </customHeaders>
          </httpProtocol>
     </system.webServer>
</configuration>
 
Mit dieser Konfigurations-Datei ist der letzte Schritt getan, damit das Deployment auf Azure erfolgreich verläuft. Anschließend hinterlege ich die URL meiner Azure-Seite in der Bot-Registrierung als Messaging-Endpoint und der Bot lebt! 
 
Ohne viel Aufwand und nur mit ein paar Zeilen Code, ist es somit möglich einen produktiven Bot auf Azure zu deployen. 
 
 
Referenzen:
 
 

Neuen Kommentar schreiben