Zum Inhalt

HTTP-Routing

Alvine unterstützt das Routing von HTTP-Anfragen. Darunter versteht man das Übersetzen von HTTP-Anfragen bestehend aus der Request-Methode und der übergebenen URL. Neben dem Alias können URI auch durch Ihre ID direkt aufgerufen werden. Der Webserver muß für die Verwendung der URL richtig konfiguriert sein.

Bestandteile

Ein HTTP-Routing besteht aus mehreren Bestandteilen, die im folgendne genauer betrachtet werden.

HTTP-Methode

Gültige HTTP Request Methoden für HTTP sind GET, POST, PUT, DELETE und HEAD.

URI-Muster

Das URI-Muster kann entweder eine feste Zeichenkette oder ein Regulärer Ausdruck sein. Reguläre Ausdrücke werden mittels der PREG-Methoden ausgewertet. Alle gültigen RegEx-Muster sind erlaubt.

Statische Pfade

Bei statischen Pfaden ist eine fest definierte Zeichenkette ohne regulären Ausdruck definiert. Außerdem ist diese Route auch noch über die ID aufrufbar. http://localhost/page/lists ist in diesem Fall also gleichbedeutend mit http://localhost/550e8400-e29b-11d4-a716-446655440002.

    GET    /page/lists          \Application\App\Web\Page     550e8400-e29b-11d4-a716-446655440002

Einbetten in Templates

Um in der Anwendung auf die definierten URI zugreifen zu können, besteht die Möglichkeit einer Rückwärtssuche über die ID.

Pfade mit dynamischen Teilen

Bei Pfaden mit dynamischen Teilen kann entweder ein fester Wert, ein Wildcard oder ein regulärer Ausdruck für den dynamischen Teil verwendet werden. Der dynamische Teil muss dabei immer in geschweiften Klammern notiert werden.

    GET    /page/{id}                           \Application\App\Web\Page    550e8400-e29b-11d4-a716-446655440000
    GET    /page/{id}/sub/{[a-z]+name}          \Application\App\Web\Page    550e8400-e29b-11d4-a716-446655440001

Priorisierung

Die Auswertung der Routingtabelle erfolgt von oben nach unten. Trifft eine Aussage zu, so wird diese als Anweisung an die Applikation übergeben.

Presenter

Der Presenter implementiert die Logik für die Darstellung des Objektes und muss über den kompletten Namen der Zielklasse definiert werden.

Zusammenspiel

Das Zusammenspiel der einzelnen Objekte ist im folgenden Sequenzdiagramm abgebildet.

Klasse Typ
\Alvine\Application\Assembly\Web Web
\Alvine\Application\Web\DefaultRouter Router
\Alvine\Application\Web\Route Route
\Alvine\Application\Web\Presenter Presenter
\Alvine\Application\Web\View View
Alvine\Application\Web\Model\Generic Model

In der Route wird geprüft, ob es eine dem HTTP-Verb (Presenter::get(), Presenter::post(), Presenter::put(),
Presenter::post()) entsprechende Methode gibt. Ist dies nicht der Fall, so wird die Methode Presenter::execute() aufgerufen.

Schaubild Zusammenspiel

Definition des Routings

Text-Parser

Eine schnelle und einfache Konfiguration erfolgt in Abhängigkeit des eingesetzten Parsers. Bei der Verwendung des Alvine\Application\Web\Router\RouteParser stehen die Routen in einer einer einfachen Textdatei. Jede Zeile in der Konfiguration startet mit der Request-Methode, gefolgt von URI-Muster und der Klasse und der Methode die Aufgerufen werden soll. Nach der Presenter-Klasse folgt die eindeutige ID der Route. Als letzter Teil des Routings sind die Parameter definiert. Kommentare werden mittels # in der Datei eingeleitet.

Weitere Routingtabellen können über das INCLUDE-Statement eingebunden werden. Das erste Argument ist dabei die Routingtabelle. Ein zweites optionales Argument ist eine Eigenschaftsdatei, die Platzhalter definiert (siehe XML-Parser).

    # ROUTING-TABLE

    # Laden einer Seite
    GET    /page/home          \Application\App\Web\Page       550e8400-e29b-11d4-a716-446655440000     id:'home'
    GET    /page/{id}          \Application\App\Web\Page       550e8400-e29b-11d4-a716-446655440001

    POST   /page/{id}          \Application\App\Web\Page       550e8400-e29b-11d4-a716-446655440002     name:'neue Seite'

    # Einbinden anderer Routingtabellen mit einer Propertydatei
    INCLUDE myRouting.xml      detail.properties

    # Einbinden anderer Routingtabellen mit einer Propertydatei
    INCLUDE myrouting.xml

    # Einbinden einer Route aus einem Phar-Archive
    # Das Phar-Archive muss aber bereits eingebunden (include) worden sein.
    INCLUDE phar://myphar.phar/config/route/my.xml

XML-Parser

Eine weitaus umfassendere Konfiguration lässt sich mit dem XML-Parser realisieren. Hier stehen neben der im Abschnitt über den Text-Parser beschrieben Möglichkeiten weitere zur Verfügung. Im folgenden Beispiel ist die URI /info/ mit der ID spezifiziert. Eine weitere URI mit der ID erlaubt darüber hinaus die Angabe einer Kategorie (siehe PHP-Dokumentation).

Ein weiteres Merkmal des XML-Parsers ist die Möglichkeit Handler für Exceptions zu definieren. Im Falle eines Fehlers wird in diesem Beispiel die entsprechende Route aufgerufen.

Der Nachfolgende Code ist ein Beispiel für eine Data-Route. Die Platzhalter %{} werden dabei durch eine Property-Datei, din im include-Statement definiert wurde ersetzt.

Beispiel Routen

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definition>
    <routes>
        <group>
            <route default="true" id="%{id-prefix}-LIST">
                <method>GET</method>
                <presenter>%{list-presenter}</presenter>
                <pattern><![CDATA[^/%{endpoint}$]]></pattern>
                <template>/%{endpoint}</template>
                <parameters>
                    <parameter name="model" type='string'>%{model}</parameter>
                </parameters>
                <links>
                    <link route="%{id-prefix}-DELETE" relation="delete" label="i18n{delete}"/>
                    <link route="%{id-prefix}-ADD-FORM" relation="add" label="i18n{add}"/>
                    <link route="%{id-prefix}-UPDATE-FORM" relation="action" label="i18n{update}"/>
                </links>
            </route>
            <route id="%{id-prefix}-DELETE">
                <method>DELETE</method>
                <presenter>%{delete-presenter}</presenter>
                <pattern><![CDATA[^/%{endpoint}/(?<uuid>([0-9a-z\-]+)){1}$]]></pattern>
                <template>/%{endpoint}/${uuid}</template>
                <parameters>
                    <parameter name="model" type='string'>%{model}</parameter>
                </parameters>
            </route>
            <route id="%{id-prefix}-ADD-FORM">
                <method>GET</method>
                <presenter>%{add-presenter}</presenter>
                <pattern><![CDATA[^/%{endpoint}/add$]]></pattern>
                <template>/%{endpoint}/add</template>
                <parameters>
                    <parameter name="model" type='string'>%{model}</parameter>
                </parameters>
                <links>
                    <link route="%{id-prefix}-ADD" relation="add" id="add" label="i18n{add}"/>
                    <link route="%{id-prefix}-LIST" relation="action" id="back" label="i18n{list}"/>
                </links>
            </route>
            <route id="%{id-prefix}-ADD">
                <method>POST</method>
                <presenter>%{add-presenter}</presenter>
                <pattern><![CDATA[^/%{endpoint}$]]></pattern>
                <template>/%{endpoint}</template>
                <parameters>
                    <parameter name="model" type='string'>{model}</parameter>
                </parameters>
                <links>
                    <link route="%{id-prefix}-UPDATE-FORM" relation="redirect"/>
                    <link route="%{id-prefix}-UPDATE-FORM" relation="redirect-fail"/>
                </links>
            </route>             
            <route id="%{id-prefix}-UPDATE-FORM">
                <method>GET</method>
                <presenter>%{update-presenter}</presenter>
                <pattern><![CDATA[^/%{endpoint}/(?<uuid>([0-9a-z\-]+)){1}$]]></pattern>
                <template>/%{endpoint}/${uuid}</template>
                <parameters>
                    <parameter name="model" type='string'>%{model}</parameter>
                </parameters>
                <links>
                    <link route="%{id-prefix}-UPDATE" relation="update" label="i18n{update}"/>
                    <link route="%{id-prefix}-LIST" relation="action" id="back" label="i18n{list}"/>
                    <link route="%{id-prefix}-ADD-FORM" relation="action" id="add" label="i18n{add}"/>
                </links>
            </route>
            <route id="%{id-prefix}-UPDATE">
                <method>PUT</method>
                <presenter>%{update-presenter}</presenter>
                <pattern><![CDATA[^/%{endpoint}/(?<uuid>([0-9a-z\-]+)){1}$]]></pattern>
                <template>/%{endpoint}/${uuid}</template>
                <parameters>
                    <parameter name="model" type='string'>%{model}</parameter>
                </parameters>
                <links>
                    <link route="%{id-prefix}-UPDATE-FORM" relation="redirect"/>
                    <link route="%{id-prefix}-UPDATE-FORM" relation="redirect-fail"/>
                </links>
            </route>
        </group>       
    </routes>
</definition>

Gruppen

Jede Route in einer Gruppe bekommen eine Referenz auf die anderen Routen in der Gruppe. Diese Referenz definieren welche Methoden auf einen Entpunkt möglich sind.

Hinweis

Jede Route muss in einer Gruppe definiert sein.

Ein Anwendungsfall ist zum Beispiel bei einer Darstellung von Listen, in denen der User Objekte löschen oder ändern kann. Die URLs zum löschen oder ändern, kann dann von der Anwendung im Response direkt mitgeliefert werden.

Definitionen der Gruppen

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <definition>
    <routes>
       <group>
          <route id="A1">
            <method>GET</method>
            ...
          </route>
          <route id="A2">
            <method>POST</method>
            ...
          </route>
          <route id="A3">
            <method>PUT</method>
            ...
          </route>   
           <route id="A4">
            <method>DELETE</method>
             ...
          </route>
      </group>
    </routes>
</definition>

Über Links lassen sich Routen definieren, die als Ausgwabspunkt der aktuellen Route genommen werden können. Wird zum Beispiel eine Liste angezeigt, besteht ausgehend von der Liste die Möglichkeit Einträge zu ändern und zu löschen.

<links>
    <link route="A1" relation="delete" label="Löschen"/>
    <link route="A2" relation="list" label="Liste"/>
    <link route="A3" relation="update" label="Ändern"/>
    <link route="A4" relation="patch" label="Patchen"/>
</links>

Attribute

Attribute Beschreibung
relation Schlüsselwort der Beziehung
route ID der Zielroute
label Bezeichnung des Links (z.B. im Anchor)

Besondere Relationen

Beziehung Beschreibung
delete URL zum löschen der Route
list Liste
update Ändern eines Eintrages
patch Ändern eines Feldes
add Hinzufügen eines neuen Eintrages

Default-Route

Wenn keine Route passt, so kann über das Attribute default="true" eine Route als Default gekennzeichnet werden. Bei dieser Route wird das Pattern nicht überprüft und die Route wird unabhängig davon ausgeführt.

<route id="B1" default="true">
     <method>GET</method>
     <presenter>\Alvine\Application\Web\Presenter\Home</presenter>
     <pattern><![CDATA[^/$]]></pattern>
     <template>/</template>
</route>

Parameter

Definition von Parametern

Dem Presenter werden im entsprechenden Aufruf nur die die Parameter die der Presenter in der Methode::getExecuteParameter() definiert hat übergeben. Die Standardimplementierung nimmt alle Werte die in der Route innerhalb des -Blocks definiert sind als gültige Parameter.

public function getExecuteParameter(Route $routeObj, \Alvine\Types\Map\ObjectMap $parameter, $method, \Alvine\Net\Resource\URI $route) {
    $collection = new \Alvine\Types\Collection('\Alvine\Types\Parameter');
    foreach($parameter as $param) {
        $collection->append($param);
    }
    return $collection;
}

XML-Definition der Route

<parameters>
   <!-- String-Parameter mit Defaultwert my -->
   <parameter name="template" type="string">my</parameter>
   <!-- Boolean-Parameter mit Defaultwert true -->
   <parameter name="flag" type="boolean">true</parameter>
</parameters>

Auswertung der Parameter

Die Werte eines Parameters können aus unterschiedlichen Quellen kommen. Die Standardimplementierung prüft der Reihe nach Parameter die über die Route definiert wurden, Cookies, Werte aus dem Query, aus dem HTTP-Body und von der URI.

Hinweis

Gibt eine der Methoden einen Wert ungleich null zurück, so wird dieser Wert genommen.

Reihen- folge Wert Konstante Methode im Router Beschreibung Beispiel
1 url PARAMETERFROMURL getUrlParameter Werte die über ein Matching der URL entstehen - pattern www.example.com/mustermann/ (in der Route definiert (?[a-z])/) )
2 body PARAMETERFROMBODY getBodyParameter alle im Body per Post übergebenen Variablen mit MIMEType Formular name=mustermann (im HTTP-Body übertragen)
3 query PARAMETERFROMQUERY getQueryParameter Die Werte in der URL die nach dem Fragezeichen kommen www.example.com/?name=mustermann
4 coockie PARAMETERFROMCOOKIE getCookieParameter alle im Cookie übertragenen Werte name=mustermann (im Header übertragen)
5 route PARAMETERFROMROUTE getRouteParameter die in der Route definierten Parameters true

Die verwendeten Parameter und die Reihenfolge kann über das Attribute source definiert werden. In dem folgenden Beispiel sollen nur Werte die über ein Cookie kommen, oder im Query definiert sind genommen werden.

<parameters source="cookie,query">
   <!-- String-Parameter mit Defaultwert my -->
   <parameter name="template" type="string">my</parameter>
   <!-- Boolean-Parameter mit Defaultwert true -->
   <parameter name="flag" type="boolean">true</parameter>
</parameters>

Im Presenter stehen definierten Parameter zur Verfügung. Wird in der Route keine gesonderte Angabe gemacht stehen alle Werte die über eine HTTP-Anfrage gekommen sind (post, get, cookie) und die über das Pattern definierten Parameter (url) zur Verfügung.

<route id="A3">
     <method>GET</method>
     <presenter>\Alvine\Application\Web\Presenter\PHPInfo</presenter>
     <pattern><![CDATA[^/info/(?<category>([0-9]+))$]]></pattern>
</route>

Parameter deaktivieren

Soll kein Parameter überschrieben werden und der Standard vom Presenter verwendet werden , können die Parameter auch leer gelassen werden

<parameters source="" />

Template

Das Template wird für die Erstellung der URL zu dieser Route verwendet. Die Platzhalter in den geschweiften Klammern werden von der Anwendung ersetzt.

Bei einem Redirekt zum Beispiel, wird eine definierte Route über die ID geladen und das Template definiert die URL für die Location.

<route id="A1">
    ...
    <template>/info/{category}</template>
    ...
</route>

Exception-Handling

Tritt im Presenter oder View während der Verarbeitung eine Exception auf, so kann in der Route auf diese Exception mit einem Handler reagiert werden. In dem Beispiel mit der Route A4.

<route id="852d38ed-1e1b-480a-9b0a-0cc3c3ad8b98">
    <method>GET</method>
    <presenter>\MyPackage\MyPresenter</presenter>
    <pattern>^/repos/$</pattern>
    <exceptions>
        <exception class="\Alvine\Application\Web\Route\Exception\AccessDenied" handler="\Alvine\Application\Web\Route\Handler\Redirect">
            <parameters>
                <parameter name="location">http://www.example.com/login/</parameter>
            </parameters>
        </exception>
        <exception class="\Alvine\Persistence\ObjectNotFoundException" handler="\Alvine\Application\Web\Route\Handler\InternalRedirect">
                    <parameters>
                        <parameter name="path">/notfound</parameter>
                    </parameters>
        </exception>
    </exceptions>
</route>

<route id="2258c4cc-f6bc-49d4-958f-f849f08ed431">
   <method>GET</method>
   <presenter>\Alvine\Service\Account\Presenter\NotFoundPresenter</presenter>
   <pattern>^/notfound$</pattern>
   <parameters/> 
</route>

Wird im Presenter \MyPackage\MyPresenter nun eine AccessDenied-Exception geworfen, so kommt der Redirect-Handler auf die angegeben Login-Seite zum Zuge.

class MyPresenterextends \Alvine\Application\Web\Presenter {
 public function get(\Alvine\Types\Map\ObjectMap $parameter) {
     throw new \Alvine\Application\Web\Route\Exception\AccessDenied();
 }
}

Standard-Handler für Exeptions

Die folgenden Handler können in der Reoutendefinition verwendet werden. Eigene Handler lassen sich einfach integrieren.

Klasse Information Parameter Beschreibung
\Alvine\Application\Web \Route\Handler\Redirect Dieser Handler führt einen permanenten Redirect durch (StatusCode 301). Ein eigener StatusCode (z.B. 303) kann über den Parameter statuscode übergeben werden location statuscode Ziel-URL HTTP-Statuscode optional
\Alvine\Application\Web \Route\Handler\InternalRedirect Dieser Handler führt die angegebene Route ohne vorheriges überprüfen des Musters aus. path Pfad auszuführenden Route
\Alvine\Application\Web \Route\Handler\ServerFailure Es wird eine interne Fehlerseite für Serverfehler angezeigt. Das ist dann Nützlich, wenn ein schwerwiegender Fehler auftritt und kein Presenter oder View zurVerfügung steht. Dieser Handler sollte sparsam eingesetzt werden und besser durch einen internen Redirect ersetzt werden. message Fehlermeldung optional

Hinweis

Handler sollten nicht zur Ausgabe von Inhalten missbraucht werden.

Routenklasse

Die Standardroute stellt eine Vielzahl von Möglichkeiten bereit. Möchte man jedoch Änderungen am Verhalten der Route vornehmen, so kann man eine eigene Route-Klasse über den Parameter class im Route-Tag definieren. Die eigene Route-Klasse muss aber von Route abgeleitet sein.

<route id="A1" class="MyRoute">
    <method>GET</method>
    <presenter>\Alvine\Application\Web\Presenter\PHPInfo</presenter>
</route> 

Die PHP-Klasse sieht dann folgendermaßen aus:

class MyRoute extends \Alvine\Application\Web\Route {
}

Überschreiben der HTTP-Methode

Da bei HTML-Formularen nur GET und POST möglich sind, kann die HTTP-Methode mit verschiedene Techniken überschrieben werden.

http://www.w3.org/html/wg/drafts/html/master/forms.html#attributes-for-form-submission

X-HTTP-Method-Override: PUT > // Google/GData X-HTTP-Method: PUT > // Microsoft X-Method-Override: PUT > // IBM

Paramter

Mit dem Attribute "name = X_HTTP_METHOD_OVERRIDE " als hidden Feld in einem Formular kann die Methode überschrieben werden. Hierzu muss das Formular mit POST übergeben werden.

Beispiel:

<form method="post" action="/index.php">
 <input type="hidden" name="X_HTTP_METHOD_OVERRIDE" value="DELETE" >
 <input type="input" name="test">
 <input type=submit>
</form>

Hinweis

Der Parameter muss nicht in der Route definiert werden.

Kommentare