From 4eb0871cefa6201f91322b6b4d3413642ef92cea Mon Sep 17 00:00:00 2001 From: jordan0day Date: Mon, 19 Jan 2015 13:48:47 -0600 Subject: [PATCH 1/3] add a stream-bytes/:n endpoint & tests --- lib/httparrot.ex | 1 + lib/httparrot/stream_bytes_handler.ex | 43 ++++++++++ test/stream_bytes_handler_test.exs | 117 ++++++++++++++++++++++++++ test/stream_handler_test.exs | 2 +- 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 lib/httparrot/stream_bytes_handler.ex create mode 100644 test/stream_bytes_handler_test.exs 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/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} From 362150a94a349bd8345f47853a0b1c9b9e61a2f2 Mon Sep 17 00:00:00 2001 From: jordan0day Date: Tue, 20 Jan 2015 09:27:37 -0600 Subject: [PATCH 2/3] forgot to update index.html with link to stream-bytes functionality --- priv/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/priv/index.html b/priv/index.html index 5a5a23e..ab3e875 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.
  • From 4d75571fd16a1d91aaf545b8d609bc4351584d2c Mon Sep 17 00:00:00 2001 From: Jordan Day Date: Tue, 20 Jan 2015 10:10:29 -0600 Subject: [PATCH 3/3] whoopsie --- priv/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/index.html b/priv/index.html index ab3e875..42f9218 100644 --- a/priv/index.html +++ b/priv/index.html @@ -86,7 +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. +
  • /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.