Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Ruby to Erlang: An Experience Report

From Ruby to Erlang: An Experience Report

Chicago Erlang Factory Lite 2013

Christopher Meiklejohn

October 04, 2013
Tweet

More Decks by Christopher Meiklejohn

Other Decks in Programming

Transcript

  1. {erl_opts, [debug_info, warnings_as_errors, {parse_transform, lager_transform}]}. {edoc_opts, [{preprocess, true}]}. {deps, [

    {erlydtl, "0.7.0", {git, "git://github.com/evanmiller/erlydtl.git", {tag, "6a9845f"}}}, {node_package, "1.3.4", {git, "git://github.com/basho/node_package", {tag, "1.3.4"}}}, {lager, ".*", {git, "git://github.com/basho/lager", {tag, "1.2.2"}}}, {webmachine, ".*", {git, "git://github.com/basho/webmachine.git", {tag, "1.10.0"}}}, {erlcloud, ".*", {git, "git://github.com/basho/erlcloud.git", {tag, "0.4.1p1"}}} ]}. riak_cs_control/rebar.con!g Friday, October 4, 13
  2. init([]) -> riak_cs_control_configuration:configure_s3_connection(), {ok, #state{}}. get_users() -> gen_server:call(?MODULE, get_users, infinity).

    get_user(KeyId) -> gen_server:call(?MODULE, {get_user, KeyId}, infinity). handle_call(get_users, _From, State) -> case handle_request({multipart_get, "users"}) of {ok, Response} -> Users = riak_cs_control_formatting:format_users(Response), {reply, {ok, Users}, State}; Error -> {reply, Error, State} end; handle_call({get_user, KeyId}, _From, State) -> case handle_request({get, "user/" ++ KeyId}) of {ok, Response} -> User = riak_cs_control_formatting:format_user(Response), {reply, {ok, User}, State}; Error -> {reply, Error, State} end; riak_cs_control/src/riak_cs_control_session.erl Friday, October 4, 13
  3. %% @doc Format a user, by ensuring that all keys

    are atomized. format_user({struct, _} = Attributes) -> {struct, AttributeList} = atomize(Attributes), {struct, append_admin_status(AttributeList)}; format_user(Attributes) -> Attributes. %% @doc Given a proplist of attributes, add admin status. append_admin_status(Attributes) -> RiakCsAdminKey = riak_cs_control_configuration:cs_configuration( cs_admin_key), BinaryAdminKey = list_to_binary(RiakCsAdminKey), KeyId = proplists:get_value(key_id, Attributes), lists:append(Attributes, [{admin, KeyId =:= BinaryAdminKey}]). %% @doc Format an entire list of users. format_users(Users) -> [format_user(User) || User <- Users]. riak_cs_control/src/riak_cs_control_formatting.erl Friday, October 4, 13
  4. %% @doc Initialize the resource. init([]) -> {ok, #context{users=undefined,user=undefined}}. %%

    @doc Provide respones in JSON only. content_types_provided(ReqData, Context) -> {[{"application/json", to_json}], ReqData, Context}. %% @doc Validate CSRF token. forbidden(ReqData, Context) -> {riak_cs_control_security:is_protected(ReqData, Context), ReqData, Context}. %% @doc Support retrieval and creation of users. allowed_methods(ReqData, Context) -> {['HEAD', 'GET', 'POST'], ReqData, Context}. %% @doc Return true if we were able to retrieve the user. resource_exists(ReqData, Context) -> case maybe_retrieve_users(Context) of {true, NewContext} -> {true, ReqData, NewContext}; {false, Context} -> {false, ReqData, Context} end. riak_cs_control/src/riak_cs_control_wm_users.erl Friday, October 4, 13
  5. %% @doc Attempt to retrieve the users and store in

    the context if %% possible. maybe_retrieve_users(Context) -> case Context#context.users of undefined -> case riak_cs_control_session:get_users() of {ok, Response} -> {true, Context#context{users=Response}}; _ -> {false, Context} end; _Users -> {true, Context} end. %% @doc Return serialized users. to_json(ReqData, Context) -> case maybe_retrieve_users(Context) of {true, NewContext} -> Users = NewContext#context.users, Response = mochijson2:encode({struct, [{users, Users}]}), {Response, ReqData, NewContext}; {false, Context} -> Response = mochijson2:encode({struct, [{users, []}]}), {Response, ReqData, Context} end. riak_cs_control/src/riak_cs_control_wm_users.erl Friday, October 4, 13
  6. %% @doc Return a context which determines if we serve

    %% up the application template or a file resource. identify_resource(ReqData, #context{resource=undefined}=Context) -> case wrq:disp_path(ReqData) of "" -> {true, Context#context{resource=template, filename=undefined}}; _ -> Tokens = wrq:path_tokens(ReqData), Filename = normalize_filepath(Tokens), {true, Context#context{resource=filename, filename=Filename}} end; identify_resource(_ReqData, Context) -> {true, Context}. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13
  7. %% @doc If the file exists, allow it through, otherwise

    assume true if %% they are asking for the application template. resource_exists(ReqData, Context) -> case identify_resource(ReqData, Context) of {true, NewContext=#context{resource=template}} -> {true, ReqData, NewContext}; {true, NewContext=#context{resource=filename, filename=Filename}} -> case filelib:is_regular(Filename) of true -> {true, ReqData, NewContext}; _ -> {false, ReqData, NewContext} end end. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13
  8. %% @doc Return the proper content type of the file,

    or default to %% text/html. content_types_provided(ReqData, Context) -> case identify_resource(ReqData, Context) of {true, NewContext=#context{resource=filename, filename=Filename}} -> MimeType = webmachine_util:guess_mime(Filename), {[{MimeType, to_resource}], ReqData, NewContext}; {true, NewContext} -> {[{"text/html", to_resource}], ReqData, NewContext} end. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13
  9. %% @doc Return the application template, or the contents of

    a file %% resource. to_resource(ReqData, Context) -> case identify_resource(ReqData, Context) of {true, NewContext=#context{resource=template}} -> Token = riak_cs_control_security:csrf_token( ReqData, Context), {ok, Content} = application_dtl:render( [{csrf_token, Token}]), {Content, wrq:set_resp_header("Set-Cookie", "csrf_token="++Token++ "; httponly", ReqData), NewContext}; {true, NewContext=#context{resource=filename, filename=Filename}} -> {ok, Source} = file:read_file(Filename), {Source, ReqData, NewContext} end. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13
  10. {js_handlebars, [ {doc_root, "priv/www/js/templates"}, {out_dir, "priv/www/js"}, {target, "Ember.TEMPLATES"}, {compiler, "Ember.Handlebars.compile"},

    {templates, [{"generated/templates.js", [ "application.hbs", "users.hbs", "users/button.hbs", "users/new.hbs", "users/item.hbs", "users/index.hbs"]}]} ]}. riak_cs_control/rebar.con!g Friday, October 4, 13
  11. {js_concatenator, [ {doc_root, "priv/www/js/vendor"}, {out_dir, "priv/www/js"}, {concatenations, [{"generated/vendor.js", [ "jquery-1.7.2.js",

    "jquery-ui-1.8.16.custom.min.js", "spin-1.2.7.min.js", "handlebars.js", "ember-latest.js", "ember-data-latest.js", "minispade.js"], []}]} ]}. riak_cs_control/rebar.con!g Friday, October 4, 13
  12. ➜ riak_cs_control git:(master) rebar compile skip_deps=true ==> rel (compile) ==>

    riak_cs_control (compile) Compiled handlebars asset priv/www/js/generated/templates.js Friday, October 4, 13
  13. %% @doc Validate CSRF token. forbidden(ReqData, Context) -> {riak_cs_control_security:is_protected(ReqData, Context),

    ReqData, Context}. riak_cs_control/src/riak_cs_control_wm_user.erl Friday, October 4, 13
  14. %% @doc Get the CSRF token from the cookie. -spec

    get_csrf_token(reqdata(), context()) -> csrf_token(). get_csrf_token(ReqData, _Context) -> wrq:get_cookie_value("csrf_token", ReqData). %% @doc Ensure this request contains a valid csrf protection token. -spec is_valid_csrf_token(reqdata(), context()) -> boolean(). is_valid_csrf_token(ReqData, Context) -> HeaderToken = wrq:get_req_header("X-CSRF-Token", ReqData), CookieToken = get_csrf_token(ReqData, Context), HeaderToken /= undefined andalso HeaderToken == CookieToken. %% @doc Is this a protected method? -spec is_protected_method(reqdata()) -> boolean(). is_protected_method(ReqData) -> Method = wrq:method(ReqData), Method == 'POST' orelse Method == 'PUT'. %% @doc Is this a protected? -spec is_protected(reqdata(), context()) -> boolean(). is_protected(ReqData, Context) -> (is_null_origin(ReqData) or not is_valid_csrf_token(ReqData, Context)) and is_protected_method(ReqData). riak_cs_control/src/riak_cs_control_security.erl Friday, October 4, 13
  15. -module(fib). -export([fib/1]). -include_lib("eunit/include/eunit.hrl"). fib(0) -> 1; fib(1) -> 1; fib(N)

    when N > 1 -> fib(N-1) + fib(N-2). fib_test_() -> [?_assert(fib(0) =:= 1), ?_assert(fib(1) =:= 1), ?_assert(fib(2) =:= 2), ?_assert(fib(3) =:= 3), ?_assert(fib(4) =:= 5), ?_assert(fib(5) =:= 8), ?_assertException(error, function_clause, fib(-1)), ?_assert(fib(31) =:= 2178309)]. Friday, October 4, 13
  16. -module(test). -compile([export_all]). is_ok(Symbol) -> case Symbol of {ok, _} ->

    true; {error, _} -> false end. check() -> is_ok(ok). Friday, October 4, 13
  17. Checking whether the PLT /Users/cmeiklejohn/.dialyzer_plt is up-to- date... yes Proceeding

    with analysis... test.erl:13: Function check/0 has no local return test.erl:14: The call test:is_ok('ok') will never return since it differs in the 1st argument from the success typing arguments: ({'error',_} | {'ok',_}) done in 0m0.60s -module(test). -compile([export_all]). is_ok(Symbol) -> case Symbol of {ok, _} -> true; {error, _} -> false end. check() -> is_ok(ok). Friday, October 4, 13
  18. prop_reverse() -> ?FORALL(X, %% Binding list(int()), %% Generator %% Property

    lists:reverse(lists:reverse(X)) == X ). Friday, October 4, 13