clojureclojure-contrib

clojure-api: Single main clj for multiple API handlers


The below is my app project.clj

(defproject clojure-my-app-api "0.1.0-SNAPSHOT"
      :description "FIXME: write description"
      :url "http://example.com/FIXME"
      :license {:name "Eclipse Public License"
                :url "http://www.eclipse.org/legal/epl-v10.html"}
      :ring {:handler clojure-my-app-api.core/app} 
      :dependencies [[org.clojure/clojure "1.8.0"]
                     [metosin/compojure-api "1.1.10"]
                     [ring/ring-core "1.4.0"]
                     [ring/ring-jetty-adapter "1.4.0"]]
      :main clojure-my-app-api.core)

and my app core.clj is

(ns clojure-my-app-api.core
  (:require [ring.adapter.jetty :as jetty])
  (:require [compojure.api.sweet :refer :all])
  (:require [ring.util.http-response :refer :all]))

(defapi app
  (GET "/hello" []
    :query-params [name :- String]
    (ok {:message (str "Dear " name ", Hello I am here ")})))

(jetty/run-jetty app {:port 3000})

My doubt is, Is it mandatory to put (jetty/run-jetty app {:port 3000}) in every clj class if we have multiple classes for handling multiple API requests.

Can you please help me out is there any single main class mechanism for multiple clj class to handle different API path.

I have modified my code.

project.clj

(defproject clojure-dauble-business-api "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :ring {:handler {clojure-dauble-business-api.core/app 
                  clojure-dauble-business-api.test/test1}}
  :repl-options {:init-ns clojure-dauble-business-api.user}
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [metosin/compojure-api "1.1.10"]
                 [ring/ring-core "1.4.0"]
                 [ring/ring-jetty-adapter "1.4.0"]]
  :main clojure-dauble-business-api.user)

user.clj

(ns clojure-dauble-business-api.user
  (:require [ring.adapter.jetty :as jetty])
  (:require [compojure.api.sweet :refer :all])
  (:require [ring.util.http-response :refer :all])
  (:require [clojure-dauble-business-api.core :as core])
  (:require [clojure-dauble-business-api.test :as test]))

(jetty/run-jetty (list core/app test/test) {:port 3000})

core.clj

(ns clojure-dauble-business-api.core
  (:require [ring.adapter.jetty :as jetty])
  (:require [compojure.api.sweet :refer :all])
  (:require [ring.util.http-response :refer :all]))

(defapi app
  (GET "/hello" []
    :query-params [name :- String]
    (ok {:message (str "Dear " name ", Hello I am here ")})))

test.clj

(ns clojure-dauble-business-api.test
  (:require [ring.adapter.jetty :as jetty])
  (:require [compojure.api.sweet :refer :all])
  (:require [ring.util.http-response :refer :all]))

(defapi test
  (GET "/ping" []
    :query-params [name :- String]
    (ok {:message (str "Dear " name ", Hello I am here ")})))

Error while running http://localhost:3000/hello?name=acbd endpoint in Postman

2017-07-08 10:46:34.413:WARN:oejs.HttpChannel:qtp191004666-15: /hello?name=abcd
java.lang.ClassCastException: clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
    at ring.adapter.jetty$proxy_handler$fn__1401.invoke(jetty.clj:24)
    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:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:497)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:745)

Solution

  • You shouldn't have (jetty/run-jetty (list core/app test/test) {:port 3000}) in any of your namespaces. (Having it in user.clj is fine for REPL work).

    Typically, you have a single handler route that you pass to ring for handing your application.

    For example, I generally have a routes namespace that I combine by routes from other namespaces.

    So, from your example.

    (ns clojure-dauble-business-api.routes
        :require [compojure.core :refer :all]
                 (clojure-dauble-business-api [core :as core]
                                              [test :as t]))
    (def app
     (routes core/app t/test))
    

    This is what you could pass into Jetty, or refer to in your :ring entry in your project.clj.

    :ring {:handler clojure-dauble-business-api.routes}
    

    You're getting the error, because run-jetty must be passed a function, but you're passing it a list instead (because you're trying to consolidate your routes with (list core/app test/test)

    I would also add the plugin lein-ring to your project and remove the :main entry. You will also have to correct your :ring entry in your project as above.