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 <[email protected]> Authored: Thu Feb 20 13:00:30 2014 -0800 Committer: James Peach <[email protected]> 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()
