2 %% Copyright (C) 2006 Damien Katz
4 %% This program is free software; you can redistribute it and/or
5 %% modify it under the terms of the GNU General Public License
6 %% as published by the Free Software Foundation; either version 2
7 %% of the License, or (at your option) any later version.
9 %% This program is distributed in the hope that it will be useful,
10 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
11 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 %% GNU General Public License for more details.
14 %% You should have received a copy of the GNU General Public License
15 %% along with this program; if not, write to the Free Software
16 %% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 -include("couch_db.hrl").
22 -export([do
/1, load
/2]).
24 -include_lib("inets/src/httpd.hrl").
32 -record(doc_query_args
,
40 %% do. This is the main entry point into CouchDb from the HTTP server
43 #mod
{request_uri
=Uri
,request_line
=Request
, parsed_header
=Header
,entity_body
=Body
} = ModData
,
45 "/_utils/" ++ RestURI
->
46 % if the URI is the utils directory, then this
47 % tells mod_get (a std HTTP module) where to serve the file from
48 DocumentRoot
= httpd_util:lookup(ModData#mod
.config_db
, document_root
, ""),
49 {Path
, AfterPath
} = httpd_util:split_path(DocumentRoot
++ "/" ++ RestURI
),
50 case filelib:is_file(Path
) of
52 {proceed
, [{real_name
, {Path
, AfterPath
}} | ModData#mod
.data
]};
54 case filelib:is_dir(Path
) of
56 % this ends up causing a "Internal Server Error", need to fix.
57 {proceed
, [{response
,{403,"Forbidden"}}]};
59 {proceed
, [{response
,{404,"Not found"}}]}
63 DocumentRoot
= httpd_util:lookup(ModData#mod
.config_db
, document_root
, ""),
64 RealName
= DocumentRoot
++ "/" ++ Uri
,
65 {Path
, AfterPath
} = httpd_util:split_path(RealName
),
66 {proceed
, [{real_name
, {Path
, AfterPath
}} | ModData#mod
.data
]};
68 couch_log:info("HTTP Request: ~s~nHeader:~p", [Request
, Header
]),
69 couch_log:debug("Body:~P", [Body
, 100]),
70 Parts
= parse_uri(Uri
),
73 #uri_parts
{db
="_all_dbs"} ->
74 send_all_dbs(ModData
, Parts
);
77 show_couch_welcome(ModData
);
79 case (catch handle_request(ModData
, Parts
)) of
82 {error_response
, Error
} ->
83 send_error(ModData
, Error
);
85 send_error(ModData
, Error
)
88 couch_log:info("HTTP Response Code:~p~n", [ResponseCode
]),
89 {proceed
, [{response
, {already_sent
, ResponseCode
, 0}} | ModData#mod
.data
]}
92 parse_uri(RequestUri
) ->
93 % seperate out the path and query portions and
94 % strip out leading slash and question mark.
95 case httpd_util:split_path(RequestUri
) of
96 {[$
/|UriPath
], [$?
|QueryStr
]} -> ok
;
97 {[$
/|UriPath
], QueryStr
} -> ok
% there is no query
102 #uri_parts
{querystr
=QueryStr
};
104 case lists:last(UriPath
) of
105 $
/ -> % ends with a slash means database only
106 DbId
= lists:sublist(UriPath
, length(UriPath
) - 1), % strip off trailing slash
107 #uri_parts
{db
=DbId
,querystr
=QueryStr
};
109 % lets try to parse out the UriPath
110 {ok
, SplitUrl
} = regexp:split(UriPath
, "/"),
112 [DbId
] -> % single element list.
113 #uri_parts
{db
= DbId
, querystr
= QueryStr
};
115 % multi element list. treat last element as the docid
116 [DocIdDocResource
| RevDbPath
] = lists:reverse(SplitUrl
),
117 DbId
= couch_util:implode(lists:reverse(RevDbPath
), "/"),
118 % Everything before first ":" is the docId,
119 % and everything after is the resource id.
120 {ok
, [DocId
| DocResourceList
]} = regexp:split(DocIdDocResource
, ":"),
121 ResourceId
= couch_util:implode(DocResourceList
, ":"),
122 #uri_parts
{db
=DbId
, doc
=DocId
, resource
=ResourceId
, querystr
=QueryStr
}
127 % return json doc header values list
128 resp_json_header(ModData
) ->
129 resp_header(ModData
, "text/plain; charset=utf-8").
131 % return doc header values list
132 resp_header(#mod
{http_version
=Version
}, ContentType
) ->
133 [{"cache-control", "no-cache"},
134 {"pragma", "no-cache"},
135 {"expires", httpd_util:rfc1123_date()},
136 {"damien", "awesome"},
137 {"content-type", ContentType
},
138 case Version
== "HTTP/1.1" of
140 {"transfer-encoding", "chunked"};
142 {"connection", "close"}
146 url_decode([$
%, Hi, Lo | Tail]) ->
147 Hex
= erlang:list_to_integer([Hi
, Lo
], 16),
148 xmerl_ucs:to_utf8([Hex
]) ++ url_decode(Tail
);
155 send_header(ModData
, RespCode
, Headers
) ->
156 couch_log:debug("HTTP response headers (code ~w):~p", [RespCode
, Headers
]),
157 httpd_response:send_header(ModData
, RespCode
, Headers
).
159 send_chunk(ModData
, Data
) ->
160 httpd_response:send_chunk(ModData
, Data
, false
).
162 send_final_chunk(ModData
) ->
163 httpd_response:send_final_chunk(ModData
, false
).
165 show_couch_welcome(ModData
) ->
166 send_header(ModData
, 200, resp_json_header(ModData
)),
167 send_chunk(ModData
, "{\"couchdb\": \"Welcome\", "),
168 send_chunk(ModData
, "\"version\": \"" ++ couch_server:get_version()),
169 send_chunk(ModData
, "\"}"),
170 send_final_chunk(ModData
),
173 handle_request(#mod
{method
="POST"}=ModData
, #uri_parts
{db
=DbName
, doc
="_missing_revs"}=UriParts
) ->
174 handle_missing_revs_request(ModData
, UriParts
, open_db(DbName
));
175 handle_request(#mod
{method
="POST"}=ModData
, #uri_parts
{db
=DbName
, doc
=""}=UriParts
) ->
176 % POST with no doc specified, therefore document addition
177 handle_db_request(ModData#mod
{method
="PUT"}, UriParts
, open_db(DbName
));
178 handle_request(#mod
{method
="PUT"}=ModData
, #uri_parts
{db
=DbName
, doc
=""}=_UriParts
) ->
179 % put with no doc specified, therefore database creation
180 case couch_server:create(DbName
, []) of
182 send_ok(ModData
, 201);
183 {error
, database_already_exists
} ->
184 Msg
= io_lib:format("Database ~p already exists.", [DbName
]),
185 throw({error_response
, {database_already_exists
, Msg
}});
187 Msg
= io_lib:format("Error creating database ~p: ~p", [DbName
, Error
]),
188 throw({error_response
, {unknown_error
, Msg
}})
190 handle_request(#mod
{method
="DELETE"}=ModData
, #uri_parts
{db
=DbName
, doc
=""}=_UriParts
) ->
191 % delete with no doc specified, therefore database delete
192 case couch_server:delete(DbName
) of
194 send_ok(ModData
, 202);
196 throw({error_response
, Error
})
198 handle_request(ModData
, #uri_parts
{db
=DbName
}=UriParts
) ->
199 handle_db_request(ModData
, UriParts
, open_db(DbName
)).
201 % returns db, otherwise throws exception. Note, no {ok,_}.
203 case couch_server:open(DbName
) of
207 throw({error_response
, Error
})
210 handle_missing_revs_request(#mod
{entity_body
=RawJson
}=ModData
, _UriParts
, Db
) ->
211 {obj
, DocIdRevs
} = cjson:decode(RawJson
),
212 {ok
, Results
} = couch_db:get_missing_revs(Db
, tuple_to_list(DocIdRevs
)),
213 send_json(ModData
, 200, {obj
, Results
}).
217 handle_db_request(#mod
{method
="POST", entity_body
=RawBody
, parsed_header
=Headers
}=ModData
, #uri_parts
{doc
="_temp_view"}=Uri
, Db
) ->
218 Type
= proplists:get_value(Headers
, "content-type", "text/javascript"),
219 handle_db_request(ModData#mod
{method
="GET"}, Uri#uri_parts
{resource
=Type
++ "|" ++ RawBody
}, Db
);
220 handle_db_request(#mod
{method
="DELETE"}=ModData
, #uri_parts
{doc
=DocId
,querystr
=QueryStr
}, Db
) ->
221 % handle doc deletion
222 #doc_query_args
{revs
=Revs
} = doc_parse_query(QueryStr
),
224 Revs2
= case Revs
of [] -> all
; _
-> Revs
end,
226 case couch_db:delete_doc(Db
, DocId
, Revs2
) of
227 {ok
, [{ok
, NewRev
}]} ->
228 send_ok(ModData
, 202, [{"_rev",NewRev
}]); % single doc success
230 throw({error_response
, conflict
}); % single doc conflict failure
232 % multiple results, send results as list
234 lists:map(fun(Result
) ->
242 send_ok(ModData
, 202, [{multi
, list_to_tuple(Results2
)}]);
244 throw({error_response
, Error
})
246 handle_db_request(#mod
{method
="PUT",entity_body
=RawBody
}=ModData
, #uri_parts
{doc
=UrlDocId
,querystr
=QueryStr
}, Db
) ->
247 % handle doc creation
248 % just assume the content
249 % is application/json utf-8
251 #doc_query_args
{options
=SaveOptions
} = doc_parse_query(QueryStr
),
252 couch_log:debug("Body:~100s", [RawBody
]),
254 Json
= cjson:decode(RawBody
),
256 {obj
, [{"_bulk_docs", SubJsonDocs
}]} ->
257 % convert all the doc elements to native docs
260 fun(SubDoc
, DocAcc
) ->
261 NewDoc
= couch_doc:from_json_obj(SubDoc
),
262 NewDoc2
= NewDoc#doc
{uuid
=
263 case NewDoc#doc
.uuid
of
264 [] -> couch_util:new_uuid();
267 [{[NewDoc2
], [new_edits
| SaveOptions
]} | DocAcc
]
269 [], tuple_to_list(SubJsonDocs
)),
271 {ok
, Results
} = couch_db:save_docs(Db
, DocsAndOptions
),
273 lists:zipwith(fun([Result
], {[Doc
],_
}) ->
276 {obj
, [{"ok",true
}, {"_id", Doc#doc
.uuid
}, {"_rev", RevId
}]};
278 {JsonError
, _HttpCode
} = error_to_json(Error
),
281 end, Results
, DocsAndOptions
),
283 send_ok(ModData
, 201, [{results
, list_to_tuple(DocResults
)}], []);
285 Doc
= couch_doc:from_json_obj(Json
),
286 %if the docid is specified in the URL, use that (we override the
287 % json document's _id member).
289 case {UrlDocId
, Doc#doc
.uuid
} of
290 {"", ""} -> % both blank
291 Doc#doc
{uuid
= couch_util:new_uuid()};
292 {"", _
} -> % url is blank, doc's id is not, use that
294 {_
, _
} -> % both are available, prefer the Url one
295 Doc#doc
{uuid
= UrlDocId
}
297 {obj
, ObjProps
} = Json
,
299 case proplists:get_value("_rev", ObjProps
) of
303 % there is a "based on" revisionid
304 % load the document from the database and see if its the same
305 % revision. If so then snag the revision history and use it.
306 case couch_db:open_doc(Db
, Doc2#doc
.uuid
, [allow_stub
]) of
308 case PrevRev
== lists:nth(1, DiskDoc#doc
.revisions
) of
310 Doc2#doc
{revisions
= DiskDoc#doc
.revisions
};
312 % oops, the revision in db isn't what this doc is based
314 throw({error_response
, {update_error
, conflict
}})
316 {not_found
, missing
} ->
317 throw({error_response
, {update_error
, missing_rev
}})
320 case couch_db:save_doc(Db
, Doc3
, SaveOptions
) of
322 Props
= [{"_id", Doc3#doc
.uuid
}, {"_rev", NewRevId
}],
323 HeaderInfo
= [{"x-couch-id", Doc3#doc
.uuid
}, {"x-couch-rev", integer_to_list(NewRevId
)}],
324 send_ok(ModData
, 201, Props
, HeaderInfo
);
326 throw({error_response
, {update_error
, Error
}})
329 handle_db_request(#mod
{method
="GET"}=ModData
,
330 #uri_parts
{doc
=DocId
, resource
=Resource
}=UriParts
, Db
) ->
331 % handle doc retrieval
332 case {DocId
, Resource
} of
334 send_database_info(ModData
, UriParts
, Db
);
336 send_all_docs(ModData
, UriParts
, Db
);
337 {"_all_docs_by_update_seq", ""} ->
338 send_all_docs_by_seq(ModData
, UriParts
, Db
);
340 send_doc(ModData
, UriParts
, Db
);
341 {?DESIGN_DOC_PREFIX
++ _
, _ViewId
} ->
342 send_view(ModData
, UriParts
, Db
);
343 {"_temp_view", _ViewId
} ->
344 send_view(ModData
, UriParts
, Db
);
346 throw({error_response
, {error
, not_a_design_doc
}})
349 doc_parse_query(QueryStr
) ->
350 QueryList
= httpd:parse_query(QueryStr
),
351 lists:foldl(fun({Key
,Value
}, Args
) ->
354 Args#doc_query_args
{full
=true
};
355 {"latest", "true"} ->
356 Options
= [latest
| Args#doc_query_args
.options
],
357 Args#doc_query_args
{options
=Options
};
359 Revs
= [list_to_integer(RevIdStr
) | Args#doc_query_args
.revs
],
360 Args#doc_query_args
{revs
=Revs
};
361 {"all_revs", "true"} ->
362 Args#doc_query_args
{revs
=all
};
363 {"new_edits", "true"} ->
364 Options
= [new_edits
| Args#doc_query_args
.options
],
365 Args#doc_query_args
{options
=Options
};
366 {"attachment", BinName
} ->
367 Args#doc_query_args
{bin
=BinName
};
368 {"delay_commit", "true"} ->
369 Options
= [delay_commit
| Args#doc_query_args
.options
],
370 Args#doc_query_args
{options
=Options
};
371 _Else
-> % unknown key value pair, ignore.
375 #doc_query_args
{}, QueryList
).
377 send_database_info(ModData
, #uri_parts
{db
=DbName
}, Db
) ->
378 {ok
, InfoList
} = couch_db:get_info(Db
),
379 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
380 DocCount
= proplists:get_value(doc_count
, InfoList
),
381 LastUpdateSequence
= proplists:get_value(last_update_seq
, InfoList
),
382 ok
= send_chunk(ModData
, "{\"db_name\": \"" ++ DbName
++ "\", \"doc_count\":" ++ integer_to_list(DocCount
) ++ ", \"update_seq\":" ++ integer_to_list(LastUpdateSequence
)++"}"),
383 ok
= send_final_chunk(ModData
),
386 send_doc(ModData
, #uri_parts
{doc
=DocId
,querystr
=QueryStr
}, Db
) ->
387 #doc_query_args
{full
=Full
, options
=OpenOptions
, revs
=Revs
, bin
=BinName
} = doc_parse_query(QueryStr
),
392 case couch_db:open_doc(Db
, DocId
, OpenOptions
) of
394 send_json(ModData
, 200, couch_doc:to_json_obj(Doc
, Full
));
396 throw({error_response
, Error
})
399 {ok
, Results
} = couch_db:open_doc_revs(Db
, DocId
, Revs
, OpenOptions
),
400 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
401 ok
= send_chunk(ModData
, "{\"docs\":["),
402 % We loop through the docs. The first time through the seperator
403 % is whitespace, then a comma on subsequent iterations.
405 fun(Result
, AccSeperator
) ->
408 JsonDoc
= couch_doc:to_json_obj(Doc
, Full
),
409 ok
= send_chunk(ModData
, AccSeperator
++ lists:flatten(cjson:encode(JsonDoc
)));
410 {{not_found
, missing
}, RevId
} ->
411 Json
= {obj
, [{id
, DocId
}, {rev
, RevId
}, {missing
, true
}]},
412 ok
= send_chunk(ModData
, AccSeperator
++ lists:flatten(cjson:encode(Json
)))
414 ",\n" % AccSeperator now has a comma
417 ok
= send_chunk(ModData
, "\n]}"),
418 ok
= send_final_chunk(ModData
),
422 case couch_db:open_doc(Db
, DocId
, OpenOptions
) of
423 {ok
, #doc
{attachments
=Bins
}} ->
424 case proplists:lookup(BinName
,Bins
) of
426 throw({error_response
, {not_found
, missing
}});
427 {_
, {"base64", Bin
}} ->
428 Suffix
= httpd_util:suffix(BinName
),
429 MimeType
= httpd_util:lookup_mime_default(ModData#mod
.config_db
,
430 Suffix
,"text/plain"),
431 ok
= send_header(ModData
, 200, resp_header(ModData
, MimeType
) ++ [{"content-length", integer_to_list(couch_doc:bin_size(Bin
))}]),
432 couch_doc:bin_foldl(Bin
,
433 fun(BinSegment
, []) ->
434 ok
= send_chunk(ModData
, BinSegment
),
438 ok
= send_final_chunk(ModData
),
441 % for now, anything else, send as json
442 Json
= binary_to_term(couch_doc:bin_to_binary(Bin
)),
443 send_json(ModData
, 200, Json
)
446 throw({error_response
, Error
})
450 send_json(ModData
, Code
, JsonData
) ->
451 send_json(ModData
, Code
, JsonData
, []).
453 send_json(ModData
, Code
, JsonData
, AdditionalHeaders
) ->
454 ok
= send_header(ModData
, Code
, resp_json_header(ModData
) ++ AdditionalHeaders
),
455 ok
= send_chunk(ModData
, lists:flatten(cjson:encode(JsonData
))),
456 ok
= send_final_chunk(ModData
),
460 send_ok(ModData
, Code
) ->
461 send_ok(ModData
, Code
, []).
463 send_ok(ModData
, Code
, AdditionalProps
) ->
464 send_ok(ModData
, Code
, AdditionalProps
, []).
466 send_ok(ModData
, Code
, AdditionalProps
, AdditionalHeaders
) ->
467 send_json(ModData
, Code
, {obj
, [{ok
, true
}|AdditionalProps
]}, AdditionalHeaders
).
473 count
= 10000000000, % a huge huge default number. Picked so we don't have
474 % to do different logic for when there is no count limit
482 reverse_key_default(nil
) -> <<>>;
483 reverse_key_default(<<>>) -> nil
;
484 reverse_key_default(Key
) -> Key
.
486 view_parse_query(QueryStr
) ->
487 QueryList
= httpd:parse_query(QueryStr
),
488 lists:foldl(fun({Key
,Value
}, Args
) ->
491 JsonKey
= cjson:decode(url_decode(Value
)),
492 Args#query_args
{start_key
=JsonKey
,end_key
=JsonKey
};
493 {"startkey_docid", DocId
} ->
494 Args#query_args
{start_docid
=DocId
};
495 {"startkey", Value
} ->
496 Args#query_args
{start_key
=cjson:decode(url_decode(Value
))};
498 Args#query_args
{end_key
=cjson:decode(url_decode(Value
))};
500 case (catch list_to_integer(Value
)) of
501 Count
when is_integer(Count
) ->
502 Args#query_args
{count
=Count
};
504 Msg
= io_lib:format("Bad URL query value, number expected: count=~s", [Value
]),
505 throw({error_response
, {query_parse_error
, Msg
}})
507 {"update", "false"} ->
508 Args#query_args
{update
=false
};
509 {"reverse", "true"} ->
510 case Args#query_args
.direction
of
514 start_key
= reverse_key_default(Args#query_args
.start_key
),
515 start_docid
= reverse_key_default(Args#query_args
.start_docid
),
516 end_key
= reverse_key_default(Args#query_args
.end_key
),
517 end_docid
= reverse_key_default(Args#query_args
.end_docid
)};
519 Args
%already reversed
522 case (catch list_to_integer(Value
)) of
523 Count
when is_integer(Count
) ->
524 Args#query_args
{skip
=Count
};
526 Msg
= io_lib:format("Bad URL query value, number expected: skip=~s", [Value
]),
527 throw({error_response
, {query_parse_error
, Msg
}})
529 _Else
-> % unknown key value pair, ignore.
533 #query_args
{}, QueryList
).
536 make_view_fold_fun(ModData
, #uri_parts
{doc
=DocId
, resource
=ViewId
}, QueryArgs
) ->
543 fun({Uuid
, Rev
}, Key
, Value
, Offset
, TotalViewCount
, {AccCount
, AccSkip
, HeaderSent
}) ->
547 couch_view_group:less_json({EndKey
,EndDocId
}, {Key
, Uuid
});
549 couch_view_group:less_json({Key
, Uuid
}, {EndKey
,EndDocId
})
553 % The stop key has been passed, stop looping.
554 {stop
, {AccCount
, AccSkip
, HeaderSent
}};
560 {ok
, {AccCount
, AccSkip
- 1, HeaderSent
}};
564 JsonObj
= {obj
, [{"_id", Uuid
}, {"_rev", Rev
} | key_value_to_props(Key
,Value
)]},
565 ok
= send_chunk(ModData
, ",\n" ++ lists:flatten(cjson:encode(JsonObj
)));
567 % We do this the first time through, NOT before folding,
568 % because we want to make sure the view exists efficiently.
569 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
570 FullViewId
= cjson:encode(lists:flatten(io_lib:format("~s:~s", [DocId
, ViewId
]))),
571 JsonBegin
= io_lib:format("{\"view\":~s,\"total_rows\":~w, \"offset\":~w, \"rows\":[\n",
572 [FullViewId
, TotalViewCount
, Offset
]),
573 JsonObj
= {obj
, [{"_id", Uuid
}, {"_rev", Rev
} | key_value_to_props(Key
,Value
)]},
574 ok
= send_chunk(ModData
, lists:flatten(JsonBegin
++ cjson:encode(JsonObj
)))
576 {ok
, {AccCount
- 1, 0, header_sent
}}
579 {stop
, {0, 0, HeaderSent
}} % we've done "count" rows, stop foldling
584 key_value_to_props(Key
,Value
) ->
585 case Key
of nil
-> []; _
-> [{"key", Key
}] end ++
586 case Value
of nil
-> []; _
-> [{"value", Value
}] end.
589 send_view(ModData
, #uri_parts
{doc
=DocId
, resource
=ViewId
, querystr
=QueryStr
}=UriParts
, Db
) ->
590 QueryArgs
= view_parse_query(QueryStr
),
597 start_docid
=StartDocId
} = QueryArgs
,
603 couch_db:update_temp_view_group_sync(Db
, ViewId
);
605 couch_db:update_view_group_sync(Db
, DocId
)
609 Error
-> throw({error_response
, Error
})
614 FoldlFun
= make_view_fold_fun(ModData
, UriParts
, QueryArgs
),
618 couch_db:fold_temp_view(Db
, ViewId
, {StartKey
, StartDocId
}, Dir
, FoldlFun
, {Count
, SkipCount
, header_not_sent
});
620 couch_db:fold_view(Db
, DocId
, ViewId
, {StartKey
, StartDocId
}, Dir
, FoldlFun
, {Count
, SkipCount
, header_not_sent
})
623 {ok
, TotalRows
, {_
, _
, header_not_sent
}} ->
624 % nothing found in the view, nothing has been returned
626 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
627 FullViewId
= lists:flatten(cjson:encode(lists:flatten(io_lib:format("~s:~s", [DocId
, ViewId
])))),
628 JsonEmptyView
= lists:flatten(
629 io_lib:format("{\"view\":~s,\"total_rows\":~w,\"rows\":[]}",
630 [FullViewId
, TotalRows
])),
631 ok
= send_chunk(ModData
, JsonEmptyView
),
632 ok
= send_final_chunk(ModData
),
634 {ok
, _TotalRows
, {_
, _
, header_sent
}} ->
636 ok
= send_chunk(ModData
, "\n]}"),
637 ok
= send_final_chunk(ModData
),
640 throw({error_response
, Error2
})
643 send_all_dbs(ModData
, _Parts
)->
644 {ok
, DbNames
} = couch_server:all_databases(),
645 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
646 ok
= send_chunk(ModData
, lists:flatten(cjson:encode(list_to_tuple(DbNames
)))),
647 ok
= send_final_chunk(ModData
),
651 send_all_docs(ModData
, #uri_parts
{querystr
=QueryStr
}, Db
) ->
652 QueryArgs
= view_parse_query(QueryStr
),
656 direction
=Dir
} = QueryArgs
,
657 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
658 ok
= send_chunk(ModData
,
659 "{\"view\":\"_all_docs\", \"rows\":[\n"),
660 couch_db:enum_docs(Db
, StartKey
, Dir
,
661 fun(#doc_info
{uuid
=Id
, revision
=Rev
, deleted
=Deleted
}, {AccSeperator
, AccCount
}) ->
662 case {Deleted
, AccCount
> 0} of
664 Json
= {obj
, [{"_id", Id
}, {"_rev", Rev
}]} ,
665 ok
= send_chunk(ModData
, AccSeperator
++ lists:flatten(cjson:encode(Json
))),
666 {ok
, {",\n", AccCount
- 1}};
668 {ok
, {AccSeperator
, AccCount
}}; % skip
670 {stop
, 0} % we've sent "count" rows, stop folding
673 ok
= send_chunk(ModData
, "\n]}"),
674 ok
= send_final_chunk(ModData
),
677 send_all_docs_by_seq(ModData
, #uri_parts
{querystr
=QueryStr
}, Db
) ->
678 QueryArgs
= view_parse_query(QueryStr
),
682 direction
=Dir
} = QueryArgs
,
683 ok
= send_header(ModData
, 200, resp_json_header(ModData
)),
684 ok
= send_chunk(ModData
,
685 "{\"view\":\"_docs_by_update_seq\", \"docs\":[\n"),
687 couch_db:enum_docs_since(Db
, list_to_integer(StartKey
), Dir
,
688 fun(#doc_info
{uuid
=Id
, revision
=Rev
, update_seq
=UpdateSeq
, deleted
=Deleted
, conflict_revs
=ConflictRevs
}, {AccSeperator
, AccCount
}) ->
691 {stop
, 0}; % we've sent "count" rows, stop folding
696 _
-> [{conflicts
, list_to_tuple(ConflictRevs
)}]
700 true
-> [{deleted
, true
}];
706 {update_seq
, UpdateSeq
}] ++
707 ConflictRevsProp
++ DeletedProp
},
708 ok
= send_chunk(ModData
, AccSeperator
++ lists:flatten(cjson:encode(Json
))),
709 {ok
, {",\n", AccCount
- 1}}
712 ok
= send_chunk(ModData
, "\n]}"),
713 ok
= send_final_chunk(ModData
),
716 send_error(ModData
, Error
) ->
717 {Json
, Code
} = error_to_json(Error
),
718 couch_log:info("HTTP Error (code ~w): ~p", [Code
, Json
]),
719 send_json(ModData
, Code
, Json
).
723 % convert an error response into a json object and http error code.
724 error_to_json(Error
) ->
725 {HttpCode
, Atom
, Reason
} = error_to_json0(Error
),
728 is_list(Reason
) -> lists:flatten(Reason
);
729 is_atom(Reason
) -> atom_to_list(Reason
);
730 true
-> lists:flatten(io_lib:write(Reason
)) % else term to text
736 [{id
, atom_to_list(Atom
)},
737 {reason
, Reason1
}]}}]},
740 error_to_json0(not_found
) ->
741 {404, not_found
, "missing"};
742 error_to_json0({not_found
, Reason
}) ->
743 {404, not_found
, Reason
};
744 error_to_json0({database_already_exists
, Reason
}) ->
745 {409, database_already_exists
, Reason
}; % 409, conflict error
746 error_to_json0({update_error
, conflict
}) ->
747 {409, conflict
, conflict
}; % 409, conflict error
748 error_to_json0({Id
, Reason
}) when is_atom(Id
) ->
750 error_to_json0(Error
) ->
759 load("Foo Bar", []) ->
760 {ok
, [], {script_alias
, {"foo", "bar"}}}.