Namdak Tonpa 5 лет назад
Родитель
Сommit
ccd0e993f1
5 измененных файлов с 85 добавлено и 46 удалено
  1. 6 6
      README.md
  2. 1 1
      src/rest.app.src
  3. 4 3
      src/rest_app.erl
  4. 60 36
      src/rest_cowboy.erl
  5. 14 0
      src/users.erl

+ 6 - 6
README.md

@@ -47,12 +47,12 @@ post(Data) -> post(from_json(Data, #user{})).
 Usage
 -----
 
-    curl -i -X POST -d "id=vlad" localhost:8000/rest/users
-    curl -i -X POST -d "id=doxtop" localhost:8000/rest/users
-    curl -i -X GET localhost:8000/rest/users
-    curl -i -X PUT -d "id=5HT" localhost:8000/rest/users/vlad
-    curl -i -X GET localhost:8000/rest/users/5HT
-    curl -i -X DELETE localhost:8000/rest/users/5HT
+    curl -i -X POST -d "id=vlad" localhost:8005/rest/users
+    curl -i -X POST -d "id=doxtop" localhost:8005/rest/users
+    curl -i -X GET localhost:8005/rest/users
+    curl -i -X PUT -d "id=5HT" localhost:8005/rest/users/vlad
+    curl -i -X GET localhost:8005/rest/users/5HT
+    curl -i -X DELETE localhost:8005/rest/users/5HT
 
 Credits
 -------

+ 1 - 1
src/rest.app.src

@@ -1,7 +1,7 @@
 {application, rest, [
     {description,  "REST Yoctoframework"},
     {vsn,          "5.10.1"},
-    {applications, [kernel, stdlib]},
+    {applications, [public_key,asn1,kernel,stdlib,ranch,cowboy,syntax_tools,compiler,n2o]},
     {modules, []},
     {registered,   []},
     {mod, { rest_app, []}},

+ 4 - 3
src/rest_app.erl

@@ -3,11 +3,12 @@
 -behaviour(supervisor).
 -export([init/1,start/2, stop/1]).
 
-init([]) -> {ok, {{one_for_one, 5, 10}, []}}.
+init([]) ->
+   users:init(),
+   cowboy:start_clear(http, [{port,8005}], #{env=>#{dispatch=> points() }}),
+   {ok, {{one_for_one, 5, 10}, []}}.
 stop(_State) -> ok.
 start(_StartType, _StartArgs) ->
-   cowboy:start_tls(http,n2o_cowboy:env(rest),
-                 #{env=>#{dispatch=> points() }}),
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
 
 points() -> cowboy_router:compile([{'_', [

+ 60 - 36
src/rest_cowboy.erl

@@ -1,40 +1,46 @@
 -module(rest_cowboy).
 -author('Dmitry Bushmelev').
 -record(st, {resource_module = undefined :: atom(), resource_id = undefined :: binary()}).
--export([init/3, rest_init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
+-export([init/2, rest_init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
          to_html/2, to_json/2, content_types_accepted/2, delete_resource/2,
          handle_urlencoded_data/2, handle_json_data/2]).
 
-init(_, _, _) -> {upgrade, protocol, cowboy_rest}.
+init(Req, Opts) -> {cowboy_rest, Req, Opts}.
 
 -ifndef(REST_JSON).
 -define(REST_JSON, (application:get_env(rest,json,jsone))).
 -endif.
 
+c(X) -> list_to_atom(binary_to_list(X)).
+
 rest_init(Req, _Opts) ->
     {Resource, Req1} = cowboy_req:binding(resource, Req),
     Module = case rest_module(Resource) of {ok, M} -> M; _ -> undefined end,
     {Id, Req2} = cowboy_req:binding(id, Req1),
     {Origin, Req3} = cowboy_req:header(<<"origin">>, Req2, <<"*">>),
     Req4 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>, Origin, Req3),
+    io:format("REST INIT~p"),
     {ok, Req4, #st{resource_module = Module, resource_id = Id}}.
 
-resource_exists(Req, #st{resource_module = undefined} = State)       -> {false, Req, State};
-resource_exists(Req, #st{resource_id     = <<"undefined">>} = State)       -> {true, Req, State};
-resource_exists(Req, #st{resource_module = M, resource_id = Id} = S) -> {M:exists(Id), Req, S}.
+resource_exists(#{bindings := #{resource := _}} = Req, State) -> {false, Req, State};
+resource_exists(#{bindings := #{id := _}} = Req, State) -> {true, Req, State};
+resource_exists(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    M = c(Module),
+    {M:exists(Id), Req, State}.
 
 allowed_methods(Req, #st{resource_id = <<"undefined">>} = State) -> {[<<"GET">>, <<"POST">>], Req, State};
 allowed_methods(Req, State)                                -> {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}.
 
-content_types_provided(Req, #st{resource_module = M} = State) ->
-    {case erlang:function_exported(M, to_html, 1) of
-         true  -> [{<<"text/html">>, to_html}, {<<"application/json">>, to_json}];
-         false -> [{<<"application/json">>, to_json}] end,
+content_types_provided(#{bindings := #{resource := Module}} = Req, State) ->
+    {case erlang:function_exported(c(Module), to_html, 1) of
+         false -> [{<<"application/json">>, to_json}];
+         true  -> [{<<"text/html">>, to_html}, {<<"application/json">>, to_json}] end,
      Req, State}.
 
-to_html(Req, #st{resource_module = M, resource_id = Id} = State) ->
+to_html(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    M = c(Module),
     Body = case Id of
-               <<"undefined">> -> [M:to_html(Resource) || Resource <- M:get()];
+               undefined -> [M:to_html(Resource) || Resource <- M:get()];
                _ -> M:to_html(M:get(Id)) end,
     Html = case erlang:function_exported(M, html_layout, 2) of
                true  -> M:html_layout(Req, Body);
@@ -43,37 +49,53 @@ to_html(Req, #st{resource_module = M, resource_id = Id} = State) ->
 
 default_html_layout(Body) -> [<<"<html><body>">>, Body, <<"</body></html>">>].
 
-to_json(Req, #st{resource_module = M, resource_id = Id} = State) ->
+to_json(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    M = c(Module),
     Struct = case Id of
-                 <<"undefined">> -> [{M, [ M:to_json(Resource) || Resource <- M:get() ] } ];
+                 undefined -> [{M, [ M:to_json(Resource) || Resource <- M:get() ] } ];
                  _         -> M:to_json(M:get(Id)) end,
     {iolist_to_binary(?REST_JSON:encode(Struct)), Req, State}.
 
-content_types_accepted(Req, State) -> {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
-                                        {<<"application/json">>, handle_json_data}], Req, State}.
+content_types_accepted(Req, State) ->
+  {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
+    {<<"application/json">>, handle_json_data}], Req, State}.
+
+handle_urlencoded_data(#{bindings := #{resource := Module}} = Req0, State) ->
+    {ok, Data1, Req} = cowboy_req:read_urlencoded_body(Req0),
+    io:format("FORM: ~p, Data1: ~p~n",[Module,Data1]),
+    {handle_data(c(Module), [], Data1, Req), Req, State};
 
-handle_urlencoded_data(Req, #st{resource_module = M, resource_id = Id} = State) ->
-    {ok, Data, Req2} = cowboy_req:body_qs(Req),
-    {handle_data(M, Id, Data), Req2, State}.
+handle_urlencoded_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    {ok, Data, Req2} = cowboy_req:read_urlencoded_body(Req),
+    io:format("FORM: ~p~n",[Data]),
+    {handle_data(c(Module), Id, Data, Req), Req2, State}.
+
+handle_json_data(#{bindings := #{resource := Module}} = Req, State) ->
+    {ok, Binary, Req2} = cowboy_req:read_body(Req),
+    io:format("JSON: ~p~n",[Binary]),
+    Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
+    {handle_data(c(Module), [], Data, Req), Req2, State};
 
-handle_json_data(Req, #st{resource_module = M, resource_id = Id} = State) ->
-    {ok, Binary, Req2} = cowboy_req:body(Req),
+handle_json_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+    {ok, Binary, Req2} = cowboy_req:read_body(Req),
+    io:format("JSON: ~p~n",[Binary]),
     Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
-    {handle_data(M, Id, Data), Req2, State}.
+    {handle_data(c(Module), Id, Data, Req), Req2, State}.
 
-handle_data(Mod, Id, Data) ->
+handle_data(Mod, Id, Data, Req) ->
     Valid = case erlang:function_exported(Mod, validate, 2) of
                 true  -> Mod:validate(Id, Data);
-                false -> default_validate(Mod, Id, Data) end,
+                false -> default_validate(Mod, Id, Data, Req) end,
     case {Valid, Id} of
         {false, _}         -> false;
         {true,  <<"undefined">>} -> Mod:post(Data);
         {true,  _}         -> case erlang:function_exported(Mod, put, 2) of
                                   true  -> Mod:put(Id, Data);
-                                  false -> default_put(Mod, Id, Data) end
+                                  false -> default_put(Mod, Id, Data, Req) end
     end.
 
-default_put(Mod, Id, Data) ->
+default_put(Mod, Id, Data, Req) when is_map(Data) -> default_put(Mod, Id, maps:to_list(Data), Req);
+default_put(Mod, Id, Data, Req) ->
     NewRes = Mod:from_json(Data, Mod:get(Id)),
     NewId = proplists:get_value(id, Mod:to_json(NewRes)),
     case Id =/= NewId of
@@ -81,21 +103,23 @@ default_put(Mod, Id, Data) ->
         false -> true end,
     Mod:post(NewRes).
 
-default_validate(Mod, Id, Data) ->
+default_validate(Mod, Id, DataX, Req0) when is_map(DataX) -> default_validate(Mod, Id, maps:to_list(DataX), Req0);
+default_validate(Mod, Id, Data, Req0) ->
     Allowed = case erlang:function_exported(Mod, keys_allowed, 1) of
-                  true  -> Mod:keys_allowed(proplists:get_keys(Data));
-                  false -> true end,
+      true  -> Mod:keys_allowed(proplists:get_keys(Data));
+      false -> true end,
     validate_match(Mod, Id, Allowed, proplists:get_value(<<"id">>, Data)).
 
-validate_match(_Mod, <<"undefined">>, true, <<"undefined">>) -> false;
-validate_match(_Mod, <<"undefined">>, true, <<"">>)    -> false;
-validate_match( Mod, <<"undefined">>, true, NewId)     -> not Mod:exists(NewId);
-validate_match(_Mod,       _Id, true, <<"undefined">>) -> true;
-validate_match(_Mod,        Id, true, Id)        -> true;
-validate_match( Mod,       _Id, true, NewId)     -> not Mod:exists(NewId);
-validate_match(   _,         _,    _, _)         -> false.
+validate_match(_Mod,  [], true, [])      -> false;
+validate_match( Mod,  [], true, NewId)   -> not Mod:exists(NewId);
+validate_match(_Mod, _Id, true, [])      -> true;
+validate_match(_Mod,  Id, true, Id)      -> true;
+validate_match( Mod, _Id, true, NewId)   -> not Mod:exists(NewId);
+validate_match(   _,         _,    _, _) -> false.
 
-delete_resource(Req,  #st{resource_module = M, resource_id = Id} = State) -> {M:delete(Id), Req, State}.
+delete_resource(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+   M = c(Module),
+   {M:delete(Id), Req, State}.
 
 rest_module(Module) when is_binary(Module) -> rest_module(binary_to_list(Module));
 rest_module(Module) ->

+ 14 - 0
src/users.erl

@@ -0,0 +1,14 @@
+-module(users).
+-compile({parse_transform, rest}).
+-record(user, {id,cn,name,type}).
+-export([init/0, populate/1, exists/1, get/0, get/1, post/1, delete/1]).
+-rest_record(user).
+
+init()               -> ets:new(users, [public, named_table, {keypos, #user.id}]).
+populate(Users)      -> ets:insert(users, Users).
+exists(Id)           -> ets:member(users, n2o:to_binary(Id)).
+get()                -> ets:tab2list(users).
+get(Id)              -> #user{id=Id}.
+delete(Id)           -> ets:delete(users, n2o:to_binary(Id)).
+post(#user{} = User) -> ets:insert(users, User);
+post(Data)           -> post(from_json(Data, #user{})).