Search code examples
scalasoapfinagle

Using Finagle for simple Scala SOAP client


I'm struggling to use Twitter's Finagle library to implement an HTTP request to a SOAP server.

The code below executes the first test successfully (using java.net.URL), but I'm having a hard time with the second test (using Finagle client). What am I doing wrong?

Also, I keep being dragged into and imperative style of writing. If you could help me make the Finagle bit more 'scala' like, I will be extremely grateful.

Here goes:

import java.net.InetSocketAddress 
import scala.xml.{Elem, XML} 
import org.jboss.netty.buffer.ChannelBuffers 
import org.jboss.netty.util.CharsetUtil.UTF_8 
import com.twitter.finagle.Service; 
import com.twitter.finagle.builder.ClientBuilder; 
import com.twitter.finagle.http.Http; 
import org.jboss.netty.handler.codec.http._ 

class SoapClient { 
  private def error(msg: String) = { 
    println("SoapClient error: " + msg) 
  } 

  def wrap(xml: Elem): String = { 
    val buf = new StringBuilder 
    buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone= 
\"no\"?>\n") 
    buf.append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http:// 
schemas.xmlsoap.org/soap/envelope/\">\n") 
    buf.append("<SOAP-ENV:Body>\n") 
    buf.append(xml.toString) 
    buf.append("\n</SOAP-ENV:Body>\n") 
    buf.append("</SOAP-ENV:Envelope>\n") 
    buf.toString 
  } 

  def sendWithJavaNetURL(host: String, req: Elem): Option[Elem] = { 
    val url = new java.net.URL(host) 
    val outs = wrap(req).getBytes 
    val conn = 
url.openConnection.asInstanceOf[java.net.HttpURLConnection] 
    try { 
      conn.setRequestMethod("POST") 
      conn.setDoOutput(true) 
      conn.setRequestProperty("Content-Length", outs.length.toString) 
      conn.setRequestProperty("Content-Type", "text/xml") 
      conn.getOutputStream.write(outs) 
      conn.getOutputStream.close 
      Some(XML.load(conn.getInputStream)) 
    } 
    catch { 
      case e: Exception => error("post: " + e) 
      error("post:" + 
scala.io.Source.fromInputStream(conn.getErrorStream).mkString) 
      None 
    } 
  } 

  def sendWithFinagle(host: String, path: String, req: Elem) = { 
    val clientService: Service[HttpRequest, HttpResponse] = 
ClientBuilder() 
      .codec(Http()) 
      .hosts(new InetSocketAddress(host, 80)) 
      .hostConnectionLimit(1) 
      .build() 
    val request: HttpRequest = new 
DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/") 
    val soapPacket: String = wrap(req) 
    request.setContent(ChannelBuffers.copiedBuffer(soapPacket, UTF_8)) 
    request.setHeader("Content-Lenght", soapPacket.length()) 
    request.setHeader("Content-Type", "text/xml") 
    request.setUri("path") 
    val client = clientService(request) 
    val response = client.get() 
    println(response) 
  } 
} 

object SoapTest { 

  def testWithJavaNetURL { 
    val host = "https://apitest.authorize.net/soap/v1/Service.asmx" 
    val req = <IsAlive xmlns="https://api.authorize.net/soap/v1/"/> 
    val cli = new SoapClient 
    println("##### Test with Java Net URL: request:\n" + 
cli.wrap(req)) 
    val resp = cli.sendWithJavaNetURL(host, req) 
    if (resp.isDefined) { 
      println("##### response:\n" + resp.get.toString) 
    } 
  } 

  def testWithFinagle { 
    val host = "apitest.authorize.net" 
    val path = "/soap/v1/Service.asmx" 
    val req = <IsAlive xmlns="https://api.authorize.net/soap/v1/"/> 
    val cli = new SoapClient 
    println("##### Test with Finagle: request:\n" + cli.wrap(req)) 
    cli.sendWithFinagle(host, path, req) 
  } 

  def main(args: Array[String]) { 
    testWithJavaNetURL 
    testWithFinagle 
  } 

Solution

  • Jean-Phillippe, from the Finagles forum, kindly supplied an answer on github

    Here is the full working and nicely updated code:

    import java.net.InetSocketAddress
    import scala.xml.{Elem, XML}
    import org.jboss.netty.buffer.ChannelBuffers
    import org.jboss.netty.util.CharsetUtil.UTF_8
    import com.twitter.finagle.Service;
    import com.twitter.finagle.builder.ClientBuilder;
    import com.twitter.finagle.http.{Http, RequestBuilder};
    import org.jboss.netty.handler.codec.http._
    import org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer
    import java.net.URL
    class SoapClient {
      private def error(msg: String) = {
        println("SoapClient error: " + msg)
      }
      def wrap(xml: Elem): String = {
        val wrapper = <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
                    <SOAP-ENV:Body>
                      {xml}
                    </SOAP-ENV:Body>
                  </SOAP-ENV:Envelope>
        wrapper.toString
      }
      def sendWithJavaNetURL(host: String, req: Elem): Option[Elem] = {
        val url = new java.net.URL(host)
        val outs = wrap(req).getBytes
        val conn =
    url.openConnection.asInstanceOf[java.net.HttpURLConnection]
        try {
          conn.setRequestMethod("POST")
          conn.setDoOutput(true)
          conn.setRequestProperty("Content-Length", outs.length.toString)
          conn.setRequestProperty("Content-Type", "text/xml")
          conn.getOutputStream.write(outs)
          conn.getOutputStream.close
          Some(XML.load(conn.getInputStream))
        }
        catch {
          case e: Exception => error("post: " + e)
          error("post:" +
    scala.io.Source.fromInputStream(conn.getErrorStream).mkString)
          None
        }
      }
      def sendWithFinagle(host: String, path: String, req: Elem) = {
        val clientService: Service[HttpRequest, HttpResponse] =
    ClientBuilder()
          .codec(Http())
          .hosts(new InetSocketAddress(host, 443))
          .tls(host)
          .hostConnectionLimit(1)
          .build()
        val payload = wrap(req).getBytes("UTF-8")
        val request: HttpRequest = RequestBuilder().url(new URL("https", host, 443, path))
                                                    .setHeader("Content-Type", "text/xml")
                                                    .setHeader("Content-Length", payload.length.toString)
                                                    .buildPost(wrappedBuffer(payload))
    
        val client = clientService(request)
    
        for(response <- client) {
          println(response)
          println(response.getContent.toString("UTF-8"))
        }
        // val response = client.get()
      }
    }
    
    object SoapTest {
      def testWithJavaNetURL {
        val host = "https://apitest.authorize.net/soap/v1/Service.asmx"
        val req = <IsAlive xmlns="https://api.authorize.net/soap/v1/"/>
        val cli = new SoapClient
        println("##### Test with Java Net URL: request:\n" +
    cli.wrap(req))
        val resp = cli.sendWithJavaNetURL(host, req)
        if (resp.isDefined) {
          println("##### response:\n" + resp.get.toString)
        }
      }
      def testWithFinagle {
        val host = "apitest.authorize.net"
        val path = "/soap/v1/Service.asmx"
        val req = <IsAlive xmlns="https://api.authorize.net/soap/v1/"/>
        val cli = new SoapClient
        println("##### Test with Finagle: request:\n" + cli.wrap(req))
        cli.sendWithFinagle(host, path, req)
      }
      def main(args: Array[String]) {
        testWithJavaNetURL
        testWithFinagle
      }
    }
    
    SoapTest.main(Array[String]())