Fork me on GitHub
#reitit
<
2019-10-29
>
mitchelkuijpers09:10:14

I am running into a problem with Muuntaja it seems, when I return this from a handler:

{:status 200
   :headers {"Content-Type" "application/transit+json"}
   :body {:foo "bar"}}
Without the content-type it always returns json and with the conten-type header with transit it gives me:
java.lang.IllegalArgumentException: No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:583)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:575)
	at ring.core.protocols$eval22168$fn__22169$G__22159__22178.invoke(protocols.clj:8)
	at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:106)
	at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
	at ring.adapter.jetty$async_proxy_handler$fn__22292$fn__22293.invoke(jetty.clj:38)
	at reitit.http$ring_handler$fn__21792$respond_SINGLEQUOTE___21795.invoke(http.cljc:155)
	at sieppari.core$deliver_result.invokeStatic(core.cljc:63)
	at sieppari.core$deliver_result.invoke(core.cljc:56)
	at sieppari.core$execute.invokeStatic(core.cljc:81)
	at sieppari.core$execute.invoke(core.cljc:75)
	at reitit.interceptor.sieppari$reify__26267.execute(sieppari.clj:18)
	at reitit.http$ring_handler$fn__21792.invoke(http.cljc:158)
	at clojure.lang.AFn.applyToHelper(AFn.java:160)
	at clojure.lang.AFn.applyTo(AFn.java:144)
	at clojure.lang.AFunction$1.doInvoke(AFunction.java:31)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:665)
	at clojure.core$apply.invoke(core.clj:660)
	at avisi.topdesk.cloud.server$fn__52550.invokeStatic(server.clj:17)
	at avisi.topdesk.cloud.server$fn__52550.doInvoke(server.clj:17)
	at clojure.lang.RestFn.invoke(RestFn.java:436)
	at ring.adapter.jetty$async_proxy_handler$fn__22292.invoke(jetty.clj:35)
	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:132)
	at org.eclipse.jetty.server.Server.handle(Server.java:503)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
	at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
	at java.lang.Thread.run(Thread.java:748)
And I am using this interceptor in [:data :interceptors]
;;; Content-negotiation
   (muuntaja/format-interceptor)

mitchelkuijpers09:10:48

Oh lol I get why I get the error, muuntja now thinks that i already made the body transit

mitchelkuijpers09:10:14

Ah and the call sends accept: */*

mitchelkuijpers10:10:18

So false alarm please ignore

valerauko13:10:14

so @ikitommi i tried doing the lazy array parsing for jsonista, but got stuck... i'd make a deserializer class that takes a clojure function (ifn) that just creates a lazy-seq, but for some reason the jsonparser is closed when wrapped by lazy-seq -- something that doesn't seem to happen in cheshire. i'm kinda lost...

valerauko13:10:20

public class LazyVectorDeserializer extends StdDeserializer<LazySeq> {
  private final IFn func;

  public LazyVectorDeserializer(IFn func) {
    super(List.class);
    this.func = func;
  }

  @Override
  @SuppressWarnings("unchecked")
  public LazySeq deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    JsonDeserializer<Object> deser = ctxt.findNonContextualValueDeserializer(ctxt.constructType(Object.class));
    p.nextToken();
    return (LazySeq) func.invoke(p);
  }
}

valerauko13:10:17

if i init this with a version of cheshire's lazy-parse, it still won't work

valerauko13:10:01

(defn ^clojure.lang.LazySeq parse-element
  [^com.fasterxml.jackson.core.JsonParser my-parser]
  (println (.isClosed my-parser))
  (lazy-seq
   (loop [chunk-idx 0, buf (chunk-buffer *chunk-size*)]
     (if (identical? (.getCurrentToken jp) JsonToken/END_ARRAY)
       (chunk-cons (chunk buf) nil)
       (do
         (chunk-append buf (.getCurrentToken jp))
         (println (.isClosed jp))
         (.nextToken jp)
         (let [chunk-idx* (unchecked-inc chunk-idx)]
           (if (< chunk-idx* *chunk-size*)
             (recur chunk-idx* buf)
             (chunk-cons
              (chunk buf)
              (parse-element jp )))))))))
first isParse (outside the lazy-seq) returns true but the one inside is false so the nextToken throws a nullpointer.

valerauko13:10:39

i figure it gets closed because deserialize returned a value so jackson thinks its job is done, instead of letting the clojure function create a closure with it

ikitommi15:10:06

Yeah, I think it doesn't work that way. Jackson closes it when exiting.

ikitommi15:10:25

Could you ask help from Jackson people?

valerauko15:10:02

i can try, but i think it's more of a clojure issue. after all it does work when cheshire wraps it in lazy-seq

ikitommi15:10:42

use that code instead then?

ikitommi15:10:45

can you parse a intermediate object within a lazy-seq with object-mappers json parser or does it too close the stream?

valerauko15:10:11

i couldn't get it not to close the stream so far... cheshire's architecture is very different from how you're doing things in jsonista, so just taking their lazy parse function isn't very obvious

valerauko16:10:37

later i'll try doing it a different way, "parsing" all the array but making the parsing of the elements lazy.

heyarne14:10:11

How would I tell swagger that I expect a bearer token for some APIs? Is this possible so that it's reflected in the UI? I found [this](https://swagger.io/docs/specification/2-0/authentication/api-keys/) but I'm unsure how the data flows from reitit

valerauko14:10:33

I don't know if using apiKey is supported at this point, but adding :parameters {:header {:authorization ::spec/bearer-token}} should work. With a spec like (s/and string? #(re-matches #"Bearer .+" %))

ikitommi15:10:01

For swagger-ui, you need to add the :securityDefinitions manually under :swagger, to here: https://github.com/metosin/reitit/blob/master/examples/http-swagger/src/example/server.clj

ikitommi15:10:53

there could be some automation here, but isn't.

ikitommi15:10:08

would be relatively easy (and handy) to have a security-middleware & security-interceptor that both enforces the api-keys and writes the swagger-spec data so the UI is in sync. All the needed extension points already exist, just needs someone to do those..

heyarne15:10:46

If I manage to write something that I trust enough I'll contribute that back 🙂

👍 4