diff --git a/lib/httparrot.ex b/lib/httparrot.ex
index 92415a3..b9df28b 100644
--- a/lib/httparrot.ex
+++ b/lib/httparrot.ex
@@ -23,6 +23,7 @@ defmodule HTTParrot do
{'/basic-auth/:user/:passwd', HTTParrot.BasicAuthHandler, []},
{'/hidden-basic-auth/:user/:passwd', HTTParrot.HiddenBasicAuthHandler, []},
{'/stream/:n', HTTParrot.StreamHandler, []},
+ {'/stream-bytes/:n', HTTParrot.StreamBytesHandler, []},
{'/delay/:n', HTTParrot.DelayedHandler, []},
{'/html', :cowboy_static, {:priv_file, :httparrot, "html.html"}},
{'/deny', HTTParrot.DenyHandler, []},
diff --git a/lib/httparrot/stream_bytes_handler.ex b/lib/httparrot/stream_bytes_handler.ex
new file mode 100644
index 0000000..0edcdc9
--- /dev/null
+++ b/lib/httparrot/stream_bytes_handler.ex
@@ -0,0 +1,43 @@
+defmodule HTTParrot.StreamBytesHandler do
+ @moduledoc """
+ Streams n bytes of data, with chunked transfer encoding.
+ """
+ alias HTTParrot.GeneralRequestInfo
+ use HTTParrot.Cowboy, methods: ~w(GET)
+
+ def content_types_provided(req, state) do
+ {[{{"application", "octet-stream", []}, :get_bytes}], req, state}
+ end
+
+ def malformed_request(req, state) do
+ {n, req} = :cowboy_req.binding(:n, req)
+ {seed, req} = :cowboy_req.qs_val("seed", req, "1234")
+ {chunk_size, req} = :cowboy_req.qs_val("chunk_size", req, "1024")
+
+ try do
+ n = n |> String.to_integer
+ seed = seed |> String.to_integer
+ chunk_size = chunk_size |> String.to_integer
+ {false, req, {n, seed, chunk_size}}
+ rescue
+ ArgumentError -> {true, req, state}
+ end
+ end
+
+ def get_bytes(req, state) do
+ {n, seed, chunk_size} = state
+ :random.seed(seed, seed, seed)
+ {{:chunked, stream_response(n, chunk_size)}, req, nil}
+ end
+
+ defp stream_response(n, chunk_size) do
+ fn(send_func) ->
+ Stream.repeatedly(fn -> :random.uniform(255) end)
+ |> Stream.take(n)
+ |> Enum.chunk(chunk_size, chunk_size, [])
+ |> Enum.each fn chunk ->
+ send_func.(List.to_string(chunk))
+ end
+ end
+ end
+end
diff --git a/priv/index.html b/priv/index.html
index 5a5a23e..42f9218 100644
--- a/priv/index.html
+++ b/priv/index.html
@@ -86,6 +86,7 @@
/robots.txt
Returns some robots.txt rules.
/deny
Denied by robots.txt file.
/cache
200 unless If-Modified-Since was sent, then 304.
+/stream-bytes/:n
Streams n random bytes of binary data, accepts optional seed and chunk_size integer parameters.
/base64/:value
Decodes value base64url-encoded string.
/image
Return an image based on Accept header.
/websocket
Echo message received through websocket.
diff --git a/test/stream_bytes_handler_test.exs b/test/stream_bytes_handler_test.exs
new file mode 100644
index 0000000..5441396
--- /dev/null
+++ b/test/stream_bytes_handler_test.exs
@@ -0,0 +1,117 @@
+defmodule HTTParrot.StreamBytesHandlerTest do
+ use ExUnit.Case, async: false
+ import :meck
+ import HTTParrot.StreamBytesHandler
+
+ setup do
+ new :cowboy_req
+ on_exit fn -> unload end
+ :ok
+ end
+
+ test "malformed_request returns true if n is not an integer" do
+ expect(:cowboy_req, :binding, [{[:n, :req1], {"a2B=", :req2}}])
+
+ expect(:cowboy_req, :qs_val, fn name, req, default ->
+ case {name, req, default} do
+ {"seed", :req2, "1234"} -> {"1234", :req3}
+ {"chunk_size", :req3, "1024"} -> {"1024", :req4}
+ end
+ end)
+
+ assert malformed_request(:req1, :state) == {true, :req4, :state}
+
+ assert validate :cowboy_req
+ end
+
+ test "malformed_request returns false if n is an integer" do
+ expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])
+
+ expect(:cowboy_req, :qs_val, fn name, req, default ->
+ case {name, req, default} do
+ {"seed", :req2, "1234"} -> {"1234", :req3}
+ {"chunk_size", :req3, "1024"} -> {"1024", :req4}
+ end
+ end)
+
+ assert malformed_request(:req1, :state) == {false, :req4, {2, 1234, 1024}}
+
+ assert validate :cowboy_req
+ end
+
+ test "malformed_request returns true if seed is not an integer" do
+ expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])
+
+ expect(:cowboy_req, :qs_val, fn name, req, default ->
+ case {name, req, default} do
+ {"seed", :req2, "1234"} -> {"a2B=", :req3}
+ {"chunk_size", :req3, "1024"} -> {"1024", :req4}
+ end
+ end)
+
+ assert malformed_request(:req1, :state) == {true, :req4, :state}
+
+ assert validate :cowboy_req
+ end
+
+ test "malformed_request returns false if seed is an integer" do
+ expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])
+
+ expect(:cowboy_req, :qs_val, fn name, req, default ->
+ case {name, req, default} do
+ {"seed", :req2, "1234"} -> {"7", :req3}
+ {"chunk_size", :req3, "1024"} -> {"1024", :req4}
+ end
+ end)
+
+ assert malformed_request(:req1, :state) == {false, :req4, {2, 7, 1024}}
+
+ assert validate :cowboy_req
+ end
+
+ test "malformed_request returns true if chunk_size is not an integer" do
+ expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])
+
+ expect(:cowboy_req, :qs_val, fn name, req, default ->
+ case {name, req, default} do
+ {"seed", :req2, "1234"} -> {"1234", :req3}
+ {"chunk_size", :req3, "1024"} -> {"a2B=", :req4}
+ end
+ end)
+
+ assert malformed_request(:req1, :state) == {true, :req4, :state}
+
+ assert validate :cowboy_req
+ end
+
+ test "malformed_request returns false if chunk_size is an integer" do
+ expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])
+
+ expect(:cowboy_req, :qs_val, fn name, req, default ->
+ case {name, req, default} do
+ {"seed", :req2, "1234"} -> {"1234", :req3}
+ {"chunk_size", :req3, "1024"} -> {"13", :req4}
+ end
+ end)
+
+ assert malformed_request(:req1, :state) == {false, :req4, {2, 1234, 13}}
+
+ assert validate :cowboy_req
+ end
+
+ test "response must stream chunks" do
+ assert {{:chunked, func}, :req1, nil} = get_bytes(:req1, {9, 3, 4})
+ assert is_function(func)
+
+ send_func = fn(body) -> send(self, {:chunk, body}) end
+ func.(send_func)
+
+ assert_receive {:chunk, chunk1}
+ assert_receive {:chunk, chunk2}
+ assert_receive {:chunk, chunk3}
+
+ assert String.length(chunk1) == 4
+ assert String.length(chunk2) == 4
+ assert String.length(chunk3) == 1
+ end
+end
\ No newline at end of file
diff --git a/test/stream_handler_test.exs b/test/stream_handler_test.exs
index 632fb78..ce2ff5b 100644
--- a/test/stream_handler_test.exs
+++ b/test/stream_handler_test.exs
@@ -11,7 +11,7 @@ defmodule HTTParrot.StreamHandlerTest do
:ok
end
- test "malformed_request returns false if it's not an integer" do
+ test "malformed_request returns true if it's not an integer" do
expect(:cowboy_req, :binding, [{[:n, :req1], {"a2B=", :req2}}])
assert malformed_request(:req1, :state) == {true, :req2, :state}