A customer had a requirement to invoke a REST web service that is designed to accept a multipart/form-data request body. I could not get it to work with the APEX_WEB_SERVICE API package, so I reverted to using the good old UTL_HTTP package. That package provides the required flexibility when composing a multipart/form-data request. Here’s the code fragment I ended up with and its resulting debug output.
set serveroutput on; declare l_attachment blob; l_newline varchar2(50) := chr(13) || chr(10); lco_boundary constant varchar2(30) := 'gc0p4Jq0M2Yt08jU534c0p'; l_http_request utl_http.req; l_request_body clob; l_request_body_length number; l_http_response utl_http.resp; l_response_header_name varchar2(256); l_response_header_value varchar2(1024); l_response_body varchar2(32767); l_offset number := 1; l_amount number := 2000; l_buffer varchar2(2000); begin select my_file into l_attachment from my_files where seq_nr = 1; l_request_body := l_newline || '--' || lco_boundary || l_newline || 'Content-Disposition: form-data; name="attachment"; filename="file.pdf"' || l_newline || 'Content-Type: application/pdf' || l_newline || l_newline || apex_web_service.blob2clobbase64(l_attachment) || l_newline || '--' || lco_boundary || l_newline || 'Content-Disposition: form-data; name="filename"' || l_newline || l_newline || 'file.pdf' || l_newline || '--' || lco_boundary || l_newline || 'Content-Disposition: form-data; name="MAX_FILE_SIZE"' || l_newline || l_newline || '4000000' || l_newline || '--' || lco_boundary || '--'; dbms_output.put_line('Request body>'); dbms_output.put_line(dbms_lob.substr(l_request_body, 4000, 1)); l_request_body_length := dbms_lob.getlength(l_request_body); utl_http.set_wallet( path => 'file:/path/to/wallet', password => 'my_secret_password' ); l_http_request := utl_http.begin_request( url => 'https://domain.com/rest/addfile/1288', method => 'POST', http_version => 'HTTP/1.1' ); utl_http.set_header(l_http_request, 'Content-Type', 'multipart/form-data; boundary="' || lco_boundary || '"'); utl_http.set_header(l_http_request, 'Content-Length', l_request_body_length); while l_offset < l_request_body_length loop dbms_lob.read(l_request_body, l_amount, l_offset, l_buffer); utl_http.write_text(l_http_request, l_buffer); l_offset := l_offset + l_amount; end loop; l_http_response := utl_http.get_response(l_http_request); dbms_output.put_line('Response> Status Code: ' || l_http_response.status_code); dbms_output.put_line('Response> Reason Phrase: ' || l_http_response.reason_phrase); dbms_output.put_line('Response> HTTP Version: ' || l_http_response.http_version); for i in 1 .. utl_http.get_header_count(l_http_response) loop utl_http.get_header(l_http_response, i, l_response_header_name, l_response_header_value); dbms_output.put_line('Response> ' || l_response_header_name || ': ' || l_response_header_value); end loop; utl_http.read_text(l_http_response, l_response_body, 32767); dbms_output.put_line('Response body>'); dbms_output.put_line(l_response_body); if l_http_request.private_hndl is not null then utl_http.end_request(l_http_request); end if; if l_http_response.private_hndl is not null then utl_http.end_response(l_http_response); end if; exception when others then if l_http_request.private_hndl is not null then utl_http.end_request(l_http_request); end if; if l_http_response.private_hndl is not null then utl_http.end_response(l_http_response); end if; raise; end;
anonymous block completed Request body> --gc0p4Jq0M2Yt08jU534c0p Content-Disposition: form-data; name="attachment"; filename="file.pdf" Content-Type: application/pdf JVBERi0xLjUNCiW1tbW1DQoxIDAgb2JqDQo8PC9UeXBlL0NhdGFsb2cvUGFnZXMg MiAwIFIvTGFuZyhubC1OTCkgL1N0cnVjdFRyZWVSb290IDEwIDAgUi9NYXJrSW5m bzw8L01hcmtlZCB0cnVlPj4+Pg0KZW5kb2JqDQoyIDAgb2JqDQo8PC9UeXBlL1Bh Z2VzL0NvdW50IDEvS2lkc1sgMyAwIFJdID4+DQplbmRvYmoNCjMgMCBvYmoNCjw8 L1R5cGUvUGFnZS9QYXJlbnQgMiAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUg MCBSPj4vRXh0R1N0YXRlPDwvR1M3IDcgMCBSL0dTOCA4IDAgUj4+L1Byb2NTZXRb L1BERi9UZXh0L0ltYWdlQi9JbWFnZUMvSW1hZ2VJXSA+Pi9NZWRpYUJveFsgMCAw IDU5NS4yIDg0MS45Ml0gL0NvbnRlbnRzIDQgMCBSL0dyb3VwPDwvVHlwZS9Hcm91 cC9TL1RyYW5zcGFyZW5jeS9DUy9EZXZpY2VSR0I+Pi9UYWJzL1MvU3RydWN0UGFy ZW50cyAwPj4NC... Response> Status Code: 200 Response> Reason Phrase: OK Response> HTTP Version: HTTP/1.1 Response> Date: Mon, 22 Feb 2016 06:52:52 GMT Response> Server: Apache Response> X-Powered-By: PHP/5.6.5 ZendServer/8.0.2 Response> ZRay-ID: 39@168458@1455872499@0 Response> Expires: Thu, 19 Nov 1981 08:52:00 GMT Response> Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Response> Pragma: no-cache Response> Connection: close Response> Transfer-Encoding: chunked Response> Content-Type: application/xml Response body> <?xml version="1.0" ?> <RestResponse service="enquiryattachfile"> <attachedFileSize>125328</attachedFileSize> </RestResponse>
Hello ! Tell me please read_text does not return BLOB, there is a parsing error, who knows how to do it? xml который вернулся
soap:Client
Error processing input
ORA-19202
LikeLike
Xml which is back
soap:Client
Error processing input
ORA-19202
LikeLike
”
soap:Client
Error processing input
ORA-31011
“
LikeLike
ErrorNumberORA-31011ErrorNumber
Message CDATA[XML parsing failed]] Message
LikeLike
I’m sorry, I need more info before I can help you any further. What code are you executing? My e-mail address is apexplained@gmail.com.
LikeLike
Thanks so much for this, I had been struggling trying to get apex_web_service to work with passing a file along with parameters and this is exactly what I needed. One comment though, even though l_request_body is declared as a clob, because of the string concatenation used when building up the clob oracle will treat it as a varchar2(32767) instead of a clob. If you have a large file that you are transferring, you will get a value/numeric error. If large files need to be transferred you need to use dbms_lob to build up your clob instead.
LikeLiked by 2 people
Thank you for that remark. I wasn’t aware of the implicit CLOB to VARCHAR2 conversion.
LikeLike