Created
April 17, 2011 12:13
-
-
Save sumerman/923995 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-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. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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