(blog ‘lucindo)

um dia eu aprendo a programar

Arquivo de Abril de 2007

Video: Concurrency and Erlang

Apresentação feita por Andre Pang no linux.conf.au 2007 destacando algumas características gerais da linguagem e focando na importância de suporte nativo na linguagem a programação concorrente.



Sem comentários »

Primeiros passos com Erlang (toy NNTP client)

Nada melhor do que programar para aprender uma linguagem de programação. Por isso comecei um pequeno projeto em Erlang. Faz parte desse projeto um cliente do protocolo NNTP. Implementei apenas os comandos NNTP que eu precisava.

Essa implementação está bem crua ainda, sem tratamento de erros do protocolo, que se acontecerem vão resultar em crash por badmatch.

-module(nntp).
-vsn(1).
-author({renato, lucindo}).
-purpose(”work :-P “).

%% Implements a little client for:
%%  NNTP: http://tools.ietf.org/html/rfc977
%%  Simple NNTP Auth: http://tools.ietf.org/html/rfc4643

%% protocol functions
-export([connect/2, connect/4, cmd_quit/1]).
-export([cmd_list/1, cmd_group/2, cmd_stat/2, cmd_head/2, cmd_body/2]).
%% utils
-export([get_header_contents/2]).

-include(”nntp.hrl“).

%% Little documentation:
%% warning 1:
%%   You must include “nntp.hrl” to the definition of newsgroup record.
%%
%% Warning 2:
%%   This is a quick and really dirty implementation. Will result on a badmath
%%   error due protocol erros. This will be fixed one day (maybe with wappers
%%   functions with try…catch).
%%
%% Commands (functions exported):
%%
%% nntp:connect(Host, Port)
%%   Simple connection on Host:Port
%%   Returns {ok, Socket} on success. {error, Reason} otherwise
%%
%% nntp:connect(Host, Port, User, Password)
%%   Connect and authenticate on Host:Port. Same return values as connect/2
%%
%% nntp:cmd_quit(Sockt)
%%   Terminate de NNTP session and close the socket.
%%   Aways returns ok.
%%
%% nntp:cmd_list(Socket)
%%   List all groups on server.
%%   Resturns a erlang:list of newsgroups record (see “nntp.hrl”).
%%
%% nntp:cmd_group(Socket, GroupName)
%%   Selects the group GroupName.
%%   Returns a tuple {ok, NMessages, First, Last}, where NMessages is the number of
%%   messages on group, First is the first article number and Last is the last
%%   article number
%%
%% nntp:cmd_stat(Socket, Msgnum)
%%   Selects the message Msgnum (message number) for reading
%%   Returns {ok, MessageID} where MessageID is the message unique ID
%%
%% nntp:cmd_head(Socket, Msgnum)
%%   Gets the headers of article Msgnum.
%%   Returns a tuple {ok, MessageID, Headers} where Headers is a list of strings
%%
%% nntp:get_header_contents(Header, Field)
%%   Gets the contents of a Header’s Field (the header as returned by nntp:cmd_head)
%%   Returns {ok, Filed, Content} on success, error otherwise
%%
%% nntp:cmd_body(Socket, Msgnum)
%%   Gets the body of message Msgnum as string.
%%   Returns {ok, MessageID, Message} on success, error otherwise

connect(Hostname, Port) ->
    case gen_tcp:connect(Hostname, Port, [binary, {packet, line}, {active, false}]) of
        {ok, Socket} -> 
            case read_response(Socket) of
                {ok, Code, _} when Code == 200; Code == 201 ->
                    {ok, Socket};
                _ ->
                    gen_tcp:close(Socket),
                    {error, “Greetings read error“}
            end;
        _ ->
            {error, “Connection error“}
    end.
connect(Hostname, Port, User, Pass) ->
    {ok, Socket} = connect(Hostname, Port),
    auth(Socket, User, Pass).

auth(Socket, User, Pass) ->
    ok = send_request(Socket, “AUTHINFO USER ” ++ User),
    {ok, 381, _} = read_response(Socket),
    ok = send_request(Socket, “AUTHINFO PASS ” ++ Pass),
    {ok, 281, _} = read_response(Socket),
    {ok, Socket}.

cmd_quit(Socket) ->
    ok = send_request(Socket, “QUIT“),
    {ok, 205, _} = read_response(Socket),
    ok = gen_tcp:close(Socket),
    ok.

cmd_list(Socket) ->
    ok = send_request(Socket, “LIST“),
    {ok, 215, _} = read_response(Socket),
    cmd_list(Socket, []).
cmd_list(Socket, Glist) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Response} ->
            Line = remove_rn(binary_to_list(Response)),
            if
                Line == “.” ->
                    Glist;
                true ->
                    Tk = string:tokens(Line, “ “),
                    Name = lists:nth(1, Tk),
                    Last = list_to_integer(lists:nth(2, Tk)),
                    First = list_to_integer(lists:nth(3, Tk)),
                    CanPost = list_to_atom(lists:nth(4, Tk)),
                    Group = #newsgroup {
                      name = Name,
                      last = Last,
                      first = First,
                      canpost = CanPost
                     },
                    cmd_list(Socket, lists:append(Glist, [Group]))
            end;
        _ ->
            error
    end.

cmd_group(Socket, Group) ->
    ok = send_request(Socket, “GROUP ” ++ Group),
    {ok, 211, GrpInfo} = read_response(Socket),
    Tk = string:tokens(GrpInfo, “ “),
    NMessages = list_to_integer(lists:nth(1, Tk)),
    First = list_to_integer(lists:nth(2, Tk)),
    Last = list_to_integer(lists:nth(3, Tk)),
    {ok, NMessages, First, Last}.

cmd_stat(Socket, Msgnum) ->
    ok = send_request(Socket, “STAT “++ integer_to_list(Msgnum)),
    {ok, 223, Response} = read_response(Socket),
    {ok, lists:nth(2, string:tokens(Response, “ “))}.

cmd_head(Socket, Msgnum) ->
    exec_cmd_response_and_stream(Socket, Msgnum, “HEAD “, 221, fun read_lines_as_list/3).

cmd_body(Socket, Msgnum) ->
    exec_cmd_response_and_stream(Socket, Msgnum, “BODY “, 222, fun read_lines_as_string/3).

exec_cmd_response_and_stream(Socket, Msgnum, Command, RespCode, ReadFunction) when integer(Msgnum) ->
    exec_cmd_response_and_stream(Socket, integer_to_list(Msgnum), Command, RespCode, ReadFunction);
exec_cmd_response_and_stream(Socket, Msgnum, Command, RespCode, ReadFunction) ->
    ok = send_request(Socket, Command ++ Msgnum),
    {ok, RespCode, Response} = read_response(Socket),
    {ok, lists:nth(2, string:tokens(Response, “ “)), apply(ReadFunction, [Socket, “.“, []])}.

read_lines_as_list(Socket, Delim, Str) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Response} ->
            Line = remove_rn(binary_to_list(Response)),
            if
                Line == Delim ->
                    Str;
                true ->
                    read_lines_as_list(Socket, Delim, lists:append(Str, [Line]))
            end;
        _ ->
            error
    end.    

read_lines_as_string(Socket, Delim, Str) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Response} ->
            StrR = binary_to_list(Response),
            Line = remove_rn(StrR),
            if
                Line == Delim ->
                    Str;
                true ->
                    read_lines_as_string(Socket, Delim, [Str, StrR])
            end;
        _ ->
            error
    end.    

send_request(Socket, Cmd) ->
    Str = Cmd ++ “rn“,
    case gen_tcp:send(Socket, Str) of
        ok->
            ok;
        {error, Reason} ->
            io:format(”error on send socket: ~w~n“, [Reason]),
            gen_tcp:close(Socket),
            error
    end.

read_response(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Response} ->
            parse_response(Response);
        {error, Reason} ->
            io:format(”error on recv socket: ~w~n“, [Reason]),
            gen_tcp:close(Socket),
            error
    end.

parse_response(Response) ->
    <<Code:3/binary, _:1/binary, Message/binary>> = Response,
    {ok, list_to_integer(binary_to_list(Code)), remove_rn(binary_to_list(Message))}.

get_header_contents(Header, Field) ->
    get_header_contents_by_field(Field, Header).

get_header_contents_by_field(Field, [H|T]) ->
    Tk = string:tokens(H, “: “),
    if
        length(Tk) > 1 ->       
            F = lists:nth(1, Tk),
            C = lists:nth(2, Tk),
            if
                F == Field ->
                    {ok, F, C};
                true ->
                    get_header_contents_by_field(Field, T)
            end;
        true ->
            get_header_contents_by_field(Field, T)
    end;
get_header_contents_by_field(_, []) ->
    error.

remove_rn(Str) ->
    string:strip(string:strip(Str, both, $n), both, $r).

O arquivo nntp.hrl tem apenas:

-record(newsgroup,
        {
          name = “”,
          last = 0,
          first = 0,
          canpost = n
         }).

Alguém sabe como exportar records de uma maneira fácil?

1 comentário »

Makefile genério para Erlang

O Makefile a seguir compila os arquivos nntp.erl e crawler.erl

.SUFFIXES: .erl .beam .hrl

.erl.beam:
        erlc -W $<

MODULES = nntp crawler

all: compile

compile: ${MODULES:%=%.beam}

clean:
        rm -rf *.beam erl_crash.dump *~
Sem comentários »

Erlang no Mac OS X

Uma maneira fácil de instalar o sistema Erlang no Mac OS X é pelo Darwin Ports. Com ele instalado vá no terminal e dê os comandos:

$ sudo port selfupdate
$ sudo port install erlang +smp

Se você usa Emacs (Aquamacs) adicione no seu .emacs:

(setq load-path (cons "/opt/local/lib/erlang/lib/tools-2.5.3/emacs”
    load-path))
(setq erlang-root-dir “/opt/local/“)
(setq exec-path (cons “/opt/local/bin” exec-path))
(requireerlang-start)
Sem comentários »