|  | #!/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) | 
|  |  | 
|  | 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() |