Coder Social home page Coder Social logo

bishop's Introduction

Build Status

Bishop is a Webmachine-like library for Clojure. Bishop provides tools that make it easy and straightforward for your web-service to treat HTTP as a first-class application protocol. The library handles things like content negotiation and predictable caching behavior, leaving you to concentrate on a building a clean and consistent API be it REST-ful or even HATEOAS compliant.

When you create a “resource” with Bishop, you receive a function that expects a map of request values and will return a map of response values. This library was designed to be used with Ring and should work with any Ring middle-ware. Bishop provides its own routing mechanism but you can use another if you like (for instance Moustache).

This is our first release of this library and there may be bugs that need squashing, please register an issue if you notice any or send us a pull request if you fix them. We’re also providing a sample application that provides a more in-depth example. We’re working on implementing an application for production use that leverages this library, we expect to be polishing it further over the coming months. If you find it useful in any way, please feel free to...

Buy Me A Coffee

Aren't There Other Projects that Do This?

Yes, there are several other projects that are looking to do this very same thing. The ones that I am aware of are...

This project has slightly different goals from those mentioned above. For one, this project isn't particularly interested in exposing a nice interface to Java code. Our primary concern is to make things easier for the Clojure developer.

Plugboard is constructed on top of the excellent Compojure library which in turn builds on Ring, this project instead builds on top of Ring directly. The web APIs that I have constructed so far have been coded on Ring and I didn't want to pull Compojure into the mix.

Breaking Changes from 1.1.9 to 1.2.0

The way routing is handled has been changed from version 1.2.0 forward. Earlier versions of Bishop used a map for routing and this did not allow for the routing rules to be provided in any specific order (i.e., the wildcard route is last so only use it if nothing else matches). While this worked fine for smaller applications, it makes more sense to provide ordered routes. From version 1.2.0 forward, routes are now specified as a sequence.

Installation

To use Bishop, add the following to your project’s “:dependencies”:

[tnrglobal/bishop "1.2.0"]

How Does it Work?

Anyway, let's say you have a function that will say "Hello" to people. Add the com.tnrglobal.bishop.core namespace to your project.

(ns hello.core
  (:require [com.tnrglobal.bishop.core :as bishop]))

We also define the function that does our work.

(defn hello
  [name]
  (str "Hello " name "!"))

We can then define a resource that says "Hello" in HTML or JSON. In this example we use Hiccup to generate our HTML and CLJ-JSON to generate our JSON output.

(def hello-resource
  (bishop/resource
    {"text/html" (fn [request]
      (hiccup/html
        [:p (hello (:name (:path-info request)))]))}

    {"text/json" (fn [request]
      {:body (clj-json/generate-string
               {:message (hello (:name (:path-info request)))}))}))

This resource can return either HTML or JSON content, depending on the “Accept” headers of the request. It expects to have a value in the "path-info" map under the ":name" key. This comes from the routing.

(defroutes routes
  ["hello" :name] hello-resource
  ["*"] (bishop/halt-resource 404))

We route incoming request for "/hello/something" to our "hello-resource" functions, anything else will result in sending a "404" code to the client. Bishop will parse the route and the request's URI to populate the "path-info" map for your application, the goal is to do it in the same way that Webmachine handles dispatch.

Lastly, you can add this as your Ring handler function.

(def app
  (-> (bishop/handler #'routes)))

In this example we pass our routes to the handler as a var, this is done so that changes to the routes are visible in a running REPL session or through Ring's reloading middleware.

Using Another Routing Library

If you'd like to use another routing library, you may use the "raw-handler" function instead. This will provide you with a Ring handler that simply applies the incoming request to the Bishop resource. For instance, you might prefer Moustache.

(def hello-resource
  (bishop/raw-handler
    (bishop/resource {"text/html" 
      (fn [request] (hiccup/html [:p (hello name)]))})))

(def moustache-handler
  (moustache/app
    ["hello" name] hello-resource
	[&] (bishop/raw-handler
	      (bishop/halt-resource 404))))

(def app
  (-> moustache-handler))

Instead of asking Bishop to provide a resource equipped to handle it's own routing, we ask for a "raw" handler that expects routing to already have been handled. We can then plug-in Moustache and provide our Bishop resources as end-points. More examples are available in the unit tests.

What Else Does it Do?

Aside from parsing the URI and matching it to the route, Bishop is doing a lot of other work as well. It covers all of the behavior in this HTTP 1.1 flow chart, it does this by providing a state-machine that implements the decision tree. In our example, Bishop is...

  • Parsing the client URI and route, then populating the "path-info" map

  • Verifying that the client is trying to either GET or HEAD the resource.

  • Making sure that the length of the URI isn't totally nutty.

  • Verifying that our resource can accept either a GET or a HEAD request.

  • Selecting the appropriate content type to provide to the client and sending the appropriate error if the client asks for a resource that we do not provide.

And so on.

Sample Application

We have put a small, sample application that provides a more in-depth example. You may find it useful to look the sample code over to get a better idea of how Bishop functions.

https://github.com/tnr-global/bishop-sample

bishop's People

Contributors

aviflax avatar boxxxie avatar cmiles74 avatar edtsech avatar jolby avatar tmciver avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

bishop's Issues

How can I return a representation with an error response?

For example, in the case of a malformed request, the function supplied as the value for :malformed-request? can only return a boolean value. But I want to specify the error response representation. Is there some way to do this?

When a request doesn’t include the Accept header, the response does not include a representation

I think it’d be better to simply use the first specified representation function as the default.

$ curl -v -H "Accept:" http://localhost:3000/
* About to connect() to localhost port 3000 (#0)
*   Trying ::1...
* connected
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:3000
> 
< HTTP/1.1 200 OK
< Date: Wed, 15 Aug 2012 03:35:32 GMT
< Vary: accept-charset
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 0
< Server: Jetty(7.6.1.v20120215)
< 
* Connection #0 to host localhost left intact
* Closing connection #0

SimpleDateFormat concurency problems

java.text.SimpleDateFormat isn't thread safe (mentioned in the javadoc). You should either:

  • create a new instance for each use
  • synchronize the use
  • or use another formatter (e.g. FastDateFormat from commons-lang, but note that it cannot parse)

Regression in 1.1.1: java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to clojure.lang.Associative

I’m getting this exception when sending a POST request containing a webform to my resource:


Exception: java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to clojure.lang.Associative
                                      RT.java:691 clojure.lang.RT.assoc
                                     core.clj:187 clojure.core/assoc
                                     flow.clj:307 com.tnrglobal.bishop.flow/response-code[fn]
                                    core.clj:5575 clojure.core/trampoline
                                    flow.clj:1095 com.tnrglobal.bishop.flow/run
                                     core.clj:167 com.tnrglobal.bishop.core/handler[fn]
                                  resource.clj:14 ring.middleware.resource/wrap-resource[fn]
                                 file_info.clj:40 ring.middleware.file-info/wrap-file-info[fn]
                                    params.clj:55 ring.middleware.params/wrap-params[fn]
                         multipart_params.clj:103 ring.middleware.multipart-params/wrap-multipart-params[fn]
                                     Var.java:415 clojure.lang.Var.invoke
                                stacktrace.clj:15 ring.middleware.stacktrace/wrap-stacktrace-log[fn]
                                stacktrace.clj:79 ring.middleware.stacktrace/wrap-stacktrace-web[fn]
                                    reload.clj:18 ring.middleware.reload/wrap-reload[fn]
                                     jetty.clj:18 ring.adapter.jetty/proxy-handler[fn]
                                 (Unknown Source) ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$0.handle
                          HandlerWrapper.java:111 org.eclipse.jetty.server.handler.HandlerWrapper.handle
                                  Server.java:349 org.eclipse.jetty.server.Server.handle
                  AbstractHttpConnection.java:452 org.eclipse.jetty.server.AbstractHttpConnection.handleRequest
                  AbstractHttpConnection.java:894 org.eclipse.jetty.server.AbstractHttpConnection.content
                  AbstractHttpConnection.java:948 org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content
                              HttpParser.java:857 org.eclipse.jetty.http.HttpParser.parseNext
                              HttpParser.java:235 org.eclipse.jetty.http.HttpParser.parseAvailable
                      AsyncHttpConnection.java:76 org.eclipse.jetty.server.AsyncHttpConnection.handle
                   SelectChannelEndPoint.java:609 org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle
                    SelectChannelEndPoint.java:45 org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run
                        QueuedThreadPool.java:599 org.eclipse.jetty.util.thread.QueuedThreadPool.runJob
                        QueuedThreadPool.java:534 org.eclipse.jetty.util.thread.QueuedThreadPool$3.run
                                  Thread.java:722 java.lang.Thread.run

the stacktrace doesn’t include any of my code, so I won’t bother including my resource code.

I’ve confirmed that this doesn’t occur with 1.1.0.

Do you recommend Bishop for production systems?

Hi,
I'm sorry to use "Issues" to ask a question.
I need to code a REST API and Bishop totally captivated me. Do you recommend it to production systems, or it is better to fall back to Compojure at this moment?
Thanks a lot.

It’s unclear whether a representation function has to return a map…

…or can just return the representation body.

The example in the README shows this example:

{"text/html" (fn [request]
      (hiccup/html
        [:p (hello (:name (:path-info request)))]))}

in which case the “representation function” is just returning the content of the response body.

But the functions in the sample project return a map:

(fn [request]
      {:body (xhtml {:lange "en" }
                    [:body
                     [:h1 "To-Do List"]
                     [:ul (map #(todo-short-html (add-resource-links URI-BASE %))
                               (app/todos))]])})}

…it appears that the current version of Bishop (1.0.8) requires that these functions return a map containing at least :body; if I just return a String then I get java.lang.ClassCastException: java.lang.Character cannot be cast to java.util.Map$Entry.

So it would appear that we either need to revise the README to be consistent with the current requirements, OR change the current behavior of the library.

Personally, I’d prefer we do the latter. It’s not uncommon that all I’ll need to set in the response is the body. So it would be very convenient if the library would automatically detect that the result value is not a map, and just automatically wrap it in the key :body in a map. This would lead to clearer, more concise code. I’d be happy to try to contribute a patch for this.

How can I prevent the parameter charset from being added to the response header Content-Type?

The library, or possibly some other part of the stack such as Ring or Jetty, is adding this parameter.

Example:

$ curl -i http://localhost:3000/
HTTP/1.1 200 OK
Date: Wed, 11 Jul 2012 20:04:33 GMT
Vary: accept
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 12
Server: Jetty(7.6.1.v20120215)

hello, world

I’d prefer that the parameter charset not be present in the response header Content-Type. How can I remove or suppress it?

The callback function :is-conflict? should be called for POST requests

I was confused why my resource wasn’t behaving as I expected it to. Finally I looked up resource.clj and saw that the comment on :is-conflict? says “Returns true if the provided PUT request would cause a conflict” — this limitation seems incorrect. There are many cases wherein I might want to return a 409 when handling a POST request.

Make bishop/handler play nice with ring middleware.

Today I wanted to play with ring middleware to speed up development time. So I gave this a shot

(ns test.core
    (:gen-class)
    (:use [ring.adapter.jetty]
          [ring.middleware.reload]
          [ring.middleware.stacktrace])
    (:require [com.tnrglobal.bishop.core :as bishop]
            [test.routes :as routes]))

(defn main
    "Exposes the main function for bootstrapping the application."
    [& args]
    (let [port (Integer/parseInt (get (System/getenv) "PORT" "3000"))
                base-handler (bishop/handler routes/routes)
          handler (if (= env "DEV") (wrap-stacktrace (wrap-reload base-handler))
                      base-handler)]
        (println handler)
        (println (str "Webserver starting on port " port " with env " env))
        (run-jetty handler {:port port})))

(defn -main
    [& args]
    (main args))

Which did not work as expected, since the middleware expects the handler to be a var. So after a bunch of messing around I ended up with this solution.

(ns test.core
    (:gen-class)
    (:use [ring.adapter.jetty]
          [ring.middleware.reload]
          [ring.middleware.stacktrace])
    (:require [com.tnrglobal.bishop.core :as bishop]
            [test.routes :as routes]))

(def base-handler 
    (bishop/handler routes/routes))

(defn main
    "Exposes the main function for bootstrapping the application."
    [& args]
    (let [port (Integer/parseInt (get (System/getenv) "PORT" "3000"))
          handler (if (= env "DEV") (wrap-stacktrace (wrap-reload base-handler))
                      base-handler)]
        (println handler)
        (println (str "Webserver starting on port " port " with env " env))
        (run-jetty handler {:port port})))

(defn -main
    [& args]
    (main args))

You'd think it would just play nicely with Ring out of the box. A possible solution is to have the handler function return a var, which I am not too sure how to do but it looks like compojure does it using macros(https://github.com/weavejester/compojure/blob/master/src/compojure/core.clj#L113) so perhaps you could ape their solution.

I am pretty green to clojure/clojure library development(how do you use lein with a local repo? push to clojars on every fix?) otherwise I would have given this a shot.

Anyways let me know if you need anymore help and I also understand if this is a low priority.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.