UTL_TCP Appendix A

advertisement
Appendix A
UTL_TCP
Oracle 8.1.6 introduced for the first time, the UTL_TCP package. This package allows PL/SQL to open a
network socket connection over TCP/IP to any server accepting connections. Assuming you know the
protocol of a server, you can now 'talk' to it from PL/SQL. For example, given that I know HTTP
(Hyper Text Transfer Protocol), I can code in UTL_TCP the following:
test_jsock@DEV816> DECLARE
2
c utl_tcp.connection; -- TCP/IP connection to the web server
3
n number;
4
buffer varchar2(255);
5 BEGIN
6
c := utl_tcp.open_connection('proxy-server', 80);
7
n := utl_tcp.write_line(c, 'GET http://www.apress.com/ HTTP/1.0');
8
n := utl_tcp.write_line(c);
9
BEGIN
10
LOOP
11
n:=utl_tcp.read_text( c, buffer, 255 );
12
dbms_output.put_line( buffer );
13
END LOOP;
14
EXCEPTION
15
WHEN utl_tcp.end_of_input THEN
16
NULL; -- end of input
17
end;
18
utl_tcp.close_connection(c);
19 END;
20 /
HTTP/1.1 200 OK
Date: Tue, 30 Jan 2001 11:33:50 GMT
Server: Apache/1.3.9 (Unix) mod_perl/1.21
ApacheJServ/1.1
Content-Type: text/html
<head>
<title>Oracle
Corporation</title>
1244
5254appAQ.pdf 1
2/28/2005 6:50:07 PM
UTL_TCP
This lets me open a connection to a server, in this case a proxy server named proxy-server. It lets me
get through our firewall to the outside Internet. This happens on line 6. I then request a web page on
lines 7 and 8. On lines 10 through to 13, we receive the contents of the web page, including the allimportant HTTP headers (something UTL_HTTP, another supplied package, won't share with us) and
print it. When UTL_TCP throws the UTL_TCP.END_OF_INPUT exception, we are done, and we break out
of the loop. We then close our connection and that's it.
This simple example demonstrates a majority of the functionality found in the UTL_TCP package. We
didn't see functions such as AVAILABLE, which tells us if any data is ready to be received. We skipped
FLUSH, which causes any buffered output to be transmitted (we didn't use buffering, hence did not need
this call). Likewise, we did not use every variation of READ, WRITE, and GET to put and get data on the
socket, but the example above shows how to use UTL_TCP fairly completely.
The one thing I don't necessarily like about the above is the speed at which it runs. It is in my
experience that UTL_TCP, while functional, is not as high performing as it could be in this release
(Oracle 8i). In Oracle 8.1.7.1, this performance issue is fixed (bug #1570972 corrects this issue).
So, how slow is slow? The above code, to retrieve a 16 KB document, takes anywhere from four to ten
seconds, depending on platform. This is especially bad considering the native UTL_HTTP function can
do the same operation with a sub-second response time. Unfortunately, UTL_HTTP doesn't permit access
to cookies, HTTP headers, binary data, basic authentication, and the like, so using an alternative is
many times useful. I think we can do better. To this end, we will implement our own UTL_TCP package.
However, we will use the Object Type metaphor we discussed in Chapter 20 on Using Object Relational
Features. What we will do is to implement a SocketType in PL/SQL with some of the underlying 'guts'
in Java. In the UTL_HTTP section, we put this SocketType we created to use in building a better
UTL_HTTP package for ourselves as well. Since our functionality will be modeled after the functionality
available in the UTL_TCP package, when Oracle9i is released with native and faster UTL_TCP support,
we can easily re-implement our type body using the real UTL_TCP package, and stop using our Javasupported one.
The SocketType
Our SocketType Object Type will use the following specification:
tkyte@TKYTE816> create or replace type SocketType
2 as object
3 (
4
-- 'Private data', rather than you
5
-- passing a context to each procedure, like you
6
-- do with UTL_FILE.
7
g_sock
number,
8
9
-- A function to return a CRLF. Just a convenience.
10
static function crlf return varchar2,
11
12
-- Procedures to send data over a socket.
13
member procedure send( p_data in varchar2 ),
14
member procedure send( p_data in clob ),
15
16
member procedure send_raw( p_data in raw ),
17
member procedure send_raw( p_data in blob ),
1245
5254appAQ.pdf 2
2/28/2005 6:50:07 PM
Appendix A
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- Functions to receive data from a socket. These return
-- Null on eof. They will block waiting for data. If
-- this is not desirable, use PEEK below to see if there
-- is any data to read.
member function recv return varchar2,
member function recv_raw return raw,
-- Convienence function. Reads data until a CRLF is found.
-- Can strip the CRLF if you like (or not, by default).
member function getline( p_remove_crlf in boolean default FALSE )
return varchar2,
-- Procedures to connect to a host and disconnect from a host.
-- It is important to disconnect, else you will leak resources
-- and eventually will not be able to connect.
member procedure initiate_connection( p_hostname in varchar2,
p_portno
in number ),
member procedure close_connection,
-- Function to tell you how many bytes (at least) might be
-- ready to be read.
member function peek return number
);
/
Type created.
This set of functionality is pretty much modeled after the UTL_TCP package, and provides much of the
same interface. In fact, it could be implemented on top of that package if you wanted. We are going to
implement it on top of a different package however, one which I call the SIMPLE_TCP_CLIENT. This is
a ordinary PL/SQL package that the SocketType will be built on. This is really our specification of a
UTL_TCP package:
tkyte@TKYTE816> CREATE OR REPLACE PACKAGE simple_tcp_client
2 as
3
-- A function to connect to a host. Returns a 'socket',
4
-- which is really just a number.
5
function connect_to( p_hostname in varchar2,
6
p_portno
in number ) return number;
7
8
-- Send data. We only know how to send RAW data here. Callers
9
-- must cast VARCHAR2 data to RAW. At the lowest level, all
10
-- data on a socket is really just 'bytes'.
11
12
procedure send( p_sock
in number,
13
p_data
in raw );
14
15
-- recv will receive data.
16
-- If maxlength is -1, we try for 4k of data. If maxlength
17
-- is set to anything OTHER than -1, we attempt to
18
-- read up to the length of p_data bytes. In other words,
19
-- I restrict the receives to 4k unless otherwise told not to.
20
procedure recv( p_sock
in
number,
21
p_data
out raw,
1246
5254appAQ.pdf 3
2/28/2005 6:50:07 PM
UTL_TCP
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
p_maxlength in
number default -1 );
-- Gets a line of data from the input socket. That is, data
-- up to a \n.
procedure getline( p_sock
in number,
p_data
out raw );
-- Disconnects from a server you have connected to.
procedure disconnect( p_sock
in number );
-- Gets the server time in GMT in the format yyyyMMdd HHmmss z
procedure get_gmt( p_gmt out varchar2 );
-- Gets the server's timezone. Useful for some Internet protocols.
procedure get_timezone( p_timezone
out varchar2 );
-- Gets the hostname of the server you are running on. Again,
-- useful for some Internet protocols.
procedure get_hostname( p_hostname
out varchar2 );
-- Returns the number of bytes available to be read.
function peek( p_sock in number ) return number;
-- base64 encodes a RAW. Useful for sending e-mail
-- attachments or doing HTTP which needs the user/password
-- to be obscured using base64 encoding.
procedure b64encode( p_data in raw, p_result out varchar2 );
end;
/
Package created.
Now, as none of these functions can actually be written in PL/SQL, we will implement them in Java
instead. The Java for doing this is surprisingly small. The entire script is only 94 lines long. We are
using the native Socket class for Java, and will maintain a small array of them, allowing PL/SQL to
have up to ten connections open simultaneously. If you would like more than ten, just make the
socketUsed array larger in the code below. I've tried to keep this as simple, and as small as possible,
preferring to do the bulk of any work in PL/SQL. I'll present the small class we need, and then
comment on it:
tkyte@TKYTE816> set define off
tkyte@TKYTE816> CREATE or
2 NAMED "jsock"
3 AS
4 import java.net.*;
5 import java.io.*;
6 import java.util.*;
7 import java.text.*;
8 import sun.misc.*;
9
10 public class jsock
11 {
12 static int
13 static Socket
14 static DateFormat
15 static DateFormat
16
17 static BASE64Encoder
18
replace and compile JAVA SOURCE
socketUsed[] = { 0,0,0,0,0,0,0,0,0,0 };
sockets[] = new Socket[socketUsed.length];
tzDateFormat = new SimpleDateFormat( "z" );
gmtDateFormat =
new SimpleDateFormat( "yyyyMMdd HHmmss z" );
encoder = new BASE64Encoder();
1247
5254appAQ.pdf 4
2/28/2005 6:50:07 PM
Appendix A
This class has some static variables – the two arrays, socketUsed and sockets are the main ones.
When returns are called from PL/SQL, we must return to it something it can send to us on subsequent
calls, to identify the socket connection it wants to use. We cannot return the Java Socket class to
PL/SQL, so I am using an array in which to store them, and will return to PL/SQL an index into that
array. If you look at the java_connect_to method, it looks in the socketsUsed array for an empty
slot, and allocates this to the connection. That index into socketsUsed is what PL/SQL will see. We
use this in the remaining sockets routines to access the actual Java class that represents a socket.
The other static variables are there for reasons of performance. I needed some date format objects, and
rather than NEW them each time you call java_get_gmt or java_get_timezone, I allocate them
once, and just reuse them. Lastly is the base 64 encoder object. For the same reason I allocate the date
formatter objects, I allocate the encoder.
Now for the routine that connects over TCP/IP, to a server. This logic loops over the socketUsed
array looking for an empty slot (where socketUsed[I] is not set to 1). If it finds one, it uses the Java
Socket class to create a connection to the host/port combination that was passed in, and sets the
socketUsed flag for the array slot to 1. It then returns a -1 on error (no empty slots), or a non-negative
number upon success:
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static public int java_connect_to( String p_hostname, int p_portno )
throws java.io.IOException
{
int
i;
for( i = 0; i < socketUsed.length && socketUsed[i] == 1; i++ );
if ( i < socketUsed.length )
{
sockets[i] = new Socket( p_hostname, p_portno );
socketUsed[i] = 1;
}
return i<socketUsed.length?i:-1;
}
The next routines are the two most frequently called Java routines. They are responsible for sending and
receiving data on a connected TCP/IP socket. The java_send_data routine is straightforward – it
simply gets the output stream associated with the socket, and writes the data. The java_recv_data is
slightly more complex. It uses OUT parameters, hence the use of int[] p_length for example, in order
to return data. This routine inspects the length that was sent in by the caller, and if the length was -1, it
will allocate a 4 KB buffer to read into, else it will allocate a buffer of the size specified. It will then try
to read that much data from the socket. The actual amount of data read (which will be less than or equal
to the amount requested) is placed in p_length as a return value:
34
35
36
37
38
39
40
static public void java_send_data( int p_sock, byte[] p_data )
throws java.io.IOException
{
(sockets[p_sock].getOutputStream()).write( p_data );
}
static public void java_recv_data( int p_sock,
1248
5254appAQ.pdf 5
2/28/2005 6:50:07 PM
UTL_TCP
41
42
43
44
45
46
47
byte[][] p_data, int[] p_length)
throws java.io.IOException
{
p_data[0] = new byte[p_length[0] == -1 ? 4096:p_length[0] ];
p_length[0] = (sockets[p_sock].getInputStream()).read( p_data[0] );
}
java_getline is a convenience function. Many Internet protocols respond to operations 'a line at a
time', and being able to get a simple line of text is very handy. For example, the headers sent back in
the HTTP protocol are simply lines of ASCII text. This routine works by using the
DataInputStream.readLine method, and if a line of text is read in, it will return it (putting the new
line, which readLine strips off, back on). Otherwise, the data will be returned as Null:
48
49
50
51
52
53
54
55
56
static public void java_getline( int p_sock, String[] p_data )
throws java.io.IOException
{
DataInputStream d =
new DataInputStream((sockets[p_sock].getInputStream()));
p_data[0] = d.readLine();
if ( p_data[0] != null ) p_data[0] += "\n";
}
java_disconnect is very straightforward as well. It simply sets the socketUsed array flag back to
zero, indicating we can reuse this slot in the array, and closes the socket down for us:
57
58
59
60
61
62
63
static public void java_disconnect( int p_sock )
throws java.io.IOException
{
socketUsed[p_sock] = 0;
(sockets[p_sock]).close();
}
The java_peek_sock routine is used to see if data on a socket is available to be read. This is useful for
times when the client does not want to block on a receive of data. If you look to see if anything is
available, you can tell if a receive will block, or return right away:
64
65
66
67
68
69
static public int java_peek_sock( int p_sock )
throws java.io.IOException
{
return (sockets[p_sock].getInputStream()).available();
}
Now we have our two time functions. java_get_timezone is used to return the time zone of the
database server. This is particularly useful if you need to convert an Oracle DATE from one time zone to
another using the NEW_TIME built-in function, or if you just need know the time zone in which the
server is operating. The second function, java_get_gmt, is useful for getting the server's current date
and time in GMT (Greenwich Mean Time):
1249
5254appAQ.pdf 6
2/28/2005 6:50:07 PM
Appendix A
70
71
72
73
74
75
76
77
78
79
80
81
82
static public void java_get_timezone( String[] p_timezone )
{
tzDateFormat.setTimeZone( TimeZone.getDefault() );
p_timezone[0] = tzDateFormat.format(new Date());
}
static public void java_get_gmt( String[] p_gmt )
{
gmtDateFormat.setTimeZone( TimeZone.getTimeZone("GMT") );
p_gmt[0] = gmtDateFormat.format(new Date());
}
The b64encode routine will base 64 encode a string of data. Base 64 encoding is an Internet-standard
method of encoding arbitrary data into a 7bit ASCII format, suitable for transmission. We will use this
function in particular when implementing our HTTP package, as it will support basic authentication
(used by many web sites that require you to log in via a username and password).
83
84
85
86
87
static public void b64encode( byte[] p_data, String[] p_b64data )
{
p_b64data[0] = encoder.encode( p_data );
}
The last routine in this class simply returns the hostname of the database server. Some Internet
protocols request that you transmit this information (for example, SMTP – simple mail transfer
protocol):
88
89
90
91
92
93
94
95
static public void java_get_hostname( String[] p_hostname )
throws java.net.UnknownHostException
{
p_hostname[0] = (InetAddress.getLocalHost()).getHostName();
}
}
/
Java created.
The Java methods themselves are rather straightforward. If you recall from Chapter 19 on Java Stored
Procedures, in order to get OUT parameters, we must send, what appears to be an array, to Java. Hence,
most of the procedures above take the form of:
40
41
static public void java_recv_data( int p_sock,
byte[][] p_data, int[] p_length)
This allows me to return a value in p_data, and return a value in p_length. Now that we have our
Java class, we are ready to build our package body for the SIMPLE_TCP_CLIENT package. It consists
almost entirely of bindings to Java:
1250
5254appAQ.pdf 7
2/28/2005 6:50:07 PM
UTL_TCP
tkyte@TKYTE816> CREATE OR REPLACE PACKAGE BODY simple_tcp_client
2 as
3
4
function connect_to( p_hostname in varchar2,
5
p_portno
in number ) return number
6
as language java
7
name 'jsock.java_connect_to( java.lang.String, int ) return int';
8
9
10
procedure send( p_sock in number, p_data in raw )
11
as language java
12
name 'jsock.java_send_data( int, byte[] )';
13
14
procedure recv_i ( p_sock
in number,
15
p_data
out raw,
16
p_maxlength in out number )
17
as language java
18
name 'jsock.java_recv_data( int, byte[][], int[] )';
19
20
procedure recv( p_sock
in
number,
21
p_data
out raw,
22
p_maxlength in
number default -1 )
23
is
24
l_maxlength
number default p_maxlength;
25
begin
26
recv_i( p_sock, p_data, l_maxlength );
27
if ( l_maxlength <> -1 )
28
then
29
p_data := utl_raw.substr( p_data, 1, l_maxlength );
30
else
31
p_data := NULL;
32
end if;
33
end;
Here, I have a RECV_I and a RECV procedure. RECV_I is a private procedure (the _I stands for
internal), not directly callable out of this package. It is called by RECV. RECV provides a 'friendly'
internal on top of RECV_I – it checks to see if any data was read from the socket and if so, it sets the
length correctly. If you recall from the Java code above, we allocated a fixed size buffer in the RECV
routine, and read up to that many bytes from the socket. We need to resize our buffer to be exactly that
size here, and this is the purpose of the UTL_RAW.SUBSTR function. Otherwise, if no data was read, we
simply return Null.
34
35
36
37
38
39
40
41
42
43
44
45
46
47
procedure getline_i( p_sock
in number,
p_data
out varchar2 )
as language java
name 'jsock.java_getline( int, java.lang.String[] )';
procedure getline( p_sock
in number,
p_data
out raw )
as
l_data
long;
begin
getline_i( p_sock, l_data );
p_data := utl_raw.cast_to_raw( l_data );
end getline;
1251
5254appAQ.pdf 8
2/28/2005 6:50:07 PM
Appendix A
Again, much like RECV_I/RECV above, GETLINE_I is an internal function called only by GETLINE. The
external PL/SQL interface exposes all data as the RAW type, and the GETLINE function here simply
converts the VARCHAR2 data into a RAW for us.
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
procedure disconnect( p_sock
in number )
as language java
name 'jsock.java_disconnect( int )';
procedure get_gmt( p_gmt
out varchar2 )
as language java
name 'jsock.java_get_gmt( java.lang.String[] )';
procedure get_timezone( p_timezone
out varchar2 )
as language java
name 'jsock.java_get_timezone( java.lang.String[] )';
procedure get_hostname( p_hostname
out varchar2 )
as language java
name 'jsock.java_get_hostname( java.lang.String[] )';
function peek( p_sock
in number ) return number
as language java
name 'jsock.java_peek_sock( int ) return int';
procedure b64encode( p_data in raw, p_result out varchar2 )
as language java
name 'jsock.b64encode( byte[], java.lang.String[] )';
end;
/
Package body created.
We are now ready to test some of our functions to see that they are installed, and actually work:
tkyte@TKYTE816> declare
2
l_hostname varchar2(255);
3
l_gmt
varchar2(255);
4
l_tz
varchar2(255);
5 begin
6
simple_tcp_client.get_hostname( l_hostname );
7
simple_tcp_client.get_gmt( l_gmt );
8
simple_tcp_client.get_timezone( l_tz );
9
10
dbms_output.put_line( 'hostname ' || l_hostname );
11
dbms_output.put_line( 'gmt time ' || l_gmt );
12
dbms_output.put_line( 'timezone ' || l_tz );
13 end;
14 /
hostname tkyte-dell
gmt time 20010131 213415 GMT
timezone EST
PL/SQL procedure successfully completed.
1252
5254appAQ.pdf 9
2/28/2005 6:50:08 PM
UTL_TCP
An important point for running the TCP/IP components of this package is that we need special
permission to use TCP/IP in the database. For more information on the DBMS_JAVA package and
privileges associated with Java, please see the DBMS_JAVA section in this appendix. In this case, we
specifically we need to execute:
sys@TKYTE816> begin
2 dbms_java.grant_permission(
3 grantee => 'TKYTE',
4 permission_type => 'java.net.SocketPermission',
5 permission_name => '*',
6 permission_action => 'connect,resolve' );
7 end;
8 /
PL/SQL procedure successfully completed.
Refer to the section on DBMS_JAVA for more details on what, and how, this procedure works. In a
nutshell, it allows the user TKYTE to create connections and resolve hostnames to IP addresses to any
host (that's the '*' above). If you are using Oracle 8.1.5, you will not have the DBMS_JAVA package.
Rather, in this version you would grant the JAVASYSPRIV to the owner of jsock. You should be aware
that the JAVASYSPRIV is a very 'broad' privilege. Whereas DBMS_JAVA.GRANT_PERMISSION is very
granular, JAVASYSPRIV is very broad, and conveys a lot of privileges at once. Now that I have this
permission, we are ready to implement and test our SocketType, similar to the way we tested in which
we tested UTL_TCP initially. Here is the body of SocketType. The type body contains very little actual
code, and is mostly a layer on the SIMPLE_TCP_CLIENT package we just created. It hides the 'socket'
from the caller:
tkyte@TKYTE816> create or replace type body SocketType
2 as
3
4 static function crlf return varchar2
5 is
6 begin
7
return chr(13)||chr(10);
8 end;
9
10 member function peek return number
11 is
12 begin
13
return simple_tcp_client.peek( g_sock );
14 end;
15
16
17 member procedure send( p_data in varchar2 )
18 is
19 begin
20
simple_tcp_client.send( g_sock, utl_raw.cast_to_raw(p_data) );
21 end;
22
23 member procedure send_raw( p_data in raw )
24 is
25 begin
26
simple_tcp_client.send( g_sock, p_data );
27 end;
28
29 member procedure send( p_data in clob )
1253
5254appAQ.pdf 10
2/28/2005 6:50:08 PM
Appendix A
30
31
32
33
34
35
36
37
38
39
40
41
42
is
l_offset number default 1;
l_length number default dbms_lob.getlength(p_data);
l_amt
number default 4096;
begin
loop
exit when l_offset > l_length;
simple_tcp_client.send( g_sock,
utl_raw.cast_to_raw(
dbms_lob.substr(p_data,l_amt,l_offset) ) );
l_offset := l_offset + l_amt;
end loop;
end;
The SEND routine is overloaded for various data types, and takes a CLOB of arbitrary length. It will
break the CLOB into 4 KB chunks for transmission. The SEND_RAW routine below is similar, but
performs the operation for a BLOB:
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
member procedure send_raw( p_data in blob )
is
l_offset number default 1;
l_length number default dbms_lob.getlength(p_data);
l_amt
number default 4096;
begin
loop
exit when l_offset > l_length;
simple_tcp_client.send( g_sock,
dbms_lob.substr(p_data,l_amt,l_offset) );
l_offset := l_offset + l_amt;
end loop;
end;
member function recv return varchar2
is
l_raw_data
raw(4096);
begin
simple_tcp_client.recv( g_sock, l_raw_data );
return utl_raw.cast_to_varchar2(l_raw_data);
end;
member function recv_raw return raw
is
l_raw_data
raw(4096);
begin
simple_tcp_client.recv( g_sock, l_raw_data );
return l_raw_data;
end;
member function getline( p_remove_crlf in boolean default FALSE )
return varchar2
is
l_raw_data
raw(4096);
begin
1254
5254appAQ.pdf 11
2/28/2005 6:50:08 PM
UTL_TCP
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
simple_tcp_client.getline( g_sock, l_raw_data );
if ( p_remove_crlf ) then
return rtrim(
utl_raw.cast_to_varchar2(l_raw_data), SocketType.crlf );
else
return utl_raw.cast_to_varchar2(l_raw_data);
end if;
end;
member procedure initiate_connection( p_hostname in varchar2,
p_portno
in number )
is
l_data
varchar2(4069);
begin
-- we try to connect 10 times and if the tenth time
-- fails, we reraise the exception to the caller
for i in 1 .. 10 loop
begin
g_sock := simple_tcp_client.connect_to( p_hostname, p_portno );
exit;
exception
when others then
if ( i = 10 ) then raise; end if;
end;
end loop;
end;
We try the connection ten times in order to avoid issues with 'server busy' type messages. It is not
entirely necessary, but makes it so the caller doesn't get errors as often as it otherwise might on a busy
web server, or some other service.
107
108
109
110
111
112
113
114
115
116
member procedure close_connection
is
begin
simple_tcp_client.disconnect( g_sock );
g_sock := NULL;
end;
end;
/
Type body created.
As you can see, these are mostly convenience routines layered on top of SIMPLE_TCP_CLIENT to make
this package easier to use. It also serves as a nice way to encapsulate the functionality of the
SIMPLE_TCP_CLIENT in an object type. Using SocketType instead of UTL_TCP, our simple 'get a web
page via a proxy' routine looks like this:
1255
5254appAQ.pdf 12
2/28/2005 6:50:08 PM
Appendix A
tkyte@TKYTE816> declare
2
s
SocketType := SocketType(null);
3
buffer varchar2(4096);
4 BEGIN
5
s.initiate_connection( 'proxy-server', 80 );
6
s.send( 'GET http://www.oracle.com/ HTTP/1.0'||SocketType.CRLF);
7
s.send( SocketType.CRLF);
8
9
loop
10
buffer := s.recv;
11
exit when buffer is null;
12
dbms_output.put_line( substr( buffer,1,255 ) );
13
end loop;
14
s.close_connection;
15 END;
16 /
HTTP/1.1 200 OK
Date: Thu, 01 Feb 2001 00:16:05 GMT
Server: Apache/1.3.9 (Unix) mod_perl/1.21
ApacheJServ/1.1
yyyyyyyyyy: close
Content-Type: text/html
<head>
<title>Oracle Corporation</title>
This code is not radically different from using UTL_TCP directly, but it does show how encapsulating
your packages with an Object Type can add a nice feeling of object-oriented programming to your
PL/SQL. If you are a Java or C++ programmer, you feel very comfortable with the above code,
declaring a variable of type SocketType and then calling methods against that type. This is as opposed
to declaring a variable of some record type that you pass down to each routine as UTL_TCP does. The
above is more object-oriented than the procedural method first shown.
Summary
In this section we looked at the new functionality provided by the UTL_TCP package. We also
investigated an alternative implementation in Java. Additionally, we packaged this functionality into a
new Object Type for PL/SQL, fully encapsulating the capabilities of the TCP/IP socket nicely. We saw
how easy it is to integrate networking functionality into our PL/SQL applications using this facility, and
in an earlier section on UTL_HTTP, we saw how we can make use of this to provide full access to the
HTTP protocol.
1256
5254appAQ.pdf 13
2/28/2005 6:50:08 PM
UTL_TCP
1257
5254appAQ.pdf 14
2/28/2005 6:50:08 PM
Download