DMS documents via HTTP service
Hi everyone,
A few weeks ago, I was looking for ways to get documents (pictures, PDF files) from SAP DMS to my iPhone. This might be possible via a direct connection between the content server and the iPhone. However, I didn't like exposing the content server in this way and was hoping to use some kind of Internet service in the R/3 system.
It turned out that this was quite easy to do with a custom REST service. It came with a nice bonus; I can also use the service in my browser and it will start the appropiate program (Acrobat Reader, Powerpoint, ...) as well. The last part depends on the correct configuration of DMS customizing, I will show you at the end of the blog how to do that.
The requirement
The requirement was quite simple. I want an URL, similar to the RFC via SOAP URLs, to which I can pass an HTTP request with the key of the DMS document. I want response to contain the (binary) content of the document. Documents in DMS can be identified uniquely using the four key fields:
- Type
(DRAW-DOKAR)
- Number
(DRAW-DOKNR)
- Version
(DRAW-DOKVR)
- Part
(DRAW-DOKTL)
The simplest way of passing the document key is via an HTTP GET call, where the parameters are appended to the URL. So if the service URL is http://<sapserver>:<port>/sap/bc/zdoc
, the GET call is http://<sapserver>:<port>/sap/bc/zdoc?type=<type>&number=<number>&version=<version>&part=<part>
Creating the service
You can create custom HTTP services in transaction SICF. In the selection screen, choose Execute (F8) and the following screen will appear:
Within the tree, you will find some frequently used services. For example, BSP pages can be found under path default_host/sap/bc/bsp/sap/
, while WebDynpros reside under default_host/sap/bc/webdynpro/sap/
. The path in the tree corresponds to the resulting URL of the service.
To create a new service, right-click on its parent and choose New Subelement from the context menu.
Dismiss the popup with a warning that SAP upgrades might overwrite the new service. A new popup appears where you must provide the name of the service:
After you choose Input (Enter), a screen appears where you can enter more information. At this moment, nothing is needed but you can enter a simple description. Choose Store (Ctrl + S) afterwards; because a service is a repository object you must either declare it as local or put it in a transport request.
When you go back, you can see the service in the tree. It is still gray because it isn't active yet.
Creating a handler
The next step is to create ABAP code which handles a call to the zdoc
service. For this, we need a class which implements the IF_HTTP_EXTENSION
interface. Start transaction SE24, enter a class name and choose Create (F5).
In the next popup, enter a description and choose Save (Enter). Like with the service, a popup will appear for choosing a transport request.
On the tabpage Interfaces, type IF_HTTP_EXTENSION
on the first line and press Enter.
If you go to tabpage Methods, you will see the interface method HANDLE_REQUEST. Double-click it, and confirm the popup that you want to save the class.
Replace the method with the following code:
METHOD if_http_extension~handle_request. DATA: lr_request TYPE REF TO if_http_request, lr_response TYPE REF TO if_http_response, lv_value TYPE string, lv_data TYPE xstring, ls_draw TYPE draw, ls_checkout_def TYPE dms_checkout_def, ls_doc_file TYPE dms_doc_file, ls_phio TYPE dms_phio, ls_file TYPE cvapi_doc_file, lt_files TYPE TABLE OF cvapi_doc_file, lt_content TYPE TABLE OF drao. FIELD-SYMBOLS <fs_content> TYPE drao. * 1. lr_request = server->request. lr_response = server->response. IF lr_request->get_method( ) EQ 'GET'. * 2. * Retrieve document key lv_value = lr_request->get_form_field( 'type' ). ls_draw-dokar = lv_value. lv_value = lr_request->get_form_field( 'number' ). ls_draw-doknr = lv_value. lv_value = lr_request->get_form_field( 'version' ). ls_draw-dokvr = lv_value. lv_value = lr_request->get_form_field( 'part' ). ls_draw-doktl = lv_value. * 3. * Check document existence and read details CALL FUNCTION 'CVAPI_DOC_GETDETAIL' EXPORTING pf_dokar = ls_draw-dokar pf_doknr = ls_draw-doknr pf_dokvr = ls_draw-dokvr pf_doktl = ls_draw-doktl IMPORTING psx_draw = ls_draw TABLES pt_files = lt_files EXCEPTIONS not_found = 1 OTHERS = 2. IF sy-subrc NE 0. * Put the error in the response MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_value. lr_response->set_cdata( lv_value ). lr_response->set_status( code = 400 reason = 'Document not found!' ). EXIT. ENDIF. * 4. * Convert file information READ TABLE lt_files INTO ls_file INDEX 1. MOVE-CORRESPONDING ls_file TO: ls_doc_file, ls_phio. * Checkout document SELECT SINGLE kpro_use FROM tdwa INTO ls_checkout_def-kpro_use WHERE dokar EQ ls_draw-dokar. ls_checkout_def-comp_get = 'X'. ls_checkout_def-content_provide = 'TBL'. CALL FUNCTION 'CV120_DOC_CHECKOUT_VIEW' EXPORTING ps_cout_def = ls_checkout_def ps_doc_file = ls_doc_file ps_draw = ls_draw ps_phio = ls_phio TABLES ptx_content = lt_content EXCEPTIONS error = 1 OTHERS = 2. IF sy-subrc NE 0. * Put the error in the response MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_value. lr_response->set_cdata( lv_value ). lr_response->set_status( code = 400 reason = 'Failed to read document!' ). EXIT. ENDIF. * 5. * Put document content in response LOOP AT lt_content ASSIGNING <fs_content>. CONCATENATE lv_data <fs_content>-orblk INTO lv_data IN BYTE MODE. ENDLOOP. lv_data = lv_data(<fs_content>-orln). lr_response->set_data( lv_data ). lr_response->set_status( code = 200 reason = '' ). * 6. * Set MIME type SELECT SINGLE mimetype FROM tdwp INTO lv_value WHERE dappl EQ ls_file-dappl. IF sy-subrc EQ 0. lr_response->set_header_field( name = 'Content-Type' value = lv_value ). ENDIF. ENDIF. ENDMETHOD.
Some explanation of the code:
- The method
handle_request
has one parameter calledserver
. For us, only the HTTP request and the HTTP response are important. First of all, the HTTP method of the request is checked. - The document key is extracted from the URL. Note that the URL construct is regarded as a kind of form.
- The call of function module
CVAPI_DOC_GETDETAIL
serves two purposes: first, the existence of the document is checked; second, some of the details needed to view the document are retrieved. If there is an error, the error message is put into the body of the response (by methodset_cdata
) so the user will see it in the browser. The HTTP status is a kind of error/success code. - The document must be 'checked out' to an internal table, which is done by calling function module
CV120_DOC_CHECKOUT_VIEW
with the right parameters. Again, if there is an error, it is returned in the response. - The (binary) data in the internal table is put into a (binary) string of the correct length and put into the response. Note that we use the method
set_data
for binary data, instead ofset_cdata
which is suitable for character data only. - Your operating system can tell if a file is a picture or a PDF file by looking at its extension. Within HTTP, we don't have extensions, so we have to use another way to indicate the type of content. This is the MIME type. The MIME type should be maintained within DMS customizing, on something that is called a Workstation Application. Start transaction SPRO, and start customizing activity Cross-Application Components -> Document Management -> General Data -> Define Workstation Application. If you double click on a row, you can enter a mime type:
This is an important step, because the browser will not be able to select the right program or plugin to open the file. Otherwise, you might see just some unintelligible text.
Assign the handler to the service
After activating the handler class, you must indicate that requests to the ZDOC service are handled by this class. Return to transaction SICF and open the service (hint: you can enter the service name on the selection screen). Select tab Handler List, enter the class name and choose Store (Ctrl + S).
Return to the previous screen and choose Activate Service from the context menu. You'll have to confirm this in the popup that appears.
After activation, you can test the service by choosing Test Service from the same context menu. A browser window will open, asking you for your SAP credentials.
Notice that the SAP client is also a parameter in the URL. If you omit it, the request will be processed by the default client. The service will return some error message because you didn't specify a document yet.
To perform a real test, lookup the key of some DMS document in transaction CV03N.
If you extend the URL in the address bar with&type=<type>&number=<number>&version=<version>&part=<part>
so that it becomes something likehttp://10.150.1.232:8000/sap/bc/zdoc?sap-client=800&type=Z01&number=0000000000000010000000239&version=01&part=000
the document will appear on your screen. Please pay attention to the leading zeroes!