Archive for the ‘Clojure Coding’ Category

Interesting Rounding Issue in Clojure

Friday, August 12th, 2016

Clojure.jpgWe have a shared library that has a lot of really useful functions in it for all the apps we build, and one of the really useful things is to round to a fixed number of decimal places. Now, this isn't Rocket Science, but it is nice to have written well once, and then not have to mess with ever again.

The trick is that you need it to be fast for all Clojure datatypes as well. And several of the more common implementations aren't very fast due to boxing and the overhead associated with that. So we made some pretty simple little functions:

  (defn to-2dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 2 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 100.0 (parse-double x))) 100.0))
 
  (defn to-3dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 3 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 1000.0 (parse-double x))) 1000.0))

but we had a problem. Some of the inputs gave us a lot of grief because of some nasty rounding. And it wasn't all values - it was just those values that had a hard time being expressed in the floating point format.

The solution was to accept some of the inefficiency of rat eJVM BigDecimal and the advanced representation it had, and use that:

  (defn to-2dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 2 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 100.0 (bigdec (parse-double x)))) 100.0))
 
  (defn to-3dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 3 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 1000.0 (bigdec (parse-double x)))) 1000.0))

and then things settled down.

Sometimes Life is Unpopular

Tuesday, May 3rd, 2016

Bad Idea

This morning I was dealing with yet another change to some clojure functions that are in a library that we have created at The Shop to make it easy to make the services that we make, and easy for new clojure developers to wrap their heads around. It's not perfect, or ideal, but it does strike a nice balance between purely functional code and re-use and state in the library.

Face it - connection pooling is state. It needs to exist, but it's not what you'd like to have if you could avoid it. So you bury it in a library, so that the new clojure developers can use this library with the connection pools clearly hidden off screen doing their thing.

It's a compromise. But one that favors the new clojure developer. And most days, that's the folks I have to work with. They typically know Java, or C#, but it's a rare individual that comes into the shop with a lot of clojure experience, and usually they "get it" about the decisions, and it's something they pretty easily adapt to.

But then there are others.

Today I've been dealing with Steve. Steve is a C# developer that hates his job. He's hated it for a long time. He hates the language he's using (C#). He hates the tools he is having to learn to do his job. He pretty much hates everything about his job.

But he wants to work in clojure.

Yet he's not really interested in learning the landscape. He wants to change it to be how he thinks is should be. I don't blame him - he questions a lot because he trusts no one. Having asked him this direct question, and receiving the affirmative response, I know beyond a shadow of a doubt, this is exactly who he is. But to not learn what is before suggesting what should be is a little short-sighted.

That he's annoying about it is just "icing on the cake" for me.

So this morning I decided that emotion has no place in this conversation. Neither does his opinions. I have listened, I have tried to explain, I have listened more, and tried to show why this is the path we have taken. All of this is immaterial to him, and so in the end, I have to make a decision, list the reasons for this decision, and then move on.

I don't relish being a jerk. Or a dictator. But at this point, more conversation is wasting time. It's time to accept that this is the paradigm we are working under, and that's it.

I do not expect him to like it.

Case Insensitive Regex for Clojure

Thursday, March 24th, 2016

Clojure.jpg

One of the things that I find I need to do every now and then is to have a case-insensitive regex pattern match in clojure, and I always have to spend 15 mins looking for it on the net. Not that it's hard to find, but it's 15 mins, and if I know I wrote about it here, then I can do a much quicker search, and get the same answer.

The key is that the Java regex parser recognizes two interesting options to the source for the regex: (?i) and (?iu). The first, (?i), is the flag to indicate that the entire regex is to be considered case-insensitive. The second, (?iu) is for unicode support.

So how's it work?

  (re-find #"Cook" "This is how to cook.")
  => nil
 
  (re-find #"(?i)Cook" "This is how to cook.")
  => "cook"

It's simple and easy, and typically saves you from calling lcase on the string before the regex test.

Sharing Login Auth Tokens

Saturday, January 30th, 2016

Javascript

Over the past several months we have built several Clojure/Javascript apps at The Shop, and each one has needed to have some level of authentication, and thankfully, that's all been provided by the Security Service that was already built and maintained by another guy at The Shop. It works, what else would we need? You get authentication tokens (UUIDs) and you can get information about the user based on the token, and you can't really guess an auth token for a user, so they are pretty decently secure.

But they were app-specific because we tied them to the cookies on the pages, and those pages were all from a site that was different for each app we were building. This meant that you'd have to login to three, four apps a day with the same credentials, and still not have a clean, guaranteed way of invoking a page in one app from a page in another.

This is what I wanted to solve.

This morning, it came to me - there had to be a way to store the auth tokens in a cookie that was across the apps, and then just use that as a standard for all apps. Simple. Since it was still secure (enough), we didn't have to worry about folks yanking the token, or even knowing who the token belonged to.

The question was How?

Thankfully, jQuery has this all solved with the domain: tag in the $.cookie() function. All we needed to do was to have a consistent upper-level domain for the apps that also worked for local development.

What I came up with was a simple function:

  function loginDomain() {
    var here = document.domain;
    if (here === 'localhost') {
      return here;
    }
    var parts = here.split('.');
    parts.shift();
    return parts.join('.');
  }

so that we would have a single login for the production hosts on theshop.com and another for the development hosts on da-shop.com. This would work perfectly if I could update all the places that the cookie was accessed.

What I learned was that the accessing of the cookie did not need to have the domain: tag, but setting it - or deleting it did. So for example, to read the token, I'd say:

  var tok = $.cookie('shop-auth-token');
  if (tok) {
    // ...
  }

and no matter where the cookie was saved, if it's in the "domain path", then it's OK. But when I needed to set the cookie I'd need to:

  var exp = new Date();
  exp.setTime(exp.getTime() + (8 * 60  * 60 * 1000));
  $.cookie('shop-auth-token', tok, { domain: loginDomain(),
                                     expires: exp );

and when I needed to invalidate it:

  $.removeCookie('shop-auth-token', { domain: loginDomain() });

Once I got these all set up, and in each application, one login on the specific login domain would then be successfully used on all apps in that domain with this code. Sweet! It worked like a charm, and it took me about 90 mins to figure this out, and put it in all the apps we have.

Big win!

Indexing Dates in Postgres JSONB Data

Wednesday, November 25th, 2015

PostgreSQL.jpg

Postgres is an amazing database. With 9.4's JSONB datatype, and a little clojure code, I can save data structures to and from Postgres, and have them query-able by any JDBC client. It's just amazing. But recently we had a problem at The Shop where we needed to index a field in a JSONB element, but it was a date, and so we ran into the problem of Immutable Functions.

Simply trying to create an index on the field:

  CREATE INDEX idx_d ON my_table ((fields->>'DateStarted'))::DATE;

didn't work with Postgres saying you can't have a mutable function in the creation of the index. So it was time to dig out the browser and look up what's going on. Sure enough, this was something well-known, and all involving the conversion of dates by locale, and why that was not allowed for an index.

Makes sense, but I still needed an index because the queries using this field in the WHERE clause were taking 6+ min, and we needed to really bring them down - like way down. Then I found that you could make functions that were tagged as IMMUTABLE and so made these:

  CREATE OR REPLACE FUNCTION mk_date(src VARCHAR)
  RETURNS DATE
  immutable AS $body$
    SELECT src::DATE;
  $body$ LANGUAGE SQL;
 
  CREATE OR REPLACE FUNCTION mk_datetime(src VARCHAR)
  RETURNS TIMESTAMP
  immutable AS $body$
    SELECT src::TIMESTAMP;
  $body$ LANGUAGE SQL;

the reason I chose to use ::date and ::timestamp is that they handle NULL values very well, and the corresponding functions don't do that so well.

With these functions, I was then able to create the index:

  CREATE INDEX idx_d ON my_table (mk_date((fields->>'DateStarted')));

and then as long as I used the same functions in my WHERE clause, everything was great.

The result was a 1000x increase in speed - 319,000 msec to 317 msec. Amazing.

I do love Postgres.

Just Slogging through Stuff

Friday, October 30th, 2015

Path

There are days that I don't know how I'll make it through the day back to my little house, and the one place that I really feel safe. And I'm continually surprised that every day around 6:00-ish, I find myself looking at that facade, and unlocking the door, and walking in and feeling an amazing sense of relief. I'm not better - I'm just a little bit stronger. I can take the heartache and pain that seems to follow my day-to-day existence.

A very nice friend of mine once said "It either gets better, or you get used to it. Either way, it seems like it's better." Not bad advice. It's not something that's easy to heed, but it is true nonetheless. Very true, indeed.

So this past month has been a lot more more of getting used to it than it getting really better. Just gotta keep slogging through the muck...

Datadog Gauges in Clojure

Tuesday, August 18th, 2015

Datadog

The Shop is big into Datadog, and it's not a bad metrics collection tool, which I've used very successfully from clojure. Based on the use of the local Datadog Agent (freely available from Datadog) and how easily it's placed on linux hosts - AWS or otherwise, it's a clear win for collecting metrics from your code and shipping them to a nice graphing/alerting platform like Datadog.

The code I've set up for this is pretty simple, and based on the com.codahale.metrics java libraries. With a simple inclusion into your project.clj file:

  [io.dropwizard.metrics/metrics-core "3.1.0"]
  [org.coursera/dropwizard-metrics-datadog "1.0.2"]

you can then write a very nice metrics namespace:

  (ns ns-toolkit.metrics
    "This is the code that handles the metrics and events through the Dropwizard
    Metrics core library, which, in turn, will ship it over UDP to the DataDog
    Agent running on localhost."
    (:require [clojure.tools.logging :refer [infof debugf warnf errorf]])
    (:import [com.codahale.metrics MetricRegistry]
             [org.coursera.metrics.datadog DatadogReporter]
             [org.coursera.metrics.datadog.transport UdpTransportFactory
                                                     UdpTransport]
             [java.util.concurrent TimeUnit]))
 
  ;; Create a simple MetricRegistry - but make it only when it's needed
  (defonce def-registry
    (delay
      (let [reg (MetricRegistry.)
            udp (.build (UdpTransportFactory.))
            rpt (-> (DatadogReporter/forRegistry reg)
                  (.withTransport udp)
                  (.withHost "localhost")
                  (.convertDurationsTo TimeUnit/MILLISECONDS)
                  (.convertRatesTo TimeUnit/SECONDS)
                  (.build))]
        (.start rpt 5 TimeUnit/SECONDS)
        reg)))
 
  ;; Somewhat faking java.jdbc's original *connection* behavior so that
  ;; we don't have to pass one around.
  (def ^:dynamic *registry* nil)
 
  (defn registry
    "Function to return either the externally provided MetricRegistry, or the
    default one that's constructed when it's needed, above. This allows the user
    the flexibility to live with the default - or make one just for their needs."
    []
    (or *registry* @def-registry))

And then we can define the simple instrumentation types from this:

  ;;
  ;; Functions to create/locate the different Metrics instruments available
  ;;
 
  (defn meter
    "Function to return a Meter for the registry with the provided tag
    (a String)."
    [tag]
    (if (string? tag)
      (.meter (registry) tag)))
 
  (defn counter
    "Function to return a Counter for the registry with the provided tag
    (a String)."
    [tag]
    (if (string? tag)
      (.counter (registry) tag)))
 
  (defn histogram
    "Function to return a Histogram for the registry with the provided tag
    (a String)."
    [tag]
    (if (string? tag)
      (.histogram (registry) tag)))
 
  (defn timer
    "Function to return a Timer for the registry with the provided tag
    (a String)."
    [tag]
    (if (string? tag)
      (.timer (registry) tag)))

These can then be held in maps or used for any reason at all. They automatically send their data to the local Datadog Agent over UDP so there's no delay to the logger, and since it's on the same box, the likelihood that something will be dropped is very small. It's a wonderful scheme.

But one of the things that's not covered in these metrics is the Gauge. And there's a really good reason for that - the Gauge for Datadog is something that is read from the Datadog Agent, and so has to be held onto by the code so that subsequent calls can be made against it for it's value.

In it's simplest form, the Gauge is just a value that's read by the agent on some interval and sent to the Datadog service. This callback functionality is done with a simple anonymous inner class in Java, but that's hard to do in clojure - or is it?

With Clojure 1.6, we have something that makes this quite easy - reify. If we simply add an import:

  (:import [com.codahale.metrics Gauge])

and then we can write the code to create an instance of Gauge with a custom getValue() method where we can put any clojure code in there we want. Like:

  ;;
  ;; Java functions for the Metrics library (DataDog) so that we can
  ;; constantly monitor the breakdown of the active docs in the system
  ;; by these functions.
  ;;
  (defn cnt-status
    "Function that takes a status value and finds the count of loans
    in the `laggy-counts` response that has that status. This is used
    in all the metrics findings - as it's the exact same code - just
    different status values."
    [s]
    (reify
      Gauge
      (getValue [this]
        (let [sm (first (filter #(= s (:status %)) (laggy-counts)))]
          (parse-int (:count sm))))))
 
  (defn register-breakdown
    "Function to register all the breakdowns of the loan status counts
    with the local Datadog agent to be sent to Datadog for plotting. This
    is a little interesting because Datadog will call *these* functions
    as needed to get the data to send, and we will control the load by
    using memoized functions."
    []
    (.register (met/registry)
      "trident.loan_breakdown.unset"
      (cnt-status nil))
    (.register (met/registry)
      "trident.loan_breakdown.submit_to_agent"
      (cnt-status "Submit to Agent"))
    (.register (met/registry)
      "trident.loan_breakdown.submit_to_lender"
      (cnt-status "Submit to Lender"))
    (.register (met/registry)
      "trident.loan_breakdown.submit_to_lender_approved"
      (cnt-status "Submit to Lender - Agent Approved"))
    (.register (met/registry)
      "trident.loan_breakdown.lender_approved"
      (cnt-status "Lender Approved")))

What I like about this is that I can allow the Datadog Agent to hit this code as often as it wants, and don't have to worry about the freshness of the data - or an excessive loan on the server resources for being hit too much. I can simply memoize the functions I'm using and then control the load on my end. It's very clean, and very nice.

Code Coverage Tools and Clojure

Tuesday, August 18th, 2015

Clojure.jpg

I was asked recently by my manager to look into code coverage tools for clojure because some of the other Senior Devs at The Shop wanted to have code (test) coverage numbers automatically generated for all projects as a part of continuous integration (TeamCity), and then available for all to see and track. I can certainly understand what they are trying to achieve - testable code that allows them to feel comfortable changing the code after the original author is long gone.

With complete test coverage, the theory goes, you can make a change, and then run these tests and prove to yourself that you haven't broken something because you didn't take the time to really understand the codebase.

It's an understanable goal from a management perspective... but even there, I have to think that this has never worked in my experience - and there's no way to have really foolproof tests. Yet today I read a nice post, which contained a quote from Rich H. about testing:

A bad design with a complete test suite is still a bad design. -- Rich H.

And the author, Alex, also verbalizes a lot of the concerns I've had over the years about tests. I can remember adding a feature in 15 mins and then spending several hours updating the unit tests because there were so many that had double-coverage, and yet none could (politically) be removed. Tests, it seems, are a lot like gold bricks - once people get some, they are very reluctant to get rid of any.

Yet they are code, and cost to maintain just like code. You can't think that tests are "free" once they are written... at least not if you're honest with yourself. And to Rich's point, it's better to think first and then attack the problem than it is to believe that tests are everything you need.

It was a very illuminating article. I'm glad a have a link to it now. I'll be sending it to a lot of folks who talk about tests in the future.

Slow Starts but Strong Finishes

Wednesday, July 8th, 2015

cubeLifeView.gifOver the course of the last week or so I've noticed that I'm having a really hard time getting motivated in the morning. I'm running well - and that's nice, but it's after that - when I leave for the train, and then work. It's just hard feeling that I'm heading out to do something worthwhile and good.

More like just do what I needed to collect a paycheck. And I hate that feeling.

Yet at about 4:00 pm, when the workday is almost over, I'm looking back at what I accomplished during the day and it's simply amazing! It's like I'm able to forget about all the things that had me down at the start of the day, and the things I'm able to do with the code I'm working on make me smile and laugh -- all the time!

So I'm trying to figure out what it's all about. Why the slow start, and strong finish? Why can't I look forward to the work so that my day starts with excitement? If I know I'm heading for a day that I'm going to do great, fun things - then why can't I use that as motivation at the start of the day?

I have a feeling it's related to The Three-Martini Lunch.

Specifically, I'm not really sure what's happening at The Shop right now. Yes, there's work, for now, but it could all end on Aug 1 - when I deliver this app I'm working on. They have plans, but it's all still awaiting approval by upper-management, and that, to me, means that it's still up for debate as to what will be done.

If that's the case, then I could find myself maintaining one little clojure app - and some .NET code (shudder) in just no time at all.

Yet when I'm able to ignore all that... forget the place I'm working, and just focus on the problems at the keyboard, then I can start solving problems, and delivering value. That makes me happy. It's like work - the real act of solving probelems, using clojure and it's amazing toolset, allows me to forget the realities of my current situation.

Clojure is my mid-afternoon drinking.

It doesn't solve anything. It's just getting drunk to avoid the problems you're facing. Tomorrow you'll be sober, and it'll all come back to you. So you dive into the "bottle" (work) and don't come up for air.

I'm not sure this is the correct analogy, but I have a feeling it's close. I love the work, and the work is what I find enjoyment from. But the place seems to be having a hard time with focus, and that concerns me.

Not much I can do about it, so it's likely to continue for a while.

SSL in Jetty (Compojure)

Monday, July 6th, 2015

Clojure.jpgI've been working on a project at The Shop and it's a simple-ish clojure back-end with a Javascript/jQuery/Bootstrap front-end, and it's not too bad. Getting there. But recently, it was time to add in the user authentication code, and that meant passwords. That meant security, and simple HTTP requests weren't going to do. We had to have security. HTTPS. So I dove down the rabbit hole of getting Jetty 7 to run as SSL only within a compojure/ring application.

What I found was that there really wasn't a wealth of information on the subject. Most folks expected a front-end service like nginx to handle the SSL, and then forward on to the clojure app requests on simple port 8080. While this is possible, it also means that port 8080 is there listening, and it's possible to have an intercept on that port and all of a sudden we're not so secure.

Nope. It needed to be Jetty on SSL on port 8443. Period.

Get the Certificate File

No doubt, getting the initial certificate file is the hardest part for most folks, as it's all about money. But I suppose self-signed certificates are useful - to a point, but being in a commercial organization, it's nice to see that there's a real certificate file, and all I need to do is to convert it to a Java Key Store file. In this case, it started as a pox file - a PKCS formatted certificate. Then:

  $ keytool -importkeystore -srckeystore my_cert.pfx -srcstoretype pkcs12 \
      -destkeystore my_cert.jks -deststoretype jks -deststorepass MY_PASSWORD

where it's clear what the file names are, and passwords. This gets you a my_cert.jks file that's what you are going to need. The MY_PASSWORD is also important as it will have to be in the code as well to read the file.

Put the KeyStore File in resources/certs

Like everything additional in a Meiningen project, this needs to go into the resources directory, and for convention, I'm throwing it into resources/certs as it's not the only thing in my resources directory for this project, and a little separation is a good thing.

Convert the Code to Dual-Mode

Working with the following dependencies in the project.clj file:

    ;; command line option processing
    [org.clojure/tools.cli "0.2.2"]
    ;; web server
    [compojure "1.3.4"]
    [ring/ring-core "1.3.2"]
    [ring/ring-jetty-adapter "1.3.2"]
    [ring.middleware.jsonp "0.1.6"]
    [ring/ring-defaults "0.1.5"]

and assuming you have a it running on port 8080, it might look a little like this:

(ns myapp.main
  (:require [clojure.java.io :refer [resource input-stream]]
            [clojure.tools.cli :refer [cli]]
            [clojure.tools.logging :refer [error info infof]]
            [myapp.server :refer [app]]
            [ring.adapter.jetty :as jt])
  (:import java.security.KeyStore)
  (:gen-class))
 
  (defn handle-args
    "Function to parse the arguments to the main entry point of this
    project and do what it's asking. By the time we return, it's all
    done and over."
    [args]
    (let [[params [action]] (cli args
               ["-p" "--port" "Listen on this port"
                 :default 8080 :parse-fn #(Integer. %)]
               ["-v" "--verbose" :flag true])]
      (cond
        (= "web" action)
          (jt/run-jetty app { :port (:port params) }))
        :else
          (do
            (info "Welcome to My App!")
            (println "Welcome to My App!")))))
 
  (defn -main
    "Function to kick off everything and clean up afterwards"
    [& args]
    (with-error-handling (handle-args args)))

this will assume port 8080, and yet allow the command-line args to override this. In order to add the SSL component to this, we simply have to add a few options to Jetty:

  (defn handle-args
    "Function to parse the arguments to the main entry point of this
    project and do what it's asking. By the time we return, it's all
    done and over."
    [args]
    (let [[params [action]] (cli args
               ["-p" "--port" "Listen on this port"
                 :default 8080 :parse-fn #(Integer. %)]
               ["-s" "--ssl-port" "Listen on this port"
                 :default 8443 :parse-fn #(Integer. %)]
               ["-v" "--verbose" :flag true])]
      (cond
        (= "web" action)
          (jt/run-jetty app { :port (:port params)
                              :ssl? true
                              :ssl-port (:ssl-port params)
                              :keystore "resources/certs/my_cert.jks"
                              :key-password "MY_PASSWORD" }))
        :else
          (do
            (info "Welcome to My App!")
            (println "Welcome to My App!")))))

Start this guy now, and it'll answer on port 8080 for normal HTTP traffic, and port 8443 for HTTPS traffic. So far, so good. But there's a catch here, and we'll get to it soon.

Make it SSL Only

The real request was to make is SSL-only, so we to remove the port 8080 traffic, but we can't just tell Jetty not to run that one - we have to actively remove it. Thankfully, composure allows us this flexibility. If we make a function:

  (defn remove-non-ssl-connectors
    "Function to configure the Jetty instance to remove all non-SSL
    connectors so that there's **no way** to get into this service BUT
    by SSL (https)."
    [server]
    (doseq [c (.getConnectors server)]
      (when-not (or (nil? c)
                    (instance? org.eclipse.jetty.server.ssl.
                                    SslSelectChannelConnector c))
        (.removeConnector server c)))
    server)

then we can add that as an option to Jetty to tell it to run on it's configuration:

  (defn handle-args
    "Function to parse the arguments to the main entry point of this
    project and do what it's asking. By the time we return, it's all
    done and over."
    [args]
    (let [[params [action]] (cli args
               ["-p" "--port" "Listen on this port"
                 :default 8080 :parse-fn #(Integer. %)]
               ["-s" "--ssl-port" "Listen on this port"
                 :default 8443 :parse-fn #(Integer. %)]
               ["-v" "--verbose" :flag true])]
      (cond
        (= "web" action)
          (jt/run-jetty app { :configurator remove-non-ssl-connectors
                              :port (:port params)
                              :ssl? true
                              :ssl-port (:ssl-port params)
                              :keystore "resources/certs/my_cert.jks"
                              :key-password "MY_PASSWORD" }))
        :else
          (do
            (info "Welcome to My App!")
            (println "Welcome to My App!")))))

and now if you restart the app, it won't answer on port 8080, but it will still answer on port 8443 with HTTPS. Getting very close.

Making it Deployable in an Uberjar

The final wrinkle is that the :keystone option is a location in the filesystem of the key store file. That's not good for deployments because it means that while the keystore file will be packaged up in the uberjar, it's not going to be referenced that way - and it will have to also exist in the filesystem in the same relative location.

This stinks.

So I did some more digging, and ring-jetty had what I needed - the ability to pass it a java.security.KeyStore instance. Now I needed to read the keystone file from the jar, into an instance of that object, and pass it in.

Start with:

  (defn load-keystore
    "Function to load the SSL KeyStore from the resources so that it's ready
    to be used to run the SSL connections for Jetty. This is a preferred method
    to having just a path for locating the certificate, as this allows the cert
    to be _included_ in the uberjar itself."
    [loc pw]
    (if (and (string? loc) (string? pw))
      (doto (KeyStore/getInstance (KeyStore/getDefaultType))
        (.load (input-stream (resource loc)) (char-array pw)))))

which will load a keystone file from the uberjar - placed in the resources directory. This was a major find for me, and made it all possible. That it only takes a few lines of clojure and java is just amazing.

Now we can put it all together:

  (defn handle-args
    "Function to parse the arguments to the main entry point of this
    project and do what it's asking. By the time we return, it's all
    done and over."
    [args]
    (let [[params [action]] (cli args
               ["-p" "--port" "Listen on this port"
                 :default 8080 :parse-fn #(Integer. %)]
               ["-s" "--ssl-port" "Listen on this port"
                 :default 8443 :parse-fn #(Integer. %)]
               ["-v" "--verbose" :flag true])]
      (cond
        (= "web" action)
          (let [loc "certs/my_cert.jks"   ;; SSL keystore location
                pw "MY_PASSWORD"]         ;; ...and password
            (jt/run-jetty app { :configurator remove-non-ssl-connectors
                                :port (:port params)
                                :ssl? true
                                :ssl-port (:ssl-port params)
                                :keystore (load-keystore loc pw)
                                :key-password pw }))
        :else
          (do
            (info "Welcome to My App!")
            (println "Welcome to My App!")))))