pedestal

César Olea 2025-06-06T01:48:09.291719Z

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?

2025-06-06T09:04:43.806429Z

Is the foo response considered complete, without :headers?

César Olea 2025-06-06T15:17:37.442589Z

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"}}

hlship 2025-06-06T23:52:03.635069Z

That looks like it should work the way you describe. I've bookmarked this conversation and will take a look at it more closely.

César Olea 2025-06-07T01:38:01.967869Z

Thank you @hlship!

hlship 2025-06-12T17:43:25.808309Z

Thanks for catching this; the http-kit connector did not properly set up the terminate condition for the interceptor context. One line fix.

hlship 2025-06-12T17:48:03.973709Z

https://github.com/pedestal/pedestal/pull/926

César Olea 2025-06-12T17:48:59.203469Z

Thank you @hlship for fixing it! I’ll check it out as soon as there’s a release available.

César Olea 2025-06-12T17:52:02.187019Z

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

hlship 2025-06-12T17:54:28.285049Z

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.

César Olea 2025-06-12T17:57:40.466059Z

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!

hlship 2025-06-12T17:57:56.055409Z

Feedback very welcome!

hlship 2025-06-12T20:51:14.656469Z

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.

César Olea 2025-06-12T20:56:54.878939Z

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!

hlship 2025-06-12T20:57:31.613229Z

I added them in 0.7 to help debug this kind of "who put what in the context" issues.

👍 1
César Olea 2025-06-12T21:18:37.281949Z

I can confirm 0.8.0-beta-1 fixes the issue 🙂

1
dazld 2025-06-06T11:52:20.622269Z

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?

❌ 1
hlship 2025-06-06T15:20:59.616629Z

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.

hlship 2025-06-06T15:24:21.974589Z

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.

dazld 2025-06-06T15:27:02.599089Z

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?

dazld 2025-06-06T15:27:24.284699Z

if it’s not a map etc, as you mentioned

dazld 2025-06-06T15:29:08.782219Z

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 (!)

dazld 2025-06-06T15:33:42.085369Z

sorry, this isn’t a bug at all - it just threw me using that function, should have read the docstring a bit closer.

hlship 2025-06-06T15:36:04.670869Z

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.

➕ 2
dazld 2025-06-06T15:37:26.015309Z

worst thing you can do is make something public, sometimes - apologies for the noise, and thanks for the support

1
hlship 2025-06-06T23:54:48.748859Z

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