Skip to content

Commit 47c3b2c

Browse files
Fixed bug with handling of redirect data returned by some SCAN listeners
(#39).
1 parent 1b589d0 commit 47c3b2c

File tree

7 files changed

+162
-149
lines changed

7 files changed

+162
-149
lines changed

doc/src/release_notes.rst

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Thin Mode Changes
2323
the database sends a password challenge with a verifier type that is not
2424
recognized, instead of `ORA-01017: invalid username/password`
2525
(`issue 26 <https://github.com/oracle/python-oracledb/issues/26>`__).
26+
#) Fixed bug with handling of redirect data returned by some SCAN listeners
27+
(`issue 39 <https://github.com/oracle/python-oracledb/issues/39>`__).
2628
#) Internally, before a connection is returned from a pool, check for control
2729
packets from the server (which may inform the client that the connection
2830
needs to be closed and a new one established).

src/oracledb/base_impl.pxd

-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ cdef class ConnectParamsImpl:
187187
cdef str _get_wallet_password(self)
188188
cdef int _parse_connect_string(self, str connect_string) except -1
189189
cdef int _process_connect_descriptor(self, dict args) except -1
190-
cdef int _process_redirect_data(self, str redirect_data) except -1
191190
cdef int _set_new_password(self, str password) except -1
192191
cdef int _set_password(self, str password) except -1
193192
cdef int _set_wallet_password(self, str password) except -1

src/oracledb/connect_params.py

+6-10
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,9 @@ def _address_attr(f):
271271
"""
272272
@functools.wraps(f)
273273
def wrapped(self):
274-
output = []
275-
for description in self._impl.description_list.descriptions:
276-
for address_list in description.address_lists:
277-
for address in address_list.addresses:
278-
output.append(getattr(address, f.__name__))
279-
return output if len(output) > 1 else output[0]
274+
values = [getattr(a, f.__name__) \
275+
for a in self._impl._get_addresses()]
276+
return values if len(values) > 1 else values[0]
280277
return wrapped
281278

282279
def _description_attr(f):
@@ -285,10 +282,9 @@ def _description_attr(f):
285282
"""
286283
@functools.wraps(f)
287284
def wrapped(self):
288-
output = []
289-
for description in self._impl.description_list.descriptions:
290-
output.append(getattr(description, f.__name__))
291-
return output if len(output) > 1 else output[0]
285+
values = [getattr(d, f.__name__) \
286+
for d in self._impl.description_list.descriptions]
287+
return values if len(values) > 1 else values[0]
292288
return wrapped
293289

294290
@property

src/oracledb/impl/base/connect_params.pyx

+12-15
Original file line numberDiff line numberDiff line change
@@ -337,21 +337,6 @@ cdef class ConnectParamsImpl:
337337
address.set_from_args(addr_args)
338338
address_list.addresses.append(address)
339339

340-
cdef int _process_redirect_data(self, str redirect_data) except -1:
341-
"""
342-
Internal method used for parsing the redirect data that is returned
343-
from a listener in order to determine the new host and part that should
344-
be used to connect to the database.
345-
"""
346-
cdef:
347-
dict args = {}
348-
pos = redirect_data.find('\x00')
349-
if pos < 0:
350-
errors._raise_err(errors.ERR_INVALID_REDIRECT_DATA,
351-
data=redirect_data)
352-
_parse_connect_descriptor(redirect_data[:pos], args)
353-
self._process_connect_descriptor(args)
354-
355340
cdef int _set_new_password(self, str password) except -1:
356341
"""
357342
Sets the new password on the instance after first obfuscating it.
@@ -404,6 +389,18 @@ cdef class ConnectParamsImpl:
404389
new_params._copy(self)
405390
return new_params
406391

392+
def _get_addresses(self):
393+
"""
394+
Return a list of the stored addresses.
395+
"""
396+
cdef:
397+
AddressList addr_list
398+
Description desc
399+
Address addr
400+
return [addr for desc in self.description_list.descriptions \
401+
for addr_list in desc.address_lists \
402+
for addr in addr_list.addresses]
403+
407404
def get_connect_string(self):
408405
"""
409406
Internal method for getting the connect string. This will either be the

src/oracledb/impl/thin/connection.pyx

+42-96
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,40 @@ cdef class ThinConnImpl(BaseConnImpl):
8585
elif stmt._cursor_id != 0:
8686
self._add_cursor_to_close(stmt)
8787

88-
cdef object _connect_with_description(self, Description description,
89-
ConnectParamsImpl params,
90-
bint final_desc):
88+
cdef int _connect_with_address(self, Address address,
89+
Description description,
90+
ConnectParamsImpl params,
91+
bint raise_exception) except -1:
92+
"""
93+
Internal method used for connecting with the given description and
94+
address.
95+
"""
96+
try:
97+
self._protocol._connect_phase_one(self, params, description,
98+
address)
99+
except exceptions.DatabaseError:
100+
if raise_exception:
101+
raise
102+
return 0
103+
except (socket.gaierror, ConnectionRefusedError) as e:
104+
if raise_exception:
105+
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
106+
exception=str(e))
107+
return 0
108+
except Exception as e:
109+
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
110+
exception=str(e))
111+
self._drcp_enabled = description.server_type == "pooled"
112+
if self._cclass is None:
113+
self._cclass = description.cclass
114+
self._protocol._connect_phase_two(self, description, params)
115+
116+
cdef int _connect_with_description(self, Description description,
117+
ConnectParamsImpl params,
118+
bint final_desc) except -1:
119+
"""
120+
Internal method used for connecting with the given description.
121+
"""
91122
cdef:
92123
bint load_balance = description.load_balance
93124
bint raise_exc = False
@@ -126,25 +157,18 @@ cdef class ThinConnImpl(BaseConnImpl):
126157
raise_exc = i == num_attempts - 1 \
127158
and j == num_lists - 1 \
128159
and k == num_addresses - 1
129-
redirect_params = self._connect_with_address(address,
130-
description,
131-
params,
132-
raise_exc)
133-
if redirect_params is not None:
134-
return redirect_params
160+
self._connect_with_address(address, description, params,
161+
raise_exc)
135162
if self._protocol._in_connect:
136163
continue
137164
address_list.lru_index = (idx1 + 1) % num_addresses
138165
description.lru_index = (idx2 + 1) % num_lists
139-
return
166+
return 0
140167
time.sleep(description.retry_delay)
141168

142-
cdef ConnectParamsImpl _connect_with_params(self,
143-
ConnectParamsImpl params):
169+
cdef int _connect_with_params(self, ConnectParamsImpl params) except -1:
144170
"""
145-
Internal method used for connecting with the given parameters. If the
146-
listener requests a redirect, the redirect data is returned so that
147-
this process can be repeated as needed.
171+
Internal method used for connecting with the given parameters.
148172
"""
149173
cdef:
150174
DescriptionList description_list = params.description_list
@@ -160,14 +184,10 @@ cdef class ThinConnImpl(BaseConnImpl):
160184
else:
161185
idx = i
162186
description = descriptions[idx]
163-
redirect_params = self._connect_with_description(description,
164-
params,
165-
final_desc)
166-
if redirect_params is not None \
167-
or not self._protocol._in_connect:
187+
self._connect_with_description(description, params, final_desc)
188+
if not self._protocol._in_connect:
168189
description_list.lru_index = (idx + 1) % num_descriptions
169190
break
170-
return redirect_params
171191

172192
cdef Message _create_message(self, type typ):
173193
"""
@@ -183,75 +203,6 @@ cdef class ThinConnImpl(BaseConnImpl):
183203
self._pool = None
184204
self._protocol._force_close()
185205

186-
cdef object _connect_with_address(self, Address address,
187-
Description description,
188-
ConnectParamsImpl params,
189-
bint raise_exception):
190-
"""
191-
Creates a socket on which to communicate using the provided parameters.
192-
If a proxy is configured, a connection to the proxy is established and
193-
the target host and port is forwarded to the proxy. The socket is used
194-
to establish a connection with the database. If a redirect is
195-
required, the redirect parameters are returned.
196-
"""
197-
cdef:
198-
bint use_proxy = (address.https_proxy is not None)
199-
double timeout = description.tcp_connect_timeout
200-
if use_proxy:
201-
if address.protocol != "tcps":
202-
errors._raise_err(errors.ERR_HTTPS_PROXY_REQUIRES_TCPS)
203-
connect_info = (address.https_proxy, address.https_proxy_port)
204-
else:
205-
connect_info = (address.host, address.port)
206-
try:
207-
sock = socket.create_connection(connect_info, timeout)
208-
if use_proxy:
209-
data = f"CONNECT {address.host}:{address.port} HTTP/1.0\r\n\r\n"
210-
sock.send(data.encode())
211-
reply = sock.recv(1024)
212-
match = re.search('HTTP/1.[01]\\s+(\\d+)\\s+', reply.decode())
213-
if match is None or match.groups()[0] != '200':
214-
errors._raise_err(errors.ERR_PROXY_FAILURE,
215-
response=reply.decode())
216-
if description.expire_time > 0:
217-
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
218-
if hasattr(socket, "TCP_KEEPIDLE") \
219-
and hasattr(socket, "TCP_KEEPINTVL") \
220-
and hasattr(socket, "TCP_KEEPCNT"):
221-
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
222-
description.expire_time * 60)
223-
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL,
224-
6)
225-
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT,
226-
10)
227-
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
228-
sock.settimeout(None)
229-
if address.protocol == "tcps":
230-
sock = get_ssl_socket(sock, params, description, address)
231-
self._drcp_enabled = description.server_type == "pooled"
232-
if self._cclass is None:
233-
self._cclass = description.cclass
234-
self._protocol._set_socket(sock)
235-
redirect_params = self._protocol._connect_phase_one(self, params,
236-
description,
237-
address)
238-
if redirect_params is not None:
239-
return redirect_params
240-
except exceptions.DatabaseError:
241-
if raise_exception:
242-
raise
243-
return
244-
except (socket.gaierror, ConnectionRefusedError) as e:
245-
if raise_exception:
246-
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
247-
exception=str(e))
248-
return
249-
except Exception as e:
250-
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
251-
exception=str(e))
252-
return
253-
self._protocol._connect_phase_two(self, description, params)
254-
255206
cdef Statement _get_statement(self, str sql, bint cache_statement):
256207
"""
257208
Get a statement from the statement cache, or prepare a new statement
@@ -341,14 +292,9 @@ cdef class ThinConnImpl(BaseConnImpl):
341292
self._protocol._process_single_message(message)
342293

343294
def connect(self, ConnectParamsImpl params):
344-
cdef ConnectParamsImpl redirect_params
345295
if params._password is None:
346296
errors._raise_err(errors.ERR_NO_PASSWORD)
347-
while True:
348-
redirect_params = self._connect_with_params(params)
349-
if redirect_params is None:
350-
break
351-
params = redirect_params
297+
self._connect_with_params(params)
352298
self._statement_cache = collections.OrderedDict()
353299
self._statement_cache_size = params.stmtcachesize
354300
self._statement_cache_lock = threading.Lock()

src/oracledb/impl/thin/messages.pyx

+4-5
Original file line numberDiff line numberDiff line change
@@ -1540,7 +1540,8 @@ cdef class ConnectMessage(Message):
15401540
bytes connect_string_bytes
15411541
Description description
15421542
str redirect_data
1543-
Address address
1543+
str host
1544+
int port
15441545

15451546
cdef int process(self, ReadBuffer buf) except -1:
15461547
cdef:
@@ -1572,13 +1573,11 @@ cdef class ConnectMessage(Message):
15721573
if error_code_int == TNS_ERR_INVALID_SERVICE_NAME:
15731574
errors._raise_err(errors.ERR_INVALID_SERVICE_NAME,
15741575
service_name=self.description.service_name,
1575-
host=self.address.host,
1576-
port=self.address.port)
1576+
host=self.host, port=self.port)
15771577
elif error_code_int == TNS_ERR_INVALID_SID:
15781578
errors._raise_err(errors.ERR_INVALID_SID,
15791579
sid=self.description.sid,
1580-
host=self.address.host,
1581-
port=self.address.port)
1580+
host=self.host, port=self.port)
15821581
errors._raise_err(errors.ERR_LISTENER_REFUSED_CONNECTION,
15831582
error_code=error_code)
15841583

0 commit comments

Comments
 (0)