mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Add a more involved REST example
A pastebin type application that can optionally highlight the output as both text and HTML.
This commit is contained in:
parent
ae401f7460
commit
d7b83db92e
11 changed files with 303 additions and 0 deletions
|
@ -28,6 +28,9 @@ Cowboy Examples
|
||||||
* [rest_hello_world](./examples/rest_hello_world):
|
* [rest_hello_world](./examples/rest_hello_world):
|
||||||
return the data type that matches the request type (ex: html, text, json)
|
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_world](./examples/static_world):
|
||||||
static file handler
|
static file handler
|
||||||
|
|
||||||
|
|
52
examples/rest_pastebin/README.md
Normal file
52
examples/rest_pastebin/README.md
Normal file
|
@ -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:
|
||||||
|
```
|
||||||
|
<command> | 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=<language> 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 <url from location header>
|
||||||
|
```
|
||||||
|
|
||||||
|
Will show the text of the html file. If your terminal supports color
|
||||||
|
sequences and highlight is installed:
|
||||||
|
```
|
||||||
|
curl <url from location header>?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.
|
||||||
|
|
22
examples/rest_pastebin/priv/index.html
Normal file
22
examples/rest_pastebin/priv/index.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Simple Pastebin</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Simple Pastebin</h1>
|
||||||
|
<p>
|
||||||
|
You can paste your text into the text field to submit, or you can
|
||||||
|
capture the output of a command with:
|
||||||
|
</p>
|
||||||
|
<code>
|
||||||
|
<i>command</i> | curl -i --data-urlencode paste@- localhost:8080
|
||||||
|
</code>
|
||||||
|
<form action="/" method="post">
|
||||||
|
<textarea cols="80" rows="15" name="paste"></textarea>
|
||||||
|
<div>
|
||||||
|
<button type="submit">Upload your code</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
7
examples/rest_pastebin/priv/index.txt
Normal file
7
examples/rest_pastebin/priv/index.txt
Normal file
|
@ -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:
|
||||||
|
|
||||||
|
<command> | curl -i --data-urlencode paste@- localhost:8080
|
4
examples/rest_pastebin/rebar.config
Normal file
4
examples/rest_pastebin/rebar.config
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{deps, [
|
||||||
|
{cowboy, ".*",
|
||||||
|
{git, "git://github.com/extend/cowboy.git", "master"}}
|
||||||
|
]}.
|
15
examples/rest_pastebin/src/rest_pastebin.app.src
Normal file
15
examples/rest_pastebin/src/rest_pastebin.app.src
Normal file
|
@ -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, []}
|
||||||
|
]}.
|
14
examples/rest_pastebin/src/rest_pastebin.erl
Normal file
14
examples/rest_pastebin/src/rest_pastebin.erl
Normal file
|
@ -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).
|
25
examples/rest_pastebin/src/rest_pastebin_app.erl
Normal file
25
examples/rest_pastebin/src/rest_pastebin_app.erl
Normal file
|
@ -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.
|
23
examples/rest_pastebin/src/rest_pastebin_sup.erl
Normal file
23
examples/rest_pastebin/src/rest_pastebin_sup.erl
Normal file
|
@ -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}}.
|
132
examples/rest_pastebin/src/toppage_handler.erl
Normal file
132
examples/rest_pastebin/src/toppage_handler.erl
Normal file
|
@ -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(<<Initial>>, 7).
|
||||||
|
new_paste_id(Bin, 0) ->
|
||||||
|
Chars = <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890">>,
|
||||||
|
<< <<(binary_part(Chars, B, 1))/binary>> || <<B>> <= Bin >>;
|
||||||
|
new_paste_id(Bin, Rem) ->
|
||||||
|
Next = random:uniform(62) - 1,
|
||||||
|
new_paste_id(<<Bin/binary, Next>>, Rem - 1).
|
||||||
|
|
||||||
|
format_html(Paste, plain) ->
|
||||||
|
Text = escape_html_chars(read_file(Paste)),
|
||||||
|
<<"<!DOCTYPE html><html>",
|
||||||
|
"<head><title>paste</title></head>",
|
||||||
|
"<body><pre><code>", Text/binary, "</code></pre></body></html>\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>> || <<B>> <= Bin >>.
|
||||||
|
|
||||||
|
escape_html_char($<) -> <<"<">>;
|
||||||
|
escape_html_char($>) -> <<">">>;
|
||||||
|
escape_html_char($&) -> <<"&">>;
|
||||||
|
escape_html_char(C) -> <<C>>.
|
6
examples/rest_pastebin/start.sh
Executable file
6
examples/rest_pastebin/start.sh
Executable file
|
@ -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 <value of the location header>~n\")." \
|
||||||
|
-eval "io:format(\"Get with highlighting: curl <location>?lang=<language>~n\")." \
|
||||||
|
-eval "io:format(\"To get html, point your browser to http://localhost:8080.~n\")."
|
Loading…
Add table
Add a link
Reference in a new issue