常见的处理方法也许是创建创建database link,但dblink会对数据库性能造成影响(且会产生事务),而且不能传输大字段类型(如clob,bclob);
在不使用dblink的情况下如何实现呢?其实可以使用http请求实现,首先搭建一个http服务(post请求/get请求)返回需要的数据(返回数据类型可以是xml/json字符串,便于Oracle(xmltable/jsontable)解析),然后在Oracle语句块中请求对应服务进行操作便可。
HTTP GET方式:
CREATE OR REPLACE FUNCTION FN_HTTP_GET (v_url VARCHAR2)
RETURN VARCHAR2
AS
BEGIN
DECLARE
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
v_line VARCHAR2 ( 4000 );
v_text VARCHAR2 ( 4000 );
BEGIN
v_text := '';
BEGIN
req := UTL_HTTP.BEGIN_REQUEST ( url => v_url, method => 'GET' );
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(req, 'Content-Type', 'application/x-www-form-urlencoded');
resp := UTL_HTTP.GET_RESPONSE ( req );
LOOP
UTL_HTTP.READ_LINE ( resp, v_line, TRUE );
v_text := v_text || v_line;
END LOOP;
UTL_HTTP.END_RESPONSE( resp );
UTL_HTTP.END_REQUEST( req );
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
UTL_HTTP.END_RESPONSE ( resp );
WHEN OTHERS THEN
UTL_HTTP.END_RESPONSE(resp);
UTL_HTTP.END_REQUEST(req);
END;
return v_text;
END;
END;
HTTP POST方式:
CREATE OR REPLACE FUNCTION FN_HTTP_POST (v_url VARCHAR2, v_body VARCHAR2, v_body_type VARCHAR2)
RETURN VARCHAR2
AS
BEGIN
DECLARE
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
v_line VARCHAR2 ( 4000 );
v_text VARCHAR2 ( 4000 );
BEGIN
v_text := '';
BEGIN
req := UTL_HTTP.BEGIN_REQUEST ( url => v_url, method => 'POST' );
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(req, 'Content-Type', v_body_type);
utl_http.set_header(req, 'Content-Length',lengthb(v_body));
utl_http.write_text(req, v_body);
resp := UTL_HTTP.GET_RESPONSE ( req );
LOOP
UTL_HTTP.READ_LINE ( resp, v_line, TRUE );
v_text := v_text || v_line;
END LOOP;
UTL_HTTP.END_RESPONSE( resp );
UTL_HTTP.END_REQUEST( req );
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
UTL_HTTP.END_RESPONSE ( resp );
WHEN OTHERS THEN
UTL_HTTP.END_RESPONSE(resp);
UTL_HTTP.END_REQUEST(req);
END;
return v_text;
END;
END;
CREATE OR REPLACE
FUNCTION HTTP_REQUEST(
v_url VARCHAR2,--请求地址
v_param VARCHAR2,--POST 相当于表单提交
v_body clob,--POST body中的参数
v_type varchar2--类型
)return varchar2 is
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
v_line VARCHAR2 (4000);
v_text VARCHAR2 (4000);
v_param_length NUMBER ;
v_body_length NUMBER;
buffer VARCHAR2(32767);
offset NUMBER := 1;
amount NUMBER := 32767;
begin
IF V_TYPE='GET' THEN
v_text := '';
req := UTL_HTTP.BEGIN_REQUEST ( url => v_url, method => 'GET' );
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(req, 'Content-Type', 'application/x-www-form-urlencoded');
resp := UTL_HTTP.GET_RESPONSE ( req );
UTL_HTTP.READ_LINE ( resp, v_line, TRUE );
v_text := v_text || v_line;
UTL_HTTP.END_RESPONSE( resp );
ELSIF V_TYPE='POST_FORM' THEN
v_param_length := LENGTHB(v_param);
req := UTL_HTTP.BEGIN_REQUEST (url=> v_url, method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER (r => req,
name => 'Content-Type',
VALUE => 'application/x-www-form-urlencoded');
UTL_HTTP.SET_HEADER (r => req,
name => 'Content-Length',
VALUE => v_param_length);
UTL_HTTP.WRITE_RAW (r => req,
data => UTL_RAW.CAST_TO_RAW(v_param));
resp := UTL_HTTP.GET_RESPONSE(req);
UTL_HTTP.READ_LINE(resp, v_text, TRUE);
UTL_HTTP.END_RESPONSE(resp);
ELSE
v_body_length := dbms_lob.getlength(v_body);
req := UTL_HTTP.BEGIN_REQUEST (url=> v_url, method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER (r => req,
name => 'Content-Type',
VALUE => 'application/json;charset=utf-8');
UTL_HTTP.SET_HEADER (r => req,
name => 'Content-Length',
VALUE => v_body_length);
--分段输出请求参数,这个header设置很重要
UTL_HTTP.SET_HEADER (req, 'Transfer-Encoding', 'chunked');
--分段输出请求参数,避免内容超出长度限制
WHILE (offset < v_body_length) LOOP
dbms_lob.read(v_body, amount, offset, buffer);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
offset := offset + amount;
END LOOP;
resp := UTL_HTTP.GET_RESPONSE(req);
UTL_HTTP.READ_LINE(resp, v_text, TRUE);
UTL_HTTP.END_RESPONSE(resp);
END IF;
return v_text;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
UTL_HTTP.END_RESPONSE ( resp );
WHEN OTHERS THEN
UTL_HTTP.END_RESPONSE(resp);
UTL_HTTP.END_REQUEST(req);
end;
网络正常,单http调用失败,无法调用时,可能是ACL控制未开启。
select * from dba_network_acls;
Select * From dba_network_acl_privileges ;
dba_network_acls为assign_acl维护进去的
dba_network_acl_privileges为create_acl和add_privilege维护进去的
如果dba_network_acl_privileges中有可用的记录,那么我们可以跳过create_acl这一步,直接使用存在的xml文件来新增权限;如果没有,那么我们就先创建一个acl
--添加acl和权限控制,若已存在访问控制文件(ACL),可不用再创建
begin
dbms_network_acl_admin.create_acl ( -- 创建访问控制文件(ACL)
acl => 'utl_http.xml', -- 文件名称
description => 'HTTP Access', -- 描述
principal => 'YBZNFK', -- 授权或者取消授权账号,大小写敏感
is_grant => TRUE, -- 授权还是取消授权
privilege => 'connect', -- 授权或者取消授权的权限列表
start_date => null, -- 起始日期
end_date => null -- 结束日期
);
--acl中给用户增加一个resolve的权限
dbms_network_acl_admin.add_privilege ( -- 添加访问权限列表项
acl => 'utl_http.xml', -- 刚才创建的acl名称
principal => 'YBZNFK', -- 授权或取消授权用户
is_grant => TRUE, -- 与上同
privilege => 'resolve', -- 权限列表
start_date => null,
end_date => null
);
--acl中添加要访问的目标网址(ip和域名均可),可以指定端口范围
dbms_network_acl_admin.assign_acl ( -- 该段命令意思是允许访问acl名为utl_http.xml下授权的用户,使用oracle网络访问包,所允许访问的目的主机,及其端口范围。
acl => 'utl_http.xml',
host => '*', -- 访问接口服务的ip地址或者域名,*代表所有
lower_port => null, -- 允许访问的起始端口号
upper_port => Null -- 允许访问的截止端口号
);
commit;
end;
当响应结果比较大时,可使用如下逻辑处理输出:
begin
loop
utl_http.read_raw( resp, raw_buf );
dbms_lob.append( v_rblob, to_blob(raw_buf) );
end loop;
exception
when utl_http.END_OF_BODY
then
null;
end;
包案例:
CREATE OR REPLACE PACKAGE CM.pkg_scene_http_option AS
/***
* create user: 杨江兵
* date:*
* http工具包
* ***/
/*HTTP_REQUEST
* @pharms:
* :
* */
FUNCTION HTTP_REQUEST(v_url VARCHAR2, --请求地址
v_param VARCHAR2, --POST 相当于表单提交
v_body clob, --POST body中的参数
v_type varchar2 --类型
) return varchar2;
/*
* @pharms:
* :
*结果返回大文本
* */
FUNCTION HTTP_REQUEST_BLOB(v_url VARCHAR2, --请求地址
v_param VARCHAR2, --POST 相当于表单提交
v_body clob, --POST body中的参数
v_type varchar2 --类型
) return blob ;
/*
* @pharms:
* :
*结果返回大文本
* */
FUNCTION HTTP_REQUEST_CLOB(v_url VARCHAR2, --请求地址
v_param VARCHAR2, --POST 相当于表单提交
v_body clob, --POST body中的参数
v_type varchar2 --类型
) return clob ;
END pkg_scene_http_option;
/
CREATE OR REPLACE PACKAGE body CM.pkg_scene_http_option AS
/***
* create user: 杨江兵
* date: *
* http工具包
* ***/
/*
* @pharms:
* :
* */
FUNCTION HTTP_REQUEST(v_url VARCHAR2, --请求地址
v_param VARCHAR2, --POST 相当于表单提交
v_body clob, --POST body中的参数
v_type varchar2 --类型
) return varchar2 is
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
v_line VARCHAR2(4000);
v_text VARCHAR2(4000);
v_param_length NUMBER;
v_body_length NUMBER;
buffer VARCHAR2(32767);
offset NUMBER := 1;
amount NUMBER := 32767;
begin
IF V_TYPE = 'GET' THEN
v_text := '';
req := UTL_HTTP.BEGIN_REQUEST(url => v_url, method => 'GET');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(req,
'Content-Type',
'application/x-www-form-urlencoded');
resp := UTL_HTTP.GET_RESPONSE(req);
UTL_HTTP.READ_LINE(resp, v_line, TRUE);
v_text := v_text || v_line;
UTL_HTTP.END_RESPONSE(resp);
ELSIF V_TYPE = 'POST_FORM' THEN
v_param_length := LENGTHB(v_param);
req := UTL_HTTP.BEGIN_REQUEST(url => v_url,
method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Type',
VALUE => 'application/x-www-form-urlencoded');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Length',
VALUE => v_param_length);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(v_param));
resp := UTL_HTTP.GET_RESPONSE(req);
UTL_HTTP.READ_LINE(resp, v_text, TRUE);
UTL_HTTP.END_RESPONSE(resp);
ELSE
v_body_length := dbms_lob.getlength(v_body);
req := UTL_HTTP.BEGIN_REQUEST(url => v_url,
method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Type',
VALUE => 'application/json;charset=utf-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Length',
VALUE => v_body_length);
--分段输出请求参数,这个header设置很重要
UTL_HTTP.SET_HEADER(req, 'Transfer-Encoding', 'chunked');
--分段输出请求参数,避免内容超出长度限制
WHILE (offset < v_body_length) LOOP
dbms_lob.read(v_body, amount, offset, buffer);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
offset := offset + amount;
END LOOP;
resp := UTL_HTTP.GET_RESPONSE(req);
UTL_HTTP.READ_LINE(resp, v_text, TRUE);
UTL_HTTP.END_RESPONSE(resp);
END IF;
return v_text;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
UTL_HTTP.END_RESPONSE(resp);
return sqlerrm;
WHEN OTHERS THEN
UTL_HTTP.END_RESPONSE(resp);
UTL_HTTP.END_REQUEST(req);
return sqlerrm;
end;
/*
* @pharms:
* :
*结果返回大文本
* */
FUNCTION HTTP_REQUEST_BLOB(v_url VARCHAR2, --请求地址
v_param VARCHAR2, --POST 相当于表单提交
v_body clob, --POST body中的参数
v_type varchar2 --类型
) return blob is
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
v_rblob blob;
l_raw_data RAW(32767); -- 用于存储原始字节流
out_buffer VARCHAR2(32767);
v_param_length NUMBER;
v_body_length NUMBER;
buffer VARCHAR2(32767);
offset NUMBER := 1;
amount NUMBER := 32767;
begin
dbms_lob.createtemporary(v_rblob, true);
IF V_TYPE = 'GET' THEN
req := UTL_HTTP.BEGIN_REQUEST(url => v_url, method => 'GET');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(req,
'Content-Type',
'application/x-www-form-urlencoded');
resp := UTL_HTTP.GET_RESPONSE(req);
--响应结果设置字符集
utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
begin
UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
-- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
-- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
--out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
-- 尝试不同的字符集名称
/*BEGIN
-- 首先尝试 UTF8(最常用)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
EXCEPTION
WHEN OTHERS THEN
-- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
END;
END;
END;*/
--编码一致,则使用直接读取文本
UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
DBMS_LOB.WRITEAPPEND(v_rblob, LENGTH(out_buffer), out_buffer);
exception
when utl_http.END_OF_BODY
then
null;
end;
UTL_HTTP.END_RESPONSE(resp);
ELSIF V_TYPE = 'POST_FORM' THEN
v_param_length := LENGTHB(v_param);
req := UTL_HTTP.BEGIN_REQUEST(url => v_url,
method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Type',
VALUE => 'application/x-www-form-urlencoded');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Length',
VALUE => v_param_length);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(v_param));
resp := UTL_HTTP.GET_RESPONSE(req);
utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
begin
UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
-- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
-- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
--out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
-- 尝试不同的字符集名称
/*BEGIN
-- 首先尝试 UTF8(最常用)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
EXCEPTION
WHEN OTHERS THEN
-- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
END;
END;
END;*/
--编码一致,则使用直接读取文本
UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
DBMS_LOB.WRITEAPPEND(v_rblob, LENGTH(out_buffer), out_buffer);
exception
when utl_http.END_OF_BODY
then
null;
end;
UTL_HTTP.END_RESPONSE(resp);
ELSE
v_body_length := dbms_lob.getlength(v_body);
req := UTL_HTTP.BEGIN_REQUEST(url => v_url,
method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Type',
VALUE => 'application/json;charset=utf-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Length',
VALUE => v_body_length);
--分段输出请求参数,这个header设置很重要
UTL_HTTP.SET_HEADER(req, 'Transfer-Encoding', 'chunked');
--分段输出请求参数,避免内容超出长度限制
WHILE (offset < v_body_length) LOOP
dbms_lob.read(v_body, amount, offset, buffer);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
offset := offset + amount;
END LOOP;
resp := UTL_HTTP.GET_RESPONSE(req);
utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
begin
/*loop
utl_http.read_raw( resp, l_raw_data );
dbms_lob.append( v_rblob, to_blob(l_raw_data) );
end loop;*/
UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
-- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
-- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
--out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
-- 尝试不同的字符集名称
/*BEGIN
-- 首先尝试 UTF8(最常用)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
EXCEPTION
WHEN OTHERS THEN
-- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
END;
END;
END;*/
--编码一致,则使用直接读取文本
UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
DBMS_LOB.WRITEAPPEND(v_rblob, LENGTH(out_buffer), out_buffer);
exception
when utl_http.END_OF_BODY
then
null;
end;
UTL_HTTP.END_RESPONSE(resp);
END IF;
return v_rblob;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
UTL_HTTP.END_RESPONSE(resp);
return to_blob(sqlerrm);
WHEN OTHERS THEN
UTL_HTTP.END_RESPONSE(resp);
UTL_HTTP.END_REQUEST(req);
return to_blob(sqlerrm);
end;
/*
* @pharms:
* :
*结果返回大文本
* */
FUNCTION HTTP_REQUEST_CLOB(v_url VARCHAR2, --请求地址
v_param VARCHAR2, --POST 相当于表单提交
v_body clob, --POST body中的参数
v_type varchar2 --类型
) return clob is
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
v_rclob clob;
v_param_length NUMBER;
v_body_length NUMBER;
l_raw_data RAW(32767); -- 用于存储原始字节流
buffer VARCHAR2(32767);
out_buffer VARCHAR2(32767);
offset NUMBER := 1;
amount NUMBER := 32767;
begin
-- 初始化 CLOB
DBMS_LOB.CREATETEMPORARY(v_rclob, TRUE);
IF V_TYPE = 'GET' THEN
req := UTL_HTTP.BEGIN_REQUEST(url => v_url, method => 'GET');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(req,
'Content-Type',
'application/x-www-form-urlencoded');
resp := UTL_HTTP.GET_RESPONSE(req);
--响应结果设置字符集
utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
--读取响应,注意:这里读取的是 RAW 数据
BEGIN
LOOP
UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
-- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
-- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
--out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
-- 尝试不同的字符集名称
/*BEGIN
-- 首先尝试 UTF8(最常用)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
EXCEPTION
WHEN OTHERS THEN
-- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
END;
END;
END;*/
--编码一致,则使用直接读取文本
UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
DBMS_LOB.WRITEAPPEND(v_rclob, LENGTH(out_buffer), out_buffer);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
NULL;
END;
UTL_HTTP.END_RESPONSE(resp);
ELSIF V_TYPE = 'POST_FORM' THEN
v_param_length := LENGTHB(v_param);
req := UTL_HTTP.BEGIN_REQUEST(url => v_url,
method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Type',
VALUE => 'application/x-www-form-urlencoded');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Length',
VALUE => v_param_length);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(v_param));
resp := UTL_HTTP.GET_RESPONSE(req);
utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
BEGIN
LOOP
UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
-- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
-- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
--out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
-- 尝试不同的字符集名称
/*BEGIN
-- 首先尝试 UTF8(最常用)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
EXCEPTION
WHEN OTHERS THEN
-- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
END;
END;
END;*/
--编码一致,则使用直接读取文本
UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
DBMS_LOB.WRITEAPPEND(v_rclob, LENGTH(out_buffer), out_buffer);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
NULL;
END;
UTL_HTTP.END_RESPONSE(resp);
ELSE
v_body_length := dbms_lob.getlength(v_body);
req := UTL_HTTP.BEGIN_REQUEST(url => v_url,
method => 'POST');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Type',
VALUE => 'application/json;charset=utf-8');
UTL_HTTP.SET_HEADER(r => req,
name => 'Content-Length',
VALUE => v_body_length);
--分段输出请求参数,这个header设置很重要
UTL_HTTP.SET_HEADER(req, 'Transfer-Encoding', 'chunked');
--分段输出请求参数,避免内容超出长度限制
WHILE (offset < v_body_length) LOOP
dbms_lob.read(v_body, amount, offset, buffer);
UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
offset := offset + amount;
END LOOP;
resp := UTL_HTTP.GET_RESPONSE(req);
utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
BEGIN
LOOP
UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
--字符集不一致导致乱码时尝试 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
-- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
-- 明确指定源编码为 ZHS16GBK
--out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'ZHS16GBK');
--原样输出
--out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
-- 尝试不同的字符集名称
/*BEGIN
-- 首先尝试 UTF8(最常用)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
EXCEPTION
WHEN OTHERS THEN
-- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
END;
END;
END;*/
--编码一致,则使用直接读取文本
UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
DBMS_LOB.WRITEAPPEND(v_rclob, LENGTH(out_buffer), out_buffer);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
NULL;
END;
UTL_HTTP.END_RESPONSE(resp);
END IF;
-- 释放临时 LOB
--DBMS_LOB.FREETEMPORARY(v_rclob);
return v_rclob;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
-- 释放临时 LOB
--DBMS_LOB.FREETEMPORARY(v_rclob);
UTL_HTTP.END_RESPONSE(resp);
return sqlerrm;
WHEN OTHERS THEN
-- 释放临时 LOB
--DBMS_LOB.FREETEMPORARY(v_rclob);
UTL_HTTP.END_RESPONSE(resp);
UTL_HTTP.END_REQUEST(req);
return sqlerrm;
end;
END pkg_scene_http_option;
/
UTL_HTTP.READ_RAW与UTL_HTTP.READ_TEXT
核心区别对比
特性 | UTL_HTTP.READ_RAW | UTL_HTTP.READ_TEXT |
---|---|---|
返回类型 | RAW (原始字节流) | VARCHAR2 (字符串) |
字符集处理 | 不进行任何字符集转换 | 自动进行字符集转换 |
数据完整性 | 保持原始字节完整性 | 可能因字符集转换丢失数据 |
使用场景 | 二进制数据、字符集不确定时 | 文本数据、字符集明确且匹配时 |
性能 | 稍高(无转换开销) | 稍低(有转换开销) |
错误风险 | 低(需要手动处理字符集) | 高(自动转换可能出错) |
详细解释
1. UTL_HTTP.READ_RAW
-
返回原始字节流,不进行任何字符集解释
-
相当于获取HTTP响应的"二进制副本"
-
需要开发者手动处理字符集转换
sql
DECLARE l_raw_data RAW(32767); BEGIN UTL_HTTP.READ_RAW(l_http_response, l_raw_data, 1000); -- 此时 l_raw_data 包含的是原始字节,需要手动转换 l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8'); END;
2. UTL_HTTP.READ_TEXT
-
返回已转换的字符串,自动进行字符集转换
-
转换基于NLS设置或HTTP响应头中的字符集信息
-
如果字符集不匹配,可能产生乱码
sql
DECLARE l_text_data VARCHAR2(32767); BEGIN UTL_HTTP.READ_TEXT(l_http_response, l_text_data, 1000); -- 此时 l_text_data 已经是转换后的字符串 END;
使用场景推荐
使用 UTL_HTTP.READ_RAW
的场景:
-
字符集不确定或可能不匹配时
sql
-- 安全读取,避免自动转换导致的乱码 UTL_HTTP.READ_RAW(l_response, l_raw_data, l_amount); l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8'); -- 明确指定源编码
-
处理二进制数据
sql
-- 下载图片、PDF、ZIP等二进制文件 UTL_HTTP.READ_RAW(l_response, l_file_data, l_amount); INSERT INTO files_table (file_content) VALUES (l_file_data);
-
需要精确控制字符集转换时
sql
-- 源数据是GBK,数据库是AL32UTF8 UTL_HTTP.READ_RAW(l_response, l_raw_data, l_amount); l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'ZHS16GBK'); -- 明确转换
-
处理多种编码的HTTP服务时
sql
-- 不同接口返回不同编码 UTL_HTTP.READ_RAW(l_response, l_raw_data, l_amount); -- 根据Content-Type头动态选择编码进行转换
使用 UTL_HTTP.READ_TEXT
的场景:
-
字符集明确且匹配时
sql
-- 确认接口返回UTF-8,数据库也是UTF-8 UTL_HTTP.SET_BODY_CHARSET(l_response, 'UTF-8'); UTL_HTTP.READ_TEXT(l_response, l_text_data, l_amount);
-
处理纯文本且编码简单时
sql
-- 处理ASCII或简单英文文本 UTL_HTTP.READ_TEXT(l_response, l_text_data, l_amount);
-
快速原型开发时
sql
-- 开发测试阶段,快速获取文本内容 UTL_HTTP.READ_TEXT(l_response, l_text_data, l_amount); DBMS_OUTPUT.PUT_LINE(l_text_data);
实战示例对比
示例1:处理中文文本(推荐使用READ_RAW)
sql
-- 方法1:使用READ_RAW(安全) DECLARE l_response UTL_HTTP.RESP; l_raw_data RAW(32767); l_text VARCHAR2(32767); BEGIN l_response := UTL_HTTP.GET_RESPONSE(...); LOOP UTL_HTTP.READ_RAW(l_response, l_raw_data, 1000); -- 明确指定源编码为UTF-8,目标为数据库字符集 l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8'); DBMS_OUTPUT.PUT_LINE(l_text); END LOOP; END; / -- 方法2:使用READ_TEXT(风险较高) DECLARE l_response UTL_HTTP.RESP; l_text_data VARCHAR2(32767); BEGIN l_response := UTL_HTTP.GET_RESPONSE(...); UTL_HTTP.SET_BODY_CHARSET(l_response, 'UTF-8'); -- 需要正确设置 LOOP UTL_HTTP.READ_TEXT(l_response, l_text_data, 1000); DBMS_OUTPUT.PUT_LINE(l_text_data); -- 可能乱码 END LOOP; END; /
示例2:下载二进制文件(必须使用READ_RAW)
sql
-- 下载图片文件 DECLARE l_response UTL_HTTP.RESP; l_raw_data RAW(32767); l_file_blob BLOB; BEGIN l_response := UTL_HTTP.GET_RESPONSE('http://example.com/image.jpg'); DBMS_LOB.CREATETEMPORARY(l_file_blob, TRUE); LOOP UTL_HTTP.READ_RAW(l_response, l_raw_data, 32767); -- 将RAW数据存入BLOB DBMS_LOB.WRITEAPPEND(l_file_blob, UTL_RAW.LENGTH(l_raw_data), l_raw_data); END LOOP; -- 保存到数据库 INSERT INTO images (image_name, image_data) VALUES ('downloaded.jpg', l_file_blob); DBMS_LOB.FREETEMPORARY(l_file_blob); END; /
字符集处理机制
READ_TEXT的自动转换过程:
-
检查HTTP响应头的
Content-Type
中的charset
参数 -
如果没有指定,使用NLS设置或默认字符集
-
将字节流转换为数据库字符集
READ_RAW的手动控制过程:
-
获取原始字节流
-
开发者根据需要调用
UTL_I18N.RAW_TO_CHAR()
进行转换 -
完全控制源编码和目标编码
总结建议
情况 | 推荐使用 | 原因 |
---|---|---|
中文内容处理 | READ_RAW | 避免自动转换乱码 |
二进制文件下载 | READ_RAW | 必须使用原始字节流 |
字符集不确定 | READ_RAW | 手动控制更安全 |
纯英文文本 | READ_TEXT | 简单高效 |
编码明确匹配 | READ_TEXT | 方便快捷 |
开发调试 | READ_TEXT | 快速查看内容 |