Special characters in Oracle utl_http request When calling a web service using utl_http, the calls fail when the SOAP envelope contains characters like ¿, ± or º.

IS
     http_req  utl_http.req;
     http_resp utl_http.resp;
     request_env varchar2(32767);
     response_env varchar2(32767); 
BEGIN

    request_env:='
&lt;soap:Envelope 
     ...
&lt;/soap:Envelope&gt;';

    http_req := utl_http.begin_request('http://ws.domain.com/Project.asmx', 'POST', utl_http.HTTP_VERSION_1_1);
    utl_http.set_header(http_req, 'Content-Type', 'text/xml; charset=utf-8');
    utl_http.set_header(http_req, 'Content-Length', length(request_env));
    utl_http.set_header(http_req, 'SOAPAction', '"http://domain.com/WS/ProjectAction"');
    utl_http.write_text(http_req, request_env);


I thought that charset=utf-8 in Content-Type should take care of that, but it still bombs and the characters above have to be replaced to their ASCII multi-characters equivalents beforehand.

This is an NLS report for this database:

NLS_CALENDAR    GREGORIAN
NLS_CHARACTERSET    WE8ISO8859P1
NLS_COMP    BINARY
NLS_CURRENCY    $
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE   AMERICAN
NLS_DUAL_CURRENCY   $
NLS_ISO_CURRENCY    AMERICA
NLS_LANGUAGE    AMERICAN
NLS_LENGTH_SEMANTICS    BYTE
NLS_NCHAR_CHARACTERSET  AL16UTF16
NLS_NCHAR_CONV_EXCP FALSE
NLS_NUMERIC_CHARACTERS  .,
NLS_SORT    BINARY
NLS_TERRITORY   AMERICA
NLS_TIMESTAMP_FORMAT    DD-MON-RR HH.MI.SSXFF AM
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_TIME_FORMAT HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT  HH.MI.SSXFF AM TZR


I am kind of consulting the in-house developers on this issue and before I will try to convince them to try redeclare request_env as NVARCHAR2, wanted to get the community opinion on whether that should be the fix they should attempt.
The problem is length or lengthb functions treat these special characters as 1 byte but write_text function (correctly) considers these as 2 bytes. Hence a mal-formed XML is sent to the server. 

To resolve this error please use the following instead of the length / lengthb function.

lv_clob_length_bytes := utl_raw.length(utl_raw.convert(utl_raw.cast_to_raw(lv_request_body),'american_america.al32utf8','ENGLISH_UNITED KINGDOM.WE8ISO8859P15'));   

The above casts the XML to raw and then converts it from our characterset to UTF8 before finding its length.