|
@@ -0,0 +1,102 @@
|
|
|
+-module(n4u_multipart).
|
|
|
+
|
|
|
+-export([init/3, handle/2, terminate/3]).
|
|
|
+
|
|
|
+-export([multipart/2, multipart/3, stream_file/4,
|
|
|
+ temp_filename/0, data_payload/2, file_payload/4]).
|
|
|
+
|
|
|
+
|
|
|
+-define(MAX_FILE_SIZE_LIMIT, 900 * 1000 * 1000). % 300Kb
|
|
|
+-define(TMP_PATH, ".").
|
|
|
+
|
|
|
+
|
|
|
+% test multipart
|
|
|
+% curl -F file=file.pdf http://localhost:8000/multipart
|
|
|
+
|
|
|
+
|
|
|
+init(_Type, Req, []) ->
|
|
|
+ {ok, Req, undefined}.
|
|
|
+
|
|
|
+
|
|
|
+handle(Req, State) ->
|
|
|
+ {_Method, Req2} = cowboy_req:method(Req),
|
|
|
+ {B, ReqU} = case multipart(Req2, ?MODULE) of
|
|
|
+ {ok, ReqM} -> {<<"<h1>This is a response for POST</h1>">>, ReqM};
|
|
|
+ {rejected, file_size_limit, ReqM} -> {<<"POST: File Size Limit">>, ReqM}
|
|
|
+ end,
|
|
|
+ {ok, Req3} = cowboy_req:reply(200, [], B, ReqU),
|
|
|
+ {ok, Req3, State}.
|
|
|
+
|
|
|
+
|
|
|
+terminate(_Reason, _Req, _State) ->
|
|
|
+ ok.
|
|
|
+
|
|
|
+
|
|
|
+multipart(Req, C) ->
|
|
|
+ multipart(Req, C, ?MAX_FILE_SIZE_LIMIT).
|
|
|
+
|
|
|
+multipart(Req, C, Max_FileSize_Limit) when erlang:is_atom(C) and erlang:is_integer(Max_FileSize_Limit) ->
|
|
|
+ case cowboy_req:part(Req) of % todo check cowboy changes (1.0.1 to 2.9.0)
|
|
|
+ {ok, Headers, Req2} ->
|
|
|
+ Req5 = case cow_multipart:form_data(Headers) of
|
|
|
+ {data, Field_Name} ->
|
|
|
+ {ok, Body, Req3} = cowboy_req:part_body(Req2),
|
|
|
+ ok = C:data_payload(Field_Name, Body),
|
|
|
+ Req3;
|
|
|
+
|
|
|
+ {file, Field_Name, Filename, _CType, _CTransferEncoding} ->
|
|
|
+ Temp_Filename = temp_filename(),
|
|
|
+ {ok, IoDevice} = file:open(Temp_Filename, [raw, write]),
|
|
|
+ Rsf = stream_file(Req2, IoDevice, 0, Max_FileSize_Limit),
|
|
|
+ ok = file:close(IoDevice),
|
|
|
+
|
|
|
+ case Rsf of
|
|
|
+ {ok, File_Size, Req4} ->
|
|
|
+ ok = C:file_payload(Field_Name, Filename, Temp_Filename, File_Size),
|
|
|
+ Req4;
|
|
|
+
|
|
|
+ {limit, Reason, Req4} ->
|
|
|
+ error_logger:warning_msg("Upload limit detected! Type: ~p, FieldName: ~p, Filename: ~p,~nReq: ~p~n",
|
|
|
+ [Reason, Field_Name, Filename, Req4]),
|
|
|
+ ok = file:delete(Temp_Filename),
|
|
|
+ Req4
|
|
|
+ end
|
|
|
+ end,
|
|
|
+ multipart(Req5, C, Max_FileSize_Limit);
|
|
|
+
|
|
|
+ {done, Req2} ->
|
|
|
+ {ok, Req2}
|
|
|
+ end.
|
|
|
+
|
|
|
+
|
|
|
+stream_file(Req, IoDevice, File_Size, Max_FileSize_Limit) ->
|
|
|
+ {Control, Data, Req2} = cowboy_req:part_body(Req),
|
|
|
+ New_FileSize = erlang:byte_size(Data) + File_Size,
|
|
|
+ case New_FileSize > Max_FileSize_Limit of
|
|
|
+ true -> {limit, file_size, Req2};
|
|
|
+ false ->
|
|
|
+ ok = file:write(IoDevice, Data),
|
|
|
+ case Control of
|
|
|
+ ok -> {ok, New_FileSize, Req2};
|
|
|
+ more -> stream_file(Req2, IoDevice, New_FileSize, Max_FileSize_Limit)
|
|
|
+ end
|
|
|
+ end.
|
|
|
+
|
|
|
+
|
|
|
+temp_filename() ->
|
|
|
+ erlang:list_to_binary(filename:join(
|
|
|
+ [?TMP_PATH, erlang:atom_to_list(?MODULE) ++
|
|
|
+ erlang:integer_to_list( erlang:phash2(erlang:make_ref()) ) ] )).
|
|
|
+
|
|
|
+
|
|
|
+data_payload(_Field_Name, _Body) ->
|
|
|
+ %error_logger:info_msg("DATA PAYLOAD {Field_Name, Body} = {~p, ~p}", [_Field_Name, _Body]),
|
|
|
+ ok.
|
|
|
+
|
|
|
+
|
|
|
+file_payload(_Field_Name, _File_Name, Temp_Filename, _File_Size) ->
|
|
|
+ New_File_Name = <<"/tmp/upload.jpg">>, % todo think and change
|
|
|
+ % error_logger:info_msg("FILE PAYLOAD: {~p, ~p, ~p}", [_Field_Name, _File_Name, Temp_Filename]),
|
|
|
+ {ok, _Bytes_Copied} = file:copy(Temp_Filename, New_File_Name),
|
|
|
+ ok.
|
|
|
+
|