Showing posts with label Proxy. Show all posts
Showing posts with label Proxy. Show all posts

Thursday, December 15, 2011

How to use WPF in a web service?


Using WPF in a web service might seem bizarre, but consider the following scenarios:
  1. You need to burn in a watermark into images requested from web application,
  2. You want to apply a red-eye reduction process to photographs takes from a mobile application, or
  3. You want to apply a Jason Bourne night effect to images in a web mapping application.
We recently implemented something similar to the third scenario (see blog, source, live app). A client web application written in JavaScript, Flex or Silverlight could apply an image rendering effects by parsing an image’s url through a proxy service. The proxy service, using WPF, would apply a bitmap effect and return a modified image. This technique adds the power of WPF to thin client at the cost of minimal network latency.
Below is a detailed description of this proxy web service.
The following class is an ASP.NET generic web handler called “invert”. When published it will be accessible as ../virtualdirectory/invert.ashx. and will perform a color negative effect on parsed images. Because our server is accommodating multiple effects, one effect per handler, the web context is handed to static method to reduce the amount of redundant code.
public class Invert : IHttpHandler {
    public void ProcessRequest(HttpContext context) {
        Shader.Process(context, ShaderType.Invert);
    }
    public bool IsReusable {
        get { return false; }
    }
}
The code below is performing a number of important tasks. Firstly, because this proxy is published on the internet we need to add some restrictions so that it is only used as intended. The proxy will only entertain requests from web applications published on the maps.esri.com domain and it will only process images hosted on the services.arcgisonline.com domain. If the proxy request is legitimate, the remote image is downloaded and passed to a new background thread. The key is the creation of a background thread with a single-threaded apartment which is required to use COM and hence WPF.  The proxy (or IIS) process is put only hold as the WPF operation is underway.  The background thread returns a new image as an array of bytes which are then passed back to the client.
public class Shader {
    public static void Process(HttpContext context, ShaderType effect) {
        // Only accept request from apps from this domain!
        if (!context.Request.UrlReferrer.ToString().ToLowerInvariant().
                Contains("maps.esri.com")) {
            context.Response.StatusCode = 403;
            context.Response.End();
            return;
        }
            
        // Check for query string
        string query = Uri.UnescapeDataString(context.Request.QueryString.ToString());
        if (string.IsNullOrEmpty(query)) {
            context.Response.StatusCode = 403;
            context.Response.End();
            return;
        }
 
        // Filter requests
        if (!query.ToLowerInvariant().Contains("services.arcgisonline.com")) {
            context.Response.StatusCode = 403;
            context.Response.End();
            return;
        }
 
        // Create web request
        WebRequest webRequest = WebRequest.Create(new Uri(query));
        webRequest.Method = context.Request.HttpMethod;
 
        // Send the request to the server
        WebResponse serverResponse = null;
        try {
            serverResponse = webRequest.GetResponse();
        }
        catch (WebException webException) {
            context.Response.StatusCode = 500;
            context.Response.StatusDescription = webException.Status.ToString();
            context.Response.Write(webException.Response);
            context.Response.End();
            return;
        }
 
        // Exit if invalid response
        if (serverResponse == null) {
            context.Response.End();
            return;
        }
 
        // Configure reponse        context.Response.ContentType = serverResponse.ContentType;
        Stream stream = serverResponse.GetResponseStream();
 
        // Read response
        byte[] buffer = new byte[32768];
        int size = 0;
        int chunk;
        while ((chunk = stream.Read(buffer, size, buffer.Length - size)) > 0) {
            size += chunk;
            if (size != buffer.Length) { continue; }
            int nextByte = stream.ReadByte();
            if (nextByte == -1) { break; }
 
            // Resize the buffer
            byte[] newBuffer = new byte[buffer.Length * 2];
            Array.Copy(buffer, newBuffer, buffer.Length);
            newBuffer[size] = (byte)nextByte;
            buffer = newBuffer;
            size++;
        }
        serverResponse.Close();
        stream.Close();
 
        // Shrink buffer
        byte[] bytes = new byte[size];
        Array.Copy(buffer, bytes, size);
 
        // If the request is not a JPEG image then return data
        if (!serverResponse.ContentType.ToLowerInvariant().Contains("image/jpg")) {
            context.Response.OutputStream.Write(bytes, 0, bytes.Length);
            context.Response.End();
            return;
        }
 
        // Create class to send to STA backgroung thread
        ShaderWorker work = new ShaderWorker() {
            Source = bytes,
            ShaderEffect = effect
        };
 
        // Execute background thread
        Thread worker = new Thread(new ThreadStart(work.Execute));
        worker.SetApartmentState(ApartmentState.STA);
        worker.Name = "CreateImageWorker";
        worker.Start();
        worker.Join();
 
        // Return processed stream
        context.Response.OutputStream.Write(work.Target, 0, work.Target.Length);
        context.Response.End();
    }
}
This enumerator lists available pixel shaders. These shaders (or “effects”) were downloaded from the Windows Presentation Foundation Pixel Shader Effects Library.
public enum ShaderType {
    Emboss,
    Invert,
    Monochrome,
    Mosaic,
    Pixelate,
    Tint
}
And lastly, this is were the magic happens. This class is executed from the main IIS thread as a single-apartment thread as explained above. The source image is loaded as a generic BitmapImage in an Image, rendered with an effect and then exported to a new byte array.
public class ShaderWorker {
    public ShaderType ShaderEffect { private get; set; }
    public byte[] Source { private get; set; }
    public byte[] Target { get; private set; }
    public void Execute() {
        // Create bitmapimage
        MemoryStream m = new MemoryStream(this.Source);
        BitmapImage b = new BitmapImage();
        b.BeginInit();
        b.DecodePixelWidth = 256;
        b.DecodePixelHeight = 256;
        b.StreamSource = m;
        b.EndInit();
        b.Freeze();
 
        // Create image element
        Image i = new Image() {
            Source = b
        };
 
        // Assign shade effect
        switch (this.ShaderEffect) {
            case ShaderType.Emboss:
                i.Effect = new EmbossedEffect();
                break;
            case ShaderType.Invert:
                i.Effect = new InvertEffect();
                break;
            case ShaderType.Monochrome:
                i.Effect = new MonochromeEffect();
                break;
            case ShaderType.Mosaic:
                i.Effect = new MosaicEffect();
                break;
            case ShaderType.Pixelate:
                i.Effect = new PixelateEffect();
                break;
            case ShaderType.Tint:
                i.Effect = new TintEffect();
                break;
        }
 
        // Assign element size        i.Arrange(
            new System.Windows.Rect(
                new System.Windows.Size(256, 256)
            )
        );
        i.UpdateLayout();
 
        // Render image element to a new bitmap
        RenderTargetBitmap r = new RenderTargetBitmap(256, 256, 96, 96,
                                                      PixelFormats.Default);
        r.Render(i);
 
        // Create a new memory stream
        MemoryStream memoryStream = new MemoryStream();
 
        // Export bitmap to the stream
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(r));
        encoder.Save(memoryStream);
 
        // Export the stream to a byte array
        byte[] bytes = memoryStream.ToArray();
        memoryStream.Close();
 
        // Shutdown rendering thread (if alive)
        if (r.Dispatcher.Thread.IsAlive) {
            r.Dispatcher.InvokeShutdown();
        }
 
        // Store processed image (as a byte array)
        this.Target = bytes;
    }
}
In summary, this post describes the technique of using WPF in a web service to perform advanced rendering. In theory, this technique is not restricted to image processing but could also be used to generate graphics of charts, diagrams and even three dimensional scenes. During limited testing it is obvious that this technique introduces network latency, particularly when the service has been dormant. And what is still unknown is whether the rendering is performed in hardware (i.e. GPU) or software (i.e. CPU). Please comment below if you know the answer to this or know of a definitive test for GPU vs. CPU.

Wednesday, August 24, 2011

How to create a simple proxy in ASP.NET

A common obstruction when developing Silverlight web applications is cross domain access. Silverlight has built-in security that prevents communication to servers that do not explicitly granted access using with either a client access policy or cross domain file. Even though a server has services intended for public use, if the server does not have a cross domain file then Silverlight apps will be denied access.

This post presents a convenient and legitimate way to overcome this restriction with a simple proxy service in three easy steps!

Step 1 – In Visual Studio 2010, create a new ASP.NET Web Application project called WebApplication1

How to create a simple proxy in ASP.NET

Step 2 – Add a new generic handler called proxy.ashx

How to create a simple proxy in ASP.NET

Step 3 – Copy and paste this code into proxy.ashx.cs.

using System;
using System.IO;
using System.Net;
using
System.Web;

namespace
WebApplication1 {
   
public class proxy : IHttpHandler
{
       
public void ProcessRequest(HttpContext
context) {
           
HttpResponse
response = context.Response;

           
// Check for query string
            string uri = Uri
.UnescapeDataString(context.Request.QueryString.ToString());
           
if (string
.IsNullOrWhiteSpace(uri)) {
                response.StatusCode = 403;
                response.End();
               
return
;
            }

           
// Filter requests
            if (!uri.ToLowerInvariant().Contains("
wikimedia.org")) {
                response.StatusCode = 403;
                response.End();
               
return
;
            }

           
// Create web request
            WebRequest webRequest = WebRequest.Create(new Uri
(uri));
            webRequest.Method = context.Request.HttpMethod;

           
// Send the request to the server
            WebResponse serverResponse = null
;
           
try
{
                serverResponse = webRequest.GetResponse();
            }
           
catch (WebException
webExc) {
                response.StatusCode = 500;
                response.StatusDescription = webExc.Status.ToString();
                response.Write(webExc.Response);
                response.End();
               
return
;
            }

           
// Exit if invalid response
            if (serverResponse == null
) {
                response.End();
               
return
;
            }

           
// Configure reponse
            response.ContentType = serverResponse.ContentType;
           
Stream
stream = serverResponse.GetResponseStream();

           
byte[] buffer = new byte
[32768];
           
int
read = 0;

           
int
chunk;
           
while
((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0) {
                read += chunk;
               
if (read != buffer.Length) { continue
; }
               
int
nextByte = stream.ReadByte();
               
if (nextByte == -1) { break
; }

               
// Resize the buffer
                byte[] newBuffer = new byte
[buffer.Length * 2];
               
Array
.Copy(buffer, newBuffer, buffer.Length);
                newBuffer[read] = (
byte
)nextByte;
                buffer = newBuffer;
                read++;
            }

           
// Buffer is now too big. Shrink it.
            byte[] ret = new byte
[read];
           
Array
.Copy(buffer, ret, read);

            response.OutputStream.Write(ret, 0, ret.Length);
            serverResponse.Close();
            stream.Close();
            response.End();
        }
       
public bool
IsReusable {
           
get { return false; }
        }
    }
}

You are done! To test, press F5 to run the web application in debug mode. Your default web browser will display the Default.aspx page from your project.

How to create a simple proxy in ASP.NET

Without closing the web browser, append the name of the proxy service and the web resource (highlighted below) that you need access to. For example:
http://localhost:51220/proxy.ashx?http://upload.wikimedia.org/wikipedia/en/f/f0/New-esri-logo.jpg

This is the result.

How to create a simple proxy in ASP.NET

In summary, this post described how to crate a simple proxy web service. The proxy can be used by Silverlight web application to access resources that are restricted due to a missing cross domain file. To prevent malicious use of the proxy it is advisable to add some sort of access restriction, for example, in this exercise the proxy was configured to only accept requests for content from the wikimedia.org domain.