| #!/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. |
| |
| from twisted.internet import error, protocol, reactor, tcp |
| from twisted.web import http |
| |
| print '''1..1 finalChunkEncodingDisconnect |
| # The proxy forwards the final chunk even if the origin disconnects |
| # immediately afterward''' |
| |
| |
| 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 origin connection closed.)' |
| |
| reactor.stop() |
| |
| else: |
| ctx.transport.loseConnection() |
| |
| reactor.callLater(1, callback) |
| |
| |
| origin = tcp.Port(0, factory()) |
| origin.startListening() |
| |
| print '# Listening on {0}:{1}'.format(*origin.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.)' |
| |
| def connectionMade(ctx): |
| ctx.transport.write('GET {0}:{1} HTTP/1.1\r\n\r\n'.format(*origin.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 |
| # message (if the proxy sent a Content-Length header vs. a chunked |
| # response). (Override connectionLost() when the proxy crashes or |
| # we stop the reactor.) |
| # |
| # Data that was already received will get processed (the end of |
| # the headers), then shutdown events will fire (connections will |
| # get 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() |