René van Mil

SAP Gateway on iOS With AFNetworking

SAP Gateway is an ABAP framework part of SAP NetWeaver which allows you to create RESTful OData webservices on the SAP system. Data can be transmitted in either JSON or XML format. It’s a great solution to integrate SAP as a backend system into mobile or web applications (as opposed to the complexity and rigidity of PI and SOAP services).

To demonstrate how to use SAP Gateway to integrate SAP into an iOS 7 app, I have created a simple webservice for the well-known SFLIGHT demo data (make sure to run the SAPBC_DATA_GENERATOR program to generate some demo data first) in SAP, and an iOS 7 app which displays the data.

Gateway Webservice

SAP Gateway uses so-called abstract ‘data provider’ and ‘model provider’ classes, inheriting from a bunch of other classes in the framework, which need to be implemented by you. The good news is a lot of this stuff will be generated once and implementing the custom parts is easy.

To create the model for this demo, start transaction SEGW and create a new project named ZSFLIGHTGW. Open the project and follow these steps:

1. Create an Entity Type named Flights and enter SFLIGHT as the ABAP structure name

2. Add the following properties:

1
2
3
4
5
6
Name      Key  Edm Core Type  ABAP Field
CARRID    X    Edm.String     CARRID
CONNID    X    Edm.String     CONNID
FLDATE    X    Edm.DateTime   FLDATE
PRICE          Edm.Decimal    PRICE
CURRENCY       Edm.String     CURRENCY

3. Create an Entity Set named FlightSet

4. Click the ‘Generate Runtime Objects’ button (the red-white circle) and accept the defaults

5. After generation, you should be able to see the generated service implementation and its operations

In this demo we will only read a list of SFLIGHT records, so the only operation which needs to be implemented is the GetEntitySet (Query) operation. As you can see on the previous screenshot, the operation can be implemented with class ZCL_ZSFLIGHTGW_DPC_EXT in the FLIGHTSET_GET_ENTITYSET method.

6. Implement the FLIGHTSET_GET_ENTITYSET method:

FLIGHTSET_GET_ENTITYSET method
1
2
3
method flightset_get_entityset.
  select * into table et_entityset up to 1000 rows from sflight.
endmethod.

7. Create the webservice (with SAP customizing transaction SPRO) as shown on these screenshots:

The webservice is now ready and you should be able to reach it at https://yoursapserver.local:<port>/sap/opu/odata/sap/zsflightgw_srv?sap-client=<client> (replace <port> and <client> with proper numbers for your system). Performing a GET request on that url (make sure you provide the correct credentials through HTTP basic authentication) should return something like this:

Adding the parameter $format=json will have the server return a response in JSON format. So if you prefer to work with JSON instead of XML, you should always add this parameter to every request.

iOS App

The app will show a list of flights and when tapping one of the flights it will display the details of that flight.

To perform the HTTP requests to SAP Gateway I use the AFNetworking framework. It makes performing HTTP requests a lot easier than the standard iOS SDK, and it powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac.

The SAPGatewayClient class provides the following method to retrieve all flights using SAP Gateway:

List the flights using AFNetworking
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)listFlightsWithSuccess:(void (^)(NSMutableArray *flights))success
                       failure:(void (^)(NSError *error))failure;
{
    /*
     The URL to SAP Gateway, which we'll send the GET request to. We want to list the entire FlightSet.
     Make sure to add the '$format=json' parameter, so the response will be formatted in JSON
     */
    NSString *urlString = [NSString stringWithFormat:@"%@FlightSet?$format=json", baseUrl];
    /*
     Perform the GET request.
     If the request succeeds, the list of flights is read from the response. SAP Gateway always returns a single JSON object, which always contains a single property called 'd', which always contains a single array called 'results'.
     For this call the 'results' array contains all the flights.
     After reading the flights array, the success callback is executed providing the retrieved array of flights.
     */
    [[self manager] GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSMutableArray *flights = [[(NSMutableDictionary *)responseObject valueForKey:@"d"] valueForKey:@"results"];
        if (success) {
            success(flights);
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"%@", error);
        if (failure) {
            failure(error);
        }
    }];
}

And that is all you need to integrate SAP into your iOS app using SAP Gateway. Everything else is just common viewcontroller code and a storyboard. You can find the complete iOS project and source code on GitHub: SAP Gateway App Demo.

Parse.com Push and SAP ABAP

Parse Push is a service which allows you to send push notifications to iOS, Android and Windows devices. An easy to use REST API is provided, which allows you to send push notifications from an SAP system to devices running your app. The REST API is especially useful when compared to the Apple Push Notification Service. APNS uses a streaming TCP socket which is not supported by SAP ABAP.

In this blog post I will describe how to configure SAP to use the Parse Push service, and will provide an example ABAP program which will send a push notification.

Setup Parse Push

Create an account and follow the instructions provided by Parse. Everything is really well documented, so you should be up and running quickly.

Create a test app

Create an app and implement the Parse SDK. Make sure you are able to receive test notifications sent from the Parse dashboard.

Configure SAP

Step 1 – STRUST

To allow SAP to perform HTTPS requests, the SSL Client (Anonymous) must be created. Run transaction STRUST and if the anonymous SSL client PSE is not listed yet, simply create a new one.

Since SAP does not come with preinstalled root certificates, the root certificates used by Parse must be manually installed.

  • You can download the certificates from DigiCert. Download both the DigiCert High Assurance EV Root CA and the DigiCert High Assurance CA-3.
  • Run transaction STRUST and select the anonymous SSL client PSE
  • Select the menu Certificate > Import and select the downloaded certificate file
  • After importing the certificate details should appear in the lower part of the screen. Click the Add to certificate list button, and repeat for the second certificate file

When you’re done with this step, the STRUST transaction should more or less look like this:

Notice that both DigiCert certificates are listed.

Step 2 – SM59

An RFC destination allows you to easily access the Parse Push REST API from your ABAP code. Create a destination with the following settings:

  • Name: PARSEPUSH
  • Connection Type: G
  • Target host: api.parse.com
  • Service No.: blank
  • Path Prefix: /1/push
  • SSL: Active
  • SSL Certificate: ANONYM

Technical Settings:

Logon Security:

Example ABAP Program

Below is an example of a simple ABAP program which sends an “abap test” push notification to iOS devices. It uses the SAP cl_http_client class to execute the HTTPS request for sending a push notification.

Make sure you use the correct application id and REST API key. You can find these on the Parse dashboard under your app’s settings > application keys.

Example program to send a push notification with Parse.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
report zparsepush.

data: client type ref to if_http_client,
      success type abap_bool.

" Create HTTP client
call method cl_http_client=>create_by_destination
  exporting
    destination        = 'PARSEPUSH'
  importing
    client             = client
  exceptions
    argument_not_found = 1
    plugin_not_active  = 2
    internal_error     = 3
    others             = 4.
if sy-subrc = 0.

  " Setup HTTP request
  client->request->set_method( 'POST' ).
  client->request->set_content_type( 'application/json' ).
  client->request->set_header_field( name = 'X-Parse-Application-Id' value = 'your_application_id_see_parse_dashboard' ).
  client->request->set_header_field( name = 'X-Parse-REST-API-Key' value = 'your_rest_api_key_see_parse_dashboard' ).
  client->request->set_cdata( '{ "where": { "deviceType": "ios" }, "data": { "alert": "abap test", "badge": 0, "sound": "" } }' ).

  " Send request
  call method client->send
    exceptions
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3
      http_invalid_timeout       = 4
      others                     = 5.
  if sy-subrc = 0.

    " Receive response
    call method client->receive
      exceptions
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3
        others                     = 4.
    if sy-subrc = 0.

      " Read response
      data status_code type i.
      client->response->get_status( importing code = status_code ).
      if status_code = '200'.
        success = abap_true.
      endif.

    endif.

    " Close connection
    call method client->close
      exceptions
        http_invalid_state = 1
        others             = 2.

  endif.

endif.

if success = abap_true.
  write: 'Notification sent!'.
else.
  write: 'Something went wrong :('.
endif.

Building a Backbone.js Web Application With an SAP ABAP Backend

In this blog post I will describe how to build a Backbone.js web application, which connects to an SAP ABAP backend API over a RESTful JSON interface.

Requirements

Stateless API

The RESTful API provided by the SAP system must not store any state on the SAP server itself. This means there won’t be a session, and each request to one of the web services must include proper credentials.

Allow the user to login/logout

Even though the API is stateless, the user must be able to login to the web application by providing a username and password. And even though there won’t be a session, the user must also be able to logout from the web application.
Also, the web application should allow the user to remember the entered username on the login form, and the user should be logged out automatically after a certain period of inactivity.

Host the web application on any web server

The web application must be a JavaScript application which runs inside the client’s browser and communicates directly with the API provided by SAP. There must not be any server-side scripting (PHP, ASP, etc.), and no middleware must be required.

Preparation

Webserver software

The easiest way to get this demo to work, is to run it on a local webserver. I prefer nginx because it’s easy to setup and configure a reverse proxy to an SAP system.
nginx.

JavaScript libraries

The following libraries are used in the web application. You do not have to download these though, since they are already included with the demo web application.
Backbone.js
Bootstrap
Font Awesome
jQuery
jQuery Cookie
Underscore.js

Step 1 – Setup a webserver

Install nginx and use the provided default configuration.
Add a /sapdemo location which will serve as a reverse proxy to your SAP system:

reverse proxy configuration
1
2
3
location /sapdemo {
  proxy_pass http://mysapsystem.local:8000/sap/zdemo;
}

  Note that for this demo we will not be using HTTPS, but be aware that using HTTPS is absolutely mandatory when building a web application for productive use. The reason is the username and password will be sent with each HTTP request to the API, so you must use HTTPS to protect these credentials from being intercepted by a potential attacker.

Step 2 – Build the API with ABAP

For this demo we will build 2 resources. An auth resource used to simulate logging in, and a user resource which provides the full name of the logged in user.

  Note that this process can be simplified a lot by using my REST framework included in my ABAP OOP Library, so you might want to check that out later. For the demo this library is not required though.

Auth Resource

Create a class zcl_demo_auth and implement the if_http_extension interface:

Implementation for the handle_request method.
1
2
3
4
5
6
method if_http_extension~handle_request.
  " Return an empty json object with status code 200 to indicate authentication was successful
  server->response->set_content_type( 'application/json' ).
  server->response->set_cdata( '{}' ).
  server->response->set_status( code = 200 reason = '' ).
endmethod.

User Resource

Create a class zcl_demo_user and implement the if_http_extension interface:

Implementation for the handle_request method.
1
2
3
4
5
6
7
8
9
10
11
12
method if_http_extension~handle_request.
  " Send user id and full name in json format
  data: persnumber type usr21-persnumber,
        name_text type adrp-name_text,
        json_user type string.
  select single persnumber into persnumber from usr21 where bname = sy-uname.
  select single name_text into name_text from adrp where persnumber = persnumber.
  concatenate '{ "id": "' sy-uname '", "fullName": "' name_text '" }' into json_user.
  server->response->set_content_type( 'application/json' ).
  server->response->set_cdata( json_user ).
  server->response->set_status( code = 200 reason = '' ).
endmethod.

ICF Configuration

  1. Start transaction SICF and press F8 to go to the list of web services.
  2. Expand the default_host node and then expand the sap node.
  3. Right-click the sap node and select the New Sub-Element menu option.
  4. Type the name zdemo and click the ok button.
  5. Enter demo in the Description 1 field and click the save button.
  6. Press F3 to go back to the services list.
  7. Right-click the zdemo node and select the New Sub-Element menu option.
  8. Type the name auth and click the ok button.
  9. Enter auth in the Description 1 field.
  10. Select the Logon Data tab and select the option Alternative Logon Procedure.
  11. Scroll down to the Logon Procedure List and remove all procedures except the Logon through HTTP fields procedure.
  12. Select the Handler List tab and enter zcl_demo_auth as the handler.
  13. Save and go back to the services list.
  14. Repeat steps 7-13 for to create a user node with zcl_demo_user as its handler.
  15. Right-click the zdemo node and select the option Activate Service. On the popup click the second Yes button, activating the zdemo node and both the auth and user subnodes.

Step 3 – Build the Backbone.js web application

Clone the GitHub demo web application repository. The application can be built using Grunt, but before doing so you need to do the following:

  • Install Node.js and Grunt.
  • Install the Grunt plugins specified in the package.json file.
  • Edit the Gruntfile.js and adjust the dest path inside the distToNginx copy task. It will currently point to the /opt/local/share/nginx/html/demo/ path, and it must be changed to the html path of your nginx installation (do not remove the /demo/ part though).
  • Edit the api.js file (inside the src/js/helper folder) and change the value of sap-client to the client number of the SAP system you are using for this demo.

Build the application by running the command grunt debug (or run grunt dist if you want the JavaScript code to be minified).

After building the application you will be able to run it from http://localhost/demo (make sure nginx is running).