content-type content-disposition

341 views
Skip to first unread message

SteveB

unread,
Feb 23, 2015, 2:42:16 PM2/23/15
to clojure-...@googlegroups.com
Hello, 

I'm currently returning csv data from a liberator web-service with a {:representation {:media-type "application/csv"}} which causes the browser to download as a file.

When my URL is localhost:3000/my-service?param=xyz  the file downloaded is "my-service"

I'd like to give the hint to the browser to use data.csv as the file name.

Where can I put this in the response header?  Is there liberator docs on this topic?

Something like ...

{:content-type "application/csv; charset=utf-8" :content-disposition "attachment; filename=data.csv"}

Thank you!
Steve



SteveB

unread,
Feb 23, 2015, 3:34:08 PM2/23/15
to clojure-...@googlegroups.com
After seeing "add extra custom response headers" post from 12/18/2014. I figured it out.

(assoc-in [:headers "Content-Disposition"] "attachment; filename=data.csv")

The docs are here https://clojure-liberator.github.io/liberator/doc/representations.html

SteveB

unread,
Feb 23, 2015, 6:00:30 PM2/23/15
to clojure-...@googlegroups.com
After I added :as-response to add in the headers to name the downloaded file, I'm getting an java.lang.Exception Unrecognized body: {:invalid-results {:extra-params #{"formdat"}, :missing-params #{"format"}}
when I have invalid parameters in my URL.

I'm thinking now :malformed? and :handle-malformed are not the correct place to do the checks I'm doing, but let's see..

I have a :malformed? function which returns a Vector indicating the Liberator if the URL is malformed or not. i.e. [true {:my-invalid-results  {:error1 "some-error}  :representation {media-type "application/json"} ]

true tells Liberator that the URL is malformed and to therefore call the :handle-malformed function.

In handle-malformed I was simply pulling out some-data from the map and returning it. (ctx-map :my-invalid-results) This worked until I added :as-response

Worked meaning the map was formatted and shown in the browser as a table, to indicate to the caller was is wrong when calling the restful service.

After adding the :as-response function, I see the return value of handle-malformed is (the map) is passed as the first param (i.e. body) to this function yielding the Exception noted above. It seems it's expecting a String as the body.

Here's a boiled down negative case example:

(defresource testit
:available-media-types ["text/plain"]
:malformed? (fn [ctx]
[
true ; true = the request is malformed
{:invalid-results {:extra-params #{"formdat"}, :missing-params #{"format"}} :representation {:media-type "text/html"}}])
:handle-malformed (fn [ctx]
(println "In handle-malformed
= " (ctx :invalid-results))
                      (ctx :invalid-results))
  :handle-ok (fn [ctx]
               "some,csv,data")
:as-response (fn [resp-body ctx]
(println "In as-response = " resp-body)
(let [media-type "csv"] ; declare media-type for demo purposes only
(if (= media-type "csv")
(-> {:body resp-body}
(assoc-in [:headers "Content-Disposition"] "attachment; filename=data.csv"))
{:body resp-body}))))

With :as-response I get...

In handle-malformed =  {:extra-params #{formdat}, :missing-params #{format}}
In as-response =  {:extra-params #{formdat}, :missing-params #{format}}
2015-02-23 17:38:12 WARN  AbstractHttpConnection:521 - /testit
java.lang.Exception: Unrecognized body: {:extra-params #{"formdat"}, :missing-params #{"format"}}
        at ring.util.servlet$set_body.invoke(servlet.clj:103)
        at ring.util.servlet$update_servlet_response.invoke(servlet.clj:113)
        at ring.adapter.jetty$proxy_handler$fn__97.invoke(jetty.clj:22)
        at ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
        at org.eclipse.jetty.server.Server.handle(Server.java:369)
        at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:486)
        at org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:933)
        at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:995)
        at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:644)
        at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235)
        at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
        at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:668)
        at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
        at java.lang.Thread.run(Thread.java:745)

If I comment the :as-response out, I see the map nicely formatted as html.. in the browser.

extra-params#{"formdat"}
missing-params#{"format"}

How can I add in the filename in as-response when needed and to have error checking and pass back what is wrong with the url (currently in map form, but married to it)?

Thanks,
Steve

Philipp Meier

unread,
Feb 24, 2015, 5:50:47 AM2/24/15
to clojure-...@googlegroups.com
Hi Steve,


Am Dienstag, 24. Februar 2015 00:00:30 UTC+1 schrieb SteveB:
After I added :as-response to add in the headers to name the downloaded file, I'm getting an java.lang.Exception Unrecognized body: {:invalid-results {:extra-params #{"formdat"}, :missing-params #{"format"}}
when I have invalid parameters in my URL.
[...] 
The function "as-response" must return a proper ring response map. 

-billy. 

SteveB

unread,
Feb 24, 2015, 9:37:32 AM2/24/15
to clojure-...@googlegroups.com
Thanks Billy that worked for the example. 

:as-response (fn [resp-body ctx]
(println "In as-response = " resp-body)
(let [media-type "csv"] ; declare media-type for demo purposes only
(if (= media-type "csv")
(-> {:body resp-body}
(assoc-in [:headers "Content-Disposition"] "attachment; filename=data.csv")
                       (assoc-in [:status] 200))
                     {:body resp-body :status 200}))))

It seems the as-response function is going down a level in the abstraction from Liberator down to Ring and is called after handle-ok and handle-malformed which both return there own status 200 and 400 respectively. By adding :as-response I won't want to have to manage state of the status code. Is there a way to get this code from Liberator since as-response just was called by an upstream method that set this somewhere?  Or is it expected to assoc :status 200 from handle-ok and 400 from handle-malformed when using :as-response?

WfG,
Steve

SteveB

unread,
Feb 24, 2015, 10:39:13 AM2/24/15
to clojure-...@googlegroups.com
I think this example answers my question..

(ns foo.core
(:require [liberator.core :refer [defresource]]
[liberator.representation :refer [as-response]]))

(defresource example
:available-media-types ["text/plain"]

:as-response (fn [d ctx]
(-> (as-response d ctx) ; wrap the map returned from malformed? as a response
(assoc-in [:headers "X-FOO"] "Bar"))) ; add on some extra headers.

:handle-ok (fn [_] 
"test") ; never gets called since malformed is always true.
               
:malformed? (fn [ctx]
[true ; hard code a negative case - malformed true
                 {:invalid-results {:extra-params #{"formdat"}, :missing-params #{"format"}} :representation {:media-type "text/html"}}])

:handle-malformed (fn [ctx]
(ctx :invalid-results)) )
; return the unwrapped invalid results
Reply all
Reply to author
Forward
0 new messages