mtrudel / bandit

Bandit is a pure Elixir HTTP server for Plug & WebSock applications
MIT License
1.7k stars 85 forks source link

link type text/css interpreted as text/html and not loaded #401

Closed Rashidwi closed 1 month ago

Rashidwi commented 1 month ago

<link href="my_page.css" rel="stylesheet" type="text/css">

when served from bandit produces the following console:

The stylesheet http://192.168.1.1:4001/styles/my_page.css was not loaded because its MIME type, "text/html", is not "text/css".

When the same html is captured with curl and presented to a browser the css is correctly handled.

I've searched (and searched...) but I can find nothing about handling css links. I believe the problem is in my plug specifying text/html which is overriding the text/css in the code.

 defmodule Web2.MyPlug do
  import Plug.Conn

  def init(options) do
    # initialize options
    options
  end

  def call(conn, _opts) do
    html = '''
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="cache-control" content="no-cache">
        <meta charset="utf-8">
        <link href="my_page.css" rel="stylesheet" type="text/css">
        <title>Grisp2 testsite</title>
    </head>
    <body class="bg">
        <p class="pp">where's my css?</p>
    </body>
</html>
'''
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, html)
    |> IO.inspect
  end
end

Could some kind soul please point me in the right direction...

Rashidwi commented 1 month ago

For other newbies struggling with this or similar problems, my solution was to understand the nature of plugs and the sequence of network operations when a browser loads a web page. When you are creating the server, all the needs of the browser need to be serviced.

There is documentation, but it's very hard to piece together what is relevant, what seems to be missing is an overview of the process. With so many different packages involved: Bandit, Plug, ThousandIsland, Router not to mention Websocket, it's hard to know where to start reading. Two articles were very helpful here:

Jean-Hadrien Chabran Ilija Eftimov

My solution to the problem was an asset plug. Requests are checked for the file suffix and the correct mime type is set, as is the location of the asset (which means you don't need to have hard coded paths in the html or css)


defmodule AssetPlug do
    import Plug.Conn
    @behaviour Plug

    def init(opts) do
        opts
    end

    def get_mime(conn) do
         mimes = %{
            ".css" => %{mimetype: "text/css", path: "assets/css"},
            ".png" => %{mimetype: "image/png", path: "assets/graphics"},
            ".ico" => %{mimetype: "image/vnd.microsoft.icon", path: "assets/graphics"},
        }

        conn.path_info
        |> List.last
        |> Path.extname
        |> (&(Map.get(mimes, &1))).()
    end

    def call(conn, _opts) when conn.path_info != [] do
        case get_mime(conn) do
        nil ->
            conn
            |> send_resp(404, "mime type not found #{conn.request_path}")
            |> halt()

        %{mimetype: mime, path: path} ->
            f = Path.join(path, conn.request_path)
            case File.read(f) do       
            { :ok, data} ->
                conn
                |> put_resp_content_type(mime)
                |> send_resp(200,data)
                |> halt()

            { :error, reason} ->
                conn
                |> send_resp(404, inspect(reason))
                |> halt()
            end
        end
    end

    def call(conn, _opts) do
        conn
    end

I'm sure there are many improvements possible, It's not tuned for efficiency, I've simply got something working.

mtrudel commented 1 month ago

Thanks for the issue @Rashidwi! Glad you got it sorted (and left a helpful breadcrumb for future viewers! Thank you!) In the future, user issues such as this are better suited to Elixir forum or slack; it's a more interactive and 'help' based avenue rather than here, which we try to keep focused on actual issues with the library.