SOAP Faults

The single most important piece of any web service integration is error handling. At some point, your application will not receive the expected answer to its call. You can blame hardware, maintenance policies, natural disasters, missed utility bills, etc but in the end it is still up to you to ensure that your application correctly handles errors that it will encounter. However, it is the service provider's responsibility to return a usable error.

Luckily, SOAP provides a mechanism especially for this: the SOAP Fault. Faults provide a standardized format for which a server can inform a client that some error condition has occurred. Although the format is standard, the specific contents of each fault subelement is pretty much up to the server. For example in SOAP 1.1, the faultcode is supposed to extend from a few predefined values ('Client', 'Server', etc) but many services choose instead to return a custom faultcode. As a client, your application should be robust enough to handle whatever is passed its way.

Now, let's take a look at how various SOAP toolkits handle faults. All of the major toolkits treat a fault as an exception. The big difference that I noticed between the toolkits was in how AXIS handled custom fault definitions. Take the following WSDL snippet from our API:

     <schema elementFormDefault="qualified" xmlns="http://www.w3.org/2001/XMLSchema" 
      targetNamespace="http://api.bronto.com">
      <element name="fault" type="tns:error" />
      <complexType name="error">
        <sequence>
          <element name="code" type="xsd:int" minOccurs="1" maxOccurs="1" />
          <element name="message" type="xsd:string" minOccurs="1" maxOccurs="1" />
          <element name="extra" type="tns:errorExtra" minOccurs="0" maxOccurs="unbounded" />
        </sequence>
      </complexType>
    </schema>
  <message name="faultResponse">
    <part name="fault" element="tns:fault" />
  </message>
  <portType name="apiPortType">
    <operation name="login">
      <documentation>Logs in a user</documentation>
      <input message="tns:loginRequest" />
      <output message="tns:loginResponse" />
      <fault name="fault" message="tns:faultResponse" />
    </operation>
  </portType>


This WSDL basically says that the login call can return a fault whose message is a complex element of type "error". That element will be within the detail element of the fault (the faultcode, faultstring, and faultactor are reserved).

In .NET, this fault takes the following form. The final generic Exception catch is for any other unforeseen exception that could occur (eg http timeout). Notice how we have to actually parse the fault xml by hand in order to get the "error" contents from the detail element.

try
{
    loginResult login = binding.login(username, password, sitename);
}
catch(SoapException e)
{
    //we have to parse the xml for a more detailed error message
    System.Xml.XmlNode fault = e.Detail.FirstChild;
    System.Xml.XmlNode code = fault.FirstChild;
    System.Xml.XmlNode message = code.NextSibling;
    
    System.Console.WriteLine("soap exception:");
    System.Console.WriteLine(code.InnerText);
    System.Console.WriteLine(message.InnerText);
}
catch(Exception e)
{
    System.Console.WriteLine("Exception: " + e.Message);
    System.Console.WriteLine("Inner: " + e.InnerException);
    System.Console.WriteLine(e.StackTrace);
}

In PHP-SOAP, this gets translated a little better into the following. The detail element gets deserialized into a stdClass object that we can use to obtain the "error" contents.

try{
    $result = $Binding->login($username, $password, sitename);
}
catch(SoapFault $Error){
    echo'Error Fault: ' . $Error->faultcode . "::" . $Error->faultstring;
    echo'Error Detail: ' . $Error->detail->fault->code . '::' . $Error->detail->fault->message;
}


In AXIS, this get translated to a custom class Error that extends Exception. The "error" contents are immediately available as instance variables of the exception.

try{                 
      LoginResult login = binding.login(username, password, sitename);
}
catch(com.bronto.api.Error e){
    System.out.println("Soap Fault");
    System.out.println("code: " + e.getCode());
    System.out.println("message: " + e.getMessage1());
    System.exit(0);
}
catch(Exception e){
    System.out.println("other exception");
    System.out.println(e.toString());
    System.exit(0);
}

Thus in AXIS, we could provide WSDL that throws a specific exception for each type of error that could occur. However, only AXIS users would benefit from that type of error handling. Since each implementation behaves slightly different, it is generally best to keep it simple. You can never go wrong with a single fault definition that provides a unique identifier for each error (in our case, the detail->fault->code). Providing a string message about the error is also extremely useful for the human user.


Ed

Akamai

One technical problem we encountered early on as we started to grow our customer base is the issue of handling large spikes in bandwidth.  While small requests to our tracking counted for a majority of web requests the bulk of our bandwidth came from serving images off our hosting service.

For most websites, requests are distributed throughout the day and throughout the week fairly evenly.  With our traffic if one or two large customers sent out to 100,000+ contacts at the same time then we would see a large spike in the number of requests.  As contacts opened their email our total bandwidth would grow as requests came in for images from our hosting service.

To counter this problem we decided to incorporate Akamai into our hosting.  Akamai is a content delivery network or CDN provider.  What this means is they have a network of servers that act as a caching proxy between a site and visitors.  This provides a significant decrease in the number of requests that have to hit the servers directly.  It also allows for faster downloading since Akamai will chose to serve requests from the closest node in their network to the visitor.

The actual integration into Akamai required a few changes from our end.  In order for new requests to go to Akamai's caching servers instead of our hosting servers we had to update our hosting.bronto.com DNS record to a CNAME that goes to Akamai:

hosting.bronto.com.    IN    CNAME    hosting.bronto.com.edgesuite.net.

We also setup a new subdomain that served hosting directly so that we could have Akamai pull uncached images and files from there.

For sites that host static images that won't ever change this solution would be complete.  However with our application users are allowed to overwrite images and files that exist in hosting.  This means that we needed a way to let Akamai know that the file is no longer current and needs to be refreshed from the source.  Thankfully Akamai has an API that allows the ability to purge specific files from the cache servers.  So in our application whenever a user goes to overwrite or edit a file in hosting directly we issue a request to purge that content from Akamai's network and redownload it from us.

Hopefully this will help anyone looking to integrate a CDN solution into their setup.  A lot of this information of how Akamai actually gets implemented, while not difficult, wasn't widely available when we were setting this up.

Justin

Graceful Framework Degradation

This interview with Twitter developer Alex Payne further confirms my belief that we made the right decision in developing our own framework here at Bronto.

When we determined that a complete rewrite of Bronto was necessary, we evaluated the available frameworks available to us at the time and determined to roll our own. I tried many frameworks in the past and found many of them to be very nice for quick and dirty prototypes, but none seemed like they would be able to handle team development of a large application. They either were too cute (RoR) or too kludgy (Maypole, Catalyst) and all seemed to suffer from the same problem -- they all thought they were a 100% solution.

Frameworks exist mainly to make coding easier and safer for the developer. They do this by abstracting away details and presenting a standard and generic interface. For Bronto, this is exactly what we need for most cases. In other cases, speed or precision are more important. My goal when designing our framework (aka "The Platform") was to make the conveniences degrade gracefully.

For example:

// 100% convenience
$Messages = DB::find('Message', array('name' => 'apple',
                                 'status' => 'active'));
// A more precise query
$Messages = DB::find_sql('Message', "select message.* from 
               message, someothertable where message.status = 
               :status and message.name = :name and 
               message.someothertable_id = someothertable.id and 
               someothertable.column = :val1",
               array('name' => 'apple', 'status' => 'active', 
               'val1' => 'orange'));

In both cases, an array of Message objects is returned. This may seem unimpressive, however it's quite powerful in practice. Bugs in this type of code usually fall into two categories, an incorrect query or incorrect assumptions as to what was actually being returned from the query. By standardizing what is returned and how it's returned (it is always an array), we have all but eliminated the bugs caused from the second case.

Look for more about "The Platform" in upcoming posts.

Thad

PHP and Soap Headers

When I first began experimenting with PHP-SOAP, the one thing I noticed immediately was the distinct lack of documentation on php.net for SOAP. Functions and features go undocumented (eg __SetSoapHeaders) and those that are found include little relevant information beyond language syntax. The following blog post(s) cover some of the concepts that I found difficult to find any information on.

This post concerns itself with how PHP-SOAP handles SOAP Headers as a client and a server. SOAP Headers provide a standardized mechanism for web services to include function modifiers that do not correspond to directly to the function being called. The most common use of headers are for authentication purposes (eg username/password or token). The authentication data could technically be included as just another function parameter but doing so clutters the function semantics.

The complete code including WSDL, client, and server can be found here. You will need to manually change the soap:address in the WSDL to your own server.

If you look at ed.wsdl, you should be able to tell the following about the service:

- it is Document/Literal
-- the binding is defined with a style of document
-- for each operation in the binding, inputs/outputs have use defined as literal

- it defines a single function named "hello"
--accepts a complex element named "hello" that contains a single attribute "firstname"
--accepts a SOAP Header consisting of a single complex element named "helloHeader" that contains a single attribute "lastname"
--returns a complex element named helloResponse that contains a single attribute "goodbye"

As you can see, the service is fairly light-weight. In PHP, the complex elements will simply become associative arrays or stdClass objects.

The important piece to note is how the SOAP Headers are defined. A <header /> element from the soap namespace must be defined within the binding element for the operation. This informs the client that a header may be sent. The message and part of the header indicate the structure of the header. The message must point to a <message /> element. The part should point to a <part /> within that message.

The reason why the header object must be explicitly defined as a message element as opposed to simply a schema element is because the headers are processed as a separate function call. A SOAP Header should only be used for function modifiers, not arguments. As the header could be defined for multiple functions, it makes sense that the header processing be done without regard to the function being called.

If you look at ed_server.php, you will notice the class Ed{} has two functions defined: hello and helloHeader. When a client includes the header with a call to hello, helloHeader() will be the first call after service(). The value from the header is stored as a private class variable and will be used later in the local hello() call. The result is an object with a single element "goodbye" consisting of the firstname and possibly lastname that were submitted.

Lastly, PHP-SOAP is somewhat immature in terms of SOAP Headers when used as a client. In other toolkits such as .NET and AXIS, headers that are defined as complex elements are automatically (de)serialized to/from the appropriate custom class. In PHP-SOAP, there is only one generic header class that must be explicitly told what definition to use. You do this by specifying the namespace, object name, and values in the SoapHeader constructor. For really complex headers (eg one element is an array of other complex elements), the values simply become multi-dimensional arrays.

Hopefully, this post provides a bit of insight into how you can incorporate headers into your web service with PHP-SOAP. My next post will discuss SOAP faults and how other toolkits interpret your faults.


Ed

E-Mail Rate Limiting

My name is Justin Sanders and I have been working here at Bronto a little over 2 years now.  Along with my general development responsibilities a lot of what I do here is make sure our systems are running smoothly.  This includes the network at our datacenter, the web/mail/db servers and the actual software running on these machines.

On our all mail servers we run postfix for the mail daemon.  Over the years we've learned that to have the best delivery rates it's important to dive in and do custom configuration to the system instead the default install.  Some of these setting include upping the concurrency rate per server, decreasing the deferred queue time, or setting lower timeout values for certain ISPs.

Our configuration is setup to send mail as fast as we can to each ISP.  Each mail server has had both its default_process_limit and default_destination_concurrency_limit raised above original values.  While this is great for sending email to servers that are setup to receive it this fast it can lead to overloading on smaller mail setups or greylisting with certain ISPs.

So normally in postfix email will always go out of the smtp transport and use the default values specified in the main.cf file.  It is possible however to override these settings on a per domain basis by sending email to those domains through a custom delivery transport.

For example if we wanted to modify the settings for email going to aol.com we would need to create a custom transport for it.  This is done by editing the /etc/postfix/master.cf file:

aol     unix    -       -       n       -       -      smtp

There are settings here that can be overwritten at the transport level.  For example if we wanted to shorten the connect timeout value to five seconds we would want to add `-o smtp_connect_timeout=5` to this line and smtp_helo_timeout could be set the same way.

In order to redirect aol.com email an addition will need to be made to the /etc/postfix/transport file:

aol.com aol

Once the transport file is edited we'll need to run `postmap /etc/postfix/transport` to generate the correct /etc/postfix/transport.db file.  Now email that goes to the aol.com domain will go through our new aol transport as defined above.

So far all we've done is specify a new transport for our aol mail, we haven't actually added any rate limiting settings to this new transport.  In order to do this we'll need to add some settings to the main.cf file.  Normally you'll notice smtp_ settings that apply to the smtp transport.  These settings can also be applied to any new transports that are created in master.cf.

One way to rate limit email is to make sure that at any given time you only have X number of connections to a given destinations mail servers.  In our example we only want to allow 4 concurrent connections to aol.com mail servers.  Using our new aol transport we can edit this setting in the main.cf:

aol_destination_concurrency_limit = 4

In addition to the concurrency limit it is also possible to limit the total number of recipients that can be included in one mailing.  Any recipients over this limit will be broken out into separate messages.  So in our example if we wanted to limit individual mailings to 10 recipients on aol.com servers we would add to the main.cf file:

aol_destination_recipient_limit = 10

Other rate limiting options are possible for example if we wanted to limit the total number of processes our postfix install will allocate for sending to aol.com at any point we could have specified this in the transport.  So for example instead of the master.cf line we used above to specify the aol delivery transport we could have used this line:

aol     unix    -       -       n       -       10      smtp

You'll notice the addition of the 10 number.  This setting overrides the default options to tell postfix that the total number of postfix processes used for the aol transport should never go over 10 processes.

So these are just a few of the ways you can rate limit email based on the destination address.  There are many other postfix changes that can be made to ensure better deliverability but I will save those for a later blog post.

Justin

NuSOAP -> PHP-SOAP

Being my first post, I thought I would start things off with a short introduction.  My stay at Bronto has spanned 3 March Madness brackets (all losses) and has included everything from database schema design and feature implementation to job recruiting.  In the beginning, it seemed as if anything with wires was under my direction.  Nowadays, the scope of my activities are a bit more focused though I do still enjoy some variety.

As the lead developer for most things SOAP at Bronto, I have been able to amass a good bit of experience with PHP toolkits for SOAP from both the client and server perspectives.   The following post is primarily concerned with some of the syntactical differences you will notice when upgrading from NuSOAP to PHP-SOAP as a client.

Binding Instantiation

A couple of things to note:

  • NuSOAP and PHP-SOAP have a classname conflict in PHP5
    • (to use NuSOAP in PHP5 just do a search and replace for soapclient in nusoap.php.  I change the classname to nusoapclient)
  • debugging in PHP-SOAP is not nearly as exhaustive as NuSOAP and not automatic (must specify 'trace' option)
  • specifying SOAP_SINGLE_ELEMENT_ARRAYS means that single element arrays will now be deserialized as an array of one element rather than simply the element

 

//NuSOAP
$Binding = new soapclient($wsdl, TRUE);
$Binding = $Binding->getProxy();
 
//PHP-SOAP
  $Binding = new SoapClient($wsdl, array('trace' => 1, 'features' => SOAP_SINGLE_ELEMENT_ARRAYS));

Dynamic Endpoint

 

//NuSOAP
$Binding->setEndpoint($url);

//PHP-SOAP
$Binding->__setLocation($url);

Errors

Note, I use the login() call as an example web call.

 

//NuSOAP
$result = $Binding->login();
if ($Binding->fault) {
    //error is a SOAP Fault
    $error = $result;
}   
else {
    //error is some other error
    $error = $Binding->getError();
}

if(!$error){
    //success
}

//PHP-SOAP
try{
    $result = $Binding->login();

    //success
}
catch(SoapFault $Error){
    //handle the error
}

Debugging

 

//NuSOAP
$debug = $Binding->debug_str;
$request_xml = $Binding->request;
$response_xml = $Binding->response;

//PHP-SOAP
$request_xml = $Binding->__getLastRequestHeaders() . $Binding->__getLastRequest();
$response_xml = $Binding->__getLastResponseHeaders() . $Binding->__getLastResponse();

Returns

NuSOAP deserializes WSDL complexType as an associative array while PHP-SOAP deserializes complexType to an object of stdClass.  In PHP-SOAP, you can also specify a 'classmap' instantiation option will allow you to have complexTypes deserialized to custom classes though you have to manually generate the custom classes (other non-PHP SOAP implementations automatically create the classes).

Headers

Header support in PHP-SOAP is much better than NuSOAP.  In NuSOAP, you had to manually generate the XML.

 

//Assume a header of complexType "session" in namespace "http://namespace" 
containing a single element "id" (Document/Literal)
//NuSOAP
$session_header = "<session xmlns=\"http://namespace\"><id>blah</id></session>";
$Binding->setHeaders($session_header);

//PHP-SOAP
$SessionHeader = new SoapHeader('http://namespace', 'session', array('id' => 'blah'));
$Binding->__setSoapHeaders(array($SessionHeader));

Ed

Bronto is Hiring: System Administrator

2006 was a big year for us.  We doubled our infrastructure capacity and plan to do that again in 2007.  We need someone to own this.  If you're up for a challenge, some interesting work, and a chance to work with a lot of interesting hardware and software, send us your resume.

System Administrator

Bronto is seeking a System Administrator to maintain server and networking hardware and software, and design, document and implement security and backup best practices and policies.  Bronto is a fast growing company with plans to double our capacity in 2007. 

Requirements:

  • At least 4 years of professional experience managing Linux/Unix environments
  • Experience administrating MTA's such as sendmail or postfix
  • Experience in compiling, installing and troubleshooting open source applications, including Apache, PHP, and MySQL.
  • Experience with Nagios or BigBrother, Cacti, Cricket, Munin or other RRD-based reporting tool
  • Familiarity with shell script/Perl/Python or similar languages for automating system administration tasks
  • Experience with MySQL or PostgreSQL preferred
  • Experience with Cisco IOS preferred
  • BS degree
  • U.S. Resident

Apply Online

Thad

Bronto is Hiring: User Interface Engineer

Want to join our team and help us improve the user experience?  Do you consider yourself to be multilingual and (X)HTML/CSS/Javascript your first language?  If so, send us a resume and a portfolio of your work!

User Interface Engineer

Bronto Software is seeking a User Interface Engineer to design and develop its web applications.  As a member of the engineering team, you will drive the interaction design process from initial concept through engineering implementation.  You will work closely with product management and engineers to develop interaction models, wireframes, information architectures, and the user interface.

Requirements:

  • At least 2 years of professional experience designing and developing web applications
  • Extensive experience with HTML, CSS, and Javascript (AJAX experience a plus)
  • Experience with OOP and SQL (PHP, Ruby or Perl a plus)
  • Experience with MVC frameworks
  • Experience developing in a Unix/Linux environment
  • Degree in Human Factors, HCI, computer science, or related area
  • Must provide a portfolio with resume
  • U.S. Resident

Apply Online

Thad

With a little help from our YUI friends

The Yahoo User Interface (YUI) Library is a set of utilities and controls, written in JavaScript, for building interactive web applications. The YUI Library is used throughout our application for a variety of things, the most visible being the expandable/collapsible treeview components on the messages and hosting tab. A big reason for using the YUI Library's controls is that most of the the hard work of making sure that these controls work across all browsers has already been done, specifically, through Yahoo's notion of "graded browser support."

Anytime you have CSS and JavaScript-laden interactive web controls, browser compatibility becomes an issue. All of the web browsers and browser versions out there have quirky differences in the way their CSS and JavaScript implementations work.  This can lead to functionality and display issues depending on what browser you're using.

The YUI Library attempts to solve this problem by introducing the concept of graded browser support. This system does not ensure that everything will look and behave exactly the same in Netscape 4.0 as it does it IE7.  However, it does mean that you will get the maximum functionality and visual style that your browser can provide. The key to this is building for the lowest common denominator and then enhancing the user experience for more modern, capable browsers.

Browser support is divided into three classes or "grades" of support in the YUI Library. C-grade support provides core functionality for old, antiquated browsers. Because of the limitations of C-grade browsers you may not get all the bells and whistles that you would on modern browsers but you will get core content and functionality. A-grade is support for modern browsers. This is the highest level of support and should provide the richest user-experience. Finally, X-grade browsers are unidentified clients that are assumed to be capable modern browsers. No QA testing is done against X-grade browsers.

For Bronto, graded browser support ensures that our application maintains maximum compatibility across all browsers. We don't have to reinvent the wheel in creating these controls and we don't have to worry about making sure they work in browser X or browser Y.

Matt

Welcome to Bronto's Tech Blog

Welcome to Bronto Software's technical blog.  If you want the official scoop, try our official blog or our website.  If you want to hear about how we build and operate Bronto, you're at the right place.

Bronto Software is an Email Service Provider.  We build and run web based email marketing software for marketing departments, agencies, online retailers, and other organizations across North America and Europe.  Joe wrote early versions of the application.  He later brought in interns and part time developers to help out.  As Bronto grew, full time developers were hired.  We now have a team of engineers to develop the product and maintain the infrastructure.

We hope to use the blog to tell the story of how Bronto got where it is today and what we are doing to move  forward.  Enjoy!

Thad