cancel
Showing results for 
Search instead for 
Did you mean: 

Watermarking Images with Java Extensions

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:

 

javajars.pngDownload 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:

 

eclipsecreate.png

  • 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, paste 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:

Screen Shot 2013-03-19 at 17.21.12.pngNote 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

Version history
Revision #:
2 of 2
Last update:
‎01-12-2021 05:22:PM
Updated by:
 
Labels (1)
Contributors