Skip to content

Instantly share code, notes, and snippets.

@sumerman
Created April 17, 2011 12:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sumerman/923995 to your computer and use it in GitHub Desktop.
Save sumerman/923995 to your computer and use it in GitHub Desktop.
-module(head_srv).
-compile(export_all).
%-export([start/1, benchmark/1, console_start/0, loop/1]).
-include("jsonerl/jsonerl.hrl").
-record(command, {action, error, login, desc}).
-record(logic_state, {ies = none}).
-define(INTERNAL_TIMEOUT, 10000).
-define(LISTEN_PORT, 6070).
-define(TCP_OPTS, [binary, {packet, line}, {nodelay, true}, {reuseaddr, true}, {active, false}, {backlog, 500}]).
-define(URL, "http://localhost:5984/clients/_design/bundles/_list/merge/purchased?key=").
-define(BASE_DIR, "./").
%%%
%%% Actual-work functions
%%%
traverse1(I) ->
case filelib:is_dir(I) of
true ->
{ok, L} = file:list_dir(I),
F = fun
([$.|_]) -> []; % omit unix hidden files ('.*')
(X) -> traverse1(filename:join([I, X]))
end,
lists:map(F, L);
false -> [{I}]
end.
traverse(I) ->
lists:map(fun({X}) -> X end, lists:flatten(traverse1(I))).
read_file(F) ->
case file:read_file(F) of
{ok, B} -> B;
_ -> <<>>
end.
dir_to_data(Dir) ->
lists:map(fun(F) -> {list_to_binary(F), read_file(F)} end, traverse(Dir)).
interactives_list(Username) ->
URL = lists:flatten(io_lib:format("~s\"~s\"", [?URL, Username])),
case httpc:request(URL) of
{ok, {{_, Code, _}, _, Data}} when Code == 200 -> {ok, jsonerl:decode(Data)};
_ -> {error, "Unable to fetch ies"}
end.
%%%
%%% Generic tcp serving code
%%%
listen(Port, FLogic) ->
{ok, Listen} = gen_tcp:listen(Port, ?TCP_OPTS),
start_accepts(10, Listen, FLogic).
start_accepts(0, _, _) ->
ok;
start_accepts(N, Listen, FLogic) ->
spawn(fun() -> accept(Listen, FLogic) end),
start_accepts(N-1, Listen, FLogic).
fork_logic(FLogic) ->
process_flag(trap_exit, true),
spawn_link(FLogic).
accept(Listen, FLogic) ->
case gen_tcp:accept(Listen) of
{ok, Socket} ->
spawn(fun() -> accept(Listen, FLogic) end),
loop(Socket, fork_logic(FLogic));
_ ->
void
end.
loop(Socket, Pid) ->
inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Line} ->
% TODO protocol function should be param
gen_tcp:send(Socket, handle_head_protocol_msg(Pid, Line)),
loop(Socket, Pid);
{tcp_closed, Socket} ->
exit(Pid, shutdown);
{'EXIT', _, shutdown} ->
exit(Pid, shutdown);
Any ->
io:format("What a fuck? ~w ~n", [Any]),
loop(Socket, Pid)
end.
%%%
%%% High level protocol
%%%
decode_msg(Msg) ->
Decoded = ?json_to_record(command, Msg),
#command{action = Action} = Decoded,
{binary_to_atom(Action, latin1), Decoded}.
encode_success(Action, Res) ->
jsonerl:encode({{Action, {{results, [Res]}}}}) ++ "\n".
encode_fail(Why)->
jsonerl:encode({{error, list_to_binary(Why)}}) ++ "\n".
handle_head_protocol_msg(Pid, Msg) ->
{Action, Decoded} = decode_msg(Msg),
Pid ! {Action, self(), Decoded},
receive
{ok, Pid, Res} -> encode_success(Action, Res);
{error, Pid, Why} -> encode_fail(Why);
{'EXIT', Pid, _} -> encode_fail("Internal Error.")
after ?INTERNAL_TIMEOUT ->
encode_fail("Internal timeout.")
end.
%%%
%%% Application logic
%%%
logic_process(State) ->
receive
{Action, Pid, Data} ->
{{What, Res}, S} = handle(Action, Data, State),
Pid ! {What, self(), Res},
logic_process(S);
Any ->
io:format("Logic: "),
io:format("What a fuck? ~w ~n", [Any]),
exit(Any)
end.
logic_process() ->
logic_process(#logic_state{}).
select_library(IERequested, IEsAllowed) ->
case [ IE || IE <- IEsAllowed, IE == IERequested ] of
[] ->
{error, "Access to library denied, or library doesn't exist."};
_ ->
DD = dir_to_data(filename:join(?BASE_DIR, binary_to_list(IERequested))),
{ok, [ {X} || X <- DD ]}
end.
handle(auth, Data, State) ->
case interactives_list(Data#command.login) of
{ok, IEs} ->
{{ok, IEs}, #logic_state{ies=IEs}};
{error, Why} ->
{{error, Why}, State}
end;
handle(getlib, _Data, #logic_state{ies=none} = State) ->
{{error, "Unauthorized."}, State};
handle(getlib, Data, State) ->
{select_library(Data#command.desc, State#logic_state.ies), State};
handle(_, _, S) ->
{{error, "Invalid action."}, S}.
%%%
%%% Supervision
%%%
start(Port) ->
spawn(?MODULE, loop, [Port]).
loop(Port) ->
inets:start(),
process_flag(trap_exit, true),
Pid = spawn_link(fun() ->
listen(Port, fun logic_process/0),
timer:sleep(infinity) end),
receive
{'EXIT', Pid, Reason} ->
io:format("Process ~p exited for reason ~p~n",[Pid,Reason]),
loop(Port);
{'EXIT', _From, shutdown} ->
exit(shutdown) % will kill the child too
end.
#!/usr/bin/ruby
require 'rubygems'
require 'json'
require 'eventmachine'
require 'em-http'
URL = "http://localhost:5984/clients/_design/bundles/_list/merge/purchased?key="
BASE_DIR = "./"
module Dir2Data
def self.read dirname
resp = {}
target = File.join dirname, "**", "*.*"
Dir.glob target do |name|
contents = File.read name
resp[name] = contents;
end
resp
end
end
class InteractivesList
include EM::Deferrable
def initialize clientname
req = EM::HttpRequest.new("#{URL}\"#{clientname}\"").get
req.callback do
self.fail("Unable to fetch #{clientname} interactives") unless req.response_header.status == 200
info = JSON.parse req.response
self.succeed info
end
end
end
module HeadProtocol
include EM::P::LineText2
def receive_line data
p data
obj = JSON.parse data, :symbolize_names=>true
send "action_"+obj[:action], obj
rescue Exception => e
send_error "Invalid request #{data} or something else happened. Description: #{e}"
end
def send_error text
send_data({:error=>text}.to_json + "\n")
end
def send_result action, res
send_data({ action => {:results=> [res]} }.to_json + "\n")
end
end
class HeadConnection < EM::Connection
include HeadProtocol
def get_interactives_list client
il = InteractivesList.new client
il.callback { |ints| @interactives = ints; yield ints }
il.errback { |e| send_error e }
end
def action_auth obj
get_interactives_list(obj[:login]) { |iv| send_result :auth, iv }
end
def action_getlib obj
return send_error "Unauthorized access" unless @interactives
libname = obj[:desc]
return send_error "Access to '#{libname}' denied or library not found" unless @interactives.include? libname
EM.defer proc { Dir2Data::read File.join(BASE_DIR, libname) },
proc { |int| send_result :getlib, int }
end
end
EM.run do
EM.epoll
EM.start_server 'localhost', 6070, HeadConnection
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment