Additional Blogs by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member181879
Active Contributor
0 Kudos

BSP pages contain, effectively, HTML. When binary objects are requested, they are placed in the MIME repository and referenced from there. However, it is often necessary to handle binary objects/documents during the runtime of the program. It is not feasible to place these runtime documents in the MIME repository. (For any change in the MIME repository, transport records are written. This is usually not possible on a productive system, and is relatively slow compared to the runtime requirements of the running BSP application.)

Typical (real!) examples that we have seen:

    • For a personnel system, all colleagues' pictures are available in a database table and must be displayed as part of an HTML page.

    • To use an ActiveX object (examples are Flash or SVG plug-in), it dynamically generates XML data that is available for the plug-in (via HTTP).

    • Some internal data is converted into a PDF document that must be displayed in the browser.

It would be interesting to look at three different ways to use/do this:

0.1.

0.2. Assume we are somewhere on a BSP page and wish to display the complete document. Typical example: a button is pressed for a receipt, and then a PDF document must be displayed.

0.3.

0.4. Next, a slightly more complex example is to display the new document as part of an HTML page. This requires that the HTML page must be rendered back and then, on a second HTTP request, the document is fetched and displayed in the same page.

0.5.

0.6. The last approach is to open a new window and display the document in the window.

0.7.

We've taken on a large challenge today, so let's get cracking!

Test Harness

Our first step is to build a small test program to have a document available to display. As we don't feel like generating PDF documents on the fly or reading images from some database table, we'll just upload the test document. In all cases, we assume that either an image (.jpg, gif or .png) or some "known" document (.pdf, .doc, .xls, etc.) is specified.

After the document is uploaded, we have it "in our hands" and must do something with it. Keep in mind that after the response is processed, our session will be closed (stateless program).

First, we create a new BSP application and add a few page attributes:

<!code>  file_length    TYPE  STRING

<!code>  file_mime_type TYPE  STRING

<!code>  file_name      TYPE  STRING

<!code>  file_content   TYPE  XSTRING

<!code>  display_type   TYPE  STRING

<!code>  display_url    TYPE  STRING

The four file_* attributes reflect the dynamic document that we "created" via an upload. Note that the content is of type XSTRING because we are working with binary documents.

The next step is to write the BSP application that will do the upload. After many hours:

<!codeLayout:

<!code>   

<!code>  <%@page language="abap" %>

<!code>  <%@extension name="htmlb" prefix="htmlb" %>

<!code>   

<!code>  <htmlb:content design="design2002" >

<!code>    <htmlb:page>

<!code>      <htmlb:form method       = "post"

<!code>                  encodingType = "multipart/form-data" >

<!code>   

<!code>        <htmlb:radioButtonGroup id="display_type" >

<!code>          <htmlb:radioButton    id = "inline" text="Display Inline" />

<!code>          <htmlb:radioButton    id = "html"   text="Display Inside HTML Page" />

<!code>          <htmlb:radioButton    id = "window" text="Display In New Window" />

<!code>        </htmlb:radioButtonGroup>

<!code>   

<!code>        <htmlb:fileUpload id          = "myUpload"

<!code>                          onUpload    = "HandleUpload"

<!code>                          upload_text = "Display"

<!code>                          size        = "90" />

<!code>   

<!code>        -


<!code>       

<br>Name = <%= file_name%>

<!code>       

<br>MIME-Type = <%= file_mime_type%>

<!code>       

<br>Length = <%= file_length%>

<!code>   

<!code>      </htmlb:form>

<!code>    </htmlb:page>

<!code

<!codeOnInputProcessing:

<!code>   

<!code>  DATA: fileUpload TYPE REF TO CL_HTMLB_FILEUPLOAD.

<!code>  fileUpload ?= CL_HTMLB_MANAGER=>GET_DATA(

<!code>                         request = request

<!code>                         id      = 'myUpload'

<!code>                         name    = 'fileUpload' ).

<!code>   

<!code>  file_name      = fileUpload->file_name.

<!code>  file_mime_type = fileUpload->file_content_type.

<!code>  file_length    = fileUpload->file_length.

<!code>  file_content   = fileUpload->file_content.

<!code>   

<!code>  DATA: radioButtonGroup TYPE REF TO CL_HTMLB_RADIOBUTTONGROUP.

<!code>  radioButtonGroup ?= CL_HTMLB_MANAGER=>GET_DATA(

<!code>                         request = request

<!code>                         id      = 'display_type'

<!code>                         name    = 'radioButtonGroup' ).

<!code>   

<!code>  display_type = radioButtonGroup->selection.

The final results in the browser are:

</p>

Display Document Inline

With this simplest approach, we have an incoming HTTP request for an HTML page. Instead of processing the BSP page (thus rendering out HTML code), we just send out the dynamic document that we uploaded, so that it is displayed inline.

What do we have to do? Effectively, write the content that we already have available into the HTTP response and set the correct content data. Lastly, inform the BSP runtime that the response has been completely written, and that no further processing is required.

<!codeOnInputProcessing:

<!code>   

<!code>  ... code previously displayed above ...

<!code>   

<!code>  IF display_type = 'inline' AND XSTRLEN( file_content ) > 0.

<!code>    DATA: response TYPE REF TO if_http_response.

<!code>    response = runtime->server->response.

<!code>    response->set_data( file_content ).

<!code>    response->set_header_field( name  = if_http_header_fields=>content_type

<!code>                                value = file_mime_type ).

<!code>  " response->set_header_field( name  = if_http_header_fields=>content_length

<!code>  "                             value = file_length ).

<!code>    response->delete_header_field( name = if_http_header_fields=>cache_control ).

<!code>    response->delete_header_field( name = if_http_header_fields=>expires ).

<!code>    response->delete_header_field( name = if_http_header_fields=>pragma ).

<!code>    navigation->response_complete( ). " signal that response is complete

<!code>    RETURN.

<!code>  ENDIF.

The IF statement checks that it is the inline test and that we actually have content available to display. The set_data() method writes the complete XSTRING into the response. The "Content-Type" HTTP header field is set. This MIME type is critical so the browser knows what is coming down the pipe.

Theoretically we also want to set the "Content-Length" HTTP header field. However, we found a small bug in the kernel :(. If we set the content length, and the response is also gzip'd when it is streamed to the browser, the content length is not reset to the shorter length. Therefore the browser waits forever for more data that is not coming. So, we use a trick. We don't set the content length, and just let ICM set it after the gzip compression. (From our ICM expert: One additional remark I forgot to mention: it is good programming practice within our HTTP framework to <b>never</b> manually set the content length field. It is the kernel serialize code's task to determine the actual serialized length. This may depend on various side effects like compression, chunking, etc.)

One can also consider deleting the three HTTP headers "Cache-Control", "Expires" and "Pragma". As BSP pages are effectively HTML pages with business data, the BSP runtime already pre-set these HTTP headers to indicate that BSP pages must not be cached. However, we are now reusing the HTTP response for our binary document. We can delete these headers.

The last problem is that after the onInputProcessing method, the layout is also processed. This results in all output from the layout also being written into the response. Thus the response_complete() call, which informs the BSP runtime that the response is completed and no further processing is required.

After the BSP application runs, we select a nice photo and see the picture displayed directly inside the browser window.

Display Document Inside HTML Page

Displaying the document inside the HTML page is slightly more complex. The problem is that in the response we must write HTML coding for the browser to render the page. The HTML coding must reference the dynamic document that we have available at this very moment. So where should we park the dynamic document until the browser has time to fetch it? (Keep in mind that once the response has been processed, the session will be closed and we will lose everything we had on hand.)

The solution is actually very simple and elegant. The ICM supports an excellent HTTP cache. Whenever a MIME object is retrieved from Web AS, it's also added into the ICM cache. All other requests for the same document are served directly from the cache and do not require a switch to ABAP. These requests are processed in the kernel.

When we have the dynamic document on hand, we can just as well write it into the ICM cache. Thus, any HTTP requests for the document (actually for this specific URL!) will retrieve the document from the cache.

So the first interesting part of the code is to write the dynamic document directly into the ICM cache. The complete coding is:

<!codeOnInputProcessing:

<!code>   

<!code>  ... code previously displayed above ...

<!code>   

<!code>  IF display_type = 'html' AND XSTRLEN( file_content ) > 0.

<!code>   

<!code>    DATA: cached_response TYPE REF TO if_http_response.

<!code>    CREATE OBJECT cached_response TYPE CL_HTTP_RESPONSE EXPORTING add_c_msg = 1.

<!code>   

<!code>    cached_response->set_data( file_content ).

<!code>    cached_response->set_header_field( name  = if_http_header_fields=>content_type

<!code>                                       value = file_mime_type ).

<!code>    cached_response->set_status( code = 200 reason = 'OK' ).

<!code>    cached_response->server_cache_expire_rel( expires_rel = 180 ).

<!code>   

<!code>    DATA: guid TYPE guid_32.

<!code>    CALL FUNCTION 'GUID_CREATE' IMPORTING ev_guid_32 = guid.

<!code>    CONCATENATE runtime->application_url '/' guid INTO display_url.

<!code>   

<!code>    cl_http_server=>server_cache_upload( url      = display_url

<!code>                                         response = cached_response ).

<!code>    RETURN.

<!code>   

<!code>  ENDIF.

To write the information into the ICM cache, it's necessary to create a complete HTTP response. Keep in mind that the browser will later send an HTTP request for this document, to which the ICM cache will return the cached HTTP response directly.

First, we create a new HTTP response object and add a new message. (The message is the actual buffers required to move the document from the ABAP VM into the kernel.)

The next few lines were already discussed above. We set the content into the response and the content type. The set_status() call is required to indicate to the browser that for this request-response cycle everything went perfectly.

The next aspect is to set the time that the dynamic document will stay in the ICM cache. Keep in mind that this time should be long enough for the browser to load all URLs referenced in the HTML page. However, there is no need to leave the document in the ICM cache for too long. Here a value of 3 minutes (180 seconds) is used. Anything between 1 and 5 minutes should be OK.

The more difficult problem is the URL to use. This URL is effectively the "address" of the dynamic document on the server. The browser will later fetch the document from the server with this key.

The first idea was to use the uploaded filename as part of the URL. In this case , take care to replace the ':' and '/' characters in the URL to make it a new valid URL. However, such a static type of URL does not scale very well. What happens if different people are running the same application, and uploading the same generic document (example: "travel_expenses.xls")? Then each new response will overwrite the previous copy in the cache. Therefore, the recommendation is to use some form of random number (GUID)in the URL that is generated.

We could place the generated URL anywhere into the "namespace" of valid URLs. However, we recommend placing the URL into the "namespace" of the current active BSP application. Another nice touch you could consider is to also copy the document extension from the uploaded filename over into the URL. However, this is not critical. It's more important that the MIME type is set correctly in the HTTP response, which we already do.

The last step is to place the document into the ICM cache.

With the above coding, we successfully created a new HTTP response in the ICM cache that can be addressed under the URL stored in "display_url" (page attribute of type STRING). The last step is to change the rendered HTML coding to also display the uploaded document. For this we just use an .  The following HTML sequence is added in the layout, just before the end of the page.

<!codeLayout:

<!code>   

<!code>  ... code previously displayed above ...

<!code>   

<!code>   <% IF display_type = 'html' AND display_url IS NOT INITIAL. %>

<!code>     

<!code>      </iframe>

<!code>   <% ENDIF. %>

<!code>   

<!code>    </htmlb:page>

<!code

This is just an IF-guard to check for the specific case of displaying the document inline, plus the sequence to load the newly created URL.

The output is as expected. Both the data about the dynamic document and the document itself are displayed.

With this approach there are just two smaller problems that should not be forgotten. By default, the ICM cache is always configured. What happens if it is not available? You should really highlight this in the product documentation. The second problem is if the ICM cache is too small or flushed by someone. In that case we will get the dreaded !./BSP-ProgrammingHandling-Of-Non-HTML-Documents_004.jpg|width=13 height=16  /|src=./BSP-ProgrammingHandling-Of-Non-HTML-Documents_004.jpg! in the browser! But this is a small probability versus the pure win of this technique!</p>

Display Document In New Window

By now most of the difficult work is complete. For the final leg of our explorations, we would like to place the dynamic document into a new window. The biggest problem is just how to trigger opening a new window.

It is not possible to open a new browser window from the server. The simplest technique is to open the new window directly in the browser with a small JavaScript sequence: "window.open(url)".

The first step is to require the dynamic document stored as a URL on the server. All this coding is already in place. We just change the IF-guard to include this new case.

<!codeOnInputProcessing:

<!code>   

<!code>  ... code previously displayed above ...

<!code>   

<!code>  IF ( display_type = 'html' OR display_type = "window" ) AND

<!code>     XSTRLEN( file_content ) > 0.

<!code>   

<!code>    ... code as previously displayed ...

<!code>   

<!code>  ENDIF.

Next, add the code in the layout to open the new window. This is quickly done with:

<!codeLayout:

<!code>   

<!code>  ... code previously displayed above ...

<!code>   

<!code>   <% IF display_type = 'window' AND display_url IS NOT INITIAL. %>

<!code>     

<!code>        window.open("<%=display_url%>").focus();

<!code>      </script>

<!code>   <% ENDIF. %>

<!code>   

<!code>    </htmlb:page>

<!code

With the final results:

!https://weblogs.sdn.sap.com/weblogs/images/13/BSP-ProgrammingHandling-Of-Non-HTML-Documents_005.JPG|height=359|width=604|src=https://weblogs.sdn.sap.com/weblogs/images/13/BSP-ProgrammingHandling-Of-Non-HTML-Documents_005.JPG|border=0!</body>

16 Comments