From d7b83db92ea6b6a0022c7d19c252037197fc28c4 Mon Sep 17 00:00:00 2001 From: Adam Cammack Date: Wed, 6 Feb 2013 13:09:49 -0600 Subject: Add a more involved REST example A pastebin type application that can optionally highlight the output as both text and HTML. --- examples/README.md | 3 + examples/rest_pastebin/README.md | 52 +++++++++ examples/rest_pastebin/priv/index.html | 22 ++++ examples/rest_pastebin/priv/index.txt | 7 ++ examples/rest_pastebin/rebar.config | 4 + examples/rest_pastebin/src/rest_pastebin.app.src | 15 +++ examples/rest_pastebin/src/rest_pastebin.erl | 14 +++ examples/rest_pastebin/src/rest_pastebin_app.erl | 25 +++++ examples/rest_pastebin/src/rest_pastebin_sup.erl | 23 ++++ examples/rest_pastebin/src/toppage_handler.erl | 132 +++++++++++++++++++++++ examples/rest_pastebin/start.sh | 6 ++ 11 files changed, 303 insertions(+) create mode 100644 examples/rest_pastebin/README.md create mode 100644 examples/rest_pastebin/priv/index.html create mode 100644 examples/rest_pastebin/priv/index.txt create mode 100644 examples/rest_pastebin/rebar.config create mode 100644 examples/rest_pastebin/src/rest_pastebin.app.src create mode 100644 examples/rest_pastebin/src/rest_pastebin.erl create mode 100644 examples/rest_pastebin/src/rest_pastebin_app.erl create mode 100644 examples/rest_pastebin/src/rest_pastebin_sup.erl create mode 100644 examples/rest_pastebin/src/toppage_handler.erl create mode 100755 examples/rest_pastebin/start.sh diff --git a/examples/README.md b/examples/README.md index 0e75485..d00fd11 100644 --- a/examples/README.md +++ b/examples/README.md @@ -28,6 +28,9 @@ Cowboy Examples * [rest_hello_world](./examples/rest_hello_world): return the data type that matches the request type (ex: html, text, json) + * [rest_pastebin](./examples/rest_pastebin): + create text objects and return the data type that matches the request type (html, text) + * [static_world](./examples/static_world): static file handler diff --git a/examples/rest_pastebin/README.md b/examples/rest_pastebin/README.md new file mode 100644 index 0000000..0e9d9b5 --- /dev/null +++ b/examples/rest_pastebin/README.md @@ -0,0 +1,52 @@ +Cowboy Rest Hello World +======================= + +To compile this example you need rebar in your PATH. + +Type the following command: +``` +$ rebar get-deps compile +``` + +You can then start the Erlang node with the following command: +``` +./start.sh +``` + +Then run any given command or point your browser to the indicated URL. + +Examples +-------- + +To upload something to the paste application, you can use curl like: +``` + | curl -i --data-urlencode paste@- localhost:8080 +``` +or to upload my_file: +``` +curl -i --data-urlencode paste@my_file localhost:8080 +``` + +The URL of your data will be in the location header. Alternately, you can visit +http://localhost:8080 with your favorite web browser and submit your paste via +the form. + +Code that has been pasted can be highlighted with ?lang= option if +you have [highlight](http://www.andre-simon.de/doku/highlight/en/highlight.html) +installed (although pygments or any other should work just fine). For example: +``` +curl -i --data-urlencode paste@priv/index.html localhost:8080 +curl +``` + +Will show the text of the html file. If your terminal supports color +sequences and highlight is installed: +``` +curl ?lang=html +``` + +Will show a syntax highlighted version of the source file. If you open the +same URL in your web browser and your web browser tells cowboy that it prefers +html files, you will see the file highlighted with html/css markup. Firefox is +known to work. + diff --git a/examples/rest_pastebin/priv/index.html b/examples/rest_pastebin/priv/index.html new file mode 100644 index 0000000..1c7e2eb --- /dev/null +++ b/examples/rest_pastebin/priv/index.html @@ -0,0 +1,22 @@ + + + + Simple Pastebin + + +

Simple Pastebin

+

+ You can paste your text into the text field to submit, or you can + capture the output of a command with: +

+ + command | curl -i --data-urlencode paste@- localhost:8080 + +
+ +
+ +
+
+ + diff --git a/examples/rest_pastebin/priv/index.txt b/examples/rest_pastebin/priv/index.txt new file mode 100644 index 0000000..0135fb0 --- /dev/null +++ b/examples/rest_pastebin/priv/index.txt @@ -0,0 +1,7 @@ +Simple Pastebin +--------------- + +You can paste your text into the text field to submit, or you can capture the +output of a command with: + + | curl -i --data-urlencode paste@- localhost:8080 diff --git a/examples/rest_pastebin/rebar.config b/examples/rest_pastebin/rebar.config new file mode 100644 index 0000000..6ad3062 --- /dev/null +++ b/examples/rest_pastebin/rebar.config @@ -0,0 +1,4 @@ +{deps, [ + {cowboy, ".*", + {git, "git://github.com/extend/cowboy.git", "master"}} +]}. diff --git a/examples/rest_pastebin/src/rest_pastebin.app.src b/examples/rest_pastebin/src/rest_pastebin.app.src new file mode 100644 index 0000000..7701ebe --- /dev/null +++ b/examples/rest_pastebin/src/rest_pastebin.app.src @@ -0,0 +1,15 @@ +%% Feel free to use, reuse and abuse the code in this file. + +{application, rest_pastebin, [ + {description, "Cowboy REST Pastebin example inspired by sprunge."}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib, + cowboy + ]}, + {mod, {rest_pastebin_app, []}}, + {env, []} +]}. diff --git a/examples/rest_pastebin/src/rest_pastebin.erl b/examples/rest_pastebin/src/rest_pastebin.erl new file mode 100644 index 0000000..cf03a71 --- /dev/null +++ b/examples/rest_pastebin/src/rest_pastebin.erl @@ -0,0 +1,14 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(rest_pastebin). + +%% API. +-export([start/0]). + +%% API. + +start() -> + ok = application:start(crypto), + ok = application:start(ranch), + ok = application:start(cowboy), + ok = application:start(rest_pastebin). diff --git a/examples/rest_pastebin/src/rest_pastebin_app.erl b/examples/rest_pastebin/src/rest_pastebin_app.erl new file mode 100644 index 0000000..10ba4c5 --- /dev/null +++ b/examples/rest_pastebin/src/rest_pastebin_app.erl @@ -0,0 +1,25 @@ +%% Feel free to use, reuse and abuse the code in this file. + +%% @private +-module(rest_pastebin_app). +-behaviour(application). + +%% API. +-export([start/2]). +-export([stop/1]). + +%% API. + +start(_Type, _Args) -> + Dispatch = cowboy_router:compile([ + {'_', [ + {"/[:paste_id]", toppage_handler, []} + ]} + ]), + {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ + {env, [{dispatch, Dispatch}]} + ]), + rest_pastebin_sup:start_link(). + +stop(_State) -> + ok. diff --git a/examples/rest_pastebin/src/rest_pastebin_sup.erl b/examples/rest_pastebin/src/rest_pastebin_sup.erl new file mode 100644 index 0000000..b5f08d7 --- /dev/null +++ b/examples/rest_pastebin/src/rest_pastebin_sup.erl @@ -0,0 +1,23 @@ +%% Feel free to use, reuse and abuse the code in this file. + +%% @private +-module(rest_pastebin_sup). +-behaviour(supervisor). + +%% API. +-export([start_link/0]). + +%% supervisor. +-export([init/1]). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% supervisor. + +init([]) -> + Procs = [], + {ok, {{one_for_one, 10, 10}, Procs}}. diff --git a/examples/rest_pastebin/src/toppage_handler.erl b/examples/rest_pastebin/src/toppage_handler.erl new file mode 100644 index 0000000..758e11d --- /dev/null +++ b/examples/rest_pastebin/src/toppage_handler.erl @@ -0,0 +1,132 @@ +%% Feel free to use, reuse and abuse the code in this file. + +%% @doc Pastebin handler. +-module(toppage_handler). + +%% REST Callbacks +-export([init/3]). +-export([allowed_methods/2]). +-export([content_types_provided/2]). +-export([content_types_accepted/2]). +-export([resource_exists/2]). +-export([post_is_create/2]). +-export([create_path/2]). + +%% Callback Callbacks +-export([create_paste/2]). +-export([paste_html/2]). +-export([paste_text/2]). + +init(_Transport, _Req, []) -> + % For the random number generator: + {X, Y, Z} = now(), + random:seed(X, Y, Z), + {upgrade, protocol, cowboy_rest}. + +allowed_methods(Req, State) -> + {[<<"GET">>, <<"POST">>], Req, State}. + +content_types_provided(Req, State) -> + {[ + {{<<"text">>, <<"plain">>, []}, paste_text}, + {{<<"text">>, <<"html">>, []}, paste_html} + ], Req, State}. + +content_types_accepted(Req, State) -> + {[{{<<"application">>, <<"x-www-form-urlencoded">>, []}, create_paste}], + Req, State}. + +resource_exists(Req, _State) -> + case cowboy_req:binding(paste_id, Req) of + {undefined, Req2} -> + {true, Req2, index}; + {PasteID, Req2} -> + case valid_path(PasteID) and file_exists(PasteID) of + true -> {true, Req2, PasteID}; + false -> {false, Req2, PasteID} + end + end. + +post_is_create(Req, State) -> + {true, Req, State}. + +create_path(Req, State) -> + {<<$/, (new_paste_id())/binary>>, Req, State}. + +create_paste(Req, State) -> + {<<$/, PasteID/binary>>, Req2} = cowboy_req:meta(put_path, Req), + {ok, [{<<"paste">>, Paste}], Req3} = cowboy_req:body_qs(Req2), + ok = file:write_file(full_path(PasteID), Paste), + {true, Req3, State}. + +paste_html(Req, index) -> + {read_file("index.html"), Req, index}; +paste_html(Req, Paste) -> + {Style, Req2} = cowboy_req:qs_val(<<"lang">>, Req, plain), + {format_html(Paste, Style), Req2, Paste}. + +paste_text(Req, index) -> + {read_file("index.txt"), Req, index}; +paste_text(Req, Paste) -> + {Style, Req2} = cowboy_req:qs_val(<<"lang">>, Req, plain), + {format_text(Paste, Style), Req2, Paste}. + +% Private + +read_file(Name) -> + {ok, Binary} = file:read_file(full_path(Name)), + Binary. + +full_path(Name) -> + {ok, Cwd} = file:get_cwd(), + filename:join([Cwd, "priv", Name]). + +file_exists(Name) -> + case file:read_file_info(full_path(Name)) of + {ok, _Info} -> true; + {error, _Reason} -> false + end. + +valid_path(<<>>) -> true; +valid_path(<<$., _T/binary>>) -> false; +valid_path(<<_Char, T/binary>>) -> valid_path(T). + +new_paste_id() -> + Initial = random:uniform(62) - 1, + new_paste_id(<>, 7). +new_paste_id(Bin, 0) -> + Chars = <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890">>, + << <<(binary_part(Chars, B, 1))/binary>> || <> <= Bin >>; +new_paste_id(Bin, Rem) -> + Next = random:uniform(62) - 1, + new_paste_id(<>, Rem - 1). + +format_html(Paste, plain) -> + Text = escape_html_chars(read_file(Paste)), + <<"", + "paste", + "
", Text/binary, "
\n">>; +format_html(Paste, Lang) -> + highlight(full_path(Paste), Lang, "html"). + +format_text(Paste, plain) -> + read_file(Paste); +format_text(Paste, Lang) -> + highlight(full_path(Paste), Lang, "ansi"). + +highlight(Path, Lang, Type) -> + Path1 = binary_to_list(Path), + Lang1 = binary_to_list(Lang), + os:cmd(["highlight --syntax=", Lang1, + " --doc-title=paste ", + " --out-format=", Type, + " --include-style ", Path1]). + +% Escape some HTML characters that might make a fuss +escape_html_chars(Bin) -> + << <<(escape_html_char(B))/binary>> || <> <= Bin >>. + +escape_html_char($<) -> <<"<">>; +escape_html_char($>) -> <<">">>; +escape_html_char($&) -> <<"&">>; +escape_html_char(C) -> <>. diff --git a/examples/rest_pastebin/start.sh b/examples/rest_pastebin/start.sh new file mode 100755 index 0000000..24f5709 --- /dev/null +++ b/examples/rest_pastebin/start.sh @@ -0,0 +1,6 @@ +#!/bin/sh +erl -pa ebin deps/*/ebin -s rest_pastebin \ + -eval "io:format(\"Upload: echo foo | curl -i --data-urlencode paste@- localhost:8080~n\")." \ + -eval "io:format(\"Get: curl ~n\")." \ + -eval "io:format(\"Get with highlighting: curl ?lang=~n\")." \ + -eval "io:format(\"To get html, point your browser to http://localhost:8080.~n\")." -- cgit v1.2.3