How would i add an interceptor to "everything"
When building your service, you can specify interceptors that come before routing; these affect everything.
Disadvantage: may do work that is unnecessary, especially in case where incoming request can not be routed.
I'm thinking it makes sense to have my db pool as context accessible from an http request
@emccue if you mean having an interceptor execute prior to any handler irrespective of route then you can create a common-interceptors coll and add it there. The buddy-auth sample does this to ensure the authentication interceptors are executed for each route (https://github.com/pedestal/pedestal/blob/master/samples/buddy-auth/src/buddy_auth/service.clj#L74-L80)
How do I know which websocket session sent the message here? https://github.com/pedestal/pedestal/blob/master/samples/jetty-web-sockets/src/jetty_web_sockets/service.clj#L58
Seems like the callback only gets the message. Is there a way to change it?
thanks, I was just about to make my own listener
i was trying to find the slack logs, but looks like it's down now
this was the sample listener
(defn ws-listener
[_request _response ws-map]
(proxy [WebSocketAdapter] []
(onWebSocketConnect [^Session ws-session]
(proxy-super onWebSocketConnect ws-session)
(when-let [f (:on-connect ws-map)]
(f ws-session)))
(onWebSocketClose [status-code reason]
(when-let [f (:on-close ws-map)]
(f (.getSession this) status-code reason)))
(onWebSocketError [^Throwable e]
(when-let [f (:on-error ws-map)]
(f (.getSession this) e)))
(onWebSocketText [^String message]
(when-let [f (:on-text ws-map)]
(f (.getSession this) message)))
(onWebSocketBinary [^bytes payload offset length]
(when-let [f (:on-binary ws-map)]
(f (.getSession this) payload offset length)))))
;; in your service map:
::http/container-options {:context-configurator #(ws/add-ws-endpoints % ws-paths {:listener-fn ws-listener})}
nice, thanks @danvingo
no prob, I've been meaning to try this out too. would be great if you don't mind posting your working service setup when you have one
ok
@isak I hacked together the above sample, inspired by https://github.com/sunng87/ring-jetty9-adapter/blob/40aab6d0b91a7d7b63b9bf39e62e1f9801a02d3e/src/ring/adapter/jetty9/websocket.clj#L122 Hit me up if you cannot get it to work.
cool @hindol.adhya @danvingo, I just got it to work. Btw I noticed it is pretty hard to get a good reloading experience with the socket handlers, but here is one thing that works:
(I tried putting the var at a higher level, but didn't work)
Yeah Java code is calling into this code. Reloading should work with the right indirection in place.
i'm writing a 'not found' interceptor to send all requests that were not fulfilled to another backend server and passes through its response to the output. all works, except for requests w/ a really large body, the body gets cut off in the output. it's complete in the request though. when i println the request i can see the entire body. but the output is truncated. i'm using immutant though jetty does the same. what could be causing this?
(defn send-request
[request]
(println request)
(try
(let [params {:url (str (config/env :neo-api-url) (:uri request))
:body (:body request)
:method (:request-method request)
:socket-timeout 10000 ;; in milliseconds
:connection-timeout 1000
:headers (merge (:headers request)
{:authorization (str "Bearer " (config/env :neo-api-token))
:accept "application/vnd.ucf.v1+json"})}
response (client/request params)]
(println response)
response)
(catch Exception e
(timbre/warn "Could not reach API server" e))))
(def passthrough
{:name ::passthrough
:leave (fn [context]
(let [request (:request context)]
(assoc context :response (send-request request))))})
right
Hi, I have created a Gist with the code sample. If you find a clean way to make reloaded workflow work, please add here. https://gist.github.com/Hindol/854ed24594e2909abfae7d45cd8cf391
Couldn't find an ideal way, but I'll add what I have
I have a hunch update-proxy will come handy here. https://clojuredocs.org/clojure.core/update-proxy There was something in The Joy of Clojure book.
i think the issue is the headers coming back esp content-length. i updated the code to only take the body and put it in a new response and it's working now:
(assoc context :response (response/response (:body (send-request request))))))})
thanks to both of you!
oh good point hindol, let me try
Think I'm close, but I don't know how to properly listen to when a variable changes - i tried add-watch but that did not work
Does manually calling update-proxy work? I feel adding a watch is going too far.
alright I got it, but I guess you won't like it
(defn add-ws-endpoints
"Given a ServletContextHandler and a map of WebSocket (String) paths to action maps,
produce corresponding Servlets per path and add them to the context.
Return the context when complete.
You may optionally also pass in a map of options.
Currently supported options:
:listener-fn - A function of 3 args,
the ServletUpgradeRequest, ServletUpgradeResponse, and the WS-Map
that returns a WebSocketListener."
([^ServletContextHandler ctx ws-paths]
(add-ws-endpoints ctx ws-paths {:listener-fn (fn [req response ws-map]
(ws/make-ws-listener ws-map))}))
([^ServletContextHandler ctx ws-paths opts]
(let [{:keys [listener-fn]
:or {listener-fn (fn [req response ws-map]
(ws/make-ws-listener ws-map))}} opts
ws-paths-orig ws-paths
ws-paths (cond-> ws-paths var? deref)]
(doseq [[path ws-map] ws-paths]
(let [servlet (ws/ws-servlet
(fn [req response]
(let [ws-paths (cond-> ws-paths-orig var? deref)]
(listener-fn req response (get ws-paths path)))))]
(.addServlet ctx (ServletHolder. ^javax.servlet.Servlet servlet) path)))
ctx)))
(def WebSocketAdapterProxy (get-proxy-class WebSocketAdapter))
(defn ws-listener
[_request _response ws-map]
(let [get-proxy-members
(fn [ws-map]
{"onWebSocketConnect"
(fn [this ^Session ws-session]
(proxy-super onWebSocketConnect ws-session)
(when-let [f (:on-connect ws-map)]
(f ws-session)))
"onWebSocketClose"
(fn [this status-code reason]
(when (var? ws-map)
(remove-watch ws-map :ws-map-watcher))
(when-let [f (:on-close ws-map)]
(f (.getSession this) status-code reason)))
"onWebSocketError"
(fn [this ^Throwable e]
(when-let [f (:on-error ws-map)]
(f (.getSession this) e)))
"onWebSocketText"
(fn [this ^String message]
(when-let [f (:on-text ws-map)]
(f (.getSession this) message)))
"onWebSocketBinary"
(fn [this ^bytes payload offset length]
(when-let [f (:on-binary ws-map)]
(f (.getSession this) payload offset length)))})
adapter (construct-proxy WebSocketAdapterProxy)]
(init-proxy adapter (get-proxy-members ws-map))
(when (var? ws-map)
(add-watch
ws-map
:ws-map-watcher
(fn [_ _ _ ws-map]
(update-proxy adapter (get-proxy-members ws-map)))))
adapter))
I guess a weakness is you cannot add new websocket paths at runtime (e.g., ["ws"] -> ["ws", "ws2"]), but I think that is more rare
That's a cool solution. If you are willing to go so far, I wonder if making ws-map an atom will be better. That way the proxy will "see" the latest defs without needing a watcher and update proxy.
Yea that would be simpler, though then the distance between the dev version and the prod version would be a little greater. So I just wanted to do it like how pedestal people normally treat the routes in dev vs prod.
Yeah, what you said makes sense actually.
is there an interceptor or other way to define params using plumatic schema like compojure-api/reitit or some other way of declarative parameter handling? other than using the reitit router in pedestal that is
Hi, have you looked at https://github.com/oliyh/pedestal-api ?
i have briefly. wasn't sure if it was still an active project. same thing with the rook project. i will take a closer look
It's stable as in no outstanding bugs and no outstanding features. If you think it's missing anything or you find a bug just raise an issue/pr