
BTP - HTTP Service
Du brauchst einen generischen Endpunkt im ABAP Environment? In diesem Artikel schauen wir uns die Möglichkeit des HTTP Services an.
Inhaltsverzeichnis
In diesem Artikel schauen wir uns den HTTP Endpunkt im ABAP Environment an und wie du ihn verwenden kannst, um einen generischen Endpunkt zur Verfügung zu stellen. Dabei gehen wir auf die Themen Implementierung, Sicherheit und Möglichkeiten ein.
Einleitung
On-Premise gibt es den HTTP Endpunkt schon eine ganze Weile, allerdings fanden wir die Implementierung bisher immer etwas sperrig und nicht so einfach und schnell durchführbar. Diesen Endpunkt gibt es nun auch im wieder im ABAP Environment, dieses Mal nur als ABAP Objekt und einfacher. Doch lasse uns dazu erst einmal die Implementierung anschauen.
Anlage
Legen wir uns dazu einen neuen HTTP Service an. Dazu gehen wir in die ABAP Development Tools (ADT) und legen auf einem Paket ein neues Objekt an. Dazu per Rechts-Klick auf dem Packet das Kontextmenü öffnen und per "New -> Other ABAP Repository Object" den Wizard starten.
Entweder suchst du manuell in der Liste oder gibst oben in die Suchleiste "http" ein, um die Treffermenge einzuschränken. Im Bereich "Connectivity" finden wir den gewünschten Service.
Dem Objekt geben wir einen Namen und eine Beschreibung. Der Name der HTTP Klasse wird automatisch aus dem Namen der Klasse abgeleitet oder kann im Nachgang noch angepasst werden, falls der Name nicht deinen Wünschen entsprechen sollte.
Sind wir dem Wizard durch, sollten wir unseren HTTP Service finden. In dem UI erhältst du Absprünge zum HTTP Handler, dem URL Endpunkt und kannst weitere Werte konfigurieren. Der Service wird automatisch im Hintergrund aktiviert, sodass wir keine weiteren Schritte durchführen müssen und der HTTP Endpunkt im System nun aktiv ist.
HTTP Service
Schauen wir uns nun einmal die generierte HTTP Klasse und ihre Methoden an.
Klasse
In der Klasse wurde das Interface IF_HTTP_SERVICE_EXTENSION implementiert, welches die Methode HANDLE_REQUEST zur Verfügung stellt.
CLASS zcl_bs_demo_http_endpoint DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_http_service_extension.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_http_endpoint IMPLEMENTATION.
METHOD if_http_service_extension~handle_request.
ENDMETHOD.
ENDCLASS.
Wenn wir uns die Methode im Detail anschauen, bekommen wir eine Anfrage (REQUEST) übergeben und können eine Antwort (RESPONSE) geben, mehr bietet uns die Schnittstelle nicht an.
Request
Wird der HTTP Endpunkt aufgerufen, erhalten wir ein Objekt vom Typ IF_WEB_HTTP_REQUEST. Die Anfrage bezieht sich auf einen HTTP Request und enthält damit alle gängigen Methoden und Merkmale eines HTTP Requests, wie Header Informationen, einen Body, den Content Type, Informationen zu GET Parametern und viele weitere Informationen. Je nach Kontext oder Endpunkt brauchen wir nicht immer alle Informationen. Die aktuelle Schnittstelle sieht wie folgt aus:
Response
Als Antwort haben wir fast ebenso viele Möglichkeiten, auf die Anfrage zu reagieren. Das Objekt ist vom Typ IF_WEB_HTTP_RESPONSE und muss nicht mehr instanziiert werden, da wir bereits das fertige Objekt erhalten. Die Aktuelle Schnittstelle dazu würde wie folgt aussehen:
Implementierung
Implementieren wir dazu erst einmal ein paar Beispiele und schauen uns die Art und Weise der Funktion an.
Erster Start
Wenn du den Service über die "URL" im HTTP Service Objekt startest, dann sollte eine weiße Seite erscheinen. Grundsätzlich funktioniert der Service und wird durchlaufen, da wir aber keine Antwort geben, wird die Seite nach Abschluss weiß bleiben.
Rückgabe
Im nächsten Schritt übergeben wir an das RESPONSE Objekt über die Methode SET_TEXT unsere Antwort an den Service. Da es hier um einen HTTP Service geht, können wir auch einfaches HTML als Antwort geben. Lassen wir also unseren Service eine erste Antwort geben.
response->set_text( `<h1>Hello World ...</h1><p>This is my little HTTP Service :)</p>` ).
Je nach Browser und Standard sollten wir nun eine formatierte Antwort erhalten, in diesem Fall eine Überschrift und ein Paragraf.
Grundsätzlich findest du auch weitere Informationen zur Anfrage in den DevTools deines Browsers, hier einmal im Beispiel von Chrome. Die DevTools öffnest du hier per F12 auf dem Fenster. So kannst du im Netzwerk Tab überprüfen, welche Anfrage an den Endpunkt geschickt wurde und was wir als Antwort erhalten haben. In diesem Fall war die Antwort das einfache HTML.
Status
Genau so können wir als Antwort einen HTTP Status zurückgeben, um einen Fehlerfall oder fehlende Berechtigungen anzuzeigen. In unserem Beispiel liefern wir eine 404 ("Not Found") zurück, entsprechend erhalten wir im Browser auch eine Fehlerseite.
response->set_status( i_code = 404 ).
In den DevTools sehen wir dann die Fehlermeldung in unserer Anfrage und können dort noch einmal den Status prüfen.
Formular
Nach dieser kurzen Einführung wollen wir dir ein komplexeres Szenario vorstellen und wie du darauf reagieren kannst. Wird unser Service das erste Mal aufgerufen (GET), dann übergeben wir ein HTTP Dokument, in diesem Fall ein Formular für die Eingabe.
result = |<!DOCTYPE HTML>| &
|<html>| &
|<head>| &
| <meta http-equiv="X-UA-Compatible" content="IE=edge">| &
| <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />| &
| <style>| &
| body { padding: 20px; }| &
| </style>| &
|</head>| &
|<body>| &
| <h1>Generic Form</h1>| &
| <p>Choose a value and start</p>| &
| <form method="POST">| &
| | &
| <label for="id_dropdown">Dopdown</label>| &
| <br>| &
| <select id="id_dropdown" name="id_dropdown">| &
| <option value="A" selected>Value A</option>| &
| <option value="B">Value B</option>| &
| </select>| &
| <br><br>| &
| | &
| <label for="id_input">Insert Text</label>| &
| <br>| &
| <input id="id_input" name="id_input" type="text" value="Empty Text">| &
| <br><br>| &
| <input type="submit" name="id_submit">| &
| </form>| &
|</body>| &
|</html>|.
Starten wir den Endpunkt, dann erhalten wir eine Eingabe mit zwei Feldern und einem Senden Button. Damit erhält unser Anwender ein Eingabemaske, um weitere Informationen abzufragen.
Da wir beim Formular die Methode POST definiert haben, wir bei einem Klick auf den Senden Button ein POST Request an das Backend geschickt, damit wissen wir, dass nicht mehr das Formular aufgebaut werden soll, sondern das dieses ausgefüllt wurde. Über die Methode GET_FORM_FIELDS lesen wir die Felder aus dem Formular ein und erhalten eine interne Tabelle mit den Inhalten.
Über die SET_TEXT Methode, können wir dann dem Aufrufer eine Antwort schicken. In diesem Fall spiegeln wir ihm die Inhalte der Felder zurück.
DATA(fields) = request->get_form_fields( ).
response->set_text( |Field 1: { fields[ 1 ]-value }, Field 2: { fields[ 2 ]-value }| ).
Sicherheit
Wenn es um das Thema Sicherheit geht, solltest du beim HTTP Endpunkt vorsichtig sein, welche Daten du an andere Anwendungen weitergibst oder in deiner Verarbeitung verwendest. Ein Angreifer kann dir über die verschiedenen Möglichkeiten, wie die Header Felder, die Form Felder oder die Payload (GET_TEXT), alle möglichen Schadinformationen bzw. präparierte Informationen mitgeben. Validiere deshalb zuvor am besten immer den Inhalt.
Praxis
Wie sieht es nun also in der Praxis aus? Wo kann man so einen HTTP Endpunkt sinnvoll nutzen und was damit umsetzen? Grundsätzlich ist der Service so generisch, dass du jede HTTP Schnittstelle oder ein UI über den Endpunkt Rendern kannst. Im Beispiel UI setzt das Open Source Projekt abap2UI5 auf den HTTP Endpunkt. Dabei geniert der erste Aufruf des Frameworks die Fiori UI und alles folgende sind dann Aktualisierungen. Mehr Informationen zum Projekt und weitere Links findest du im verlinkten GitHub Repository.
Vollständiges Beispiel
Hier findest du das heutige Beispiel der Implementierung des Endpunkts. Den HTTP Service kannst du einfach selbst generieren, folge dazu einfach den Schritten weiter oben im Artikel.
CLASS zcl_bs_demo_http_endpoint DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_http_service_extension.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS get_form
RETURNING VALUE(result) TYPE string.
ENDCLASS.
CLASS zcl_bs_demo_http_endpoint IMPLEMENTATION.
METHOD if_http_service_extension~handle_request.
* response->set_text( `<h1>Hello World ...</h1><p>This is my little HTTP Service :)</p>` ).
* response->set_status( i_code = 404 ).
CASE request->get_method( ).
WHEN 'GET'.
response->set_text( get_form( ) ).
WHEN 'POST'.
DATA(fields) = request->get_form_fields( ).
response->set_text( |Field 1: { fields[ 1 ]-value }, Field 2: { fields[ 2 ]-value }| ).
ENDCASE.
ENDMETHOD.
METHOD get_form.
result = |<!DOCTYPE HTML>| &
|<html>| &
|<head>| &
| <meta http-equiv="X-UA-Compatible" content="IE=edge">| &
| <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />| &
| <style>| &
| body { padding: 20px; }| &
| </style>| &
|</head>| &
|<body>| &
| <h1>Generic Form</h1>| &
| <p>Choose a value and start</p>| &
| <form method="POST">| &
| | &
| <label for="id_dropdown">Dopdown</label>| &
| <br>| &
| <select id="id_dropdown" name="id_dropdown">| &
| <option value="A" selected>Value A</option>| &
| <option value="B">Value B</option>| &
| </select>| &
| <br><br>| &
| | &
| <label for="id_input">Insert Text</label>| &
| <br>| &
| <input id="id_input" name="id_input" type="text" value="Empty Text">| &
| <br><br>| &
| <input type="submit" name="id_submit">| &
| </form>| &
|</body>| &
|</html>|.
ENDMETHOD.
ENDCLASS.
Fazit
Der HTTP Service ist eine nette Ergänzung und kann als generischer Endpunkt genutzt werden. Dabei kannst du eine API oder ein UI zur Verfügung stellen. Vergiss bei der Nutzung aber nicht die Sicherheit und prüfe die Daten, die du an den Endpunkt gesendet bekommst.