Created
January 27, 2012 11:04
-
-
Save loudej/1688308 to your computer and use it in GitHub Desktop.
Comparison of chunked middleware
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using Gate.Owin; | |
| using System.Text; | |
| namespace Gate.Middleware | |
| { | |
| public static class Chunked | |
| { | |
| public static IAppBuilder UseChunked(this IAppBuilder builder) | |
| { | |
| return builder.Use<AppDelegate>(Middleware); | |
| } | |
| static readonly ArraySegment<byte> EndOfChunk = new ArraySegment<byte>(Encoding.ASCII.GetBytes("\r\n")); | |
| static readonly ArraySegment<byte> FinalChunk = new ArraySegment<byte>(Encoding.ASCII.GetBytes("0\r\n\r\n")); | |
| static readonly byte[] Hex = Encoding.ASCII.GetBytes("0123456789abcdef\r\n"); | |
| public static AppDelegate Middleware(AppDelegate app) | |
| { | |
| return | |
| (env, result, fault) => | |
| app( | |
| env, | |
| (status, headers, body) => | |
| { | |
| if (IsStatusWithNoNoEntityBody(status) || | |
| headers.ContainsKey("Content-Length") || | |
| headers.ContainsKey("Transfer-Encoding")) | |
| { | |
| result(status, headers, body); | |
| } | |
| else | |
| { | |
| headers["Transfer-Encoding"] = new[] { "chunked" }; | |
| result( | |
| status, | |
| headers, | |
| (write, flush, end, cancel) => | |
| body( | |
| data => | |
| { | |
| if (data.Count == 0) | |
| { | |
| return write(data); | |
| } | |
| write(ChunkPrefix((uint) data.Count)); | |
| write(data); | |
| return write(EndOfChunk); | |
| }, | |
| flush, | |
| ex => | |
| { | |
| write(FinalChunk); | |
| end(ex); | |
| }, | |
| cancel)); | |
| } | |
| }, | |
| fault); | |
| } | |
| public static ArraySegment<byte> ChunkPrefix(uint dataCount) | |
| { | |
| var prefixBytes = new[] | |
| { | |
| Hex[(dataCount >> 28) & 0xf], | |
| Hex[(dataCount >> 24) & 0xf], | |
| Hex[(dataCount >> 20) & 0xf], | |
| Hex[(dataCount >> 16) & 0xf], | |
| Hex[(dataCount >> 12) & 0xf], | |
| Hex[(dataCount >> 8) & 0xf], | |
| Hex[(dataCount >> 4) & 0xf], | |
| Hex[(dataCount >> 0) & 0xf], | |
| Hex[16], | |
| Hex[17], | |
| }; | |
| var shift = (dataCount & 0xffff0000) == 0 ? 16 : 0; | |
| shift += ((dataCount << shift) & 0xff000000) == 0 ? 8 : 0; | |
| shift += ((dataCount << shift) & 0xf0000000) == 0 ? 4 : 0; | |
| return new ArraySegment<byte>(prefixBytes, shift / 4, 10 - shift / 4); | |
| } | |
| private static bool IsStatusWithNoEntityBody(string status) | |
| { | |
| return status.StartsWith("1") || | |
| status.StartsWith("204") || | |
| status.StartsWith("205") || | |
| status.StartsWith("304"); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'rack/utils' | |
| module Rack | |
| # Middleware that applies chunked transfer encoding to response bodies | |
| # when the response does not include a Content-Length header. | |
| class Chunked | |
| include Rack::Utils | |
| # A body wrapper that emits chunked responses | |
| class Body | |
| TERM = "\r\n" | |
| TAIL = "0#{TERM}#{TERM}" | |
| include Rack::Utils | |
| def initialize(body) | |
| @body = body | |
| end | |
| def each | |
| term = TERM | |
| @body.each do |chunk| | |
| size = bytesize(chunk) | |
| next if size == 0 | |
| chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding) | |
| yield [size.to_s(16), term, chunk, term].join | |
| end | |
| yield TAIL | |
| end | |
| def close | |
| @body.close if @body.respond_to?(:close) | |
| end | |
| end | |
| def initialize(app) | |
| @app = app | |
| end | |
| def call(env) | |
| status, headers, body = @app.call(env) | |
| headers = HeaderHash.new(headers) | |
| if env['HTTP_VERSION'] == 'HTTP/1.0' || | |
| STATUS_WITH_NO_ENTITY_BODY.include?(status) || | |
| headers['Content-Length'] || | |
| headers['Transfer-Encoding'] | |
| [status, headers, body] | |
| else | |
| headers.delete('Content-Length') | |
| headers['Transfer-Encoding'] = 'chunked' | |
| [status, headers, Body.new(body)] | |
| end | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment