dmac / spin

Write RESTful web apps in Racket.
MIT License
229 stars 18 forks source link

How to serve static files? #6

Open glsdesign opened 9 years ago

glsdesign commented 9 years ago

Hi, how to serve static files like .css files, or images to put in a web page with spin? And, it is advisable/efficient doing that (if it is possible), or it will be better (but not elegant...) serving static files with another light http only file server? Thanks for this and for answering.

dkvasnicka commented 9 years ago

Spin's run procedure passes its kw args to serve/servlet (docs), which means that it supports (or ought to support) serving static files using the #:extra-files-paths kw arg.

Try defining a path with (define-runtime-path static-files "static") and then passing it as that aforementioned kw arg, wrapped in a list: (list static-files)

glsdesign commented 9 years ago

Thanks for answering dkvasnicka. I can serve static files with serve/servlet without problems, but using your suggestion to do the same with spin doesn't work for me. Passing various parameters of serve/servlet to spin's "run" got unexpected results. I tried very hard to make a simple self-contained example of static file serving with spin to share/contribute to the doc, but I surely miss something important. I think this is an important severely undocument feature, I have to ask if someone can help. I am continuing experimenting in the meantime.

dkvasnicka commented 9 years ago

I think I've found the problem. The problem is that the #:servlet-regexp #rx"" interferes with serving static files, "hijacks" the requests and returns 404. When I change the regexp to something different, static files start to work but the rest of Spin behaves weirdly. This was not immediately apparent to me because this is not the case when you use URL-based dispatching (which is what I now use when doing a Racket web app). When the start arg passed to serve/servlet is created using dispatch-case serving static files works like a charm.

I think it shouldn't be rocket science to fix this but to be honest I'm currently satisfied with the web app facilities provided by Racket itself.

langston-barrett commented 9 years ago

Could this be fixed by simply adding an optional positional argument to run, namely one that gets passed as start to serve/servlet?

langston-barrett commented 9 years ago

Something like:

(define run
  (make-keyword-procedure
    (lambda (kws kw-args . etc)
      (cond
        [(< 1 (length etc)) ; change this to just check that there is only one
         (error 'run
                "serve/servlet only takes one positional argument, found ~a"
                (length etc))]
        [(ormap (curryr memq '(#:servlet-regexp #:command-line?)) kws)
         (error 'run
                "kw args may not include #:servlet-regexp or #:command-line?")]
        [else
         (let* ([kw-pairs (append '((#:servlet-regexp #rx"")
                                    (#:command-line? #t))
                                  (filter (lambda (kw-pair)
                                            (not (eq? '#:response-maker (car kw-pair))))
                                          (map list kws kw-args)))]
                [sorted-pairs (sort kw-pairs keyword<? #:key first)]
                [response-maker (let ([response-maker-pair
                                       (findf (lambda (p) (eq? (car p) '#:response-maker))
                                              (map list kws kw-args))])
                                  (if response-maker-pair
                                      (cadr response-maker-pair)
                                      default-response-maker))])
           (keyword-apply serve/servlet
                          (map first sorted-pairs)
                          (map second sorted-pairs)
                          (if (< 1 (length etc)) ; if there was a positional argument, pass it
                            (list (car etc)) 
                            (list (lambda (req)
                                    (request->handler req response-maker))))))]))))

perhaps?

I'm not sure about this at all, the code is not commented or documented, so it is difficult to figure out.

dkvasnicka commented 9 years ago

Something like this should work but then it seems to me that it doesn't make much sense to actually use Spin (because you'd do a lot of the crucial work in dispatch-case anyway). You might as well go with vanilla Racket webserver API, am I right?

langston-barrett commented 9 years ago

That's true. Could Spin's regex-based routing system be adapted to create dispatch-rules? That way, the framework does all the work, and static files could be routed accordingly (the best of both worlds!)

dkvasnicka commented 9 years ago

I believe it could and I'm sure the author won't turn his back on a nice pull request ;)

stephengaito commented 8 years ago

I have just added a pull request which adds a simple "get-file" function to provide spin based routing for the http "GET" method which serves binary files from the filesystem.

I have also provided a new section in the README.md file on the topic of serving binary files.

Note that there is a very simple way to hook into the standard Racket web-application, you simply need to ensure the #:response-maker keyword in the run function is provided with a response-maker which calls the (next-dispatcher) function. Please see the updated README.me for details.

Note that the Adding binary servlets pull request requires the previous Adding multi segment route parameters pull request.