How to get response without request – Turbolinks over Sockets

I’m working on a library that does something similar to Turbolinks, but uses virtual-dom and vdom-parser to diff the requested page and patch it, rather than swapping out the body. You can see a draft / proof of concept here, which has yet to be refactored out into a standalone, reusable library.

However, to maximize the speed on this, I want the page to be fetched over sockets / channels, which will also be the mechanism for prefetching, predictive prefetching, etc.

What I can’t figure out is, in Phoenix, how to generate the HTML response for a particular endpoint without going actually generating an HTTP request.

I want something like

defmodule MyApp.TurboChannel do
  use Phoenix.Channel

  def join("turbo", _message, socket) do
    {:ok, socket}
  end

  def handle_in("url", %{"url" => url}, socket) do
     # Look here! Look here!
     html = FunctionICantFigureOut.fetch(url)
     push socket, url, %{"html" => html}
    {:noreply, socket}
  end
end

Does anyone know of an easy way to do this before I go reading Phoenix’s source code?

Thanks! I’m new to Elixir + Phoenix and really looking forward to contributing to the ecosystem!

The templates in Phoenix are generated as function in the requisite *View module, so say you have a PageView that links to certain eex template for the page section, you can just call the PageView.render function passing in the normal bindings and all, without a request. :slight_smile:

The phoenix controller render function just looks up which view to use in the plug (which you can call the view directly there too if you wanted anyway) and just calls it’s render function with the args that you pass in then takes the result and stuffs it into the output for the socket. :slight_smile:

You can call the controller function directly if you artificially generate a plug as well without creating a full http request too.

EDIT: If however you are really looking up full http URL’s and need to process it all as normal, you may just need to call into the system via a local request or so.

Thanks, @OvermindDL1 – Ok, I think I’ll give artificially generating a plug a try – by that, you mean just fudging all of the fields I need in the conn / params struct and map?

I think I’ll be trying to get the conn from the socket, and I’ll pass the endpoint and params over the channel.

The last component of this is how to know what controller and what controller method maps to which endpoint – it’s obviously in my router.ex, but how can my code know what the proper controller method is without copying my router.ex to a map or something?

1 Like

The router is just a plug in the pipeline. If you know the Endpoint module (i.e. MyApp.Web.Endpoint), then you should be able create a %Plug.Conn{} with the correct info and run it through. The response from that function should be a new conn struct that has your response. You’ll have to dig around to find how how to do this, but its done quite commonly in testing, so I’d check out how that works and you may find your answer.

1 Like

For testing this is an acceptable trade off. Using the same approach in production code may come across as “clever” (not in a good way: “superficially skillful”, “smart in a superficial way”).

What I’m seeing is two separate scenarios that need to essentially reuse content generation functionality that is currently “trapped” (dare I say tightly coupled) in the HTTP response generation functionality. In my mind the appropriate response is to refactor. Refactor - so that the same content generation logic can be effectively reused when generating an HTTP response for Plug - or when generating a page fragment to be shipped off by some other means (but taking Plug out of the picture).

Using Plug when Plug isn’t needed is an invitation for accidental complexity.

1 Like