cancel
Showing results for 
Search instead for 
Did you mean: 

Pulse Secure vADC

Sort by:
The following code uses Stingray's RESTful API to delete a pool.  The code is written in TrafficScript.  This rule deletes the "tstest" pool created by the stmrest_addpool example.  To delete a resource you do a HTTP DELETE on the URI for the resource.  If the delete is successful a 204 HTTP status code will be returned.  Subroutines in stmrestclient are used to do the actual RESTful API calls.  stmrestclient is attached to the article Tech Tip: Using the RESTful Control API with TrafficScript - Overview.   stmrest_deletepool ################################################################################ # stmrest_deletepool # # This rule deletes the pool "tspool". # # To run this rule add it as a request rule to an HTTP Virtual Server and in a # browser enter the path /rest/deletepool. # # It uses the subroutines in stmrestclient ################################################################################ import stmrestclient; if (http.getPath() != "/rest/deletepool") break; $pool = "tspool"; $resource = "pools/" . string.escape($pool); $accept = "json"; $html = "<br><b>Delete Pool " . $pool . "</b><br><br>"; # Check to make sure that the Pool exists $response = stmrestclient.stmRestGet($resource, $accept); if ($response["rc"] == 1) { $response = stmrestclient.stmRestDelete($resource); if ($response["rc"] == 1) { $html = $html . "Pool " . $pool . " deleted"; } else { $html = $html . "There was an error deleting pool " . $pool . ": " . $response['info']; } } else { if ($response['status'] == 404) { $html = $html . "Pool " . $pool . " not found"; } else { $html = $html . "There was an error getting the configuration for pool " . $pool . ": " . $response['info']; } } http.sendResponse("200 OK", "text/html", $html, ""); Running the example This rule should be added as a request rule to a Virtual Server and run with the URL: http://<hostname>/rest/deletepool Pool tstest deleted   Read More REST API Guide in the Product Documentation Tech Tip: Using the RESTful Control API with TrafficScript - Overview Feature Brief: Traffic Manager's RESTful Control API Collected Tech Tips: Using the RESTful Control API  
View full article
The following code uses Stingray's RESTful API to add a pool.   The code is written in TrafficScript. This rule creates a new pool, "tstest", first doing a GET to make sure the pool doesn't already exist, and if the pool doesn't exist, the pool is created by doing a PUT with just the minimum data needed to create a pool.  In this case the rule creates a properties hash with just one node.  All other values will get default values when Stingray creates the pool.  Subroutines in stmrestclient are used to do the actual RESTful API calls.  stmrestclient is attached to the article Tech Tip: Using the RESTful Control API with TrafficScript - Overview.   stmrest_addpool   ################################################################################ # stmrest_addpool # # This rule adds the pool "tspool" with the node "192.168.168.135:80" # # To run this rule add it as a request rule to an HTTP Virtual Server and in a # browser enter the path /rest/addpool. # # It uses a set of subroutines in stmrestclient ################################################################################ import stmrestclient; if (http.getPath() != "/rest/addpool") break; $pool = "tspool"; $node = "192.168.168.135"; $resource = "pools/" . string.escape($pool); $accept = "json"; $html = "<br><b>Add Pool " . $pool . "</b><br><br>"; # Check to make sure that the Pool doesn't already exist $response = stmrestclient.stmRestGet($resource, $accept); if ($response["rc"] == 1) { $html = $html . "Pool " . $pool . " already exists"; } else { $poolConfig =["properties" => ["basic" => ["nodes" => [$node . ":80"]]]]; $response = stmrestclient.stmRestPut($resource, $accept, $poolConfig); if ($response["rc"] == 1) { $html = $html . "Pool " . $pool . " created"; } else { $html = $html . "There was an error creating pool " . $pool . ": " . $response['info']; } } http.sendResponse("200 OK", "text/html", $html, "");   Running the example   This rule should be added as a request rule to a Virtual Server and run with the URL: http://<hostname>/rest/addpool Pool tstest added   Notes The only difference between doing a PUT to change a resource and a PUT to add a resource is the HTTP status code returned.  When changing a resource, 200 is the expected status code and when adding a resource, 201 is the expected status code.   Read More REST API Guide in the Product Documentation Tech Tip: Using the RESTful Control API with TrafficScript - Overview Feature Brief: Traffic Manager's RESTful Control API Collected Tech Tips: Using the RESTful Control API  
View full article
A client-first virtual server accepts traffic, waits for the first data from the client and then makes a load balancing decision (selects a pool and node). It connects to the selected node (server) and shuttles data back and fro between the client and the server.   For more details, take a look at the document: Server First, Client First and Generic Streaming Protocols.   The SSL passthrough virtual server type is a slightly specialized version of the client-first virtual server. It forwards SSL-encrypted data without any modification, but with two key differences:   Difference 1: Session Persistence   You can use an SSL Session ID persistence class with an SSL passthrough virtual server. This persistence class identifies when a new SSL session is established by a server, and pins future connections that use that session to the same server. This technique is used to improve performance. An SSL session identifies the encryption key and connection state; using persistence with the session ID allows clients to re-use previously-negotiated SSL credentials, so the compute-intensive RSA operation does not need to be repeated. Not using this technique will decrease the capacity of your server farm and increase latency.   Note: many clients (browsers) routinely renegotiate their SSL session every few minutes to reduce the opportunity to sniff large quantities of data that use the same key, so SSL session ID persistence is not appropriate to pin client sessions to the same server. Because you cannot inspect the encrypted application data (to accurately identify user sessions), you would need to use IP address persistence for this purpose.   Difference 2: SSL transparency   SSL Passthrough allows you to use a Stingray-specific hack to the SSL protocol that prepends the connection with data that identifes the client IP address and port. This is an alternative to the 'X-Cluster-Client-Ip' header that Stingray adds to plaintext HTTP connections. This capability is disabled by default, and only functions if the destination node is another Stingray Traffic Manager; check the Product Documentation (keys 'ssl_enhance' and 'ssl_trust_magic') for more details.   Processing SSL traffic   None of the ssl TrafficScript functions operate with an SSL Passthrough virtual server.If you need to inspect, persist or modify the data in an SSL connection, or you want to centralize the SSL decryption, then you should terminate and decrypt the SSL connection on Stingray Traffic Manager. This is easy to do; start with an existing SSL Passthrough service and apply the 'SSL Decrypt a Service' wizard to apply the correct configuration:
View full article
Content and Intellectual Property protection is a serious issue for any web site providing paid-for content. The article How to stop 'login abuse', using TrafficScript article describes how Stingray can be used to detect when a username and password is reused from different locations; this article showcases the power of Stingray's Java Extensions to apply a dynamically-generated, visible watermark to every image served up by your website.   The article covers how to use the Eclipse IDE or the command line to build the extension, how to apply the extension to your traffic, some optimization tricks to maximise performance, and how to debug and patch the code on-the-fly.   For more information on Java Extensions, you may like to read the article Feature Brief: Java Extensions in Stingray Traffic Manager.     Prerequisites   Before you begin, make sure that you have:   A working copy of Stingray, with the correct Java Runtime Environment (Sun JRE 1.5+) installed on the server (or just deploy the Stingray virtual appliance); A copy of the Java Platform JDK (to compile from the command line), and optionally an IDE such as Eclipse.   Configure the Stingray to load-balance traffic to a suitable website; you can use a public website like www.riverbed.com (remember to add a rule to set the host header if necessary). Check you can receive the website content through Stingray.   Step 1: Create your Java Extension   If you want to skip this section, just grab the ImageWatermark.class file attached to this article and proceed with that.   Go to the Stingray Admin interface, and locate the Catalog->Java Extensions page. On that page, locate the links to the Java Servlet API and ZXTM Java Extensions API files, and save these two Jar files in a convenient, long-term location:   Download the Java Servlet API and Stingray Java Extensions API files   If you are using the command line   Save the .jar files in the current directory, and create a new file named ImageWatermark.java .   If you are using Eclipse:   In Eclipse, create a new project:   Project Type: Java Project; Project Name: WaterMark; use the default options; Java Settings: Select the 'Libraries' tar and add the two external Jar files that you stored in the previous step:   Once you've created the project, go to the Package Explorer. Right-click on your project and create a new Class named 'ImageWatermark' with the default options.   The Java Code   Paste the following code into the ImageWatermark.java file that is created:   import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // Additional imports import java.awt.*; import java.awt.color.*; import java.awt.geom.*; import java.awt.image.*; import java.io.*; import javax.imageio.ImageIO; import com.zeus.ZXTMServlet.*; public class ImageWatermark extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { try { ZXTMHttpServletResponse zres = (ZXTMHttpServletResponse) res; String ct = zres.getHeader( "Content-Type" ); if( ct == null || ! ct.startsWith( "image/" ) ) return; InputStream is = zres.getInputStream(); BufferedImage img = ImageIO.read( is ); Graphics2D g = (Graphics2D)img.getGraphics(); int width = img.getWidth(); int height = img.getHeight(); if( width < 200 || height < 30 ) return; String[] args = (String[])req.getAttribute( "args" ); String message = ( args != null ) ? args[0] : "Hello world!"; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, (float)0.5 )); Font myFont = new Font( "Sans", Font.PLAIN, 18 ); Rectangle2D bb = myFont.getStringBounds( message, g.getFontRenderContext() ); int x = 2; int y = (int)bb.getHeight(); g.setFont( myFont ); g.setColor( Color.darkGray ); g.drawString( message, x, y ); zres.setHeader( "Content-Type", "image/png" ); ImageIO.write( img, "PNG", zres.getOutputStream() ); } catch( Exception e ) { log( req.getRequestURI() + ": " + e.toString() ); } } public void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { doGet( req, res ); } }   From the command line, you can compile this as follows:   $ javac -cp servlet.jar:zxtm-servlet.jar ImageWatermark.java   This will create an 'ImageWatermark.class' file in the current directory.   Using Eclipse, p aste this source in and hit ‘Ctrl-Shift-O’ to get the correct imports. Then save the file – this will automatically compile it; check that there were no errors in the compilation.   This should create the output file ImageWatermark.class in somewhere like HOMEDIR/workspace/ImageWatermark/bin .   Step 2: Load the extension into Stingray and watermark some images   Go to the Java Extensions catalog page and upload the Java Extension 'class' file for the WaterMark extension.   When you upload the class file, the Stingray Admin Server will automatically create a simple RuleBuilder rule that invokes the Java Extension.   Configure your virtual server to run the RuleBuilder rule on each response, then shift-reload the webpage that is delivered through Stingray to clear your cache and reload each image: Note the little "Hello world!" watermark on the top left of any images larger then 200x30 pixels.   Step 3: Optimize the way that the extension is called   The Java Extension is called by the RuleBuilder rule on every HTTP response. However, the extension only processes images; HTML, CSS and other document types are ignored.   Selectively running Java Extensions   Invoking a Java Extension carries some overhead, so it is prudent to ensure that Extensions are only invoked when they are needed. With a small change to the rule, you can ensure that this is the case.   First, convert the "ImageWatermark" RuleBuilder rule to TrafficScript by editing the rule and using the "Convert Rule" button. This will create a simple TrafficScript rule which calls the WaterMark extension:   java.run( "ImageWatermark" );   Edit the rule to add a condition that only runs the WaterMark extension when the object type is an image:   $contenttype = http.getResponseHeader( "Content-Type" ); if( string.startsWith( $contenttype, "image/" ) ) { java.run( "ImageWatermark" ); }   Passing parameters to a Java Extension   You can pass parameters from TrafficScript to a Java Extension. They are passed in as additional arguments to the ‘java.run()’ TrafficScript function:   $ip = request.getRemoteIP(); $time = sys.timeToString( sys.time() ); $message = "IP: ".$ip.", ".$time; $contenttype = http.getResponseHeader( "Content-Type" ); if( string.startsWith( $contenttype, "image/" ) ) { java.run( "ImageWatermark", $message ); }   The Java extension will read these arguments using the 'args' attribute, which returns a string array of the argument values:   String[] args = (String[])req.getAttribute( "args" ); String message = ( args != null ) ? args[0] : "Hello world!";   Use the Stingray Admin Interface to load in the new copy of the Java extension.   Now, when you shift-reload the web page (to clear the cache) the watermark text on the image will contain the message created in the TrafficScript rule, with the IP address of the remote user and the time when the image was downloaded.   Of course, you could also generate this message directly in the Java Extension, but quick code changes (such as modifying the text in the message) are easier when the code resides in TrafficScript rather than a compiled Java class.   Step 4: Live debugging and hot code patching   Finally, refer to the "Remote Debugging" section of the Java Development Guide (Product Documentation). This describes how to configure the arguments used to start the Java Virtual Machine (JVM) so that it can accept live debugging sessions from a remote debugger, and how to configure Eclipse to connect to the JVM.   You can edit and save the code in Eclipse. When you save the code, Eclipse compiles it and patches the code in the JVM on the fly. Try changing the point size of the font or the color and see the effects immediately:   Font myFont = new Font( "Serif", Font.BOLD, 12 );   and...   g.setColor( Color.red );   Live patching in Eclipse is a great way to debug, test and update code, but remember that it only patches the code in the live Java VM. When Stingray restarts, it will fall back to the version of the Java class that you originally uploaded, so once you’re finished, remember to upload the compiled class through the Admin interface so that it persists.   Read more Feature Brief: Java Extensions in Traffic Manager Writing Java Extensions - an introduction Watermarking PDF documents with Java
View full article
Content protection is a key concern for many online services, and watermarking downloaded documents with a unique ID is one way to discourage and track unauthorized sharing. This article describes how to use Stingray to uniquely watermark every PDF document served from a web site.     In this example, Stingray will run a Java Extension to process all outgoing PDF documents from the web sites it is managing. The Java Extension can watermark each download with a custom message, including details such as the IP address, time of day and authentication credentials (if available) of the client:     The extension then encrypts the PDF document to make it difficult to remove the watermark.   Quick Start   Upload the attached PdfWatermark.jar file to your Java Extensions Catalog in Stingray Traffic Manager:   Create the following 'PDFWatermark' rule and apply it as a response rule to your virtual server:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if ( http.getresponseheader( "Content-Type" ) != "application/pdf" ) break;       java.run( "PdfWatermark" ,         "x" , 10,         "y" , 20,         "textAlpha" , 30,         "textSize" , 40,         "textColor" , "0xff7f00" ,         "drawText" , "Downloaded by " .request.getRemoteIP(),         "textSize" , 26,         "drawText" , sys. gmtime . format ( "%a, %d %b %Y %T GMT" ),        "textSize" , 14,        "drawText" , http.getHostHeader() . http.getPath(),         "x" , 40,         "y" , 25,         "textAlpha" , 70,         "textColor" , "0xcccccc" ,         "textSize" , 16,         "textAngle" , 0,         "drawText" , "Copyright " .sys. time .year(),         "drawText" , "For restricted distribution"   );    Download a PDF document from your website, managed by the virtual server configured above.  Verify that the PDF document has been watermarked with the URL, client IP address, and time of download.   Troubleshooting   The Java Extension applies the watermark to PDF documents, and then encrypts them to make the watermark difficult to remove.   The Java Extension will not be able to apply a watermark to PDF documents that are already encrypted, or which are served with a mime type that does not begin ‘application/pdf’.   Customizing the extension   The behaviour of the extension is controlled by the parameters passed into the Java extension by the ‘ java.run() ’ function.   The following example applies a simple watermark:   1 2 3 4 5 6 7 8 9 10 11 if ( http.getresponseheader( "Content-Type" ) != "application/pdf" ) break;       $msg1 = http.getHostHeader() . http.getPath();  $msg2 = "Downloaded by " .http.getRemoteIP();  $msg3 = sys. gmtime . format ( "%a, %d %b %Y %T GMT" );       java.run( "PdfWatermark" ,      "drawText" , $msg1 ,      "drawText" , $msg2 ,      "drawText" , $msg3 ,  );    Advanced use of the Java Extension   This Java Extension takes a list of commands to control how and where it applies the watermark text:   Command Notes Default x As a percentage between 0 and 100; places the cursor horizontally on the page. 30 y As a percentage between 0 and 100; places the cursor vertically on the page. 30 textAngle In degrees, sets the angle of the text. 0 is horizontal (left to right); 90 is vertical (upwards). The special value "auto" sets the text angle from bottom-left to top-right in accordance with the aspect ratio of the page. “auto” textAlign Value is "L" (left), "R" (right), or "C" (center); controls the alignment of the text relative to the cursor placement. “L” textAlpha As a percentage, sets the alpha of the text when drawn with drawText."0" is completely transparent, "100" is solid (opaque). 75 textColor The color of the text when it is drawn with drawText, as hex value in a string. “0xAAAAAA” textSize In points, sets the size of the text when it is drawn with drawText. 20 drawText Draw the value (string) using the current cursor placement and text attributes; automatically moves the cursor down one line so that multiple lines of text can be rendered with successive calls to drawText.     Dependencies and Licenses   For convenience, the .jar extension contains the iText 5.4.0 library from iText software corp (http://www.itextpdf.com) and the bcprov-148 and bcmail-148 libraries from The Legion of the Bouncy Castle (http://www.bouncycastle.org), in addition to the PdfWatermark.class file.  The jar file was packaged using JarSplice (http://ninjacave.com/jarsplice).   Building the extension from source   If you'd like to build the Java Extension from source, here's the code:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 import java.awt.Color;  import java.io.IOException;  import java.io.InputStream;  import java.io.OutputStream;  import java.util.ArrayList;  import java.util.Enumeration;  import java.util.Hashtable;       import javax.servlet.ServletConfig;  import javax.servlet.ServletException;  import javax.servlet.http.HttpServlet;  import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse;       import com.itextpdf.text.BaseColor;  import com.itextpdf.text.pdf.BaseFont;  import com.itextpdf.text.pdf.PdfContentByte;  import com.itextpdf.text.pdf.PdfGState;  import com.itextpdf.text.pdf.PdfReader;  import com.itextpdf.text.pdf.PdfStamper;  import com.itextpdf.text.pdf.PdfWriter;  import com.zeus.ZXTMServlet.ZXTMHttpServletResponse;       public class PdfWatermark extends HttpServlet {       private static final long serialVersionUID = 1L;            Hashtable<String, String> defaults = new Hashtable<String, String>();            public void init(ServletConfig config) throws ServletException {          super.init(config);               // Initialize defaults.  These are 'commands' that are run before any commands          // passed in to the extension through the args list          defaults.put( "x" , "30" );          defaults.put( "y" , "30" );          defaults.put( "textAngle" , "auto" );          defaults.put( "textAlign" , "L" );          defaults.put( "textAlpha" , "75" );          defaults.put( "textSize" , "20" );          defaults.put( "textColor" , "0xAAAAAA" );               // Read any values defined in the ZXTM configuration for this class          // to override the defaults          Enumeration<String> e = defaults. keys ();          while (e.hasMoreElements()) {             String k = e.nextElement();             String v = config.getInitParameter(k);             if (v != null)                defaults.put(k, v);          }       }            public void doGet(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {          try {             ZXTMHttpServletResponse zres = (ZXTMHttpServletResponse) res;                  String ct = zres.getHeader( "Content-Type" );             if (ct == null || !ct.startsWith( "application/pdf" ))                return ;                  // process args             String[] args = (String[]) req.getAttribute( "args" );             if (args == null)                throw new Exception( "Missing argument list" );             if (args. length % 2 != 0)                throw new Exception(                      "Malformed argument list (expected even number of args)" );                  ArrayList<String[]> actions = new ArrayList<String[]>();                  Enumeration<String> e = defaults. keys ();             while (e.hasMoreElements()) {                String k = e.nextElement();                actions.add(new String[] { k, defaults.get(k) });             }             for ( int i = 0; i < args. length ; i += 2) {                actions.add(new String[] { args[i], args[i + 1] });             }                  InputStream is = zres.getInputStream();             OutputStream os = zres.getOutputStream();                  PdfReader reader = new PdfReader(is);                  int n = reader.getNumberOfPages();                  PdfStamper stamp = new PdfStamper(reader, os);             stamp.setEncryption(                   PdfWriter.STANDARD_ENCRYPTION_128 | PdfWriter.DO_NOT_ENCRYPT_METADATA,                   null, null,                   PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY                      | PdfWriter.ALLOW_FILL_IN | PdfWriter.ALLOW_SCREENREADERS                      | PdfWriter.ALLOW_DEGRADED_PRINTING);                  for ( int i = 1; i <= n; i++) {                PdfContentByte pageContent = stamp.getOverContent(i);                com.itextpdf.text.Rectangle pageSize = reader                      .getPageSizeWithRotation(i);                     watermarkPage(pageContent, actions, pageSize.getWidth(),                      pageSize.getHeight());             }                  stamp. close ();               } catch (Exception e) {             log (req.getRequestURI() + ": " + e.toString());             e.printStackTrace();          }       }            public void doPost(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {          doGet(req, res);       }            private void watermarkPage(PdfContentByte pageContent,             ArrayList<String[]> actions, float width, float height)             throws Exception {          float x = 0;          float y = 0;          double textAngle = 0;          int textAlign = PdfContentByte.ALIGN_CENTER;          int fontSize = 14;                    pageContent.beginText();               for ( int i = 0; i < actions.size(); i++) {             String action = actions.get(i)[0];             String value = actions.get(i)[1];                  if (action.equals( "x" )) {                x = Float.parseFloat(value) / 100 * width;                continue ;             }                  if (action.equals( "y" )) {                y = Float.parseFloat(value) / 100 * height;                continue ;             }                  if (action.equals( "textColor" )) {                Color c = Color.decode( value );                pageContent.setColorFill(                   new BaseColor( c.getRed(), c.getGreen(), c.getBlue() ) );                     continue ;             }                  if (action.equals( "textAlpha" )) {                PdfGState gs1 = new PdfGState();                gs1.setFillOpacity(Float.parseFloat(value) / 100f);                pageContent.setGState(gs1);                continue ;             }                  if (action.equals( "textAngle" )) {                if (value.equals( "auto" )) {                   textAngle = (float) Math. atan2 (height, width);                } else {                   textAngle = Math.toRadians( Double.parseDouble(value) );                }                continue ;             }                  if (action.equals( "textAlign" )) {                if (value.equals( "L" ))                   textAlign = PdfContentByte.ALIGN_LEFT;                else if (value.equals( "R" ))                   textAlign = PdfContentByte.ALIGN_RIGHT;                else                    textAlign = PdfContentByte.ALIGN_CENTER;                continue ;             }                  if (action.equals( "textSize" )) {                fontSize = Integer.parseInt(value);                pageContent.setFontAndSize(BaseFont                      .createFont(BaseFont.HELVETICA, BaseFont.WINANSI,                            BaseFont.EMBEDDED), fontSize);                continue ;             }                  // x,y is top left/center/right of text, so that when we move the             // cursor at the end of a line, we can cater for subsequent fontSize             // changes             if (action.equals( "drawText" )) {                pageContent.showTextAligned(textAlign, value,                      (float) (x + fontSize * Math. sin (textAngle)),                      (float) (y - fontSize * Math. cos (textAngle)),                      (float) Math.toDegrees(textAngle));                     x += fontSize * 1.2 * Math. sin (textAngle);                y -= fontSize * 1.2 * Math. cos (textAngle);                continue ;             }                  throw new Exception( "Unknown command '" + action + "'" );          }               pageContent.endText();       }  }   Compile against the Stingray servlet libraries (see Writing Java Extensions - an introduction), and the most recent versions of the iText library (http://www.itextpdf.com ) and the bcprov and bcmail libraries ( http://www.bouncycastle.org ):   $ javac -cp servlet.jar:zxtm-servlet.jar:bcprov-jdk15on-148.jar:\ bcmail-jdk15on-148.jar:itextpdf-5.4.0.jar PdfWatermark.java   You can then upload the generated PdfWatermark.class file and the three iText/bcmail/bcprov jar files to the Stingray Java Catalog.   Creating a Fat Jar   Alternatively, you can package the class files and their jar dependencies as a single Fat Jar (http://ninjacave.com/jarsplice):   1. Package the PdfWatermark.class file as a jar file   $ jar cvf PdfWatermark.jar PdfWatermark.class   2. Run JarSplice   $ java -jar ~/Downloads/jarsplice-0.40.jar   4. Set the main class: 5. Hit 'CREATE FAT JAR' to generate your single fat jar: You can upload the resulting Jar file to the Stingray Java catalog, and Stingray will identify the PdfWatermark.class within.
View full article
Having described Watermarking PDF documents with Stingray and Java and Watermarking Images with Java Extensions, this article describes the much simpler task of adding a watermark to an HTML document.   Stingray's TrafficScript language is fully capable of managing text content, so there's no need to revert to a more complex Java Extension to modify a web page:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # Only process text/html responses  $ct = http.getResponseHeader( "Content-Type" );  if ( ! string.startsWith( $ct , "text/html" ) ) break;       # Calculate the watermark text  $text = "Hello, world!" ;       # A new style, named watermark, that defines how the watermark text should be displayed:  $style = '  <style type= "text/css" >  .watermark {      color: #d0d0d0;      font-size: 100pt;      -webkit-transform: rotate(-45deg);      -moz-transform: rotate(-45deg);      -o-transform: rotate(-45deg);      transform: rotate(-45deg);      position: absolute;      width: 100%;      height: 100%;      margin: 0;      z- index : 100;      top:200px;      left:25%;      opacity: .5;  }  </style>';       # A div that contains the watermark text  $div = '<div class="watermark">' . $text . '</div>' ;       # Imprint in the body of the document  $body = http.getResponseBody();  if ( string.regexmatch( $body , "^(.*)</body>(.*?)$" , "i" ) ) {      http.setResponseBody( $1 . $style . $div . "</body>" . $2 );  }  - See more at: https://splash.riverbed.com/docs/DOC-1664 #sthash.BsuXFRP2.dpuf   This rule has the following effect...: Of course, you can easily change the watermark text:   1 $watermark = "Hello, world!" ;   ... perhaps to add more debugging or instrumentation  to the page.   The CSS style for the watermark is based on this article , and other conversations on stackoverflow; you'll probably need to adapt it to get precisely the effect that you want.   This rule uses a simple technique to append text to an HTML document (see the Tracking user activity with Google Analytics article for another example). You could use it to perform other page transforms, such as the common attempt to apply copy-protection by putting a full-size transparent layer over the entire HTML document.
View full article
Stingray allows you to inspect and manipulate both incoming and outgoing traffic with a customized version of Java's Servlet API. In this article we'll delve more deeply into some of the semantics of Stingray's Java Extensions and show how to validate XML files in up- and in downloads using TrafficScript and Java Extensions.   The example that will allow us to illustrate the use of XML processing by Stingray is a website that allows users to share music play-lists. We'll first look at the XML capabilities of 'conventional' TrafficScript, and then investigate the use of Java Extensions.   A play-list sharing web site   You have spent a lot of time developing a fancy website where users can upload their personal play-lists, making them available to others who can then search for music they like and download it. Of course you went for XML as the data format, not least because it allows you to make sure uploads are valid. Therefore, you can help your users' applications by providing them with a way of validating their XML-files as they are uploaded. Also, to be on the safe side, whenever an application downloads an XML play-list it should be checked and only reach the user if it passes the validation.   XML provides the concept of schema files to describe what a valid document has to look like. One popular schema language is the W3C's XML Schema Definition (XSD), see http://www.w3.org/TR/xmlschema-0/. Given an XSD file, you can hand an XML document to a validator to find out whether it actually conforms to the data structure specified in the schema.   Coming back to our example of a play-list sharing website, you have downloaded the popular xspf (XML Shareable Playlist Format, 'spiff') schema description from http://xspf.org/validation/ . One of the tags allowed inside a track in XML files of this type is image. By specifying tags like <image> http://images.amazon.com/images/P/B000002J0B.01.MZZZZZZZ.jpg </image> a user could see the following pictures:     Validating XML with TrafficScript   How do you validate an XML file from a user against that schema? Stingray's TrafficScript provides the xml.validate() function. Here's a simple rule to check the response of a web server against a XSD:   1 2 3 4 5 6 7 8 9 10 $doc = http.getResponseBody();  $schema = resource.get( "xspf.xsd" );  $result = xml.validate.xsd( $doc , $schema );  if ( 1 == $result ) {      log .info( "Validation succeeded" );  } else if ( 0 == $result ) {      log .info( "Validation failed" );  } else {      log .info( "Validation error" );  }   Let's have a closer look at what this rule does:   First, it reads in the whole response by calling http.getResponseBody(). This function is very practical but you have to be extremely careful with it. The reason is that you do not know beforehand how big the response actually is. It might be an audio stream totaling many hundred megabytes in size. Surely you don't want Stingray to buffer all that data. Therefore, when using http.getResponseBody() you should always check the mime type and the content length of the response (see below for code that does this). Our rule then goes on to load the schema definition file with resource.get(), which must be located in ZEUSHOME/zxtm/conf/extra/ for this step to work. Finally it does the actual validation and checks the result. In this simple example, we are only logging the result, on your music-sharing web site you would have to take the appropriate action.   The last rule was a response rule that worked on the result from the back-end web server. These files are actually under your control (at least theoretically), so validation is not that urgent. Things are different if you allow uploads to your web site. Any user-provided data must be validated before you let it through to your back-ends. The following request rule does the XML validation for you:   1 2 3 4 5 6 7 8 9 10 11 12 $m = http.getMethod();  if ( 0 == string.icmp( $m , "POST" ) ) {      $clen = http.getHeader( "Content-Length" );      if ( $clen > 0 && $clen <= 1024*1024 ) {         $schema = resource.get( "xspf.xsd" );         $doc = http.getBody();         $result = xml.validate.xsd( $doc , $schema );         # handle result      } else {         # handle over-sized posts      }  }    Note how we first look at the HTTP method, then retrieve the length of the post's body and check it. That check, which is done in the line   1 if ( $clen > 0 && $clen <= 1024*1024 ) {   ...deserves a bit more comment: The variable $clen was initialized from the post's Content-Length header, so it could be the empty string at that stage. When TrafficScript converts data to integers, variables that do not actually represent numbers are converted to 0 (see the TrafficScript reference for more details). Therefore, we have to check that $clen is greater than zero and at most 1 megabyte (or whatever limit you choose to impose on the size of uploads). After checking the content length we can safely invoke getBody().   A malicious user might have faked the HTTP header to specify a length larger than his actual post. This would lead Stingray to try to read more data than the client sends, pausing on a file descriptor until the connection times out. Due to Stingray's non-blocking IO multiplexing, however, other requests would be processed normally.   Validating XML with Stingray's Java Extensions   After having explored TrafficScript's built-in XML support, let's now see how XML validation can be done using Java Extensions.   If you are at all familiar with Java Servlets, Stingray's Java Extensions should feel like home for you. The main differences are   You have a lot of Stingray's built-in functionality ready at hand via attributes. You can manipulate both the response (as in conventional Servlets) and the request (unique to Stingray's Servlet Extensions as Stingray sits between the client and the server).   There's lots more detail in the Feature Brief: Java Extensions in Traffic Manager.   The interesting thing here is that this flow actually applies twice: First when the request is sent to the server (you can invoke the Java extension from a request rule) and then again when the response is sent back to the client (allowing you to change the result from a response rule). This is very practical for your music-sharing web site as you only have to write one Servlet. However, you have to be able to tell whether you are working on the response or the request. The ZXTMHttpServletResponse object which is passed to both the doGet() and doPost() methods of the HttpServlet object has a method to find out which direction of the traffic flow you are currently in: boolean isResponseRule() . This distinction is never needed in conventional Servlet programming as in that scenario it's the Servlet's task to create the response, not to modify an existing response.   These considerations make it easy to design the Stingray Servlet for your web site:   There will be an init() method to read in the schema definition and to set up the xml.validation.Validator object. We'll have a single private validate() method to do the actual work. The doGet() method will invoke validate() on the server's response, whereas the doPost() method does the same on the body of the request   After all that theory it's high time for some real code (note that any import directives have been removed for the sake of readability as they don't add anything to our discussion - see Writing Java Extensions - an introduction ):   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class XmlValidate extends HttpServlet {      private static final long serialVersionUID = 1L;      private static Validator validator = null;      public void init( ServletConfig config ) throws ServletException {         super.init( config );         String schema_file = config.getInitParameter( "schema_file" );              if ( schema_file == null )            throw new ServletException( "No schema file specified" );              SchemaFactory factory = SchemaFactory.newInstance(            XMLConstants.W3C_XML_SCHEMA_NS_URI);              Source schemaFile = new StreamSource(new File(schema_file));         try {            Schema schema = factory.newSchema(schemaFile);            validator = schema.newValidator();         } catch( SAXException saxe ) {            throw new ServletException(saxe.getMessage());         }      }  // ... other methods below  }   The validate() function is actually very simple as all the hard work is done inside the Java library. The only thing to be careful about is to make sure that we don't allow concurrent access to the Validator object from multiple threads:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private boolean validate( InputStream in, HttpServletResponse res, String errmsg )         throws IOException      {         Source src=new StreamSource(in);         try {            synchronized( validator ) {               validator.validate(src);            }         } catch( SAXException saxe ) {            String msg = saxe.getMessage();            res.setContentType( "text/plain" );            PrintWriter out = res.getWriter();            out.println(errmsg);            out. print ( "Validation of the xml file has failed with error message: " );            out.println(msg);            return false;         }         return true;      }    Note that the only thing we have to do in case of a failure is to write to the stream that makes up the response. No matter whether this is being done in a request or a response rule, Stingray will take that as an indication that this is what should be sent back to the client. In the case of a request rule, Stingray won't even bother to hand on the request to a back-end server and instead send the result of the Java Servlet; in a response rule, the server's answer will be replaced by what the Servlet has produced.   Now we are ready for the doGet() method:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void doGet( HttpServletRequest req, HttpServletResponse res )        throws ServletException, IOException     {        try {           ZXTMHttpServletResponse zres = (ZXTMHttpServletResponse) res;           if ( !zres.isResponseRule() ) {              log ( "doGet called in request rule ... bailing out" );              return ;           }           InputStream in = zres.getInputStream();           validate(in, zres, "The file you requested was rejected." );        } catch( Exception e ) {           throw new ServletException(e.getMessage());        }     }    There's not really much work left apart from calling our validate() method with the error message to append in case of failure. As discussed previously, we make sure that we are actually working in the context of a response rule because otherwise the response would be empty. Exactly the opposite has to be done when processing a post:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void doPost( HttpServletRequest req, HttpServletResponse res )       throws ServletException, IOException    {       try {          ZXTMHttpServletRequest zreq = (ZXTMHttpServletRequest) req;          ZXTMHttpServletResponse zres = (ZXTMHttpServletResponse) res;            if ( zres.isResponseRule() ) {             log ( "doPost called in response rule ... bailing out" );             return ;          }            InputStream in = zreq.getInputStream();          if ( validate(in, zres, "Your upload was unsuccessful" ) ) {             // just let the post through to the backends          }       } catch(Exception e) {          throw new ServletException(e.getMessage());       }    }    The only thing missing are the rules to invoke the Servlet, so here they are (assuming that the Servlet has been loaded up via the 'Java' tab of the 'Catalogs' section in Stingray's UI as a file called XmlValidate.class ). First the request rule:   1 2 3 4 $m = http.getMethod();  if ( 0 == string.icmp( $m , "POST" ) ) {      java.run( "XmlValidate" );  }    and the response rule is almost the same:   1 2 3 4 $m = http.getMethod();  if ( 0 == string.icmp( $m , "GET" ) ) {      java.run( "XmlValidate" );  }    It's your choice: TrafficScript or Java Extensions   Which is better?   So now you are left with a difficult decision: you have two implementations of the same functionality, which one do you choose? Bearing in mind that the unassuming java.run() leads to a considerable amount of inter-process communication between the Stingray child process and the Java Servlet runner, whereas the xml.validate() is handled in C++ inside the same process, it is a rather obvious choice. But there are still situations when you might prefer the Java solution.   One example would be that you have to do XML processing not supported directly by Stingray. Java is more flexible and complete in the XML support it provides. But there is another advantage to using Java: you can replace the actual implementation of the XML functionality. You might want to use Intel's XML Software SuiteJ for Java, for example. But how do you tell Stingray's Java runner to use another XML library? Only two settings have to be adapted:   java!classpath /opt/intel/xmlsoftwaresuite/java/1.0/lib/intel-xss.jar java!command java -Djava.library.path=/opt/intel/xmlsoftwaresuite/java/1.0/bin/intel64 -server   This applies if you have installed Intel's XML Software SuiteJ in /opt/intel/xmlsoftwaresuite/java/1.0/ and are using the 64 bit version of the shared library. Both changes can be made in the 'Global Settings' tab of the 'System' section in Stingray's UI.
View full article
Java Extensions may look complex, but they follow a very standard template that you can use as a basis for all of your code.   What is a Java Extension?   A Java Extension is a class derived from either the GenericServlet or HttpServlet class. HttpServlet-derived extensions are most common, so we'll focus on that class.   On its own, the abstract class implementation does not perform any useful functions.  When you derive an extension from the HttpServlet class, you provide your own implementation of its methods:   doGet() is called to service a GET request doPost() is called to service a POST request init() is called when the servlet is loaded (before it processes any requests) and can be used to perform initialization tasks   You then compile the extension to a Java class, upload it to Stingray's Java Extensions catalog, and configure Stingray to invoke the extension using the java.run() trafficscript function.  Stingray will then execute the code in the doGet() or doPost() methods against any transactions it processes.   An HttpServlet template   Every HTTP Servlet can start with the following template:   import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // Insert additional imports here... public class Example extends HttpServlet { private static final long serialVersionUID = 1L; public void init() throws ServletException { super.init(); // Add any initialization code here... } public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { // Add the extension code here.... } public void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { doGet( req, res ); // replace this to handle POSTs differently to GETs } }   Class names   Important note: The class name ('Example') and the source file name ('Example.java') must match, and together these will create a Java Extension named 'Example'.  If you want to refer to your extension by a different name, make sure to rename the class and the source file.   An example   To illustrate this, we'll use the eponymous 'Hello World' example.  HelloWorld does not require any explicit initialization (so we can omit the init() method) and because the class is named 'HelloWorld', make sure to save the source in the file HelloWorld.java :   import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // Additional imports import java.io.PrintWriter; public class HelloWorld extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { // Implementation of the servlet action res.setContentType( "text/html" ); PrintWriter out = res.getWriter(); out.println( "<h1>Hello, world!</h1>"); } public void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { doGet( req, res ); } }   Save as HelloWorld.java   Compilation   The servlet classes and the custom Stingray extensions are not part of the standard Java (J2SE) distribution.  Download the two files (Java Servlet API ( servlet.jar ) and Stingray Java Extension API ( zxtm-servlet.jar )) from Stingray's Java Extensions Catalog as below:   Download the Java Servlet API and Stingray Java Extensions API files   With the two servlet jar files and your source file HelloWorld.java in the same directory, you can build your Java class as follows:   javac -cp servlet.jar:zxtm-servlet.jar HelloWorld.java   Invoking the servlet   Upload the Java Servlet to your Java Extensions catalog.  Stingray will register HelloWorld.class and also create a simple TrafficScript (RuleBuilder) rule named 'HelloWorld'.  Assign this rule to a Virtual Server:   If you run this from a request rule, all subsequent rules processing will terminate and the response will be sent directly back to the client. If you run this from a response rule, it will modify the response data that was provided by the back-end server. Subsequent response rules will be run, and if they try to inspect the response data, they will see the response that was set by this code.   Additional Code Samples   Many of the code samples you will see here just provide the implementations for the service() or doGet() methods, and any anciliary methods required. You'll need to insert this code into the full class code (copy and paste from this page) and add the necessary import statements in order to create the full source file.  You can then compile it as above, or use your favourite IDE.   Most IDEs will help you insert the correct imports - Ctrl-Shift-O in Eclipse will do the trick for example.   Read more   Collected Tech Tips: Java Extension Examples Feature Brief: Java Extensions in Traffic Manager  
View full article
Stingray's Feature Brief: Java Extensions in Traffic Manager are useful for managing HTTP traffic, but you can also use the GenericServlet interface to implement new TrafficScript functions using a Java Extension.   The following code sample illustrates how to create a TrafficScript subroutine called soundex(); the Soundex algorithm calculates a phonetic representation of a word, and can be used to determine if two words sound similar. It's most usefully used to compare surnames, to detect related names and resolve simple misspellings.   The TrafficScript code   First, the TrafficScript code for our new soundex() subroutine:   sub soundex( $word ) { java.run( "Soundex", $word ); return connection.data.get( "soundex" ); }   This code declares a TrafficScript subroutine called soundex that takes one argument, then calls a Java Extension and passes it the value of that argument.   Note that, as per the Java Servlet API, Java Extensions cannot return values directly. The easiest way to return a value is to set connection local data in the Extension, then look the value up in TrafficScript using connection.data.get() .   The Java Extension   The Java Extension uses the GenericServlet API and implements a new service() method:   import java.io.IOException; import javax.servlet.*; import com.zeus.ZXTMServlet.ZXTMServletRequest; public class Soundex extends GenericServlet { private static final long serialVersionUID = 1L; public void service( ServletRequest req, ServletResponse res ) throws IOException { String[] args = (String[])req.getAttribute( "args" ); String result = doSoundex( args[0] ); ((ZXTMServletRequest)req).setConnectionData( "soundex", result ); } static String soundex = "01230120022455012623010202"; String doSoundex( String s ) { s = s.toUpperCase(); StringBuilder r = new StringBuilder(); char last = '0'; if( s.length() > 0 ) last = s.charAt( 0 ); r.append( last ); for( int i = 1; i < s.length(); i++ ) { int j = s.charAt( i )-'A'; char next = ( j >= 0 && j < soundex.length() ) ? soundex.charAt( j ) : '0'; if( next != last && next != '0' ) { r.append( next ); last = next; } if( r.length() >= 4 ) break; } while( r.length() < 4 ) r.append( '0' ); return r.toString(); } }   Compile and upload the extension class file to your Stingray.   Using the new TrafficScript function   Here's a simple TrafficScript request rule that you can assign to a Generic Client-First virtual server (ensure that it's set to 'Run Every', not 'Run Once'):   sub soundex( $word ) { java.run( "Soundex", $word ); return connection.data.get( "soundex" ); } $word = string.trim( request.getLine() ); request.sendResponse( " '".$word."' sounds like " . soundex( $word ) . "\n" );   Connect to the virtual server using telnet, and type in a few words: This is just a toy example, and you could probably, with a little more work, implement a soundex algorithm directly in TrafficScript, but this example serves to illustrate how you can create new TrafficScript subroutines using implementations in Java.   Read more   Collected Tech Tips: Java Extension Examples Feature Brief: Java Extensions in Traffic Manager
View full article
This is an update to the "Simply WURFL" article written in 2009.  Since that time, some of the underlying libraries have changed, especially with regards to logging.   What is WURFL?  WURFL stands for Wireless Universal Resource FiLe.  From the WURFL webpage: "WURFL is a Device Description Repository (DDR), i.e. a framework that enables applications to map HTTP requests to a description of the capability of the mobile device that requests the page."  WURFL is licensed and maintained by ScientiaMobile, Inc. and includes a Java API so it can be used with the Stingray Traffic Manager.  It is up to the user to make sure they comply with the ScientiaMobile WURFL license.  You do not need to know Java to get these examples working since all the necessary class and jar files are either attached to this article or available at the links below, but if you do want to modify the source code, the source files are also attached.  To get started with WURFL you first need to ensure your Stingray Traffic Manager has working Java support and then download the following items.   wurfl Java API : This code has been tested with version 1.4.4.3.  You will need wurfl-1.4.4.3.jar. wurfl: The WURFL respository.  wurfl.zip which can be used as is or extracted as wurfl.xml.  This code has been tested with version 2.3.4. commons-collections: Java interfaces, implementations and utilities.  This code has been tested with version 3.2.1.  You will need commons-collections-3.2.1.jar. commons-lang: Helper utilities.  This code has been tested with version 3.1.  You will need commons-lang3-3.1.jar. sl4j: Logging framework.  This code has been tested with version 1.7.5.  You will need slf4j-api-1.7.5.jar and slf4j-noop-1.7.5.jar or slf4j-simple-1.7.5.jar depending on what you want to see with regards to logging messages.  slf4j-noop-1.7.5.jar will cause all messages to be suppressed while slf4j-simple-1.7.5.jar will cause all messages to appear in the Stingray event log as warnings.   Upload the files specified using the Calalogs > Java UI page on your Stingray Traffic Manager. Now you're all set to experiment with the following examples.   The first sample servlet is a very simple use-case of WURFL, useful as a base for your own servlet or for debugging. The intention is for it to introduce the WURFL API as it fits within the framework of Stingray Java Extensions. WURFL is typically configured using the ConfigListener model, but Stingray doesn't go as far as implementing all the nuts and bolts required by full web applications. Our WURFL servlet must perform the required initialization itself, so an init method has been implemented that sets up a WURFLManager. As much work as possible is done at servlet initialization time. Then all the doGet method needs to do is check the request against the pre-initialised WURFLManager.   The source code and the compiled class for this example, StingrayWURFLInfoServlet are attached to this article.  To compile the code yourself on the Stingray instance, after uploading the specified jar files, you can upload the source file as well and then from the $ZEUSHOME/zxtm/conf/jars directory execute the following command:   javac -cp "$ZEUSHOME/zxtm/lib/*:$ZEUSHOME/zxtm/conf/jars/* StingrayWURFLInfoServlet.java   To compile it on a different machine, you will need the following jar files that were uploaded to Stingray: commons-collections-3.2.1.jar, commons-lang3.3.1.jar, wurfl-1.4.4.3.jar as well as servlet.jar and zxtm-servlet.jar from Stingray.  These are available via links in the Stingray UI under Catalogs > Java.  Please see the Stingray Java Development Guide for more information.   To get the servlet working follow these steps:   Upload the StingrayWURFLInfoServlet.class file using Stingray Catalogs > Java UI page, leave the "Automatically create TrafficScript rule" checkbox ticked If you want devices that are not included in the standard WURFL repository, like desktop browsers, to be detected properly, a patches file can be created and uploaded to Stingray using the Catalogs > Java page.   If this file is uploaded, click on the StingrayWURFLInfoServlet link on the UI page and add the parameter "wurfl_patches" with the value of the name of the patches file, e.g., "web_browsers_patch.xml".  For details on creating a patches file see the ScientiaMobile website. Set up a Virtual Server for testing.  The pool can be set to "discard" since StingrayWURFLInfoServlet will create a response. Associate the auto-generated StingrayWURFLInfoServlet rule with the test Virtual Server as a request rule. Visit the virtual server from different browsers on different devices.   The result should be a page showing some general information about your browser at the top followed by the full table of WURFL capabilities and their values. The following screenshots show the top part of the output using a iPhone and a desktop browser:   Safari on iPhone:     Firefox on Windows:   This is fine as a demo but not particularly useful for a real world application. What we really want is a module that can export capabilities so that they can be used by TrafficScript and other extensions. We also don't want to specifically be a doGet processor, so we'll modify the servlet along the lines described in the article: Writing TrafficScript functions in Java   in Java*</a>   There are a lot of capabilities covered by WURFL, the tables in the above screenshots go on for several pages - more than 500 capabilities in all. So we'll make it possible for TrafficScript to specify what capability fields it wants.   This gives us a servlet that could be used from TrafficScript using a rule such as the following example.  This rule extracts a few values and caches them in the Global Associative Array using the user-agent as the key so that subsequent requests for the same user-agent don't require another look up.   sub checkWURFL() { $ua = http.getHeader( "User-Agent" ); if (!string.length($ua)) return; $markup = data.get("WURFL" . $ua . "preferred_markup"); $datarate = data.get("WURFL" . $ua . "max_data_rate"); $brand = data.get("WURFL" . $ua . "brand_name"); $cookie = data.get("WURFL" . $ua . "cookie_support"); $os = data.get("WURFL" . $ua . "device_os"); $osVersion = data.get("WURFL" . $ua . "device_os_version"); $isWireless = data.get("WURFL" . $ua . "is_wireless_device"); if (string.length($markup)) { log.info("Returning cached values for User-Agent: " . $ua . ", datarate: " . $datarate . ", markup: " . $markup . ", brand: " . $brand . ", cookies: " . $cookie . ", os: " . $os . ", version: " . $osVersion . ", iswireless: " . $isWireless); $1 = $markup; $2 = $datarate; $3 = $brand; $4 = $cookie; $5 = $os; $6 = $osVersion; $7 = $isWireLess; return; } # no cached values for the UA, so run it through WURFL java.run("StingrayWURFLServlet", "max_data_rate", "preferred_markup", "brand_name", "cookie_support", "device_os", "device_os_version", "is_wireless_device"); $markup = connection.data.get("preferred_markup"); $datarate = connection.data.get("max_data_rate"); $brand = connection.data.get("brand_name"); $cookie = connection.data.get("cookie_support"); $os = connection.data.get("device_os"); $osVersion = connection.data.get("device_os_version"); $isWireless = connection.data.get("is_wireless_device"); data.set("WURFL" . $ua . "preferred_markup", $markup); data.set("WURFL" . $ua . "max_data_rate", $datarate); data.set("WURFL" . $ua . "max_data_rate", $datarate); data.set("WURFL" . $ua . "brand_name", $brand); data.set("WURFL" . $ua . "cookie_support", $cookie); data.set("WURFL" . $ua . "device_os", $os); data.set("WURFL" . $ua . "device_os_version", $osVersion); data.set("WURFL" . $ua . "is_wireless_device", $isWireless); log.info("Returning fresh WURFL values for User-Agent: " . $ua . ", datarate: " . $datarate . ", markup: " . $markup . ", brand: " . $brand . ", cookies: " . $cookie . ", os: " . $os . ", version: " . $osVersion . ", iswireless:" . $isWireless); $1 = $markup; $2 = $datarate; $3 = $brand; $4 = $cookie; $5 = $os; $6 = $osVersion; $7 = $isWireless; return; } # simple case to test the checkWURFL function checkWURFL(); $html = "Max Data Rate: " . $2 . "kbps Preferred Markup: " . $1 . " Brand: " . $3 . " OS: " . $5 . " Version: " . $6 . " Cookie Support: " . $4 . " Is Wireless Device: " . $7; http.sendResponse("200", "text/html", $html, "");   The corresponding StingrayWURFLServlet source and compiled class file and slf4j-stingray.jar file are attached to this article.  The slf4j-stingray.jar file includes an implementation to properly direct slf4j messages to the Stingray Event Log. The logging code is documented here: slf4j Logging and Stingray Java Extensions . The slf4j-noop-1.7.5.jar or slf4j-simple-1.7.5.jar file uploaded for the previous example should be deleted.  To control what level of logging to output to the Stingray Event Log, a parameter, "log_level" can be set to a value (case insensitive) of "debug", "info", "warn" or "error".  If no value is set, the default is "warn".   Rather than the doGet method implemented in the StingrayWURFLInfoServlet we now have a simpler service method.   public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String[] args = (String[])request.getAttribute( "args" ); if (args == null || args.length == 0) { throw new ServletException("error: no arguments supplied"); } Device device = manager.getDeviceForRequest(request); Map capabilities = device.getCapabilities(); for (int i = 0; i < args.length; ++i) { String cap = (String)capabilities.get(args[i]); if (cap == null) { Logger.warn("Java Servlet StingrayWURFLServlet: No capability found matching: " + args[i]); } else { ((ZXTMServletRequest)request).setConnectionData( args[i], cap ); } } }   There is much more you could do with the StingrayWURFLServlet Java Extension provided in this article. Think of it as a starting point for developing your own solutions to improve the web browsing experience of your mobile users.   A few examples:   The max_data_rate value retrieved above could be used to reduce image quality or size for people with low bandwidth devices. This would result in a snappier web browsing experience for these people as there would be less data for them to retrieve over their slow links. The preferred_markup value can be used to direct clients to different backend pools based on whether they can handle XHTML, or should be served WML. The streaming_flv value can be checked to see if the device has Flash video support and can thus be sent to your full bells-and-whistles website. A scaled down version could be made for those that only have Flash Lite, which is specified by the value of flash_lite_version. Devices that don't support flash at all (such as the iPhone) can be sent to a plain HTML version of the site, or WML as in the previous bullet point.   Speaking of the iPhone, it doesn't have Flash but its browser does have excellent AJAX support. You know your site is being visited by an iPhone user using the normal iPhone web browser when model_name is "iPhone" and mobile_browser is "Safari". If there are important differences between iPhone OS releases you can also check model_extra_info ordevice_os_version for this detail. For AJAX in general there are a whole set of specific properties: ajax_manipulate_css ajax_manipulate_dom ajax_support_event_listener ajax_support_events   Up to date documentation on all the WURFL capabilities can be found on the WURFL website.   <a title=h   Any of these values can also be passed on to your backend nodes of course. You could add special headers containing the values, a cookie, or a URL argument. You could also cache browser capabilities uniquely to each device with cookies or another method of session tracking, rather than cache the capabilities based solely on the user agent. Then you could offer users the ability to override special mobile device modes.   We would love to hear your ideas and learn how we can help you in this exciting area - the opportunities are practically limitless.
View full article
The following is a Traffic Script Rule for load-balancing VMware View Servers in an AlwaysOn Architecture serving all VMware clients.   This Rule is used both as a Request and a Response rule. More details on the usage and configurations of the Brocade Virtual Traffic Manager can be found in the Deployment Guide (link below).   This link is used as a reference in the Brocade Virtual Traffic Manager and VMware Horizon View Servers Deployment Guide     #// TS Rule for LB VMWare View    #// Set the Debug Level (possible values: 0,1,2,3) #// 0 = Logging Off #// 1 = Informational logging #// 2 = Full Debug #// 3 = Full Debug INCLUDING PASSWORDS - USE WITH EXTREME CARE $debug = 2;    #// Rule to Direct Traffic Based on AD Group Membership  #// Please declare the names of the pools you have configured, and ensure  #// that the trafficscript!variable_pool_use Global setting is set to 'yes'    #// What is the name of your VTM AD Authenticator #// This can be setup under "Catalogs > Authenticators" in the Traffic Manager GUI. $authenticator = "AD_AUTHENTICATOR";    #// What are the names of your pools to differentiate between Site A and Site B? #// These are setup under "Services > Pools" in the Traffic Manager GUI. $view_siteA_pool = "SITE-A-POOL"; $view_siteB_pool = "SITE-B-POOL";   #// Following are some View Specific variables that we need. #// 'server' is the name of your view Connection Server #// 'dns' is the DNS search suffix (single suffix only) used in your setup #// 'domain' is the NT4 style name of your active directory #// 'guid' is the GUID of the view connection server, and can be found by checking #//     'HKLM\SOFTWARE\VMware, Inc.\VMware VDM\Node Manager\ConnectionServer Cluster GUID' with regedit.exe #//    on your connection server.   $view_info =      [ "guid" => "648a7ef8-dcba-abcd-efgh-58973ad94085",        "server" => "rvbdvTMs1",        "dns" => "brocade.local",        "domain" => "brocade" ];      #// Here we need to know about the names of the AD Groups to map to each Site $siteA_AD_Groupname = "brcd_DC0"; $siteB_AD_Groupname = "brcd_DC1";   #// Here we need to know about the IP addresses of your SITEB Traffic Manager.   #// Site B VTM IP addresses (up to two supported. If your setup requires more, please #// Contact your Brocade sales representative to arrange for Brocade Professional #// Services to provide a quote for customising this deployment guide.       $site_B_VTM1_IP = "10.10.10.6";  $site_B_VTM2_IP = "";       #// Is this a request or response rule? $rulestate = rule.getstate();   #// What is the Client's IP address - useful for logging: $client_ip = request.getRemoteIP();    if ($rulestate == "REQUEST" ){ if( $debug > 0 ) {log.info("#*#* Start Reqeust Section *#*#");} #//We need to extract the tunnelID for persistence purposes then break out of the rule to let the tunnel through $path = http.getPath(); if (string.startswith($path, "/ice/tunnel")){     $tunnelID = http.getQueryString();     if ($tunnelID){       if ($debug > 0){log.info("Request from client: " . $client_ip . " Using tunnelID:". $tunnelID . " as persistence value");}       connection.setPersistenceKey( $tunnelID );       $choose_pool = data.get( $tunnelID );       if( $debug > 0 ) { log.info( "Matching this to pool: ".$choose_pool ); }       if( $choose_pool ) {         pool.select( $choose_pool );       }     } else {       log.error("Request from client: " . $client_ip . " Found no tunnelID:". $tunnelID . " in request for /ice/tunnel: This shouldn't happen, contact support.");     }     if ($debug > 1){log.info("Request from client: " . $client_ip . " Request is for /ice/tunnel - exiting rule to let it through");}     #//Bypass Script if the path is for "/ice/tunnel" to let the desktop session start     break; }     $JSESSIONID = http.getCookie("JSESSIONID"); if($JSESSIONID){   if ($debug > 0) {log.info("Request from client: " . $client_ip . " JSESSIONID=" .$JSESSIONID." found in the request...");}   connection.setPersistenceKey($JSESSIONID);   $choose_pool = data.get( $JSESSIONID );   if( $debug > 0 ) { log.info( "Matching this to pool: ".$choose_pool ); }   if( $choose_pool ) {     pool.select( $choose_pool );   } } else {   if ($debug > 0) {log.info("Request from client: " . $client_ip . " No JSESSIONID found in the request...");} }   #//Bypass Script if the path is for / as it could be GLB health monitor  if( $path == "/") { break;}   #// Collect the HTTP request headers and body $req_headers = http.getRequest(); if(!http.getHeader("Expect")=="100-continue"){       $req_body = http.getBody(); } if ($debug > 1){log.info("Request from client: " . $client_ip . " HTTP Request is:\n" . $req_headers);} if ($debug > 1){log.info("Request from client: " . $client_ip . " HTTP BODY is:\n" . $req_body);}       #// Reset flags to the needed defaults. $username = '';  $password = '';    #// Inspect the request and see if it is something we are interested in: #// syntax is xml.xpath.matchNodeSet( doc, nspacemap, query ) $is_xml = false; if (!string.regexmatch($req_body, '\<\?xml',"i")){   #//Document is _not_ an XML                   if ($debug > 1){log.info("Request from client: " . $client_ip . " Request is NOT an XML document - exiting");}                   $is_xml = false;                   break; } else {   #//Document is an XML Doc   $is_xml = true;   if ($debug > 0){log.info("Request from client: " . $client_ip . " Request is an XML document");} }   #// test to see if we have been sent a "<get-configuration>" request $get_configuration = xml.xpath.matchNodeCount( $req_body, "", "//broker/get-configuration" ); if ($debug > 0){log.info("Request from client: " . $client_ip . " get-config is:" . $get_configuration);}   #// test to see if we have been sent a "<get-tunnel-connection>" request $get_tunnel_connection = xml.xpath.matchNodeCount( $req_body, "", "//broker/get-tunnel-connection" ); if ($debug > 0){log.warn("Request from client: " . $client_ip . " get-tunnel-connection is:" . $get_tunnel_connection);}   #// test to see if we have been sent a "<do-submit-authentication>" request $do_submit_authentication = xml.xpath.matchNodeCount( $req_body, "", "//broker/do-submit-authentication" ); if ($debug > 0){log.info("Request from client: " . $client_ip . " do-submit-authentication is:" . $do_submit_authentication);}   #// test to see if we have been sent a "<do-logout>" request $do_logout = xml.xpath.matchNodeCount( $req_body, "", "//broker/do-logout" ); if ($debug > 0){log.info("Request from client: " . $client_ip . " do-logout is:" . $do_logout);}   #// test to see if we have been sent a "<get-tunnel-connection>" request if ($get_tunnel_connection == 1 && $is_xml == true){   if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <get-tunnel-connection> identified from: " . $client_ip ); }     connection.data.set("connection_state", "get-tunnel-connection");  } #// If we have a <get-configuration> query, we will send the first response and exit if ($get_configuration == 1 && $is_xml == true){   if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <get-configuration> response - Sending first_response to client: " . $client_ip ); }   sendFirstResponse($view_info, $debug);   break; } #// If we have been sent authentication credentials, we will go to work if ($do_submit_authentication == 1 && $is_xml == true){   if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <do-submit-authentication> identified from: " . $client_ip ); }     $xml_user_credentials_data = xml.xpath.matchNodeSet($req_body,"","//broker/do-submit-authentication/screen/params/param/values/value/text()");   $xml_user_credentials_fieldnames = xml.xpath.matchNodeSet($req_body,"","//broker/do-submit-authentication/screen/params/param/name/text()");     #// we check that $xml_user_credentials_fieldnames contains "username, domain, password":   if(string.regexmatch( $xml_user_credentials_fieldnames, "username, domain, password")){        if ($debug > 0){log.info("Request from client: " . $client_ip . " <do-submit-authentication>: extracted username, domain, password fields in submitted request.");}     if ($debug > 1){log.info("Request from client: " . $client_ip . " <do-submit-authentication> extracted XML Fields: " .$xml_user_credentials_fieldnames);}     if ($debug > 2){log.info("Request from client: " . $client_ip . " <do-submit-authentication> extracted XML Values: " .$xml_user_credentials_data);}       #//lets extract the username and password:     $credentials = string.split($xml_user_credentials_data,",");     $username = $credentials[0];     $password = $credentials[2];     #// Currently we don't need the domain name, so we won't extract it, but it is here for future use if needed     #//$cred_domain = $credentials[1];          $auth = auth.query( $authenticator, $username, $password );      #// We should check to ensure auth.query returned successfully:     #/// If $auth returns 'Error' then it means something is wrong with the authenticator and the admin needs to investigate     if( $auth['Error'] ) {          log.error( "Request from client: " . $client_ip ." Error with authenticator " . $authenticator . ": " . $auth['Error'] );     }         #// Lets extract the list of groups the user is a 'memberOf'     $groups = $auth['memberOf'];      #// If there is only one group, "$auth['memberOf']" will return a string,     #// not an array, so we need to force the $groups value to be an array     $group_isArray = lang.isarray($groups);     if ($group_isArray != true){       if ($debug > 1){log.info("Connection From: " . $client_ip .": $auth['memberOf'] returned a single group, forcing $group to be an array");}       $groups = lang.toArray($groups);     }       if ($debug > 1){log.info("Request from client: " . $client_ip ." Full Auth Info: " . lang.dump($auth));}     if ($debug > 1){log.info("Request from client: " . $client_ip ." Group Info: " . lang.dump($groups));}            #// Map Site B users to the Site B pool of servers     foreach ( $group in $groups){       if( $debug > 0 ) {log.info("$group is" . lang.dump($group));}               if( string.contains( $group, $siteB_AD_Groupname ) ){            if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." member of SiteB Users group" );}            pool.select( $view_siteB_pool );            break;         } else{         if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." is NOT a member of SiteB Users group" );}           }     }     #// Map Site A users to the Site A pool of servers     foreach ( $group in $groups){        if( string.contains( $group, $siteA_AD_Groupname ) ) {            if( $debug > 0 ) { log.info( "Request from client: " . $client_ip ." User: ".$username." member of Default SiteA Users group" ) ;}            pool.select( $view_siteA_pool );           break;         } else{         if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." is NOT a member of SiteA Users group" );}           }     }   } } #//end do-submit-authentication    if( $debug > 0 ) {log.info("#*#* End Reqeust Section *#*#");} } #// End of REQUEST Section   sub sendFirstResponse( $info, $debug )  {   #//$first_response = '<?xml version="1.0"?><broker version="7.0"><configuration><result>ok</result><broker-guid>'.$info["guid"].'</broker-guid><broker-service-principal><type>kerberos</type><name>'.$info["server"].'[email protected]'.$info["dns"].'</name></broker-service-principal><authentication><screen><name>windows-password</name><params><param><name>domain</name><values><value>'.$info["domain"].'</value></values></param></params></screen></authentication></configuration></broker>'; $first_response = '<?xml version="1.0"?><broker version="7.0"><set-locale><result>ok</result></set-locale><configuration><result>ok</result><broker-guid>'.$info["guid"].'</broker-guid><broker-service-principal><type>kerberos</type><name>'.$info["server"].'[email protected]'.$info["dns"].'</name></broker-service-principal><authentication><screen><name>windows-password</name><params><param><name>domain</name><values><value>'.$info["domain"].'</value></values></param></params></screen></authentication></configuration></broker>';   if( $debug > 1 ){ log.info( "first_response data&colon;\n" .$first_response); }    http.sendResponse( "200 OK", "text/xml;charset=UTF-8", $first_response, "XFF: VTM_SiteA" );  } ###// AK: should be == if ($rulestate == "RESPONSE" ){ if( $debug > 0 ) {log.info("#*#* Start Response Section *#*#");} $debug_node = connection.getNode(); $debug_pool = connection.getPool(); $debug_http_response_code=http.getResponseCode();   $resp_headers = http.getResponseHeaders(); if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}   #//$resp_body = http.getResponseBody( 96 );  #// What is the nature of the response? $content_type = http.getResponseHeader( "Content-Type" ); if ($debug > 1) { log.info("Content type of response is" . $content_type ); } if( string.startsWith( $content_type, "text/xml" ) ) {   #// TODO: What if the response doesn't contain a </broker> tag?   $resp_body = http.stream.readResponse( 4096, "</broker>" ); #// Limit was arbitrarily chosen } else {     if( $debug > 0 ) {log.warn( "This response was not XML - not extracting content for logging." );} }   #// ASSUMPTION: Any XML response we care about (i.e. the one with the session ID in it) is less than 4096 bytes. #//             If it's longer, then we'll just stream it to the client. if( string.length( $content_type ) < 4096 ) {    if ($debug > 1) { log.info( "Grabbed response body:\n" . $resp_body ); }    if( $debug > 0 ) {log.info("RESPONSE: connection was sent to node:" . $debug_node);}  if( $debug > 0 ) {log.info("RESPONSE: connection was sent to pool:" . $debug_pool);}  if( $debug > 0 ) {log.info("RESPONSE: Server Responded with code:" . $debug_http_response_code);}    $response_cookies = http.getResponseCookies();       if($response_cookies){     if($response_cookies["JSESSIONID"]){       $JSESSIONID = $response_cookies["JSESSIONID"];       #// need to bypasS for <set-locale> message        if (string.regexmatch($resp_body, '\<\?xml',"i")){           if( $debug > 0 ) {log.info("#### PARSING XML RESPONSE ###");}           $set_locale = xml.xpath.matchNodeCount($resp_body,"","//broker/set-locale/result");           if( $debug > 0 ) {log.info("#### SETLOCALE IS: ". $set_locale . " ###");}           if (!$set_locale){              connection.setPersistenceKey( $JSESSIONID );              data.set( $JSESSIONID, $debug_pool );              if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set JSESSIONID=" . $JSESSIONID .". Extracting and using for persistence key");}           } else {              if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set JSESSIONID=" . $JSESSIONID .". _NOT_ extracting and using for persistence key");}           }        } else {           if( $debug > 0 ) {log.warn( "Response data did not seem to be XML." );}       }     }   } else {     if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " No JSESSIONID found in response...");}   }      if (connection.data.get("connection_state") ==  "get-tunnel-connection"){     if ($debug > 0){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}     if ($debug > 0){log.info("Response to: " . $client_ip . " HTTP Response BODY is:" . $resp_body);}              #// Inspect the response and see if it is something we are interested in:     #// syntax is xml.xpath.matchNodeSet( doc, nspacemap, query )     if (string.regexmatch($resp_body, '\<\?xml',"i")){       #//Document is an XML Doc       if ($debug > 0){log.info("Request from client: " . $client_ip . " Response to <get-tunnel-connection> is an XML document");}       $tunnelID = xml.xpath.matchNodeSet($resp_body,"","//broker/tunnel-connection/connection-id/text()");       if ($tunnelID){         if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set tunnelID=" . $tunnelID .". Extracting and using for persistence key");}         connection.setPersistenceKey( $tunnelID );         data.set( $tunnelID, $debug_pool );       }      } else {        lang.warn( "Didn't think response body was XML." );     }    } #// End of connection_state == get-tunnel-connection     if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}   if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response BODY is:" . $resp_body);} }   if( $resp_body ) {    http.stream.startResponse( http.getResponseCode(), $content_type, "" );    http.stream.writeResponse( $resp_body );    http.stream.continueFromBackend(); }   if( $debug > 0 ) {log.info("#*#* End Response Section *#*#");} } ##/// End of RESPONSE Section#//  
View full article
An HTTP request contains a number of different parameters:   GET /search?hl=en&q=Stingray&btnG=Google+Search HTTP/1.1 Host: www.google.com User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://www.google.com/ Cookie: PREF=4; recall=true; AdSenseLocale=en_US   There are a number of TrafficScript functions that make it easy to read and modify each of these parameters when Stingray is processing a request:   Inspecting the first line   $method = http.getMethod(); # returns 'GET' $rawurl = http.getRawURL(); # returns '/search?hl=en&q=ZXTM&btnG=Google+Search' $path = http.getPath(); # returns '/search' $querystr = http.getQueryString(); # returns 'hl=en&q=ZXTM&btnG=Google+Search' $version = http.getVersion(); # returns '1.1'   Inspecting headers   $host = http.getHeader( "Host" ); # returns 'www.google.com' $cookie = http.getHeader( "Cookie" ); # returns 'PREF=4; recall=true; AdSenseLocale=en_US' $adlocale = http.getCookie( "AdSenseLocale" ); # returns 'en_US'   Other functions   # An HTTP request may contain an additional POST body $body = http.getBody(); # Iterate through a multipart POST body $count = 0; while( $data = http.getMultipartAttachment( $count ) ) { ... $count = $count + 1; } # Get a form parameter - this function inspects both the query string # and the POST body to look up the form parameter $searchterm = http.getFormParam( "q" );   Most of these functions have counterparts to set the value, delete it or to test if it exists. For example:   http.setHeader( "Host", "www.zeus.com" ); # set the host header http.removeHeader( "Accept-Encoding" ); # delete the 'Accept-Encoding' header http.setPath( "/secure/index.html" ); # set the URL in the request if( http.headerExists( "Cookie" ) ) .... # did the client provide a Cookie header?   Read more   Collected Tech Tips: TrafficScript examples
View full article
TrafficScript rules often need to refer to tables of data - redirect mappings, user lists, IP black lists and the like.   For small tables that are not updated frequently, you can place these inline in the TrafficScript rule:   $redirect = [ "/widgets" => "/sales/widgets", "/login" => "/cgi-bin/login.cgi" ]; $path = http.getPath(); if( $redirect[ $path ] ) http.redirect( $redirect[ $path ] ); This approach becomes difficult to manage if the table becomes large, or you want to update it without having to edit the TrafficScript rule.  In this case, you can store the table externally (in a resource file) and reference it from the rule:     The following examples will consider a file that follows a standard space-separated 'key value' pattern, and we'll look at alternative TrafficScript approaches to efficiently handle the data and look up key-value pairs:   # cat /opt/zeus/zxtm/conf/extra/redirects.txt /widgets /sales/widgets /login /cgi-bin/login.cgi /support http://support.site.com We'll propose several 'ResourceTable' TrafficScript library implementations that express a lookup() function that can be used in the following fashion:   # ResourceTable provides a lookup( filename, key ) function import ResourceTable as table; $path = http.getPath(); $redirect = table.lookup( "redirects.txt", $path ); We'll then look at the performance of each to see which is the best.   For a summary of the solutions in this article, jump straight to libTable.rts: Interrogating tables of data in TrafficScript.   Implementation 1: Search the file on each lookup   ResourceTable1 sub lookup( $filename, $key ) { $contents = resource.get( $filename ); if( string.regexmatch( $contents, '\n'.$key.'\s+([^\n]+)' ) ) return $1; if( string.regexmatch( $contents, '^'.$key.'\s+([^\n]+)' ) ) return $1; return ""; } This simple implementation searches the file on each and every lookup, using a regular expression to locate the key and also the text on the remainder of the line.  It pins the key to the start of the line so that it does not mistakenly match lines where $key is a substring (suffix) of the key.   The implementation is simple and effective, but we would reasonably expect that it would become less and less efficient, the larger the resource file became.   Implementation 2: Store the table in a TrafficScript hash table for easy lookup   The following code builds a TrafficScript hash table from the contents of the resource file: $contents = resource.get( $filename ); $h = [ ]; foreach( $l in string.split( $contents, "\n" ) ) { if( ! string.regexmatch( $l, '(.*?)\s+(.*)' ) ) continue; $key = string.trim( $1 ); $value = string.trim( $2 ); $h[$key] = $value; } You can then quickly look up values in the hash table using $h[ $key ].   However, we don't want to have to create the hash table every time we call the lookup function; we would like to create it once and then cache it somewhere.  We can use the global data table to store persistent data, and we can verify that the data is still current by checking that the MD5 of the resource file has not changed:   ResourceTable2a sub update( $filename ) { # Store the md5 of the resource file we have cached. No need to update if the file has not changed $md5 = resource.getMD5( $filename ); if( $md5 == data.get( "resourcetable:".$filename.":md5" ) ) return; # Do the update $contents = resource.get( $filename ); $h = [ ]; foreach( $l in string.split( $contents, "\n" ) ) { if( ! string.regexmatch( $l, "(.*?)\\s+(.*)" ) ) continue; $key = string.trim( $1 ); $value = string.trim( $2 ); $h[$key] = $value; } data.set( "resourcetable:".$filename.':data', $h ); data.set( "resourcetable:".$filename.':md5', $md5 ); } sub lookup( $filename, $key ) { # Check to see if the file has been updated, and update our table if necessary update( $filename ); $h = data.get( "resourcetable:".$filename.':data' ); return $h[$key]; } Version 2a: we store the MD5 of the file in the global key 'resourcetable:filename:md5', and the hash table in the global key 'resourcetable:filename:data'.   This implementation has one significant fault.  If two trafficscript rules are running concurrently, they may both try to update the keys in the global data table and a race condition may result in inconsistent data.  This situation is not possible on a single-core system with one zeus.zxtm process because rules are run serially and only pre-empted if they invoke a blocking operation, but it's entirely possible on a multi-core system, and TrafficScript does not implement mutexes or locks to help protect against this.   The simplest solution is to give each core its own, private copy of the data.  Because system memory should be scaled with the number of cores, the additional overhead of these copies is generally acceptable:   ResourceTable2b: sub update( $filename ) { $pid = sys.getPid(); $md5 = resource.getMD5( $filename ); if( $md5 == data.get( "resourcetable:".$pid.$filename.":md5" ) ) return; $contents = resource.get( $filename ); $h = [ ]; foreach( $l in string.split( $contents, "\n" ) ) { if( ! string.regexmatch( $l, "(.*?)\\s+(.*)" ) ) continue; $key = string.trim( $1 ); $value = string.trim( $2 ); $h[$key] = $value; } data.set( "resourcetable:".$pid.$filename.':data', $h ); data.set( "resourcetable:".$pid.$filename.':md5', $md5 ); } sub lookup( $filename, $key ) { update( $filename ); $pid = sys.getPid(); $h = data.get( "resourcetable:".$pid.$filename.':data' ); return $h[$key]; }   Version 2b: by including the pid in the name of the key, we avoid multi-core race conditions at the expense of multiple copies of the date   Implementation 3: Store the key/value data directly in the global hash table   data.set and data.get address a global key/value table.  We could use that directly, rather than constructing a TrafficScript hash:   sub update( $filename ) { $pid = sys.getPid(); $md5 = resource.getMD5( $filename ); if( $md5 == data.get( "resourcetable".$pid.$filename.":md5" ) ) return; data.reset( "resourcetable".$pid.$filename.":" ); data.set( "resourcetable".$pid.$filename.":md5", $md5 ); $contents = resource.get( $filename ); foreach( $l in string.split( $contents, "\n" ) ) { if( ! string.regexmatch( $l, "(.*?)\\s+(.*)" ) ) continue; $key = string.trim( $1 ); $value = string.trim( $2 ); data.set( "resourcetable".$pid.$filename."::".$key, $value ); } } sub lookup( $filename, $key ) { update( $filename ); $pid = sys.getPid(); return data.get( "resourcetable".$pid.$filename."::".$key ); }   Version 3: key/value pairs are stored in the global data table.  Keys begin with the string "resourcetable:pid:filename:", so it's easy to delete all of the key/value pairs using data.reset() before rebuilding the dataset   How do these implementations compare?   We tested the number of lookups-per-second that each implementation could achieve (using a single-core virtual machine running on a laptop Core2 processor) to investigate performance for different dataset sizes:   Resource file size (entries) 10 100 1,000 10,000 Implementation 1: simple search 300,000 100,000 17,500 1,000 Implementation 2: trafficscript hash, cached in global data table 27,000 2,000 250 10 Implementation 3: key/value pairs in the global data table 200,000 200,000 200,000 200,000   ResourceTable lookups per second (single core, lightweight processor)   The test just exercised the rate of lookups in resource files of various sizes; the cost of building the cached datastructures (implementations 2 and 3) and one-off costs and not included in the tests.   Interpreting the results   The degradation of performance in implementation 1 as the file size increases was to be expected.   The constant performance of implementation 3 was as expected, as hash tables should generally give O(1) lookup speed, not affected by the number of entries.   The abysmal performance of implementation 2 is surprising, until you note that on every lookup we retrieve the entire hash table from the global data table:   $h = data.get( "resourcetable:".$pid.$filename.':data' ); return $h[$key];     The global data table is a key/value store; all keys and values are serialized as strings.  The data.get() operation will read the serialized version of the hash table and reconstruct the entire table (up to 10,000 entries) before the O(1) lookup operation.   What is most surprising perhaps is the speed at which you can search and extract data from a string using regular expressions (implementation 1).  For small and medium datasets (up to approx 50 entries), this is the simplest and fastest method, and it's only worth considering the more complex data.get key/value implementation for large datasets.   Read more   Check out the article How is memory managed in TrafficScript? for more detail on the ways that TrafficScript handles data and memory    
View full article
This article presents a TrafficScript library that give you easy and efficient access to tables of data stored as files in the Stingray configuration:   libTable.rts   Download the following TrafficScript library from gihtub and import it into your Rules Catalog, naming it libTable.rts :   libTable.rts   # libTable.rts # # Efficient lookups of key/value data in large resource files (>100 lines) # Use getFirst() and getNext() to iterate through the table sub lookup( $filename, $key ) { update( $filename ); $pid = sys.getPid(); return data.get( "resourcetable".$pid.$filename."::".$key ); } sub getFirst( $filename ) { update( $filename ); $pid = sys.getPid(); return data.get( "resourcetable".$pid.$filename.":first" ); } sub getNext( $filename, $key ) { update( $filename ); $pid = sys.getPid(); return data.get( "resourcetable".$pid.$filename.":next:".$key ); } # Internal functions sub update( $filename ) { $pid = sys.getPid(); $md5 = resource.getMD5( $filename ); if( $md5 == data.get( "resourcetable".$pid.$filename.":md5" ) ) return; data.reset( "resourcetable".$pid.$filename.":" ); data.set( "resourcetable".$pid.$filename.":md5", $md5 ); $contents = resource.get( $filename ); $pkey = ""; foreach( $l in string.split( $contents, "\n" ) ) { if( ! string.regexmatch( $l, "(.*?)\\s+(.*)" ) ) continue; $key = string.trim( $1 ); $value = string.trim( $2 ); data.set( "resourcetable".$pid.$filename."::".$key, $value ); if( !$pkey ) { data.set( "resourcetable".$pid.$filename.":first", $key ); } else { data.set( "resourcetable".$pid.$filename.":next:".$pkey, $key ); } $pkey = $key; } }   Usage:   import libTable.rts as table; $filename = "data.txt"; # Look up a key/value pair $value = table.lookup( $filename, $key ); # Iterate through the table for( $key = table.getFirst( $filename ); $key != ""; $key = table.getNext( $filename, $key ) ) { $value = table.lookup( $filename, $key ); }   The library caches the contents of the file internally, and is very efficient for large files.  For smaller files, it may be slightly more efficient to search these files using a regular expression, but the convenience of this library may outweigh the small performance gains.   Data file format   This library provides access to files stored in the Stingray conf/extra folder (by way of the Extra Files > Miscellaneous Files) section of the catalog.  These files can be uploaded using the UI, the SOAP or REST API, or by manually copying them in place and initiating a configuration replication.   Files should contain  key-value pairs, one per line, space separated:   key1value1 key2value2 key3value3   Preservation of order   The lookup operation uses an open hash table, so is efficient for large files. The getFirst() and getNext() operations will iterate through the data table in order, returning the keys in the order they appear in the file.   Performance and alternative implementations   The performance of this library is investigated in the article Investigating the performance of TrafficScript - storing tables of data.  It is very efficient for large tables of data, and marginally less efficient than a simple regular-expression string search for small files.   If performance is of a concern and you only need to work with small datasets, then you could use the following library instead:   libTableSmall.rts   # libTableSmall.rts: Efficient lookups of key/value data in a small resource file (<100 lines) sub lookup( $filename, $key ) { $contents = resource.get( $filename ); if( string.regexmatch( $contents, '\n'.$key.'\s+([^\n]+)' ) ) return $1; if( string.regexmatch( $contents, '^'.$key.'\s+([^\n]+)' ) ) return $1; return ""; }  
View full article
This document describes some Solaris operating system tunables you may wish to apply to a production Stingray Traffic Manager instance.  Note that the kernel tunables only apply to Stingray Traffic Manager software installed on a customer-provided Solaris instance; it does not apply to the Stingray Traffic Manager Virtual Appliance or Cloud instances.   Consider the tuning techniques in this document when: Running Stingray on a severely-constrained hardware platform, or where Stingray should not seek to use all available resources; Running in a performance-critical environment; The Stingray host appears to be overloaded (excessive CPU or memory usage); Running with very specific traffic types, for example, large video downloads or heavy use of UDP; Any time you see unexpected errors in the Stingray event log or the operating system syslog that relate to resource starvation, dropped connections or performance problems   For more information on performance tuning, start with the Tuning Stingray Traffic Manager article.   Increasing the ephemeral port range   The ephemeral port range sets the upper limit on the the number of TCP connections a server can hold open to a particular IP address (including connections that have been closed and are in the TIME_WAIT state). Increasing it is recommended; doing so has no unwanted side effects on a typical system.   /usr/sbin/ndd -set /dev/tcp tcp_smallest_anon_port 1024 /usr/sbin/ndd -set /dev/tcp tcp_largest_anon_port 65535 # This is usually the default value   Increasing the TCP listen queue   tcp_conn_req_max_q sets the maximum size of the TCP listen queue (the number of completed TCP connections waiting to be accept()ed), per port.   tcp_conn_req_max_q0 sets the maximum number of half-open TCP connections.   /usr/sbin/ndd -set /dev/tcp tcp_conn_req_max_q 1024 /usr/sbin/ndd -set /dev/tcp tcp_conn_req_max_q0 4096   If you expect a very high rate of new connections, you may find it beneficial to increase these again (by up to a factor of 10 or so).   TCP window scaling and timestamps   The following two tunables force window scaling on and enable TCP timestamps (a protection against sequence number wraparound). We recommend they both be enabled.   /usr/sbin/ndd -set /dev/tcp tcp_wscale_always 1 /usr/sbin/ndd -set /dev/tcp tcp_tstamp_if_wscale 1   TCP send/receive buffer size   The following increases the size of the TCP/Send receive buffer, enabling more efficient handling of connections.   /usr/sbin/ndd -set /dev/tcp tcp_max_buf 16777216   TCP window sizes   The following three tunables set the maximum size of the TCP congestion, transmit and receive windows respectively.   /usr/sbin/ndd -set /dev/tcp tcp_cwnd_max 8388608 /usr/sbin/ndd -set /dev/tcp tcp_xmit_hiwat 4000000 /usr/sbin/ndd -set /dev/tcp tcp_recv_hiwat 4000000   Duplicate Address Detection   Solaris' duplicate address detection can interfere with correct fail over of Traffic IP addresses. We recommend that the following tuning is applied to prevent this.   ndd -set /dev/arp arp_probe_count 0 ndd -set /dev/ip ip_dup_recovery 50   Solaris UltraSPARC T1/T2 systems   The following tunables have been found to increase performance on systems that use UltraSPARC T1 and T2 processors. We recommend that you consult Oracle for the recommended tunings for your Solaris release. The following lines should be added to /etc/system . The system will need to be rebooted before they take effect.   set ip:ip_soft_rings_cnt=4 set ip_squeue_soft_ring=1 set ip:ip_squeue_fanout=1 set ip:ip_squeue_bind=0 set hires_tick=1   TCP Fusion   During our testing on OpenSolaris we encountered some issues when the tcp_fusion option was enabled. We found that the kernel could incorrectly buffer large amounts of data, which could cause the machine in question to hang. We recommend that customers using this release of OpenSolaris turn off the tcp_fusion feature to avoid this issue.   To turn off this feature you can immediately turn it off with the following command:   # echo 'do_tcp_fusion/W 0' | mdb -kw   This will not persist across system reboots. If you want 'tcp_fusion' to be permanently disabled you will need to add the following line to '/etc/system':   set ip:do_tcp_fusion = 0   UltraSparc T2 Crypto Acceleration   The Sun UltraSPARC T1 and T2 processors contain on-board cryptographic routines that speed up SSL connections, and Stingray will use these routines if they are detected.   The T1 and T2 processors contain the ncp module which provides operations for the RSA algorithm. Additionally the T2 contains n2cp module which provides symmetric cipher support (such as AES, DES and 3DES) and digest algorithms (e.g. MD5 and SHA-1).   When Stingray starts up it will detect the presence of the ncp support and if it is present it will take full advantage of the performance improvements. In particular, don't configure PKCS#11 support on the Global Settings -> SSL Hardware support (this is used for external SSL hardware such as a PCI card or network device).   You can check Stingray is using the ncp support by running:   $ $ZEUSHOME/zxtm/bin/zeus.zxtm -vv Version 5.0, Build date: May 20 2008 22:05:47 Compiled for platform: SunOS Compression library      : zlib v1.2.3 Regex library            : PCRE v7.6 Crypto library           : Built-in PKCS#11: ncp/0 Crypto Accel Asym 1.0 Compiler                 : CC: Sun C++ 5.8 2005/10/13 XML library              : 2.6.23 XSLT library             : 10115   Note the "Crypto Library" line refers to ncp. If Stingray is reporting itself as using something else you should check that the ncp support is enabled using the cryptoadm list command.
View full article
In Stingray, each virtual server is configured to manage traffic of a particular protocol.  For example, the HTTP virtual server type expects to see HTTP traffic, and automatically applies a number of optimizations - keepalive pooling, HTTP upgrades, pipelines - and offers a set of HTTP-specific functionality (caching, compression etc).   A virtual server is bound to a specific port number (e.g. 80 for HTTP, 443 for HTTPS) and a set of IP addresses.  Although you can configure several virtual servers to listen on the same port, they must be bound to different IP addresses; you cannot have two virtual servers bound to the same IP: port pair as Stingray will not know which virtual server to route traffic to.   "But I need to use one port for several different applications!"   Sometimes, perhaps due to firewall restrictions, you can't publish services on arbitrary ports.  Perhaps you can only publish services on port 80 and 443; all other ports are judged unsafe and are firewalled off. Furthermore, it may not be possible to publish several external IP addresses.   You need to accept traffic for several different protocols on the same IP: port pair.  Each protocol needs a particular virtual server to manage it;  How can you achieve this?   The scenario   Let's imagine you are hosting several very different services:   A plain-text web application that needs an HTTP virtual server listening on port 80 A second web application listening for HTTPS traffic listening on port 443 An XML-based service load-balanced across several servers listening on port 180 SSH login to a back-end server (this is a 'server-first' protocol) listening on port 22   Clearly, you'll need four different virtual servers (one for each service), but due to firewall limitations, all traffic must be tunnelled to port 80 on a single IP address.  How can you resolve this?   The solution - version 1   The solution is relatively straightforward for the first three protocols.  They are all 'client-first' protocols (see Feature Brief: Server First, Client First and Generic Streaming Protocols), so Stingray can read the initial data written from the client.   Virtual servers to handle individual protocols   First, create three internal virtual servers, listening on unused private ports (I've added 7000 to the public ports).  Each virtual server should be configured to manage its protocol appropriately, and to forward traffic to the correct target pool of servers.  You can test each virtual server by directing your client application to the correct port (e.g. http://stingray-ip-address:7080/ ), provided that you can access the relevant port (e.g. you are behind the firewall):   For security, you can bind these virtual servers to localhost so that they can only be accessed from the Stingray device.   A public 'demultiplexing' virtual server   Create three 'loopback' pools (one for each protocol), directing traffic to localhost:7080, localhost:7180 and localhost:7443.   Create a 'public' virtual server listening on port 80 that interrogates traffic using the following rule, and then selects the appropriate pool based on the data the clients send.  The virtual server should be 'client first', meaning that it will wait for data from the client connection before triggering any rules:     # Get what data we have... $data = request.get(); # SSL/TLS record layer: # handshake(22), ProtocolVersion.major(3), ProtocolVersion.minor(0-3) if( string.regexmatch( $data, '^\026\003[\000-\003]' )) { # Looks like SSLv3 or TLS v1/2/3 pool.use( "Internal HTTPS loopback" ); } if( string.startsWithI( $data, "<xml" )) { # Looks like our XML-based protocol pool.use( "Internal XML loopback" ); } if( string.regexmatch( $data, "^(GET |POST |PUT |DELETE |OPTIONS |HEAD )" )) { # Looks like HTTP pool.use( "Internal HTTP loopback" ); } log.info( "Request: '".$data."' unrecognised!" ); connection.discard();   The Detect protocol rule is triggered once we receive client data   Now you can target all your client applications at port 80, tunnel through the firewall and demultiplex the traffic on the Stingray device.   The solution - version 2   You may have noticed that we omitted SSH from the first version of the solution.   SSH is a challenging protocol to manage in this way because it is 'server first' - the client connects and waits for the server to respond with a banner (greeting) before writing any data on the connection.  This means that we cannot use the approach above to identify the protocol type before we select a pool.   However, there's a good workaround.  We can modify the solution presented above so that it waits for client data.  If it does not receive any data within (for example) 5 seconds, then assume that the connection is the server-first SSH type.   First, create a "SSH" virtual server and pool listening on (for example) 7022 and directing traffic to your target SSH virtual server (for example, localhost:22 - the local SSH on the Stingray host):     Note that this is a 'Generic server first' virtual server type, because that's the appropriate type for SSH.   Second, create an additional 'loopback' pool named 'Internal SSH loopback' that forwards traffic to localhost:7022 (the SSH virtual server).   Thirdly, reconfigure the Port 80 listener public virtual server to be 'Generic streaming' rather than 'Generic client first'.  This means that it will run the request rule immediately on a client connection, rather than waiting for client data.   Finally, update the request rule to read the client data.  Because request.get() returns whatever is in the network buffer for client data, we spin and poll this buffer every 10 ms until we either get some data, or we timeout after 5 seconds.   # Get what data we have... $data = request.get(); $count = 500; while( $data == "" && $count-- > 0 ) { connection.sleep( 10 ); # milliseconds $data = request.get(); } if( $data == "" ) { # We've waited long enough... this must be a server-first protocol pool.use( "Internal SSH loopback" ); } # SSL/TLS record layer: # handshake(22), ProtocolVersion.major(3), ProtocolVersion.minor(0-3) if( string.regexmatch( $data, '^\026\003[\000-\003]' )) { # Looks like SSLv3 or TLS v1/2/3 pool.use( "Internal HTTPS loopback" ); } if( string.startsWithI( $data, "<xml" )) { # Looks like our XML-based protocol pool.use( "Internal XML loopback" ); } if( string.regexmatch( $data, "^(GET |POST |PUT |DELETE |OPTIONS |HEAD )" )) { # Looks like HTTP pool.use( "Internal HTTP loopback" ); } log.info( "Request: '".$data."' unrecognised!" ); connection.discard();   This solution isn't perfect (the spin and poll may incur a hit for a busy service over a slow network connection) but it's an effective solution for the single-port firewall problem and explains how to tunnel SSH over port 80 (not that you'd ever do such a thing, would you?)   Read more   Check out Feature Brief: Server First, Client First and Generic Streaming Protocols for background The WebSockets example (libWebSockets.rts: Managing WebSockets traffic with Traffic Manager) uses a similar approach to demultiplex websockets and HTTP traffic
View full article
A great feature of the Stingray Traffic Manager is the ability to upload External Program Monitors.  An External Program Monitor is a custom health monitor that can be written to monitor any service.  An External Program Monitor for LDAP is available here.   To use it first install ldapsearch onto the Stingray Traffic Manager: apt-get install ldap-utils (For Ubuntu based distros)   The key is to install ldap-utils.  Once that is installed, upload and install the monitor:   In the Stingray web interface navigate to Catalogs -> Extra Files -> Monitor Programs .  Upload ldap.pl (in the ldap.zip file) Navigate to Catalogs -> Monitors .  Scroll down to Create new monitor .  Give it a name and select External program monitor as the type. Select ldap.pl from the drop down menu that appears. Scroll down to program arguments and create four arguments: base, filter, pass, user.  It should look like the below screenshot: Fill in the fields appropriately: base is your LDAP search base, user and pass are your LDAP login credentials, and filter should be set to the CN associated with user .  For the pass field, Stingray does not automatically insert asterisks, so please be aware of that. Attach the monitor to the appropriate pool.   That completes the configuration of the LDAP Health Monitor for the Stingray Traffic Manager.   Note: If you are using the virtual appliance, then follow the instructions in this KB article instead. (edit - the file ldapmonitor.pl is attached to this article)
View full article
The following code uses Stingray's RESTful API to enable or disabled a specific Virtual Server.   The code is written in Python.  This program checks to see if the Virtual Server "test vs" is enabled and if it is, it disables it and if it is disabled, it enables it.  A GET is done to retrieve the configuration data for the Virtual Server and the "enabled" value in the "basic" properties section is checked.  This is a boolean value, so if it is true it is set to false and if it is false it is set to true. The changed data is then sent to the server using a PUT.   startstopvs.py   #! /usr/bin/env python import requests import json import sys vs = "test vs" url = 'https://stingray.example.com:9070/api/tm/1.0/config/active/vservers/' + vs; jsontype = {'content-type': 'application/json'} client = requests.Session() client.auth = ('admin', 'admin') client.verify = False # Get the config data for virtual server "test vs" try: response = client.get(url) except requests.exceptions.ConnectionError: print "Error: Unable to connect to " + url sys.exit(1) # Decode the json response. The result will be a dictionary vsConfig = json.loads(response.content) if response.status_code == 200: # Get the properties status = vsConfig['properties']['basic']['enabled']; if status: # virtual server is enabled, disable it. We only need to send the data that we are changing # so create a new dictionary with just this 'enabled'. newVSConfig = {'properties': {'basic': {'enabled': False}}} print vs + " is enabled. Disable it." else: # virtual server is disabled, enable it newVSConfig = {'properties': {'basic': {'enabled': True}}} print vs + " is disabled. Enable it." response = client.put(url, data = json.dumps(newVSConfig), headers = jsontype) vsConfig = json.loads(response.content) if response.status_code != 200: print "Error putting virtual server config: URL=%s Status=%d Id=%s: %s" %(url, response.status_code, vsConfig['error_id'], vsConfig['error_text']) else: print "Error getting virtual server config: URL=%s Status=%d Id=%s: %s" %(url, response.status_code, vsConfig['error_id'], vsConfig['error_text'])   Running the example   This code was tested with Python 2.7.3 and version 1.1.0 of the requests library.   Run the Python script as follows:   $ startstopvs.py test vs is enabled. Disable it.   Notes   This program it is sending only the 'enabled' value to the server by creating a new dictionary with just this value in the 'basic' properties section.  Alternatively, the entire Virtual Server configuration could have been returned to the server with just the enabled value changed.  Sending just the data that has changed reduces the chances of overwriting another user's changes if multiple programs are concurrently accessing the RESTful API.   Read More   The REST API Guide in the   Product Documentation Feature Brief: Traffic Manager's RESTful Control API Collected Tech Tips: Using the RESTful Control API Tech Tip: Using the RESTful Control API with Python - Overview  
View full article
The following code uses Stingray's RESTful API to list all the pools defined for a cluster and for each pool it lists the nodes defined for that pool, including draining and disabled nodes. The code is written in Python. This example builds on the previous listpools.py example.  This program does a GET request for the list of pool and then while looping through the list of pools, a GET is done for each pool to retrieve the configuration parameters for that pool.   listpoolnodes.py #! /usr/bin/env python import requests import json import sys print "Pools:\n" url = 'https://stingray.example.com:9070/api/tm/1.0/config/active/pools' jsontype = {'content-type': 'application/json'} client = requests.Session() client.auth = ('admin', 'admin') client.verify = False try: # Do the HTTP GET to get the lists of pools. We are only putting this client.get within a try # because if there is no error connecting on this one there shouldn't be an error connnecting # on later client.get so that would be an unexpected exception. response = client.get(url) except requests.exceptions.ConnectionError: print "Error: Unable to connect to " + url sys.exit(1) data = json.loads(response.content) if response.status_code == 200: if data.has_key('children'): pools = data['children'] for i, pool in enumerate(pools): poolName = pool['name'] # Do the HTTP GET to get the properties of a pool response = client.get(url + "/" + poolName) poolConfig = json.loads(response.content) if response.status_code == 200: # Since we are getting the properties for a pool we expect the first element to be 'properties' if poolConfig.has_key('properties'): # The value of the key 'properties' will be a dictionary containing property sections # All the properties that this program cares about are in the 'basic' section # nodes is the list of all active or draining nodes in this pool # draining the list of all draining nodes in this pool # disabled is the list of all disabled nodes in this pool nodes = poolConfig['properties']['basic']['nodes'] draining = poolConfig['properties']['basic']['draining'] disabled = poolConfig['properties']['basic']['disabled'] print pool['name'] print " Nodes: ", for n, node in enumerate(nodes): print node + " ", print "" if len(draining) > 0: print " Draining Nodes: ", for n, node in enumerate(draining): print node + " ", print "" if len(disabled) > 0: print " Disabled Nodes: ", for n, node in enumerate(disabled): print node + " ", print "" else: print "Error: No properties found for pool " + poolName print "" else: print "Error getting pool config: URL=%s Status=%d Id=%s: %s" %(url + "/" + poolName, response.status_code, poolConfig['error_id'], poolConfig['error_text']) else: print 'Error: No chidren found' else: print "Error getting pool list: URL=%s Status=%d Id=%s: %s" %(url, response.status_code, data['error_id'], data['error_text']) Running the example   This code was tested with Python 2.7.3 and version 1.1.0 of the requests library.   Run the Python script as follows:   $ listpoolnodes.py Pools:   Pool1     Nodes:  192.168.1.100 192.168.1.101     Draining:  192.168.1.101     Disabled:  192.168.1.102   Pool2     Nodes:  192.168.1.103 192.168.1.104   Read More   REST API Guide in the vADC Product Documentation Tech Tip: Using the RESTful Control API Collected Tech Tips: Using the RESTful Control API Tech Tip: Using the RESTful Control API with Python - Overview  
View full article
The following code uses Stingray's RESTful API to delete a pool.  The code is written in Python.  This program deletes the "pytest" pool created by the addpool.py example.  To delete a resource you do a HTTP DELETE on the URI for the resource.  If the delete is successful a 204 HTTP status code will be returned.   deletepool.py   #! /usr/bin/env python import requests import sys poolName = 'pytest' url = 'https://stingray.example.com:9070/api/tm/1.0/config/active/pools/' + poolName client = requests.Session() client.auth = ('admin', 'admin') client.verify = False try: response = client.delete(url) except requests.exceptions.ConnectionError: print "Error: Unable to connect to " + url sys.exit(1) if response.status_code == 204: print 'Pool %s deleted' %(poolName) else: if response.status_code == 404: print 'Pool %s not found' %(poolName) else: print 'Error: Status=%d URL=%s' %(response.status_code, url)   Running the example   This code was tested with Python 2.7.3 and version 1.1.0 of the requests library.   Run the Python script as follows:   $ delelepool.py Pool pytest deleted   Read More   The REST API Guide in the   Product Documentation Feature Brief: Traffic Manager's RESTful Control API Collected Tech Tips: Using the RESTful Control API Tech Tip: Using the RESTful Control API with Python - Overview    
View full article