Pages

Advertisement

Sunday, August 5, 2007

Fire and Forget Fun: RPC Pings, GET, POST and more.

I've covered the asynchronous Fire and Forget pattern several times here and now I want to show a final usage pattern.

Most blogging APIs include the RPC Ping API to ping various RPC servers that you've updated your content. However, this works for any content, not just blog posts or changes in your RSS feed. So, if you have a forums app on your site, you can use it to ask the servers to revisit for a new forum post. Same with a new article, and so on.

Manyy of these RPC ping services have directories that they update. Weblogs.com even has a "rolling update" of links and you can download xml files of the most recent ping updates. A lot of this is legitimately indexed - which can result in increased traffic to your site. Some of it is just mindlessly populated with search results and other more or less "made for Adsense" bogus content, too.
The problem that often occurs with these RPC servers -- and often with many other types of "notification" URLS, be they GET or POST - is that this is a blocking call and you don't know how long it will take to return. For that matter, it might even time out, and in either case you don't want your web pages sitting around waiting, because that creates a really lousy user experience, making it seem like it is your site that is at fault.

So it's FIRE and FORGET to the rescue, once again!  What I've done here is to wrap up four different utility methods in the Fire and Forget idiom, all in a nice self-contained class library, all static methods.

All of these methods return immediately; they are not blocking calls:

1) RequestURL - makes a Fire and Forget GET request to any url with optional "stuff" on the querystring.
2) PostToUrl  - makes a Fire and Forget HTTP FORM POST to any url with optional querystring. You supply the FORM values in a NameValueCollection.
3) DoPingOMatic - makes a call to the popular Pingomatic.com service with your title and URL. Pingomatic takes care of the rest with its list of RPC servers.
4) DoPings - makes calls to a list of known RPC servers (which you can override with your own in an appSettings section).


In the sample web project that comes with this I also illustrate how to call the Yahoo API to ping the yahoo crawler as it requires a RESTful call which uses my RequestURL method.  There is also a CHM Help file for the library included in the /doc folder for the SearchPinger project.

Some sample usage code:

// you would need all three of the below lines to ping everthing including Yahoo:
           SearchPinger.ThreadUtil.DoPingOMatic(txtTitle.Text, txtUrl.Text);
           SearchPinger.ThreadUtil.DoPings(txtTitle.Text, txtUrl.Text);
          // Yahoo'a API wants a RESTful call which is essentially an HTTP GET, so here you are:

SearchPinger.ThreadUtil.RequestUrl

("http://search.yahooapis.com/SiteExplorerService/V1/ping?sitemap="+txtUrl.Text);

     
          // Sample of a fire and forget form  post:
          NameValueCollection nvc = new NameValueCollection();
          nvc.Add("Test", "form1value_1234");
          nvc.Add("Test2", "form2value_5678");

string targetUrl =

HttpContext.Current.Request.Url.OriginalString.Replace

(HttpContext.Current.Request.Url.LocalPath, "");

          SearchPinger.ThreadUtil.PostToUrl( targetUrl+"/Receiver.aspx", nvc);

 


And here is the SearchPinger class:



using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Net;
using System.Text;
 
namespace SearchPinger
{
    /// <summary>
    /// Provides threadsafe, non-blocking methods to make httpRequests using the Fire and Forget pattern
    /// <b>Usage:</b>
    ///<example> SearchPinger.ThreadUtil.DoPingOMatic(txtTitle.Text, txtUrl.Text);</example>
    /// <example>SearchPinger.ThreadUtil.DoPings(txtTitle.Text, txtUrl.Text);</example>
    /// <example>SearchPinger.ThreadUtil.RequestUrl(txtUrl.Text);</example>
    /// </summary>
    public static class ThreadUtil
    {
        // RPC Ping specification xml template
 
        #region Delegates
 
        public delegate void PingDelegate(string title, string url, string rpcServer);
 
        public delegate void PingOMaticDelegate(string title, string url);
 
        public delegate void PostUrlDelegate(string url, NameValueCollection postData);
 
        public delegate void RequestUrlDelegate(string url);
 
        #endregion
 
        /// <summary>
        /// Callback used to call <code>EndInvoke</code> on the asynchronously
        /// invoked DelegateWrapper.
        /// </summary>
        private static AsyncCallback callback = EndWrapperInvoke;
 
        private static string pingoMatic =
            "http://pingomatic.com/ping/?title=blogname&blogurl=bloggurl&rssurl=&chk_weblogscom=on&chk_blogs=on&chk_technorati=on&chk_feedburner=on&chk_syndic8=on&chk_newsgator=on&chk_feedster=on&chk_myyahoo=on&chk_pubsubcom=on&chk_blogdigger=on&chk_blogrolling=on&chk_blogstreet=on&chk_moreover=on&chk_weblogalot=on&chk_icerocket=on&chk_newsisfree=on&chk_topicexchange=on";
 
        private static string template =
            "<?xml version=\"1.0\"?><methodCall><methodName>weblogUpdates.ping</methodName><params><param><value>blogname</value></param><param><value>blogurl</value></param></params></methodCall>";
 
        /// <summary>
        /// An instance of DelegateWrapper which calls InvokeWrappedDelegate,
        /// which in turn calls the DynamicInvoke method of the wrapped
        /// delegate.
        /// </summary>
        private static DelegateWrapper wrapperInstance = new DelegateWrapper(InvokeWrappedDelegate);
 
        /// <summary>
        /// Pings list of RPC servers, optionally loading alternate list from comma-delimited appSettings section "rpcServers"
        /// </summary>
        /// <param name="title">The title.</param>
        /// <param name="url">The URL.</param>
        public static void DoPings(string title, string url)
        {
            //http://search.yahooapis.com/SiteExplorerService/V1/ping?sitemap=http://www.yahoo.com
            string servers = ConfigurationManager.AppSettings["rpcServers"];
            if (servers == null)
                servers =
                    "http://rpc.weblogs.com/RPC2,http://blogsearch.google.com/ping/RPC2,http://api.feedster.com/ping,http://api.moreover.com/RPC2,http://api.moreover.com/ping,http://api.my.yahoo.com/RPC2,http://ping.bloggers.jp/rpc/,http://ping.feedburner.com,http://ping.syndic8.com/xmlrpc.php,http://rpc.pingomatic.com,http://rpc.technorati.com/rpc/ping,http://www.blogoon.net/ping/,http://www.blogpeople.net/servlet/weblogUpdates,http://www.newsisfree.com/xmlrpctest.php";
 
            string[] rpcServerArray = servers.Split(',');
            foreach (string s in rpcServerArray)
            {
                FireAndForget(new PingDelegate(PingIt),
                              new object[] {title, url, s});
            }
        }
 
        /// <summary>
        /// Does the ping O matic call
        /// </summary>
        /// <param name="title">The title.</param>
        /// <param name="url">The URL.</param>
        public static void DoPingOMatic(string title, string url)
        {
            FireAndForget(new PingOMaticDelegate(PingOMatic),
                          new object[] {title, url});
        }
 
        /// <summary>
        /// Requests any URL, which can include a full querystring. Returns null.
        /// </summary>
        /// <param name="url">The URL.</param>
        public static void RequestUrl(string url)
        {
            FireAndForget(new RequestUrlDelegate(RequestAUrl), new object[] {url});
        }
 
        /// <summary>
        /// Posts NameValueCollection Data  to a URL. Url can also have a Querystring
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="postData">The post data.</param>
        public static void PostToUrl(string url, NameValueCollection postData)
        {
            FireAndForget(new PostUrlDelegate(PostToAUrl),
                          new object[] {url, postData});
        }
 
 
        private static void PostToAUrl(string url, NameValueCollection postData)
        {
            WebClient myWebClient = new WebClient();
            myWebClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
            try
            {
                myWebClient.UploadValues(url, "POST", postData);
            }
            catch(Exception ex)
            {
                Debug.WriteLine("Post--" + ex.Message);
            }
            finally
            {
                myWebClient.Dispose();
            }
        }
 
 
        private static void RequestAUrl(string url)
        {
            WebClient reqWc = new WebClient();
            try
            {
             string s=   reqWc.DownloadString(url);
                System.Diagnostics.Debug.WriteLine(url + ": " + s);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("pingomatic--" + ex.Message);
            }
            finally
            {
                reqWc.Dispose();
            }
        }
 
 
        private static void PingOMatic(string title, string url)
        {
            pingoMatic = pingoMatic.Replace("blogname", title).Replace("bloggurl", url);
            WebClient pingoWc = new WebClient();
            try
            {
                string pingoString = pingoWc.DownloadString(pingoMatic);
                Debug.WriteLine("pingomatic--" + pingoString);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("pingomatic--" + ex.Message);
            }
            finally
            {
                pingoWc.Dispose();
            }
        }
 
        private static void PingIt(string title, string url, string rpcServer)
        {
            string postContent = template.Replace("blogname", title).Replace("blogurl", url);
            byte[] bytesToPost = Encoding.ASCII.GetBytes(postContent);
            WebClient wc = new WebClient();
            try
            {
                byte[] resultBytes = wc.UploadData(rpcServer, bytesToPost);
                wc.Dispose();
                string blah = Encoding.ASCII.GetString(resultBytes);
                Debug.WriteLine(rpcServer + "--" + blah);
            }
            catch (Exception ex)
            {
 
                Debug.WriteLine(rpcServer + "ERROR:  " + ex.Message);
            }
            finally
            {
                wc.Dispose();
            }
        }
 
        /// <summary>
        /// Executes the specified delegate with the specified arguments
        /// asynchronously on a thread pool thread.
        /// </summary>
        public static void FireAndForget(Delegate d, params object[] args)
        {
            // Invoke the wrapper asynchronously, which will then
            // execute the wrapped delegate synchronously (in the
            // thread pool thread)
            wrapperInstance.BeginInvoke(d, args, callback, null);
        }
 
        /// <summary>
        /// Invokes the wrapped delegate synchronously
        /// </summary>
        private static void InvokeWrappedDelegate(Delegate d, object[] args)
        {
            d.DynamicInvoke(args);
        }
 
        /// <summary>
        /// Calls EndInvoke on the wrapper and Close on the resulting WaitHandle
        /// to prevent resource leaks.
        /// </summary>
        private static void EndWrapperInvoke(IAsyncResult ar)
        {
            wrapperInstance.EndInvoke(ar);
            ar.AsyncWaitHandle.Close();
        }
 
        #region Nested type: DelegateWrapper
 
        /// <summary>    
        /// Delegate to wrap another delegate and its arguments
        /// </summary>
        private delegate void DelegateWrapper(Delegate d, object[] args);
 
        #endregion
    }
}

1 comment:

  1. Diewiplygew [url=http://wiki.openqa.org/display/~buy-zithromax-without-no-prescription-online]Buy Zithromax without no prescription online[/url] [url=http://wiki.openqa.org/display/~buy-mobic-without-no-prescription-online]Buy Mobic without no prescription online[/url]

    ReplyDelete