busyloop / rucksack

Attach static files to your compiled crystal binary and access them at runtime.
MIT License
53 stars 5 forks source link

Problem serving a html file when it points in turn to js files (assets) #6

Closed serge-hulne closed 1 year ago

serge-hulne commented 1 year ago

The following file

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Svelte + TS</title>
    <script type="module" crossorigin src="/assets/index-c88b134a.js"></script>
    <link rel="stylesheet" href="/assets/index-9ea02431.css">
  </head>
  <body>
    <div id="app"></div>    
  </body>
</html>

refers to images, .CSS and .JS files which are structured as follows:

Screenshot 2023-02-04 at 11 28 37

When I try to use rucksack to serve the files in this webfoot folder (which have been packed by rucksack), I get the following error (in the browser's debug tools):

Loading module from “http://127.0.0.1:8080/assets/index-c88b134a.js” was blocked because of a disallowed MIME type (“”).
Screenshot 2023-02-04 at 11 37 40

The code I have been using is:

require "http/server"
require "rucksack"

server = HTTP::Server.new do |context|
  path = context.request.path
  path = "/index.html" if path == "/"
  path = "./webroot#{path}"

  begin
    # Here we read the requested file from the Rucksack
    # and write it to the HTTP response. By default Rucksack
    # falls back to direct filesystem access in case the
    # executable has no Rucksack attached.
    rucksack(path).read(context.response.output)
  rescue Rucksack::FileNotFound
    context.response.status = HTTP::Status.new(404)
    context.response.print "404 not found :("
  end
end

address = server.bind_tcp 8080
puts "Listening on http://#{address}"
server.listen

# Here we statically reference the files to be included
# once - otherwise Rucksack wouldn't know what to pack.
{% for name in `find ./webroot -type f`.split('\n') %}
  rucksack({{name}})
{% end %}

Is it possible to modify this server so as to also serve the JS and CSS files associated with the index.html file?

m-o-e commented 1 year ago

Yes. Your browser refuses to load these files because the webserver snippet is meant only for demonstration purposes and doesn't include a Content-Type header in its responses.

But you can add that with just two lines:

require "http/server"
require "rucksack"
+ require "mime"

server = HTTP::Server.new do |context|
  path = context.request.path
  path = "/index.html" if path == "/"
  path = "./webroot#{path}"

  begin
    # Here we read the requested file from the Rucksack
    # and write it to the HTTP response. By default Rucksack
    # falls back to direct filesystem access in case the
    # executable has no Rucksack attached.
+   context.response.content_type = MIME.from_filename(path)
    rucksack(path).read(context.response.output)
  rescue Rucksack::FileNotFound
    context.response.status = HTTP::Status.new(404)
    context.response.print "404 not found :("
  end
end

address = server.bind_tcp 8080
puts "Listening on http://#{address}"
server.listen

# Here we statically reference the files to be included
# once - otherwise Rucksack wouldn't know what to pack.
{% for name in `find ./webroot -type f`.split('\n') %}
  rucksack({{name}})
{% end %}

Enjoy! :)

serge-hulne commented 1 year ago

Thank you very much!

That allows one to server a single page app without having to ship a lot of files.

My aim is to build self contained, single file apps using Svelte, Webview and Crystal:

https://github.com/serge-hulne/CrystApp

Therefore Rucksack is an essential component :-)

m-o-e commented 1 year ago

That looks like fun, good luck with it!

And yes, for local use the above snippet should indeed be perfectly fine. :+1:

The builtin crystal webserver is very fast and if the app is not going to be served to the internet you probably won't need the fancier features of fully fledged web-frameworks like kemal or athena. But in case you ever do need them then rucksack can of course be paired with those as well. :)