Skip to content

Instantly share code, notes, and snippets.

@loudej
Created January 27, 2012 11:04
Show Gist options
  • Select an option

  • Save loudej/1688308 to your computer and use it in GitHub Desktop.

Select an option

Save loudej/1688308 to your computer and use it in GitHub Desktop.
Comparison of chunked middleware
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");
}
}
}
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