Skip to content

Commit 3953066

Browse files
committed
Merge branch 'experimental'
2 parents 9856b13 + fb6b8c2 commit 3953066

File tree

2 files changed

+60
-61
lines changed

2 files changed

+60
-61
lines changed

src/tarantool/connection.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
This module provides low-level API for Tarantool
55
'''
66

7+
import ctypes
78
import socket
89
import sys
910
import time
@@ -84,6 +85,38 @@ def connect(self):
8485
raise NetworkError(e)
8586

8687

88+
def _read_response(self):
89+
'''
90+
Read response from the transport (socket)
91+
92+
:return: tuple of the form (header, body)
93+
:rtype: tuple of two byte arrays
94+
'''
95+
# Read response header
96+
buff_header = ctypes.create_string_buffer(12)
97+
nbytes = self._socket.recv_into(buff_header, 12)
98+
99+
# Immediately raises an exception if the data cannot be read
100+
if nbytes != 12:
101+
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
102+
103+
# Extract body lenght from header
104+
body_length = struct_L.unpack(buff_header[4:8])[0]
105+
106+
# Unpack body if it is not empty (i.e. not PING)
107+
if body_length != 0:
108+
buff_body = ctypes.create_string_buffer(body_length)
109+
nbytes = self._socket.recv_into(buff_body)
110+
# Immediately raises an exception if the data cannot be read
111+
if nbytes != body_length:
112+
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
113+
else:
114+
buff_body = b""
115+
116+
return buff_header, buff_body
117+
118+
119+
87120
def _send_request_wo_reconnect(self, request, field_types=None):
88121
'''\
89122
:rtype: `Response` instance
@@ -96,7 +129,8 @@ def _send_request_wo_reconnect(self, request, field_types=None):
96129
for attempt in xrange(RETRY_MAX_ATTEMPTS): # pylint: disable=W0612
97130
try:
98131
self._socket.sendall(bytes(request))
99-
response = Response(self._socket, field_types)
132+
header, body = self._read_response()
133+
response = Response(header, body, field_types)
100134
except socket.error as e:
101135
raise NetworkError(e)
102136

src/tarantool/response.py

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# pylint: disable=C0301,W0105,W0401,W0614
33

44
import ctypes
5-
import socket
65
import struct
76
import sys
87

@@ -75,16 +74,19 @@ class Response(list):
7574
packet received from the server.
7675
'''
7776

78-
def __init__(self, _socket, field_types=None):
77+
def __init__(self, header, body, field_types=None):
7978
'''\
8079
Create an instance of `Response` using data received from the server.
8180
8281
__init__() itself reads data from the socket, parses response body and
8382
sets appropriate instance attributes.
8483
85-
:params _socket: socket connected to the server
86-
:type _socket: instance of socket.socket class (from stdlib)
84+
:param header: header of the response
85+
:type header: array of bytes
86+
:param body: body of the response
87+
:type body: array of bytes
8788
'''
89+
8890
# This is not necessary, because underlying list data structures are created in the __new__(). But let it be.
8991
super(Response, self).__init__()
9092

@@ -97,57 +99,10 @@ def __init__(self, _socket, field_types=None):
9799
self._rowcount = None
98100
self.field_types = field_types
99101

100-
# Read response header
101-
buff = ctypes.create_string_buffer(16)
102-
nbytes = _socket.recv_into(buff, 16, )
103-
104-
# Immediately raises an exception if the data cannot be read
105-
if nbytes != 16:
106-
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
107-
108-
# Unpack header (including <return_code> attribute)
109-
self._request_type, self._body_length, self._request_id, self._return_code = struct_LLLL.unpack(buff)
110-
111-
# Separate return_code and completion_code
112-
self._completion_status = self._return_code & 0x00ff
113-
self._return_code = self._return_code >> 8
114-
115-
# Unpack body if there is one (i.e. not PING)
116-
if self._body_length != 0:
117-
118-
# In the protocol description <body_length> includes 4 bytes of <return_code>
119-
self._body_length -= 4
120-
121-
# Read response body
122-
buff = ctypes.create_string_buffer(self._body_length)
123-
nbytes = _socket.recv_into(buff)
124-
125-
# Immediately raises an exception if the data cannot be read
126-
if nbytes != self._body_length:
127-
raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
128-
129-
if self._return_code == 0:
130-
# If no errors, unpack response body
131-
self._unpack_body(buff)
132-
else:
133-
# In case of error unpack body as error message
134-
self._unpack_message(buff)
135-
if self._completion_status == 2:
136-
raise DatabaseError(self._return_code, self._return_message)
137-
138-
139-
def _unpack_message(self, buff):
140-
'''\
141-
Extract error message from response body
142-
Called when return_code! = 0.
143-
144-
:param buff: buffer containing request body
145-
:type byff: ctypes buffer
146-
:return: error message
147-
:rtype: str
148-
'''
149-
150-
self._return_message = unicode(buff.value, "utf8", "replace")
102+
# Unpack header
103+
self._request_type, self._body_length, self._request_id = struct_LLL.unpack(header)
104+
if body:
105+
self._unpack_body(body)
151106

152107

153108
@staticmethod
@@ -209,16 +164,26 @@ def _unpack_body(self, buff):
209164
:type byff: ctypes buffer
210165
'''
211166

212-
# Unpack <count> (first 4 bytes) - how many records returned
213-
self._rowcount = struct_L.unpack_from(buff)[0]
167+
# Unpack <return_code> and <count> (how many records affected or selected)
168+
self._return_code, self._rowcount = struct_LL.unpack_from(buff, offset=0)
169+
170+
# Separate return_code and completion_code
171+
self._completion_status = self._return_code & 0x00ff
172+
self._return_code = self._return_code >> 8
173+
174+
# In case of an error unpack the body as an error message
175+
if self._return_code != 0:
176+
self._return_message = unicode(buff.value, "utf8", "replace")
177+
if self._completion_status == 2:
178+
raise DatabaseError(self._return_code, self._return_message)
214179

215-
# If the response body contains only <count> - there is no tuples to unpack
216-
if self._body_length == 4:
180+
# If the response don't contains any tuples - there is no tuples to unpack
181+
if self._body_length == 8:
217182
return
218183

219184
# Parse response tuples (<fq_tuple>)
220185
if self._rowcount > 0:
221-
offset = 4 # The first 4 bytes in the response body is the <count> we have already read
186+
offset = 8 # The first 4 bytes in the response body is the <count> we have already read
222187
while offset < self._body_length:
223188
'''
224189
# In resonse tuples have the form <size><tuple> (<fq_tuple> ::= <size><tuple>).

0 commit comments

Comments
 (0)