Buffer rows to reduce number of chunks

This patch reduces the number of chunks in an HTTP chunked response body
by coalescing multiple rows into a single transmission. The default
value is chosen to fill a standard Ethernet frame and can be configured
by setting

     [httpd]
     chunked_response_buffer = 1490

Note that the same setting is several other streaming responses (e.g.
_changes, _all_docs, _views).

COUCHDB-2724
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index 54510fb..566ff09 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -25,7 +25,10 @@
 -record(vacc, {
     resp,
     prepend,
-    kvs
+    kvs,
+    buffer = [],
+    bufsize = 0,
+    threshold = 1490
 }).
 
 handle_req(#httpd{} = Req, Db0) ->
@@ -218,13 +221,14 @@
     chttpd:start_delayed_json_response(Req, 200, [], "{\"docs\":[").
 
 
-end_find_resp(Acc) ->
-    #vacc{resp=Resp0, kvs=KVs} = Acc,
+end_find_resp(Acc0) ->
+    #vacc{resp=Resp00, buffer=Buf, kvs=KVs, threshold=Max} = Acc0,
+    {ok, Resp0} = chttpd:close_delayed_json_object(Resp00, Buf, "\r\n]}", Max),
     FinalAcc = lists:foldl(fun({K, V}, Acc) ->
         JK = ?JSON_ENCODE(K),
         JV = ?JSON_ENCODE(V),
         [JV, ": ", JK, ",\r\n" | Acc]
-    end, ["\r\n]"], KVs),
+    end, [], KVs),
     Chunk = lists:reverse(FinalAcc, ["}\r\n"]),
     {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, Chunk),
     chttpd:end_delayed_json_response(Resp1).
@@ -234,20 +238,34 @@
     Acc0 = #vacc{
         resp = Resp,
         prepend = "\r\n",
-        kvs = []
+        kvs = [],
+        threshold = chttpd:chunked_response_buffer_size()
     },
     mango_crud:find(Db, Sel, fun handle_doc/2, Acc0, Opts).
 
 
 handle_doc({add_key, Key, Value}, Acc0) ->
-    #vacc{resp=Resp, prepend=Prepend, kvs=KVs} = Acc0,
+    #vacc{kvs=KVs} = Acc0,
     NewKVs = lists:keystore(Key, 1, KVs, {Key, Value}),
-    {ok, {Resp, Prepend, NewKVs}};
+    {ok, Acc0#vacc{kvs = NewKVs}};
 handle_doc({row, Doc}, Acc0) ->
-    #vacc{resp=Resp0, prepend=Prepend, kvs=KVs} = Acc0,
+    #vacc{prepend=Prepend} = Acc0,
     Chunk = [Prepend, ?JSON_ENCODE(Doc)],
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, Chunk),
-    {ok, {Resp1, ",\r\n", KVs}}.
+    maybe_flush_response(Acc0, Chunk, iolist_size(Chunk)).
+
+maybe_flush_response(#vacc{bufsize=Size, threshold=Max} = Acc, Data, Len)
+        when Size > 0 andalso (Size + Len) > Max ->
+    #vacc{buffer = Buffer, resp = Resp} = Acc,
+    {ok, R1} = chttpd:send_delayed_chunk(Resp, Buffer),
+    {ok, Acc#vacc{prepend = ",\r\n", buffer = Data, bufsize = Len, resp = R1}};
+maybe_flush_response(Acc0, Data, Len) ->
+    #vacc{buffer = Buf, bufsize = Size} = Acc0,
+    Acc = Acc0#vacc{
+        prepend = ",\r\n",
+        buffer = [Buf | Data],
+        bufsize = Size + Len
+    },
+    {ok, Acc}.
 
 
 parse_index_param("limit", Value) ->