justin / webmachine (http://blog.therestfulway.com/)

A REST-based system for building web applications.

Clone this repository (size: 2.6 MB): HTTPS / SSH
$ hg clone http://bitbucket.org/justin/webmachine/
commit 113: 0b8aac8fa334
parent 112: 9957a15e5779
branch: default
tags: tip
some webmachine_util tests
Bryan Fink
3 months ago
webmachine / demo / src / demo_fs_resource.erl
    #   Introduced
1
e68ae3bcc090
%% @author Bryan Fink <bryan@basho.com>
2
e68ae3bcc090
%% @author Andy Gross <andy@basho.com>
3
e68ae3bcc090
%% @author Justin Sheehy <justin@basho.com>
4
e68ae3bcc090
%% @copyright 2008-2009 Basho Technologies, Inc.
5
e68ae3bcc090
6
e68ae3bcc090
-module(demo_fs_resource).
7
e68ae3bcc090
-export([init/1]).
8
e68ae3bcc090
-export([allowed_methods/2,
9
e68ae3bcc090
	 resource_exists/2,
10
e68ae3bcc090
	 last_modified/2,
11
e68ae3bcc090
	 content_types_provided/2,
12
e68ae3bcc090
	 content_types_accepted/2,
13
e68ae3bcc090
         delete_resource/2,
14
e68ae3bcc090
         post_is_create/2,
15
e68ae3bcc090
         create_path/2,
16
e68ae3bcc090
	 provide_content/2,
17
e68ae3bcc090
	 accept_content/2,
18
e68ae3bcc090
	 generate_etag/2]).
19
e68ae3bcc090
20
e68ae3bcc090
-record(context, {root,response_body=undefined,metadata=[]}).
21
e68ae3bcc090
22
e68ae3bcc090
-include_lib("kernel/include/file.hrl").
23
e68ae3bcc090
-include_lib("webmachine/include/webmachine.hrl").
24
e68ae3bcc090
25
e68ae3bcc090
init(ConfigProps) ->
26
e68ae3bcc090
    {root, Root} = proplists:lookup(root, ConfigProps),
27
e68ae3bcc090
    {ok, #context{root=Root}}.
28
e68ae3bcc090
    
29
e68ae3bcc090
allowed_methods(ReqData, Context) ->
30
e68ae3bcc090
    {['HEAD', 'GET', 'PUT', 'DELETE', 'POST'], ReqData, Context}.
31
e68ae3bcc090
32
e68ae3bcc090
file_path(Context, Name) ->
33
e68ae3bcc090
    RelName = case hd(Name) of
34
e68ae3bcc090
        "/" -> tl(Name);
35
e68ae3bcc090
        _ -> Name
36
e68ae3bcc090
    end,
37
e68ae3bcc090
    filename:join([Context#context.root, RelName]).
38
e68ae3bcc090
39
e68ae3bcc090
file_exists(Context, Name) ->
40
e68ae3bcc090
    NamePath = file_path(Context, Name),
41
e68ae3bcc090
    case filelib:is_regular(NamePath) of 
42
e68ae3bcc090
	true ->
43
e68ae3bcc090
	    {true, NamePath};
44
e68ae3bcc090
	false ->
45
e68ae3bcc090
	    false
46
e68ae3bcc090
    end.
47
e68ae3bcc090
48
e68ae3bcc090
resource_exists(ReqData, Context) ->
49
e68ae3bcc090
    Path = wrq:disp_path(ReqData),
50
e68ae3bcc090
    case file_exists(Context, Path) of 
51
e68ae3bcc090
	{true, _} ->
52
e68ae3bcc090
	    {true, ReqData, Context};
53
e68ae3bcc090
	_ ->
54
e68ae3bcc090
            case Path of
55
e68ae3bcc090
                "p" -> {true, ReqData, Context};
56
e68ae3bcc090
                _ -> {false, ReqData, Context}
57
e68ae3bcc090
            end
58
e68ae3bcc090
    end.
59
e68ae3bcc090
60
e68ae3bcc090
maybe_fetch_object(Context, Path) ->
61
e68ae3bcc090
    % if returns {true, NewContext} then NewContext has response_body
62
e68ae3bcc090
    case Context#context.response_body of
63
e68ae3bcc090
	undefined ->
64
e68ae3bcc090
	    case file_exists(Context, Path) of 
65
e68ae3bcc090
		{true, FullPath} ->
66
e68ae3bcc090
		    {ok, Value} = file:read_file(FullPath),
67
e68ae3bcc090
		    {true, Context#context{response_body=Value}};
68
e68ae3bcc090
		false ->
69
e68ae3bcc090
		    {false, Context}
70
e68ae3bcc090
	    end;
71
e68ae3bcc090
	_Body ->
72
e68ae3bcc090
	    {true, Context}
73
e68ae3bcc090
    end.
74
e68ae3bcc090
75
e68ae3bcc090
content_types_provided(ReqData, Context) ->
76
e68ae3bcc090
    CT = webmachine_util:guess_mime(wrq:disp_path(ReqData)),
77
e68ae3bcc090
    {[{CT, provide_content}], ReqData,
78
e68ae3bcc090
     Context#context{metadata=[{'content-type', CT}|Context#context.metadata]}}.
79
e68ae3bcc090
80
e68ae3bcc090
content_types_accepted(ReqData, Context) ->
81
e68ae3bcc090
    CT = case wrq:get_req_header("content-type", ReqData) of
82
e68ae3bcc090
             undefined -> "application/octet-stream";
83
e68ae3bcc090
             X -> X
84
e68ae3bcc090
         end,
85
e68ae3bcc090
    {MT, _Params} = webmachine_util:media_type_to_detail(CT),
86
e68ae3bcc090
    {[{MT, accept_content}], ReqData,
87
e68ae3bcc090
     Context#context{metadata=[{'content-type', MT}|Context#context.metadata]}}.
88
e68ae3bcc090
89
e68ae3bcc090
accept_content(ReqData, Context) ->
90
e68ae3bcc090
    Path = wrq:disp_path(ReqData),
91
e68ae3bcc090
    FP = file_path(Context, Path),
92
e68ae3bcc090
    ok = filelib:ensure_dir(filename:dirname(FP)),
93
e68ae3bcc090
    ReqData1 = case file_exists(Context, Path) of 
94
e68ae3bcc090
	{true, _} ->
95
e68ae3bcc090
            ReqData;
96
e68ae3bcc090
	_ ->
97
e68ae3bcc090
            LOC = "http://" ++
98
e68ae3bcc090
                   wrq:get_req_header("host", ReqData) ++
99
e68ae3bcc090
                   "/fs/" ++ Path,
100
e68ae3bcc090
            wrq:set_resp_header("Location", LOC, ReqData)
101
e68ae3bcc090
    end,
102
e68ae3bcc090
    Value = wrq:req_body(ReqData1),
103
e68ae3bcc090
    case file:write_file(FP, Value) of
104
e68ae3bcc090
        ok ->
105
e68ae3bcc090
            {true, wrq:set_resp_body(Value, ReqData1), Context};
106
e68ae3bcc090
        Err ->
107
e68ae3bcc090
            {{error, Err}, ReqData1, Context}
108
e68ae3bcc090
    end.    
109
e68ae3bcc090
110
e68ae3bcc090
post_is_create(ReqData, Context) ->
111
e68ae3bcc090
    {true, ReqData, Context}.
112
e68ae3bcc090
113
e68ae3bcc090
create_path(ReqData, Context) ->
114
e68ae3bcc090
    case wrq:get_req_header("slug", ReqData) of
115
e68ae3bcc090
        undefined -> {undefined, ReqData, Context};
116
e68ae3bcc090
        Slug ->
117
e68ae3bcc090
            case file_exists(Context, Slug) of
118
e68ae3bcc090
                {true, _} -> {undefined, ReqData, Context};
119
e68ae3bcc090
                _ -> {Slug, ReqData, Context}
120
e68ae3bcc090
            end
121
e68ae3bcc090
    end.
122
e68ae3bcc090
123
e68ae3bcc090
delete_resource(ReqData, Context) ->
124
e68ae3bcc090
    case file:delete(file_path(
125
e68ae3bcc090
                       Context, wrq:disp_path(ReqData))) of
126
e68ae3bcc090
        ok -> {true, ReqData, Context};
127
e68ae3bcc090
        _ -> {false, ReqData, Context}
128
e68ae3bcc090
    end.
129
e68ae3bcc090
130
e68ae3bcc090
provide_content(ReqData, Context) ->
131
e68ae3bcc090
    case maybe_fetch_object(Context, wrq:disp_path(ReqData)) of 
132
e68ae3bcc090
	{true, NewContext} ->
133
e68ae3bcc090
	    Body = NewContext#context.response_body,
134
e68ae3bcc090
	    {Body, ReqData, Context};
135
e68ae3bcc090
	{false, NewContext} ->
136
e68ae3bcc090
	    {error, ReqData, NewContext}
137
e68ae3bcc090
    end.
138
e68ae3bcc090
139
e68ae3bcc090
last_modified(ReqData, Context) ->
140
e68ae3bcc090
    {true, FullPath} = file_exists(Context,
141
e68ae3bcc090
                                   wrq:disp_path(ReqData)),
142
e68ae3bcc090
    LMod = filelib:last_modified(FullPath),
143
e68ae3bcc090
    {LMod, ReqData, Context#context{metadata=[{'last-modified',
144
e68ae3bcc090
                    httpd_util:rfc1123_date(LMod)}|Context#context.metadata]}}.
145
e68ae3bcc090
146
e68ae3bcc090
hash_body(Body) -> mochihex:to_hex(binary_to_list(crypto:sha(Body))).
147
e68ae3bcc090
148
e68ae3bcc090
generate_etag(ReqData, Context) ->
149
e68ae3bcc090
    case maybe_fetch_object(Context, wrq:disp_path(ReqData)) of
150
e68ae3bcc090
        {true, BodyContext} ->
151
e68ae3bcc090
            ETag = hash_body(BodyContext#context.response_body),
152
e68ae3bcc090
            {ETag, ReqData,
153
e68ae3bcc090
             BodyContext#context{metadata=[{etag,ETag}|
154
e68ae3bcc090
                                           BodyContext#context.metadata]}};
155
e68ae3bcc090
        _ ->
156
e68ae3bcc090
            {undefined, ReqData, Context}
157
e68ae3bcc090
    end.