%% @copyright 2008 Jacob Torrey %% @author Jacob Torrey %% @doc A very simple HTTP/1.0 web server -module(httpd). -export([start/1, stop/0]). -author('Jacob Torrey '). -include_lib("kernel/include/file.hrl"). -define(DIR, "."). -define(NAME, http_server). %% @doc Start listening on Port start(Port) -> {ok, LSock} = gen_tcp:listen(Port, [binary, {active, false}, {packet, 0}, {reuseaddr, true}]), register(?NAME, spawn(fun() -> listen_loop(LSock) end)), ?NAME. %% @doc Stops the server stop() -> ?NAME ! stop, ok. %% @doc Listens for incoming connections and spawns a new process to handle each request listen_loop(LSock) -> receive stop -> gen_tcp:close(LSock), io:format("Quitting~n", []), exit(normal); _ -> listen_loop(LSock) after 0 -> Res = gen_tcp:accept(LSock, 500), case Res of {ok, Sock} -> spawn(fun() -> server_loop(Sock) end); {error, timeout} -> ok; {error, Reason} -> exit(Reason) end, listen_loop(LSock) end. %% @doc The client socket loop, handles the HTTP protocol server_loop(Sock) -> case gen_tcp:recv(Sock, 0) of {ok, D} -> Str = erlang:binary_to_list(D), Tokens = string:tokens(Str, " "), [Req, Dir | _] = Tokens, case Req of "GET" -> case filelib:is_file(?DIR ++ Dir) of true -> gen_tcp:send(Sock, "HTTP/1.0 200 OK\r\n"), {Size, Res} = handle(?DIR ++ Dir), gen_tcp:send(Sock, "Content-Length: " ++ erlang:integer_to_list(Size) ++ "\r\n"), gen_tcp:send(Sock, Res), gen_tcp:close(Sock); false -> gen_tcp:send(Sock, "HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\n\r\nFile Not Found404 File Not Found"), gen_tcp:close(Sock) end; _ -> server_loop(Sock) end; {error, closed} -> gen_tcp:close(Sock), ok end. %% @doc Handles different types of files handle(File) -> Tokens = string:tokens(File, "."), [Type|_] = lists:reverse(Tokens), case Type of "/" -> Mime = get_mime("html"), Dat = "\n\nWelcome to Jacob's Webserver\n\nWelcome to Jacob's Erlang webserver, aka 'The GOATIEPOATIEPIEGOATIEPOATIE!'\n\n", Res = add_res(Mime, Dat); "cgi" -> Res = os:cmd(File); "pl" -> Res = os:cmd(File); "php" -> Res = os:cmd("php-cgi " ++ File); _ -> Mime = get_mime(string:to_lower(Type)), {ok, Dat} = file:read_file(File), Res = add_res(Mime, Dat) end, {string:len(Res), erlang:list_to_binary(Res)}. %% @doc A function to auotmatically add the Content-Type header to the response add_res(Mime, Dat) when is_binary(Dat) -> "Content-Type: " ++ Mime ++ "\r\n\r\n" ++ erlang:binary_to_list(Dat); add_res(Mime, Dat) when is_list(Dat) -> "Content-Type: " ++ Mime ++ "\r\n\r\n" ++ Dat; add_res(Mime, Dat) -> "Content-Type: " ++ Mime ++ "\r\n\r\n" ++ erlang:atom_to_list(Dat). %% @doc Returns the MIME type of a file get_mime("html") -> "text/html"; get_mime("css") -> "text/css"; get_mime("js") -> "text/javascript"; get_mime("jpg") -> "image/jpeg"; get_mime("jpeg") -> "image/jpeg"; get_mime(_) -> "text/plain".