From f9dd3c9e54da7690db6f936d644c1c7b88ac6e9b Mon Sep 17 00:00:00 2001 From: Adam Cammack Date: Sun, 10 Feb 2013 13:54:51 -0600 Subject: Add a REST example that streams its response --- examples/README.md | 3 + examples/rest_stream_response/README.md | 66 ++++++++++++++++++++++ examples/rest_stream_response/rebar.config | 4 ++ .../src/rest_stream_response.app.src | 15 +++++ .../src/rest_stream_response.erl | 14 +++++ .../src/rest_stream_response_app.erl | 38 +++++++++++++ .../src/rest_stream_response_sup.erl | 23 ++++++++ .../rest_stream_response/src/toppage_handler.erl | 42 ++++++++++++++ examples/rest_stream_response/start.sh | 3 + 9 files changed, 208 insertions(+) create mode 100644 examples/rest_stream_response/README.md create mode 100644 examples/rest_stream_response/rebar.config create mode 100644 examples/rest_stream_response/src/rest_stream_response.app.src create mode 100644 examples/rest_stream_response/src/rest_stream_response.erl create mode 100644 examples/rest_stream_response/src/rest_stream_response_app.erl create mode 100644 examples/rest_stream_response/src/rest_stream_response_sup.erl create mode 100644 examples/rest_stream_response/src/toppage_handler.erl create mode 100755 examples/rest_stream_response/start.sh diff --git a/examples/README.md b/examples/README.md index 442b149..e166876 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,6 +34,9 @@ Cowboy Examples * [rest_pastebin](./rest_pastebin): create text objects and return the data type that matches the request type (html, text) + * [rest_stream_response](./rest_stream_response): + stream results from a data store + * [static_world](./static_world): static file handler diff --git a/examples/rest_stream_response/README.md b/examples/rest_stream_response/README.md new file mode 100644 index 0000000..70e1ba7 --- /dev/null +++ b/examples/rest_stream_response/README.md @@ -0,0 +1,66 @@ +Cowboy REST Streaming Responses +=============================== + +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 +``` + +This example simulates streaming a large amount of data from a data store one +record at a time in CSV format. It also uses a constraint to ensure that the +last segment of the route is an integer. + +Examples +-------- + +### Get records with a field 2 value of 1 + +``` bash +$ curl -i localhost:8080 +HTTP/1.1 200 OK +transfer-encoding: identity +server: Cowboy +date: Sun, 10 Feb 2013 19:32:16 GMT +connection: close +content-type: text/csv + +DBUZGQ0C,1,28 +BgoQAxMV,1,6 +DAYEFxER,1,18 +... +``` + +### Get records with a field 2 value of 4 + +``` bash +$ curl -i localhost:8080/4 +HTTP/1.1 200 OK +transfer-encoding: identity +server: Cowboy +date: Sun, 10 Feb 2013 19:34:31 GMT +connection: close +content-type: text/csv + +ABcFDxcE,4,42 +DgYQCgEE,4,5 +CA8BBhYD,4,10 +... +``` + +### Get a 404 + +``` bash +$ curl -i localhost:8080/foo +HTTP/1.1 404 Not Found +connection: keep-alive +server: Cowboy +date: Sun, 10 Feb 2013 19:36:16 GMT +content-length: 0 +``` diff --git a/examples/rest_stream_response/rebar.config b/examples/rest_stream_response/rebar.config new file mode 100644 index 0000000..6ad3062 --- /dev/null +++ b/examples/rest_stream_response/rebar.config @@ -0,0 +1,4 @@ +{deps, [ + {cowboy, ".*", + {git, "git://github.com/extend/cowboy.git", "master"}} +]}. diff --git a/examples/rest_stream_response/src/rest_stream_response.app.src b/examples/rest_stream_response/src/rest_stream_response.app.src new file mode 100644 index 0000000..4a458b8 --- /dev/null +++ b/examples/rest_stream_response/src/rest_stream_response.app.src @@ -0,0 +1,15 @@ +%% Feel free to use, reuse and abuse the code in this file. + +{application, rest_stream_response, [ + {description, "Cowboy REST with streaming."}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib, + cowboy + ]}, + {mod, {rest_stream_response_app, []}}, + {env, []} +]}. diff --git a/examples/rest_stream_response/src/rest_stream_response.erl b/examples/rest_stream_response/src/rest_stream_response.erl new file mode 100644 index 0000000..ef24309 --- /dev/null +++ b/examples/rest_stream_response/src/rest_stream_response.erl @@ -0,0 +1,14 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(rest_stream_response). + +%% API. +-export([start/0]). + +%% API. + +start() -> + ok = application:start(crypto), + ok = application:start(ranch), + ok = application:start(cowboy), + ok = application:start(rest_stream_response). diff --git a/examples/rest_stream_response/src/rest_stream_response_app.erl b/examples/rest_stream_response/src/rest_stream_response_app.erl new file mode 100644 index 0000000..a382d29 --- /dev/null +++ b/examples/rest_stream_response/src/rest_stream_response_app.erl @@ -0,0 +1,38 @@ +%% Feel free to use, reuse and abuse the code in this file. + +%% @private +-module(rest_stream_response_app). +-behaviour(application). + +%% API. +-export([start/2]). +-export([stop/1]). + +%% API. + +start(_Type, _Args) -> + Table = ets:new(stream_tab, []), + generate_rows(Table, 1000), + Dispatch = cowboy_router:compile([ + {'_', [ + {"/[:v1]", [{v1, int}], toppage_handler, Table} + ]} + ]), + {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ + {env, [{dispatch, Dispatch}]} + ]), + rest_stream_response_sup:start_link(). + +stop(_State) -> + ok. + +generate_rows(_Table, 0) -> ok; +generate_rows(Table, N) -> + ets:insert(Table, {key(), val(), val()}), + generate_rows(Table, N - 1). + +key() -> key(10). +key(N) -> key(<< (random:uniform(26) - 1) >>, N - 1). +key(Acc, 0) -> binary_part(base64:encode(Acc), 0, 8); +key(Acc, N) -> key(<< Acc/binary, (random:uniform(26) - 1) >>, N - 1). +val() -> random:uniform(50). diff --git a/examples/rest_stream_response/src/rest_stream_response_sup.erl b/examples/rest_stream_response/src/rest_stream_response_sup.erl new file mode 100644 index 0000000..73142d4 --- /dev/null +++ b/examples/rest_stream_response/src/rest_stream_response_sup.erl @@ -0,0 +1,23 @@ +%% Feel free to use, reuse and abuse the code in this file. + +%% @private +-module(rest_stream_response_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_stream_response/src/toppage_handler.erl b/examples/rest_stream_response/src/toppage_handler.erl new file mode 100644 index 0000000..5052038 --- /dev/null +++ b/examples/rest_stream_response/src/toppage_handler.erl @@ -0,0 +1,42 @@ +%% Feel free to use, reuse and abuse the code in this file. + +%% @doc Streaming handler. +-module(toppage_handler). + +-export([init/3]). +-export([rest_init/2]). +-export([content_types_provided/2]). +-export([streaming_csv/2]). + +init(_Transport, _Req, _Table) -> + {upgrade, protocol, cowboy_rest}. + +rest_init(Req, Table) -> + {ok, Req, Table}. + +content_types_provided(Req, State) -> + {[ + {{<<"text">>, <<"csv">>, []}, streaming_csv} + ], Req, State}. + +streaming_csv(Req, Table) -> + {N, Req1} = cowboy_req:binding(v1, Req, 1), + MS = [{{'$1', '$2', '$3'}, [{'==', '$2', N}], ['$$']}], + + {{stream, result_streamer(Table, MS)}, Req1, Table}. + +result_streamer(Table, MS) -> + fun (Socket, Transport) -> + send_records(Socket, Transport, ets:select(Table, MS, 1)) + end. + +send_records(Socket, Transport, {[Rec], Cont}) -> + timer:sleep(500), + send_line(Socket, Transport, Rec), + send_records(Socket, Transport, ets:select(Cont)); +send_records(_Socket, _Transport, '$end_of_table') -> + ok. + +send_line(Socket, Transport, [Key, V1, V2]) -> + Transport:send(Socket, + [Key, $,, integer_to_list(V1), $,, integer_to_list(V2), $\r, $\n]). diff --git a/examples/rest_stream_response/start.sh b/examples/rest_stream_response/start.sh new file mode 100755 index 0000000..f5efcb0 --- /dev/null +++ b/examples/rest_stream_response/start.sh @@ -0,0 +1,3 @@ +#!/bin/sh +erl -pa ebin deps/*/ebin -s rest_stream_response \ + -eval "io:format(\"Streaming results: curl -i http://localhost:8080~n\")." -- cgit v1.2.3