I’m testing pedestal 8 with a small application I have, and ran into unexpected behavior
(require '[io.pedestal.http.http-kit :as hk])
(require '[io.pedestal.connector :as conn])
(require '[io.pedestal.connector.test :refer [response-for]])
(response-for
(hk/create-connector
(-> (conn/default-connector-map 8080)
(conn/with-routes #{["/early-response" :get
[(i/interceptor
{:enter (fn [ctx] (assoc ctx :response {:status 200 :body "foo"}))
:name ::response-foo})
(i/interceptor
{:enter (fn [ctx] (assoc ctx :response {:status 200 :body "bar"}))
:name ::response-bar})]]}))
nil) :get "/early-response")
;; => {:status 200, :body "bar", :headers {:content-type "text/plain"}}
I was expecting :body "foo" Since the ::response-foo interceptor appears earlier and it attaches a :response key to the context. The equivalent in 0.7 works as expected. Am I missing something here?Is the foo response considered complete, without :headers?
For this example, yes. I can add headers but the result is the same:
(response-for
(hk/create-connector
(-> (conn/default-connector-map 8080)
(conn/with-routes
#{["/early-response" :get
[(i/interceptor
{:enter (fn [ctx] (assoc ctx :response {:status 200
:body "foo"
:headers {"Content-Type" "text/plain"}}))
:name ::response-201})
(i/interceptor
{:enter (fn [ctx] (assoc ctx :response {:status 200
:body "bar"
:headers {"Content-Type" "text/plain"}}))
:name ::response-200})]]}))
nil) :get "/early-response")
;; => {:status 200, :body "bar", :headers {:content-type "text/plain"}}That looks like it should work the way you describe. I've bookmarked this conversation and will take a look at it more closely.
Thank you @hlship!
Thanks for catching this; the http-kit connector did not properly set up the terminate condition for the interceptor context. One line fix.
Thank you @hlship for fixing it! I’ll check it out as soon as there’s a release available.
Also I’m an old-time Tapestry 3 user, so thank you for that as well! I might have a copy of Tapestry in Action lying around in some boxes
Well, that's great to hear! I think Tapestry had some great ideas and a lot of hubris. I hope I've been able to clamp down on the hubris in the meantime. Tapestry, especially Tapestry 5, was easy but not simple. I wish I knew half of what I know now back in 2005.
I was very excited first when I learned you were doing Clojure and then that it was you maintaining pedestal. You’re doing an awesome job!
Feedback very welcome!
BTW I absolutely used https://pedestal.io/api/0.8/io.pedestal.connector.dev.html#var-with-interceptor-observer to track down what was happening with this bug. Indispensable.
I wasn’t aware these existed. I’m basically using a combination of logging and flowstorm to observe the context changes. This is very useful, thank you!
I added them in 0.7 to help debug this kind of "who put what in the context" issues.
I can confirm 0.8.0-beta-1 fixes the issue 🙂
In pedestal.jetty 0.7.2 I’ve found what looks like a possible bug with SSE support. For context, am building a remote MCP server for claude desktop, which uses an SSE & json-rpc protocol.
I’ve got the mcp authentication process all working, and a persistent connection is opened (all tested with curl etc). However, when using the function io.pedestal.http.sse/send-event to send some initialization events, data is turned into a byte array via the mk-data function - but then, inside the main dispatch loop it calls “str” on the payload, meaning the literal java .toString of the byte array is sent to consumers, looking like [b1289x123... and not the JSON string I was expecting.
I’ve got around this by directly pushing payloads to the event channel, but this feels a bit off.
Is this a known bug? should I just ignore the send-event function?
this is where the payload is cast to a string: https://github.com/pedestal/pedestal/blob/0.7.2/service/src/io/pedestal/http/sse.clj#L134-L137 where mk-data is called: https://github.com/pedestal/pedestal/blob/0.7.2/service/src/io/pedestal/http/sse.clj#L75 ..and where mk-data transforms strings to bytes: https://github.com/pedestal/pedestal/blob/0.7.2/service/src/io/pedestal/http/sse.clj#L47-L50
I haven't seen this behavior though I've been working on the 0.8.0 version of the code.
You can actually use curl to see the content sent to the browser.
The code is messy because the event can either be a single value (just the data), or a map with keys :data, :message, :id. And each of the values for those keys is passed through str.
But the result of mk-data is a byte array, which is put on the channel and ultimately streamed to the client.
I suspect your app is creating the byte arrays that are being conveyed on the event-channel, and that's what's being converted to a string via str. The intent is that the data should be a string, or simple values (numbers, booleans, etc.) that can be cleanly converted to strings by str.
I did inspect and the string payload is indeed the byte array being (str …) ified.
sse/send-event puts the result of “mk-data” onto the channel. The channel is consumed (iiuc?) inside start-dispatch-loop - and you can see there it calls (str event) on whatever it got through the channel - which, in this case, is bytes, I think?
if it’s not a map etc, as you mentioned
oh hang on, you’re completely right! sse/send-event isn’t really for public use - it’s not for consuming events to send, its for sending events to the response channel (!)
sorry, this isn’t a bug at all - it just threw me using that function, should have read the docstring a bit closer.
In 0.8.0 its a private function. Prior maintainers were, in my opinion, sloppy about what's public and what's private. I think APIs are better with absolutely minimal surface areas.
worst thing you can do is make something public, sometimes - apologies for the noise, and thanks for the support
Very happy with how WAR deployment using Pedestal connectors (give or take) came out. Default implementation is super simple, or you can roll your own. https://github.com/pedestal/pedestal/pull/923