Web-CGI für Testeingaben generieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten einen Prozess für sich allein genommen testen, indem Sie Textdaten aus einem Formular verarbeiten und diese Daten in eine physische Nachricht umwandeln, die Sie an den zu testenden Prozess senden können.

Lösung

Dieser Server-Generator gehört zum Client-Generator aus dem Rezept Einen Webclient zur Eingabe von Testdaten erzeugen. Dieser Abschnitt zeigt nur einen Teil der Lösung, aber der Diskussionsabschnitt enthält weitere Details.

Der generierte Server ist ein CGI-Programm in C++. Es muss C++ (bzw. C) sein, weil die Formulareingabe in eine Binärnachricht konvertiert wird, deren Speicherlayout dem Speichermodell von C entspricht. Er geht davon aus, dass die C++-Prozesse, die diese Nachrichten am Ende konsumieren, sie in Form von binären Daten erwarten. Das generierte CGI-Programm verwendet LibCGI, um häufige CGI-Aufgaben wie das Parsen von Abfragestrings zu vereinfachen:

<!DOCTYPE xslt [
  <!-- Wird für die Steuerung der Code-Einrückung benutzt -->
  <!ENTITY INDENT "    ">
]>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:strip-space elements="*"/>
  <!-- Die Nachricht, für die Daten generiert werden, '*' für alle. -->
  <xsl:param name="message" select=" '*' "/>
  <!-- Das Zeilverzeichnis des generierten Codes -->
  <xsl:param name="generationDir" select=" 'src/' "/>
  <!-- Die gewünschte Erweiterung der C++-Headerdateien -->
  <xsl:param name="headerExt" select=" '.h' "/>
  <!-- Die gewünschte Erweiterung der C++-Quelldateien -->
  <xsl:param name="sourceExt" select=" '.C' "/>
  <!-- Ein Schlüssel, um Datentypen über den Namen zu finden. -->
  <xsl:key name="dataTypes" match="Structure" use="Name" />
  <xsl:key name="dataTypes" match="Primitive" use="Name" />
  <xsl:key name="dataTypes" match="Array" use="Name" />
  <xsl:key name="dataTypes" match="Enumeration" use="Name" />
  <!-- Das oberste Template bestimmt, welche Nachrichten verarbeitet werden -->
  <xsl:template match="/">
    <xsl:choose>
      <xsl:when test="$message = '*'">
        <xsl:apply-templates select="*/Messages/*"/>
      </xsl:when>
      <xsl:when test="*/Messages/Message[Name=$message]">
        <xsl:apply-templates select="*/Messages/Message[Name=$message]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message terminate="yes">No such message name [<xsl:value-of select="$message"/>]</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <!-- Falls der Datentyp der Nachricht im Repository enthalten ist, dann generiere Header- und Quelldatei für einen Daten-Wrapper dafür. -->
  <xsl:template match="Message">
    <xsl:choose>
      <xsl:when test="key('dataTypes',DataTypeName)">
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="source">
          <xsl:with-param name="msg" select="Name"/>
        </xsl:apply-templates>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>Message name [<xsl:value-of select="Name"/>] uses data [<xsl:value-of select="DataTypeName"/>] that is not defined in the repository.</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <!-- Wir generieren nur dann Quelldateien, wenn der Nachrichten-Datentyp eine Struktur ist. Der einzig andere typische Nachrichten-Datentyp ist XML. Für XML-Inhalte generieren wir keine Wrapper. -->
  <xsl:template match="Structure" mode="source">
    <xsl:param name="msg"/>
    <xsl:document href="{concat($generationDir,Name,'CGI',$sourceExt)}">
      <xsl:text>
      #include &lt;stdio.h&gt;
      #include "cgi.h"
      #include "</xsl:text>
        <xsl:value-of select="Name"/><xsl:value-of select="$headerExt"/>
        <xsl:text>"

        void cgi_main(cgi_info *cgi)
        {
          </xsl:text>
          <xsl:value-of select="Name"/>
          <xsl:text> data ;
          form_entry* form_data = get_form_entries(cgi) ;
      </xsl:text>
      <xsl:for-each select="Members/Member">
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="variables">
          <xsl:with-param name="field" select="concat($msg,'_',Name)"/>
          <xsl:with-param name="var" select="Name"/>
        </xsl:apply-templates>
      </xsl:for-each>
      <xsl:for-each select="Members/Member">
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="load">
          <xsl:with-param name="field" select="concat('data.',Name)"/>
          <xsl:with-param name="var" select="Name"/>
        </xsl:apply-templates>
      </xsl:for-each>
      <xsl:text>
      &INDENT;// Daten an die Schlange des getesteten Prozesses übergeben
      &INDENT;enqueData(data) ;

     }</xsl:text>
    </xsl:document>
  </xsl:template>
  <!-- Deklariere und initialisiere Variablen für alle Felder -->
  <xsl:template match="Structure" mode="variables">
    <xsl:param name="field"/>
    <xsl:param name="var"/>
    <xsl:for-each select="Members/Member">
      <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="variables">
        <xsl:with-param name="field" select="concat($field,'_',Name)"/>
        <xsl:with-param name="var" select="$var"/>
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:template>
  <xsl:template match="*" mode="variables">
    <xsl:param name="field"/>
    <xsl:param name="var"/>
    <xsl:text>&INDENT;const char * </xsl:text>
    <xsl:value-of select="$var"/>
    <xsl:text> = parmval(form_data, "</xsl:text>
    <xsl:value-of select="$field"/>
    <xsl:text>");&#xa;</xsl:text>
  </xsl:template>
  <!-- Initialisiere Daten aus dem konvertierten Wert -->
  <xsl:template match="Structure" mode="load">
    <xsl:param name="field"/>
    <xsl:param name="var"/>
    <xsl:for-each select="Members/Member">
      <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="load">
        <xsl:with-param name="field" select="concat($field,'.',Name)"/>
        <xsl:with-param name="var" select="concat($field,'_',Name)"/>
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:template>
  <xsl:template match="Primitive" mode="load">
    <xsl:param name="field"/>
    <xsl:param name="var"/>
    <xsl:text>&INDENT;</xsl:text>
    <xsl:value-of select="$field"/>
    <xsl:text> = </xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:text>(</xsl:text>
    <xsl:value-of select="$var"/>
    <xsl:text>);&#xa;</xsl:text>
  </xsl:template>
  <xsl:template match="Enumeration" mode="load">
    <xsl:param name="field"/>
    <xsl:param name="var"/>
    <xsl:text>&INDENT;</xsl:text>
    <xsl:value-of select="$field"/>
    <xsl:text> = Enum</xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:text>NameToVal(</xsl:text>
    <xsl:value-of select="$var"/>
    <xsl:text>");&#xa;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Wenn diese XSLT-Transformation auf unserem Repository ausgeführt wird, generiert sie zu jeder einzelnen Nachricht ein cgi-Programm, zum Beispiel:

#include <stdio.h>
#include "cgi.h"
#include "msg_ids.h"
#include "AddStockOrderData.h"

void cgi_main(cgi_info *cgi)
{
    AddStockOrderData data ;
    form_entry* form_data = get_form_entries(cgi) ;
    const char * symbol = parmval(form_data, "ADD_STOCK_ORDER_symbol");
    const char * quantity = parmval(form_data, "ADD_STOCK_ORDER_quantity");
    const char * side = parmval(form_data, "ADD_STOCK_ORDER_side");
    const char * type = parmval(form_data, "ADD_STOCK_ORDER_type");
    const char * price = parmval(form_data, "ADD_STOCK_ORDER_price");
    data.symbol = StkSymbol(symbol);
    data.quantity = Shares(quantity);
    data.side = EnumBuyOrSellNameToVal(side);
    data.type = EnumOrderTypeNameToVal(type);
    data.price = Real(price);

    // Daten an die Warteschlange des getesteten Prozesses übergeben
    enqueData(ADD_STOCK_ORDER,data) ;

}

Diskussion

Diese Lösung setzt mehrere Dinge voraus. Erstens geht sie davon aus, dass die primitiven Typen (z.B. Shares) Klassen mit Konstruktoren sind, die eine Konvertierung von Strings in die interne Darstellung des Typs (z.B. int) vornehmen. Zweitens setzt sie die Existenz von Konvertierungsfunktionen zur Umwandlung der symbolischen Namen von Aufzählungskonstanten in ihre integralen Werte voraus. Wie Sie leicht erraten werden, können diese Funktionen auf einfache Weise aus der Information im Repository generiert werden. Das möchten wir hiermit als Übungsaufgabe stellen. Drittens nimmt der generierte Code an, dass es eine Funktion namens enqueData gibt, die weiß, wie man eine Nachricht an den richtigen gerade zu testenden Prozess sendet. Die Details dazu, wie diese Funktion mit der konkreten Middleware interagiert und wie sie die entsprechende Warteschlange findet, variieren von einer Implementierung zur anderen.

Die Quintessenz ist die, dass Sie diesen Code-Generator für Ihre eigenen Zwecke anpassen müssen. Aber sobald einmal ein Metadaten-Repository erstellt worden ist, das reichhaltig genug ist, wird ein hohes Maß an Automatisierung möglich. Die Funktionalität, die in diesem Beispiel und im vorherigen Rezept automatisiert wurde, könnte leicht einen Vollzeit-Programmierer beschäftigen. Das gilt besonders dann, wenn die Anwendung mit Nachrichten hantiert, die sich ständig ändern.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de