Giter VIP home page Giter VIP logo

gizmo's Introduction

What is Gizmo?

Gizmo is an effortless way to create web applications in Clojure.

Gizmo is a set of practices we've accumulated from several Web applications and APIs developed with Clojure. It's an MVC microframework, which lets you develop parts of your app completely independently, which improves composition and allows you to effortlessly implement things like A/B testing and gradual feature rollouts.

Project Goals

Gizmo is not a replacement for Ring or Compojure. It's based on them, and doesn't re-implement their features.

  • Provide a convenient, idiomatic way of developing Clojure web apps
  • Give you a set of building blocks to bring you up to speed as fast as possible
  • Leave infinite flexibility in terms of all configuration and composition decisions
  • Help you to establish reasonable convention on where to put what (handlers, services, routes, HTML, CSS, and so on)
  • Be well documented
  • Be well tested

Project Maturity

Principles that are represented in Gizmo are battle-tested and proven to work very well for large Clojure Web applications. Gizmo as a library is very young and breaking API changes currently can happen at any point without prior notice.

Maven Artifacts

Most Recent Release

With Leiningen:

[clojurewerkz/gizmo "1.0.0-alpha2"]

With Maven:

<dependency>
  <groupId>clojurewerkz</groupId>
  <artifactId>eep</artifactId>
  <version>1.0.0-alpha2</version>
</dependency>

Documentation

Intro

Gizmo is a collection of good practices that ties multiple Clojure Web development libraries and a few concepts together (similar to DropWizard in Java, although slightly more opinionated).

With Gizmo, you build HTTP apps as one or more services, each of which can be started, stopped and performed a health check on. Request handling is implemented as a pipeline, which starts with a Ring request, includes a number of middlewares, a handler (picked by the router) and a responder.

Gizmo separates UI elements from HTTP request handling, and request handling logic from serving the response.

HTTP Request Lifecycle

Incoming HTTP requests are handled by Jetty and processed through a middleware stack. Middleware implements session handling, cookies, route parameters extraction, authentication, etc. A middleware takes a request hash hands it over to the routing function, which figures out which handler the request should be routed to.

Handler prepares the response and returns HTTP response code, response body and content type, and hands this hash over to responder. Depending on response content type, an appropriate renderer is invoked (for exmaple HTML or JSON).

Renderer renders a complete response body and returns the result back to Jetty, which sends it back to the client.

Request, Response and Environment

Even though Request, Response and Environment are closely related to each other, Gizmo separates these concepts. These (plus middleware) concepts are taken directly from Ring.

request is an initial request from a HTTP client, which contains information about the referrer, user agent, path and so on.

environment is a request that has been processed and refined by the middleware stack and request handler.

environment becomes response after it has been through the middleware, handler and renderer and is ready to be returned back to the client.

With this separation, you can refer to a specific part of request processing pipeline.

In all parts of your application, you can always refer to current (immutable) state of request by calling clojurewerkz.gizmo.request/request function. We strongly advise not to overuse availability of a complete request and always pass required parts of request to all functions explicitly. Although it's hard to draw a boundary where it is acceptable, just keep in mind that it will make your code less explicit and testable.

Middleware

A middleware is a function that receives a request and modifies it. Middleware can terminate execution of request processing or return a result, or pass the request on to the next middleware.

Here's what middleware looks like:

(defn wrap-authenticated-only
  [handler]
  (fn [env]
    (if (user-authenticated? env)
      (handler (assoc env :new-key :new-value))
      {:status 401 :body "Unauthorized"})))

There are two execution paths here: if the user is authenticated, a request handler is called, so request processing is continued, otherwise middleware returns 401 Unauthorized response and halt further request processing.

In order to create a middleware stack, you thread the handler through set of middlewares, wrapping handler into the middleware, then wrapping resulting stack into another middleware function and so on.

(ns my-app.core
  (:require [compojure.handler :refer [api]]
            [ring.middleware.params :refer [wrap-params]]
            [my-app.routes :as routes]))
(def app
  (-> (api routes/main-routes)
      wrap-params))

Routing

Routing in Gizmo is built upon Compojure and Route One.

Routing recognizes URLs and dispatches them to a suitable handler. It also generates helper functions for creating Paths and URLs so that you wouldn't need to hardcode them and could specify them once for both parsing and generation purposes.

Following code defines routes for a simple application that's showing you docstrings of all the libraries in your Clojure classpath.

Root path "/" is handled by main/index handler function. Library path "/libs/:library" is handled by main/library-show, and so on.

(ns gizmo-cloc.routes
    (:use [clojurewerkz.route-one.compojure])
    (:require [compojure.core :as compojure]
              [compojure.route :as route]))

(compojure/defroutes main-routes
  (GET root      "/"                             request (gizmo-cloc.handlers.main/index request))
  (GET library   "/libs/:library"                request (gizmo-cloc.handlers.main/library-show request))
  (GET namespace "/libs/:library/nss/:namespace" request (gizmo-cloc.handlers.main/namespace-show request))
  (GET favicon   "/favicon.ico"                  _       (fn [_] {:render :nothing}))
  (route/not-found "Page not found"))

You can use generated routes by adding -path postfix for paths and -url postfix for URLs. You can find in-depth documentation for route parsing and generation in Route One.

Handlers

A handler is a function responsible for requests matching a particular URL pattern. Handler take an environment, a request that's been processed by middleware stack, and returns a hash that's passed to a responder.

You can have full control over response params in response. For example, you can specify status, headers and so on. In order to specify type of your response, set :render key to either "html" or "json" (two built-in renderers), for example:

;; Render :response-hash and return it as JSON response
(defn index-json
  [env]
  {:render :json
   :status 200
   :response-hash {:response :hash}))

Responders

In order to implement a custom response MIME type, use multimethods extending respond-with. For example, if you want to add an XML responder, you can write:

(ns my-app
  (:require [clojurewerkz.gizmo.responder :refer [respond-with]])

(defmethod respond-with :xml
  [env]
  {:status 200
   :body (xml/serialize (:response-hash env))})
(start jetty-service)
;; or you can start all services together
(start-all!)

You can check nrepl service example here and a more complex example of cooperative UDP socket listener here.

Configuration

Configuration is a file loaded by clojurewerkz.gizmo.config/load-config!, which takes a path to configuration file and loads it to clojurewerkz.gizmo.config/settings variable, that's available at all times.

Leiningen project templates

You can use Gizmo Leiningen project template to generate Gizmo application skeletons.

You can get up and running with it by creating a new template and running it:

lein new gizmo-web my-app
cd my-app
lein run --config config/development.clj

Community

Gizmo does not yet have it's own mailing list. This will be resolved as soon as first artifacts are pushed to Clojars.

To subscribe for announcements of releases, important changes and so on, please follow @ClojureWerkz on Twitter.

Development

Gizmo uses Leiningen 2. Make sure you have it installed and then run tests against supported Clojure versions using

lein all test

Then create a branch and make your changes on it. Once you are done with your changes and all tests pass, submit a pull request on GitHub.

License

Copyright © 2014-2016 Alex Petrov, Michael Klishin

Double licensed under the Eclipse Public License (the same as Clojure) or the Apache Public License 2.0.

gizmo's People

Contributors

bitdeli-chef avatar bripkens avatar endafarrell avatar ifesdjeen avatar michaelklishin avatar ujh 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

gizmo's Issues

Release latest to Clojars

I'd like to try Gizmo in a project, but use it with the upgraded set of dependencies as introduced in 7ba52d4. Could you please release a 1.0.0-alpha5?

Thanks!

Silencing the stderr in the tests

When I run lein test I get not test failures, but at first I thought I did because of that output:


lein test clojurewerkz.gizmo.config-test

lein test clojurewerkz.gizmo.enlive-test

lein test clojurewerkz.gizmo.responder-test

lein test clojurewerkz.gizmo.service-test
Exception in thread "Thread-4" java.lang.Exception: couldn't start
    at clojurewerkz.gizmo.service_test$fn$reify__2540$fn__2541.invoke(service_test.clj:18)
    at clojurewerkz.gizmo.service$start_thread$fn__2475.invoke(service.clj:26)
    at clojure.lang.AFn.run(AFn.java:24)
    at java.lang.Thread.run(Thread.java:744)

lein test clojurewerkz.gizmo.widget-test

Ran 21 tests containing 35 assertions.
0 failures, 0 errors.

But the stack trace is printed onto stderr and it seems quite complicated to redirect it somewhere else as it doesn't seem to use *err*. At least wrapping the throw call where the exception originates from in a binding that replaces *err* with a string writer didn't change a thing.

Judging from this discussion it might well be that the exception is directly handed over to System/err so we would have to (temporarily) replace that. I'm not sure if that's really worth it.

A better logging approach

So @ifesdjeen suggested using either tools.logging or timbre to add proper logging to gizmo. logging.clj seems simple enough so that this shouldn't be to difficult.

I'm just not totally sure about the configuration. That would go into config/development.clj and similar in the apps, right? That would then mean changing gizmo-web-template if I understand the setup correctly.

And testing ... How would you do that? If at all.

Unexpected "{}" returned when expecting "Page not found" for a HTTP 404

I have a gizmo-web app, modified only to change the "/templates/layouts/application.html" to /not/ refer to "testapp" but rather to refer to my app's name.

My routes.clj is unchanged at:

(ns orla.routes
    (:use [clojurewerkz.route-one.compojure])
    (:require [compojure.core :as compojure]
              [compojure.route :as route]))

(compojure/defroutes main-routes
  (GET root "/" request (orla.handlers.home/index request))
  (GET favicon "/favicon.ico" _ (fn [_] {:render :nothing}))
  (route/not-found "Page not found"))

When I run the app ( lein run --config config/development.clj ) and hit a page such as http://localhost:8080/this-does-not-exist instead of "Page not found" I get "{}" as the content.

The headers are not informative:

Request URL:http://localhost:8080/this-does-not-exist
Request Method:GET
Status Code:404 Not Found
Request Headersview source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Host:localhost:8080
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.69 Safari/537.36

Response Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:max-age=0
Content-Length:2
Content-Type:text/html;charset=UTF-8
Date:Fri, 11 Oct 2013 16:50:18 GMT
Host:localhost:8080
Server:Jetty(7.6.8.v20121106)
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.69 Safari/537.36

The logging doesn't show much either:

lein run --config config/development.clj
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
2013-10-11 18:50:13.429:INFO:oejs.Server:jetty-7.6.8.v20121106
2013-10-11 18:50:13.467:INFO:oejs.AbstractConnector:Started [email protected]:8080
Handling uri:  /this-does-not-exist  Rendering:  nil
Handling uri:  /favicon.ico  Rendering:  :nothing

This very well might be the same issue that #1 seeks to fix, but I /think/ I tried that code too and I /guess/ I didn't use it properly as it seemed that the problem was there too. Apologies if this is the same issue written in a different way.

the web template doesnt work on windows

hi

i followed the instructions to create a new app from template

in D:\www\ lein new gizmo-web gizmo
cd gizmo
lein run --config config/development.clj

and i get an error Exception in thread "main" java.nio.file.InvalidPathException: Illegal char <:>

another question i have is why doesn't lein run just work, why do i need to specify the config file?

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.