]> review.fuel-infra Code Review - packages/trusty/rabbitmq-server.git/blob
337938804640640cab4da0a91eccf4e20a8f1984
[packages/trusty/rabbitmq-server.git] /
1 %% @author Justin Sheehy <justin@basho.com>
2 %% @author Andy Gross <andy@basho.com>
3 %% @author Bryan Fink <bryan@basho.com>
4 %% @copyright 2007-2009 Basho Technologies
5 %%
6 %%    Licensed under the Apache License, Version 2.0 (the "License");
7 %%    you may not use this file except in compliance with the License.
8 %%    You may obtain a copy of the License at
9 %%
10 %%        http://www.apache.org/licenses/LICENSE-2.0
11 %%
12 %%    Unless required by applicable law or agreed to in writing, software
13 %%    distributed under the License is distributed on an "AS IS" BASIS,
14 %%    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 %%    See the License for the specific language governing permissions and
16 %%    limitations under the License.
17
18 %% @doc Decision core for webmachine
19
20 -module(webmachine_decision_core).
21 -author('Justin Sheehy <justin@basho.com>').
22 -author('Andy Gross <andy@basho.com>').
23 -author('Bryan Fink <bryan@basho.com>').
24 -export([handle_request/2]).
25 -export([do_log/1]).
26 -include("webmachine_logger.hrl").
27
28 handle_request(Resource, ReqState) ->
29     [erase(X) || X <- [decision, code, req_body, bytes_written, tmp_reqstate]],
30     put(resource, Resource),
31     put(reqstate, ReqState),
32     try
33         d(v3b13)
34     catch
35         error:_ ->
36             error_response(erlang:get_stacktrace())
37     end.
38
39 wrcall(X) ->
40     RS0 = get(reqstate),
41     Req = webmachine_request:new(RS0),
42     {Response, RS1} = Req:call(X),
43     put(reqstate, RS1),
44     Response.
45
46 resource_call(Fun) ->
47     Resource = get(resource),
48     {Reply, NewResource, NewRS} = Resource:do(Fun,get()),
49     put(resource, NewResource),
50     put(reqstate, NewRS),
51     Reply.
52
53 get_header_val(H) -> wrcall({get_req_header, H}).
54
55 method() -> wrcall(method).
56
57 d(DecisionID) ->
58     put(decision, DecisionID),
59     log_decision(DecisionID),
60     decision(DecisionID).
61
62 respond(Code) when is_integer(Code) ->
63     respond({Code, undefined});
64 respond({_, _}=CodeAndPhrase) ->
65     Resource = get(resource),
66     EndTime = now(),
67     respond(CodeAndPhrase, Resource, EndTime).
68
69 respond({Code, _ReasonPhrase}=CodeAndPhrase, Resource, EndTime)
70   when Code >= 400, Code < 600 ->
71     error_response(CodeAndPhrase, Resource, EndTime);
72 respond({304, _ReasonPhrase}=CodeAndPhrase, Resource, EndTime) ->
73     wrcall({remove_resp_header, "Content-Type"}),
74     case resource_call(generate_etag) of
75         undefined -> nop;
76         ETag -> wrcall({set_resp_header, "ETag", webmachine_util:quoted_string(ETag)})
77     end,
78     case resource_call(expires) of
79         undefined -> nop;
80         Exp ->
81             wrcall({set_resp_header, "Expires",
82                     webmachine_util:rfc1123_date(Exp)})
83     end,
84     finish_response(CodeAndPhrase, Resource, EndTime);
85 respond(CodeAndPhrase, Resource, EndTime) ->
86     finish_response(CodeAndPhrase, Resource, EndTime).
87
88 finish_response({Code, _}=CodeAndPhrase, Resource, EndTime) ->
89     put(code, Code),
90     wrcall({set_response_code, CodeAndPhrase}),
91     resource_call(finish_request),
92     wrcall({send_response, CodeAndPhrase}),
93     RMod = wrcall({get_metadata, 'resource_module'}),
94     Notes = wrcall(notes),
95     LogData0 = wrcall(log_data),
96     LogData = LogData0#wm_log_data{resource_module=RMod,
97                                    end_time=EndTime,
98                                    notes=Notes},
99     spawn(fun() -> do_log(LogData) end),
100     Resource:stop().
101
102 error_response(Reason) ->
103     error_response(500, Reason).
104
105 error_response(Code, Reason) ->
106     Resource = get(resource),
107     EndTime = now(),
108     error_response({Code, undefined}, Reason, Resource, EndTime).
109
110 error_response({Code, _}=CodeAndPhrase, Resource, EndTime) ->
111     error_response({Code, _}=CodeAndPhrase,
112                    webmachine_error:reason(Code),
113                    Resource,
114                    EndTime).
115
116 error_response({Code, _}=CodeAndPhrase, Reason, Resource, EndTime) ->
117     {ok, ErrorHandler} = application:get_env(webmachine, error_handler),
118     {ErrorHTML, ReqState} = ErrorHandler:render_error(
119                               Code, {webmachine_request,get(reqstate)}, Reason),
120     put(reqstate, ReqState),
121     wrcall({set_resp_body, ErrorHTML}),
122     finish_response(CodeAndPhrase, Resource, EndTime).
123
124 decision_test(Test,TestVal,TrueFlow,FalseFlow) ->
125     case Test of
126         {error, Reason} -> error_response(Reason);
127         {error, Reason0, Reason1} -> error_response({Reason0, Reason1});
128         {halt, Code} -> respond(Code);
129         TestVal -> decision_flow(TrueFlow, Test);
130         _ -> decision_flow(FalseFlow, Test)
131     end.
132
133 decision_test_fn({error, Reason}, _TestFn, _TrueFlow, _FalseFlow) ->
134     error_response(Reason);
135 decision_test_fn({error, R0, R1}, _TestFn, _TrueFlow, _FalseFlow) ->
136     error_response({R0, R1});
137 decision_test_fn({halt, Code}, _TestFn, _TrueFlow, _FalseFlow) ->
138     respond(Code);
139 decision_test_fn(Test,TestFn,TrueFlow,FalseFlow) ->
140     case TestFn(Test) of
141         true -> decision_flow(TrueFlow, Test);
142         false -> decision_flow(FalseFlow, Test)
143     end.
144
145 decision_flow(X, TestResult) when is_integer(X) ->
146     if X >= 500 -> error_response(X, TestResult);
147        true -> respond(X)
148     end;
149 decision_flow(X, _TestResult) when is_atom(X) -> d(X).
150
151 do_log(LogData) ->
152     webmachine_log:log_access(LogData).
153
154 log_decision(DecisionID) ->
155     Resource = get(resource),
156     Resource:log_d(DecisionID).
157
158 %% "Service Available"
159 decision(v3b13) ->
160     decision_test(resource_call(ping), pong, v3b13b, 503);
161 decision(v3b13b) ->
162     decision_test(resource_call(service_available), true, v3b12, 503);
163 %% "Known method?"
164 decision(v3b12) ->
165     decision_test(lists:member(method(), resource_call(known_methods)),
166                   true, v3b11, 501);
167 %% "URI too long?"
168 decision(v3b11) ->
169     decision_test(resource_call(uri_too_long), true, 414, v3b10);
170 %% "Method allowed?"
171 decision(v3b10) ->
172     Methods = resource_call(allowed_methods),
173     case lists:member(method(), Methods) of
174         true ->
175             d(v3b9);
176         false ->
177             wrcall({set_resp_headers, [{"Allow",
178                    string:join([atom_to_list(M) || M <- Methods], ", ")}]}),
179             respond(405)
180     end;
181
182 %% "Content-MD5 present?"
183 decision(v3b9) ->
184     decision_test(get_header_val("content-md5"), undefined, v3b9b, v3b9a);
185 %% "Content-MD5 valid?"
186 decision(v3b9a) ->
187     case resource_call(validate_content_checksum) of
188         {error, Reason} ->
189             error_response(Reason);
190         {halt, Code} ->
191             respond(Code);
192         not_validated ->
193             Checksum = base64:decode(get_header_val("content-md5")),
194             BodyHash = compute_body_md5(),
195             case BodyHash =:= Checksum of
196                 true -> d(v3b9b);
197                 _ ->
198                     respond(400)
199             end;
200         false ->
201             respond(400);
202         _ -> d(v3b9b)
203     end;
204 %% "Malformed?"
205 decision(v3b9b) ->
206     decision_test(resource_call(malformed_request), true, 400, v3b8);
207 %% "Authorized?"
208 decision(v3b8) ->
209     case resource_call(is_authorized) of
210         true -> d(v3b7);
211         {error, Reason} ->
212             error_response(Reason);
213         {halt, Code}  ->
214             respond(Code);
215         AuthHead ->
216             wrcall({set_resp_header, "WWW-Authenticate", AuthHead}),
217             respond(401)
218     end;
219 %% "Forbidden?"
220 decision(v3b7) ->
221     decision_test(resource_call(forbidden), true, 403, v3b6);
222 %% "Okay Content-* Headers?"
223 decision(v3b6) ->
224     decision_test(resource_call(valid_content_headers), true, v3b5, 501);
225 %% "Known Content-Type?"
226 decision(v3b5) ->
227     decision_test(resource_call(known_content_type), true, v3b4, 415);
228 %% "Req Entity Too Large?"
229 decision(v3b4) ->
230     decision_test(resource_call(valid_entity_length), true, v3b3, 413);
231 %% "OPTIONS?"
232 decision(v3b3) ->
233     case method() of
234         'OPTIONS' ->
235             Hdrs = resource_call(options),
236             wrcall({set_resp_headers, Hdrs}),
237             respond(200);
238         _ ->
239             d(v3c3)
240     end;
241 %% Accept exists?
242 decision(v3c3) ->
243     PTypes = [Type || {Type,_Fun} <- resource_call(content_types_provided)],
244     case get_header_val("accept") of
245         undefined ->
246             wrcall({set_metadata, 'content-type', hd(PTypes)}),
247             d(v3d4);
248         _ ->
249             d(v3c4)
250     end;
251 %% Acceptable media type available?
252 decision(v3c4) ->
253     PTypes = [Type || {Type,_Fun} <- resource_call(content_types_provided)],
254     AcceptHdr = get_header_val("accept"),
255     case webmachine_util:choose_media_type(PTypes, AcceptHdr) of
256         none ->
257             respond(406);
258         MType ->
259             wrcall({set_metadata, 'content-type', MType}),
260             d(v3d4)
261     end;
262 %% Accept-Language exists?
263 decision(v3d4) ->
264     decision_test(get_header_val("accept-language"),
265                   undefined, v3e5, v3d5);
266 %% Acceptable Language available? %% WMACH-46 (do this as proper conneg)
267 decision(v3d5) ->
268     decision_test(resource_call(language_available), true, v3e5, 406);
269 %% Accept-Charset exists?
270 decision(v3e5) ->
271     case get_header_val("accept-charset") of
272         undefined -> decision_test(choose_charset("*"),
273                                    none, 406, v3f6);
274         _ -> d(v3e6)
275     end;
276 %% Acceptable Charset available?
277 decision(v3e6) ->
278     decision_test(choose_charset(get_header_val("accept-charset")),
279                   none, 406, v3f6);
280 %% Accept-Encoding exists?
281 % (also, set content-type header here, now that charset is chosen)
282 decision(v3f6) ->
283     CType = wrcall({get_metadata, 'content-type'}),
284     CSet = case wrcall({get_metadata, 'chosen-charset'}) of
285                undefined -> "";
286                CS -> "; charset=" ++ CS
287            end,
288     wrcall({set_resp_header, "Content-Type", CType ++ CSet}),
289     case get_header_val("accept-encoding") of
290         undefined ->
291             decision_test(choose_encoding("identity;q=1.0,*;q=0.5"),
292                           none, 406, v3g7);
293         _ -> d(v3f7)
294     end;
295 %% Acceptable encoding available?
296 decision(v3f7) ->
297     decision_test(choose_encoding(get_header_val("accept-encoding")),
298                   none, 406, v3g7);
299 %% "Resource exists?"
300 decision(v3g7) ->
301     % this is the first place after all conneg, so set Vary here
302     case variances() of
303         [] -> nop;
304         Variances ->
305             wrcall({set_resp_header, "Vary", string:join(Variances, ", ")})
306     end,
307     decision_test(resource_call(resource_exists), true, v3g8, v3h7);
308 %% "If-Match exists?"
309 decision(v3g8) ->
310     decision_test(get_header_val("if-match"), undefined, v3h10, v3g9);
311 %% "If-Match: * exists"
312 decision(v3g9) ->
313     decision_test(get_header_val("if-match"), "*", v3h10, v3g11);
314 %% "ETag in If-Match"
315 decision(v3g11) ->
316     ETags = webmachine_util:split_quoted_strings(get_header_val("if-match")),
317     decision_test_fn(resource_call(generate_etag),
318                      fun(ETag) -> lists:member(ETag, ETags) end,
319                      v3h10, 412);
320 %% "If-Match exists"
321 %% (note: need to reflect this change at in next version of diagram)
322 decision(v3h7) ->
323     decision_test(get_header_val("if-match"), undefined, v3i7, 412);
324 %% "If-unmodified-since exists?"
325 decision(v3h10) ->
326     decision_test(get_header_val("if-unmodified-since"),undefined,v3i12,v3h11);
327 %% "I-UM-S is valid date?"
328 decision(v3h11) ->
329     IUMSDate = get_header_val("if-unmodified-since"),
330     decision_test(webmachine_util:convert_request_date(IUMSDate),
331                   bad_date, v3i12, v3h12);
332 %% "Last-Modified > I-UM-S?"
333 decision(v3h12) ->
334     ReqDate = get_header_val("if-unmodified-since"),
335     ReqErlDate = webmachine_util:convert_request_date(ReqDate),
336     ResErlDate = resource_call(last_modified),
337     decision_test(ResErlDate > ReqErlDate,
338                   true, 412, v3i12);
339 %% "Moved permanently? (apply PUT to different URI)"
340 decision(v3i4) ->
341     case resource_call(moved_permanently) of
342         {true, MovedURI} ->
343             wrcall({set_resp_header, "Location", MovedURI}),
344             respond(301);
345         false ->
346             d(v3p3);
347         {error, Reason} ->
348             error_response(Reason);
349         {halt, Code} ->
350             respond(Code)
351     end;
352 %% PUT?
353 decision(v3i7) ->
354     decision_test(method(), 'PUT', v3i4, v3k7);
355 %% "If-none-match exists?"
356 decision(v3i12) ->
357     decision_test(get_header_val("if-none-match"), undefined, v3l13, v3i13);
358 %% "If-None-Match: * exists?"
359 decision(v3i13) ->
360     decision_test(get_header_val("if-none-match"), "*", v3j18, v3k13);
361 %% GET or HEAD?
362 decision(v3j18) ->
363     decision_test(lists:member(method(),['GET','HEAD']),
364                   true, 304, 412);
365 %% "Moved permanently?"
366 decision(v3k5) ->
367     case resource_call(moved_permanently) of
368         {true, MovedURI} ->
369             wrcall({set_resp_header, "Location", MovedURI}),
370             respond(301);
371         false ->
372             d(v3l5);
373         {error, Reason} ->
374             error_response(Reason);
375         {halt, Code} ->
376             respond(Code)
377     end;
378 %% "Previously existed?"
379 decision(v3k7) ->
380     decision_test(resource_call(previously_existed), true, v3k5, v3l7);
381 %% "Etag in if-none-match?"
382 decision(v3k13) ->
383     ETags = webmachine_util:split_quoted_strings(get_header_val("if-none-match")),
384     decision_test_fn(resource_call(generate_etag),
385                      %% Membership test is a little counter-intuitive here; if the
386                      %% provided ETag is a member, we follow the error case out
387                      %% via v3j18.
388                      fun(ETag) -> lists:member(ETag, ETags) end,
389                      v3j18, v3l13);
390 %% "Moved temporarily?"
391 decision(v3l5) ->
392     case resource_call(moved_temporarily) of
393         {true, MovedURI} ->
394             wrcall({set_resp_header, "Location", MovedURI}),
395             respond(307);
396         false ->
397             d(v3m5);
398         {error, Reason} ->
399             error_response(Reason);
400         {halt, Code} ->
401             respond(Code)
402     end;
403 %% "POST?"
404 decision(v3l7) ->
405     decision_test(method(), 'POST', v3m7, 404);
406 %% "IMS exists?"
407 decision(v3l13) ->
408     decision_test(get_header_val("if-modified-since"), undefined, v3m16, v3l14);
409 %% "IMS is valid date?"
410 decision(v3l14) ->
411     IMSDate = get_header_val("if-modified-since"),
412     decision_test(webmachine_util:convert_request_date(IMSDate),
413                   bad_date, v3m16, v3l15);
414 %% "IMS > Now?"
415 decision(v3l15) ->
416     NowDateTime = calendar:universal_time(),
417     ReqDate = get_header_val("if-modified-since"),
418     ReqErlDate = webmachine_util:convert_request_date(ReqDate),
419     decision_test(ReqErlDate > NowDateTime,
420                   true, v3m16, v3l17);
421 %% "Last-Modified > IMS?"
422 decision(v3l17) ->
423     ReqDate = get_header_val("if-modified-since"),
424     ReqErlDate = webmachine_util:convert_request_date(ReqDate),
425     ResErlDate = resource_call(last_modified),
426     decision_test(ResErlDate =:= undefined orelse ResErlDate > ReqErlDate,
427                   true, v3m16, 304);
428 %% "POST?"
429 decision(v3m5) ->
430     decision_test(method(), 'POST', v3n5, 410);
431 %% "Server allows POST to missing resource?"
432 decision(v3m7) ->
433     decision_test(resource_call(allow_missing_post), true, v3n11, 404);
434 %% "DELETE?"
435 decision(v3m16) ->
436     decision_test(method(), 'DELETE', v3m20, v3n16);
437 %% DELETE enacted immediately?
438 %% Also where DELETE is forced.
439 decision(v3m20) ->
440     Result = resource_call(delete_resource),
441     %% DELETE may have body and TCP connection will be closed unless body is read.
442     %% See mochiweb_request:should_close.
443     maybe_flush_body_stream(),
444     decision_test(Result, true, v3m20b, 500);
445 decision(v3m20b) ->
446     decision_test(resource_call(delete_completed), true, v3o20, 202);
447 %% "Server allows POST to missing resource?"
448 decision(v3n5) ->
449     decision_test(resource_call(allow_missing_post), true, v3n11, 410);
450 %% "Redirect?"
451 decision(v3n11) ->
452     Stage1 = case resource_call(post_is_create) of
453         true ->
454             case resource_call(create_path) of
455                 undefined -> error_response("post_is_create w/o create_path");
456                 NewPath ->
457                     case is_list(NewPath) of
458                         false -> error_response({"create_path not a string", NewPath});
459                         true ->
460                             BaseUri = case resource_call(base_uri) of
461                                 undefined -> wrcall(base_uri);
462                                 Any ->
463                                     case [lists:last(Any)] of
464                                         "/" -> lists:sublist(Any, erlang:length(Any) - 1);
465                                         _ -> Any
466                                     end
467                             end,
468                             FullPath = filename:join(["/", wrcall(path), NewPath]),
469                             wrcall({set_disp_path, NewPath}),
470                             case wrcall({get_resp_header, "Location"}) of
471                                 undefined -> wrcall({set_resp_header, "Location", BaseUri ++ FullPath});
472                                 _ -> ok
473                             end,
474
475                             Res = accept_helper(),
476                             case Res of
477                                 {respond, Code} -> respond(Code);
478                                 {halt, Code} -> respond(Code);
479                                 {error, _,_} -> error_response(Res);
480                                 {error, _} -> error_response(Res);
481                                 _ -> stage1_ok
482                             end
483                     end
484             end;
485         _ ->
486             case resource_call(process_post) of
487                 true ->
488                     encode_body_if_set(),
489                     stage1_ok;
490                 {halt, Code} -> respond(Code);
491                 Err -> error_response(Err)
492             end
493     end,
494     case Stage1 of
495         stage1_ok ->
496             case wrcall(resp_redirect) of
497                 true ->
498                     case wrcall({get_resp_header, "Location"}) of
499                         undefined ->
500                             Reason = "Response had do_redirect but no Location",
501                             error_response(500, Reason);
502                         _ ->
503                             respond(303)
504                     end;
505                 _ ->
506                     d(v3p11)
507             end;
508         _ -> nop
509     end;
510 %% "POST?"
511 decision(v3n16) ->
512     decision_test(method(), 'POST', v3n11, v3o16);
513 %% Conflict?
514 decision(v3o14) ->
515     case resource_call(is_conflict) of
516         true -> respond(409);
517         _ -> Res = accept_helper(),
518              case Res of
519                  {respond, Code} -> respond(Code);
520                  {halt, Code} -> respond(Code);
521                  {error, _,_} -> error_response(Res);
522                  {error, _} -> error_response(Res);
523                  _ -> d(v3p11)
524              end
525     end;
526 %% "PUT?"
527 decision(v3o16) ->
528     decision_test(method(), 'PUT', v3o14, v3o18);
529 %% Multiple representations?
530 % (also where body generation for GET and HEAD is done)
531 decision(v3o18) ->
532     BuildBody = case method() of
533         'GET' -> true;
534         'HEAD' -> true;
535         _ -> false
536     end,
537     FinalBody = case BuildBody of
538         true ->
539             case resource_call(generate_etag) of
540                 undefined -> nop;
541                 ETag -> wrcall({set_resp_header, "ETag", webmachine_util:quoted_string(ETag)})
542             end,
543             CT = wrcall({get_metadata, 'content-type'}),
544             case resource_call(last_modified) of
545                 undefined -> nop;
546                 LM ->
547                     wrcall({set_resp_header, "Last-Modified",
548                             webmachine_util:rfc1123_date(LM)})
549             end,
550             case resource_call(expires) of
551                 undefined -> nop;
552                 Exp ->
553                     wrcall({set_resp_header, "Expires",
554                             webmachine_util:rfc1123_date(Exp)})
555             end,
556             F = hd([Fun || {Type,Fun} <- resource_call(content_types_provided),
557                            CT =:= webmachine_util:format_content_type(Type)]),
558             resource_call(F);
559         false -> nop
560     end,
561     case FinalBody of
562         {error, _} -> error_response(FinalBody);
563         {error, _,_} -> error_response(FinalBody);
564         {halt, Code} -> respond(Code);
565         nop -> d(v3o18b);
566         _ -> wrcall({set_resp_body,
567                      encode_body(FinalBody)}),
568              d(v3o18b)
569     end;
570
571 decision(v3o18b) ->
572     decision_test(resource_call(multiple_choices), true, 300, 200);
573 %% Response includes an entity?
574 decision(v3o20) ->
575     decision_test(wrcall(has_resp_body), true, v3o18, 204);
576 %% Conflict?
577 decision(v3p3) ->
578     case resource_call(is_conflict) of
579         true -> respond(409);
580         _ -> Res = accept_helper(),
581              case Res of
582                  {respond, Code} -> respond(Code);
583                  {halt, Code} -> respond(Code);
584                  {error, _,_} -> error_response(Res);
585                  {error, _} -> error_response(Res);
586                  _ -> d(v3p11)
587              end
588     end;
589
590 %% New resource?  (at this point boils down to "has location header")
591 decision(v3p11) ->
592     case wrcall({get_resp_header, "Location"}) of
593         undefined -> d(v3o20);
594         _ -> respond(201)
595     end.
596
597 accept_helper() ->
598     accept_helper(get_header_val("Content-Type")).
599
600 accept_helper(undefined) ->
601     accept_helper("application/octet-stream");
602 accept_helper([]) ->
603     accept_helper("application/octet-stream");
604 accept_helper(CT) ->
605     {MT, MParams} = webmachine_util:media_type_to_detail(CT),
606     wrcall({set_metadata, 'mediaparams', MParams}),
607     case [Fun || {Type,Fun} <-
608                      resource_call(content_types_accepted), MT =:= Type] of
609         [] -> {respond,415};
610         AcceptedContentList ->
611             F = hd(AcceptedContentList),
612             case resource_call(F) of
613                 true ->
614                     encode_body_if_set(),
615                     true;
616                 Result -> Result
617             end
618     end.
619
620 encode_body_if_set() ->
621     case wrcall(has_resp_body) of
622         true ->
623             Body = wrcall(resp_body),
624             wrcall({set_resp_body, encode_body(Body)}),
625             true;
626         _ -> false
627     end.
628
629 encode_body(Body) ->
630     ChosenCSet = wrcall({get_metadata, 'chosen-charset'}),
631     Charsetter =
632     case resource_call(charsets_provided) of
633         no_charset -> fun(X) -> X end;
634         CP -> hd([Fun || {CSet,Fun} <- CP, ChosenCSet =:= CSet])
635     end,
636     ChosenEnc = wrcall({get_metadata, 'content-encoding'}),
637     Encoder = hd([Fun || {Enc,Fun} <- resource_call(encodings_provided),
638                          ChosenEnc =:= Enc]),
639     case Body of
640         {stream, StreamBody} ->
641             {stream, make_encoder_stream(Encoder, Charsetter, StreamBody)};
642         {known_length_stream, 0, _StreamBody} ->
643             {known_length_stream, 0, empty_stream()};
644         {known_length_stream, Size, StreamBody} ->
645             case method() of
646                 'HEAD' ->
647                     {known_length_stream, Size, empty_stream()};
648                 _ ->
649                     {known_length_stream, Size, make_encoder_stream(Encoder, Charsetter, StreamBody)}
650             end;
651         {stream, Size, Fun} ->
652             {stream, Size, make_size_encoder_stream(Encoder, Charsetter, Fun)};
653         {writer, BodyFun} ->
654             {writer, {Encoder, Charsetter, BodyFun}};
655         _ ->
656             Encoder(Charsetter(iolist_to_binary(Body)))
657     end.
658
659 %% @private
660 empty_stream() ->
661     {<<>>, fun() -> {<<>>, done} end}.
662
663 make_encoder_stream(Encoder, Charsetter, {Body, done}) ->
664     {Encoder(Charsetter(Body)), done};
665 make_encoder_stream(Encoder, Charsetter, {Body, Next}) ->
666     {Encoder(Charsetter(Body)),
667      fun() -> make_encoder_stream(Encoder, Charsetter, Next()) end}.
668
669 make_size_encoder_stream(Encoder, Charsetter, Fun) ->
670     fun(Start, End) ->
671             make_encoder_stream(Encoder, Charsetter, Fun(Start, End))
672     end.
673
674 choose_encoding(AccEncHdr) ->
675     Encs = [Enc || {Enc,_Fun} <- resource_call(encodings_provided)],
676     case webmachine_util:choose_encoding(Encs, AccEncHdr) of
677         none -> none;
678         ChosenEnc ->
679             case ChosenEnc of
680                 "identity" ->
681                     nop;
682                 _ ->
683                     wrcall({set_resp_header, "Content-Encoding",ChosenEnc})
684             end,
685             wrcall({set_metadata, 'content-encoding',ChosenEnc}),
686             ChosenEnc
687     end.
688
689 choose_charset(AccCharHdr) ->
690     case resource_call(charsets_provided) of
691         no_charset ->
692             no_charset;
693         CL ->
694             CSets = [CSet || {CSet,_Fun} <- CL],
695             case webmachine_util:choose_charset(CSets, AccCharHdr) of
696                 none -> none;
697                 Charset ->
698                     wrcall({set_metadata, 'chosen-charset',Charset}),
699                     Charset
700             end
701     end.
702
703 variances() ->
704     Accept = case length(resource_call(content_types_provided)) of
705         1 -> [];
706         0 -> [];
707         _ -> ["Accept"]
708     end,
709     AcceptEncoding = case length(resource_call(encodings_provided)) of
710         1 -> [];
711         0 -> [];
712         _ -> ["Accept-Encoding"]
713     end,
714     AcceptCharset = case resource_call(charsets_provided) of
715         no_charset -> [];
716         CP ->
717             case length(CP) of
718                 1 -> [];
719                 0 -> [];
720                 _ -> ["Accept-Charset"]
721             end
722     end,
723     Accept ++ AcceptEncoding ++ AcceptCharset ++ resource_call(variances).
724
725 md5(Bin) ->
726     erlang:md5(Bin).
727
728 md5_init() ->
729     erlang:md5_init().
730
731 md5_update(Ctx, Bin) ->
732     erlang:md5_update(Ctx, Bin).
733
734 md5_final(Ctx) ->
735     erlang:md5_final(Ctx).
736
737 compute_body_md5() ->
738     case wrcall({req_body, 52428800}) of
739         stream_conflict ->
740             compute_body_md5_stream();
741         Body ->
742             md5(Body)
743     end.
744
745 compute_body_md5_stream() ->
746     MD5Ctx = md5_init(),
747     compute_body_md5_stream(MD5Ctx, wrcall({stream_req_body, 8192}), <<>>).
748
749 compute_body_md5_stream(MD5, {Hunk, done}, Body) ->
750     %% Save the body so it can be retrieved later
751     put(reqstate, wrq:set_resp_body(Body, get(reqstate))),
752     md5_final(md5_update(MD5, Hunk));
753 compute_body_md5_stream(MD5, {Hunk, Next}, Body) ->
754     compute_body_md5_stream(md5_update(MD5, Hunk), Next(), <<Body/binary, Hunk/binary>>).
755
756 maybe_flush_body_stream() ->
757     maybe_flush_body_stream(wrcall({stream_req_body, 8192})).
758
759 maybe_flush_body_stream(stream_conflict) ->
760     ok;
761 maybe_flush_body_stream({_Hunk, done}) ->
762     ok;
763 maybe_flush_body_stream({_Hunk, Next}) ->
764     maybe_flush_body_stream(Next()).