Real Web Services With REST and ICF
Real Web Services With REST and ICF
Real Web Services with REST and ICF SAP Developer Network
Summary
Content Options
DJ Adams is not an SAP employee and the opinions he expresses in this article do not reflect the official opinion or positioning of SAP. E-mail
This
Learn about the Internet Communication Framework, and how to harness it to write your own web services in SAP. Break free from the Document
stranglehold of complexity that SOAP has upon you, and start building real web services with HTTP. Email it
Print
By Dj Adams This
Article
22 Jun 2004 Print it
Add To
Favorites
Add it to
Web Services and SOAP Favorites
There's a lot of hype about SOAP and associated WS-* initiatives being the panacea of data interop over the web. But while the most common
transport of SOAP messages is HTTP, can SOAP be really classed as a 'web' service? There's really nothing web-like about SOAP, except that in
many cases, the standard HTTP port is used -- 'hijacked', almost -- as a tunnel through which to transfer opaque messages to and from a single non-
specific endpoint.
Ok, that was perhaps a little harsh. But as the saying goes, truth hurts. When I think of SOAP I feel very distant from the information I'm trying to
manage. There are just too many mechanisms and far too much protocol in the way. Why employ an application protocol (such as HTTP) and then
only use it to transfer messages between meaningless addresses? It's like buying a car and only using it to store things in.
Let's think about what web services are about. They're about the sharing and exchange of data over the web. What's the biggest, most scalable,
most interoperable, most widely used, most successful example of that? Yes, surprise surprise, it's the web itself. And what powers the web? The
Hypertext Transfer Protocol. HTTP. Sure, there are plenty of key team members that go to make up the full experience ((X)HTML, MIME, CSS,
and so on), but the one essential ingredient that makes everything work, is the application protocol -- not transport protocol -- HTTP.
So if web services are about sharing and exchanging data, what are we trying to achieve? We're trying to open up the data and functionality inside
our SAP systems, in a controlled manner of course, to share with our partners and customers.
Look at this in the context of SOAP. You have an element (or elements) of data that you want to share with your customers. Who in their right
mind would wrap a CORBA-wannabe function call in angle brackets, send the request to an opaque endpoint, expect the receiver to blindly accept
it, and then expect to have to start an XML parser just to get at the reply? People building job security, perhaps? Or with too much time on their
hands?
And another thing. When you're reading or writing data with SOAP, you're not actually addressing that data directly, are you? All you've got is the
address of some faceless SOAP server location with which you must interact. But surely, if an element of data is important enough to share with
your customer, isn't it also important enough to have its own address -- its own URL?
I pointed out earlier that HTTP is an application protocol, not a transport protocol. That's an important distinction to make here. HTTP is not just
about transporting payloads around. Let's look briefly at what HTTP is all about.
HTTP allows you to manage data, in that it allows you to read (retrieve), write, update and delete it. And as any SQL guru will testify, those are the
four essential fundamentals in any data manipulation mechanism. In HTTP there are verbs and nouns. The verbs -- GET (read), PUT (write),
POST (update), DELETE (ahem, delete) -- reflect the actions that can be carried out on the nouns. The nouns are URLs.
Looking at this in the context of, say, a page of HTML, we could have the following simple scenarios as shown in Figure 1.
With MIME types allowing us to specific and handle content types of all kinds, HTTP is arguably all we need as a flexible application protocol to
provide all kinds of web services.
Part of the appeal of using real web services is that HTTP is unadorned. What you see is exactly what you want. And what you want, you can get
using a huge array of tools, on a wide range of platforms. HTTP is probably one of the most widely ported application protocols around. There are
all sorts of ways you can construct and execute HTTP requests and parse responses. There are command line tools, like GET and POST (that come
with the fantastic LWP library), which mean that you can combine HTTP requests with your essential shell scripting and pipe-work. There are
libraries for almost every language under the sun. There are even text-mode web browsers that double up as generic HTTP tools. And one of the
most attractive side-effects of HTTP's ubiquity is that there's a plethora of debugging tools out there.
Finally, it's worth giving a thought to our poor firewall administrators, whose job it is to analyse traffic and control what enters the enterprise
network. With SOAP, the firewall administrator's toolset is about as much use as a chocolate teapot when it comes to helping police SOAP
Firstly, in many cases, all SOAP requests, whether they have read-only intentions or write intentions, go to the same endpoint (URL), so no
distinction can be made based on the action (verb) or object (noun). Secondly, even if the firewall software did take the time and effort to open up
the SOAP payload (arguably not the firewall's right), fire up an XML parse to interpret it, and look at the method call being issued, who's to say,
based on the arbitrary name of that method call, what the intention was? (There is the SOAPAction header which is supposed to help here; but for
one thing, it's optional, and for another, even if it was mandatory, the value supplied is as good as arbitrary).
With HTTP, the firewall administrator's job is easier. What's the verb? POST? Ok. What's the noun? /some/path/to/a/data/element. Ok, not allowed.
Job done.
So, I'd like to balance out this fighting talk with a very simple example of a real web service, implemented using the Internet Communication
Framework (ICF). Before we do, let's look at the context in which the example will sit.
The ICF is an essential part of the HTTP framework built into an SAP system. While the Internet Communication Manager (ICM) does the actual
low-level handling of HTTP requests (with kernel-level code), it's the ICF that exposes the ICM features to us, the lowly ABAP programmers,
giving us a fantastic platform on which to build HTTP applications. It gives us a set of core interfaces and classes representing the fundamental
objects in any web server application development environment - the web server itself, the request object, the response object, utilities for
manipulating HTTP data, and so on.
For more information about the ICF, see the relevant section in the online SAP documentation at http://help.sap.com.
With the ICF, you build web applications - web services - by writing a so-called handler. A handler is simply a piece of code, in the form of a class,
that's called by the HTTP framework for requests made to certain URLs. The code interprets the request, and constructs the response. Where does
the BSP technology fit in all of this? Well, the BSP is 'just' another handler, class CL_HTTP_EXT_BSP, pre-written and provided by SAP, a
handler that is called in the case of requests to certain URLs (starting /sap/bc/bsp). It just so happens that the BSP handler is hugely powerful and
is a complete MVC-powered development world within itself - kudos, chaps!
So let's build a simple handler class to allow authenticated HTTP requests to retrieve (GET) information from the Correction and Transport System
(CTS). I've developed this example in my copy of the free WAS 6.40 testdrive system 'NW4', which is Basis-only, meaning I'm bound to use some
Basis-orientated data unless I develop some custom applications myself. To keep things simple, I'm going to use CTS data.
We're just interested in exposing basic attributes of transport requests and tasks - who the owner is, what type it is, the date, and the short
description. That sort of thing. The exposure will be made through URLs that have this pattern:
http://servername:port/some-path/transport/<requestname>/<attribute>
For simplicity (and to save lookup tables or dynamic jiggery-pokery in the example code), we're going to use the field names from tables E070
(CTS Request / Task Header table) and E07T (CTS Request / Task Short Text table).
So for example, to retrieve the owner of request NW4K900005, all that is needed is an HTTP GET on the following URL:
http://shrdlu.local.net:8000/qmacro/transport/Y6BK039552/as4user
Note that each element of data has its own address. Each element is a noun in the context of HTTP.
The host on which my NW4 system runs is 'shrdlu.local.net', the ICM is listening for HTTP requests on port 8000, and I've defined a namespace
'qmacro' in SICF within which I develop all my test services. Under that namespace I have defined a service object 'transport', for which the
handler class Z_CL_TRANSPORT_INFO is defined. This can be seen in Figure 2.
Within the handler class we have the code to identify what transport is being requested ('Y6BK039552') and what attribute ('as4user'). The handler
will retrieve the relevant details, and return them in response. Figure 3 shows how that data can be retrieved on the command line, using the GET
command. The owner of request Y6BK039552 in this case is FILDEBRANDT. Figure 4 shows a similar piece of data retrieved via the web
browser.
Password: ******
FILDEBRANDT
Method IF_HTTP_EXTENSION~HANDLE_REQUEST
Classes that are to act as ICF handlers must implement interface IF_HTTP_EXTENSION. This interface has a single method,
HANDLE_REQUEST. It's in this method that you write the ABAP code to work out what's being requested, and what to do to respond to that
request. Figure 5 shows the code in class Z_CL_TRANSPORT_INFO's method IF_HTTP_EXTENSION~HANDLE_REQUEST.
METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST .
* [DATA DEFINITION]
TYPES:
begin of transinfo,
end of transinfo.
DATA:
* [INTERPRET REQUEST]
* [RETRIEVE DATA]
e070~as4date e07t~as4text
FROM e070
INTO transportinfo
IF sy-subrc ne 0.
code = '404'
CONCATENATE '<html>'
'<head>'
'</head>'
'<body>'
'<h1>Transport '
w_trkorr
'</body>'
'</html>'
INTO w_body.
EXIT.
ENDIF.
* [SELECT ATTRIBUTE]
SEPARATED BY '-'.
IF sy-subrc ne 0.
code = '404'
CONCATENATE '<html>'
'<head>'
'</head>'
'<body>'
'<h1>Attribute '
w_attr
' not found</h1>'
'</body>'
'</html>'
INTO w_body.
EXIT.
ENDIF.
* [STORE ATTRIBUTE]
w_body = <fs>.
name = 'Content-Type'
ENDMETHOD.
Step By Step
Let's take the code in Figure 5 step by step. The code is deliberately simplistic and sequential to make it easier to go through in stages. The
subheadings that follow refer to the sections of the same name in the code comments.
DATA DEFINITION
Here we define a structure to hold the CTS data that we're going to retrieve, a few working fields, and a field symbol to use when selecting the
requested attribute later on.
INTERPRET REQUEST
For every incoming HTTP request, a 'server control block' is made available to the handler method in the form of a CL_HTTP_SERVER object.
This object has two significant attributes, one of which is the HTTP request object (REQUEST). We can retrieve all sorts of information about the
incoming HTTP request from this, such as the path information from the URL. In this case, this is anything following what's been defined in SICF
for which this handler is specified (see Figure 2), i.e. "/default_host/qmacro/transport".
retrieves the path information, which, from the example above, is "/Y6BK039552/as4text". Note that ~path_info isn't really a proper HTTP header
field; in the ICF context it's one of the so-called 'pseudo' header fields, which are just bundled in with the rest of the real header fields for
convenience of access.
Once retrieved, the path information is split into transport number and attribute.
RETRIEVE DATA
Now we've got the desired transport number, we can read tables E070 and E07T for the information.
If the read failed, we assume the request or task cannot be found, and so must respond accordingly. The HTTP response object (RESPONSE) is the
other significant attribute of the control block and can be used to build the HTTP response. Here we do two things. We set the HTTP status, and
supply some friendly HTML in the body in case the request has been made from a browser.
Calling the set_status() method of the response object allows us to specify the HTTP status code and reason. We're setting the well-known "Not
found" status code 404 in this case, with a meaningful reason text.
Following that, some HTML is concatenated and placed in the response body with the set_cdata() method.
Figure 6 shows the response in a web browser to a request for an unknown transport.
SELECT ATTRIBUTE
At this stage it's time to select the attribute. Although not absolutely necessary, we're translating the attribute name to upper case before using a
dynamic assign to point to the right place in the transportinfo structure.
Of course, if an invalid attribute name was specified in the URL, the assign is going to fail, so we respond accordingly, in a similar manner to how
we responded in the case of a request or task not being found. We send back status code 404, with a short explanation of what happened.
STORE ATTRIBUTE
If the attribute was valid, and the assign was successful, then we can retrieve the attribute value from the structure.
We're on the home straight now; all that's needed is to build the response. Here, because the data we're sending back is straightforward text, we set
the content type of the response appropriately. The set_header_field() method of the response object is the antithesis of the get_header_field()
method of the request object, in that it's used to set a header field in the outgoing HTTP response.
In the same way that we used the set_cdata() method to put some HTML in the 404 response, we now use it to place the attribute value in the
HTTP body.
Where to next
For a start, how about dealing with the fact that we really want to avoid people interacting with the transport info URLs using anything other than
GET. It's a fairly straightforward matter to add a bit of code into the "INTERPRET REQUEST" section as shown in Figure 7.
* [INTERPRET REQUEST]
if verb NE 'GET'.
name = 'Allow'
value = 'GET' ).
code = '405'
EXIT.
ENDIF.
Here we use the get_header_field() method to retrieve the actual HTTP verb used. It's stored, like the path info, as a pseudo HTTP header. (The
new variable 'verb' is defined in the DATA DEFINITION section as TYPE string). We only want to allow GET, and so reject all others by sending
a status code of 405 in the response, indicating that the method used was not allowed. The HTTP 1.1 specification states that when you return a
status of 405 you must also return a header field in the response, to list the methods that are valid and acceptable for the resource (URL) in
question. This is achieved using the set_header_field() method as shown, before setting the status and reason with set_status(). Figure 8 shows a
typical response, with header lines, to an attempt to POST some data to a transport info URL. (In this case, we're supplying the basic authentication
information via the -C parameter.)
[dj@hadrian dj]$ echo "SOAP sucks" | POST -Se -Cdeveloper:password http://shrdlu.local.net:8000/qmacro/transport/Y6BK039552/trstatus
Allow: GET
Content-Length: 114
Client-Response-Num: 1
<HTML>
<BODY>
</BODY>
</HTML>
A Note on REST
The title of this article mentioned REST. What is REST? It stands for "REpresentational State Transfer" and is an architectural style, not a
protocol per se. The term was coined by Roy Fielding (one of the authors of the HTTP 1.1 specification) in his Ph.D. dissertation looking at the
model of web architecture and defining a framework for understanding that architecture (see the dissertation abstract for more details).
REST's premise is that HTTP has everything we need to implement web services. The ideas and example in this article are driven by the REST
philosophy.
You can read more on REST by following links from the Portland Pattern Repository's RestArchitecturalStyle wiki page.
Final Thoughts
Whether or not you're a fan of SOAP, I hope this has provided food for thought, and showed that there are plenty of facilities at your disposal in
SAP for building Real Web Services.
Copyright © 2005 SAP AG, Inc. All Rights Reserved. SAP, mySAP, mySAP.com, xApps, xApp, and other SAP products and
services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP AG in Germany and in
SAP Developer Network
several other countries all over the world. All other product, service names, trademarks and registered trademarks mentioned are
the trademarks of their respective owners.