I'm trying to send email from Clojure using the following code:
Helper function that sends email:
(defn- send [recipient from subject msg host content-type & attachments]
(let [att-ds (map (fn [at] {:ds (ByteArrayDataSource. (:source at)
(:type at))
:name (:name at)
:description (:description at)})
attachments)
mpmail (MultiPartEmail.)]
(doto mpmail
(.setHostName host)
(.setFrom from)
(.setSubject subject)
(.setContent msg content-type)
(.setCharset "utf-8"))
(.addTo mpmail recipient)
(doseq [ds att-ds]
(.attach mpmail (:ds ds) (:name ds) (:description ds)))
(.send mpmail)))
Usage:
(send "sender#my.domain"
"recipient#my.domain"
"SUBJECT"
"MSG"
"my.smtp.server"
"text/plain"
{:source (.getBytes "Attachment")
:type "text/plain"
:name "test.txt"
:description "test"})
Running the above from the REPL (or from my app) results in recipient#my.domain receiving an email with the subject "SUBJECT" and body "MSG" but without any traces of attachment. No exceptions are raised anywhere.
I have tried this with two different smtp servers.
Thanks for any help.
Try replace (.setContent msg) with (.setMsg msg). May be when you call setContent it thinks you manually set content and ignores following attach methods.
Try to use my code snippet http://middlesphere-1.blogspot.ru/2014/11/clojure-how-to-send-mail-with-attachment.html
attachment filename can be in unicode.
Related
I'm getting a baffling behavior when using Ring where POST requests all give "Invalid anti-forgery token" but only do this when I have the handler behind a function.
So,
(def app app-routes)
works fine.
Whereas
(defn app [args]
(app-routes args))
throws anti-forgery errors for POST requests.
Minimal code example. :
(defroutes app-routes
(GET "/login" request (fn [req]
(html5
[:body
[:div
[:form {:action "/login" :method "post"}
(anti-forgery-field)
[:input {:name "username"}]
[:input {:name "password"}]
[:button {:type "submit"} "submit"]]]])))
(POST "/login" request (fn [req] "Yay!")))
; WORKS A-OK like this
(def app (-> app-routes (wrap-routes site-defaults)))
In my project.clj
:plugins [[lein-ring "0.12.5"]]
:ring {:handler myapp.application/app
But again, if I change this to be a function:
(defroutes app-routes
(GET "/login" request (fn [req]
(html5
[:body
[:div
[:form {:action "/login" :method "post"}
(anti-forgery-field)
[:input {:name "username"}]
[:input {:name "password"}]
[:button {:type "submit"} "submit"]]]])))
(POST "/login" request (fn [req] "Yay!")))
; ALL POST REQUESTS FAIL
(defn app [args]
(let [handler (-> app-routes (wrap-routes site-defaults)))]
(handler args)))
the all post requests fail due to forgery tokens. All GET requests work fine.
Stranger still: I thought maybe it didn't like being re-initialized, so I stuffed it into an atom to make it more singleton-like
(def handler (atom nil))
(defn app [args]
(swap! handler #(when (nil? %)
(-> app-routes
(wrap-defaults site-defaults))))
(#handler args))
Now GET and POST requests work as expected. Except now wrap-reload (not pictured above) throws exceptions!
I have no idea why it is behaving this way, and have been completely unable to debug. Could anyone shed some light?
Edit:
For context on why I'm trying to wrap it at all: I want to be able to wire up dependencies (like database connections) and then inject them into the controllers used by the routes .
I'm not an expert on wrap-routes, but there are clear clues to your problem.
In method #1 with def, you create a handler and assign it to the var attached to the symbol app.
In method #2 (failing), you define a function app. Since handler is now in the let form, you are creating a new handler on each function call. I'd wager that is the source of the problem.
In method #3, the handler atom is only initialized upon the first call, and never changed after that (because of the nil? test).
Since it works in #1 and #3 when the handler is only created once, it seems clear that creating a new handler on each call in #2 is the problem. Since the anti-forgery token is basically a random number that is different on each call to wrap-routes, you never have the same token used by the POST that was generated by the GET route.
Re your larger goal: You may find it easier to use the Pedestal framework to inject the desired dependencies into the request. Or, you could write a simple Compojure handler (really just a function that takes a handler and returnes a wrapped version of that handler). Probably only 5-10 lines of code.
You will also probably like the book Web Development with Clojure.
Here is a list of other documentation.
I'm using the last available lacinia version: "0.36.0-alpha-3" with Luminus (Ring+reitit), but this version asks for a specific header:
$ curl 'http://localhost:3000/api/graphql' -X POST --data "{test_by_id(id: 5) { title } }" -H 'Content-Type: application/graphql'
that request works fine, but without "'Content-Type: application/graphql'" the request wouldn't work. So I need to define my re-graph init vector like:
[::re-graph/init
{:ws-url nil
:http-url "http://localhost:3000/api/graphql"
:http-parameters {:with-credentials? false
:headers {"Content-Type" "application/graphql"}
}
:ws-reconnect-timeout nil
:resume-subscriptions? false
:connection-init-payload {}}]
but putting that header makes re-graph unable to work properly:
{"errors":[{"message":"Failed to parse GraphQL query.","extensions":{"errors":[{"locations":[{"line":1,"column":null}],"message":"mismatched input '\"query\"' expecting {'query', 'mutation', 'subscription',
it looks like re-graph sends and receives data using "application/json" header, so lacinia asks for some type of header but re-graph can't work with that option.
I had the same problem, and I think I got a solution for it. re-frame requests follows the Apollo Specification, as stated by #aarkerio. Here is the code to keep the original endpoint working with the origina specification, and allow it to respond to re-frame requests. This will make the endpoint respond to Graphiql request (from your http://localhost:3000/graphiql route), and re-graph ones. Any comments or corrections are welcomed.
Replace the original function set on the /graphql route on src/clj/mem_learning/routes/services.clj:
["/graphql" {:post graphql-call}
Add the graphql-call function on that same file:
(defn graphql-call [req]
(let [body (:body-params req)
content-type (keyword (get-in req [:headers "content-type"]))]
(case content-type
:application/json (ok (graphql/execute-request-re-graph body))
:application/graphql (ok (graphql/execute-request (-> req :body slurp))))))
add the execute-request-re-graph to the src/clj/mem_learning/routes/services/graphql.clj file:
(defn execute-request-re-graph
"execute request with re-graph/apollo format"
[{:keys [variables query context]}]
(lacinia/execute compiled-schema query variables context)))
ANSWER:
It looks that Luminus creates a middleware configuration:
(defn service-routes []
["/api"
{:coercion spec-coercion/coercion
:muuntaja formats/instance
:swagger {:id ::api}
:middleware [;; query-params & form-params
parameters/parameters-middleware
;; content-negotiation
muuntaja/format-negotiate-middleware
;; encoding response body
muuntaja/format-response-middleware
;; exception handling
exception/exception-middleware
;; decoding request body
muuntaja/format-request-middleware
;; coercing response bodys
coercion/coerce-response-middleware
;; coercing request parameters
coercion/coerce-request-middleware
;; multipart
multipart/multipart-middleware
]}
commenting the line "muuntaja/format-negotiate-middleware" makes the "application/json" call possible.
SECOND UPDATE (four hours later)
Ok, that muuntaja middleware thing was not the problem at all, the real problem is that curl send the data with the format:
{ test_by_id(id: 7, archived: false) { title } }
meanwhile re-graph uses:
{"query":"query { test_by_id(id: 7, archived: false) { title } }","variables":null}
this is a normal java string btw not a data structure, so we need to do some changes, first a new function:
(defn graphql-call [req]
(let [body (-> req :body slurp)
full-query (json/read-str body :key-fn keyword)
_ (log/info (str ">>> **** full-query >>>>> " full-query))]
(ok (graphql/execute-request full-query))))
we set the function:
["/graphql" {:post graphql-call}]
and in my_app.routes.services.graphql file:
(defn execute-request [{:keys [variables query context]}]
(json/write-str (lacinia/execute compiled-schema query variables context)))
and now re-graph works!
(also now I can send and use variables in GraphQL)
It's necessary to set:
:http-parameters {:with-credentials? false
:oauth-token "ah4rdSecr3t"
:headers {"Content-Type" "application/graphql"}
btw. Also, maybe it's better:
(lacinia/execute compiled-schema query variables context)
than:
(json/write-str (lacinia/execute compiled-schema query variables context))
because it interferes with re-graph importing the data already as a native ClojureScript map.
I am using http-kit as the server with wrap-json-body from ring.middleware.json to get the stringified JSON content sent from the client as the request body. My core.clj is:
; core.clj
; ..
(defroutes app-routes
(POST "/sign" {body :body} (sign body)))
(def app (site #'app-routes))
(defn -main []
(-> app
(wrap-reload)
(wrap-json-body {:keywords? true :bigdecimals? true})
(run-server {:port 8080}))
(println "Server started."))
When I run the server using lein run the method works correctly. I am stringifying the JSON and sending it from the client. The sign method gets the json correctly as {"abc": 1}.
The problem is when during mock test. The sign method gets a ByteArrayInputStream and I am using json/generate-string to convert to string which fails in this case. I tried wrapping the handler in wrap-json-body but it is not work. Here are my test cases I tried out core_test.clj:
; core_test.clj
; ..
(deftest create-sign-test
(testing "POST sign"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test1
(testing "POST sign1"
(let [response (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test2
(testing "POST sign2"
(let [response (core/app (-> (mock/body (mock/request :post "/sign")
(json/generate-string {:user 1}))
(mock/content-type "application/json")))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test3
(testing "POST sign3"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" {:headers {"content-type" "application/json"}
:body "{\"foo\": \"bar\"}"}))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
All of the fails with the following error:
Uncaught exception, not in assertion.
expected: nil
actual: com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.ByteArrayInputStream: java.io.ByteArrayInputStream#4db77402
How can I pass a JSON string as the body to the method in ring mock test?
There are three issues in your code.
Your test doesn't wrap your app handler in wrap-json-body so it might not get correctly parsed request body in your handler. You need to first wrap your app in wrap-json-body and then call it with your mock request. (You could also have your app handler to be already wrapped instead of wrapping it both in your main function and tests)
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true})]
(handler your-mock-request))
Your mock request doesn't include proper content type and your wrap-json-body won't parse your request body to JSON. That's why your sign function gets ByteArrayInputStream instead of parsed JSON. You need to add content type to your mock request:
(let [request (-> (mock/request :post "/sign" "{\"username\": \"jane\"}")
(mock/content-type "application/json"))]
(handler request))
Verify that your sign function returns a response map with JSON as string in body. If it creates response body as input stream you need to parse it in your test function. Below I am using cheshire to parse it (converting JSON keys to keywords):
(cheshire.core/parse-stream (-> response :body clojure.java.io/reader) keyword)
Additionally instead of writing your JSON request body by hand you can use Cheshire to encode your data into JSON string:
(let [json-body (cheshire.core/generate-string {:username "jane"})]
...)
With those changes it should work correctly like in my slightly modified example:
(defroutes app-routes
(POST "/echo" {body :body}
{:status 200 :body body}))
(def app (site #'app-routes))
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true}))
json-body (json/generate-string {:username "jane"})
request (-> (mock/request :post "/echo" json-body)
(mock/content-type "application/json"))
response (handler request)]
(is (= (:status response) 200))
(is (= (:body response) {:username "jane"})))
I follow the example code of book web development with clojure, 2nd edition and I have a problem with the file upload with google closure.
I test the file upload with Swagger and it response me 200 ok, I think the error is from the upload-file! function.(see below).
But I look up the closure api doc, it seems I use the correct function.
So I was in a trouble, I don't know why it doesn't work...
I need someone help.Here is my code(I use semantic-ui for ui components):
(defn upload-file! [upload-form-id status]
(reset! status nil)
(let [io (IframeIo.)]
(gev/listen
io goog.net.EventType.SUCCESS
#(reset! status [c/success-message "file uploaded successfully"]))
(gev/listen
io goog.net.EventType.ERROR
#(reset! status [c/warning-message "failed to upload the file"]))
(.setErrorChecker io #(= "error" (.getResponseText io)))
(.sendFromForm io (.getElementById js/document upload-form-id) "/upload")))
(defn upload-form []
(let [status (atom nil)
form-id "upload-form"]
(fn []
[c/modal
[:div "Upload File"]
[:div
(when #status #status)
[:div.ui.form
{:id form-id
:enc-type "multipart/form-data"
:method "POST"}
[:label {:for "file"} "select an image for upload"]
[:input {:id "file"
:name "file"
:type "file"}]]]
[:div
[:button.ui.primary.button
{:on-click #(upload-file! form-id status)}
"upload"]
[:button.ui.red.button
{:on-click #(do
(.modal (js/$ ".ui.modal") "hide")
(reset! status nil))}
"Cancel"]]
"upload"])))
the components:
(defn modal [header content footer id]
[:div.ui.modal
{:id id}
[:div.header header]
[:div.content content]
[:div.actions footer]])
(defn success-message [content]
[:div.ui.green.message
[:div.header content]])
So I solved my question, I should type [:form:ui.form] rather than [:div.ui.form].
if you interest in the code, you can view this URL:
upload image code
I have a clojure / compojure webapp with the following routes
(defroutes my-routes
(GET "/app/preview" request (my-preview-function request))
(ANY "*" request (str "ANY page <br>" (request :params))))
The preview GET request is made with a couple of parameters. I find this works most of the time but sometimes the /ebook/preview is not found and processing drops to the ANY route, in which case the output is similar to this,
ANY page
{:* "/app/preview", :section "50", :id "48"}
Can anyone suggest what might cause the /ebook/preview request to be skipped? It is definitely a GET request being made; the HTML does not have a POST for the /app/preview URL and to be doubly sure I added a POST route for /app/preview and that was not being hit.
JAR versions:
Clojure 1.2
compojure-0.6.2
ring-core-0.3.7
jetty-6.1.14
ring-jetty-adapter-0.3.1
ring-servlet-0.3.1jar
servlet-api-2.5-6.1.14
Routes are wrapped as follows
(require '[compojure.handler :as handler])
(defn wrap-charset [handler charset]
(fn [request]
(if-let [response (handler request)]
(if-let [content-type (get-in response [:headers "Content-Type"])]
(if (.contains content-type "charset")
response
(assoc-in response
[:headers "Content-Type"]
(str content-type "; charset=" charset)))
response))))
(def app (-> my-routes
handler/site
wrap-stateful-session
(wrap-charset "utf-8")
(wrap-file "public")))
(defn run []
(run-jetty (var app) {:join? false :port 8080}))
If you're trying to figure out what request is causing the problems, stop throwing away the request map with (request :params) and just have a look at request. That will give you a map with all the information Compojure has; you can inspect it, and pass it back into your routes later to observe what happens (after you make some changes, say).
If
(my-preview-function request)
returns nil, then the routing will try the next route. Take a look at (source GET) and see how it matches (or doesn't) your route.