Skip to content

Commit 3a9a296

Browse files
Fixed bug with handling of redirect data returned by some SCAN listeners
(#39).
1 parent c6f6b20 commit 3a9a296

File tree

8 files changed

+165
-158
lines changed

8 files changed

+165
-158
lines changed

doc/src/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Thin Mode Changes
1818
the database sends a password challenge with a verifier type that is not
1919
recognized, instead of `ORA-01017: invalid username/password`
2020
(`issue 26 <https://github.com/oracle/python-oracledb/issues/26>`__).
21+
#) Fixed bug with handling of redirect data returned by some SCAN listeners
22+
(`issue 39 <https://github.com/oracle/python-oracledb/issues/39>`__).
2123

2224
Common Changes
2325
++++++++++++++

src/oracledb/base_impl.pxd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ cdef class ConnectParamsImpl:
186186
cdef str _get_wallet_password(self)
187187
cdef int _parse_connect_string(self, str connect_string) except -1
188188
cdef int _process_connect_descriptor(self, dict args) except -1
189-
cdef int _process_redirect_data(self, str redirect_data) except -1
190189
cdef int _set_new_password(self, str password) except -1
191190
cdef int _set_password(self, str password) except -1
192191
cdef int _set_wallet_password(self, str password) except -1

src/oracledb/connect_params.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,9 @@ def _address_attr(f):
264264
"""
265265
@functools.wraps(f)
266266
def wrapped(self):
267-
output = []
268-
for description in self._impl.description_list.descriptions:
269-
for address_list in description.address_lists:
270-
for address in address_list.addresses:
271-
output.append(getattr(address, f.__name__))
272-
return output if len(output) > 1 else output[0]
267+
values = [getattr(a, f.__name__) \
268+
for a in self._impl._get_addresses()]
269+
return values if len(values) > 1 else values[0]
273270
return wrapped
274271

275272
def _description_attr(f):
@@ -278,10 +275,9 @@ def _description_attr(f):
278275
"""
279276
@functools.wraps(f)
280277
def wrapped(self):
281-
output = []
282-
for description in self._impl.description_list.descriptions:
283-
output.append(getattr(description, f.__name__))
284-
return output if len(output) > 1 else output[0]
278+
values = [getattr(d, f.__name__) \
279+
for d in self._impl.description_list.descriptions]
280+
return values if len(values) > 1 else values[0]
285281
return wrapped
286282

287283
@property

src/oracledb/impl/base/connect_params.pyx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -335,21 +335,6 @@ cdef class ConnectParamsImpl:
335335
address.set_from_args(addr_args)
336336
address_list.addresses.append(address)
337337

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

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

src/oracledb/impl/thin/connection.pyx

Lines changed: 39 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,37 @@ 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:
91119
cdef:
92120
bint load_balance = description.load_balance
93121
bint raise_exc = False
@@ -126,25 +154,18 @@ cdef class ThinConnImpl(BaseConnImpl):
126154
raise_exc = i == num_attempts - 1 \
127155
and j == num_lists - 1 \
128156
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
157+
self._connect_with_address(address, description, params,
158+
raise_exc)
135159
if self._protocol._in_connect:
136160
continue
137161
address_list.lru_index = (idx1 + 1) % num_addresses
138162
description.lru_index = (idx2 + 1) % num_lists
139-
return
163+
return 0
140164
time.sleep(description.retry_delay)
141165

142-
cdef ConnectParamsImpl _connect_with_params(self,
143-
ConnectParamsImpl params):
166+
cdef int _connect_with_params(self, ConnectParamsImpl params) except -1:
144167
"""
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.
168+
Internal method used for connecting with the given parameters.
148169
"""
149170
cdef:
150171
DescriptionList description_list = params.description_list
@@ -160,14 +181,10 @@ cdef class ThinConnImpl(BaseConnImpl):
160181
else:
161182
idx = i
162183
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:
184+
self._connect_with_description(description, params, final_desc)
185+
if not self._protocol._in_connect:
168186
description_list.lru_index = (idx + 1) % num_descriptions
169187
break
170-
return redirect_params
171188

172189
cdef Message _create_message(self, type typ):
173190
"""
@@ -183,74 +200,6 @@ cdef class ThinConnImpl(BaseConnImpl):
183200
self._pool = None
184201
self._protocol._force_close()
185202

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.settimeout(None)
228-
if address.protocol == "tcps":
229-
sock = get_ssl_socket(sock, params, description, address)
230-
self._drcp_enabled = description.server_type == "pooled"
231-
if self._cclass is None:
232-
self._cclass = description.cclass
233-
self._protocol._set_socket(sock)
234-
redirect_params = self._protocol._connect_phase_one(self, params,
235-
description,
236-
address)
237-
if redirect_params is not None:
238-
return redirect_params
239-
except exceptions.DatabaseError:
240-
if raise_exception:
241-
raise
242-
return
243-
except (socket.gaierror, ConnectionRefusedError) as e:
244-
if raise_exception:
245-
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
246-
exception=str(e))
247-
return
248-
except Exception as e:
249-
errors._raise_err(errors.ERR_CONNECTION_FAILED, cause=e,
250-
exception=str(e))
251-
return
252-
self._protocol._connect_phase_two(self, description, params)
253-
254203
cdef Statement _get_statement(self, str sql, bint cache_statement):
255204
"""
256205
Get a statement from the statement cache, or prepare a new statement
@@ -340,14 +289,9 @@ cdef class ThinConnImpl(BaseConnImpl):
340289
self._protocol._process_single_message(message)
341290

342291
def connect(self, ConnectParamsImpl params):
343-
cdef ConnectParamsImpl redirect_params
344292
if params._password is None:
345293
errors._raise_err(errors.ERR_NO_PASSWORD)
346-
while True:
347-
redirect_params = self._connect_with_params(params)
348-
if redirect_params is None:
349-
break
350-
params = redirect_params
294+
self._connect_with_params(params)
351295
self._statement_cache = collections.OrderedDict()
352296
self._statement_cache_size = params.stmtcachesize
353297
self._statement_cache_lock = threading.Lock()

src/oracledb/impl/thin/messages.pyx

Lines changed: 4 additions & 5 deletions
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)