Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Reflect dum for more like clojure way.

  • Loading branch information...
commit 7a753260186224f965c02a09da7fdeee1a28db18 1 parent ac2521f
@Ruiyun authored
View
544 src/cljain/dum.clj
@@ -1,290 +1,254 @@
-(ns ^{:doc "The DSL for SIP.
- Here is a simplest example show how to use it:
-
- (use 'cljain.dum)
- (require '[cljain.sip.core :as sip]
- '[cljain.sip.address :as addr])
-
- (def-request-handler :MESSAGE [request transaction dialog]
- (println \"Received: \" (.getContent request))
- (send-response! 200 :in transaction :pack \"I receive your message.\"))
-
- (sip/global-bind-sip-provider! (sip/sip-provider! \"my-app\" \"localhost\" 5060 \"udp\"))
- (initialize! :user \"bob\" :domain \"home\" :display-name \"Bob\")
- (sip/start!)
-
- (send-request! :MESSAGE :to (addr/address \"sip:alice@localhost\") :pack \"Hello, Alice.\"
- :on-success (fn [_ _ response] (println \"Fine! response: \" (.getContent response)))
- :on-failure (fn [_ _ response] (println \"Oops!\" (.getStatusCode response)))
- :on-timeout (fn [_] (println \"Timeout, try it later.\")))"
- :author "ruiyun"
- :added "0.2.0"}
- cljain.dum
- (:use [clojure.string :only [upper-case]])
- (:require [cljain.sip.core :as core]
- [cljain.sip.header :as header]
- [cljain.sip.address :as addr]
- [cljain.sip.message :as msg]
- [cljain.sip.dialog :as dlg]
- [cljain.sip.transaction :as trans]
- [cljain.tools.timer :as timer]))
-
-(def ^{:doc "Store current account information, contain :user :domain :display-name"
- :added "0.2.0"
- :private true
- :dynamic true}
- account-map (atom {}))
-
-(def ^{:doc "Store all the request handlers by 'def-request-handler'"
- :added "0.2.0"
- :private true}
- request-handlers-map (atom {}))
-
-(defn account
- "Get current bound account information."
- {:added "0.2.0"}
- []
- @account-map)
-
-(defn add-handler!
- "Add a sip request received event handler to dum."
- {:added "0.2.0"}
- [method process-fn]
- (let [method (keyword (upper-case (name method)))]
- (swap! request-handlers-map assoc method process-fn)))
-
-(defmacro def-request-handler
- "Define the handler to handle the sip request received from under layer.
-
- (def-request-handler :MESSAGE [request transaction dialog]
- (do-somethin)
- ...)"
- {:arglists '([method [request transaction dialog] body*])
- :added "0.2.0"}
- [method args & body]
- `(let [f# (fn [~@args] ~@body)]
- (add-handler! ~method f#)))
-
-(defn- request-processor
- "Return a funcion to process request."
- {:added "0.2.0"}
- []
- (fn [{:keys [request server-transaction dialog]}]
- (let [method (keyword (upper-case (name (msg/method request))))
- process-fn (get @request-handlers-map method)
- server-transaction (or server-transaction (core/new-server-transaction! request))]
- (and process-fn (process-fn request server-transaction dialog)))))
-
-(defn- response-processor
- "Return a function to process response."
- {:added "0.2.0"}
- []
- (fn [{:keys [response client-transaction dialog]}]
- (when (not (nil? client-transaction))
- (let [{process-success :on-success
- process-failure :on-failure} (trans/application-data client-transaction)
- lead-number-of-status-code (quot (msg/status-code response) 100)]
- (cond ; ignore 1xx provisional response
- (= lead-number-of-status-code 2) ; 2xx means final success response
- (and process-success (process-success client-transaction dialog response))
- (> lead-number-of-status-code 3) ; 4xx, 5xx, 6xx means error
- (and process-failure (process-failure client-transaction dialog response)))))))
-
-(defn- timeout-processor
- "Return a function to process transaction timeout event."
- {:added "0.2.0"}
- []
- (fn [{:keys [transaction]}]
- (let [process-timeout (:on-timeout (trans/application-data transaction))]
- (and process-timeout (process-timeout transaction)))))
-
-(defn- install-event-handler
- "Install a new dum listener to under layer."
- {:added "0.2.0"}
- []
- (core/set-listener!
- :request (request-processor)
- :response (response-processor)
- :timeout (timeout-processor)))
-
-(defn initialize!
- "Set the default user account information with the current bound provider."
- {:added "0.2.0"}
- [& {:keys [user domain display-name]}]
- (reset! account-map {:user user, :domain domain, :display-name display-name})
- (install-event-handler))
-
-(defn finalize!
- "Clean user account information with the current bound provider."
- {:added "0.2.0"}
- []
- {:pre [(core/provider-can-be-found?)]}
- (reset! account-map {})
- (reset! request-handlers-map {}))
-
-(defn legal-content?
- "Check the content is a string or a map with :type, :sub-type, :length and :content keys."
- {:added "0.2.0"}
- [content]
- (or (string? content)
- (and (map? content)
- (= (sort [:type :sub-type :content]) (sort (keys content))))))
-
-(defn- try-set-content!
- "Parse the :pack argument from 'send-request!' and 'send-response!',
- then try to set the appropriate Content-Type header and content to the message."
- {:added "0.2.0"}
- [message content]
- (when (not (nil? content))
- (let [content-type (name (or (:type content) "text"))
- content-sub-type (name (or (:sub-type content) "plain"))
- content-type-header (header/content-type content-type content-sub-type)
- content (or (:content content) content)]
- (msg/set-content! message content-type-header content))))
-
-; TODO Do not user application data in transaction or dialog for event dispatch.
-
-(defn send-request!
- "Fluent style sip message send function.
-
- The simplest example just send a trivial MESSAGE:
- (send-request! :MESSAGE :to (address (uri \"192.168.1.128\"))
- (send-request! :INFO :in dialog-with-bob)
-
- More complicate example:
- (let [bob (address (uri \"dream.com\" :user \"bob\") \"Bob\")
- alice (address (uri \"dream.com\" :user \"alice\") \"Alice\")]
- (send-request! \"MESSAGE\" :pack \"Welcome\" :to bob :from alice
- :use \"UDP\" :on-success #(prn %1 %2 %3) :on-failure #(prn %1 %2 %3) :on-timeout #(prn %)))
-
- If the pack content is not just a trivial string, provide a well named funciont
- to return a content map is recommended.
- {:type \"application\"
- :sub-type \"pidf-diff+xml\"
- :content content-object}"
- {:added "0.2.0"}
- [message & {content :pack
- to-address :to
- from-address :from
- transport :use
- dialog :in
- more-headers :more-headers
- on-success :on-success
- on-failure :on-failure
- on-timeout :on-timeout}]
- {:pre [(core/provider-can-be-found?)
- (not-empty @account-map)
- (or (nil? content) (legal-content? content))
- (or (and (= (upper-case (name message)) "REGISTER") (nil? to-address))
- (addr/address? to-address))
- (or (nil? from-address) (addr/address? from-address))
- (or (nil? transport) (#{"UDP" "udp" "TCP" "tcp"} transport))
- (or (nil? dialog) (core/dialog? dialog))
- (or (nil? more-headers) (sequential? more-headers))
- (or (nil? on-success) (fn? on-success))
- (or (nil? on-failure) (fn? on-failure))
- (or (nil? on-timeout) (fn? on-timeout))]}
- (let [{:keys [user domain display-name]} (account)
- {:keys [ip port transport]} (if (nil? transport)
- (core/listening-point)
- (core/listening-point transport))
- domain (or domain ip)
- method (upper-case (name message))]
- (if (not (nil? dialog))
- (let [request (dlg/create-request dialog method)
- transaction (core/new-client-transcation! request)]
- (dlg/send-request! dialog transaction)
- request)
- (let [request-uri (if (nil? to-address)
- (throw (IllegalArgumentException. "Require the ':to' option for send an out of dialog request."))
- (addr/uri-from-address to-address))
- from-address (or from-address (addr/address (addr/sip-uri domain :user user) display-name))
- from-header (header/from from-address (header/gen-tag))
- to-header (if (= method "REGISTER") ; for REGISTER, To header's uri should equal the From header's.
- (header/to (header/get-address from-header) nil) ; tag will be auto generated in transaction
- (header/to to-address nil))
- contact-header (header/contact (addr/address (addr/sip-uri ip :port port :transport transport :user user)))
- call-id-header (core/gen-call-id-header)
- via-header (header/via ip port transport nil) ; via branch will be auto generated before message sent.
- request (msg/request method request-uri from-header call-id-header to-header via-header contact-header more-headers)
- transaction (core/new-client-transcation! request)]
- (trans/set-application-data! transaction {:on-success on-success, :on-failure on-failure :on-timeout on-timeout})
- (try-set-content! request content)
- (trans/send-request! transaction)
- request))))
-
-(defn send-response!
- "Send response with a server transactions."
- {:added "0.2.0"}
- [status-code & {transaction :in, content :pack, transport :use, more-headers :more-headers}]
- {:pre [(core/provider-can-be-found?)
- (not-empty @account-map)
- (core/transaction? transaction)
- (or (nil? content) (legal-content? content))
- (or (nil? transport) (#{"UDP" "udp" "TCP" "tcp"} transport))
- (or (nil? more-headers) (sequential? more-headers))]}
- (let [{:keys [ip port transport]} (if (nil? transport)
- (core/listening-point)
- (core/listening-point transport))
- user (:user (account))
- contact-header (header/contact (addr/address (addr/sip-uri ip :port port :transport transport :user user)))
- request (trans/request transaction)
- response (msg/response status-code request contact-header more-headers)]
- (try-set-content! response content)
- (trans/send-response! transaction response)))
-
-(def ^{:doc "Store contexts for the register auto refresh."
- :added "0.3.0"
- :private true}
- register-ctx-map (atom {}))
- :deprecated "0.4.0"
-
-(defn register-to
- "Send REGISTER sip message to target registry server, and auto refresh register before
- expired.
-
- Notice: No matter the first register whether sent successfully, the register auto refresh
- timer will be started. Application can choose to stop it use 'stop-refresh-register', or
- let it auto retry after expires secondes."
- {:added "0.3.0"}
- [registry-address expires-seconds & {:keys [on-success on-failure on-refreshed on-refresh-failed]}]
- {:pre [(addr/address? registry-address)
- (> expires-seconds 38) ; because a transaction timeout is 32 seconds
- (or (nil? on-success) (fn? on-success))
- (or (nil? on-failure) (fn? on-failure))
- (or (nil? on-refreshed) (fn? on-refreshed))
- (or (nil? on-refresh-failed) (fn? on-refresh-failed))]}
- (let [expires-header (header/expires expires-seconds)
- safer-interval-milliseconds (* (- expires-seconds 5) 1000)
- register-ctx (get @register-ctx-map registry-address)]
- (when (nil? register-ctx)
- (send-request! :REGISTER :to registry-address
- :more-headers [expires-header]
- :on-success (fn [transaction _ _]
- (swap! register-ctx-map assoc registry-address
- {:timer (timer/run! (timer/timer "Register-Refresh")
- (timer/task
- (let [request (.clone (get-in @register-ctx-map [registry-address :request]))
- request (msg/inc-sequence-number! request)
- request (msg/remove-header! request "Via")
- transaction (core/new-client-transcation! request)]
- (trans/set-application-data! transaction
- {:on-success (fn [_ _ _] (and on-refreshed (on-refreshed)))
- :on-failure (fn [_ _ _] (and on-refresh-failed (on-refresh-failed)))
- :on-timeout (fn [_] (and on-refresh-failed (on-refresh-failed)))})
- (trans/send-request! transaction)
- (swap! register-ctx-map assoc-in [registry-address :request] request)))
- :delay safer-interval-milliseconds
- :period safer-interval-milliseconds)
- :request (trans/request transaction)})
- (and on-success (on-success)))
- :on-failure (fn [_ _ _] (and on-failure (on-failure)))
- :on-timeout (fn [_] (and on-failure (on-failure)))))))
-
-(defn unregister-to
- "Send REGISTER sip message with expires 0 for unregister."
- {:added "0.3.0"}
- [registry-address]
- (let [refresh-timer (get-in @register-ctx-map [registry-address :timer])]
- (and refresh-timer (timer/cancel! refresh-timer))
- (swap! register-ctx-map dissoc registry-address)))
+(ns ^{:doc "The SIP DSL by Clojure.
+ Here is a simplest example show how to use it:
+
+ (use 'cljain.dum)
+ (require '[cljain.sip.core :as sip]
+ '[cljain.sip.address :as addr])
+
+ (defmethod handle-request :MESSAGE [request & [transaction]]
+ (println \"Received: \" (.getContent request))
+ (send-response! 200 :in transaction :pack \"I receive the message from myself.\"))
+
+ (global-alter-account :user \"bob\" :domain \"localhost\" :display-name \"Bob\")
+ (sip/global-bind-sip-provider! (sip/sip-provider! \"my-app\" \"localhost\" 5060 \"udp\"))
+ (sip/set-listener! (dum-listener))
+ (sip/start!)
+
+ (send-request! :MESSAGE :to (addr/address \"sip:bob@localhost\") :pack \"Hello, Bob.\"
+ :on-success (fn [& {:keys [response]}] (println \"Fine! response: \" (.getContent response)))
+ :on-failure (fn [& {:keys [response]}] (println \"Oops!\" (.getStatusCode response)))
+ :on-timeout (fn [_] (println \"Timeout, try it later.\")))"
+ :author "ruiyun"
+ :added "0.2.0"}
+ cljain.dum
+ (:use [clojure.string :only [upper-case]])
+ (:require [cljain.sip.core :as core]
+ [cljain.sip.header :as header]
+ [cljain.sip.address :as addr]
+ [cljain.sip.message :as msg]
+ [cljain.sip.dialog :as dlg]
+ [cljain.sip.transaction :as trans]
+ [ruiyun.tools.timer.core :as timer]
+ [clojure.tools.logging :as log])
+ (:import [javax.sip Transaction SipProvider SipFactory]
+ [javax.sip.message Request Response]
+ [javax.sip.address Address]
+ [javax.sip.header HeaderAddress]
+ [gov.nist.javax.sip.clientauthutils AccountManager UserCredentials AuthenticationHelper]
+ [gov.nist.javax.sip SipStackExt]))
+
+(def ^{:doc "A map contain these four fields: :user, :domain, :password and :display-name."
+ :added "0.4.0"
+ :dynamic true}
+ *current-account*)
+
+(defn global-alter-account [& {:keys [user domain password display-name] :as account}]
+ (alter-var-root #'*current-account* (fn [_] account)))
+
+(defmulti handle-request (fn [request & [transaction dialog]] (keyword (.getMethod request))))
+
+(defrecord AccountManagerImpl []
+ AccountManager
+ (getCredentials [this challenged-transaction realm]
+ (reify UserCredentials
+ (getUserName [_] (*current-account* :user))
+ (getPassword [_] (*current-account* :password))
+ (getSipDomain [_] (*current-account* :domain)))))
+
+(defn dum-listener
+ "Create a dum default event listener.
+ You can use it for 'cljain.sip.core/set-listener!' function."
+ {:added "0.4.0"}
+ []
+ {:request (fn [request transaction dialog]
+ (let [transaction (or transaction (core/new-server-transaction! request))]
+ (handle-request request transaction dialog)))
+ :response (fn [response transaction dialog]
+ (when (not (nil? transaction))
+ (let [{process-success :on-success
+ process-failure :on-failure} (.getApplicationData transaction)
+ status-code (.getStatusCode response)
+ lead-number-of-status-code (quot status-code 100)]
+ (cond ; ignore 1xx provisional response
+ (= lead-number-of-status-code 2) ; 2xx means final success response
+ (and process-success (process-success :transaction transaction :dialog dialog :response response))
+
+ (or (= status-code Response/UNAUTHORIZED)
+ (= status-code Response/PROXY_AUTHENTICATION_REQUIRED)) ; need authentication
+ (let [header-factory (.createHeaderFactory core/sip-factory)
+ sip-stack (.getSipStack (core/sip-provider))
+ auth-helper (.getAuthenticationHelper sip-stack (AccountManagerImpl.) header-factory)
+ client-trans-with-auth (.handleChallenge auth-helper response transaction (core/sip-provider) 5)]
+ (.setApplicationData client-trans-with-auth (.getApplicationData transaction))
+ (trans/send-request! client-trans-with-auth))
+
+ (> lead-number-of-status-code 3) ; 4xx, 5xx, 6xx means error
+ (and process-failure (process-failure :transaction transaction :dialog dialog :response response))))))
+ :timeout (fn [transaction _]
+ (prn "now calling process-timeout.")
+ (let [process-timeout (:on-timeout (.getApplicationData transaction))]
+ (and process-timeout (process-timeout :transaction transaction))))})
+
+(defn legal-content?
+ "Check the content is a string or a map with :type, :sub-type, :length and :content keys."
+ {:added "0.2.0"}
+ [content]
+ (or (string? content)
+ (and (map? content)
+ (= (sort [:type :sub-type :content]) (sort (keys content))))))
+
+(defn- set-content!
+ "Parse the :pack argument from 'send-request!' and 'send-response!',
+ then try to set the appropriate Content-Type header and content to the message."
+ {:added "0.2.0"}
+ [message content]
+ (when (not (nil? content))
+ (let [content-type (name (or (:type content) "text"))
+ content-sub-type (name (or (:sub-type content) "plain"))
+ content-type-header (header/content-type content-type content-sub-type)
+ content (or (:content content) content)]
+ (.setContent message content content-type-header))))
+
+(defn send-request!
+ "Fluent style sip message send function.
+
+ The simplest example just send a trivial MESSAGE:
+
+ (send-request! :MESSAGE :to (sip-address \"192.168.1.128\"))
+ (send-request! :INFO :in dialog-with-bob)
+
+ More complicate example:
+
+ (send-request! \"MESSAGE\" :pack \"Welcome\" :to (sip-address \"192.168.1.128\" :user \"bob\") :use \"UDP\"
+ :on-success #(prn %1 %2 %3) :on-failure #(prn %1 %2 %3) :on-timeout #(prn %))
+
+ If the pack content is not just a trivial string, provide a well named funciont
+ to return a content map like this is recommended:
+
+ {:type \"application\"
+ :sub-type \"pidf-diff+xml\"
+ :content content-object}"
+ {:added "0.2.0"}
+ [message & {content :pack
+ to-address :to
+ transport :use
+ dialog :in
+ more-headers :more-headers
+ on-success :on-success
+ on-failure :on-failure
+ on-timeout :on-timeout}]
+ {:pre [(core/provider-can-be-found?)
+ (bound? #'*current-account*)
+ (or (nil? content) (legal-content? content))
+ (or (and (= (upper-case (name message)) "REGISTER") (nil? to-address))
+ (addr/address? to-address))
+ (or (nil? transport) (#{"UDP" "udp" "TCP" "tcp"} transport))
+ (or (nil? dialog) (core/dialog? dialog))
+ (or (nil? more-headers) (sequential? more-headers))
+ (or (nil? on-success) (fn? on-success))
+ (or (nil? on-failure) (fn? on-failure))
+ (or (nil? on-timeout) (fn? on-timeout))]}
+ (let [{:keys [user domain display-name]} *current-account*
+ {:keys [ip port transport]} (if (nil? transport)
+ (core/listening-point)
+ (core/listening-point transport))
+ domain (or domain ip)
+ method (upper-case (name message))]
+ (if (not (nil? dialog))
+ (let [request (dlg/create-request dialog method)
+ transaction (core/new-client-transcation! request)]
+ (dlg/send-request! dialog transaction)
+ request)
+ (let [request-uri (if (nil? to-address)
+ (throw (IllegalArgumentException. "Require the ':to' option for send an out of dialog request."))
+ (.getURI to-address))
+ from-address (addr/sip-address domain :user user :display-name display-name)
+ from-header (header/from from-address (header/gen-tag))
+ to-header (if (= method "REGISTER") ; for REGISTER, To header's uri should equal the From header's.
+ (header/to (.getAddress from-header) nil) ; tag will be auto generated in transaction
+ (header/to to-address nil))
+ contact-header (header/contact (addr/sip-address ip :port port :transport transport :user user))
+ call-id-header (core/gen-call-id-header)
+ via-header (header/via ip port transport nil) ; via branch will be auto generated before message sent.
+ request (msg/request method request-uri from-header call-id-header to-header via-header contact-header more-headers)
+ transaction (core/new-client-transcation! request)]
+ (.setApplicationData transaction {:on-success on-success, :on-failure on-failure :on-timeout on-timeout})
+ (set-content! request content)
+ (trans/send-request! transaction)
+ request))))
+
+(defn send-response!
+ "Send response with a server transactions."
+ {:added "0.2.0"}
+ [status-code & {transaction :in, content :pack, transport :use, more-headers :more-headers}]
+ {:pre [(core/provider-can-be-found?)
+ (bound? #'*current-account*)
+ (core/transaction? transaction)
+ (or (nil? content) (legal-content? content))
+ (or (nil? transport) (#{"UDP" "udp" "TCP" "tcp"} transport))
+ (or (nil? more-headers) (sequential? more-headers))]}
+ (let [{:keys [ip port transport]} (if (nil? transport)
+ (core/listening-point)
+ (core/listening-point transport))
+ user (*current-account* :user)
+ contact-header (header/contact (addr/sip-address ip :port port :transport transport :user user))
+ request (.getRequest transaction)
+ response (msg/response status-code request contact-header more-headers)]
+ (set-content! response content)
+ (trans/send-response! transaction response)))
+
+(def ^{:doc "Store contexts for the register auto refresh."
+ :added "0.3.0"
+ :private true}
+ register-ctx-map (atom {}))
+
+(defn register-to
+ "Send REGISTER sip message to target registry server, and auto refresh register before
+ expired.
+
+ Notice: No matter the first register whether sent successfully, the register auto refresh
+ timer will be started. Application can choose to stop it use 'stop-refresh-register', or
+ let it auto retry after expires secondes."
+ {:added "0.3.0"}
+ [registry-address expires-seconds & {:keys [on-success on-failure on-refreshed on-refresh-failed]}]
+ {:pre [(addr/address? registry-address)
+ (> expires-seconds 38) ; because a transaction timeout is 32 seconds
+ (or (nil? on-success) (fn? on-success))
+ (or (nil? on-failure) (fn? on-failure))
+ (or (nil? on-refreshed) (fn? on-refreshed))
+ (or (nil? on-refresh-failed) (fn? on-refresh-failed))]}
+ (let [expires-header (header/expires expires-seconds)
+ safer-interval-milliseconds (* (- expires-seconds 5) 1000)
+ register-ctx (get @register-ctx-map registry-address)]
+ (when (nil? register-ctx)
+ (send-request! :REGISTER :to registry-address
+ :more-headers [expires-header]
+ :on-success (fn [& {:keys [transaction]}]
+ (swap! register-ctx-map assoc registry-address
+ {:timer (timer/run-task! #(let [request (.clone (get-in @register-ctx-map [registry-address :request ]))
+ request (msg/inc-sequence-number! request)
+ request (doto request (.removeHeader "Via"))
+ transaction (core/new-client-transcation! request)]
+ (.setApplicationData transaction
+ {:on-success (fn [& _] (and on-refreshed (on-refreshed)))
+ :on-failure (fn [& _] (and on-refresh-failed (on-refresh-failed)))
+ :on-timeout (fn [& _] (and on-refresh-failed (on-refresh-failed)))})
+ (trans/send-request! transaction)
+ (swap! register-ctx-map assoc-in [registry-address :request ] request))
+ :by (timer/timer "Register-Refresh")
+ :delay safer-interval-milliseconds
+ :period safer-interval-milliseconds
+ :on-exception #(log/warn "Register refresh exception: " %))
+ :request (trans/request transaction)})
+ (prn "current register-ctx-map: " @register-ctx-map)
+ (and on-success (on-success)))
+ :on-failure (fn [& _] (and on-failure (on-failure)))
+ :on-timeout (fn [& _] (and on-failure (on-failure)))))))
+
+(defn unregister-to
+ "Send REGISTER sip message with expires 0 for unregister."
+ {:added "0.3.0"}
+ [registry-address]
+ (let [refresh-timer (get-in @register-ctx-map [registry-address :timer])]
+ (and refresh-timer (timer/cancel! refresh-timer))
+ (swap! register-ctx-map dissoc registry-address)))
View
435 src/cljain/sip/core.clj
@@ -1,221 +1,214 @@
-(ns ^{:author "ruiyun"
- :added "0.2.0"}
- cljain.sip.core
- (:use [clojure.string :only [upper-case lower-case capitalize split]])
- (:require [clojure.tools.logging :as log])
- (:import [java.util Properties]
- [javax.sip SipFactory SipStack SipProvider SipListener Transaction ClientTransaction Dialog
- ResponseEvent IOExceptionEvent TimeoutEvent Timeout TransactionTerminatedEvent DialogTerminatedEvent]))
-
-(def ^{:doc "The instance of JAIN-SIP SipFactory."
- :added "0.2.0"}
- sip-factory (doto (SipFactory/getInstance) (.setPathName "gov.nist")))
-
-(def ^{:doc "Before call any function expect 'sip-provider!' in the cljain.sip.core namespace,
- please binding *sip-provider* with the current provider object first."
- :added "0.2.0"
- :dynamic true}
- *sip-provider*)
-
-(def ^{:doc "Set by 'global-start!'"
- :added "0.2.0"
- :private true}
- global-sip-provider (atom nil))
-
-(defn global-bind-sip-provider!
- "Bind the sip-provider in global scope."
- {:added "0.2.0"}
- [provider]
- (reset! global-sip-provider provider))
-
-(defn global-unbind-sip-provider!
- "Unbind the sip-provider in global scope."
- {:added "0.2.0"}
- []
- (reset! global-sip-provider nil))
-
-(defn already-bound-provider?
- "Check whether the *sip-provider* has been bound in current thread."
- {:added "0.2.0"}
- []
- (and (bound? #'*sip-provider*) (instance? SipProvider *sip-provider*)))
-
-(defn provider-can-be-found?
- "Check where the *sip-provider* has been bound or global sip-provider has been set."
- {:added "0.2.0"}
- []
- (or (already-bound-provider?) (not (nil? @global-sip-provider))))
-
-(defn sip-provider
- "Get the current bound *sip-provider* or global-sip-provider."
- {:added "0.2.0"}
- []
- (if (already-bound-provider?)
- *sip-provider*
- (or @global-sip-provider (throw (RuntimeException. "The 'cljain.sip.core/*sip-provider*' should be bound.")))))
-
-(defn sip-stack
- "Get the SipStack object from a SipProvider object."
- {:added "0.2.0"}
- []
- (.getSipStack (sip-provider)))
-
-(defn stack-name
- "Get the SipStack name from a SipProvider object."
- {:added "0.2.0"}
- []
- (.getStackName (sip-stack)))
-
-(defn- map-listening-point
- "Get the ip, port, and transport from a ListeningPoint object, then pack them as a map."
- {:added "0.2.0"}
- [lp]
- {:ip (.getIPAddress lp) :port (.getPort lp) :transport (.getTransport lp)})
-
-(defn listening-point
- "Get the current bound listening ip, port and transport information."
- {:added "0.2.0"}
- ([] (map-listening-point (.getListeningPoint (sip-provider))))
- ([transport] (map-listening-point (.getListeningPoint (sip-provider) transport))))
-
-(defn sip-provider!
- "Create a new SipProvider with meaningful name, local ip, port, transport and other optional SipStack properties.
- Rember, the name must be unique to make a distinction between other provider.
- To set standard SipStack properties, use the property's lowcase short name as keyword.
- If want to set the nist define property, let property keys lead with 'nist'.
-
- (sip-provider \"cool-phone\" \"192.168.1.2\" 5060 \"UDP\" :outbound-proxy \"192.168.1.128\")
-
- More SipStack properties document can be found here:
- http://hudson.jboss.org/hudson/job/jain-sip/lastSuccessfulBuild/artifact/javadoc/index.html
- and
- http://hudson.jboss.org/hudson/job/jain-sip/lastSuccessfulBuild/artifact/javadoc/index.html"
- {:added "0.2.0"}
- [name ip port transport & properties]
- {:pre [(or (nil? (:outbound-proxy properties))
- (re-find #"^\d+\.\d+\.\d+\.\d+(:\d+)?(/(tcp|TCP|udp|UDP))?$" (:outbound-proxy properties)))]}
- (let [more-props (apply array-map properties)
- props (Properties.)]
- (.setProperty props "javax.sip.STACK_NAME" name)
- (doseq [prop more-props]
- (let [prop-name (upper-case (.replace (name (first prop)) \- \_))
- prop-name (if (.startsWith prop-name "nist")
- (str "gov.nist.javax.sip." prop-name)
- (str "javax.sip." prop-name))]
- (.setProperty props prop-name (second prop))))
- (let [sip-stack (.createSipStack sip-factory props)
- listening-point (.createListeningPoint sip-stack ip port transport)]
- (.createSipProvider sip-stack listening-point))))
-
-(defn- trans-from-event
- "Get ServerTransaction or ClientTransaction from an event object."
- {:added "0.2.0"}
- [event]
- (if (.isServerTransaction event)
- (.getServerTransaction event)
- (.getClientTransaction event)))
-
-(defmacro trace-call
- "Log the exception from event callback."
- {:added "0.2.0"
- :private true}
- [callback event arg]
- (let [callback-name (str "process" (reduce str (map capitalize (split (str callback) #"-"))))]
- `(try
- (log/trace ~callback-name "has been invoked." \newline "event:" (bean ~event))
- (and ~callback (~callback ~arg))
- (catch Exception e#
- (log/error "Exception occurs when calling the event callback" ~callback-name ":" e#)))))
-
-(defn set-listener!
- "Set several event listening function to current bound provider.
- Because JAIN-SIP just allow set listener once, if call 'set-listener' more then one times,
- an exception will be thrown."
- {:added "0.2.0"}
- [& {:keys [request response timeout io-exception transaction-terminated dialog-terminated]}]
- {:pre [(or (nil? request) (fn? request))
- (or (nil? response) (fn? response))
- (or (nil? timeout) (fn? timeout))
- (or (nil? io-exception) (fn? io-exception))
- (or (nil? transaction-terminated) (fn? transaction-terminated))
- (or (nil? dialog-terminated) (fn? dialog-terminated))]}
- (.addSipListener (sip-provider)
- (reify SipListener
- (processRequest [this event]
- (trace-call request event {:request (.getRequest event)
- :server-transaction (.getServerTransaction event)
- :dialog (.getDialog event)}))
- (processResponse [this event]
- (trace-call response event {:response (.getResponse event)
- :client-transaction (.getClientTransaction event)
- :dialog (.getDialog event)}))
- (processIOException [this event]
- (trace-call io-exception event {:host (.getHost event)
- :port (.getPort event)
- :transport (.getTransport event)}))
- (processTimeout [this event]
- (trace-call timeout event {:transaction (trans-from-event event)
- :timeout (.. event (getTimeout) (getValue))}))
- (processTransactionTerminated [this event]
- (trace-call transaction-terminated event (trans-from-event event)))
- (processDialogTerminated [this event]
- (trace-call dialog-terminated event (.getDialog event))))))
-
-(defn start!
- "Start to run the stack which bound with current bound provider."
- {:added "0.2.0"}
- []
- (.start (sip-stack)))
-
-(defn stop-and-release!
- "Stop the stack wich bound with current bound provider. And release all resource associated the stack.
- Becareful, after called 'stop!' function, all other function include 'start!' will be invalid.
- A new provider need be generated for later call."
- {:added "0.2.0"}
- []
- (.stop (sip-stack)))
-
-(defn gen-call-id-header
- "Generate a new Call-ID header use current bound provider."
- {:added "0.2.0"}
- []
- (.getNewCallId (sip-provider)))
-
-(defn send-request!
- "Send out of dialog SipRequest use current bound provider."
- {:added "0.2.0"}
- [request]
- (.sendRequest (sip-provider) request))
-
-(defn new-server-transaction!
- "An application has the responsibility of deciding to respond to a Request
- that does not match an existing server transaction."
- {:added "0.2.0"}
- [request]
- (.getNewServerTransaction (sip-provider) request))
-
-(defn new-client-transcation!
- "Before an application can send a new request it must first request
- a new client transaction to handle that Request."
- {:added "0.2.0"}
- [request]
- (.getNewClientTransaction (sip-provider) request))
-
-(defn transaction?
- "Check the obj is an instance of javax.sip.Transaction.
- Both ClientTransaction and ServerTransaction are pass."
- {:added "0.4.0"}
- [object]
- (instance? Transaction object))
-
-(defn new-dialog!
- "Create a dialog for the given transaction."
- {:added "0.2.0"}
- [transaction]
- (.getNewDialog (sip-provider) transaction))
-
-(defn dialog?
- "Check the obj is an instance of javax.sip.Dialog"
- {:added "0.4.0"}
- [object]
- (instance? Dialog object))
+(ns ^{:author "ruiyun"
+ :added "0.2.0"}
+ cljain.sip.core
+ (:use [clojure.string :only [upper-case lower-case capitalize split]])
+ (:require [clojure.tools.logging :as log])
+ (:import [java.util Properties]
+ [javax.sip SipFactory SipStack SipProvider SipListener Transaction ClientTransaction Dialog
+ ResponseEvent IOExceptionEvent TimeoutEvent Timeout TransactionTerminatedEvent DialogTerminatedEvent]))
+
+(def ^{:doc "The instance of JAIN-SIP SipFactory."
+ :added "0.2.0"}
+ sip-factory (doto (SipFactory/getInstance) (.setPathName "gov.nist")))
+
+(def ^{:doc "Before call any function expect 'sip-provider!' in the cljain.sip.core namespace,
+ please binding *sip-provider* with the current provider object first."
+ :added "0.2.0"
+ :dynamic true}
+ *sip-provider*)
+
+(def ^{:doc "Set by 'global-start!'"
+ :added "0.2.0"
+ :private true}
+ global-sip-provider (atom nil))
+
+(defn global-bind-sip-provider!
+ "Bind the sip-provider in global scope."
+ {:added "0.2.0"}
+ [provider]
+ (reset! global-sip-provider provider))
+
+(defn global-unbind-sip-provider!
+ "Unbind the sip-provider in global scope."
+ {:added "0.2.0"}
+ []
+ (reset! global-sip-provider nil))
+
+(defn already-bound-provider?
+ "Check whether the *sip-provider* has been bound in current thread."
+ {:added "0.2.0"}
+ []
+ (and (bound? #'*sip-provider*) (instance? SipProvider *sip-provider*)))
+
+(defn provider-can-be-found?
+ "Check where the *sip-provider* has been bound or global sip-provider has been set."
+ {:added "0.2.0"}
+ []
+ (or (already-bound-provider?) (not (nil? @global-sip-provider))))
+
+(defn sip-provider
+ "Get the current bound *sip-provider* or global-sip-provider."
+ {:added "0.2.0"}
+ []
+ (if (already-bound-provider?)
+ *sip-provider*
+ (or @global-sip-provider (throw (RuntimeException. "The 'cljain.sip.core/*sip-provider*' should be bound.")))))
+
+(defn sip-stack
+ "Get the SipStack object from a SipProvider object."
+ {:added "0.2.0"}
+ []
+ (.getSipStack (sip-provider)))
+
+(defn stack-name
+ "Get the SipStack name from a SipProvider object."
+ {:added "0.2.0"}
+ []
+ (.getStackName (sip-stack)))
+
+(defn- map-listening-point
+ "Get the ip, port, and transport from a ListeningPoint object, then pack them as a map."
+ {:added "0.2.0"}
+ [lp]
+ {:ip (.getIPAddress lp) :port (.getPort lp) :transport (.getTransport lp)})
+
+(defn listening-point
+ "Get the current bound listening ip, port and transport information."
+ {:added "0.2.0"}
+ ([] (map-listening-point (.getListeningPoint (sip-provider))))
+ ([transport] (map-listening-point (.getListeningPoint (sip-provider) transport))))
+
+(defn sip-provider!
+ "Create a new SipProvider with meaningful name, local ip, port, transport and other optional SipStack properties.
+ Rember, the name must be unique to make a distinction between other provider.
+ To set standard SipStack properties, use the property's lowcase short name as keyword.
+ If want to set the nist define property, let property keys lead with 'nist'.
+
+ (sip-provider! \"cool-phone\" \"192.168.1.2\" 5060 \"UDP\" :outbound-proxy \"192.168.1.128\")
+
+ More SipStack properties document can be found here:
+ http://hudson.jboss.org/hudson/job/jain-sip/lastSuccessfulBuild/artifact/javadoc/index.html
+ and
+ http://hudson.jboss.org/hudson/job/jain-sip/lastSuccessfulBuild/artifact/javadoc/index.html"
+ {:added "0.2.0"}
+ [name ip port transport & properties]
+ {:pre [(or (nil? (:outbound-proxy properties))
+ (re-find #"^\d+\.\d+\.\d+\.\d+(:\d+)?(/(tcp|TCP|udp|UDP))?$" (:outbound-proxy properties)))]}
+ (let [more-props (apply array-map properties)
+ props (Properties.)]
+ (.setProperty props "javax.sip.STACK_NAME" name)
+ (doseq [prop more-props]
+ (let [prop-name (upper-case (.replace (clojure.core/name (first prop)) \- \_))
+ prop-name (if (.startsWith prop-name "nist")
+ (str "gov.nist.javax.sip." prop-name)
+ (str "javax.sip." prop-name))]
+ (.setProperty props prop-name (second prop))))
+ (let [sip-stack (.createSipStack sip-factory props)
+ listening-point (.createListeningPoint sip-stack ip port transport)]
+ (.createSipProvider sip-stack listening-point))))
+
+(defn- trans-from-event
+ "Get ServerTransaction or ClientTransaction from an event object."
+ {:added "0.2.0"}
+ [event]
+ (if (.isServerTransaction event)
+ (.getServerTransaction event)
+ (.getClientTransaction event)))
+
+(defmacro trace-call
+ "Log the exception from event callback."
+ {:added "0.2.0"
+ :private true}
+ [callback event & arg]
+ (let [callback-name (str "process" (reduce str (map capitalize (split (str callback) #"-"))))]
+ `(try
+ (log/trace ~callback-name "has been invoked." \newline "event:" (bean ~event))
+ (and ~callback (~callback ~@arg))
+ (catch Exception e#
+ (log/error "Exception occurs when calling the event callback" ~callback-name ":" e#)))))
+
+(defn set-listener!
+ "Set several event listening function to current bound provider.
+ Because JAIN-SIP just allow set listener once, if call 'set-listener' more then one times,
+ an exception will be thrown."
+ {:added "0.2.0"}
+ [{:keys [request response timeout io-exception transaction-terminated dialog-terminated]}]
+ {:pre [(or (nil? request) (fn? request))
+ (or (nil? response) (fn? response))
+ (or (nil? timeout) (fn? timeout))
+ (or (nil? io-exception) (fn? io-exception))
+ (or (nil? transaction-terminated) (fn? transaction-terminated))
+ (or (nil? dialog-terminated) (fn? dialog-terminated))]}
+ (.addSipListener (sip-provider)
+ (reify SipListener
+ (processRequest [this event]
+ (trace-call request event (.getRequest event) (.getServerTransaction event) (.getDialog event)))
+ (processResponse [this event]
+ (trace-call response event (.getResponse event) (.getClientTransaction event) (.getDialog event)))
+ (processIOException [this event]
+ (trace-call io-exception event (.getHost event) (.getPort event) (.getTransport event)))
+ (processTimeout [this event]
+ (trace-call timeout event (trans-from-event event) (.. event (getTimeout) (getValue))))
+ (processTransactionTerminated [this event]
+ (trace-call transaction-terminated event (trans-from-event event)))
+ (processDialogTerminated [this event]
+ (trace-call dialog-terminated event (.getDialog event))))))
+
+(defn start!
+ "Start to run the stack which bound with current bound provider."
+ {:added "0.2.0"}
+ []
+ (.start (sip-stack)))
+
+(defn stop-and-release!
+ "Stop the stack wich bound with current bound provider. And release all resource associated the stack.
+ Becareful, after called 'stop!' function, all other function include 'start!' will be invalid.
+ A new provider need be generated for later call."
+ {:added "0.2.0"}
+ []
+ (.stop (sip-stack)))
+
+(defn gen-call-id-header
+ "Generate a new Call-ID header use current bound provider."
+ {:added "0.2.0"}
+ []
+ (.getNewCallId (sip-provider)))
+
+(defn send-request!
+ "Send out of dialog SipRequest use current bound provider."
+ {:added "0.2.0"}
+ [request]
+ (.sendRequest (sip-provider) request))
+
+(defn new-server-transaction!
+ "An application has the responsibility of deciding to respond to a Request
+ that does not match an existing server transaction."
+ {:added "0.2.0"}
+ [request]
+ (.getNewServerTransaction (sip-provider) request))
+
+(defn new-client-transcation!
+ "Before an application can send a new request it must first request
+ a new client transaction to handle that Request."
+ {:added "0.2.0"}
+ [request]
+ (.getNewClientTransaction (sip-provider) request))
+
+(defn transaction?
+ "Check the obj is an instance of javax.sip.Transaction.
+ Both ClientTransaction and ServerTransaction are pass."
+ {:added "0.4.0"}
+ [object]
+ (instance? Transaction object))
+
+(defn new-dialog!
+ "Create a dialog for the given transaction."
+ {:added "0.2.0"}
+ [transaction]
+ (.getNewDialog (sip-provider) transaction))
+
+(defn dialog?
+ "Check the obj is an instance of javax.sip.Dialog"
+ {:added "0.4.0"}
+ [object]
+ (instance? Dialog object))
View
106 test/cljain/test/dum.clj
@@ -1,44 +1,62 @@
-(ns ^{:doc "place doc string here"
- :author "ruiyun"}
- cljain.test.dum
- (:require [cljain.sip.core :as core]
- [cljain.sip.header :as header]
- [cljain.sip.address :as addr]
- [cljain.sip.dialog :as dlg]
- [cljain.sip.message :as msg]
- [cljain.sip.transaction :as trans]
- [cljain.dum :as dum]))
-
-(org.apache.log4j.PropertyConfigurator/configure "log4j.properties")
-;(core/global-bind-sip-provider! (core/sip-provider! "test" "127.0.0.1" 5060 "udp"))
-;(dum/initialize! :user "reuiyun" :domain "notbook" :display-name "Ruiyun Wen")
-;(core/start!)
-
-;(def bob (cljain.sip.address/address (cljain.sip.address/sip-uri "127.0.0.1" :port 5070 :user "bob") "Bob henry"))
-;(cljain.dum/send-request! :INVITE :to bob :pack {:type :application :sub-type :sdp :content "Welcome message"}
-; :on-success (fn [a b c] (prn "success!" (dlg/send-ack! b (dlg/ack b 1))))
-; :on-failure (fn [a b c] (prn "faliure!" a))
-; :on-timeout (fn [a] (prn "tiemout" a)))
-
-;(dum/register-to (addr/address (addr/sip-uri "127.0.0.1" :port 5070)) 40
-; :on-success #(prn "register-to has success!")
-; :on-failure #(prn "register-to has failed!")
-; :on-refreshed #(prn "register refresh has success!")
-; :on-refresh-failed #(prn "register refresh has failed!"))
-
-(use 'cljain.dum)
-(require '[cljain.sip.core :as sip]
- '[cljain.sip.address :as addr])
-
-(def-request-handler :MESSAGE [request transaction dialog]
- (println "Received: " (.getContent request))
- (send-response! 200 :in transaction :pack "I receive your message."))
-
-(sip/global-bind-sip-provider! (sip/sip-provider! "my-app" "localhost" 5060 "udp"))
-(initialize! :user "bob" :domain "home" :display-name "Bob")
-(sip/start!)
-
-(send-request! :MESSAGE :to (addr/address "sip:alice@localhost") :pack "Hello, Alice."
- :on-success (fn [_ _ response] (println "Fine! response: " (.getContent response)))
- :on-failure (fn [_ _ response] (println "Oops!" (.getStatusCode response)))
- :on-timeout (fn [_] (println "Timeout, try it later.")))
+(ns ^{:doc "place doc string here"
+ :author "ruiyun"}
+ cljain.test.dum
+ (:require [cljain.sip.core :as core]
+ [cljain.sip.header :as header]
+ [cljain.sip.address :as addr]
+ [cljain.sip.dialog :as dlg]
+ [cljain.sip.message :as msg]
+ [cljain.sip.transaction :as trans]
+ [cljain.dum :as dum]))
+
+(org.apache.log4j.PropertyConfigurator/configure "log4j.properties")
+;(core/global-bind-sip-provider! (core/sip-provider! "test" "127.0.0.1" 5060 "udp"))
+;(dum/initialize! :user "reuiyun" :domain "notbook" :display-name "Ruiyun Wen")
+;(core/start!)
+
+;(def bob (cljain.sip.address/address (cljain.sip.address/sip-uri "127.0.0.1" :port 5070 :user "bob") "Bob henry"))
+;(cljain.dum/send-request! :INVITE :to bob :pack {:type :application :sub-type :sdp :content "Welcome message"}
+; :on-success (fn [a b c] (prn "success!" (dlg/send-ack! b (dlg/ack b 1))))
+; :on-failure (fn [a b c] (prn "faliure!" a))
+; :on-timeout (fn [a] (prn "tiemout" a)))
+
+;(dum/register-to (addr/address (addr/sip-uri "127.0.0.1" :port 5070)) 40
+; :on-success #(prn "register-to has success!")
+; :on-failure #(prn "register-to has failed!")
+; :on-refreshed #(prn "register refresh has success!")
+; :on-refresh-failed #(prn "register refresh has failed!"))
+
+;(use 'cljain.dum)
+;(require '[cljain.sip.core :as sip]
+; '[cljain.sip.address :as addr])
+;
+;(def-request-handler :MESSAGE [request transaction dialog]
+; (println "Received: " (.getContent request))
+; (send-response! 200 :in transaction :pack "I receive your message."))
+;
+;(sip/global-bind-sip-provider! (sip/sip-provider! "my-app" "localhost" 5060 "udp"))
+;(initialize! :user "bob" :domain "home" :display-name "Bob")
+;(sip/start!)
+;
+;(send-request! :MESSAGE :to (addr/address "sip:alice@localhost") :pack "Hello, Alice."
+; :on-success (fn [_ _ response] (println "Fine! response: " (.getContent response)))
+; :on-failure (fn [_ _ response] (println "Oops!" (.getStatusCode response)))
+; :on-timeout (fn [_] (println "Timeout, try it later.")))
+
+(use 'cljain.dum)
+(require '[cljain.sip.core :as sip]
+ '[cljain.sip.address :as addr])
+
+(defmethod handle-request :MESSAGE [request & [transaction]]
+ (println "Received: " (.getContent request))
+ (send-response! 200 :in transaction :pack "I receive the message from myself."))
+
+(global-alter-account :user "bob" :domain "localhost" :display-name "Bob")
+(sip/global-bind-sip-provider! (sip/sip-provider! "my-app" "localhost" 5060 "udp"))
+(sip/set-listener! (dum-listener))
+(sip/start!)
+
+(send-request! :MESSAGE :to (addr/address "sip:bob@localhost") :pack "Hello, Bob."
+ :on-success (fn [& {:keys [response]}] (println "Fine! response: " (.getContent response)))
+ :on-failure (fn [& {:keys [response]}] (println "Oops!" (.getStatusCode response)))
+ :on-timeout (fn [_] (println "Timeout, try it later.")))
View
20 test/cljain/test/register.clj
@@ -0,0 +1,20 @@
+(ns ^{:doc "place doc string here"
+ :author "ruiyun"}
+ cljain.test.register)
+
+(org.apache.log4j.PropertyConfigurator/configure "log4j.properties")
+
+(use 'cljain.dum)
+(require '[cljain.sip.core :as sip]
+ '[cljain.sip.address :as addr])
+
+(global-alter-account :user "bob" :domain "test" :display-name "Bob" :password "123456")
+(sip/global-bind-sip-provider! (sip/sip-provider! "my-app" "localhost" 6060 "udp" :outbound-proxy "127.0.0.1:5060"))
+(sip/set-listener! (dum-listener))
+(sip/start!)
+
+(register-to (addr/address "sip:test") 40
+ :on-success #(prn "success")
+ :on-failure #(prn "failure")
+ :on-refreshed #(prn "refreshed")
+ :on-refresh-failed #(prn "refresh failed"))
Please sign in to comment.
Something went wrong with that request. Please try again.