You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2014/03/02 18:50:50 UTC
git commit: TS-2553: improve metalink handling of event completion
Repository: trafficserver
Updated Branches:
refs/heads/master 203fc9971 -> be1f21410
TS-2553: improve metalink handling of event completion
- if we are not at the end yet and can't read any more content then don't compute the digest
- zero the downstream nbytes as a shortcut to get it to send a TS_EVENT_VCONN_WRITE_COMPLETE event
- avoid failed assert "sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS" in TSVConnWrite() if the response is
304 Not Modified
- add some functional tests
This closes #51.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/be1f2141
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/be1f2141
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/be1f2141
Branch: refs/heads/master
Commit: be1f214104b5f831fcbaf437782455c1c13eb096
Parents: 203fc99
Author: Jack Bates <ja...@nottheoilrig.com>
Authored: Thu Feb 20 13:00:30 2014 -0800
Committer: James Peach <jp...@apache.org>
Committed: Sun Mar 2 09:39:35 2014 -0800
----------------------------------------------------------------------
plugins/experimental/metalink/metalink.cc | 80 +++++++-------
.../experimental/metalink/test/chunkedEncoding | 97 ++++++++++++++++
.../metalink/test/chunkedEncodingDisconnect | 97 ++++++++++++++++
.../experimental/metalink/test/clientDisconnect | 94 ++++++++++++++++
.../experimental/metalink/test/contentLength | 99 +++++++++++++++++
.../metalink/test/contentLengthDisconnect | 92 ++++++++++++++++
.../test/finalChunkedEncodingDisconnect | 110 +++++++++++++++++++
plugins/experimental/metalink/test/http09 | 84 ++++++++++++++
plugins/experimental/metalink/test/longer | 92 ++++++++++++++++
plugins/experimental/metalink/test/notModified | 77 +++++++++++++
.../test/shortChunkedEncodingDisconnect | 96 ++++++++++++++++
.../metalink/test/shortClientDisconnect | 90 +++++++++++++++
.../metalink/test/shortContentLengthDisconnect | 93 ++++++++++++++++
plugins/experimental/metalink/test/zero | 90 +++++++++++++++
14 files changed, 1253 insertions(+), 38 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/metalink.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/metalink.cc b/plugins/experimental/metalink/metalink.cc
index a098e1b..ea253dd 100644
--- a/plugins/experimental/metalink/metalink.cc
+++ b/plugins/experimental/metalink/metalink.cc
@@ -234,7 +234,17 @@ vconn_write_ready(TSCont contp, void * /* edata ATS_UNUSED */)
/* Initialize data here because can't call TSVConnWrite() before
* TS_HTTP_RESPONSE_TRANSFORM_HOOK */
if (!data->output_bufp) {
+
+ /* Avoid failed assert "sdk_sanity_check_iocore_structure(connp) ==
+ * TS_SUCCESS" in TSVConnWrite() if the response is 304 Not Modified */
TSVConn output_connp = TSTransformOutputVConnGet(contp);
+ if (!output_connp) {
+ TSContDestroy(contp);
+
+ TSfree(data);
+
+ return 0;
+ }
data->output_bufp = TSIOBufferCreate();
TSIOBufferReader readerp = TSIOBufferReaderAlloc(data->output_bufp);
@@ -259,12 +269,11 @@ vconn_write_ready(TSCont contp, void * /* edata ATS_UNUSED */)
* nbytes is INT64_MAX.
*
* In that case to get it to send a TS_EVENT_VCONN_WRITE_COMPLETE event,
- * update the downstream nbytes and reenable it. */
+ * update the downstream nbytes and reenable it. Zero the downstream nbytes
+ * is a shortcut. */
int ntodo = TSVIONTodoGet(input_viop);
if (!ntodo) {
-
- int ndone = TSVIONDoneGet(input_viop);
- TSVIONBytesSet(data->output_viop, ndone);
+ TSVIONBytesSet(data->output_viop, 0);
TSVIOReenable(data->output_viop);
@@ -272,25 +281,22 @@ vconn_write_ready(TSCont contp, void * /* edata ATS_UNUSED */)
}
/* Avoid failed assert "sdk_sanity_check_iocore_structure(readerp) ==
- * TS_SUCCESS" in TSIOBufferReaderAvail() if the status code is 302? or the
- * message body is empty? */
+ * TS_SUCCESS" in TSIOBufferReaderAvail() if the client or server disconnects
+ * or the content length is zero.
+ *
+ * Don't update the downstream nbytes and reenable it because if not at the
+ * end yet and can't read any more content then can't compute the digest.
+ *
+ * (There hasn't been a TS_EVENT_VCONN_WRITE_COMPLETE event from downstream
+ * yet so if the response has a "Content-Length: ..." header, it is greater
+ * than the content so far. ntodo is still greater than zero so if the
+ * response is "Transfer-Encoding: chunked", not at the end yet.) */
TSIOBufferReader readerp = TSVIOReaderGet(input_viop);
if (!readerp) {
+ TSContDestroy(contp);
- /* Avoid segfault in TSVIOReenable() if the client disconnected */
- if (TSVConnClosedGet(contp)) {
- TSContDestroy(contp);
-
- TSIOBufferDestroy(data->output_bufp);
- TSfree(data);
-
- } else {
-
- int ndone = TSVIONDoneGet(input_viop);
- TSVIONBytesSet(data->output_viop, ndone);
-
- TSVIOReenable(data->output_viop);
- }
+ TSIOBufferDestroy(data->output_bufp);
+ TSfree(data);
return 0;
}
@@ -566,6 +572,12 @@ digest_handler(TSCont contp, TSEvent event, void *edata)
static int
location_handler(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
{
+ const char *value;
+ int length;
+
+ /* ATS_BASE64_DECODE_DSTLEN() */
+ char digest[33];
+
SendData *data = (SendData *) TSContDataGet(contp);
TSContDestroy(contp);
@@ -576,28 +588,20 @@ location_handler(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
/* No: Check if the "Digest: SHA-256=..." digest already exists in the cache */
case TS_EVENT_CACHE_OPEN_READ_FAILED:
- {
- const char *value;
- int length;
-
- /* ATS_BASE64_DECODE_DSTLEN() */
- char digest[33];
- value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->digest_loc, data->idx, &length);
- if (TSBase64Decode(value + 8, length - 8, (unsigned char *) digest, sizeof(digest), NULL) != TS_SUCCESS
- || TSCacheKeyDigestSet(data->key, digest, 32 /* SHA-256 */ ) != TS_SUCCESS) {
- break;
- }
+ value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->digest_loc, data->idx, &length);
+ if (TSBase64Decode(value + 8, length - 8, (unsigned char *) digest, sizeof(digest), NULL) != TS_SUCCESS
+ || TSCacheKeyDigestSet(data->key, digest, 32 /* SHA-256 */ ) != TS_SUCCESS) {
+ break;
+ }
- contp = TSContCreate(digest_handler, NULL);
- TSContDataSet(contp, data);
+ contp = TSContCreate(digest_handler, NULL);
+ TSContDataSet(contp, data);
- TSCacheRead(contp, data->key);
- TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
+ TSCacheRead(contp, data->key);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
- return 0;
- }
- break;
+ return 0;
default:
TSAssert(!"Unexpected event");
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/chunkedEncoding
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/chunkedEncoding b/plugins/experimental/metalink/test/chunkedEncoding
new file mode 100755
index 0000000..ece4768
--- /dev/null
+++ b/plugins/experimental/metalink/test/chunkedEncoding
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 chunkedEncoding
+# The proxy forwards the final chunk at the end of a chunked response'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - No final chunk yet'
+
+ reactor.stop()
+
+reactor.callLater(2, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.write('chunkedEncoding')
+
+ # If the proxy reads the final chunk before it sends the response
+ # headers, it may send a Content-Length header vs. a chunked response
+ reactor.callLater(1, ctx.finish)
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - Did the proxy crash? (The client connection closed.)'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ print 'not ok 1 - Got a Content-Length header vs. a chunked response'
+
+ # No hope of a final chunk now
+ reactor.stop()
+
+ # Avoid calling undefined handleResponse() at the end of the content (if
+ # the proxy sent a Content-Length header vs. a chunked response).
+ # (Override connectionLost() when the proxy crashes or stop the reactor.)
+ #
+ # The data that was already received will be processed (the end of the
+ # headers), then shutdown events will fire (connections will be closed),
+ # and then finally the reactor will grind to a halt.
+ def handleResponseEnd(ctx):
+ pass
+
+ def handleResponsePart(ctx, data):
+ if data.endswith('0\r\n\r\n'):
+ print 'ok 1 - Got the final chunk'
+
+ reactor.stop()
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/chunkedEncodingDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/chunkedEncodingDisconnect b/plugins/experimental/metalink/test/chunkedEncodingDisconnect
new file mode 100755
index 0000000..3c0210b
--- /dev/null
+++ b/plugins/experimental/metalink/test/chunkedEncodingDisconnect
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 chunkedEncodingDisconnect
+# The proxy closes the client connection and doesn't send a final chunk if the
+# server disconnects without sending one'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - The client was left hanging'
+
+ reactor.stop()
+
+reactor.callLater(2, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.write('chunkedEncodingDisconnect')
+
+ # If the server disconnects before the proxy sends the response
+ # headers, the proxy may send a Content-Length header vs. a chunked
+ # response
+ reactor.callLater(1, ctx.transport.loseConnection)
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'ok 1 - The client connection closed'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ print 'not ok 1 - Got a Content-Length header vs. a chunked response'
+
+ # Who cares what happens now?
+ reactor.stop()
+
+ # Avoid calling undefined handleResponse() at the end of the content (if
+ # the proxy sent a Content-Length header vs. a chunked response).
+ # (Override connectionLost() when the proxy closes the client connection or
+ # stop the reactor.)
+ def handleResponseEnd(ctx):
+ pass
+
+ def handleResponsePart(ctx, data):
+ if data.endswith('0\r\n\r\n'):
+ print 'not ok 1 - Got a final chunk'
+
+ # Who cares what happens now?
+ reactor.stop()
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/clientDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/clientDisconnect b/plugins/experimental/metalink/test/clientDisconnect
new file mode 100755
index 0000000..b8ee3da
--- /dev/null
+++ b/plugins/experimental/metalink/test/clientDisconnect
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 clientDissconnect
+# The proxy doesn't crash if the client disconnects prematurely'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - Why didn\'t the test finish yet?'
+
+ reactor.stop()
+
+reactor.callLater(3, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.write('clientDisconnect')
+
+ # The proxy crashes only after the response is complete
+ def callback():
+ try:
+ ctx.finish()
+
+ except RuntimeError:
+ pass
+
+ # Open another connection
+ class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+ print 'not ok 1 - Did the proxy crash? (Can\'t open another connection to it.)'
+
+ reactor.stop()
+
+ class protocol(protocol.Protocol):
+ def connectionMade(ctx):
+ print 'ok 1 - The proxy didn\'t crash (opened another connection to it)'
+
+ reactor.stop()
+
+ reactor.callLater(1, tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect)
+
+ reactor.callLater(1, callback)
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionMade(ctx):
+ ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ # Disconnect after the proxy sends the response headers
+ reactor.callLater(1, ctx.transport.loseConnection)
+
+ # Avoid calling undefined handleResponse() at the end of the content or
+ # when the connection closes
+ def handleResponseEnd(ctx):
+ pass
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/contentLength
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/contentLength b/plugins/experimental/metalink/test/contentLength
new file mode 100755
index 0000000..c3005ac
--- /dev/null
+++ b/plugins/experimental/metalink/test/contentLength
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 contentLength
+# The proxy forwards the Content-Length header to the client'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - Why didn\'t the test finish yet?'
+
+ reactor.stop()
+
+reactor.callLater(1, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.setHeader('Content-Length', 13)
+ ctx.write('contentLength')
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - Did the proxy crash? (The client connection closed.)'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleEndHeaders(ctx):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - No Content-Length header'
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ if v != '13':
+ print 'not',
+
+ print 'ok 1 - Content-Length header'
+
+ reactor.stop()
+
+ # Avoid calling undefined handleResponse() at the end of the content.
+ # (Override connectionLost() when the proxy crashes or stop the reactor.)
+ #
+ # The data that was already received will be processed (the end of the
+ # headers), then shutdown events will fire (connections will be closed),
+ # and then finally the reactor will grind to a halt.
+ def handleResponseEnd(ctx):
+ pass
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/contentLengthDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/contentLengthDisconnect b/plugins/experimental/metalink/test/contentLengthDisconnect
new file mode 100755
index 0000000..2c98ae8
--- /dev/null
+++ b/plugins/experimental/metalink/test/contentLengthDisconnect
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..2 contentLengthDisconnect
+# The proxy closes the client connection if the server disconnects prematurely'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 2 - The client was left hanging'
+
+ reactor.stop()
+
+reactor.callLater(2, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.setHeader('Content-Length', 24)
+ ctx.write('contentLengthDisconnect')
+
+ # If the server disconnects before the proxy sends the response
+ # headers, the proxy may send the wrong Content-Length header
+ reactor.callLater(1, ctx.transport.loseConnection)
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'ok 2 - The client connection closed'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ if v != '24':
+ print 'not',
+
+ # Who cares what happens now?
+ reactor.stop()
+
+ print 'ok 1 - Content-Length header'
+
+ # Avoid calling undefined handleResponse() at the end of the content (if
+ # the proxy sent the wrong Content-Length header). (Override
+ # connectionLost() when the proxy closes the client connection or stop the
+ # reactor.)
+ def handleResponseEnd(ctx):
+ pass
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/finalChunkedEncodingDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/finalChunkedEncodingDisconnect b/plugins/experimental/metalink/test/finalChunkedEncodingDisconnect
new file mode 100755
index 0000000..9fa4093
--- /dev/null
+++ b/plugins/experimental/metalink/test/finalChunkedEncodingDisconnect
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 finalChunkEncodingDisconnect
+# The proxy forwards the final chunk even if the server disconnects immediately
+# afterward'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - No final chunk yet'
+
+ reactor.stop()
+
+reactor.callLater(2, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.write('finalChunkedEncodingDisconnect')
+
+ # If the proxy reads the final chunk before it sends the response
+ # headers, it may send a Content-Length header vs. a chunked response
+ def callback():
+ try:
+ ctx.finish()
+
+ except RuntimeError:
+ print 'not ok 1 - Did the proxy crash? (The server connection closed.)'
+
+ reactor.stop()
+
+ else:
+ ctx.transport.loseConnection()
+
+ reactor.callLater(1, callback)
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - Did the proxy crash? (The client connection closed.)'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ print 'not ok 1 - Got a Content-Length header vs. a chunked response'
+
+ # No hope of a final chunk now
+ reactor.stop()
+
+ # Avoid calling undefined handleResponse() at the end of the content (if
+ # the proxy sent a Content-Length header vs. a chunked response).
+ # (Override connectionLost() when the proxy crashes or stop the reactor.)
+ #
+ # The data that was already received will be processed (the end of the
+ # headers), then shutdown events will fire (connections will be closed),
+ # and then finally the reactor will grind to a halt.
+ def handleResponseEnd(ctx):
+ pass
+
+ def handleResponsePart(ctx, data):
+ if data.endswith('0\r\n\r\n'):
+ print 'ok 1 - Got the final chunk'
+
+ reactor.stop()
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/http09
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/http09 b/plugins/experimental/metalink/test/http09
new file mode 100755
index 0000000..9103c8d
--- /dev/null
+++ b/plugins/experimental/metalink/test/http09
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 http09
+# The proxy doesn't crash on an HTTP/0.9 response'''
+
+# http://www.w3.org/Protocols/HTTP/AsImplemented
+#
+# The proxy crashes only after the response is complete. It closes the client
+# connection whether it crashes or not because an HTTP/0.9 response is complete
+# only after the server closes its connection, and then the proxy normally does
+# the same thing to the client connection (although it upgrades the response to
+# HTTP/1.1). So the only way to check that the proxy didn't crash is to open
+# another connection.
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - Why didn\'t the test finish yet?'
+
+ reactor.stop()
+
+reactor.callLater(2, callback)
+
+class factory(protocol.Factory):
+ class protocol(protocol.Protocol):
+ def connectionMade(ctx):
+ ctx.transport.write('http09\r\n')
+
+ # The proxy crashes only after the response is complete
+ ctx.transport.loseConnection()
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+
+ # Open another connection
+ class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+ print 'not ok 1 - Did the proxy crash? (Can\'t open another connection to it.)'
+
+ reactor.stop()
+
+ class protocol(protocol.Protocol):
+ def connectionMade(ctx):
+ print 'ok 1 - The proxy didn\'t crash (opened another connection to it)'
+
+ reactor.stop()
+
+ reactor.callLater(1, tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect)
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/longer
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/longer b/plugins/experimental/metalink/test/longer
new file mode 100755
index 0000000..f08feae
--- /dev/null
+++ b/plugins/experimental/metalink/test/longer
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 longer
+# The proxy doesn't choke if the server sends more content than it advertised'''
+
+# Unlike the contentLength test, don't stop the reactor at the end of the
+# headers. Give the proxy time to choke.
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - No Content-Length header'
+
+ reactor.stop()
+
+reactor.callLater(1, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.setHeader('Content-Length', 1)
+ ctx.write('longer')
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - Did the proxy crash? (The client connection closed.)'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ if v != '1':
+ print 'not',
+
+ print 'ok 1 - Content-Length header'
+
+ reactor.stop()
+
+ # Avoid calling undefined handleResponse() at the end of the content.
+ # (Override connectionLost() when the proxy crashes or stop the reactor.)
+ #
+ # The data that was already received will be processed (the end of the
+ # headers), then shutdown events will fire (connections will be closed),
+ # and then finally the reactor will grind to a halt.
+ def handleResponseEnd(ctx):
+ pass
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/notModified
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/notModified b/plugins/experimental/metalink/test/notModified
new file mode 100755
index 0000000..2854189
--- /dev/null
+++ b/plugins/experimental/metalink/test/notModified
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..2 notModified
+# The proxy doesn't crash on a 304 Not Modified response'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'ok 2 - The proxy didn\'t crash (the client connection didn\'t close yet)'
+
+ reactor.stop()
+
+reactor.callLater(1, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.setResponseCode(304)
+ ctx.finish()
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - Did the proxy crash? (The client connection closed.)'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleStatus(ctx, version, status, message):
+ if status != '304':
+ print 'not',
+
+ print 'ok 1 - 304 Not Modified response status'
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/shortChunkedEncodingDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/shortChunkedEncodingDisconnect b/plugins/experimental/metalink/test/shortChunkedEncodingDisconnect
new file mode 100755
index 0000000..526b233
--- /dev/null
+++ b/plugins/experimental/metalink/test/shortChunkedEncodingDisconnect
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 shortChunkedEncodingDisconnect
+# The proxy closes the client connection and doesn't send a final chunk if the
+# server disconnects without sending one, before the proxy sends the response
+# headers'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - The client was left hanging'
+
+ reactor.stop()
+
+reactor.callLater(1, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.write('shortChunkedEncodingDisconnect')
+
+ # Disconnect before the proxy sends the response headers
+ ctx.transport.loseConnection()
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'ok 1 - The client connection closed'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ print 'not ok 1 - Got a Content-Length header vs. a chunked response'
+
+ # Who cares what happens now?
+ reactor.stop()
+
+ # Avoid calling undefined handleResponse() at the end of the content (if
+ # the proxy sent a Content-Length header vs. a chunked response).
+ # (Override connectionLost() when the proxy closes the client connection or
+ # stop the reactor.)
+ def handleResponseEnd(ctx):
+ pass
+
+ def handleResponsePart(ctx, data):
+ if data.endswith('0\r\n\r\n'):
+ print 'not ok 1 - Got a final chunk'
+
+ # Who cares what happens now?
+ reactor.stop()
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/shortClientDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/shortClientDisconnect b/plugins/experimental/metalink/test/shortClientDisconnect
new file mode 100755
index 0000000..4ab6c83
--- /dev/null
+++ b/plugins/experimental/metalink/test/shortClientDisconnect
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 shortClientDisconnect
+# The proxy doesn't crash if the client disconnects before the proxy sends the
+# response headers'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - Why didn\'t the test finish yet?'
+
+ reactor.stop()
+
+reactor.callLater(3, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.write('shortClientDisconnect0')
+
+ def callback():
+ ctx.write('shortClientDisconnect1')
+
+ # Open another connection
+ class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+ print 'not ok 1 - Did the proxy crash? (Can\'t open another connection to it.)'
+
+ reactor.stop()
+
+ class protocol(protocol.Protocol):
+ def connectionMade(ctx):
+ print 'ok 1 - The proxy didn\'t crash (opened another connection to it)'
+
+ reactor.stop()
+
+ reactor.callLater(1, tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect)
+
+ reactor.callLater(1, callback)
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionMade(ctx):
+ ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ # Disconnect before the proxy sends the response headers
+ ctx.transport.loseConnection()
+
+ # Avoid calling undefined handleResponse() at the end of the content or
+ # when the connection closes
+ def handleResponseEnd(ctx):
+ pass
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/shortContentLengthDisconnect
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/shortContentLengthDisconnect b/plugins/experimental/metalink/test/shortContentLengthDisconnect
new file mode 100755
index 0000000..2e1c813
--- /dev/null
+++ b/plugins/experimental/metalink/test/shortContentLengthDisconnect
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..2 shortContentLengthDisconnect
+# The proxy sends the right Content-Length header and closes the client
+# connection if the server disconnects before the proxy sends the response
+# headers'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 2 - The client was left hanging'
+
+ reactor.stop()
+
+reactor.callLater(1, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.setHeader('Content-Length', 29)
+ ctx.write('shortContentLengthDisconnect')
+
+ # Disconnect before the proxy sends the response headers
+ ctx.transport.loseConnection()
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'ok 2 - The client connection closed'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ if v != '29':
+ print 'not',
+
+ # Who cares what happens now?
+ reactor.stop()
+
+ print 'ok 1 - Content-Length header'
+
+ # Avoid calling undefined handleResponse() at the end of the content (if
+ # the proxy sent the wrong Content-Length header). (Override
+ # connectionLost() when the proxy closes the client connection or stop the
+ # reactor.)
+ def handleResponseEnd(ctx):
+ pass
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/be1f2141/plugins/experimental/metalink/test/zero
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/test/zero b/plugins/experimental/metalink/test/zero
new file mode 100755
index 0000000..6d42d56
--- /dev/null
+++ b/plugins/experimental/metalink/test/zero
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+print '''1..1 zero
+# The proxy doesn't crash if the Content-Length is zero'''
+
+from twisted.internet import error, protocol, reactor, tcp
+from twisted.web import http
+
+def callback():
+ print 'not ok 1 - Why didn\'t the test finish yet?'
+
+ reactor.stop()
+
+reactor.callLater(1, callback)
+
+class factory(http.HTTPFactory):
+ class protocol(http.HTTPChannel):
+ class requestFactory(http.Request):
+ def requestReceived(ctx, method, target, version):
+
+ ctx.client = None
+ ctx.clientproto = version
+
+ ctx.setHeader('Content-Length', 0)
+ ctx.finish()
+
+server = tcp.Port(0, factory())
+server.startListening()
+
+print '# Listening on {0}:{1}'.format(*server.socket.getsockname())
+
+class factory(protocol.ClientFactory):
+ def clientConnectionFailed(ctx, connector, reason):
+
+ print 'Bail out!'
+ reason.printTraceback()
+
+ reactor.stop()
+
+ class protocol(http.HTTPClient):
+ def connectionLost(ctx, reason):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - Did the proxy crash? (The client connection closed.)'
+
+ connectionMade = lambda ctx: ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*server.socket.getsockname()))
+
+ def handleEndHeaders(ctx):
+ try:
+ reactor.stop()
+
+ except error.ReactorNotRunning:
+ pass
+
+ else:
+ print 'not ok 1 - No Content-Length header'
+
+ def handleHeader(ctx, k, v):
+ if k.lower() == 'content-length':
+ if v != '0':
+ print 'not',
+
+ print 'ok 1 - Content-Length header'
+
+ reactor.stop()
+
+tcp.Connector('localhost', 8080, factory(), 30, None, reactor).connect()
+
+reactor.run()