[ABAP] Einfacher REST-Service (ohne OData)

Piyush Philip auf sap.com hat eine simple Möglichkeit beschrieben, wie man in ABAP einen REST-Service mit den entsprechenden CRUD-Methoden implementieren kann. Dazu ist im Wesentlichen nur eine eigene Handler-Klasse zu implementieren, die von IF_HTTP_EXTENSION erbt. Diese Handler-Klasse wird von einem Service des ICF gerufen.

  1. SE80 -> Neue Klasse anlegen
  2. In der Klasse im Reiter Interfaces: vom Interface IF_HTTP_EXTENSION erben
  3. die Interface-Methode IF_HTTP_EXTENSION~HANDLE_REQUEST implementieren (implementiert nur REST, kein OData-Sevice!)
    METHOD if_http_extension~handle_request.
    
    * Typen
        TYPES: BEGIN OF ty_json_request,
                 param1 TYPE char64,
                 param2 TYPE char64,
               END OF ty_json_request.
    
        TYPES: BEGIN OF ty_json_response,
                 success   TYPE string,
                 msg       TYPE string,
                 operation TYPE string,
               END OF ty_json_response.
    
    * Variablen
        DATA: rv_response TYPE ty_json_response.
        DATA: it_path_info TYPE stringtab.
        DATA: it_query_strings TYPE stringtab.
    
    * Header Fields auslesen
        DATA: it_header_fields TYPE tihttpnvp.
        server->request->get_header_fields( CHANGING fields = it_header_fields ).
    
    * Request Methode (POST, GET, PUT, DELETE ...)
        DATA(lv_request_method) = server->request->get_header_field( name = '~request_method' ).
    
    * URL Pathinfo
        DATA(lv_path_info) = server->request->get_header_field( name = '~path_info' ).
    * erstes '/' entfernen
        SHIFT lv_path_info LEFT BY 1 PLACES.
        SPLIT lv_path_info AT '/' INTO TABLE it_path_info.
    
    * URL Parameter (Query Strings) für Auswertung
        DATA(lv_query_string) = server->request->get_header_field( name = '~query_string' ).
        SPLIT lv_query_string AT '&' INTO TABLE it_query_strings.
    
    * Request Methoden behandeln
        CASE lv_request_method.
    * C (Create)
          WHEN 'POST'.
    * Beispiel: per Request übermittelte JSON-Daten auslesen und behandeln
    * Content-Typ auslesen
            DATA(lv_request_content_type) = server->request->get_content_type( ).
    
    * Wenn Content-Typ JSON
            IF lv_request_content_type CP if_rest_media_type=>gc_appl_json.
    * JSON-Daten auslesen und nach ABAP-Typ konvertieren -> Parameternamen müssen übereinstimmen, sonst HTTP 500!
              DATA(lv_request_cdata) = server->request->get_cdata( ).
    
              DATA: lv_request_abap TYPE ty_json_request.
    
              TRY.
                  /ui2/cl_json=>deserialize( EXPORTING json = lv_request_cdata
                                                       pretty_name = /ui2/cl_json=>pretty_mode-camel_case
                                             CHANGING  data = lv_request_abap ).
    
                CATCH cx_root INTO DATA(e_txt).
                  MESSAGE e_txt->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
              ENDTRY.
    
              rv_response = VALUE #( success   = 'true'
                                     msg       = lv_request_abap-param1 && ' | ' && lv_request_abap-param2
                                     operation = lv_request_method ).
            ELSE.
              rv_response = VALUE #( success   = 'false'
                                     msg       = 'Request content type must be application/json'
                                     operation = lv_request_method ).
            ENDIF.
    * R (Read)
          WHEN 'GET'.
            DATA(lv_param) = server->request->get_form_field( 'param' ).
    
            rv_response = VALUE #( success   = 'true'
                                   msg       = lv_param
                                   operation = lv_request_method ).
    * U (Update)
          WHEN 'PUT'.
            rv_response = VALUE #( success   = 'true'
                                   msg       = ''
                                   operation = lv_request_method ).
    * D (Delete)
          WHEN 'DELETE'.
            rv_response = VALUE #( success   = 'true'
                                   msg       = ''
                                   operation = lv_request_method ).
    
    * unerlaubte Methoden abfangen
          WHEN OTHERS.
    * Fehlercode 405 zurückgeben
            server->response->set_status( code   = cl_rest_status_code=>gc_client_error_meth_not_allwd
                                          reason = 'method not allowed' ).
    
            server->response->set_content_type( if_rest_media_type=>gc_appl_json ).
    
            server->response->set_header_field( name  = if_http_header_fields=>allow
                                                value = 'POST,GET,PUT,DELETE' ).
    
            rv_response = VALUE #( success   = 'false'
                                   msg       = 'error'
                                   operation = lv_request_method ).
        ENDCASE.
    
    * wenn erfolgreich, dann Wert zurückgeben
        IF rv_response-success = 'true'.
    * HTTP-Status 200
          server->response->set_status( code   = cl_rest_status_code=>gc_success_ok
                                        reason = 'Ok.' ).
    
    * application/json
          server->response->set_content_type( if_rest_media_type=>gc_appl_json ).
    
    * Service-Response als JSON zurückgeben
          server->response->set_cdata( data = /ui2/cl_json=>serialize( data        = rv_response
                                                                       pretty_name = /ui2/cl_json=>pretty_mode-camel_case ) ).
    
        ENDIF.
    
    ENDMETHOD.
    
  4. SICF -> neue ICF-Node (Sub-Element) unterhalb default_host -> sap -> bc als neuen unabhängigen Service anlegen
  5. in der Handlerliste des Service die erstellte Klasse als “Handler” angeben
  6. auf die neue ICF-Node rechtsklicken -> Service aktivieren
  7. auf die neue ICF-Node rechtsklicken -> Service testen
  8. URL aus Browserfenster kopieren (http://domain.de/sap/bc/servicename?sap-client=900)
  9. im Browser mir einem Rest-Plugin (FireFox-Prugin: Rested) testen

Links

[AJAX] $.post()

Allgemein

$.post(URL, data, callback); 

Beispiel

// https://www.w3schools.com/jquery/jquery_ajax_get_post.asp
$("btn1").click(function(){
    // url
    $.post("btn1_post.php",
    // data
    {
        data1: "some data1",
        data2: "some data2"
    },
    // callback
    function(data, status){
        console.log("Data: " + data);
        console.log("Status: " + status);
    });
}); 

[ABAP] OData: CREATE – Neuen Datensatz für eine Entität erzeugen

* URL
URL/SERVICENAME/ItemSet
* METHODE: POST
* HTTP-Request: Entität mit den Keyfeldern
* HTTP-Response: 201 / Created / Erzeugte Entität bei Erfolg
METHOD xyz_create_entity
  DATA: lv_entry_data TYPE zcl_xyz_mpc=>ts_pos.

* HTTP-Daten holen
  io_data_provider->read_entry_data( IMPORTING es_data = lv_entry_data ).

* Daten aufbereiten
  ...

* Datensatz anlegen
  INSERT INTO abc VALUES ...

  IF sy-subrc = 0.
* Rückgabemenge für erzeugten Datensatz aufbereiten
    er_entity ...
  ELSE.
* Exceptionhandling
    ...
  ENDIF.
ENDMETHOD.

[ABAP] Selektionsbild mit HTML-Elementen, Dateneingabe, SALV-Grid, CSS, Eventhandling

**********************************************************************
*
* Datentypen, Variablen, Konstanten
*
**********************************************************************
TYPES: ty_it_events TYPE STANDARD TABLE OF cntl_simple_event WITH DEFAULT KEY.

CONSTANTS: co_carrid TYPE string VALUE 'carrid'.
CONSTANTS: co_airpto TYPE string VALUE 'airpto'.

DATA: o_splitter_main TYPE REF TO cl_gui_splitter_container.
DATA: o_container_l   TYPE REF TO cl_gui_container.
DATA: o_container_r   TYPE REF TO cl_gui_container.
DATA: o_html TYPE REF TO cl_gui_html_viewer.
DATA: o_salv TYPE REF TO cl_salv_table.

DATA: it_spfli TYPE STANDARD TABLE OF spfli WITH DEFAULT KEY.
DATA: it_salv_spfli TYPE STANDARD TABLE OF spfli WITH DEFAULT KEY.
DATA: it_sairport TYPE STANDARD TABLE OF sairport WITH DEFAULT KEY.

DATA: it_html TYPE html_table.
**********************************************************************
*
* Eventhandler
*
**********************************************************************
CLASS lcl_html_events DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      on_sapevent FOR EVENT sapevent OF cl_gui_html_viewer
        IMPORTING
            action
            frame
            getdata
            postdata
            query_table
            sender.
ENDCLASS.

CLASS lcl_html_events IMPLEMENTATION.
* Link- oder Button-Klick
  METHOD on_sapevent.
    IF o_salv IS BOUND.
      TRY.
* Element in der Nodes-Tabelle lesen
          ASSIGN query_table[ name = co_carrid ] TO FIELD-SYMBOL(<carrid>).
          ASSIGN query_table[ name = co_airpto ] TO FIELD-SYMBOL(<airpto>).

          IF <carrid> IS ASSIGNED AND <airpto> IS ASSIGNED.
* iTab für SALV-Table neu aufbauen, abhängig von der Eingabe in den Formularfeldern
            it_salv_spfli = VALUE #( FOR <f> IN it_spfli WHERE ( ( carrid = <carrid>-value ) AND ( airpto = <airpto>-value ) )
                                     ( <f> )
                                   ).

          ENDIF.

* SALV-Table neu anzeigen
          o_salv->refresh( ).
        CATCH cx_root INTO DATA(e_txt).
          MESSAGE e_txt->get_text( ) TYPE 'S'.
      ENDTRY.
    ENDIF.
  ENDMETHOD.
ENDCLASS.
**********************************************************************
*
* SELECTION-SCREEN
*
**********************************************************************
* leeres Selektionsbild als Dummy für die Container-Objekte (o_splitter_main ...)
SELECTION-SCREEN BEGIN OF SCREEN 2000.
SELECTION-SCREEN END OF SCREEN 2000.
**********************************************************************
*
* INITIALIZATION
*
**********************************************************************
INITIALIZATION.
* Daten holen
  SELECT * INTO TABLE it_spfli FROM spfli.
  SELECT * INTO TABLE it_sairport FROM sairport.

* HTML-Code mit Buttons und Forms
  it_html = VALUE #(
                     ( |<html>| )
                     ( |  <head>| )
                     ( |    <meta http-equiv="content-type" content="text/html; charset=utf-8">| )
                     ( |    <script type="text/javascript">| )
                     ( |      function onKeyDown(f)| )
                     ( |      \{ if(event.keyCode == 13)| )
                     ( |         \{ document[f.name].submit();| )
                     ( |         \}| )
                     ( |      \}| )
                     ( |    </script>| )
                     ( |    <style type="text/css">| )
                     ( |       body \{| )
                     ( |         font-family: Courier New;| )
                     ( |         font-size: 13px;| )
                     ( |         font-style: normal;| )
                     ( |         font-variant: normal;| )
                     ( |         font-weight: 400;| )
                     ( |         line-height: 13px;| )
                     ( |       \}| )
                     ( |       form \{| )
                     ( |         background: lightblue;| )
                     ( |       \}| )
                     ( |       label \{| )
                     ( |         text-align: right;| )
                     ( |         line-height: 1.5;| )
                     ( |         float: left;| )
                     ( |         width: 40%;| )
                     ( |       \}| )
                     ( |       input \{| )
                     ( |         margin: 0 0 1em .2em;| )
                     ( |         padding: .2em .5em;| )
                     ( |         background-color: #fffbf0;| )
                     ( |         border: 1px solid #e7c157;| )
                     ( |         float: left;| )
                     ( |         width: 40%;| )
                     ( |         font-family: Courier New;| )
                     ( |         font-size: 13px;| )
                     ( |         font-style: normal;| )
                     ( |         font-variant: normal;| )
                     ( |         font-weight: 400;| )
                     ( |         line-height: 13px;| )
                     ( |       \}| )
                     ( |       select \{| )
                     ( |         margin: 0 0 1em .2em;| )
                     ( |         padding: .2em .5em;| )
                     ( |         background-color: #fffbf0;| )
                     ( |         border: 1px solid #e7c157;| )
                     ( |         float: left;| )
                     ( |         width: 40%;| )
                     ( |         font-family: Courier New;| )
                     ( |         font-size: 13px;| )
                     ( |         font-style: normal;| )
                     ( |         font-variant: normal;| )
                     ( |         font-weight: 400;| )
                     ( |         line-height: 13px;| )
                     ( |       \}| )
                     ( |       button \{| )
                     ( |         margin-top: 1.5em;| )
                     ( |         float: right;| )
                     ( |       \}| )
                     ( |      </style>| )
                     ( |  </head>| )
                     ( |  <body>| )
                     ( |    <form name="INPUT" accept-charset="utf-8" method="post" action="SAPEVENT:INPUT_FIELDS">| )
                     ( |      <br>| )
                     ( |      <label for="{ co_carrid }">Fluggesellschaft:</label>| )
                     ( |      <input type="text" name="{ co_carrid }" value="LH" onKeyDown="onKeyDown(this.form);">| )
                     ( |      <br>| )
                     ( |      <label for="{ co_airpto }">Zielflughafen:</label>| )
                     ( |      <select name="{ co_airpto }" value="JFK">| )
                   ).

  LOOP AT it_sairport ASSIGNING FIELD-SYMBOL(<a>).
    IF <a>-id = 'JFK'.
      APPEND |        <option selected>{ <a>-id }</option>| TO it_html.
    ELSE.
      APPEND |        <option>{ <a>-id }</option>| TO it_html.
    ENDIF.
  ENDLOOP.

  it_html = VALUE #( BASE it_html
                     ( |      </select>| )
                     ( |      <br>| )
                     ( |      <button type="submit" title="Anzeigen">Anzeigen</button>| )
                     ( |      <br>| )
                     ( |      <br>| )
                     ( |      <br>| )
                     ( |      <br>| )
                     ( |    </form>| )
                     ( |  </body>| )
                     ( |</html>| )
                   ).

**********************************************************************
*
* AT SELECTION-SCREEN OUTPUT
*
**********************************************************************
AT SELECTION-SCREEN OUTPUT.
* Doppelte Objekterzeugung unterbinden
  IF NOT o_splitter_main IS BOUND.
* Splitter auf default_screen (SELECTION-SCREEN 2000) erzeugen
    o_splitter_main = NEW #( parent  = cl_gui_container=>default_screen
                             rows    = 1
                             columns = 2 ).

* Breite in % (linke Spalte für den Tree)
    o_splitter_main->set_column_width( id = 1 width = 20 ).

* linken und rechten Splitcontainer holen
    o_container_l = o_splitter_main->get_container( row = 1 column = 1 ).
    o_container_r = o_splitter_main->get_container( row = 1 column = 2 ).

* HTML-Viewer
    o_html = NEW cl_gui_html_viewer( parent = o_container_l ).

* Eventtypten müssen gesondert registriert werden
    DATA(it_events) = VALUE ty_it_events( ( eventid    = cl_gui_html_viewer=>m_id_sapevent
                                            appl_event = abap_true ) ).

    o_html->set_registered_events( events = it_events ).

* Eventhandler registrieren
    SET HANDLER lcl_html_events=>on_sapevent FOR o_html.

* URL zu HTML holen
    DATA: lv_url TYPE skwf_url.

    o_html->load_data( IMPORTING
                         assigned_url = lv_url
                       CHANGING
                         data_table   = it_html ).

* HTML anzeigen
    o_html->show_url( url = lv_url ).

    it_salv_spfli = it_spfli.

* SALV-Table für Anzeige der Daten
    cl_salv_table=>factory( EXPORTING
                              r_container    = o_container_r
                            IMPORTING
                              r_salv_table   = o_salv
                            CHANGING
                              t_table        = it_salv_spfli ).

    o_salv->get_display_settings( )->set_striped_pattern( abap_true ).
    o_salv->get_columns( )->set_optimize( abap_true ).
    o_salv->get_functions( )->set_all( ).
    o_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>row_column ).
    o_salv->display( ).
  ENDIF.
**********************************************************************
*
* AT SELECTION-SCREEN
*
**********************************************************************
AT SELECTION-SCREEN.
* wenn "Ausführen" (F8) geklickt wurde
  IF sy-ucomm = 'CRET'.
* Selektionbild 2000 wieder anzeigen
    CALL SELECTION-SCREEN 2000.
  ENDIF.
**********************************************************************
*
* START-OF-SELECTION
*
**********************************************************************
START-OF-SELECTION.
* leeres Selektionbild 2000 anzeigen
  CALL SELECTION-SCREEN 2000.