swift 2.32.0__py2.py3-none-any.whl → 2.34.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. swift/account/auditor.py +11 -0
  2. swift/account/reaper.py +11 -1
  3. swift/account/replicator.py +22 -0
  4. swift/account/server.py +13 -12
  5. swift-2.32.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
  6. swift-2.32.0.data/scripts/swift-config → swift/cli/config.py +1 -1
  7. swift-2.32.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
  8. swift-2.32.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
  9. swift-2.32.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
  10. swift/cli/info.py +131 -3
  11. swift-2.32.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
  12. swift-2.32.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
  13. swift-2.32.0.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +9 -18
  14. swift-2.32.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  15. swift/cli/relinker.py +1 -1
  16. swift/cli/reload.py +141 -0
  17. swift/cli/ringbuilder.py +24 -0
  18. swift/common/daemon.py +12 -2
  19. swift/common/db.py +14 -9
  20. swift/common/db_auditor.py +2 -2
  21. swift/common/db_replicator.py +6 -0
  22. swift/common/exceptions.py +12 -0
  23. swift/common/http_protocol.py +76 -3
  24. swift/common/manager.py +120 -5
  25. swift/common/memcached.py +24 -25
  26. swift/common/middleware/account_quotas.py +144 -43
  27. swift/common/middleware/backend_ratelimit.py +166 -24
  28. swift/common/middleware/catch_errors.py +1 -3
  29. swift/common/middleware/cname_lookup.py +3 -5
  30. swift/common/middleware/container_sync.py +6 -10
  31. swift/common/middleware/crypto/crypto_utils.py +4 -5
  32. swift/common/middleware/crypto/decrypter.py +4 -5
  33. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  34. swift/common/middleware/proxy_logging.py +57 -43
  35. swift/common/middleware/ratelimit.py +6 -7
  36. swift/common/middleware/recon.py +6 -7
  37. swift/common/middleware/s3api/acl_handlers.py +10 -1
  38. swift/common/middleware/s3api/controllers/__init__.py +3 -0
  39. swift/common/middleware/s3api/controllers/acl.py +3 -2
  40. swift/common/middleware/s3api/controllers/logging.py +2 -2
  41. swift/common/middleware/s3api/controllers/multi_upload.py +31 -15
  42. swift/common/middleware/s3api/controllers/obj.py +20 -1
  43. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  44. swift/common/middleware/s3api/s3api.py +6 -0
  45. swift/common/middleware/s3api/s3request.py +190 -74
  46. swift/common/middleware/s3api/s3response.py +48 -8
  47. swift/common/middleware/s3api/s3token.py +2 -2
  48. swift/common/middleware/s3api/utils.py +2 -1
  49. swift/common/middleware/slo.py +508 -310
  50. swift/common/middleware/staticweb.py +45 -14
  51. swift/common/middleware/tempauth.py +6 -4
  52. swift/common/middleware/tempurl.py +134 -93
  53. swift/common/middleware/x_profile/exceptions.py +1 -4
  54. swift/common/middleware/x_profile/html_viewer.py +9 -10
  55. swift/common/middleware/x_profile/profile_model.py +1 -2
  56. swift/common/middleware/xprofile.py +1 -2
  57. swift/common/request_helpers.py +101 -8
  58. swift/common/statsd_client.py +207 -0
  59. swift/common/storage_policy.py +1 -1
  60. swift/common/swob.py +5 -2
  61. swift/common/utils/__init__.py +331 -1774
  62. swift/common/utils/base.py +138 -0
  63. swift/common/utils/config.py +443 -0
  64. swift/common/utils/logs.py +999 -0
  65. swift/common/utils/timestamp.py +23 -2
  66. swift/common/wsgi.py +19 -3
  67. swift/container/auditor.py +11 -0
  68. swift/container/backend.py +136 -31
  69. swift/container/reconciler.py +11 -2
  70. swift/container/replicator.py +64 -7
  71. swift/container/server.py +276 -146
  72. swift/container/sharder.py +86 -42
  73. swift/container/sync.py +11 -1
  74. swift/container/updater.py +12 -2
  75. swift/obj/auditor.py +20 -3
  76. swift/obj/diskfile.py +63 -25
  77. swift/obj/expirer.py +154 -47
  78. swift/obj/mem_diskfile.py +2 -1
  79. swift/obj/mem_server.py +1 -0
  80. swift/obj/reconstructor.py +28 -4
  81. swift/obj/replicator.py +63 -24
  82. swift/obj/server.py +76 -59
  83. swift/obj/updater.py +12 -2
  84. swift/obj/watchers/dark_data.py +72 -34
  85. swift/proxy/controllers/account.py +3 -2
  86. swift/proxy/controllers/base.py +254 -148
  87. swift/proxy/controllers/container.py +274 -289
  88. swift/proxy/controllers/obj.py +120 -166
  89. swift/proxy/server.py +17 -13
  90. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +14 -4
  91. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/METADATA +9 -7
  92. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/RECORD +97 -120
  93. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +39 -0
  94. swift-2.34.0.dist-info/pbr.json +1 -0
  95. swift-2.32.0.data/scripts/swift-account-auditor +0 -23
  96. swift-2.32.0.data/scripts/swift-account-info +0 -52
  97. swift-2.32.0.data/scripts/swift-account-reaper +0 -23
  98. swift-2.32.0.data/scripts/swift-account-replicator +0 -34
  99. swift-2.32.0.data/scripts/swift-account-server +0 -23
  100. swift-2.32.0.data/scripts/swift-container-auditor +0 -23
  101. swift-2.32.0.data/scripts/swift-container-info +0 -56
  102. swift-2.32.0.data/scripts/swift-container-reconciler +0 -21
  103. swift-2.32.0.data/scripts/swift-container-replicator +0 -34
  104. swift-2.32.0.data/scripts/swift-container-server +0 -23
  105. swift-2.32.0.data/scripts/swift-container-sharder +0 -37
  106. swift-2.32.0.data/scripts/swift-container-sync +0 -23
  107. swift-2.32.0.data/scripts/swift-container-updater +0 -23
  108. swift-2.32.0.data/scripts/swift-dispersion-report +0 -24
  109. swift-2.32.0.data/scripts/swift-form-signature +0 -20
  110. swift-2.32.0.data/scripts/swift-init +0 -119
  111. swift-2.32.0.data/scripts/swift-object-auditor +0 -29
  112. swift-2.32.0.data/scripts/swift-object-expirer +0 -33
  113. swift-2.32.0.data/scripts/swift-object-info +0 -60
  114. swift-2.32.0.data/scripts/swift-object-reconstructor +0 -33
  115. swift-2.32.0.data/scripts/swift-object-relinker +0 -23
  116. swift-2.32.0.data/scripts/swift-object-replicator +0 -37
  117. swift-2.32.0.data/scripts/swift-object-server +0 -27
  118. swift-2.32.0.data/scripts/swift-object-updater +0 -23
  119. swift-2.32.0.data/scripts/swift-proxy-server +0 -23
  120. swift-2.32.0.data/scripts/swift-recon +0 -24
  121. swift-2.32.0.data/scripts/swift-ring-builder +0 -37
  122. swift-2.32.0.data/scripts/swift-ring-builder-analyzer +0 -22
  123. swift-2.32.0.data/scripts/swift-ring-composer +0 -22
  124. swift-2.32.0.dist-info/pbr.json +0 -1
  125. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
  126. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
  127. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
@@ -36,7 +36,8 @@ from swift.common.swob import HTTPBadRequest, \
36
36
  HTTPServiceUnavailable, Range, is_chunked, multi_range_iterator, \
37
37
  HTTPPreconditionFailed, wsgi_to_bytes, wsgi_unquote, wsgi_to_str
38
38
  from swift.common.utils import split_path, validate_device_partition, \
39
- close_if_possible, maybe_multipart_byteranges_to_document_iters, \
39
+ close_if_possible, friendly_close, \
40
+ maybe_multipart_byteranges_to_document_iters, \
40
41
  multipart_byteranges_to_document_iters, parse_content_type, \
41
42
  parse_content_range, csv_append, list_from_csv, Spliterator, quote, \
42
43
  RESERVED, config_true_value, md5, CloseableChain, select_ip_port
@@ -94,6 +95,39 @@ def get_param(req, name, default=None):
94
95
  return value
95
96
 
96
97
 
98
+ def get_valid_part_num(req):
99
+ """
100
+ Any non-range GET or HEAD request for a SLO object may include a
101
+ part-number parameter in query string. If the passed in request
102
+ includes a part-number parameter it will be parsed into a valid integer
103
+ and returned. If the passed in request does not include a part-number
104
+ param we will return None. If the part-number parameter is invalid for
105
+ the given request we will raise the appropriate HTTP exception
106
+
107
+ :param req: the request object
108
+
109
+ :returns: validated part-number value or None
110
+ :raises HTTPBadRequest: if request or part-number param is not valid
111
+ """
112
+ part_number_param = get_param(req, 'part-number')
113
+ if part_number_param is None:
114
+ return None
115
+ try:
116
+ part_number = int(part_number_param)
117
+ if part_number <= 0:
118
+ raise ValueError
119
+ except ValueError:
120
+ raise HTTPBadRequest('Part number must be an integer greater '
121
+ 'than 0')
122
+
123
+ if req.range:
124
+ raise HTTPBadRequest(req=req,
125
+ body='Range requests are not supported '
126
+ 'with part number queries')
127
+
128
+ return part_number
129
+
130
+
97
131
  def validate_params(req, names):
98
132
  """
99
133
  Get list of parameters from an HTTP request, validating the encoding of
@@ -460,15 +494,17 @@ class SegmentedIterable(object):
460
494
  :param app: WSGI application from which segments will come
461
495
 
462
496
  :param listing_iter: iterable yielding the object segments to fetch,
463
- along with the byte subranges to fetch, in the form of a 5-tuple
464
- (object-path, object-etag, object-size, first-byte, last-byte).
497
+ along with the byte sub-ranges to fetch. Each yielded item should be a
498
+ dict with the following keys: ``path`` or ``raw_data``,
499
+ ``first-byte``, ``last-byte``, ``hash`` (optional), ``bytes``
500
+ (optional).
465
501
 
466
- If object-etag is None, no MD5 verification will be done.
502
+ If ``hash`` is None, no MD5 verification will be done.
467
503
 
468
- If object-size is None, no length verification will be done.
504
+ If ``bytes`` is None, no length verification will be done.
469
505
 
470
- If first-byte and last-byte are None, then the entire object will be
471
- fetched.
506
+ If ``first-byte`` and ``last-byte`` are None, then the entire object
507
+ will be fetched.
472
508
 
473
509
  :param max_get_time: maximum permitted duration of a GET request (seconds)
474
510
  :param logger: logger object
@@ -740,7 +776,10 @@ class SegmentedIterable(object):
740
776
  for x in mri:
741
777
  yield x
742
778
  finally:
743
- self.close()
779
+ # Spliterator and multi_range_iterator can't possibly know we've
780
+ # consumed the whole of the app_iter, but we want to read/close the
781
+ # final segment response
782
+ friendly_close(self.app_iter)
744
783
 
745
784
  def validate_first_segment(self):
746
785
  """
@@ -900,6 +939,24 @@ def update_ignore_range_header(req, name):
900
939
  req.headers[hdr] = csv_append(req.headers.get(hdr), name)
901
940
 
902
941
 
942
+ def resolve_ignore_range_header(req, metadata):
943
+ """
944
+ Helper function to remove Range header from request if metadata matching
945
+ the X-Backend-Ignore-Range-If-Metadata-Present header is found.
946
+
947
+ :param req: a swob Request
948
+ :param metadata: dictionary of object metadata
949
+ """
950
+ ignore_range_headers = set(
951
+ h.strip().lower()
952
+ for h in req.headers.get(
953
+ 'X-Backend-Ignore-Range-If-Metadata-Present',
954
+ '').split(','))
955
+ if ignore_range_headers.intersection(
956
+ h.lower() for h in metadata):
957
+ req.headers.pop('Range', None)
958
+
959
+
903
960
  def is_use_replication_network(headers=None):
904
961
  """
905
962
  Determine if replication network should be used.
@@ -936,3 +993,39 @@ def get_ip_port(node, headers):
936
993
  """
937
994
  return select_ip_port(
938
995
  node, use_replication=is_use_replication_network(headers))
996
+
997
+
998
+ def is_open_expired(app, req):
999
+ """
1000
+ Helper function to check if a request with the header 'x-open-expired'
1001
+ can access an object that has not yet been reaped by the object-expirer
1002
+ based on the allow_open_expired global config.
1003
+
1004
+ :param app: the application instance
1005
+ :param req: request object
1006
+ """
1007
+ return (config_true_value(app.allow_open_expired) and
1008
+ config_true_value(req.headers.get('x-open-expired')))
1009
+
1010
+
1011
+ def is_backend_open_expired(request):
1012
+ """
1013
+ Helper function to check if a request has either the headers
1014
+ 'x-backend-open-expired' or 'x-backend-replication' for the backend
1015
+ to access expired objects.
1016
+
1017
+ :param request: request object
1018
+ """
1019
+ x_backend_open_expired = config_true_value(request.headers.get(
1020
+ 'x-backend-open-expired', 'false'))
1021
+ x_backend_replication = config_true_value(request.headers.get(
1022
+ 'x-backend-replication', 'false'))
1023
+ return x_backend_open_expired or x_backend_replication
1024
+
1025
+
1026
+ def append_log_info(environ, log_info):
1027
+ environ.setdefault('swift.log_info', []).append(log_info)
1028
+
1029
+
1030
+ def get_log_info(environ):
1031
+ return ','.join(environ.get('swift.log_info', []))
@@ -0,0 +1,207 @@
1
+ # Copyright (c) 2010-2012 OpenStack Foundation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """ Statsd Client """
17
+
18
+ import time
19
+ import warnings
20
+ from contextlib import closing
21
+ from random import random
22
+
23
+ from eventlet.green import socket
24
+ import six
25
+
26
+
27
+ def get_statsd_client(conf=None, tail_prefix='', logger=None):
28
+ """
29
+ Get an instance of StatsdClient using config settings.
30
+
31
+ **config and defaults**::
32
+
33
+ log_statsd_host = (disabled)
34
+ log_statsd_port = 8125
35
+ log_statsd_default_sample_rate = 1.0
36
+ log_statsd_sample_rate_factor = 1.0
37
+ log_statsd_metric_prefix = (empty-string)
38
+
39
+ :param conf: Configuration dict to read settings from
40
+ :param tail_prefix: tail prefix to pass to statsd client
41
+ :param logger: stdlib logger instance used by statsd client for logging
42
+ :return: an instance of ``StatsdClient``
43
+
44
+ """
45
+ conf = conf or {}
46
+
47
+ host = conf.get('log_statsd_host')
48
+ port = int(conf.get('log_statsd_port', 8125))
49
+ base_prefix = conf.get('log_statsd_metric_prefix', '')
50
+ default_sample_rate = float(
51
+ conf.get('log_statsd_default_sample_rate', 1))
52
+ sample_rate_factor = float(
53
+ conf.get('log_statsd_sample_rate_factor', 1))
54
+
55
+ return StatsdClient(host, port, base_prefix=base_prefix,
56
+ tail_prefix=tail_prefix,
57
+ default_sample_rate=default_sample_rate,
58
+ sample_rate_factor=sample_rate_factor, logger=logger)
59
+
60
+
61
+ class StatsdClient(object):
62
+ def __init__(self, host, port, base_prefix='', tail_prefix='',
63
+ default_sample_rate=1, sample_rate_factor=1, logger=None):
64
+ self._host = host
65
+ self._port = port
66
+ self._base_prefix = base_prefix
67
+ self._default_sample_rate = default_sample_rate
68
+ self._sample_rate_factor = sample_rate_factor
69
+ self.random = random
70
+ self.logger = logger
71
+ self._set_prefix(tail_prefix)
72
+ self._sock_family = self._target = None
73
+
74
+ if self._host:
75
+ self._set_sock_family_and_target(self._host, self._port)
76
+
77
+ def _set_sock_family_and_target(self, host, port):
78
+ # Determine if host is IPv4 or IPv6
79
+ addr_info = None
80
+ try:
81
+ addr_info = socket.getaddrinfo(host, port, socket.AF_INET)
82
+ self._sock_family = socket.AF_INET
83
+ except socket.gaierror:
84
+ try:
85
+ addr_info = socket.getaddrinfo(host, port, socket.AF_INET6)
86
+ self._sock_family = socket.AF_INET6
87
+ except socket.gaierror:
88
+ # Don't keep the server from starting from what could be a
89
+ # transient DNS failure. Any hostname will get re-resolved as
90
+ # necessary in the .sendto() calls.
91
+ # However, we don't know if we're IPv4 or IPv6 in this case, so
92
+ # we assume legacy IPv4.
93
+ self._sock_family = socket.AF_INET
94
+
95
+ # NOTE: we use the original host value, not the DNS-resolved one
96
+ # because if host is a hostname, we don't want to cache the DNS
97
+ # resolution for the entire lifetime of this process. Let standard
98
+ # name resolution caching take effect. This should help operators use
99
+ # DNS trickery if they want.
100
+ if addr_info is not None:
101
+ # addr_info is a list of 5-tuples with the following structure:
102
+ # (family, socktype, proto, canonname, sockaddr)
103
+ # where sockaddr is the only thing of interest to us, and we only
104
+ # use the first result. We want to use the originally supplied
105
+ # host (see note above) and the remainder of the variable-length
106
+ # sockaddr: IPv4 has (address, port) while IPv6 has (address,
107
+ # port, flow info, scope id).
108
+ sockaddr = addr_info[0][-1]
109
+ self._target = (host,) + (sockaddr[1:])
110
+ else:
111
+ self._target = (host, port)
112
+
113
+ def _set_prefix(self, tail_prefix):
114
+ """
115
+ Modifies the prefix that is added to metric names. The resulting prefix
116
+ is the concatenation of the component parts `base_prefix` and
117
+ `tail_prefix`. Only truthy components are included. Each included
118
+ component is followed by a period, e.g.::
119
+
120
+ <base_prefix>.<tail_prefix>.
121
+ <tail_prefix>.
122
+ <base_prefix>.
123
+ <the empty string>
124
+
125
+ Note: this method is expected to be called from the constructor only,
126
+ but exists to provide backwards compatible functionality for the
127
+ deprecated set_prefix() method.
128
+
129
+ :param tail_prefix: The new value of tail_prefix
130
+ """
131
+ if tail_prefix and self._base_prefix:
132
+ self._prefix = '.'.join([self._base_prefix, tail_prefix, ''])
133
+ elif tail_prefix:
134
+ self._prefix = tail_prefix + '.'
135
+ elif self._base_prefix:
136
+ self._prefix = self._base_prefix + '.'
137
+ else:
138
+ self._prefix = ''
139
+
140
+ def set_prefix(self, tail_prefix):
141
+ """
142
+ This method is deprecated; use the ``tail_prefix`` argument of the
143
+ constructor when instantiating the class instead.
144
+ """
145
+ warnings.warn(
146
+ 'set_prefix() is deprecated; use the ``tail_prefix`` argument of '
147
+ 'the constructor when instantiating the class instead.',
148
+ DeprecationWarning, stacklevel=2
149
+ )
150
+ self._set_prefix(tail_prefix)
151
+
152
+ def _send(self, m_name, m_value, m_type, sample_rate):
153
+ if not self._host:
154
+ # StatsD not configured
155
+ return
156
+
157
+ if sample_rate is None:
158
+ sample_rate = self._default_sample_rate
159
+ sample_rate = sample_rate * self._sample_rate_factor
160
+
161
+ parts = ['%s%s:%s' % (self._prefix, m_name, m_value), m_type]
162
+ if sample_rate < 1:
163
+ if self.random() < sample_rate:
164
+ parts.append('@%s' % (sample_rate,))
165
+ else:
166
+ return
167
+ if six.PY3:
168
+ parts = [part.encode('utf-8') for part in parts]
169
+ # Ideally, we'd cache a sending socket in self, but that
170
+ # results in a socket getting shared by multiple green threads.
171
+ with closing(self._open_socket()) as sock:
172
+ try:
173
+ return sock.sendto(b'|'.join(parts), self._target)
174
+ except IOError as err:
175
+ if self.logger:
176
+ self.logger.warning(
177
+ 'Error sending UDP message to %(target)r: %(err)s',
178
+ {'target': self._target, 'err': err})
179
+
180
+ def _open_socket(self):
181
+ return socket.socket(self._sock_family, socket.SOCK_DGRAM)
182
+
183
+ def update_stats(self, m_name, m_value, sample_rate=None):
184
+ return self._send(m_name, m_value, 'c', sample_rate)
185
+
186
+ def increment(self, metric, sample_rate=None):
187
+ return self.update_stats(metric, 1, sample_rate)
188
+
189
+ def decrement(self, metric, sample_rate=None):
190
+ return self.update_stats(metric, -1, sample_rate)
191
+
192
+ def _timing(self, metric, timing_ms, sample_rate):
193
+ # This method was added to disagregate timing metrics when testing
194
+ return self._send(metric, round(timing_ms, 4), 'ms', sample_rate)
195
+
196
+ def timing(self, metric, timing_ms, sample_rate=None):
197
+ return self._timing(metric, timing_ms, sample_rate)
198
+
199
+ def timing_since(self, metric, orig_time, sample_rate=None):
200
+ return self._timing(metric, (time.time() - orig_time) * 1000,
201
+ sample_rate)
202
+
203
+ def transfer_rate(self, metric, elapsed_time, byte_xfer, sample_rate=None):
204
+ if byte_xfer:
205
+ return self.timing(metric,
206
+ elapsed_time * 1000 / byte_xfer * 1000,
207
+ sample_rate)
@@ -160,7 +160,7 @@ class BaseStoragePolicy(object):
160
160
  object_ring=None, aliases='',
161
161
  diskfile_module='egg:swift#replication.fs'):
162
162
  # do not allow BaseStoragePolicy class to be instantiated directly
163
- if type(self) == BaseStoragePolicy:
163
+ if type(self) is BaseStoragePolicy:
164
164
  raise TypeError("Can't instantiate BaseStoragePolicy directly")
165
165
  # policy parameter validation
166
166
  try:
swift/common/swob.py CHANGED
@@ -55,7 +55,7 @@ from six.moves import urllib
55
55
 
56
56
  from swift.common.header_key_dict import HeaderKeyDict
57
57
  from swift.common.utils import UTC, reiterate, split_path, Timestamp, pairs, \
58
- close_if_possible, closing_if_possible, config_true_value, drain_and_close
58
+ close_if_possible, closing_if_possible, config_true_value, friendly_close
59
59
  from swift.common.exceptions import InvalidTimestamp
60
60
 
61
61
 
@@ -1395,12 +1395,15 @@ class Response(object):
1395
1395
  if empty_resp is not None:
1396
1396
  self.status = empty_resp
1397
1397
  self.content_length = 0
1398
+ # the existing successful response and it's app_iter have been
1399
+ # determined to not meet the conditions of the reqeust, the
1400
+ # response app_iter should be closed but not drained.
1398
1401
  close_if_possible(app_iter)
1399
1402
  return [b'']
1400
1403
 
1401
1404
  if self.request and self.request.method == 'HEAD':
1402
1405
  # We explicitly do NOT want to set self.content_length to 0 here
1403
- drain_and_close(app_iter) # be friendly to our app_iter
1406
+ friendly_close(app_iter) # be friendly to our app_iter
1404
1407
  return [b'']
1405
1408
 
1406
1409
  if self.conditional_response and self.request and \