From b76e327e469dc630775d493731066b0b4c0f0e89 Mon Sep 17 00:00:00 2001 From: Martin Sumner Date: Wed, 1 Feb 2023 10:00:16 +0000 Subject: [PATCH] Mas i1847 putapi (#1848) * Never GET before PUT As the if_not_modified and if_none_match are not supported via the HTTP API for non-consistent PUT - simply build object from a new object, as with PB API. * More webmachine friendly override resource_exists/2 is a key part of the flow for both GET and PUT, as conditional HTTP headers require this check for PUTs. therefore only override resource_exists (and don't fetch) when it is a PUT, and those conditional headers do not exist. * Attempt to tidy and refactor delete So that delete does not require a fetch * Pipe-cleaned delete path * Add if_not_modified conflict check To mimic if_not_modified feature via PB API * Use hyphen not underscore To be consistent with other HTTP headers * Revert to 404 on if DELETE not_found Also ensure the timeouts passed in in a delete is respected, and passed through the riak_client to the FSM. --- src/riak_client.erl | 4 +- src/riak_kv_get_fsm.erl | 3 +- src/riak_kv_wm_object.erl | 375 ++++++++++++++++++++++++++------------ src/riak_kv_wm_raw.hrl | 1 + 4 files changed, 262 insertions(+), 121 deletions(-) diff --git a/src/riak_client.erl b/src/riak_client.erl index 9a0e441df1..4ad2134574 100644 --- a/src/riak_client.erl +++ b/src/riak_client.erl @@ -531,7 +531,7 @@ delete(Bucket,Key,{?MODULE, [_Node, _ClientId]}=THIS) -> delete(Bucket,Key,[],?D %% nodes have responded with a value or error. %% @equiv delete(Bucket, Key, RW, default_timeout()) delete(Bucket,Key,Options,{?MODULE, [_Node, _ClientId]}=THIS) when is_list(Options) -> - delete(Bucket,Key,Options,?DEFAULT_TIMEOUT,THIS); + delete(Bucket,Key,Options,recv_timeout(Options),THIS); delete(Bucket,Key,RW,{?MODULE, [_Node, _ClientId]}=THIS) -> delete(Bucket,Key,[{rw, RW}],?DEFAULT_TIMEOUT,THIS). @@ -622,7 +622,7 @@ delete_vclock(Bucket,Key,VClock,{?MODULE, [_Node, _ClientId]}=THIS) -> %% nodes have responded with a value or error. %% @equiv delete(Bucket, Key, RW, default_timeout()) delete_vclock(Bucket,Key,VClock,Options,{?MODULE, [_Node, _ClientId]}=THIS) when is_list(Options) -> - delete_vclock(Bucket,Key,VClock,Options,?DEFAULT_TIMEOUT,THIS); + delete_vclock(Bucket,Key,VClock,Options,recv_timeout(Options),THIS); delete_vclock(Bucket,Key,VClock,RW,{?MODULE, [_Node, _ClientId]}=THIS) -> delete_vclock(Bucket,Key,VClock,[{rw, RW}],?DEFAULT_TIMEOUT,THIS). diff --git a/src/riak_kv_get_fsm.erl b/src/riak_kv_get_fsm.erl index 8dd1937fa8..33429c200e 100644 --- a/src/riak_kv_get_fsm.erl +++ b/src/riak_kv_get_fsm.erl @@ -23,6 +23,7 @@ -module(riak_kv_get_fsm). -behaviour(gen_fsm). -include_lib("riak_kv_vnode.hrl"). +-include_lib("kernel/include/logger.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -export([test_link/7, test_link/5]). @@ -704,7 +705,7 @@ read_repair(GetCoreIndices, RepairObj, fun({{Idx, Node}, _Type, Reason}) -> case app_helper:get_env(riak_kv, read_repair_log, false) of true -> - lager:info( + ?LOG_INFO( "Read repair of ~p on ~w ~w for reason ~w", [BKey, Idx, Node, Reason]); false -> diff --git a/src/riak_kv_wm_object.erl b/src/riak_kv_wm_object.erl index 6f6fb3a1d2..6ea8d8666e 100644 --- a/src/riak_kv_wm_object.erl +++ b/src/riak_kv_wm_object.erl @@ -137,6 +137,7 @@ allow_missing_post/2, malformed_request/2, resource_exists/2, + is_conflict/2, last_modified/2, generate_etag/2, content_types_provided/2, @@ -188,14 +189,24 @@ -type riak_kv_wm_object_dict() :: dict(). -endif. +-include_lib("webmachine/include/webmachine.hrl"). +-include("riak_kv_wm_raw.hrl"). + -type context() :: #ctx{}. +-type request_data() :: #wm_reqdata{}. + +-type validation_function() :: + fun((request_data(), context()) -> + {boolean()|{halt, pos_integer()}, request_data(), context()}). -type link() :: {{Bucket::binary(), Key::binary()}, Tag::binary()}. -define(DEFAULT_TIMEOUT, 60000). - --include_lib("webmachine/include/webmachine.hrl"). --include("riak_kv_wm_raw.hrl"). +-define(V1_BUCKET_REGEX, "/([^/]+)>; ?rel=\"([^\"]+)\""). +-define(V1_KEY_REGEX, "/([^/]+)/([^/]+)>; ?riaktag=\"([^\"]+)\""). +-define(V2_BUCKET_REGEX, "; ?rel=\"([^\"]+)\""). +-define(V2_KEY_REGEX, + "; ?riaktag=\"([^\"]+)\""). -spec init(proplists:proplist()) -> {ok, context()}. %% @doc Initialize this resource. This function extracts the @@ -215,23 +226,33 @@ init(Props) -> %% query parameter. service_available(RD, Ctx0=#ctx{riak=RiakProps}) -> Ctx = riak_kv_wm_utils:ensure_bucket_type(RD, Ctx0, #ctx.bucket_type), - case riak_kv_wm_utils:get_riak_client(RiakProps, riak_kv_wm_utils:get_client_id(RD)) of + ClientID = riak_kv_wm_utils:get_client_id(RD), + case riak_kv_wm_utils:get_riak_client(RiakProps, ClientID) of {ok, C} -> + Bucket = + case wrq:path_info(bucket, RD) of + undefined -> + undefined; + B -> + list_to_binary( + riak_kv_wm_utils:maybe_decode_uri(RD, B)) + end, + Key = + case wrq:path_info(key, RD) of + undefined -> + undefined; + K -> + list_to_binary( + riak_kv_wm_utils:maybe_decode_uri(RD, K)) + end, {true, - RD, - Ctx#ctx{ - method=wrq:method(RD), - client=C, - bucket=case wrq:path_info(bucket, RD) of - undefined -> undefined; - B -> list_to_binary(riak_kv_wm_utils:maybe_decode_uri(RD, B)) - end, - key=case wrq:path_info(key, RD) of - undefined -> undefined; - K -> list_to_binary(riak_kv_wm_utils:maybe_decode_uri(RD, K)) - end, - vtag=wrq:get_qs_value(?Q_VTAG, RD) - }}; + RD, + Ctx#ctx{ + method=wrq:method(RD), + client=C, + bucket=Bucket, + key=Key, + vtag=wrq:get_qs_value(?Q_VTAG, RD)}}; Error -> {false, wrq:set_resp_body( @@ -266,7 +287,8 @@ forbidden(RD, Ctx) -> -spec validate(#wm_reqdata{}, context()) -> term(). validate(RD, Ctx=#ctx{security=undefined}) -> - validate_resource(RD, Ctx, riak_kv_wm_utils:method_to_perm(Ctx#ctx.method)); + validate_resource( + RD, Ctx, riak_kv_wm_utils:method_to_perm(Ctx#ctx.method)); validate(RD, Ctx=#ctx{security=Security}) -> Perm = riak_kv_wm_utils:method_to_perm(Ctx#ctx.method), Res = riak_core_security:check_permission({Perm, @@ -275,7 +297,8 @@ validate(RD, Ctx=#ctx{security=Security}) -> Security), maybe_validate_resource(Res, RD, Ctx, Perm). --spec maybe_validate_resource(term(), #wm_reqdata{}, context(), string()) -> term(). +-spec maybe_validate_resource( + term(), #wm_reqdata{}, context(), string()) -> term(). maybe_validate_resource({false, Error, _}, RD, Ctx, _Perm) -> RD1 = wrq:set_resp_header("Content-Type", "text/plain", RD), {true, wrq:append_to_resp_body( @@ -285,7 +308,7 @@ maybe_validate_resource({true, _}, RD, Ctx, Perm) -> validate_resource(RD, Ctx, Perm). -spec validate_resource(#wm_reqdata{}, context(), string()) -> term(). -validate_resource(RD, Ctx, Perm) when (Perm == "riak_kv.get" orelse Perm == "riak_kv.delete") -> +validate_resource(RD, Ctx, Perm) when Perm == "riak_kv.get" -> %% Ensure the key is here, otherwise 404 %% we do this early as it used to be done in the %% malformed check, so the rest of the resource @@ -358,7 +381,9 @@ malformed_request(RD, Ctx) -> %% @doc Given a list of 2-arity funs, threads through the request data %% and context, returning as soon as a single fun discovers a %% malformed request or halts. --spec malformed_request([fun()], #wm_reqdata{}, #ctx{}) -> {boolean() | {halt, non_neg_integer()}, #wm_reqdata{}, #ctx{}}. +-spec malformed_request( + list(validation_function()), request_data(), context()) -> + {boolean() | {halt, pos_integer()}, request_data(), context()}. malformed_request([], RD, Ctx) -> {false, RD, Ctx}; malformed_request([H|T], RD, Ctx) -> @@ -394,11 +419,11 @@ malformed_timeout_param(RD, Ctx) -> catch _:_ -> {true, - wrq:append_to_resp_body(io_lib:format("Bad timeout " - "value ~p~n", - [TimeoutStr]), - wrq:set_resp_header(?HEAD_CTYPE, - "text/plain", RD)), + wrq:append_to_resp_body( + io_lib:format("Bad timeout " + "value ~p~n", + [TimeoutStr]), + wrq:set_resp_header(?HEAD_CTYPE, "text/plain", RD)), Ctx} end end. @@ -569,23 +594,26 @@ malformed_index_headers(RD, Ctx) -> extract_index_fields(RD) -> PrefixSize = length(?HEAD_INDEX_PREFIX), {ok, RE} = re:compile(",\\s"), - F = fun({K,V}, Acc) -> - KList = riak_kv_wm_utils:any_to_list(K), - case lists:prefix(?HEAD_INDEX_PREFIX, string:to_lower(KList)) of - true -> - %% Isolate the name of the index field. - IndexField = list_to_binary(element(2, lists:split(PrefixSize, KList))), - - %% HACK ALERT: Split values on comma. The HTTP - %% spec allows for comma separated tokens - %% where the tokens can be quoted strings. We - %% don't currently support quoted strings. - %% (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) - Values = re:split(V, RE, [{return, binary}]), - [{IndexField, X} || X <- Values] ++ Acc; - false -> - Acc - end + F = + fun({K,V}, Acc) -> + KList = riak_kv_wm_utils:any_to_list(K), + case lists:prefix(?HEAD_INDEX_PREFIX, string:to_lower(KList)) of + true -> + %% Isolate the name of the index field. + IndexField = + list_to_binary( + element(2, lists:split(PrefixSize, KList))), + + %% HACK ALERT: Split values on comma. The HTTP + %% spec allows for comma separated tokens + %% where the tokens can be quoted strings. We + %% don't currently support quoted strings. + %% (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) + Values = re:split(V, RE, [{return, binary}]), + [{IndexField, X} || X <- Values] ++ Acc; + false -> + Acc + end end, lists:foldl(F, [], mochiweb_headers:to_list(wrq:req_headers(RD))). @@ -594,10 +622,13 @@ extract_index_fields(RD) -> %% @doc List the content types available for representing this resource. %% The content-type for a key-level request is the content-type that %% was used in the PUT request that stored the document in Riak. -content_types_provided(RD, Ctx=#ctx{method=Method}=Ctx) when Method =:= 'PUT'; - Method =:= 'POST' -> +content_types_provided(RD, Ctx=#ctx{method=Method}=Ctx) + when Method =:= 'PUT'; Method =:= 'POST' -> {ContentType, _} = extract_content_type(RD), {[{ContentType, produce_doc_body}], RD, Ctx}; +content_types_provided(RD, Ctx=#ctx{method=Method}=Ctx) + when Method =:= 'DELETE' -> + {[{"text/html", to_html}], RD, Ctx}; content_types_provided(RD, Ctx0) -> DocCtx = ensure_doc(Ctx0), %% we can assume DocCtx#ctx.doc is {ok,Doc} because of malformed_request @@ -616,14 +647,17 @@ content_types_provided(RD, Ctx0) -> %% The charset for a key-level request is the charset that was used %% in the PUT request that stored the document in Riak (none if %% no charset was specified at PUT-time). -charsets_provided(RD, #ctx{method=Method}=Ctx) when Method =:= 'PUT'; - Method =:= 'POST' -> +charsets_provided(RD, Ctx=#ctx{method=Method}=Ctx) + when Method =:= 'PUT'; Method =:= 'POST' -> case extract_content_type(RD) of {_, undefined} -> {no_charset, RD, Ctx}; {_, Charset} -> {[{Charset, fun(X) -> X end}], RD, Ctx} end; +charsets_provided(RD, Ctx=#ctx{method=Method}=Ctx) + when Method =:= 'DELETE' -> + {no_charset, RD, Ctx}; charsets_provided(RD, Ctx0) -> DocCtx = ensure_doc(Ctx0), case DocCtx#ctx.doc of @@ -650,7 +684,13 @@ charsets_provided(RD, Ctx0) -> %% used in the PUT request that stored the document in Riak, or %% "identity" and "gzip" if no encoding was specified at PUT-time. encodings_provided(RD, Ctx0) -> - DocCtx = ensure_doc(Ctx0), + DocCtx = + case Ctx0#ctx.method of + UpdM when UpdM =:= 'PUT'; UpdM =:= 'POST'; UpdM =:= 'DELETE' -> + Ctx0; + _ -> + ensure_doc(Ctx0) + end, case DocCtx#ctx.doc of {ok, _} -> case select_doc(DocCtx) of @@ -664,7 +704,7 @@ encodings_provided(RD, Ctx0) -> multiple_choices -> {riak_kv_wm_utils:default_encodings(), RD, DocCtx} end; - {error, _} -> + _ -> {riak_kv_wm_utils:default_encodings(), RD, DocCtx} end. @@ -707,26 +747,84 @@ content_types_accepted(RD, Ctx) -> %% and either no vtag query parameter was specified, or the value of the %% vtag param matches the vtag of some value of the Riak object. resource_exists(RD, Ctx0) -> - DocCtx = ensure_doc(Ctx0), - case DocCtx#ctx.doc of - {ok, Doc} -> - case DocCtx#ctx.vtag of - undefined -> - {true, RD, DocCtx}; - Vtag -> - MDs = riak_object:get_metadatas(Doc), - {lists:any(fun(M) -> - dict:fetch(?MD_VTAG, M) =:= Vtag - end, - MDs), - RD, DocCtx#ctx{vtag=Vtag}} + Method = Ctx0#ctx.method, + ToFetch = + case Method of + UpdM when UpdM =:= 'PUT'; UpdM =:= 'POST'; UpdM =:= 'DELETE' -> + conditional_headers_present(RD) == true; + _ -> + true + end, + case ToFetch of + true -> + DocCtx = ensure_doc(Ctx0), + case DocCtx#ctx.doc of + {ok, Doc} -> + case DocCtx#ctx.vtag of + undefined -> + {true, RD, DocCtx}; + Vtag -> + MDs = riak_object:get_metadatas(Doc), + {lists:any( + fun(M) -> + dict:fetch(?MD_VTAG, M) =:= Vtag + end, + MDs), + RD, + DocCtx#ctx{vtag=Vtag}} + end; + {error, _} -> + %% This should never actually be reached because all the + %% error conditions from ensure_doc are handled up in + %% malformed_request. + {false, RD, DocCtx} end; - {error, _} -> - %% This should never actually be reached because all the error - %% conditions from ensure_doc are handled up in malformed_request. - {false, RD, DocCtx} + false -> + % Fake it - rather than fetch to see. If we're deleting we assume + % it does exist, and if PUT/POST, assume it doesn't + case Method of + 'DELETE' -> + {true, RD, Ctx0}; + _ -> + {false, RD, Ctx0} + end + end. + +-spec is_conflict(request_data(), context()) -> + {boolean(), request_data(), context()}. +is_conflict(RD, Ctx) -> + case {Ctx#ctx.method, wrq:get_req_header(?HEAD_IF_NOT_MODIFIED, RD)} of + {_ , undefined} -> + {false, RD, Ctx}; + {UpdM, NotModifiedClock} when UpdM =:= 'PUT'; UpdM =:= 'POST' -> + case Ctx#ctx.doc of + {ok, Obj} -> + InClock = + riak_object:decode_vclock( + base64:decode(NotModifiedClock)), + CurrentClock = + riak_object:vclock(Obj), + {not vclock:equal(InClock, CurrentClock), RD, Ctx}; + _ -> + {true, RD, Ctx} + end; + _ -> + {false, RD, Ctx} end. +-spec conditional_headers_present(request_data()) -> boolean(). +conditional_headers_present(RD) -> + NoneMatch = + (wrq:get_req_header("If-None-Match", RD) =/= undefined), + Match = + (wrq:get_req_header("If-Match", RD) =/= undefined), + UnModifiedSince = + (wrq:get_req_header("If-Unmodified-Since", RD) =/= undefined), + NotModified = + (wrq:get_req_header(?HEAD_IF_NOT_MODIFIED, RD) =/= undefined), + (NoneMatch or Match or UnModifiedSince or NotModified). + + -spec post_is_create(#wm_reqdata{}, context()) -> {boolean(), #wm_reqdata{}, context()}. %% @doc POST is considered a document-creation operation for bucket-level @@ -763,29 +861,40 @@ process_post(RD, Ctx) -> accept_doc_body(RD, Ctx). %% @doc Store the data the client is PUTing in the document. %% This function translates the headers and body of the HTTP request %% into their final riak_object() form, and executes the Riak put. -accept_doc_body(RD, Ctx=#ctx{bucket_type=T, bucket=B, key=K, client=C, links=L, index_fields=IF}) -> - Doc0 = case Ctx#ctx.doc of - {ok, D} -> D; - _ -> riak_object:new(riak_kv_wm_utils:maybe_bucket_type(T,B), K, <<>>) - end, +accept_doc_body( + RD, + Ctx=#ctx{ + bucket_type=T, bucket=B, key=K, client=C, + links=L, + index_fields=IF}) -> + Doc0 = riak_object:new(riak_kv_wm_utils:maybe_bucket_type(T,B), K, <<>>), VclockDoc = riak_object:set_vclock(Doc0, decode_vclock_header(RD)), {CType, Charset} = extract_content_type(RD), UserMeta = extract_user_meta(RD), CTypeMD = dict:store(?MD_CTYPE, CType, dict:new()), - CharsetMD = if Charset /= undefined -> - dict:store(?MD_CHARSET, Charset, CTypeMD); - true -> CTypeMD - end, - EncMD = case wrq:get_req_header(?HEAD_ENCODING, RD) of - undefined -> CharsetMD; - E -> dict:store(?MD_ENCODING, E, CharsetMD) - end, + CharsetMD = + if Charset /= undefined -> + dict:store(?MD_CHARSET, Charset, CTypeMD); + true -> + CTypeMD + end, + EncMD = + case wrq:get_req_header(?HEAD_ENCODING, RD) of + undefined -> CharsetMD; + E -> dict:store(?MD_ENCODING, E, CharsetMD) + end, LinkMD = dict:store(?MD_LINKS, L, EncMD), UserMetaMD = dict:store(?MD_USERMETA, UserMeta, LinkMD), IndexMD = dict:store(?MD_INDEX, IF, UserMetaMD), MDDoc = riak_object:update_metadata(VclockDoc, IndexMD), - Doc = riak_object:update_value(MDDoc, riak_kv_wm_utils:accept_value(CType, wrq:req_body(RD))), - Options0 = case wrq:get_qs_value(?Q_RETURNBODY, RD) of ?Q_TRUE -> [returnbody]; _ -> [] end, + Doc = + riak_object:update_value( + MDDoc, riak_kv_wm_utils:accept_value(CType, wrq:req_body(RD))), + Options0 = + case wrq:get_qs_value(?Q_RETURNBODY, RD) of + ?Q_TRUE -> [returnbody]; + _ -> [] + end, Options = make_options(Options0, Ctx), NoneMatch = (wrq:get_req_header("If-None-Match", RD) =/= undefined), Options2 = case riak_kv_util:consistent_object(B) and NoneMatch of @@ -815,7 +924,8 @@ send_returnbody(RD, DocCtx, _HasSiblings = false) -> %% multipart body, depending on what the client accepts. send_returnbody(RD, DocCtx, _HasSiblings = true) -> AcceptHdr = wrq:get_req_header("Accept", RD), - case webmachine_util:choose_media_type(["multipart/mixed", "text/plain"], AcceptHdr) of + PossibleTypes = ["multipart/mixed", "text/plain"], + case webmachine_util:choose_media_type(PossibleTypes, AcceptHdr) of "multipart/mixed" -> {Body, DocRD, DocCtx2} = produce_multipart_body(RD, DocCtx), {DocRD2, DocCtx3} = add_conditional_headers(DocRD, DocCtx2), @@ -832,9 +942,14 @@ send_returnbody(RD, DocCtx, _HasSiblings = true) -> add_conditional_headers(RD, Ctx) -> {ETag, RD2, Ctx2} = generate_etag(RD, Ctx), {LM, RD3, Ctx3} = last_modified(RD2, Ctx2), - RD4 = wrq:set_resp_header("ETag", webmachine_util:quoted_string(ETag), RD3), - RD5 = wrq:set_resp_header("Last-Modified", - httpd_util:rfc1123_date(calendar:universal_time_to_local_time(LM)), RD4), + RD4 = + wrq:set_resp_header( + "ETag", webmachine_util:quoted_string(ETag), RD3), + RD5 = + wrq:set_resp_header( + "Last-Modified", + httpd_util:rfc1123_date( + calendar:universal_time_to_local_time(LM)), RD4), {RD5,Ctx3}. -spec extract_content_type(#wm_reqdata{}) -> @@ -896,7 +1011,11 @@ multiple_choices(RD, Ctx) -> {false, RD, Ctx} end; multiple_choices -> - throw({unexpected_code_path, ?MODULE, multiple_choices, multiple_choices}) + throw( + {unexpected_code_path, + ?MODULE, + multiple_choices, + multiple_choices}) end. -spec produce_doc_body(#wm_reqdata{}, context()) -> @@ -918,33 +1037,45 @@ produce_doc_body(RD, Ctx) -> {ok, L} -> L; error -> [] end, - Links2 = riak_kv_wm_utils:format_links([{Bucket, "up"}|Links1], Prefix, APIVersion), + Links2 = + riak_kv_wm_utils:format_links( + [{Bucket, "up"}|Links1], Prefix, APIVersion), LinkRD = wrq:merge_resp_headers(Links2, RD), %% Add user metadata to response... UserMetaRD = case dict:find(?MD_USERMETA, MD) of {ok, UserMeta} -> - lists:foldl(fun({K,V},Acc) -> - wrq:merge_resp_headers([{K,V}],Acc) - end, - LinkRD, UserMeta); + lists:foldl( + fun({K,V},Acc) -> + wrq:merge_resp_headers([{K,V}],Acc) + end, + LinkRD, UserMeta); error -> LinkRD end, %% Add index metadata to response... - IndexRD = case dict:find(?MD_INDEX, MD) of - {ok, IndexMeta} -> - lists:foldl(fun({K,V}, Acc) -> - K1 = riak_kv_wm_utils:any_to_list(K), - V1 = riak_kv_wm_utils:any_to_list(V), - wrq:merge_resp_headers([{?HEAD_INDEX_PREFIX ++ K1, V1}], Acc) - end, - UserMetaRD, IndexMeta); - error -> UserMetaRD - end, - {riak_kv_wm_utils:encode_value(Doc), encode_vclock_header(IndexRD, Ctx), Ctx}; + IndexRD = + case dict:find(?MD_INDEX, MD) of + {ok, IndexMeta} -> + lists:foldl( + fun({K,V}, Acc) -> + K1 = riak_kv_wm_utils:any_to_list(K), + V1 = riak_kv_wm_utils:any_to_list(V), + wrq:merge_resp_headers( + [{?HEAD_INDEX_PREFIX ++ K1, V1}], Acc) + end, + UserMetaRD, IndexMeta); + error -> + UserMetaRD + end, + {riak_kv_wm_utils:encode_value(Doc), + encode_vclock_header(IndexRD, Ctx), Ctx}; multiple_choices -> - throw({unexpected_code_path, ?MODULE, produce_doc_body, multiple_choices}) + throw( + {unexpected_code_path, + ?MODULE, + produce_doc_body, + multiple_choices}) end. -spec produce_sibling_message_body(#wm_reqdata{}, context()) -> @@ -993,7 +1124,7 @@ select_doc(#ctx{doc={ok, Doc}, vtag=Vtag}) -> Mult -> case lists:dropwhile( fun({M,_}) -> - dict:fetch(?MD_VTAG, M) /= Vtag + dict:fetch(?MD_VTAG, M) /= Vtag end, Mult) of [Match|_] -> Match; @@ -1011,7 +1142,8 @@ encode_vclock_header(RD, #ctx{doc={ok, Doc}}) -> wrq:set_resp_header(Head, Val, RD); encode_vclock_header(RD, #ctx{doc={error, {deleted, VClock}}}) -> BinVClock = riak_object:encode_vclock(VClock), - wrq:set_resp_header(?HEAD_VCLOCK, binary_to_list(base64:encode(BinVClock)), RD). + wrq:set_resp_header( + ?HEAD_VCLOCK, binary_to_list(base64:encode(BinVClock)), RD). -spec decode_vclock_header(#wm_reqdata{}) -> vclock:vclock(). %% @doc Translate the X-Riak-Vclock header value from the request into @@ -1035,8 +1167,10 @@ ensure_doc(Ctx=#ctx{doc=undefined, bucket_type=T, bucket=B, key=K, client=C, basic_quorum=Quorum, notfound_ok=NotFoundOK}) -> case riak_kv_wm_utils:bucket_type_exists(T) of true -> - Options0 = [deletedvclock, {basic_quorum, Quorum}, - {notfound_ok, NotFoundOK}], + Options0 = + [deletedvclock, + {basic_quorum, Quorum}, + {notfound_ok, NotFoundOK}], Options = make_options(Options0, Ctx), BT = riak_kv_wm_utils:maybe_bucket_type(T,B), Ctx#ctx{doc=riak_client:get(BT, K, Options, C)}; @@ -1060,10 +1194,10 @@ delete_resource(RD, Ctx=#ctx{bucket_type=T, bucket=B, key=K, client=C}) -> riak_client:delete_vclock(BT, K, VC, Options, C) end, case Result of - {error, Reason} -> - handle_common_error(Reason, RD, Ctx); ok -> - {true, RD, Ctx} + {true, RD, Ctx}; + {error, Reason} -> + handle_common_error(Reason, RD, Ctx) end. -ifndef(old_hash). @@ -1094,9 +1228,10 @@ generate_etag(RD, Ctx) -> -spec last_modified(#wm_reqdata{}, context()) -> {undefined|calendar:datetime(), #wm_reqdata{}, context()}. %% @doc Get the last-modified time for this resource. -%% Documents will have the last-modified time specified by the riak_object. -%% For documents with siblings, this is the last-modified time of the latest -%% sibling. +%% Documents will have the last-modified time specified by the +%% riak_object. +%% For documents with siblings, this is the last-modified time of the +%% latest sibling. last_modified(RD, Ctx) -> case select_doc(Ctx) of {MD, _} -> @@ -1140,13 +1275,17 @@ get_link_heads(RD, Ctx) -> {BucketLinks, KeyLinks} = case APIVersion of 1 -> - {ok, BucketRegex} = re:compile("; ?rel=\"([^\"]+)\""), - {ok, KeyRegex} = re:compile("; ?riaktag=\"([^\"]+)\""), + {ok, BucketRegex} = + re:compile("= 2 -> - {ok, BucketRegex} = re:compile("; ?rel=\"([^\"]+)\""), - {ok, KeyRegex} = re:compile("; ?riaktag=\"([^\"]+)\""), + {ok, BucketRegex} = + re:compile(?V2_BUCKET_REGEX), + {ok, KeyRegex} = + re:compile(?V2_KEY_REGEX), extract_links(LinkHeaders1, BucketRegex, KeyRegex) end, diff --git a/src/riak_kv_wm_raw.hrl b/src/riak_kv_wm_raw.hrl index aeb7da4da8..08188ccd87 100644 --- a/src/riak_kv_wm_raw.hrl +++ b/src/riak_kv_wm_raw.hrl @@ -37,6 +37,7 @@ -define(HEAD_DELETED, "X-Riak-Deleted"). -define(HEAD_TIMEOUT, "X-Riak-Timeout"). -define(HEAD_CRDT_CONTEXT, "X-Riak-CRDT-Ctx"). +-define(HEAD_IF_NOT_MODIFIED, "X-Riak-If-Not-Modified"). %% Names of JSON fields in bucket properties -define(JSON_PROPS, <<"props">>).