Can I reroute a request without using redirect?

So I’m trying to write my own custom Soap service using Phoenix, and I’m running into trouble rerouting a post request using the soapaction in the request header. Basically I have an endpoint called SomethingService.svc and the client that’s calling is adding a header like “soapaction” : “ServiceV2/Service/GetDownloadAccessByLocation”.

I want to change the request_path via a plug to /soap/GetDownloadAccessByLocation before the router sends it to SomethingService.svc, but nothing I do seems to work. Here’s the plug I’ve written so far:

defmodule SomethingWeb.SoapRouterPlug do
  import Plug.Conn
  require Logger

  def init(options) do
    options
  end

  def call(conn, _opts) do
    conn
    |> put_resp_content_type("text/xml")
    |> set_new_route()
  end

  defp set_new_route(conn) do
    [header] = Plug.Conn.get_req_header(conn, "soapaction")
    soap_action = String.trim(header, "\"")

    {new_path, new_path_info} = case soap_action do
      "ServiceV2/Service/GetDownloadAccessByLocation" -> {"/soap/GetDownloadAccessByLocation", "soapGetDownloadAccessByLocation"}
      _ -> {conn.request_path, conn.path_info}
    end

     Logger.info("Updated request path to #{new_path} with new path_info #{new_path_info}")

    Map.replace(conn, :request_path, new_path)
    |> Map.replace(:path_info, new_path_info)
  end

end

Even though I’m replacing the request_path and path_info in the conn it still sends the request to /soap/SomeService.svc

[info] POST /soap/SomethingService.svc
[debug] ** (Phoenix.Router.NoRouteError) no route found for POST /soap/SomethingService.svc (SomethingWeb.Router)

Hmmm, I’m thinking here, maybe trying to use Phoenix for it just complicates things more. Phoenix was made with REST APIs in mind, trying to match a route by a request header like you are doing is not something they expect you would do.

Maybe try using Plug only? So then you can match your requests by the header and send to the correct plug.

1 Like

So I tried this out in a very small new app by doing: mix phx.new something --no-ecto --no-html --no-webpack --no-gettext --no-dashboard. Then I plugged (no pun intended) your posted plug code into it as is and ran it. Conclusion: it seems to work with a couple of very small changes! :slight_smile:

The phoenix router only reacts on the path_info part so you should set it to: ["soap", "GetDownloadAccessByLocation"] (and seems to be a list instead of a string)

But I think the real issue here was the position of your plug, from where do you call it?
I’ve put it inside of the endpoint just before calling the router like this:

defmodule SomethingWeb.Endpoint do
  # ...snip...
  plug SomethingWeb.SoapRouterPlug
  plug SomethingWeb.Router
end

To be able to easy replicate what I’ve done, here is your plug code with my changes:

defmodule SomethingWeb.SoapRouterPlug do
  import Plug.Conn
  require Logger

  def init(options) do
    options
  end

  def call(conn, _opts) do
    conn
    |> put_resp_content_type("text/xml")
    |> set_new_route()
  end

  defp set_new_route(conn) do
    [header] = Plug.Conn.get_req_header(conn, "soapaction")
    soap_action = String.trim(header, "\"")

    {new_path, new_path_info} =
      case soap_action do
        "ServiceV2/Service/GetDownloadAccessByLocation" ->
          {"/soap/GetDownloadAccessByLocation", ["soap", "GetDownloadAccessByLocation"]}

        _ ->
          {conn.request_path, conn.path_info}
      end

    Logger.info("Updated request path to #{new_path} with new path_info #{inspect(new_path_info)}")

    Map.replace(:request_path, new_path)
    |> Map.replace(:path_info, new_path_info)
  end
end

Hopefully this works for you. Goodluck!

1 Like

I think I’m going to forego changing the route, and just do the soap stuff from one controller. I don’t want to put the plug so close to the beginning because I also have to support Rest API calls and I don’t want to make this more complex than it has to be for future devs. Thanks for looking at this though.