Created
August 31, 2016 17:00
-
-
Save jwietelmann/8c13e6be8009e2a6ff0ab2fcec520907 to your computer and use it in GitHub Desktop.
A rough sketch of a Phoenix router that uses an independent rendering service to create HTML from JSON views.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This plug takes HTML requests, forwards them to our controllers as JSON | |
# requests, takes the resulting JSON and POSTs it to a rendering service, | |
# then transforms the response back into HTML using the output from the | |
# rendering service. | |
# | |
# Which means we can write our probably-JavaScript/React view layer | |
# independently of Phoenix tooling. | |
defmodule ServiceRenderer do | |
# Base URL of render service goes here. | |
def init(url) do | |
url | |
end | |
# Apply to HTML requests only. | |
def should_apply?(conn) do | |
accept_header = List.first(Plug.Conn.get_req_header(conn, "accept")) || "" | |
String.contains?(accept_header, "text/html") | |
end | |
# Change the HTML request so it will be processed first as JSON. | |
def change_format_to_json(conn) do | |
Plug.Conn.put_private(conn, :phoenix_format, "json") | |
end | |
# Register the response transformation handler that will call the service. | |
def register_before_send(conn, url) do | |
hook = fn(conn) -> | |
before_send(conn, url) | |
end | |
Plug.Conn.register_before_send(conn, hook) | |
end | |
# Success. Replace the body and set the correct content type. | |
def handle_service_response(conn, {:ok, %HTTPoison.Response{status_code: 200, body: body}}) do | |
%{conn | resp_body: body} |> Plug.Conn.put_resp_header("Content-Type", "text/html") | |
end | |
# The service didn't have a view for us. | |
def handle_service_response(conn, {:ok, %HTTPoison.Response{status_code: 404}}) do | |
raise "Not Found." | |
end | |
# The service sent an unexpected or error code. | |
def handle_service_response(conn, {:ok, %HTTPoison.Response{status_code: status_code}}) do | |
conn | |
end | |
# Something bad happened. | |
def handle_service_response(conn, {:error, %HTTPoison.Error{reason: reason}}) do | |
raise "Error." | |
end | |
# The response transformation. | |
def before_send(conn, url) do | |
body = to_string(conn.resp_body) | |
headers = [{"Content-Type", "application/json"}] | |
service_response = HTTPoison.post(url, body, headers) | |
handle_service_response(conn, service_response) | |
end | |
# Plug API | |
def call(conn, url) do | |
if should_apply?(conn) do | |
conn |> change_format_to_json |> register_before_send(url) | |
else | |
conn | |
end | |
end | |
end | |
# Phoenix won't let us forward to the same router in different scopes. | |
# So I made this macro to let us apply the same routing rules to | |
# different routers. | |
defmodule Turbophoenix.UIRouter do | |
defmacro __using__(_opts) do | |
quote do | |
use Turbophoenix.Web, :router | |
scope "/", Turbophoenix do | |
# Define UI routes here | |
get "/", PageController, :index | |
end | |
end | |
end | |
end | |
# We'll forward all /jsonui prefixed paths to this one. | |
defmodule Turbophoenix.JSONRouter do | |
use Turbophoenix.UIRouter | |
end | |
# We'll forward the rest to this one. | |
defmodule Turbophoenix.HTMLRouter do | |
use Turbophoenix.UIRouter | |
end | |
defmodule Turbophoenix.Router do | |
use Turbophoenix.Web, :router | |
pipeline :htmlui do | |
plug :put_layout, false | |
plug :accepts, ["html"] | |
plug :fetch_session | |
plug :fetch_flash | |
plug :protect_from_forgery | |
plug :put_secure_browser_headers | |
plug ServiceRenderer, "http://localhost:8000" | |
end | |
pipeline :jsonui do | |
plug :put_layout, false | |
plug :accepts, ["json"] | |
end | |
# Any path prefixed with /jsonui will send raw JSON. | |
scope "/jsonui", Turbophoenix do | |
pipe_through :jsonui | |
forward "/", JSONRouter | |
end | |
# Any other path will pump JSON through our rendering service to get the HTML. | |
scope "/", Turbophoenix do | |
pipe_through :htmlui | |
forward "/", HTMLRouter | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment