The product I major in at work offers a couple of SOAP-based web services. I figured I could make use of one of these, in order to make common changes to the administration setup after a new install.
I based my first attempt on some Clojure code I had already written for accessing a non-SOAP web service:
(defn send-req [msg] (try (let [url (URL. @saws-url) connection (doto (.openConnection url) (.setConnectTimeout 10000) (.setDoOutput true) (.setDoInput true) (.setRequestProperty "Content-Length" (str (count msg))) (.setRequestProperty "Content-Type" "text/xml; charset=utf-8") (.setRequestMethod "POST")) writer (OutputStreamWriter. (.getOutputStream connection))] (when @*trace* (prn msg)) (doto writer (.write msg) (.flush) (.close)) ;; must open input stream AFTER posting message (let [reader (BufferedReader. (InputStreamReader. (.getInputStream connection))) resp-code (.getResponseCode connection)] (if (= resp-code 200) (let [str-resp (slurp reader) resp (clojure.xml/parse (ByteArrayInputStream. (.getBytes str-resp)))] (.close reader) (when @*trace* (prn resp)) resp) {:tag :ErrorCode, :attrs nil, :content [resp-code]}))) (catch Exception e {:tag :CaughtException :attrs nil :content [(.toString e)]})))
The first problem was an error message when opening the connection to the service:
{:tag :CaughtException, :attrs nil, :content ["javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"]}
The host certificate has to be installed in the local certificate
store. This was achieved in my case by using the
InstallCert.java program. The default certificate location is
in the jre/lib/security/cacerts
file. This might be
directly under the jre directory (name varies depending on version)
or, if you are using the jdk, under the jre directory within the
jdk.
Once past this, the next hurdle to jump was the basic authentication required by our web service. This is solved by, first, creating the authentication token, which is the username and password (as a string "username:password") encoded in BASE64. Conveniently, there is a Java class for just such an encoding, but it is not documented with the standard library. Here's the Clojure code:
(defn set-login "Construct auth credentials for username:password." [s] (reset! auth-string (str "Basic " (.replace (.encodeBuffer (sun.misc.BASE64Encoder.) (.getBytes s)) (System/getProperty "line.separator") ""))))
The encoding appends a line terminator, which has to be removed, hence the (.replace) call.
The encoded credential is then passed as a property on the URL connection:
(doto connection (.setRequestProperty "Authorization" @auth-string))
Then, it's just a matter of getting the SOAP xml right. I did try
various tools to import from the WDSL (wsimport
and
Axis2
) but they didn't work and just added to the cognitive
burden of using SOAP. I ended up writing the SOAP messages by hand.